* feat(notes): rich-text editing with Fleather
Replace the plain-text note editor with Fleather, a WYSIWYG rich-text
editor built on the Parchment/Delta model. Markdown stays the canonical
stored format (content.md + derived content.html via parchment codecs;
content.json left null) so notes remain fully editable on the Open WebUI
web client.
- Add fleather + parchment dependencies.
- Add note_document_codec util wrapping the parchment markdown/HTML
codecs with best-effort fallback for malformed input.
- Rewire the editor page: FleatherController + FleatherEditor with a
formatting toolbar shown above the keyboard; word/char counts, dirty
detection, auto-save, AI-enhance, and dictation all driven off the
document. Drop the hand-rolled markdown->HTML converter.
- Hide underline/colour toolbar buttons that markdown cannot round-trip.
- Add codec round-trip + fallback unit tests.
* address greptile review feedback (greploop iteration 1)
- P1: keep _savedMarkdown baseline in sync after file-attachment saves
(audio upload + file removal) so post-save edits aren't missed.
- P2: defer the delta->markdown re-encode from every keystroke to the
debounced auto-save (authoritative dirty check), with an optimistic
unsaved indicator.
- P2: cache the FleatherThemeData (didChangeDependencies) so it is not
rebuilt twice per frame by the editor and toolbar.
- P2: correct the misleading _enhanceContent comment about _savedMarkdown.
* fix(notes): save on close, anchor toolbar over keyboard, drop lossy controls
Addresses two reported issues with the Fleather note editor:
- Formatting/edits lost on quick close: the debounced auto-save was
cancelled by dispose if the editor closed within the 800ms window. Add
a PopScope that flushes the pending save while still mounted before
popping, plus a save-on-focus-loss safety net for non-pop exits.
- Toolbar rendered at the stats row when the keyboard was up: it was
anchored at viewInsets.bottom, double-counting the inset since the
scaffold already resizes above the keyboard. Anchor at bottom: 0.
- Also hide toolbar controls markdown can't round-trip (alignment,
indentation, text direction) so users can't apply formatting that
silently vanishes on save/reopen.
Verified on iOS simulator: bold applied + immediate close persists as
**markdown** and re-renders bold on reopen; toolbar now docks above the
keyboard.
* fix(notes): reload note on reopen; add safe pull-to-refresh
- Fix reopened note showing empty/stale content until an app restart.
noteByIdProvider is keepAlive, so after an in-session edit it kept
serving the note as first opened (e.g. empty for a new note). Invalidate
it after every editor save so the next open re-reads the saved content.
- Add pull-to-refresh to the note editor. It flushes any pending edit,
runs a full sync cycle (push outbox + pull) via the sync engine, then
reloads from the reconciled LOCAL Drift row (new readLocalNote helper)
rather than a fresh server fetch — so a server copy that's behind a
not-yet-pushed local edit can't clobber it with stale/empty content.
Verified on iOS simulator: create note, type, close, reopen -> content
shows (previously empty); pull-to-refresh on an unsynced note keeps the
local content instead of wiping it.
* fix(notes): don't discard edits made during pull-to-refresh
If the user types while requestPull is in flight, those keystrokes re-set
_hasChanges and queue a fresh debounce. The refresh then overwrote the
controller and baseline, so the queued autosave saw no diff and the edits
were lost. Re-check _hasChanges after the awaits and bail out of the
reload when new edits arrived mid-flight, leaving them for their own
debounce to save. (greploop iteration 2)