- add app/proxy/vaultProxyGuard.js: inspects proxied note writes and
deletes before forwarding to upstream Joplin Server
- covers single PUT /api/items/root:/<id>.md:/content, batch PUT
/api/batch_items, single DELETE, and batch DELETE
- rejects with 403 when a vault note body lacks the encrypted marker,
or when a vault note is deleted via the sync proxy
- bodies over 10 MB stream through without inspection (resource blobs)
- unauthenticated requests stream through (upstream handles 401)
- wire guard into createServer.js proxy entry point; replay buffered
body via Readable.from() on allow
- 34 new unit tests, all 394 tests passing
Use the shared title sanitizer for both SSR and client-side title editing
so note titles are cleaned by one function. Also sanitize note titles on
create in the fragments route and add a regression test for formatted
titles.
- Install markdown-it@14.1.1 as server-side renderer (renderMarkdown only)
- Preserve all Joplin extensions: underline (++..++), checkboxes,
blank-line markers, softbreak→<br>, resource URIs, spellcheck attrs,
hx-* strip, fence/code/image/link render overrides
- Fix blank-line round-trip: emptyDiv/emptyP Turndown rules now return
the BL sentinel instead of '<br>' (which line 611 inflated to 4 newlines)
or '' (which made blank-line edits never save)
- Disable CSS scroll anchoring in preview editor so images flow down
naturally when text is typed above them
- Narrow the Notebook + button label and padding in the nav header
- Bump static asset version to 20260501a
Custom regex renderer's fence extraction required column-0 anchoring,
so any fence inside a list item (or with leading whitespace) was missed
and the loose backticks were mangled by the inline-code regex. Add a
list-item-nested fence pass that outdents the body and preserves the
surrounding list structure as a placeholder, plus relax the column-0
fence regex to allow up to 3 spaces of indentation per CommonMark and
trailing whitespace on the closing fence.
Eliminates fragile ad-hoc DOM toggling that allowed two mobile screens to
render simultaneously. All transitions now go through setMobileState()
reducer; renderMobile() is the only function that writes
.mobile-screen-active. assertSingleActiveScreen() self-heals violations
and traces them. Test asserts the architectural invariant in source.
Also: DB session lookup fails closed on transient errors instead of
crashing; mobile back-save uses formHash (not UI badge); single-screen
CSS via display:none/flex (no transforms); resize debounced without
reload; aria-hidden warning fix.
- 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