T-EFZQ-waypost-settings-screen
State: planning/proposed · Impact: medium · Complexity: large
Give waypost a settings screen that edits waypost.config.json, and let the
runner picker go back to being what it already is — a filter for this session.
Today one checkbox is asked to mean both “which runners this project uses” and
“which ones I am looking at right now”, and it does the second while looking
like the first.
Three notions of “off”, none of them the one a person clicking the picker expects, and no screen anywhere that edits configuration.
| Location | Role today |
|---|---|
packages/ts/detect-runners/src/config.ts | The whole config document — runners.disabled, runners.order, allowExec, discovery, excludePaths — plus writeConfigFile, setConfigValue and validateConfig. Reachable only from the CLI. |
packages/ts/waypost/src/web/state.ts | state.enabled filters reports that were already collected. Has the UI. Persists nothing and dies with the tab. |
packages/ts/waypost/src/web/view.ts | renderRunnerPicker draws the checkbox against state.enabled. |
packages/ts/waypost/src/web/page.html | <dialog id="help"> — the only dialog in the skeleton, and the pattern a second one follows. |
packages/ts/detect-runners/src/trust.ts | Per-repository consent to spawn probes, keyed on the resolved root. Has an endpoint and a button; is not configuration. |
Uncheck four runners, reload, and all four return with no sign a choice was
discarded. Meanwhile the setting that would actually have turned them off can
only be reached by editing a file or running waypost config set.
Proposed
Section titled “Proposed”Two homes, because there are two different things.
| Where | Store | Lifetime | |
|---|---|---|---|
| Configuration | a settings dialog you deliberately open | waypost.config.json | durable, shared, committed |
| View filter | the existing header picker | state.enabled | dies with the tab |
The picker’s behaviour does not change. It gains one line saying it filters this session and pointing at Settings, so it stops reading as a setting.
The screen covers every config key, in KEY_ORDER[""] order so it reads like
the schema and like a written file: runners, execution, discovery, excluded
paths. Each section names the layer its value came from, which LoadedConfig.sources
already reports — “this is your user config, not this project” is a fact the
screen can state without new machinery.
Saving is explicit. Edits stage. A dirty badge, Save to waypost.config.json,
and Revert. No single click dirties a tracked file.
A staged-off runner keeps its count, so the control that turns it back on
is never blind. Only after a save and a re-collect does the count read —.
The key stays runners.disabled, not an enabled allowlist. Absent means
every runner works, so a provider added by a later detect-runners starts
working on upgrade with no file to edit. An allowlist inverts that: a new
runner is silently missing until somebody edits the file. The screen still
reads as an allowlist; the file holds the small inverse.
Execution is not a plain checkbox, because it is not symmetric. A project
file may only lower allowExec — allowExec: true there is stripped with a
warning, because a repository cannot grant itself permission to run its own
code. So the section shows the repository’s trust state with Trust /
Untrust against the existing /api/trust, and offers allowExec as a
project-level opt-out only. A symmetric toggle would let someone tick it, save,
and silently get the warning path.
┌──────────────────────────────────────────────────────────────────┐│ ⚙ Settings waypost.config.json [×] │├──────────────────────────────────────────────────────────────────┤│ RUNNERS project ││ ⠿ [✓] just 412 ││ ⠿ [✓] moon 88 ││ ⠿ [ ] gradle 31 ││ Tab order is arranged locally. [Reset local order] ││ ││ EXECUTION trust store ││ This repository is trusted. Runners may spawn probes. ││ [Untrust] ││ [ ] Never run probes in this project allowExec: false ││ A project file can only turn execution OFF. Turning it ││ on is the trust grant above. ││ ││ DISCOVERY default ││ (•) auto ( ) git ( ) walk ││ ││ EXCLUDED PATHS project ││ solutions/…/manifest-graph/tests/mini-repo [×] ││ [+ add path] │├──────────────────────────────────────────────────────────────────┤│ [Revert] [Save] │└──────────────────────────────────────────────────────────────────┘Dirty:
│ ⚙ Settings ● 2 unsaved waypost.config.json [×] ││ RUNNERS project · 2 staged ││ ⠿ [ ] moon 88 will not collect ││ [Revert] [Save changes] │Reached with ,. Same four sections, same order, same labels — the terminal
version is the same information laid out for a keyboard, not a reduced one.
waypost · settings waypost.config.json ───────────────────────────────────────────────────────────────── RUNNERS project ▸ [x] just 412 [x] moon 88 [ ] gradle 31 local tab order active r reset
EXECUTION trust store trusted — probes may run u untrust [ ] never run probes here allowExec: false
DISCOVERY default (•) auto ( ) git ( ) walk
EXCLUDED PATHS project solutions/…/manifest-graph/tests/mini-repo d delete a add ───────────────────────────────────────────────────────────────── j/k move space toggle [ ] reorder ^s save esc cancelA full screen rather than a floating panel: four sections do not fit a panel at
24 rows. , because it is unbound and adjacent to nothing destructive.
The picker afterwards
Section titled “The picker afterwards” │ ⠿ [✓] just 412 │ │ ⠿ [ ] gradle 31 │ │ [All] [None] │ │ ────────────────────────────────────── │ │ Filters this session only. ⚙ Settings │ │ turns a runner off for the project. │The write endpoint
Section titled “The write endpoint”GET /api/config returns the project document, the effective one, the layer
each key came from, and whether this root is trusted. POST /api/config takes
a config document, runs the existing validateConfig server-side, rejects
unknown keys and writes through writeConfigFile — no new writer, and no
client-supplied path. Both are guarded by hasSession like every other route.
It writes configuration only. Trust stays on /api/trust, which confirms
before granting, and validateConfig already refuses a project document that
tries to raise allowExec — so a write here cannot widen what may execute.
Approach
Section titled “Approach”GET /api/configand a read-only screen: four sections, layer labels, no editing. Everything after this is making controls live.- Staging and the dirty badge, still with no write.
POST /api/config,SaveandRevert.- The execution section, wired to the existing trust endpoint.
- The TUI screen against the same endpoints.
- The picker’s one-line clarification.
| Area | Note |
|---|---|
packages/ts/waypost/src/web/page.html | The gear button and the settings dialog element. |
packages/ts/waypost/src/web/settings.ts | Render and stage the four sections. |
packages/ts/waypost/src/web/input.ts | Open, close, stage, save, revert. |
packages/ts/waypost/src/web/state.ts | The staged document and its dirty flag. |
packages/ts/waypost/src/web/api.ts | getConfig and saveConfig clients. |
packages/ts/waypost/src/web/view.ts | The picker’s session-only line. |
packages/ts/waypost/src/serve.ts | GET and POST /api/config. |
packages/ts/waypost/src/tui.ts | The settings screen and its , binding. |
packages/ts/waypost/docs-site/src/content/docs/guide/web-view.mdx | Document the screen and the two meanings of “off”. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: A gear beside
?opens the settings dialog;,opens the same four sections in the TUI. - AC-2: Each section names the layer its value came from, and a value inherited from the user config says so rather than looking local.
- AC-3: Turning a runner off stages a change and shows the dirty badge; no
file is written until
Save. - AC-4:
Savewritesrunners.disabledto the project file and the next collect skips that runner;Revertrestores from the file; a reload withoutSaveloses the edits and nothing else. - AC-5: A staged-off runner stays listed with its count until the next collect, so re-enabling it is not a blind choice.
- AC-6: The execution section offers
allowExecas an opt-out only, and says on screen that turning execution on is the trust grant. - AC-7: A
POST /api/configcarryingallowExec: trueor an unknown key is rejected, and a test proves it. - AC-8: The picker persists nothing and no longer reads as though it does.
Out of scope
Section titled “Out of scope”runners.order’s split between the config key and the browser’s dragged arrangement: T-DO2G-waypost-runner-order-split-brain. The reset control lands on this screen, but the decision and the docs are that task.- Editing the user-level config. The screen shows where an inherited value came
from; changing it stays
waypost config set, because a per-repository UI writing a machine-wide file is a surprise. - Per-runner execution grain.
allowExecis global by design and a project file cannot raise it, so there is nothing per-runner to express.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Raised on 2026-08-31 as “we need an ability to specify and retain state wrt
what runners we want to show, and which ones we even want to attempt to
execute”. Drafted as WP-0003 in #1616
and moved here. The scope grew from a persistence question to a screen once it
was clear the two meanings needed two homes rather than one smarter control.