From 6d5be523abd5e4508e01eedcf9b3d1d8fd3de832 Mon Sep 17 00:00:00 2001 From: Lukas Date: Thu, 23 Apr 2026 09:41:49 +0200 Subject: [PATCH] fix: KVEditor effect dependency uses stable JSON serialization Replace the flawed join-based comparison (entryKeys.join(',')) with JSON.stringify() to properly handle keys containing commas. The previous implementation could produce false equality when different key sets shared the same comma-separated representation (e.g., 'a,b' key vs separate 'a' and 'b' keys). This ensures the effect fires correctly when keys change, fixing silent failures to update derived state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Docs/Tasks.md | 20 ---------------- frontend/src/components/config/KVEditor.tsx | 4 ++-- .../config/__tests__/KVEditor.test.tsx | 23 +++++++++++++++++++ 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/Docs/Tasks.md b/Docs/Tasks.md index 3c8bc97..444903b 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -1,23 +1,3 @@ -### TASK-PERF-02 — `useSchedule` Exposes No `refresh` Function - -**Where found** -`frontend/src/hooks/useSchedule.ts`. The hook fetches the import schedule on mount and exposes no way to re-fetch. After a `PUT /api/blocklists/schedule` from a different component or tab, the displayed schedule data stays stale until the user navigates away and back. - -**Goal** -Expose a `refresh` callback from `useSchedule`, following the same pattern as `useListData` and other hooks. The `BlocklistsPage` (or whichever component saves schedule changes) should call `refresh()` after a successful save, and after `runImportNow()` completes. - -**Possible traps and issues** -- `useSchedule` currently uses a simple `useEffect` on mount. Adding `refresh` means converting the internal fetch into a `useCallback` and calling it from the effect. -- Ensure the `AbortController` pattern is applied correctly when adding `refresh`. - -**Docs changes needed** -None required. - -**Why this is needed** -After saving a new schedule the user sees the old schedule until they reload the page. This makes the save feel broken even when it succeeded. - ---- - ### TASK-QUALITY-01 — `KVEditor` Uses `entryKeys.join(",")` as Effect Dependency **Where found** diff --git a/frontend/src/components/config/KVEditor.tsx b/frontend/src/components/config/KVEditor.tsx index 8870a33..c372515 100644 --- a/frontend/src/components/config/KVEditor.tsx +++ b/frontend/src/components/config/KVEditor.tsx @@ -12,7 +12,7 @@ export function KVEditor({ entries, onChange }: KVEditorProps): React.JSX.Elemen const styles = useConfigStyles(); const rows = useMemo(() => Object.entries(entries), [entries]); const entryKeys = useMemo(() => Object.keys(entries), [entries]); - const entryKeyList = entryKeys.join(","); + const entryKeysJson = useMemo(() => JSON.stringify(entryKeys), [entryKeys]); const [editedKeys, setEditedKeys] = useState>( Object.fromEntries(rows.map(([key]) => [key, key])), ); @@ -27,7 +27,7 @@ export function KVEditor({ entries, onChange }: KVEditorProps): React.JSX.Elemen .map((key) => [key, previousErrors[key] ?? ""]), ), ); - }, [entryKeyList, rows, entryKeys]); + }, [entryKeysJson, rows, entryKeys]); const validateKey = (oldKey: string, newKey: string): string | null => { const trimmedKey = newKey.trim(); diff --git a/frontend/src/components/config/__tests__/KVEditor.test.tsx b/frontend/src/components/config/__tests__/KVEditor.test.tsx index ee7b9f0..8793c9f 100644 --- a/frontend/src/components/config/__tests__/KVEditor.test.tsx +++ b/frontend/src/components/config/__tests__/KVEditor.test.tsx @@ -34,4 +34,27 @@ describe("KVEditor", () => { expect(handleChange).toHaveBeenCalledWith({ primary: "1", second: "2" }); }); + + it("correctly distinguishes keys with commas from separate keys", () => { + const handleChange = vi.fn(); + const { rerender } = render( + + + , + ); + + expect(screen.getByLabelText(/Setting name: a,b/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/Setting name: c/i)).toBeInTheDocument(); + + // Verify that changing to separate keys is recognized as a different state + rerender( + + + , + ); + + expect(screen.getByLabelText(/Setting name: a/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/Setting name: b/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/Setting name: c/i)).toBeInTheDocument(); + }); });