Notechondria
Version: 0.1.37 Build Date: 2026-04-18T15:00
What's Changed
Editor \u2014 attachment upload works offline
Fixed the note-editor attachment button that did not trigger an upload on local drafts (negative IDs) or in offline mode.
Before: _pickAndUploadAttachment short-circuited with a
"Save the note before adding attachments." SnackBar whenever
noteId < 0 or the upload callback was null, and the host-side
_uploadNoteAttachment threw "Sign in to upload attachments."
whenever the token was empty. On Safari running a local-only demo,
the user saw nothing happen.
After:
- On a saved cloud note with a session, the old cloud-upload path runs; on failure it now falls through to the offline-queue path instead of just toasting an error.
- On a local draft or when the host rejects mid-upload, the editor
embeds a
data:<content-type>;base64,...URI directly into the note markdown so the attachment renders inline in preview right away, and pushes a{filename, content_type, bytes_base64, queued_at}record ontometadata_json['queued_attachments']so the draft carries the binary with it until it syncs. - A new host helper
_promoteQueuedAttachments(note, metadata)runs at the end of both_syncLocalDraftsuccess paths (cloud-copy update and fresh-create). It iterates the queued list, uploads each payload against the now-cloud-id note viawidget.client.uploadNoteAttachment, rewrites the inline data URI in the content to the server's returned URL, patches the note viaupdateNote, and drops the queue so it doesn't retry forever. Any single-attachment failure logs at warning level (Editor.Sync.Notes/attachment.promote) and the remaining queued entries are still promoted. - Every message emitted along the new path follows AGENTS.md
\u00a71.7 shape
(
Editor.UI/editor.attachment,Editor.Sync.Notes/attachment.queue,Editor.Sync.Notes/attachment.promote,Editor.Sync.Notes/attachment.upload). - Small helper
_guessContentType(filename, bytes)picks a reasonableimage/...orapplication/octet-streamfor the data URI so markdown preview renders images inline without a round-trip.
Portal and planner were not touched this round \u2014 their note
editors are inlined into lib/modules/learner.dart and wire the
upload callback differently; a follow-up round will replicate the
offline-queue pattern there.
Note editor UX polish
- Last-saved subtitle floats at lower-left. The
_SaveStatus(lastSavedAt, errorMessage, saving)widget was previously rendered inline with the editor-mode dropdown in the top bar. It now lives as aPositioned(left: 8, bottom: 8, \u2026)overlay inside the editor Stack and inherits a small, dimmedDefaultTextStyleso the subtitle hovers at the lower-left of the window without grabbing pointer events (IgnorePointerwrap). The top-bar layout reclaims the space for the title field on wide layouts. - Plain-text editor borderless. The multi-line
TextFieldunder the "P" editor mode usedOutlineInputBorder(); replaced withInputBorder.nonein editor / planner / portal so the input reads as an open canvas. The markdown live editor path was already borderless. - Removed "Stored locally until you sync" hint. The small
bottom-right caption on local-draft note preview cards
(
editor/planner/portal/lib/modules/learner.dart) is gone. Planner and portal still show the non-draft "Course metadata stays editable from the editor details panel" caption; only the local-draft branch of that ternary is removed.
Docs
docs/TODO.mdexpanded with detailed specs for three deferred tasks that did not fit in this round:- "Editor mode selection \u2192 '...' menu" (UX move, bundled with an extra "Edit note meta" entry).
- "Offline mode toggle" in shared
AppPreferencesCardgating auto-sync + lazy public-notes load. - "Download local user data" / "Restore from local imports"
replacing the minimal config-file download with a versioned
.nchronzip format carrying every persisted bucket + anattachments/directory. Spec includes the format, version-gating rules for the importer, cross-app portability policy, and a three-commit split plan.
Files Changed
New
docs/versions/0.1.37.md(this file).
Modified
VERSION: 0.1.36 \u2192 0.1.37.docs/TODO.md: deferred-task specs added.frontend/editor_app/lib/modules/note_editor.dart:_pickAndUploadAttachmentrewritten for offline queue;_guessContentTypehelper added; plain-textTextFieldborder dropped;_SaveStatushoisted out of theLayoutBuilderand added to the editor Stack as a lower-left floating subtitle.frontend/editor_app/lib/app_shell.dart:_uploadNoteAttachmentmissing-session message rewritten;_promoteQueuedAttachments(note, metadata)helper added; both_syncLocalDraftsuccess paths nowreturn _promoteQueuedAttachments(\u2026).frontend/editor_app/lib/modules/learner.dart: "Stored locally until you sync" caption block removed.frontend/planner_app/lib/modules/learner.dart: "Stored locally until you sync" branch of the ternary removed (keeps the non-draft caption); plain-textTextFieldborder dropped.frontend/portal_app/lib/modules/learner.dart: same "Stored locally" ternary branch removed; plain-textTextFieldborder dropped.
Verification
editor_app:flutter analyze\u2014 52 issues (unchanged vs 0.1.36; the new code introduces no new lint hits).planner_app:flutter analyze\u2014 70 issues (unchanged).portal_app:flutter analyze\u2014 68 issues (unchanged).- All three:
flutter test test/smoke_test.dart -r compact\u2014 passes.
Notes / follow-ups
- Deferred this round (tracked in
docs/TODO.md):- Move editor-mode selection out of the top bar into the "..." menu (adds "Edit note meta" + "Switch editor" options).
- Offline-mode toggle in the shared
AppPreferencesCard. .nchronversioned data export / import (replaces the current minimal config-file download).
- Planner and portal still carry their own inlined note editors
inside
lib/modules/learner.dart. The attachment offline-queue pattern from 0.1.37 should be replicated there when those files are next touched; their upload paths are not broken the same way because neither exposes a client-side attachment button yet (check thewidget.onUploadAttachment != nullguard on the FAB in each file).