Skip to content

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.

LocationRole today
packages/ts/detect-runners/src/config.tsThe 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.tsstate.enabled filters reports that were already collected. Has the UI. Persists nothing and dies with the tab.
packages/ts/waypost/src/web/view.tsrenderRunnerPicker 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.tsPer-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.

Two homes, because there are two different things.

WhereStoreLifetime
Configurationa settings dialog you deliberately openwaypost.config.jsondurable, shared, committed
View filterthe existing header pickerstate.enableddies 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 cancel

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

│ ⠿ [✓] just 412 │
│ ⠿ [ ] gradle 31 │
│ [All] [None] │
│ ────────────────────────────────────── │
│ Filters this session only. ⚙ Settings │
│ turns a runner off for the project. │

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.

  1. GET /api/config and a read-only screen: four sections, layer labels, no editing. Everything after this is making controls live.
  2. Staging and the dirty badge, still with no write.
  3. POST /api/config, Save and Revert.
  4. The execution section, wired to the existing trust endpoint.
  5. The TUI screen against the same endpoints.
  6. The picker’s one-line clarification.
AreaNote
packages/ts/waypost/src/web/page.htmlThe gear button and the settings dialog element.
packages/ts/waypost/src/web/settings.tsRender and stage the four sections.
packages/ts/waypost/src/web/input.tsOpen, close, stage, save, revert.
packages/ts/waypost/src/web/state.tsThe staged document and its dirty flag.
packages/ts/waypost/src/web/api.tsgetConfig and saveConfig clients.
packages/ts/waypost/src/web/view.tsThe picker’s session-only line.
packages/ts/waypost/src/serve.tsGET and POST /api/config.
packages/ts/waypost/src/tui.tsThe settings screen and its , binding.
packages/ts/waypost/docs-site/src/content/docs/guide/web-view.mdxDocument the screen and the two meanings of “off”.
  • 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: Save writes runners.disabled to the project file and the next collect skips that runner; Revert restores from the file; a reload without Save loses 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 allowExec as an opt-out only, and says on screen that turning execution on is the trust grant.
  • AC-7: A POST /api/config carrying allowExec: true or an unknown key is rejected, and a test proves it.
  • AC-8: The picker persists nothing and no longer reads as though it does.
  • 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. allowExec is global by design and a project file cannot raise it, so there is nothing per-runner to express.
  • none

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.


← Back to Tasks