Notechondria
Version: 0.1.76 Build Date: 2026-04-26T22:00
What's Changed
Two reported bugs and one new feature in editor_app, plus a small shared widget addition that planner_app / portal_app can adopt later.
Bug — "Restore default inbox" was a no-op when local content existed
User report: tapping "Restore starter inbox" in offline mode did
nothing visible when the user already had local categories or
drafts. Root cause: the maintenance action cleared the
starter_workspace_seeded_at marker and called
_ensureStarterWorkspace, but that helper short-circuits whenever
ANY local state is present (_frontPage, _courses,
_localCourses, or _localDrafts). Restore therefore only worked
on a freshly-wiped workspace — exactly the scenario where the user
wouldn't think to tap "Restore."
-
Fix: added
_seedStarterInboxAlongsideExisting()toeditor_app/lib/core/local_starter.dart. Same body as_ensureStarterWorkspaceminus the empty-state guard, with appended (not overwritten)_localCourses/_localDraftslists. The maintenance action ineditor_app/lib/core/maintenance_actions.dartnow calls the new helper, so the user always gets a fresh Inbox- 2 welcome drafts regardless of what else is in their workspace. Comment + log message updated to drop the "if missing" caveat since restore is now unconditional.
Bug — "All Notes" filter dropdown was hidden when signed out
User report: the 0.1.71 four-option filter dropdown
(Personal/Private/Public/Local) was wrapped in
if (widget.isAuthenticated) and disappeared entirely for
anonymous users — they were stuck with only the local search bar
and no way to switch between public cloud notes and local drafts.
-
Fix:
editor_app/lib/modules/learner.dartnow renders the dropdown unconditionally with a context-appropriate option set:- Authenticated: unchanged (Personal / Private / Public / Local).
- Anonymous: new two-option list (Public notes / Local
drafts only). Both map to existing backend scopes —
'all'becomes public-only since the backend filters by ownership when no token is present, and'local'skips the cloud call entirely.
-
effectiveScopenow coerces stale auth-time scopes (e.g.'personal'cached from a previous session) to'all'so the dropdown'svaluealways matches one of its items — without this, Flutter throws an "unique value" assertion on the DropdownButtonFormField. -
showCloudNotesno longer requires authentication: anonymous users witheffectiveScope='all'see the public cloud results alongside their local drafts.
Feature — Note cover images (with theme-colored barcode fallback)
User request: each note instance should have an optional cover image; the user uploads it from the note metadata dialog. When no cover is set, the frontend auto-generates a barcode placeholder keyed off the note's URL/UUID. The barcode is purely a frontend render — never persisted to R2 or the CDN — and follows the active theme colors.
Backend
-
backend/notes/models.py— addedNote.cover_imageImageField (upload_to='user_upload/note_covers/', blank+null). Newnote_cover_path()helper kept alongsidenote_attachment_pathfor symmetry, even though the model uses the simpler static-path form (instance.id may not be assigned at upload time). -
backend/notes/migrations/0017_note_cover_image.py— new migration adding the field. No data backfill needed (existing rows getnull, which the frontend reads as "no cover" → barcode fallback). -
backend/notes/api.py:NoteSummarySerializernow exposescover_image_url(SerializerMethodField usingabsolute_media_url, same shape asCourseSerializer.cover_image_url).NoteDetailSerializerinherits it automatically.- New
NoteCoverImageApiView(POST + DELETE) accepting multipart with fieldcover. Owner-only (403 from non-owners), 5 MB max per upload, deletes the previous file before saving the new one so storage doesn't accumulate orphans. Both methods return the updatedNoteSummarySerializerpayload so the client can swap the cached note row in one round-trip.
-
backend/notechondria/api_urls.py— wirednotes/<int:note_id>/cover/→NoteCoverImageApiView.
Frontend (editor_app)
-
frontend/editor_app/lib/core/client.dart—NotechondriaClientinterface gainsuploadNoteCoverImageanddeleteNoteCoverImage. Both signatures take a token + note id; upload also takes anXFile. Returns the updated note summary map. -
frontend/editor_app/lib/core/http_client.dart— multipart implementations following the existinguploadNoteAttachmentpattern (POST asMultipartRequestwith fieldcover; DELETE via_httpClient.delete). -
frontend/editor_app/lib/modules/note_metadata.dart— the metadata dialog now leads with a "Cover image" section:NoteCoverImagepreview (uploaded cover or barcode placeholder) at full dialog width.- "Upload" / "Replace" + "Remove" buttons that call
openFilefromfile_selector(same picker the avatar upload uses) and the new client methods. - Inline busy spinner + error text. Buttons hide when the note isn't synced yet (id ≤ 0) or when the user is signed out, in which case the help line explains "Sync this note to the cloud before uploading a cover image."
-
frontend/editor_app/lib/modules/note_editor.dart— threadsonUploadCover/onDeleteCovercallbacks into_NoteMetadataDialog. Local drafts (id ≤ 0) get null callbacks so the dialog auto-degrades. -
frontend/editor_app/lib/modules/learner.dartandfrontend/editor_app/lib/core/build_helpers.dartandfrontend/editor_app/lib/core/auth_flows.dart— thread the same callbacks from_AppShellState._token(when authenticated) down to the editor dialog. Three call sites: the learner page, the build-helpers builder, and the deep-link OAuth flow. -
frontend/editor_app/lib/components/note_viewer.dart— the read-only viewer renders aNoteCoverImageabove the markdown body. Cover shows in view mode but never in edit mode, matching the user spec.
Frontend (notechondria_shared)
-
frontend/notechondria_shared/lib/src/components/note_cover_image.dart— new self-contained widget. Either renders the uploaded network image (withImage.network+errorBuilderfalling back to the barcode) or a deterministic Code-39-flavored barcode painter keyed off a per-note seed (uuid, falling back to title hash). Bars usecolorScheme.primary; background usescolorScheme.surfaceContainerLow; caption (when shown) usescolorScheme.onSurfaceVariant— fully theme-aware. The painter uses an FNV-1a hash and a Code 39-style 9-cells-per-character bar/gap pattern with quiet zones and start/end guards so the rendered output reads as a real barcode at a glance without pulling inqr_flutterorbarcode_widget(no new dependencies). -
frontend/notechondria_shared/lib/notechondria_shared.dart— exportsNoteCoverImageso editor_app (and planner_app / portal_app, when they adopt it) can use it.
Files Changed
VERSION— 0.1.75 → 0.1.76.backend/notes/models.py—Note.cover_image+note_cover_path.backend/notes/api.py— serializer field + getter +NoteCoverImageApiView.backend/notes/migrations/0017_note_cover_image.py— new.backend/notechondria/api_urls.py—notes/<id>/cover/route.frontend/editor_app/lib/core/local_starter.dart—_seedStarterInboxAlongsideExisting().frontend/editor_app/lib/core/maintenance_actions.dart— restore action calls the new helper; comment + log adjusted.frontend/editor_app/lib/modules/learner.dart— context-aware dropdown items + always-visible filter row +onUploadCover/onDeleteCoverprops passthrough.frontend/editor_app/lib/core/client.dart— interface methods.frontend/editor_app/lib/core/http_client.dart— multipart implementations.frontend/editor_app/lib/modules/note_editor.dart— callback fields +_openDetailsplumbing.frontend/editor_app/lib/modules/note_metadata.dart— cover section, picker, busy/error state.frontend/editor_app/lib/components/note_viewer.dart— cover render above body.frontend/editor_app/lib/core/build_helpers.dartandfrontend/editor_app/lib/core/auth_flows.dart— wire cover-image callbacks at the two_NoteEditorDialogcall sites that aren't inside learner.frontend/notechondria_shared/lib/src/components/note_cover_image.dart(new) — shared widget with barcode painter.frontend/notechondria_shared/lib/notechondria_shared.dart— export.docs/TODO.md— both bug entries removed (deep-link regression-test stays since it's an open test-coverage item, not a behavior bug).
Notes
- All four packages (editor / planner / portal / shared) pass
flutter analyzewith zero errors andflutter testsmoke suites. Backend changes were not run-tested locally (no configured Django venv); the migration follows the existing0015_noteattachment.pyshape and the view follows the existingNoteAttachmentApiViewpattern. - Cover-image upload is editor-only this round. planner_app and
portal_app's
NotechondriaClientinterfaces don't have the new methods yet — they would mirror editor's two-line implementation whenever those apps grow a metadata-edit surface for notes. - Chrome autofill error reported by the user
(
autofill.service.ts:528 Did not autofill) was triaged but not addressed: the stack trace originates inside the Bitwarden browser extension, not Notechondria. If the issue is our login form not signaling autocomplete intent properly, that's a separate audit (verifyautocomplete="username"/autocomplete="current-password"on the editor's login fields) — left as a TODO follow-up.