- Add highlightActiveLine bool setting (default true) wired through
settingsService → api allowedKeys → settings UI → _joplockConfig → app.js
- Fix CM theme fontSize to use var(--font-size-note) instead of hardcoded 14px
- Fix CM lineHeight to 1.7 to match .editor-preview
- Fix mobile CSS: remove hardcoded font-size/line-height from .cm-host .cm-content
- Fix Turndown br option: '' instead of '\n' to prevent double-spacing on
render-mode edits (Turndown appends its own \n, so br:'\n' produced \n\n)
- Guard flushSave() syncPV() call behind _previewDirty to prevent Turndown
corruption during autosave when user hasn't edited in preview mode
- Mark ensureEditableAfterPre injected nodes with data-pv-trail and strip them
in htmlToMarkdown clone to prevent extra blank lines after trailing code blocks
- Fix setEditorMode('markdown') to use content comparison for cmSetVal decision
- Increase .editor-preview p margin to 0.75em for better paragraph spacing
- Add 5 new round-trip tests (426 total passing)
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 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
Register BASIC with highlight.js (preview mode) and add it to the
code modal language picker. CodeMirror falls back to plain text in
the modal editor as no CM6 BASIC parser is available.
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.