← all documents · raw markdown · 8 KB

SETTINGS COMPLETION AUDIT — what actually works

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.

---

The headline

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.

| Category | Subs | Save? | Honest SOON | Silently inert |
|---|---|---|---|---|
| Account | 6 | Profile only | 9 | Sign-in toggles, Danger Zone |
| Subscription & Usage | 6 | none | 4 | Extra-credits toggle |
| Internet & Data | 4 | honest (v9.10.396) | 5 | — |
| Ecosystem | 5 | all wired (v9.10.381/384) | 2 | — |
| Agents | 5 | none | 5 | Behavior, Memory, Automations |
| Appearance | 5 | all wired (v9.10.382) | 0 | — |
| Privacy | 6 | all wired (v9.10.381/383) | 0 | — |
| General | 3 | Startup wired (v9.10.397) | 1 (Language) | — |

---

Priority order (recommended)

P0 — the ones that are actively misleading

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.

P1 — cheap wins, already have a backend

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.

DONE since this audit

P2 — needs new backend

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

---

Missing sections the owner asked for

Activity log / timelineDONE (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:

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.

---

The pattern to fix, not just the instances

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.

---

Rules for whoever picks this up

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.

---

Two settings systems (found v9.10.387 by test-settings.mjs)

There are two switch components in oracle.html, rendering identically:

| Component | Persists to | Scope |
|---|---|---|
| _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:

| localStorage key | account key |
|---|---|
| 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.