feat(settings): a Decisions preview switch that writes config, not localStorage - #11495
Conversation
|
Intent: Give the upcoming DecisionOracle shadow feature a visible off switch in the place users already look for unreleased things, and make that switch write the gateway's own config rather than this device's storage — because the gate that reads the flag runs in the gateway and cannot see browser storage. The card must ship inert and say why on every gateway that has no Not a goal: Any backend. No |
Design Review (Fable 5) — 🟡 CONCERNSDesign-level review of Design-Verdict: CONCERNS The config contract ( WatchThe only mechanism that would catch the backend landing with a different path or shape — Suggestions
[DESIGN-REVIEWED] bfcf16a |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of First-Principles-Verdict: CONCERNS The entire card gates a backend that is not on main — zero readers of Not justified as shipped
What this change shipsInventory (10 items) — 7 justifiedIntent: give the upcoming Jev shadow-decisions feature a visible, gateway-side off switch before it exists — an ADDITION.
[FIRST-PRINCIPLES-REVIEWED] bfcf16a |
Opus 4.8 Review — ✅ no blocking findingsReviewed Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
UX Review (Fable 5) — 🟡 CONCERNSUX-level review of UX-Verdict: CONCERNS The consent moment stalls: the egress copy names no destination, and both repair paths ("Reload", "Update it first") are prose with no action. Watch
Suggestions
[UX-REVIEWED] bfcf16a |
d8c8b7d to
a3d1c9d
Compare
|
self-added: no span=a54c634103a4
|
|
self-added: no span=36d0697758c4
|
|
self-added: no span=a4ad832dba77
|
|
self-added: no span=52fcf3b0c21c
|
|
self-added: no span=042c75028efa
|
|
self-added: no span=f43af3ea00df
|
|
Readiness is red on a main-side failure, not on anything in this diff.
Reproduced on a pristine The other three reds are consequences of that one:
🟥 real · 🟨 cascade The fix is already open as #11518 ( Everything else on this head is green, including all three whole-design lanes and both line-level reviewer lanes. |
…calStorage Settings > Developer > Feature Previews gains a fifth card, "Decisions (Jev)". It is the first card in that section whose switch is a `config.json` value (`decisions.preview`) rather than a per-device `previewFlags.ts` key, because the gate that acts on it runs in the gateway and cannot read this browser's localStorage. The switch writes through the existing `PATCH /api/config/kirocrew` route and reads the shared `['kirocrewConfig']` cache, so it cannot disagree with the file about its own state. A gateway whose config has no `decisions` section — every gateway until the backend PR lands — renders the switch disabled with the reason beside it instead of offering a write that comes back 400. The card carries the egress fact in body weight, three read-only rows for the point arms when the config exposes them, and no `configKey`: that prop feeds `settingsRegistry.gen.ts`, whose every key is asserted to exist in the backend `SCHEMA_REGISTRY`. The section's once-per-section caveat no longer claims every switch here is per-device, which stopped being true with this card.
a3d1c9d to
bfcf16a
Compare
Problem / Motivation
There is a new shadow-mode feature coming — Kiro Crew asking a cheap decision model called Jev a yes-or-no or pick-one question at three small moments (which skills to load, whether a skill it just learned repeats one you have, whether a scheduled job's result says anything new). It needs an off switch a user can see.
Every existing switch in Settings > Developer > Feature Previews is a browser localStorage key. This one cannot be. The gate that decides whether to ask anything runs in the gateway, and the gateway cannot read your browser's storage.
Why it matters
The switch turns on something that sends your message text off this machine. A feature like that needs a visible switch, in the place a user already looks for unreleased things, that says in plain words what leaves the machine and where it goes — not a hand edit of
config.json.What changed (motivation → approach → change)
The card writes the gateway's config instead of this device's storage.
localStorage(previewFlags.ts)config.jsondecisions.previewPATCH /api/config/kirocrew🟩 added · 🟦 unchanged
The switch reads the shared
['kirocrewConfig']cache and writes through the same route the Privacy panel's telemetry switch uses, so it cannot disagree with the file about its own state. That is the whole reason it is config and not a preview flag. It stays closed to input until the write is reflected in a fresh read, so a flip never looks like it failed while the old value is still on screen.Nothing reads
decisions.previewyet. The gate, the config section and the schema entry all arrive in a separate backend PR, and the code says so rather than naming a module that does not exist. So every gateway answers the config read without adecisionssection and would refuse the write as a non-editable field: the switch renders disabled with the reason beside it. A read that simply failed says something different, because an old gateway needs an update and a failed read needs a retry.flowchart LR R[read config] --> Q{decisions<br/>section?} Q -->|no| D[disabled<br/>update the gateway] Q -->|read failed| E[disabled<br/>reload to retry] Q -->|yes| S[switch live] S -->|flip| P[PATCH decisions.preview] P --> R classDef added fill:#DCFCE7,stroke:#16A34A class R,Q,D,E,S,P added🟩 added · 🟦 unchanged
The copy names the destination in human terms rather than a config path, expands the Jev codename in its first sentence, and states persistence the way a user meets it: saved on the machine that runs Kiro Crew, so it holds on every device you open it from. The egress sentence carries body weight rather than muted fine print, because that is the fact a reader is consenting to. Each check's mode is shown read-only: which mode a check runs in is an experiment setting, and three more switches would imply this release can act on what Jev answers, which it cannot.
The toggle carries no
configKey, unlike other config-backed toggles, and that omission is a guard switched off rather than merely absent:test_settingref_schema_fixture.pyasserts every generated key exists in the backendSCHEMA_REGISTRY, so naming a path the schema does not have yet would fail onmain. Issue #11510 tracks re-engaging it once the backend lands, and the code names that issue.Two claims elsewhere stopped being true and are corrected: the section's once-per-section caveat called every switch here per-device, and the feature map's
developerrow called every preview a client flag.Seven new strings go through i18n into every catalog under
website/src/i18n/locales—catalogParityfails on any locale that misses a key or spells a placeholder a different number of times — anden-XAis regenerated. Five locale style gates ruled on the new values: French takes U+202F before:, Bengali addresses the reader as তুমি, Hindi ends sentences on purna viram, Korean writes a 조사 after an interpolation in both forms, and German'sSie <verb>rule sent one sentence's subject to lower case.Adding a Settings primitive means regenerating both settings registries:
settingsRegistry.gen.tsfor Settings search, andsrc/kiro_crew/docs/settings-registry.generated.json, the copy a deployed gateway hands the agent when someone asks where a setting is.Tests
decisionsPreview.test.tspins the config reader: an absentdecisionssection reads as unsupported and never as off, an unresolved or failed read reads the same way, a section present with no flag reads as off, and only an exacttrueturns the preview on — a hand-edited"true"or1is not consent to send message text off the machine. It also pins which checks get a row and that a check with no mode gets none.decisionsCard.test.tsxpins the card through the real section: disabled while the read is pending, disabled and naming the gateway's age when the section is absent, disabled and naming the read failure when the read fails, the exact config path written on a flip in both directions, a refused write reported with the switch back on the stored value, the egress sentence present in every state, and no preview-flag key written. One case holds the post-write read open to pin the window in between: while the switch still shows the stored value it must refuse input, or the flip reads as having failed and a second click writes it again.previewSurfaces.test.tsx,FeaturePreviewIntro.test.tsxandDeveloperPanel.test.tsxall mount the section — the last one through the panel, without naming it — so each gets a query client and a stubbed config read. The anchor test's switch count rises to five, which is the guard that catches a card added outside the ring.capture-decisions-preview-card.mjsis the screenshot harness, and it asserts rather than only capturing: each frame's own last element must sit inside the viewport, each check row must have rendered, and the refused-write frame must show the switch back on the stored value. A capture script that only writes PNGs fails toward a false pass.Manual verification
The seven frames below are the harness output against the real built SPA, gateway-free, with the config and the write answered from fixtures — one fixture carrying the
decisionssection, one without, one failing the read, one refusing the write.Screenshots / video
The card with the section present, flag off, all three checks and their modes in frame:
The state every gateway is in today — no
decisionssection, so the switch refuses and says why:Flag on, the two failure states, and the Settings search hit
The config read failed — a different note from the one above, because the fix is a retry rather than an update:
The write was refused, and the switch is back on the stored value rather than showing the click:
Related Issues
no linked issue closed here: the switch is one slice of the DecisionOracle work, and the backend that reads
decisions.previewis a separate PR (feat/decisions-oracle, in flight). Until it lands the switch renders disabled, which is what the third screenshot shows. #11510 tracks re-engaging the schema guard afterwards and is deliberately left open.Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)