refactor(ux): address /code-review high findings on the fixpack
- Extract the optimistic + debounced-flush feed-priority flow into a shared useChannelPriority hook (mirrors useChannelTagActions); the manager and the channel-detail page now share it instead of two drifting copies. - NavSidebar: drop the always-true `slim` const and its dead expanded-rail branches; refresh the now-stale pinned/collapsible comments. - Remove the orphaned `navCollapsed` localStorage key (no readers/writers left after the always-slim rail). - Channel page: drop the wrapper native `title` that duplicated PriorityStepper's own Tooltip. The keyboard-focus nav-label finding needed no change: the popover platform already wires useFocus (visibleOnly), so a real Tab press shows the flyout label (verified in-browser).
This commit is contained in:
@@ -28,6 +28,7 @@ import ChannelLayoutGrid from "./ChannelLayoutGrid";
|
||||
import { LoadingState, StateMessage } from "./QueryState";
|
||||
import { ChannelTagsControl } from "./ChannelTags";
|
||||
import { useChannelTagActions } from "../lib/useChannelTagActions";
|
||||
import { useChannelPriority } from "../lib/useChannelPriority";
|
||||
import ViewSwitcher, { type ViewOption } from "./ViewSwitcher";
|
||||
import ColumnSortControl from "./ColumnSortControl";
|
||||
import Pager from "./Pager";
|
||||
@@ -143,56 +144,19 @@ export default function Channels() {
|
||||
qc.invalidateQueries({ queryKey: qk.feedCount() });
|
||||
},
|
||||
});
|
||||
// Priority is updated optimistically in place and NOT re-sorted/refetched, so the list
|
||||
// doesn't jump (the server list is priority-sorted; refetching would reorder rows and
|
||||
// lose your scroll position). The new order takes effect next time the page loads.
|
||||
// Press-and-hold fires many bumps, so the PATCH is DEBOUNCED per channel: each bump reads the
|
||||
// accumulated value from the cache, and the server call flushes once ~400ms after the last step
|
||||
// (instead of one request per notch). Pending flushes are fired on unmount so a last-moment bump
|
||||
// isn't lost.
|
||||
const prioFlush = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
||||
const flushPriority = (id: string) => {
|
||||
const latest = qc
|
||||
.getQueryData<ManagedChannel[]>(qk.channels())
|
||||
?.find((c) => c.id === id)?.priority;
|
||||
if (latest == null) return;
|
||||
api
|
||||
.updateChannel(id, { priority: latest })
|
||||
.catch(() => qc.invalidateQueries({ queryKey: qk.channels() }));
|
||||
};
|
||||
// Set an absolute priority (used by both the arrows — via bumpPriority — and the inline edit),
|
||||
// optimistically + a debounced flush.
|
||||
const setPriority = (id: string, next: number) => {
|
||||
qc.setQueryData<ManagedChannel[]>(qk.channels(), (old) =>
|
||||
(old ?? []).map((ch) => (ch.id === id ? { ...ch, priority: next } : ch)),
|
||||
);
|
||||
const timers = prioFlush.current;
|
||||
const pending = timers.get(id);
|
||||
if (pending) clearTimeout(pending);
|
||||
timers.set(
|
||||
id,
|
||||
setTimeout(() => {
|
||||
timers.delete(id);
|
||||
flushPriority(id);
|
||||
}, 400),
|
||||
);
|
||||
};
|
||||
const bumpPriority = (id: string, delta: number) => {
|
||||
const cur =
|
||||
qc.getQueryData<ManagedChannel[]>(qk.channels())?.find((c) => c.id === id)?.priority ?? 0;
|
||||
setPriority(id, cur + delta);
|
||||
};
|
||||
useEffect(() => {
|
||||
const timers = prioFlush.current;
|
||||
return () => {
|
||||
timers.forEach((timer, id) => {
|
||||
clearTimeout(timer);
|
||||
flushPriority(id);
|
||||
});
|
||||
timers.clear();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
// Priority is updated optimistically in place and NOT re-sorted/refetched, so the list doesn't
|
||||
// jump (the server list is priority-sorted; refetching would reorder rows and lose your scroll
|
||||
// position). The new order takes effect next time the page loads. Press-and-hold is debounced +
|
||||
// flushed on unmount by the shared hook (same flow the channel detail page uses).
|
||||
const { setPriority, bumpPriority } = useChannelPriority({
|
||||
read: (id) =>
|
||||
qc.getQueryData<ManagedChannel[]>(qk.channels())?.find((c) => c.id === id)?.priority,
|
||||
optimistic: (id, next) =>
|
||||
qc.setQueryData<ManagedChannel[]>(qk.channels(), (old) =>
|
||||
(old ?? []).map((ch) => (ch.id === id ? { ...ch, priority: next } : ch)),
|
||||
),
|
||||
onError: () => qc.invalidateQueries({ queryKey: qk.channels() }),
|
||||
});
|
||||
|
||||
// Tagging is updated optimistically in place (no refetch of the whole channel list, which
|
||||
// made the toggle feel ~2s slow); only revert by refetching if the server call fails. The
|
||||
|
||||
Reference in New Issue
Block a user