Notechondria
Version: 0.1.42 Build Date: 2026-04-18T12:00
What's Changed
Attachment CDN rework, commit 3 of 3: preview parity + attachments list
Wraps up the three-commit plan opened in 0.1.40 / continued in 0.1.41.
Planner and portal now render local:// URLs the same way the editor
does (so drafts imported from an editor-exported .nchron archive
preview correctly), and the editor grows a compact "Attachments" list
bottom-sheet — the sub-surface called out in the 0.1.41 follow-ups.
Planner + portal preview parity
frontend/planner_app/lib/core/helpers.dartandfrontend/portal_app/lib/core/helpers.dart: new_localAttachmentImageBuilder(MarkdownImageConfig)helper, identical to the editor's. Fetches bytes fromLocalAttachmentStoreforlocal://URIs and renders viaImage.memory; falls through toImage.networkforhttp(s)://. Missing / evicted entries render a small warning pill naming the attachment.- Both
learner.dartMarkdownBody(...)call sites (planner_app/lib/modules/learner.dart, portal_app/lib/modules/learner.dart) now passsizedImageBuilder: _localAttachmentImageBuilder. - Planner and portal do not get the picker / queue path this
round — neither app has an attachment picker today, so wiring the
preview side is enough to correctly render drafts synced from the
editor. If the portal/planner grow their own picker, they can
reuse the editor's
_pickAndUploadAttachmentpattern verbatim.
Attachments list sub-surface (editor only)
- Toolbar FAB region in
note_editor.dart
changed from a single attach FAB to a Column of two
FloatingActionButton.smallwidgets with distinctheroTags (editor-attachments-list+editor-attach-file) to avoid Hero animation conflicts. - New
_openAttachmentsList()method opens ashowModalBottomSheetcapped at 70% of screen height showing:- A title ("Attachments") and a subtitle with the local / uploaded counts.
- One row per entry in
metadata_json['queued_attachments'](local attachments, with delete action) + one row perhttp(s)://URL extracted from the note body via ther'(?:!\[[^\]]*\]|\[[^\]]*\])\((https?://[^)\s]+)\)'regex (uploaded attachments, with copy-link action).
- New helpers:
_readQueuedAttachmentEntries,_extractCloudAttachmentUrls,_filenameFromUrl, and_deleteLocalAttachment(callsLocalAttachmentStore.delete(localUrl:), strips the queue entry from metadata, removes body lines containing thelocalUrl, and logsEditor.UI/editor.attachment.delete). - New private
_AttachmentSheetRowwidget at the bottom of the file. For image/* local attachments it renders a 40×40Image.memorythumbnail viaFutureBuilder; otherwise it picks an icon by content-type family. Title = filename, subtitle = formatted size + content-type + local/uploaded tag, trailing action = delete for locals, copy-link for uploaded.
Files Changed
New
docs/versions/0.1.42.md(this file).
Modified
VERSION: 0.1.41 → 0.1.42.- frontend/planner_app/lib/core/helpers.dart:
_localAttachmentImageBuilder(MarkdownImageConfig)added. - frontend/planner_app/lib/modules/learner.dart:
sizedImageBuilder: _localAttachmentImageBuilderwired into the learnerMarkdownBody. - frontend/portal_app/lib/core/helpers.dart:
_localAttachmentImageBuilder(MarkdownImageConfig)added. - frontend/portal_app/lib/modules/learner.dart:
sizedImageBuilder: _localAttachmentImageBuilderwired into the learnerMarkdownBody. - frontend/editor_app/lib/modules/note_editor.dart:
single attach FAB replaced with a two-FAB Column;
_openAttachmentsList+_readQueuedAttachmentEntries+_extractCloudAttachmentUrls+_filenameFromUrl+_deleteLocalAttachmenthelpers added;_AttachmentSheetRowwidget class added at end of file.
Verification
editor_app:flutter analyze— 54 issues, same as 0.1.41. No errors.flutter test test/smoke_test.dart— passes.planner_app:flutter analyze— 70 issues, same as 0.1.41.flutter test test/smoke_test.dart— passes.portal_app:flutter analyze— 68 issues, same as 0.1.41.flutter test test/smoke_test.dart— passes.
Notes / follow-ups
The attachment CDN rework is now functionally complete across all three apps. Remaining deferred work carried over from 0.1.41:
IndexedDB web backend (deferred)
The web stub in notechondria_shared/lib/src/local_attachment_store.dart
(_WebLocalAttachmentBackend) still stores bytes in an in-memory
Map<String, Uint8List> keyed by local:// URL, which means
attachments are lost when the tab is closed or refreshed. Next round
should swap it for an idb_shim-backed implementation:
- Open an IndexedDB database
notechondria_attachments(version 1) on firstopen(). - Object store
entrieskeyed bylocal://<note_uuid>/<filename>with value{bytes: Uint8List, content_type: String, size_bytes: int, created_at: int}. put/getBytes/deletebecometransaction('entries', 'readwrite' | 'readonly').objectStore('entries').put / get / delete.totalBytes()iterates the store and sumssize_bytes(cached in memory after first walk).migrateBase64Draftswalks existing drafts as today; the only platform-specific piece is the backend.- Add
idb_shim: ^2.6.0tonotechondria_shared/pubspec.yaml. - Tests in
notechondria_shared/test/local_attachment_store_test.dartalready cover native; add a web test gated by@TestOn('browser')that usesidb_shim/idb_browser.dart.
Storage-budget UI surface (deferred)
LocalAttachmentStore.totalBytes() exists but is not yet read by
any UI. Next round should surface it in the debug log window
(app_shell.dart's shared debug card) and in settings:
- Debug log card: a small footer row "Local attachments:
${formatBytes(total)}across${entryCount}files" refreshed alongside the existing health chips. - Settings: a new "Storage" section under the existing "Data"
panel showing local attachment total + a "Clear unused local
attachments" button that walks
metadata_json['queued_attachments']across all drafts and deletes any orphaned store entries. - Display threshold warning pill when
totalBytes() > 500 MBto nudge the user toward a sync.