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)
This commit is contained in:
21
docs/adr/0017-clearing-data-is-not-resetting-the-account.md
Normal file
21
docs/adr/0017-clearing-data-is-not-resetting-the-account.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Clearing an account's data empties what it logged, not what it is
|
||||
|
||||
`delete_all_user_data` deleted four tables — entries, activities, reminders, daily metrics — and logged "all user data cleared". Cycle starts, rejected metrics and media ownership survived it, and so did every blob the account had uploaded but never attached to an entry.
|
||||
|
||||
The tables were missed because the account row stays, so nothing cascades. `delete_user_account` has no such gap: it deletes the users row and `foreign_keys` is on, so `ON DELETE CASCADE` reaches everything hanging off it.
|
||||
|
||||
## What counts as data
|
||||
|
||||
CompleteBackup already draws the line this needed: it carries everything an account knows and explicitly "no credentials and no RejectedMetrics". Clearing follows the same seam, one table further out.
|
||||
|
||||
Emptied: MoodEntries, Activities, Reminders, DailyMetrics, CycleStarts, RejectedMetrics, and the media a User owns. A RejectedMetric is a trace of the account's own readings, so clearing the readings and keeping the complaints about them would be incoherent — it goes, even though a backup does not carry it.
|
||||
|
||||
Kept: the account, its UserPreferences, its sessions and push subscriptions, its ApiTokens and its ProviderConnections. A preference is a choice the User made, not something they logged; clearing cycle records while leaving cycle tracking on is a coherent state and turning it off silently is not. Credentials are revoked deliberately, one at a time, and a request to clear a journal is not a request to break every automation writing into it.
|
||||
|
||||
## Consequences
|
||||
|
||||
The table list is a named constant the cascade loops over, so a new user-scoped table is one line rather than one forgotten `DELETE`. `InMemoryStore` clears the same set through one shared method, because a fake that forgets a table cannot fail the test that would have caught this.
|
||||
|
||||
Media is no longer resolved by composing entries and reading their photo ids. `MediaOwnershipPort::owned_by` returns everything the account owns, so a blob uploaded and abandoned before its entry was saved is cleared with the rest — the orphan that the entry-shaped question could never see. `clear_data` and `delete_user` lost their `dimensions` and `entry_query` dependencies as a result; `delete_entries_by_date_range` keeps the entry-shaped path, because it deletes a subset of entries and ownership does not say which entry a blob belongs to.
|
||||
|
||||
Ownership is still read before the cascade runs, for the same reason it always was: the cascade deletes the rows that name the blobs.
|
||||
Reference in New Issue
Block a user