From bf148902abe72bb6611811f084c02340ae07699b Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Fri, 28 Aug 2026 14:59:21 +0200 Subject: [PATCH] spa hardening, offline logging, rate limit fixes server: - backup exporter, auth extractors, error shapes, CONTEXT (prior work) - spa assets served outside the rate limit via route_layer - requests_per_second went to per_second(), which takes an interval not a rate: 50 meant one request per 50s once burst was spent. now converted properly. 15/s, burst 60 spa fixes: - account delete cleared snake_case token keys that were never written - refresh interceptor could retry forever - date ranges used local day boundaries stamped +00:00 - "all" period trend plotted one page; calendar days fabricated mood 3 - chart grid invisible: hsl(var(--border)) against rgba tokens - blob url leak, orphaned media on failed save, devtools in prod bundle - pt-safe/safe-area-pb classes never existed spa features: - offline outbox: entries queue to IndexedDB, replay with backoff, only server refusals count against an entry - drafts persist, quick-log sheet, diary infinite scroll + filters - route error boundary, stale-chunk recovery, no service worker in dev a11y + perf: - mood picker is a radiogroup, activity picker keyboard-operable, text alternatives for colour/emoji, locale week start - dark glass over the bright photo: worst case 1.4:1 -> 4.9-9.6:1 - initial payload 1095->769kB raw, 306->230kB gzip; 38 unused components and 5 deps dropped; fonts 218->133kB 53 tests added (43 spa, 10 server) --- CONTEXT.md | 26 +- Cargo.lock | 175 +++++ Cargo.toml | 3 + config.example.toml | 20 + crates/adapters/exporter/src/backup.rs | 34 +- crates/adapters/exporter/src/lib.rs | 4 +- crates/adapters/http-axum/Cargo.toml | 6 + crates/adapters/http-axum/src/errors.rs | 29 +- .../src/extractors/authenticated_user.rs | 57 -- .../src/extractors/authentication.rs | 145 ++++ .../adapters/http-axum/src/extractors/body.rs | 59 ++ .../src/extractors/importing_provider.rs | 12 +- .../http-axum/src/extractors/metric_writer.rs | 53 -- .../adapters/http-axum/src/extractors/mod.rs | 12 +- .../http-axum/src/extractors/path_id.rs | 8 +- .../http-axum/src/extractors/scoped.rs | 99 +++ .../http-axum/src/handlers/activities.rs | 86 ++- .../adapters/http-axum/src/handlers/auth.rs | 48 +- .../http-axum/src/handlers/correlations.rs | 19 +- .../adapters/http-axum/src/handlers/cycle.rs | 46 +- .../adapters/http-axum/src/handlers/data.rs | 31 +- .../http-axum/src/handlers/entries.rs | 238 ++++-- .../http-axum/src/handlers/import_export.rs | 12 +- .../adapters/http-axum/src/handlers/jobs.rs | 35 + .../adapters/http-axum/src/handlers/media.rs | 80 +- .../http-axum/src/handlers/metrics.rs | 53 +- crates/adapters/http-axum/src/handlers/mod.rs | 2 + .../http-axum/src/handlers/providers.rs | 38 +- .../adapters/http-axum/src/handlers/push.rs | 66 +- .../http-axum/src/handlers/reminders.rs | 48 +- .../adapters/http-axum/src/handlers/server.rs | 76 ++ .../adapters/http-axum/src/handlers/tokens.rs | 42 +- .../adapters/http-axum/src/handlers/users.rs | 61 +- crates/adapters/http-axum/src/openapi.rs | 29 +- crates/adapters/http-axum/src/router.rs | 116 ++- crates/adapters/http-axum/src/state.rs | 17 +- .../http-axum/tests/openapi_contract_test.rs | 129 ++++ .../http-axum/tests/rate_limit_test.rs | 119 +++ crates/adapters/importer/src/backup_reader.rs | 40 +- crates/adapters/sqlite/src/db.rs | 23 + crates/adapters/sqlite/src/lib.rs | 2 +- .../src/migrations/013_reminder_last_sent.sql | 1 + .../src/migrations/014_media_ownership.sql | 19 + .../src/migrations/015_logged_at_in_utc.sql | 6 + .../src/migrations/016_api_token_scopes.sql | 5 + .../src/repositories/api_token/command.rs | 4 +- .../src/repositories/api_token/query.rs | 4 +- .../sqlite/src/repositories/api_token/rows.rs | 8 +- .../sqlite/src/repositories/cascade/mod.rs | 50 +- .../sqlite/src/repositories/entry/command.rs | 10 +- .../sqlite/src/repositories/entry/query.rs | 163 ++-- .../sqlite/src/repositories/job/backfill.rs | 18 + .../sqlite/src/repositories/job/weather.rs | 19 + .../sqlite/src/repositories/media/mod.rs | 3 + .../src/repositories/media/ownership.rs | 95 +++ .../adapters/sqlite/src/repositories/mod.rs | 2 + .../repositories/push_subscription/command.rs | 9 +- .../src/repositories/reminder/command.rs | 8 +- .../sqlite/src/repositories/reminder/rows.rs | 2 + .../sqlite/src/repositories/shared.rs | 8 + .../adapters/sqlite/tests/api_token_test.rs | 5 +- crates/adapters/sqlite/tests/cascade_test.rs | 167 ++++ .../sqlite/tests/entry_instant_test.rs | 142 ++++ .../adapters/sqlite/tests/migration_check.rs | 103 +++ crates/adapters/web-push/src/lib.rs | 2 +- crates/api-types/src/lib.rs | 1 + crates/api-types/src/mappers/api_token.rs | 7 +- crates/api-types/src/mappers/calendar.rs | 2 +- crates/api-types/src/mappers/entry.rs | 87 ++- crates/api-types/src/mappers/job.rs | 19 + crates/api-types/src/mappers/media.rs | 19 +- crates/api-types/src/mappers/metric.rs | 3 +- crates/api-types/src/mappers/mod.rs | 2 + crates/api-types/src/mappers/push.rs | 13 + crates/api-types/src/mappers/restore.rs | 1 + crates/api-types/src/mappers/stats.rs | 8 +- crates/api-types/src/params/activity.rs | 5 + crates/api-types/src/params/entry.rs | 18 + crates/api-types/src/params/metric.rs | 6 + crates/api-types/src/params/mod.rs | 7 + crates/api-types/src/requests/api_token.rs | 1 + crates/api-types/src/requests/entry.rs | 30 +- crates/api-types/src/requests/metric.rs | 7 - crates/api-types/src/requests/mod.rs | 5 +- crates/api-types/src/requests/push.rs | 3 +- crates/api-types/src/responses/api_token.rs | 2 +- crates/api-types/src/responses/auth.rs | 26 + crates/api-types/src/responses/bulk.rs | 21 - crates/api-types/src/responses/bulk_action.rs | 5 + crates/api-types/src/responses/error.rs | 23 + .../api-types/src/responses/import_result.rs | 7 + crates/api-types/src/responses/job.rs | 13 + crates/api-types/src/responses/media.rs | 13 + crates/api-types/src/responses/mod.rs | 24 +- crates/api-types/src/responses/page.rs | 11 + crates/api-types/src/responses/push.rs | 9 + crates/api-types/src/responses/restore.rs | 1 + crates/api-types/src/responses/server.rs | 50 ++ crates/api-types/src/responses/stats.rs | 6 +- .../src/activity/use_cases/delete_activity.rs | 19 +- crates/application/src/api_token/commands.rs | 2 + .../use_cases/authenticate_api_token.rs | 19 +- .../src/api_token/use_cases/mint_api_token.rs | 9 +- .../correlation/use_cases/get_correlations.rs | 126 +-- crates/application/src/entry/commands.rs | 4 +- crates/application/src/entry/mod.rs | 1 + crates/application/src/entry/queries.rs | 21 +- crates/application/src/entry/tagging.rs | 63 ++ .../src/entry/use_cases/create_entries.rs | 65 ++ .../src/entry/use_cases/create_entry.rs | 11 +- .../src/entry/use_cases/filter_by_activity.rs | 24 - .../src/entry/use_cases/filter_by_mood.rs | 21 - .../src/entry/use_cases/get_mood_stats.rs | 8 +- .../src/entry/use_cases/list_entries.rs | 20 +- crates/application/src/entry/use_cases/mod.rs | 3 +- .../src/entry/use_cases/update_entry.rs | 72 +- .../src/export/use_cases/write_backup.rs | 34 +- .../src/export/use_cases/write_extract.rs | 2 +- crates/application/src/import/already_here.rs | 41 + crates/application/src/import/mod.rs | 1 + .../src/import/use_cases/import_entries.rs | 21 +- .../src/job/use_cases/list_exhausted_jobs.rs | 15 + crates/application/src/job/use_cases/mod.rs | 1 + .../src/job/use_cases/run_due_jobs.rs | 16 +- crates/application/src/media/cleanup.rs | 42 +- crates/application/src/media/custody.rs | 22 + crates/application/src/media/dropped.rs | 58 ++ crates/application/src/media/mod.rs | 2 + .../src/media/use_cases/delete_photo.rs | 16 +- .../src/media/use_cases/delete_voice_memo.rs | 16 +- .../src/media/use_cases/list_media.rs | 16 + crates/application/src/media/use_cases/mod.rs | 1 + .../src/media/use_cases/upload_photo.rs | 15 +- .../src/media/use_cases/upload_voice_memo.rs | 17 +- .../src/metric/use_cases/list_rejections.rs | 19 + .../application/src/metric/use_cases/mod.rs | 1 + crates/application/src/push/commands.rs | 1 + .../src/push/use_cases/list_subscriptions.rs | 19 + crates/application/src/push/use_cases/mod.rs | 1 + .../src/push/use_cases/subscribe.rs | 26 +- .../src/push/use_cases/unsubscribe.rs | 8 +- .../use_cases/process_due_reminders.rs | 103 ++- .../src/restore/use_cases/restore_backup.rs | 148 +++- .../src/user/use_cases/change_password.rs | 8 +- .../src/user/use_cases/clear_data.rs | 19 +- .../src/user/use_cases/delete_user.rs | 19 +- .../application/tests/activity/crud_test.rs | 72 ++ .../tests/api_token/api_token_test.rs | 100 ++- .../tests/backup/round_trip_test.rs | 195 ++++- .../correlation/get_correlations_test.rs | 145 +++- .../tests/deletion/best_effort_test.rs | 18 +- .../tests/deletion/ordering_test.rs | 17 +- crates/application/tests/entry.rs | 3 - .../tests/entry/create_entry_test.rs | 191 +++++ .../application/tests/entry/dimension_test.rs | 8 +- crates/application/tests/entry/filter_test.rs | 67 -- .../tests/entry/list_entries_test.rs | 167 +++- .../tests/entry/mood_stats_test.rs | 69 ++ .../tests/entry/replace_activity_test.rs | 20 +- .../tests/entry/update_entry_test.rs | 144 +++- .../tests/import/wall_clock_test.rs | 83 +- crates/application/tests/media.rs | 2 + .../application/tests/media/custody_test.rs | 138 ++++ crates/application/tests/push.rs | 2 + .../tests/push/subscription_test.rs | 112 +++ .../tests/reminder/process_due_test.rs | 90 ++- .../tests/user/change_password_test.rs | 62 ++ .../application/tests/user/clear_data_test.rs | 169 +++- .../tests/user/delete_user_test.rs | 3 +- crates/bootstrap/src/factory.rs | 11 +- crates/config/src/entry_config.rs | 3 + crates/config/src/lib.rs | 2 + crates/config/src/rate_limit_config.rs | 24 + crates/config/src/server_config.rs | 4 + crates/config/src/worker_config.rs | 3 + crates/domain/src/api_token/api_token.rs | 25 +- crates/domain/src/api_token/mod.rs | 2 + crates/domain/src/api_token/token_scope.rs | 38 + crates/domain/src/api_token/token_scopes.rs | 62 ++ crates/domain/src/attachment/media_ref.rs | 78 ++ crates/domain/src/attachment/mod.rs | 2 + crates/domain/src/entry/entry_selection.rs | 69 ++ crates/domain/src/entry/mod.rs | 4 + crates/domain/src/entry/mood.rs | 18 + crates/domain/src/entry/pagination.rs | 86 +++ crates/domain/src/macros.rs | 9 +- crates/domain/src/metric/metric_kind.rs | 47 ++ crates/domain/src/ports/entry.rs | 25 +- crates/domain/src/ports/import_export.rs | 2 + crates/domain/src/ports/job.rs | 10 + crates/domain/src/ports/media.rs | 12 +- crates/domain/src/ports/mod.rs | 6 +- crates/domain/src/ports/push.rs | 3 +- crates/domain/src/ports/restore.rs | 11 +- crates/domain/src/push/push_subscription.rs | 6 + crates/domain/src/reminder/due_occurrence.rs | 62 ++ crates/domain/src/reminder/mod.rs | 2 + crates/domain/src/reminder/reminder.rs | 30 +- .../src/testing/fakes/activity_dimension.rs | 54 ++ .../domain/src/testing/fakes/media_storage.rs | 65 +- crates/domain/src/testing/fakes/mod.rs | 3 + crates/domain/src/testing/fakes/store.rs | 2 + .../domain/src/testing/fakes/store_entry.rs | 128 ++-- .../domain/src/testing/fakes/store_infra.rs | 93 ++- crates/domain/src/testing/fakes/store_job.rs | 20 + .../domain/src/testing/fakes/store_media.rs | 47 ++ crates/domain/src/testing/mod.rs | 4 +- .../domain/tests/api_token/api_token_test.rs | 62 +- crates/domain/tests/reminder.rs | 3 + .../tests/reminder/due_occurrence_test.rs | 169 ++++ crates/server/src/main.rs | 9 +- crates/worker/Cargo.toml | 1 + crates/worker/src/loops.rs | 13 +- crates/worker/src/main.rs | 1 + ...edia-ownership-is-recorded-not-inferred.md | 24 + ...015-one-canonical-instant-in-the-column.md | 32 + .../0016-a-reminder-remembers-what-it-sent.md | 24 + ...aring-data-is-not-resetting-the-account.md | 21 + ...s-grant-scopes-sessions-own-the-account.md | 22 + ...0019-a-page-is-a-selection-and-a-window.md | 28 + docs/adr/0020-one-refusal-shape.md | 21 + spa/bun.lock | 68 -- spa/package.json | 5 - spa/public/sw.js | 105 ++- spa/src/aero-theme.css | 231 +----- spa/src/api/client.ts | 87 ++- spa/src/api/schema.ts | 85 +- spa/src/api/server-url.ts | 56 ++ spa/src/api/session.ts | 45 ++ .../components/activity/activity-picker.tsx | 69 +- spa/src/components/auth/auth-card.tsx | 24 + spa/src/components/auth/form-error.tsx | 11 + spa/src/components/auth/password-strength.tsx | 60 ++ spa/src/components/dashboard/metrics-card.tsx | 64 ++ spa/src/components/dashboard/setup-card.tsx | 84 ++ spa/src/components/dashboard/today-card.tsx | 39 + spa/src/components/dev/devtools.tsx | 11 + .../entry/detail/entry-activities.tsx | 40 + .../components/entry/detail/entry-photos.tsx | 43 ++ .../entry/draft-restored-notice.tsx | 19 + spa/src/components/entry/entry-details.tsx | 85 ++ spa/src/components/entry/entry-filters.tsx | 96 +++ spa/src/components/entry/entry-form.tsx | 352 +++------ spa/src/components/entry/entry-list.tsx | 45 +- spa/src/components/entry/entry-missing.tsx | 27 + .../entry/fields/location-field.tsx | 71 ++ .../components/entry/fields/song-field.tsx | 65 ++ spa/src/components/entry/log-sheet.tsx | 47 ++ spa/src/components/entry/pending-entries.tsx | 103 +++ spa/src/components/layout/app-shell.tsx | 4 +- spa/src/components/layout/bottom-nav.tsx | 135 +++- spa/src/components/layout/route-error.tsx | 67 ++ spa/src/components/media/audio-player.tsx | 53 +- spa/src/components/media/media-thumbnail.tsx | 12 +- spa/src/components/media/photo-lightbox.tsx | 91 +++ spa/src/components/media/photo-picker.tsx | 46 +- .../components/media/voice-memo-picker.tsx | 52 +- spa/src/components/metric/day-stepper.tsx | 52 ++ spa/src/components/metric/metrics-editor.tsx | 114 +++ spa/src/components/mood/mood-dot.tsx | 23 +- spa/src/components/mood/mood-picker.tsx | 92 ++- .../components/offline/connection-banner.tsx | 62 ++ .../components/settings/api-tokens-card.tsx | 241 ++++-- .../components/settings/collapsible-card.tsx | 50 ++ spa/src/components/settings/server-card.tsx | 24 + spa/src/components/settings/server-picker.tsx | 94 +++ spa/src/components/settings/subsonic-card.tsx | 73 +- spa/src/components/stats/heatmap-section.tsx | 50 ++ .../components/stats/mood-distribution.tsx | 86 +-- spa/src/components/stats/mood-heatmap.tsx | 56 +- spa/src/components/stats/mood-summary.tsx | 43 ++ spa/src/components/stats/mood-trend-chart.tsx | 179 ++--- spa/src/components/stats/stat-card.tsx | 16 +- spa/src/components/ui/accordion.tsx | 78 -- spa/src/components/ui/aspect-ratio.tsx | 22 - spa/src/components/ui/attachment.tsx | 208 ----- spa/src/components/ui/avatar.tsx | 107 --- spa/src/components/ui/breadcrumb.tsx | 122 --- spa/src/components/ui/bubble.tsx | 129 ---- spa/src/components/ui/button-group.tsx | 88 --- spa/src/components/ui/carousel.tsx | 239 ------ spa/src/components/ui/chart.tsx | 369 --------- spa/src/components/ui/combobox.tsx | 298 ------- spa/src/components/ui/command.tsx | 193 ----- spa/src/components/ui/context-menu.tsx | 272 ------- spa/src/components/ui/direction.tsx | 4 - spa/src/components/ui/dropdown-menu.tsx | 270 ------- spa/src/components/ui/hover-card.tsx | 51 -- spa/src/components/ui/input-group.tsx | 159 ---- spa/src/components/ui/input-otp.tsx | 84 -- spa/src/components/ui/item.tsx | 202 ----- spa/src/components/ui/kbd.tsx | 26 - spa/src/components/ui/marker.tsx | 72 -- spa/src/components/ui/menubar.tsx | 284 ------- spa/src/components/ui/message-scroller.tsx | 128 ---- spa/src/components/ui/message.tsx | 92 --- spa/src/components/ui/native-select.tsx | 65 -- spa/src/components/ui/navigation-menu.tsx | 171 ----- spa/src/components/ui/pagination.tsx | 133 ---- spa/src/components/ui/progress.tsx | 83 -- spa/src/components/ui/questionnaire.tsx | 328 -------- spa/src/components/ui/radio-group.tsx | 36 - spa/src/components/ui/resizable.tsx | 50 -- spa/src/components/ui/scroll-area.tsx | 52 -- spa/src/components/ui/select.tsx | 202 ----- spa/src/components/ui/sheet.tsx | 135 ---- spa/src/components/ui/sidebar.tsx | 724 ------------------ spa/src/components/ui/table.tsx | 114 --- spa/src/components/ui/toggle-group.tsx | 87 --- spa/src/components/ui/toggle.tsx | 44 -- spa/src/components/ui/tooltip.tsx | 66 -- spa/src/fonts.css | 31 + spa/src/hooks/use-api-tokens.ts | 3 +- spa/src/hooks/use-auth.ts | 35 +- spa/src/hooks/use-calendar.ts | 5 +- spa/src/hooks/use-date-bounds.ts | 27 +- spa/src/hooks/use-entries.ts | 61 +- spa/src/hooks/use-entry-draft.ts | 96 +++ spa/src/hooks/use-infinite-scroll.ts | 37 + spa/src/hooks/use-metrics.ts | 19 + spa/src/hooks/use-new-entry-form.ts | 23 + spa/src/hooks/use-online.ts | 24 + spa/src/hooks/use-outbox.ts | 105 +++ spa/src/hooks/use-preview-urls.ts | 29 + spa/src/hooks/use-save-entry.ts | 97 +++ spa/src/hooks/use-server.ts | 12 + spa/src/hooks/use-stats.ts | 72 +- spa/src/hooks/use-timezone.ts | 27 +- spa/src/hooks/use-trend.ts | 52 ++ spa/src/hooks/use-update-entry.ts | 7 +- spa/src/hooks/use-voice-recorder.ts | 6 - spa/src/hooks/use-year-calendar.ts | 11 +- spa/src/lib/activity-groups.ts | 60 ++ spa/src/lib/api-message.ts | 19 + spa/src/lib/button-style.ts | 15 + spa/src/lib/day-label.ts | 13 + spa/src/lib/day.ts | 17 + spa/src/lib/duration.ts | 11 + spa/src/lib/entry-draft.ts | 146 ++++ spa/src/lib/entry-preview.ts | 10 + spa/src/lib/instant-range.ts | 38 + spa/src/lib/media-upload.ts | 54 ++ spa/src/lib/media.ts | 36 +- spa/src/lib/mood.ts | 74 +- spa/src/lib/outbox-sync.ts | 77 ++ spa/src/lib/period.ts | 18 + spa/src/lib/service-worker.ts | 99 +++ spa/src/lib/store/draft-store.ts | 38 + spa/src/lib/store/idb.ts | 83 ++ spa/src/lib/store/outbox.ts | 64 ++ spa/src/lib/trend-buckets.ts | 89 +++ spa/src/lib/week.ts | 44 ++ spa/src/main.tsx | 13 +- spa/src/routeTree.gen.ts | 34 +- spa/src/router.tsx | 3 + spa/src/routes/__root.tsx | 44 +- spa/src/routes/_auth.tsx | 10 +- spa/src/routes/_auth/activities.lazy.tsx | 99 +++ spa/src/routes/_auth/activities.tsx | 98 +-- spa/src/routes/_auth/add.lazy.tsx | 26 + spa/src/routes/_auth/add.tsx | 29 +- spa/src/routes/_auth/calendar.lazy.tsx | 190 +++++ spa/src/routes/_auth/calendar.tsx | 174 +---- spa/src/routes/_auth/diary.lazy.tsx | 94 +++ spa/src/routes/_auth/diary.tsx | 66 +- spa/src/routes/_auth/edit.$id.lazy.tsx | 66 ++ spa/src/routes/_auth/edit.$id.tsx | 86 +-- spa/src/routes/_auth/entry.$id.lazy.tsx | 196 +++++ spa/src/routes/_auth/entry.$id.tsx | 258 +------ spa/src/routes/_auth/export.lazy.tsx | 100 +++ spa/src/routes/_auth/export.tsx | 99 +-- spa/src/routes/_auth/import.lazy.tsx | 220 ++++++ spa/src/routes/_auth/import.tsx | 213 +----- spa/src/routes/_auth/index.tsx | 85 +- spa/src/routes/_auth/metrics.lazy.tsx | 54 ++ spa/src/routes/_auth/metrics.tsx | 157 +--- spa/src/routes/_auth/reminders.lazy.tsx | 322 ++++++++ spa/src/routes/_auth/reminders.tsx | 323 +------- spa/src/routes/_auth/settings.lazy.tsx | 562 ++++++++++++++ spa/src/routes/_auth/settings.tsx | 559 +------------- spa/src/routes/_auth/stats.lazy.tsx | 80 +- spa/src/routes/login.tsx | 136 ++-- spa/src/routes/register.tsx | 173 +++-- spa/src/styles.css | 115 +-- spa/tests/api/session.test.ts | 39 + spa/tests/lib/activity-groups.test.ts | 42 + spa/tests/lib/entry-draft.test.ts | 118 +++ spa/tests/lib/instant-range.test.ts | 42 + spa/tests/lib/media.test.ts | 30 + spa/tests/lib/outbox-sync.test.ts | 133 ++++ spa/tests/lib/trend-buckets.test.ts | 51 ++ spa/tests/lib/week.test.ts | 21 + spa/tsconfig.json | 3 +- spa/vite.config.ts | 28 + spa/vitest.config.ts | 12 + 395 files changed, 13972 insertions(+), 10635 deletions(-) delete mode 100644 crates/adapters/http-axum/src/extractors/authenticated_user.rs create mode 100644 crates/adapters/http-axum/src/extractors/authentication.rs create mode 100644 crates/adapters/http-axum/src/extractors/body.rs delete mode 100644 crates/adapters/http-axum/src/extractors/metric_writer.rs create mode 100644 crates/adapters/http-axum/src/extractors/scoped.rs create mode 100644 crates/adapters/http-axum/src/handlers/jobs.rs create mode 100644 crates/adapters/http-axum/src/handlers/server.rs create mode 100644 crates/adapters/http-axum/tests/openapi_contract_test.rs create mode 100644 crates/adapters/http-axum/tests/rate_limit_test.rs create mode 100644 crates/adapters/sqlite/src/migrations/013_reminder_last_sent.sql create mode 100644 crates/adapters/sqlite/src/migrations/014_media_ownership.sql create mode 100644 crates/adapters/sqlite/src/migrations/015_logged_at_in_utc.sql create mode 100644 crates/adapters/sqlite/src/migrations/016_api_token_scopes.sql create mode 100644 crates/adapters/sqlite/src/repositories/media/mod.rs create mode 100644 crates/adapters/sqlite/src/repositories/media/ownership.rs create mode 100644 crates/adapters/sqlite/tests/entry_instant_test.rs create mode 100644 crates/api-types/src/mappers/job.rs create mode 100644 crates/api-types/src/mappers/push.rs create mode 100644 crates/api-types/src/params/activity.rs create mode 100644 crates/api-types/src/params/entry.rs create mode 100644 crates/api-types/src/params/metric.rs create mode 100644 crates/api-types/src/params/mod.rs create mode 100644 crates/api-types/src/responses/auth.rs delete mode 100644 crates/api-types/src/responses/bulk.rs create mode 100644 crates/api-types/src/responses/bulk_action.rs create mode 100644 crates/api-types/src/responses/error.rs create mode 100644 crates/api-types/src/responses/import_result.rs create mode 100644 crates/api-types/src/responses/job.rs create mode 100644 crates/api-types/src/responses/media.rs create mode 100644 crates/api-types/src/responses/page.rs create mode 100644 crates/api-types/src/responses/push.rs create mode 100644 crates/api-types/src/responses/server.rs create mode 100644 crates/application/src/entry/tagging.rs create mode 100644 crates/application/src/entry/use_cases/create_entries.rs delete mode 100644 crates/application/src/entry/use_cases/filter_by_activity.rs delete mode 100644 crates/application/src/entry/use_cases/filter_by_mood.rs create mode 100644 crates/application/src/import/already_here.rs create mode 100644 crates/application/src/job/use_cases/list_exhausted_jobs.rs create mode 100644 crates/application/src/media/custody.rs create mode 100644 crates/application/src/media/dropped.rs create mode 100644 crates/application/src/media/use_cases/list_media.rs create mode 100644 crates/application/src/metric/use_cases/list_rejections.rs create mode 100644 crates/application/src/push/use_cases/list_subscriptions.rs delete mode 100644 crates/application/tests/entry/filter_test.rs create mode 100644 crates/application/tests/media.rs create mode 100644 crates/application/tests/media/custody_test.rs create mode 100644 crates/application/tests/push.rs create mode 100644 crates/application/tests/push/subscription_test.rs create mode 100644 crates/config/src/rate_limit_config.rs create mode 100644 crates/domain/src/api_token/token_scopes.rs create mode 100644 crates/domain/src/attachment/media_ref.rs create mode 100644 crates/domain/src/entry/entry_selection.rs create mode 100644 crates/domain/src/entry/pagination.rs create mode 100644 crates/domain/src/reminder/due_occurrence.rs create mode 100644 crates/domain/src/testing/fakes/activity_dimension.rs create mode 100644 crates/domain/src/testing/fakes/store_media.rs create mode 100644 crates/domain/tests/reminder/due_occurrence_test.rs create mode 100644 docs/adr/0014-media-ownership-is-recorded-not-inferred.md create mode 100644 docs/adr/0015-one-canonical-instant-in-the-column.md create mode 100644 docs/adr/0016-a-reminder-remembers-what-it-sent.md create mode 100644 docs/adr/0017-clearing-data-is-not-resetting-the-account.md create mode 100644 docs/adr/0018-tokens-grant-scopes-sessions-own-the-account.md create mode 100644 docs/adr/0019-a-page-is-a-selection-and-a-window.md create mode 100644 docs/adr/0020-one-refusal-shape.md create mode 100644 spa/src/api/server-url.ts create mode 100644 spa/src/api/session.ts create mode 100644 spa/src/components/auth/auth-card.tsx create mode 100644 spa/src/components/auth/form-error.tsx create mode 100644 spa/src/components/auth/password-strength.tsx create mode 100644 spa/src/components/dashboard/metrics-card.tsx create mode 100644 spa/src/components/dashboard/setup-card.tsx create mode 100644 spa/src/components/dashboard/today-card.tsx create mode 100644 spa/src/components/dev/devtools.tsx create mode 100644 spa/src/components/entry/detail/entry-activities.tsx create mode 100644 spa/src/components/entry/detail/entry-photos.tsx create mode 100644 spa/src/components/entry/draft-restored-notice.tsx create mode 100644 spa/src/components/entry/entry-details.tsx create mode 100644 spa/src/components/entry/entry-filters.tsx create mode 100644 spa/src/components/entry/entry-missing.tsx create mode 100644 spa/src/components/entry/fields/location-field.tsx create mode 100644 spa/src/components/entry/fields/song-field.tsx create mode 100644 spa/src/components/entry/log-sheet.tsx create mode 100644 spa/src/components/entry/pending-entries.tsx create mode 100644 spa/src/components/layout/route-error.tsx create mode 100644 spa/src/components/media/photo-lightbox.tsx create mode 100644 spa/src/components/metric/day-stepper.tsx create mode 100644 spa/src/components/metric/metrics-editor.tsx create mode 100644 spa/src/components/offline/connection-banner.tsx create mode 100644 spa/src/components/settings/collapsible-card.tsx create mode 100644 spa/src/components/settings/server-card.tsx create mode 100644 spa/src/components/settings/server-picker.tsx create mode 100644 spa/src/components/stats/heatmap-section.tsx create mode 100644 spa/src/components/stats/mood-summary.tsx delete mode 100644 spa/src/components/ui/accordion.tsx delete mode 100644 spa/src/components/ui/aspect-ratio.tsx delete mode 100644 spa/src/components/ui/attachment.tsx delete mode 100644 spa/src/components/ui/avatar.tsx delete mode 100644 spa/src/components/ui/breadcrumb.tsx delete mode 100644 spa/src/components/ui/bubble.tsx delete mode 100644 spa/src/components/ui/button-group.tsx delete mode 100644 spa/src/components/ui/carousel.tsx delete mode 100644 spa/src/components/ui/chart.tsx delete mode 100644 spa/src/components/ui/combobox.tsx delete mode 100644 spa/src/components/ui/command.tsx delete mode 100644 spa/src/components/ui/context-menu.tsx delete mode 100644 spa/src/components/ui/direction.tsx delete mode 100644 spa/src/components/ui/dropdown-menu.tsx delete mode 100644 spa/src/components/ui/hover-card.tsx delete mode 100644 spa/src/components/ui/input-group.tsx delete mode 100644 spa/src/components/ui/input-otp.tsx delete mode 100644 spa/src/components/ui/item.tsx delete mode 100644 spa/src/components/ui/kbd.tsx delete mode 100644 spa/src/components/ui/marker.tsx delete mode 100644 spa/src/components/ui/menubar.tsx delete mode 100644 spa/src/components/ui/message-scroller.tsx delete mode 100644 spa/src/components/ui/message.tsx delete mode 100644 spa/src/components/ui/native-select.tsx delete mode 100644 spa/src/components/ui/navigation-menu.tsx delete mode 100644 spa/src/components/ui/pagination.tsx delete mode 100644 spa/src/components/ui/progress.tsx delete mode 100644 spa/src/components/ui/questionnaire.tsx delete mode 100644 spa/src/components/ui/radio-group.tsx delete mode 100644 spa/src/components/ui/resizable.tsx delete mode 100644 spa/src/components/ui/scroll-area.tsx delete mode 100644 spa/src/components/ui/select.tsx delete mode 100644 spa/src/components/ui/sheet.tsx delete mode 100644 spa/src/components/ui/sidebar.tsx delete mode 100644 spa/src/components/ui/table.tsx delete mode 100644 spa/src/components/ui/toggle-group.tsx delete mode 100644 spa/src/components/ui/toggle.tsx delete mode 100644 spa/src/components/ui/tooltip.tsx create mode 100644 spa/src/fonts.css create mode 100644 spa/src/hooks/use-entry-draft.ts create mode 100644 spa/src/hooks/use-infinite-scroll.ts create mode 100644 spa/src/hooks/use-new-entry-form.ts create mode 100644 spa/src/hooks/use-online.ts create mode 100644 spa/src/hooks/use-outbox.ts create mode 100644 spa/src/hooks/use-preview-urls.ts create mode 100644 spa/src/hooks/use-save-entry.ts create mode 100644 spa/src/hooks/use-server.ts create mode 100644 spa/src/hooks/use-trend.ts create mode 100644 spa/src/lib/activity-groups.ts create mode 100644 spa/src/lib/api-message.ts create mode 100644 spa/src/lib/button-style.ts create mode 100644 spa/src/lib/day-label.ts create mode 100644 spa/src/lib/day.ts create mode 100644 spa/src/lib/duration.ts create mode 100644 spa/src/lib/entry-draft.ts create mode 100644 spa/src/lib/entry-preview.ts create mode 100644 spa/src/lib/instant-range.ts create mode 100644 spa/src/lib/media-upload.ts create mode 100644 spa/src/lib/outbox-sync.ts create mode 100644 spa/src/lib/period.ts create mode 100644 spa/src/lib/service-worker.ts create mode 100644 spa/src/lib/store/draft-store.ts create mode 100644 spa/src/lib/store/idb.ts create mode 100644 spa/src/lib/store/outbox.ts create mode 100644 spa/src/lib/trend-buckets.ts create mode 100644 spa/src/lib/week.ts create mode 100644 spa/src/routes/_auth/activities.lazy.tsx create mode 100644 spa/src/routes/_auth/add.lazy.tsx create mode 100644 spa/src/routes/_auth/calendar.lazy.tsx create mode 100644 spa/src/routes/_auth/diary.lazy.tsx create mode 100644 spa/src/routes/_auth/edit.$id.lazy.tsx create mode 100644 spa/src/routes/_auth/entry.$id.lazy.tsx create mode 100644 spa/src/routes/_auth/export.lazy.tsx create mode 100644 spa/src/routes/_auth/import.lazy.tsx create mode 100644 spa/src/routes/_auth/metrics.lazy.tsx create mode 100644 spa/src/routes/_auth/reminders.lazy.tsx create mode 100644 spa/src/routes/_auth/settings.lazy.tsx create mode 100644 spa/tests/api/session.test.ts create mode 100644 spa/tests/lib/activity-groups.test.ts create mode 100644 spa/tests/lib/entry-draft.test.ts create mode 100644 spa/tests/lib/instant-range.test.ts create mode 100644 spa/tests/lib/media.test.ts create mode 100644 spa/tests/lib/outbox-sync.test.ts create mode 100644 spa/tests/lib/trend-buckets.test.ts create mode 100644 spa/tests/lib/week.test.ts create mode 100644 spa/vitest.config.ts diff --git a/CONTEXT.md b/CONTEXT.md index 52b2290..019cfbe 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -23,7 +23,7 @@ Optional markdown text attached to a MoodEntry. Serves both quick annotations (" _Avoid_: Note, quick note, description, journal entry **Activity**: -A named item from a user's personal catalog, tagged onto MoodEntries. Covers heterogeneous concepts (social contexts like "friends", health actions like "exercise", sleep indicators like "good sleep") under one umbrella term. Each Activity belongs to exactly one User. Activities are archivable — archived Activities remain on historical entries but cannot be tagged onto new ones. +A named item from a user's personal catalog, tagged onto MoodEntries. Covers heterogeneous concepts (social contexts like "friends", health actions like "exercise", sleep indicators like "good sleep") under one umbrella term. Each Activity belongs to exactly one User. Activities are archivable — an archived Activity stays on every entry already wearing it, and those entries stay editable, but it cannot be added to an entry that does not already carry it. Unarchiving makes it taggable again. _Avoid_: Tag, label, habit, tracker **Category**: @@ -43,9 +43,13 @@ _Avoid_: TZ, offset, locale, region ### Scheduling **Reminder**: -A per-user notification schedule. Each Reminder defines an `Option