mobile: refresh label counts when a local note write changes them - #936
Conversation
The drawer's per-label counts are derived from the notes table, not the
labels one: getLocalLabelCounts counts each label across the active notes
(archived and trashed excluded). A note write that touches no label still
changes them by moving a note in or out of that set — but nothing in
useNotes.ts ever invalidated labelCountsQueryKey, so archiving, trashing,
restoring, duplicating, or creating a note with labels left the drawer
showing the pre-write number.
useLabels.ts already invalidates the counts for every membership edit, and
the SSE handler does it for every remote note event, so this was the local
write path missing a rule the rest of the app follows. Recovery was an
isConnected flip, an SSE reconnect resync, or an app restart; the drawer
stays mounted, so merely opening it did not refresh anything.
Verified against a real SQLite test database: after archiving a labelled
note the rows said {} while the drawer still served {"l1": 1}.
Invalidate the counts from each path that moves a note in or out of the
counted set. The invalidation re-reads local SQLite rather than the
server, so it is gated where the trigger is narrow: only an archive flag
on an update (a debounced title/content save must not re-scan every note's
labels), and only a create or duplicate that actually carries labels.
Permanent delete and empty-trash need nothing — a trashed note was already
uncounted.
The import path needed more than an invalidation. It pulled only notes
after a Keep import, and the drawer's label list reads the local labels
table, which the import never wrote — so invalidating that query would
have re-served the pre-import rows. Pull labels alongside notes, then
refresh all four SQLite-derived caches.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FoZMaSuY8Qia39VMiAW44T
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. WalkthroughThe mobile note mutations now invalidate label-count queries when labelled notes change active status or when labelled notes are created or duplicated. Sync failure resolution also invalidates label counts. Settings import now fetches and saves notes and labels, then invalidates related queries. Tests cover import behavior and label-count changes for note mutations. Poem
Merge Risk: ⚪ Minimal · up to This change refreshes local label counts after note writes and updates import cache refreshes; no actionable merge-blocking risk remains beyond normal checks and review. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Found while auditing for siblings of the stale-dashboard bug in #935 — same category, different derived cache. Independent of that PR; either can land first.
The bug
The drawer's per-label counts are derived from the notes table, not the labels one:
So a note write that touches no label still changes them, by moving a note in or out of that counted set. But nothing in
useNotes.tsever invalidatedlabelCountsQueryKey— whileuseLabels.tsinvalidates it for every membership edit, and the SSE handler invalidates it for every remote note event. This was the local-write path missing a rule the rest of the app follows.Result: archiving, trashing, restoring, or duplicating a labelled note left the drawer showing the pre-write number. Recovery was an
isConnectedflip, an SSE reconnect resync, or an app restart — and since the drawer stays mounted for the life of the navigator, merely opening it refreshes nothing.Verified against a real SQLite test database before writing any fix. SQLite was already correct; only the cache was behind:
The fix
useUpdateNoteuseDeleteNoteuseRestoreNoteuseDuplicateNoteuseCreateNoteuseSyncFailuresThe invalidation re-reads local SQLite rather than the server, so it is gated where the trigger is narrow: only an
archivedflag on an update (a debounced title/content save must not re-scan every note's labels), and only a create or duplicate that actually carries labels. There's a test asserting the content-save case leaves the counts untouched.usePermanentDeleteNoteand empty-trash need nothing — a trashed note was already uncounted.Import needed more than an invalidation
ImportSectionpulled only notes after a Keep import and invalidated only the dashboard. Adding alabelsQueryKeyinvalidation alone would have been theatre: that query'squeryFnreads the locallabelstable, which the import never writes, so the re-read would just re-serve the pre-import rows. It now pulls labels alongside notes and then refreshes all four SQLite-derived caches.Tests
mobile/__tests__/labelCountInvalidation.test.tsx— five cases against a real migrated SQLite database (archive, trash + restore, duplicate, create-with-labels, and the negative case). The four behavioural ones were confirmed failing on master and passing with the fix; the negative one passes both ways by design.SettingsScreen-import.test.tsxgained assertions for the label pull and the four invalidations. Doing so surfaced asaveLabels is not a functionthat the screen's owntry/catchwas swallowing — the suite'snoteQueriesmock was incomplete, so the test would have passed while the save silently failed. Fixed in the mocks.task checkpasses (lint + 1399 tests + docs/migration/translation gates).Not in this diff
useOfflineNotes' list sync has the mirror asymmetry (invalidates the list scope, not the note scope). It cannot strand the editor the way mobile: refresh the notes list when a single-note sync writes to SQLite #935's gap stranded the dashboard: the editor fetches its note on mount, and both hooks subscribe to the reconnect-resync bus while it is open.useCreateNote's offline branch hardcodeslabels: [], ignoringdata.labels, so an offline "Keep my version" fork shows unlabelled locally until its queued create drains and a sync brings the server's copy back. Different category — data fidelity, not cache invalidation — and it self-heals. Fixing it means resolving label names to localLabelrows, which is real work rather than a one-liner.Artifacts
No screenshot: the visible change is a number in the drawer moving when it previously didn't, which a still frame can't show any better than the test output above. No API-breaking changes; server untouched.
Generated by Claude Code