diff --git a/web/src/features/channels/components/channels-columns.tsx b/web/src/features/channels/components/channels-columns.tsx
index 8d91485c..36dc8f67 100644
--- a/web/src/features/channels/components/channels-columns.tsx
+++ b/web/src/features/channels/components/channels-columns.tsx
@@ -71,7 +71,7 @@ import {
handleUpdateChannelField,
handleUpdateTagField,
handleUpdateChannelBalance,
- createChannelPriorityUpdateScheduler,
+ createChannelFieldUpdateScheduler,
isTagAggregateRow,
type TagRow,
} from '../lib'
@@ -177,7 +177,14 @@ function PriorityCell({ channel }: { channel: Channel }) {
return
}
- return
+ return (
+
+ )
}
function TagPriorityCell({ channel }: { channel: TagRow }) {
@@ -219,32 +226,34 @@ function TagPriorityCell({ channel }: { channel: TagRow }) {
)
}
-function ChannelPriorityCell({ channel }: { channel: Channel }) {
+function ChannelFieldCell({
+ channelId,
+ value,
+ field,
+ min,
+}: {
+ channelId: number
+ value: number | null | undefined
+ field: 'priority' | 'weight'
+ min: number
+}) {
const queryClient = useQueryClient()
- const priorityUpdateScheduler = useMemo(
+ const fieldUpdateScheduler = useMemo(
() =>
- createChannelPriorityUpdateScheduler((value) => {
- void handleUpdateChannelField(
- channel.id,
- 'priority',
- value,
- queryClient
- )
+ createChannelFieldUpdateScheduler((nextValue) => {
+ void handleUpdateChannelField(channelId, field, nextValue, queryClient)
}),
- [channel.id, queryClient]
+ [channelId, field, queryClient]
)
- useEffect(
- () => () => priorityUpdateScheduler.flush(),
- [priorityUpdateScheduler]
- )
+ useEffect(() => () => fieldUpdateScheduler.flush(), [fieldUpdateScheduler])
return (
)
}
@@ -253,57 +262,56 @@ function ChannelPriorityCell({ channel }: { channel: Channel }) {
* Weight cell component with inline editing
*/
function WeightCell({ channel }: { channel: Channel }) {
+ if (isTagAggregateRow(channel)) {
+ return
+ }
+
+ return (
+
+ )
+}
+
+function TagWeightCell({ channel }: { channel: TagRow }) {
const { t } = useTranslation()
const queryClient = useQueryClient()
- const isTagRow = isTagAggregateRow(channel)
const weight = channel.weight
const [confirmOpen, setConfirmOpen] = useState(false)
const [pendingValue, setPendingValue] = useState(null)
+ const tag = channel.tag || ''
+ const channelCount = channel.children?.length || 0
- // Tag row - editable with confirmation for all tag channels
- if (isTagRow) {
- const tag = channel.tag || ''
- const channelCount = channel.children?.length || 0
-
- return (
- <>
- {
- setPendingValue(value)
- setConfirmOpen(true)
- }}
- min={0}
- />
- {
- if (pendingValue !== null) {
- handleUpdateTagField(tag, 'weight', pendingValue, queryClient)
- }
- setConfirmOpen(false)
- }}
- />
- >
- )
- }
-
- // Regular channel row - editable
return (
- {
- handleUpdateChannelField(channel.id, 'weight', value, queryClient)
- }}
- min={0}
- />
+ <>
+ {
+ setPendingValue(value)
+ setConfirmOpen(true)
+ }}
+ min={0}
+ />
+ {
+ if (pendingValue !== null) {
+ handleUpdateTagField(tag, 'weight', pendingValue, queryClient)
+ }
+ setConfirmOpen(false)
+ }}
+ />
+ >
)
}
diff --git a/web/src/features/channels/components/numeric-spinner-input.tsx b/web/src/features/channels/components/numeric-spinner-input.tsx
index cd53f45a..6890d3f3 100644
--- a/web/src/features/channels/components/numeric-spinner-input.tsx
+++ b/web/src/features/channels/components/numeric-spinner-input.tsx
@@ -122,8 +122,10 @@ export function NumericSpinnerInput({
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault()
- commitValue()
- onCommit?.()
+ // Blurring routes Enter through the same focusout path as clicking
+ // away (input onBlur -> commitValue, container onBlur -> onCommit),
+ // so commit and onCommit each fire exactly once.
+ inputRef.current?.blur()
} else if (e.key === 'Escape') {
setEditing(false)
setLocalValue(String(value ?? 0))
diff --git a/web/src/features/channels/lib/channel-priority-update.test.ts b/web/src/features/channels/lib/__tests__/channel-field-update.test.ts
similarity index 85%
rename from web/src/features/channels/lib/channel-priority-update.test.ts
rename to web/src/features/channels/lib/__tests__/channel-field-update.test.ts
index f77efd7c..d984f656 100644
--- a/web/src/features/channels/lib/channel-priority-update.test.ts
+++ b/web/src/features/channels/lib/__tests__/channel-field-update.test.ts
@@ -20,9 +20,9 @@ import assert from 'node:assert/strict'
import { describe, test } from 'node:test'
import {
- CHANNEL_PRIORITY_UPDATE_DELAY_MS,
- createChannelPriorityUpdateScheduler,
-} from './channel-priority-update'
+ CHANNEL_FIELD_UPDATE_DELAY_MS,
+ createChannelFieldUpdateScheduler,
+} from '../channel-field-update'
function createFakeTimers() {
const pending = new Map void>()
@@ -31,7 +31,7 @@ function createFakeTimers() {
return {
timers: {
setTimeout: (callback: () => void, delay: number) => {
- assert.equal(delay, CHANNEL_PRIORITY_UPDATE_DELAY_MS)
+ assert.equal(delay, CHANNEL_FIELD_UPDATE_DELAY_MS)
const id = nextId++
pending.set(id, callback)
return id
@@ -51,11 +51,11 @@ function createFakeTimers() {
}
}
-describe('channel priority update scheduler', () => {
+describe('channel field update scheduler', () => {
test('coalesces rapid schedules into one update with the latest value', () => {
const fake = createFakeTimers()
const updates: number[] = []
- const scheduler = createChannelPriorityUpdateScheduler(
+ const scheduler = createChannelFieldUpdateScheduler(
(value) => updates.push(value),
fake.timers
)
@@ -73,7 +73,7 @@ describe('channel priority update scheduler', () => {
test('flush commits the pending value immediately and cancels the timer', () => {
const fake = createFakeTimers()
const updates: number[] = []
- const scheduler = createChannelPriorityUpdateScheduler(
+ const scheduler = createChannelFieldUpdateScheduler(
(value) => updates.push(value),
fake.timers
)
@@ -90,7 +90,7 @@ describe('channel priority update scheduler', () => {
test('flush without a pending value does nothing', () => {
const fake = createFakeTimers()
const updates: number[] = []
- const scheduler = createChannelPriorityUpdateScheduler(
+ const scheduler = createChannelFieldUpdateScheduler(
(value) => updates.push(value),
fake.timers
)
@@ -105,7 +105,7 @@ describe('channel priority update scheduler', () => {
test('preserves a pending value of 0', () => {
const fake = createFakeTimers()
const updates: number[] = []
- const scheduler = createChannelPriorityUpdateScheduler(
+ const scheduler = createChannelFieldUpdateScheduler(
(value) => updates.push(value),
fake.timers
)
diff --git a/web/src/features/channels/lib/channel-priority-update.ts b/web/src/features/channels/lib/channel-field-update.ts
similarity index 84%
rename from web/src/features/channels/lib/channel-priority-update.ts
rename to web/src/features/channels/lib/channel-field-update.ts
index 746de26b..c93df937 100644
--- a/web/src/features/channels/lib/channel-priority-update.ts
+++ b/web/src/features/channels/lib/channel-field-update.ts
@@ -17,21 +17,21 @@ along with this program. If not, see .
For commercial licensing, please contact support@quantumnous.com
*/
-export const CHANNEL_PRIORITY_UPDATE_DELAY_MS = 800
+export const CHANNEL_FIELD_UPDATE_DELAY_MS = 800
-interface ChannelPriorityUpdateTimers {
+interface ChannelFieldUpdateTimers {
setTimeout: (callback: () => void, delay: number) => number
clearTimeout: (id: number) => void
}
-const browserTimers: ChannelPriorityUpdateTimers = {
+const browserTimers: ChannelFieldUpdateTimers = {
setTimeout: (callback, delay) => window.setTimeout(callback, delay),
clearTimeout: (id) => window.clearTimeout(id),
}
-export function createChannelPriorityUpdateScheduler(
+export function createChannelFieldUpdateScheduler(
onUpdate: (value: number) => void,
- timers: ChannelPriorityUpdateTimers = browserTimers
+ timers: ChannelFieldUpdateTimers = browserTimers
) {
let timeoutId: number | undefined
let pendingValue: number | undefined
@@ -57,7 +57,7 @@ export function createChannelPriorityUpdateScheduler(
pendingValue = value
timeoutId = timers.setTimeout(
commitPendingValue,
- CHANNEL_PRIORITY_UPDATE_DELAY_MS
+ CHANNEL_FIELD_UPDATE_DELAY_MS
)
},
flush: commitPendingValue,
diff --git a/web/src/features/channels/lib/index.ts b/web/src/features/channels/lib/index.ts
index 71059a81..43eb7773 100644
--- a/web/src/features/channels/lib/index.ts
+++ b/web/src/features/channels/lib/index.ts
@@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
*/
// Re-export all library functions
export * from './channel-actions'
-export * from './channel-priority-update'
+export * from './channel-field-update'
export * from './advanced-custom'
export * from './channel-form-errors'
export * from './channel-form'