Written at v9.10.380. Owner asked for this so the remaining work can be tackled a few
pieces at a time instead of guessing.
---
40 sub-sections across 8 categories. Exactly 4 of them save anything. Everything else
renders controls that look live, respond to clicks, and then discard the result on close.
That is worse than a "SOON" label, because a toggle that flips and forgets teaches the user the
setting is applied. 24 SOON markers are honest; the ~30 silently-inert controls are not.
---
Privacy and Ecosystem/Notifications are the worst offenders: a user toggles "Hide my presence"
or "Don't notify me about X", the switch moves, and nothing changes. A privacy control that lies
is a genuine trust problem, not a polish item.
1. ~~Privacy → Visibility / Presence / Read receipts~~ — DONE (v9.10.381). Persisted AND
enforced at 8 server call sites.
2. ~~Ecosystem → Notifications~~ — DONE (v9.10.381). 8 per-kind switches, filtered server-side.
3. ~~Privacy → Blocked~~ — DONE (v9.10.383). Full block list end-to-end: store, endpoints,
and enforcement in both directions across DMs, feed, discovery, presence, profiles, friend
requests and notifications.
4. ~~Appearance → Density & Text / Motion~~ — DONE (v9.10.382). All six Appearance keys save
and apply live; Reduce motion genuinely suppresses the wallpaper parallax.
5. ~~Account → Sessions & Devices~~ — already complete (audit was wrong; revoke works on both
sides and refuses to kill the current session).
6. ~~Internet & Data → Web Browser / Connections~~ — DONE (v9.10.396). Search-engine dropdown
was fiction (provider is key-based) → now reports the real one; export wired from Data & Storage;
a third autoplay control folded onto the shared key; four given honest SOONs with reasons.
7. Agents → Behavior / Memory / Automations — real feature work, not settings plumbing.
8. Subscription → Billing / Invoices — shadow billing exists; real billing does not.
9. Account → Sign-in & Security — password change, 2FA, login alerts.
10. ~~Account → Danger Zone~~ — DONE. Export v9.10.384; Deactivate + Delete v9.10.398 with the
owner-approved cascade (comments kept but de-identified, DM threads kept for the other party,
handles retired permanently, 30-day grace, scheduled purge).
---
Activity log / timeline — DONE (v9.10.382), Account → Activity, derived at read time.
Original finding kept below for context. It did not exist anywhere in Settings. It should live under
Account → Activity, and there is already plenty to populate it from — every write path is
recorded somewhere:
social_feed), shorts, DMssessions map has createdAt, ua, ip)credits.mjs has a ledger)badges.json stores an ISO date per badge)Suggested shape: one merged, reverse-chronological feed with a type filter, reading from the
existing stores at request time (same derive-at-read-time pattern as notifications — no new
write path, nothing to keep in sync). Add GET /api/me/activity?limit=&type=.
Also missing: a Notifications *history* view (the bell only shows current items), and an
account-wide search for your own content.
---
Every inert control shares one cause: _s2tog() renders a switch whose only behaviour is
s2Toggle(this) flipping its own colour. There is no id, no binding to a settings key, and no
save path.
Fix the component, not 30 call sites: give _s2tog(on, key) a settings key, have it write
into a pending-changes object, and add a single save per pane. Then each remaining task is
"wire key X to behaviour Y" rather than "build a settings form". Roughly:
_s2tog(cur.notifyMentions, 'notifyMentions') → pending.notifyMentions = true/false
→ PATCH /api/me/settings on Save
/api/me/settings already exists with sanitizeUserSettings() + mergeUserSettings(), so the
storage half is done. The gap is purely that the UI never calls it.
---
1. A control that cannot be honoured yet gets a SOON tag, not a working-looking switch.
Inert-but-convincing is the one outcome to avoid.
2. Persist AND enforce. Saving hidePresence: true is half a feature; the presence endpoint
has to read it.
3. Privacy defaults stay private. When adding a toggle, the safe value is the default.
4. Add each newly-wired key to the settings test surface so it cannot silently regress.
---
test-settings.mjs)There are two switch components in oracle.html, rendering identically:
_s2tog() / _s2Stage() | PATCH /api/me/settings | account — follows you everywhere, in the export, enforceable server-side |gmSw() with data-key="oracle.*" | localStorage only | device — this browser only |The 9 oracle.* switches do persist, so they are not the flip-and-forget problem. But being
device-level means they do not follow you to another browser or your phone, they are absent from
the data export, and nothing server-side can act on them.
Three of them shadow an account-level key, so the two can disagree about the same preference
with no way for the user to tell which is winning:
oracle.reduceMotion | reduceMotion |oracle.autoplay | feed.autoplayShorts |oracle.compactFeed | density |RESOLVED (v9.10.388). The three shadowing switches now WRITE THROUGH to their account key via
GM_ACCOUNT_MAP, and read from the account rather than localStorage — so the account is the single
source of truth and the two can no longer disagree. Fixed in gmSw() / gmInitSwitches() (the
component) rather than in the three legacy rows, so any future row using those keys inherits it.
localStorage is still mirrored, so a failed/offline save degrades to the old behaviour instead of
losing the toggle. density needed special handling: it is an enum, not a flag, so "Compact feed"
ON maps to 'compact' and OFF to 'normal'.
test-settings.mjs now ASSERTS the write-through (it would previously only have reported the
clash), so silently reverting to localStorage-only fails the suite.
Still device-local by design (no account equivalent, and arguably correct as per-device):
oracle.aiCanDm, oracle.aiInFeed, oracle.dataSaver, oracle.deepThink, oracle.safeBrowsing,
oracle.showTokens. The suite prints this list every run so it cannot quietly grow.