When the same note is open in multiple browsers, changes made in one
browser are now detected and surfaced in the other.
Server:
- itemService.noteFreshnessByUserIdAndJopId: cheap single-row probe
returning {updatedTime, deletedTime} without decoding the note body
- GET /api/web/notes/:id/freshness: serves the probe, marked
isHeartbeat:true so polling does not reset the session idle timer
- PUT /fragments/editor/:id conflict response now sends X-Note-Conflict:1
header so the client can distinguish it from a real save success
Client:
- checkNoteFreshness() runs on every 30s connectivity ping tick and
immediately on visibilitychange (tab focus)
- Clean editor: silently reloads the note fragment
- Dirty editor: shows a banner (same pattern as search-nav-bar) with
'Use server version' and 'Overwrite with mine' buttons
- Deleted note: shows a 'deleted in another window' banner
- _activeEditorIsDirty() uses durable signals (_previewDirty,
_pvSyncTimer, title contenteditable vs hidden input, formHash vs
_savedHash) not the transient autosave-status text
- Banner is server-rendered inside the form (hidden attribute), reuses
search-nav-bar CSS and buttons — no dynamic DOM creation
- htmx:afterRequest skips snapshotHash/setSaved when X-Note-Conflict:1
is present, keeping the editor dirty and the conflict UI visible
- Service worker bumped to v25
- Add DB index on (owner_id, jop_type, jop_parent_id, jop_updated_time DESC)
- New folderNoteCountsByUserId query: GROUP BY folder, no row data fetched
- New noteHeadersByFolder query: paginated per folder (LIMIT 100)
- navData() now fetches only folders + counts (no note rows on page load)
- navigationFragment: lazy mode renders empty note lists, lazy-loaded on expand
- toggleNavFolder / initNavPanel: htmx fetch on first expand, data-loaded guard
- Add /fragments/folder-notes endpoint for lazy per-folder note pages
- Add folderNotesPageFragment with Load more button for pagination
- Mobile: folders use counts Map, notes use paginated noteHeadersByFolder
- Tests: 129/129 passing