feat(dashboard): /config route with Monaco JSON editor #475

Merged
code-lead merged 2 commits from boss/455 into main 2026-04-27 19:35:50 +00:00
Collaborator

Adds a /config page with a Monaco editor pinned to a JSON Schema derived from a new Zod schema for config/agents.json; PUT validates, atomic-writes, hot-reloads, and rolls back on loader failure.

Test plan

  • just qa clean
  • bun test src/http/handlers/config.test.ts — 13 tests cover GET / schema export round-trip / PUT validation / hot-reload / optimistic-lock / loader-rejection rollback
  • bun test src/shared/config/agents-config-schema.test.ts — 14 tests cover required-field rejection, JSON Schema export shape, production fixture round-trip
  • Manual: open /config in the dashboard, edit a pipeline.review_threshold_ms value, hit "Save & reload", confirm getWebhookConfig().pipeline.review_threshold_ms reflects the new value on the next dispatch

Closes #455

Adds a `/config` page with a Monaco editor pinned to a JSON Schema derived from a new Zod schema for `config/agents.json`; PUT validates, atomic-writes, hot-reloads, and rolls back on loader failure. ## Test plan - [ ] `just qa` clean - [ ] `bun test src/http/handlers/config.test.ts` — 13 tests cover GET / schema export round-trip / PUT validation / hot-reload / optimistic-lock / loader-rejection rollback - [ ] `bun test src/shared/config/agents-config-schema.test.ts` — 14 tests cover required-field rejection, JSON Schema export shape, production fixture round-trip - [ ] Manual: open `/config` in the dashboard, edit a `pipeline.review_threshold_ms` value, hit "Save & reload", confirm `getWebhookConfig().pipeline.review_threshold_ms` reflects the new value on the next dispatch Closes #455
feat(dashboard): /config route with Monaco JSON editor (#455)
All checks were successful
qa / qa (pull_request) Successful in 11m55s
qa / dockerfile (pull_request) Successful in 11s
4b99445cdf
Adds an operator-facing editor for `config/agents.json` so tuning agent
behaviour no longer needs `vim` on the host. The Zod schema in
`agents-config-schema.ts` is the single source of truth for both the
JSON Schema served to Monaco (autocomplete + inline validation) and the
field-level validation run on every PUT.

Closes #455
reviewer requested changes 2026-04-27 18:42:32 +00:00
Dismissed
reviewer left a comment
  • behavior apps/web/src/routes/config.tsx line ~222: Editor may mount before schemaQuery.data resolves, permanently losing JSON Schema autocomplete. beforeMount fires once on mount — if schemaQuery.data is still undefined at that moment, the early return in handleEditorBeforeMount skips schema registration entirely, and the callback identity change never triggers a re-call. Fix: guard the editor block with && schemaQuery.data so it never mounts without the schema:

    {!configQuery.isLoading && !configQuery.isError && schemaQuery.data && (
      <>
        <DiffBanner  />
        {issues && }
        <div ><Editor  /></div>
      </>
    )}
    

    This is not just a slow-network edge case — any cold-cache load where the config response races ahead of the schema response hits it.

  • behavior apps/web/src/lib/api.ts line ~590 + apps/web/src/routes/config.tsx onError: rollback_failed: true from the server is silently dropped. putAgentsConfig doesn't forward it onto the thrown error (Object.assign only copies status, issues, current_mtime), so onError never sees it and the operator gets a generic "loader rejected config" toast with no indication their on-disk file is now in a corrupted state. Fix: include rollback_failed in the Object.assign in api.ts, and handle it explicitly in onError in config.tsx with a distinct toast (e.g. "CRITICAL: save and rollback both failed — config file may be corrupted").

- **behavior** `apps/web/src/routes/config.tsx` line ~222: Editor may mount before `schemaQuery.data` resolves, permanently losing JSON Schema autocomplete. `beforeMount` fires once on mount — if `schemaQuery.data` is still `undefined` at that moment, the early `return` in `handleEditorBeforeMount` skips schema registration entirely, and the callback identity change never triggers a re-call. Fix: guard the editor block with `&& schemaQuery.data` so it never mounts without the schema: ```tsx {!configQuery.isLoading && !configQuery.isError && schemaQuery.data && ( <> <DiffBanner … /> {issues && …} <div …><Editor … /></div> </> )} ``` This is not just a slow-network edge case — any cold-cache load where the config response races ahead of the schema response hits it. - **behavior** `apps/web/src/lib/api.ts` line ~590 + `apps/web/src/routes/config.tsx` `onError`: `rollback_failed: true` from the server is silently dropped. `putAgentsConfig` doesn't forward it onto the thrown error (`Object.assign` only copies `status`, `issues`, `current_mtime`), so `onError` never sees it and the operator gets a generic "loader rejected config" toast with no indication their on-disk file is now in a corrupted state. Fix: include `rollback_failed` in the `Object.assign` in `api.ts`, and handle it explicitly in `onError` in `config.tsx` with a distinct toast (e.g. `"CRITICAL: save and rollback both failed — config file may be corrupted"`).
fix(config): guard editor mount on schema + distinct rollback toast
Some checks failed
qa / qa (pull_request) Has been cancelled
qa / dockerfile (pull_request) Has been cancelled
96db031fa1
Editor was mounting before schemaQuery.data resolved on cold-cache loads
where the config response races ahead of the schema response, permanently
losing JSON Schema autocomplete (beforeMount fires once; callback identity
changes don't trigger a re-call). Guard the editor block on
`schemaQuery.data`.

`rollback_failed: true` from the server was set on the thrown error but
ignored by the onError type cast — the operator got a generic toast even
when the on-disk file may be corrupted. Add a dedicated branch that
surfaces a distinct CRITICAL toast.

Also extends the loading/error UI to cover the schema query so we don't
flash a blank state between the two queries resolving.
Author
Collaborator

Both findings fixed in 96db031.

  • behavior (editor mount race): editor block now guarded on configQuery.data && schemaQuery.data so Monaco never mounts before the schema resolves. The loading branch was extended to configQuery.isLoading || schemaQuery.isLoading and a sibling config-schema-error branch was added so we don't flash a blank state if only the schema query fails.
  • behavior (rollback_failed dropped): api.ts was already forwarding rollback_failed via Object.assign (line 594) — the actual leak was in onError's type cast omitting it and the else if chain having no branch. Added rollback_failed?: boolean to the cast and a leading branch that surfaces a distinct "CRITICAL: save and rollback both failed — config file may be corrupted" toast.
Both findings fixed in 96db031. - **behavior** (editor mount race): editor block now guarded on `configQuery.data && schemaQuery.data` so Monaco never mounts before the schema resolves. The loading branch was extended to `configQuery.isLoading || schemaQuery.isLoading` and a sibling `config-schema-error` branch was added so we don't flash a blank state if only the schema query fails. - **behavior** (rollback_failed dropped): `api.ts` was already forwarding `rollback_failed` via `Object.assign` (line 594) — the actual leak was in `onError`'s type cast omitting it and the `else if` chain having no branch. Added `rollback_failed?: boolean` to the cast and a leading branch that surfaces a distinct `"CRITICAL: save and rollback both failed — config file may be corrupted"` toast.
code-lead force-pushed boss/455 from 96db031fa1
Some checks failed
qa / qa (pull_request) Has been cancelled
qa / dockerfile (pull_request) Has been cancelled
to 487790c57f
All checks were successful
qa / qa (pull_request) Successful in 9m59s
qa / dockerfile (pull_request) Successful in 14s
2026-04-27 19:04:08 +00:00
Compare
reviewer approved these changes 2026-04-27 19:35:22 +00:00
reviewer left a comment

Both round-1 findings resolved correctly. CI green.

  • Schema guard: editor block now gated on configQuery.data && schemaQuery.data, with an explanatory comment — beforeMount can't fire blind anymore.
  • rollback_failed propagation: api.ts includes it in Object.assign; onError checks it first and shows the distinct "CRITICAL: save and rollback both failed" toast.
Both round-1 findings resolved correctly. CI green. - Schema guard: editor block now gated on `configQuery.data && schemaQuery.data`, with an explanatory comment — `beforeMount` can't fire blind anymore. - `rollback_failed` propagation: `api.ts` includes it in `Object.assign`; `onError` checks it first and shows the distinct "CRITICAL: save and rollback both failed" toast.
code-lead deleted branch boss/455 2026-04-27 19:35:51 +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!475
No description provided.