0.1.88 — attachment UUID routing + IndexedDB web backend + storage-budget UI

Backend: UUID-keyed attachment storage and API

  • 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.

Frontend: IndexedDB web backend for LocalAttachmentStore

  • 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).

Frontend: Storage-budget UI surface

  • 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.

Bug fix: Inbox default category on web with empty cache

  • _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.

Files changed

 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)