feat(dashboard): /config form — Thresholds + Container sections #479

Merged
code-lead merged 2 commits from dev/459 into main 2026-04-27 22:15:43 +00:00
Collaborator

Adds Thresholds and Container tabs to the /config per-type form panel.

Thresholds tab: per-type WIP cap, escalation cap, model dropdown; pipeline-global stall thresholds (stall, CI, review, implement, default — with ms unit and hardcoded default hint); watchdogs-global intervals (container, tail-PR-rebase, dead-letter, janitor). Each field has a Reset button that clears the override back to the runtime default.

Container tab: per-type image override toggle (when off → shows global container_image_default fallback); env var key→value row editor with add/remove.

API fix: fetchAgentConfig and putAgentConfig now correctly unwrap/wrap the server's { content, mtime, etag } envelope. putAgentConfig merges form-managed fields back into the full config before writing, preserving operator-managed fields the form doesn't touch.

Test plan

  • ThresholdsSection: typing "abc" in a number input does not update state
  • ThresholdsSection: Reset button is disabled at default, enabled after editing, clears field on click
  • ThresholdsSection: model dropdown calls onTypeConfigChange with the selected model ID
  • ThresholdsSection: pipeline + watchdog fields call onGlobalConfigChange
  • ContainerSection: unchecked override shows fallback text; checking it shows image input
  • ContainerSection: Add env var / edit key / edit value / remove row all update staged state
  • All 16 new tests pass

Closes #459

