AFK spawn-options checkbox is 2-state over a 3-state inherit model (misleading + no path back to inherit) #21

Open
opened 2026-07-07 15:20:06 +02:00 by dominik.polakovics · 0 comments

This was generated by AI while landing a PR.

Follow-up from the landing review of #20 (ADR-0021, spawn defaults + provider-options bag). Not a correctness bug — the behaviour matches the ADR's documented null = inherit / present = explicit model — but the AFK spawn-options control is a 2-state checkbox over a 3-state model, which can mislead an operator about what an AFK fleet will actually run.

Problem

The bool spawn-option (today: ultracode) renders as a plain checkbox in the global and repo AFK-defaults sections (web/src/routes/RepoSettings.tsx ~L283, and the equivalent in Settings.tsx). The underlying model is three-state:

  • afk_options = nullinherit (repo falls back to the global AFK bag)
  • afk_options = {} or {"ultracode": "..."}explicit (overrides global)

A checkbox can only show two of those three states, so:

  1. Inherited state reads as "off". A repo with afk_options = null that inherits a global ultracode: "true" renders the repo checkbox unchecked, even though that repo's AFK runs will run ultracode. The checkbox shows the repo override, not the effective value.
  2. No UI path back to inherit. Unchecking a previously-explicit true stores {"ultracode": "false"} — an explicit off that overrides the global — rather than clearing the bag back to null (inherit). Once an operator touches the checkbox there is no way, from the UI, to return the repo to "same as global default".

Why it matters

An operator configuring a repo can be misled into thinking an AFK fleet won't run ultracode (unchecked box) when it will (inherited from global), or can't express "go back to inheriting the global default" once they've set an explicit value. Model/effort already avoid this — they use a select with an explicit "same as default (inherit)" entry.

Proposed direction

Make the bool spawn-option a tri-state control at the repo scope, mirroring how model/effort already render an inherit entry:

  • e.g. a 3-way select / segmented control per bool option: Inherit (same as global) · On · Off, where Inherit clears the key back to null.
  • Show the resolved effective value alongside the inherit choice (e.g. "Inherit — currently on from global") so the operator sees what will actually run.
  • The global scope has no inherit state, so a plain checkbox there is fine as-is.

Schema-driven so future bool options (and eventual enum/string types) get the same treatment automatically.

Scope / notes

  • UX refinement only — no backend or migration change; the null/present semantics from ADR-0021 stay exactly as shipped.
  • Also worth a glance while here: the whitespace-only-InitialPrompt NIT in internal/provider/claudecode/claudecode.go (~L283) — the ultracode no-op keys on prompt == "" rather than strings.TrimSpace(prompt) == "". Unreachable in current flows (manual passes "", AFK passes substantive text), so purely defensive — fold in only if trivial.

Refs: #20, ADR-0021, #19.

> *This was generated by AI while landing a PR.* Follow-up from the landing review of **#20** (ADR-0021, spawn defaults + provider-options bag). Not a correctness bug — the behaviour matches the ADR's documented `null = inherit` / `present = explicit` model — but the AFK spawn-options control is a **2-state checkbox over a 3-state model**, which can mislead an operator about what an AFK fleet will actually run. ## Problem The bool spawn-option (today: `ultracode`) renders as a plain checkbox in the global **and** repo AFK-defaults sections (`web/src/routes/RepoSettings.tsx` ~L283, and the equivalent in `Settings.tsx`). The underlying model is three-state: - `afk_options = null` → **inherit** (repo falls back to the global AFK bag) - `afk_options = {}` or `{"ultracode": "..."}` → **explicit** (overrides global) A checkbox can only show two of those three states, so: 1. **Inherited state reads as "off".** A repo with `afk_options = null` that inherits a global `ultracode: "true"` renders the repo checkbox **unchecked**, even though that repo's AFK runs *will* run ultracode. The checkbox shows the repo *override*, not the *effective* value. 2. **No UI path back to inherit.** Unchecking a previously-explicit `true` stores `{"ultracode": "false"}` — an explicit *off* that overrides the global — rather than clearing the bag back to `null` (inherit). Once an operator touches the checkbox there is no way, from the UI, to return the repo to "same as global default". ## Why it matters An operator configuring a repo can be misled into thinking an AFK fleet won't run ultracode (unchecked box) when it will (inherited from global), or can't express "go back to inheriting the global default" once they've set an explicit value. Model/effort already avoid this — they use a select with an explicit **"same as default (inherit)"** entry. ## Proposed direction Make the bool spawn-option a **tri-state control** at the repo scope, mirroring how model/effort already render an inherit entry: - e.g. a 3-way select / segmented control per bool option: **Inherit (same as global)** · **On** · **Off**, where *Inherit* clears the key back to `null`. - Show the resolved *effective* value alongside the inherit choice (e.g. "Inherit — currently **on** from global") so the operator sees what will actually run. - The global scope has no inherit state, so a plain checkbox there is fine as-is. Schema-driven so future bool options (and eventual enum/string types) get the same treatment automatically. ## Scope / notes - UX refinement only — no backend or migration change; the `null`/present semantics from ADR-0021 stay exactly as shipped. - Also worth a glance while here: the whitespace-only-`InitialPrompt` NIT in `internal/provider/claudecode/claudecode.go` (~L283) — the ultracode no-op keys on `prompt == ""` rather than `strings.TrimSpace(prompt) == ""`. Unreachable in current flows (manual passes `""`, AFK passes substantive text), so purely defensive — fold in only if trivial. Refs: #20, ADR-0021, #19.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab#21
No description provided.