note_attachment_path now uses note.uuid.hex instead of the
integer note.id in the storage directory, so the server-side path
mirrors the frontend's local://<note-uuid>/<filename> scheme.
Old uploads remain at their original paths (Django FileField
stores the resolved path in the DB row); only new uploads use the
UUID-based directory.
New UUID-keyed API endpoints:
GET /notes/uuid/<uuid:note_uuid>/attachments/ — list attachments
by note UUID.
POST /notes/uuid/<uuid:note_uuid>/attachments/ — upload
attachment by note UUID.
DELETE /notes/uuid/<uuid:note_uuid>/attachments/<int:attachment_id>/
— delete attachment by note UUID.
Backward-compatible: the original integer-keyed endpoints at
/notes/<int:note_id>/attachments/ continue to work unchanged.
New backend tests (NoteAttachmentByUuidApiTests) covering list,
upload, delete, permission checks, size caps, and path verification.
Added idb_shim: ^2.6.0 dependency to
notechondria_shared/pubspec.yaml.
Replaced the in-memory _WebLocalAttachmentBackend with an
IndexedDB-backed implementation using idb_shim:
Database notechondria_attachments (version 1), object store
entries keyed by local://<note_uuid>/<filename> string.
Store records carry {key, bytes, metadataJson} — the same
contract the old in-memory map provided, but now persisted in
the browser's IndexedDB so attachments survive tab refreshes.
totalBytes() iterates the store with a cursor rather than
maintaining a cached counter (correctness over perf; the store
is typically small).
New shared formatBytes utility in
notechondria_shared/lib/src/utils/format_bytes.dart (exported
from the shared barrel). Formats byte counts as B/KB/MB.
_AttachmentStorageTile widget added to the editor's settings
page (settings_build.dart), displayed below the debug log card:
Shows "Local attachments: N MB" when attachments are present.
Shows a warning icon (via Tooltip) when totalBytes() > 500 MB.
Auto-hides when the attachment store is empty or not initialized.
_frontPage empty-map edge case in _seedStarterInboxAlongsideExisting:
On fresh web boot _frontPage is {} (from defaultCache()), not null,
so ??= in the starter seeder silently skipped applying the
frontPageFallbackPayload. Changed both ??= sites to an explicit
null-or-empty check so the fallback payload is always applied.
Defensive safety net in _loadInitialData:
Added a guard after the existing course-selection logic that ensures
anonymous/local-only users always have a default course selected even
if _chooseDefaultCourse — for any reason — returns null. Covers the
edge case where the Inbox's negative timestamp ID doesn't match any
entry during the iteration.
M backend/notes/models.py (note_attachment_path UUID)
M backend/notes/api.py (UUID-based views + _get_note_by_uuid)
M backend/notechondria/api_urls.py (UUID attachment URL patterns)
M backend/notes/tests.py (NoteAttachmentByUuidApiTests)
M frontend/notechondria_shared/pubspec.yaml (+idb_shim)
M frontend/notechondria_shared/lib/notechondria_shared.dart (+formatBytes export)
A frontend/notechondria_shared/lib/src/utils/format_bytes.dart
M frontend/notechondria_shared/lib/src/utils/local_attachment_store_web.dart (IndexedDB)
M frontend/editor_app/lib/core/client.dart (+UUID-based client methods)
M frontend/editor_app/lib/core/http_client.dart (+UUID-based HTTP implementations)
M frontend/editor_app/lib/modules/settings_build.dart (+_AttachmentStorageTile)
M VERSION (0.1.87 → 0.1.88)
A docs/versions/0.1.88.md (this file)