Adds Thresholds and Container tabs to the `/config` per-type form panel. **Thresholds tab:** per-type WIP cap, escalation cap, model dropdown; pipeline-global stall thresholds (stall, CI, review, implement, default — with ms unit and hardcoded default hint); watchdogs-global intervals (container, tail-PR-rebase, dead-letter, janitor). Each field has a Reset button that clears the override back to the runtime default. **Container tab:** per-type image override toggle (when off → shows global `container_image_default` fallback); env var key→value row editor with add/remove. **API fix:** `fetchAgentConfig` and `putAgentConfig` now correctly unwrap/wrap the server's `{ content, mtime, etag }` envelope. `putAgentConfig` merges form-managed fields back into the full config before writing, preserving operator-managed fields the form doesn't touch. ## Test plan - [ ] `ThresholdsSection`: typing "abc" in a number input does not update state - [ ] `ThresholdsSection`: Reset button is disabled at default, enabled after editing, clears field on click - [ ] `ThresholdsSection`: model dropdown calls `onTypeConfigChange` with the selected model ID - [ ] `ThresholdsSection`: pipeline + watchdog fields call `onGlobalConfigChange` - [ ] `ContainerSection`: unchecked override shows fallback text; checking it shows image input - [ ] `ContainerSection`: Add env var / edit key / edit value / remove row all update staged state - [ ] All 16 new tests pass Closes #459
feat(dashboard): /config form — Thresholds + Container sections (#459)
Some checks failed
qa / qa (pull_request) Has been cancelled
qa / dockerfile (pull_request) Has been cancelled
10afd7ee62
Adds ThresholdsSection and ContainerSection to the /config per-type form.

- ThresholdsSection: per-type WIP cap, escalation cap, model dropdown;
  pipeline-global stall thresholds (6 fields); watchdogs-global intervals
  (4 fields). NumberField helper with unit hint, default hint, and reset
  button per field.
- ContainerSection: per-type image override toggle + text input that falls
  back to global container_image_default; env var key→value row editor
  (add / edit key / edit value / remove rows).
- Fixes fetchAgentConfig / putAgentConfig to properly unwrap/wrap the
  server's { content, mtime, etag } envelope instead of treating the raw
  response as { types }.
- Adds env field to containerSchema (server-side Zod schema).
- Extends AgentTypeConfig with wip_soft_limit, container.image, container.env.
- Adds GlobalPipelineConfig and GlobalWatchdogsConfig API types.
- ConfigRoute gains globalDraft state (pipeline + watchdogs + container_image_default).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dev force-pushed dev/459 from 10afd7ee62
Some checks failed
qa / qa (pull_request) Has been cancelled
qa / dockerfile (pull_request) Has been cancelled
to ada2fa0b5e
Some checks failed
qa / qa (pull_request) Has been cancelled
qa / dockerfile (pull_request) Has been cancelled
2026-04-27 21:17:29 +00:00
Compare
dev force-pushed dev/459 from ada2fa0b5e
Some checks failed
qa / qa (pull_request) Has been cancelled
qa / dockerfile (pull_request) Has been cancelled
to debe146562
All checks were successful
qa / qa (pull_request) Successful in 10m22s
qa / dockerfile (pull_request) Successful in 14s
2026-04-27 21:44:09 +00:00
Compare
dev requested review from reviewer 2026-04-27 21:55:13 +00:00
reviewer requested changes 2026-04-27 21:58:26 +00:00
Dismissed
reviewer left a comment
  • behavior apps/web/src/routes/config.tsx:1308: Clearing the image text input while the override toggle is checked silently unchecks the toggle and hides the input. The culprit is onChange={(e) => patchContainer({ image: e.target.value || undefined })} — when the field is emptied, "" || undefined evaluates to undefined, hasImageOverride (container.image !== undefined) flips to false, and the input disappears mid-typing. A user who checks the override and then clears the field to retype loses the toggle state without clicking it. Fix: patchContainer({ image: e.target.value }) — accept the empty string as a valid in-progress state while the toggle is on; the checkbox is the explicit control for removing the override.

  • test-gap apps/web/src/routes/config.test.tsx: No test covers the "clear image input while toggle is checked" path, which is why the above bug slipped through. Add a test that renders ContainerSection with container: { image: "foo" }, fires a change event on container-image-input with value "", and asserts (a) the onChange call still has container.image defined (not undefined), and (b) the container-image-input is still in the document.

- **behavior** `apps/web/src/routes/config.tsx:1308`: Clearing the image text input while the override toggle is checked silently unchecks the toggle and hides the input. The culprit is `onChange={(e) => patchContainer({ image: e.target.value || undefined })}` — when the field is emptied, `"" || undefined` evaluates to `undefined`, `hasImageOverride` (`container.image !== undefined`) flips to `false`, and the input disappears mid-typing. A user who checks the override and then clears the field to retype loses the toggle state without clicking it. Fix: `patchContainer({ image: e.target.value })` — accept the empty string as a valid in-progress state while the toggle is on; the checkbox is the explicit control for removing the override. - **test-gap** `apps/web/src/routes/config.test.tsx`: No test covers the "clear image input while toggle is checked" path, which is why the above bug slipped through. Add a test that renders `ContainerSection` with `container: { image: "foo" }`, fires a change event on `container-image-input` with value `""`, and asserts (a) the onChange call still has `container.image` defined (not undefined), and (b) the `container-image-input` is still in the document.
fix(dashboard): keep image input visible when cleared while override toggle is on
All checks were successful
qa / qa (pull_request) Successful in 9m5s
qa / dockerfile (pull_request) Successful in 14s
5df5d72495
`e.target.value || undefined` converted an empty string to `undefined`,
flipping `hasImageOverride` false and hiding the input mid-edit. Accept the
empty string as a valid in-progress state; the checkbox is the explicit
control. Adds a regression test for the cleared-input path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Collaborator
  • behavior fixed: e.target.value || undefinede.target.value — empty string is now accepted as a valid in-progress state; the checkbox remains the sole control for removing the override.
  • test-gap added: "clearing image input while toggle is checked keeps toggle on and input in DOM" — fires a change event with "" on container-image-input, asserts container.image is defined and the input stays in the DOM.
- **behavior** fixed: `e.target.value || undefined` → `e.target.value` — empty string is now accepted as a valid in-progress state; the checkbox remains the sole control for removing the override. - **test-gap** added: `"clearing image input while toggle is checked keeps toggle on and input in DOM"` — fires a change event with `""` on `container-image-input`, asserts `container.image` is defined and the input stays in the DOM.
dev requested review from reviewer 2026-04-27 22:09:39 +00:00
reviewer approved these changes 2026-04-27 22:10:25 +00:00
reviewer left a comment

Both prior findings addressed: patchContainer({ image: e.target.value }) correctly preserves empty-string state, and the new "clearing image input while toggle is checked" test covers the regression path. CI green.

Both prior findings addressed: `patchContainer({ image: e.target.value })` correctly preserves empty-string state, and the new "clearing image input while toggle is checked" test covers the regression path. CI green.
code-lead deleted branch dev/459 2026-04-27 22:15:44 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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
charles/claude-hooks!479
No description provided.