feat(dashboard): /config route with Monaco JSON editor #475
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!475
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "boss/455"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds a
/configpage with a Monaco editor pinned to a JSON Schema derived from a new Zod schema forconfig/agents.json; PUT validates, atomic-writes, hot-reloads, and rolls back on loader failure.Test plan
just qacleanbun test src/http/handlers/config.test.ts— 13 tests cover GET / schema export round-trip / PUT validation / hot-reload / optimistic-lock / loader-rejection rollbackbun test src/shared/config/agents-config-schema.test.ts— 14 tests cover required-field rejection, JSON Schema export shape, production fixture round-trip/configin the dashboard, edit apipeline.review_threshold_msvalue, hit "Save & reload", confirmgetWebhookConfig().pipeline.review_threshold_msreflects the new value on the next dispatchCloses #455
behavior
apps/web/src/routes/config.tsxline ~222: Editor may mount beforeschemaQuery.dataresolves, permanently losing JSON Schema autocomplete.beforeMountfires once on mount — ifschemaQuery.datais stillundefinedat that moment, the earlyreturninhandleEditorBeforeMountskips schema registration entirely, and the callback identity change never triggers a re-call. Fix: guard the editor block with&& schemaQuery.dataso it never mounts without the schema: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.tsline ~590 +apps/web/src/routes/config.tsxonError:rollback_failed: truefrom the server is silently dropped.putAgentsConfigdoesn't forward it onto the thrown error (Object.assignonly copiesstatus,issues,current_mtime), soonErrornever 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: includerollback_failedin theObject.assigninapi.ts, and handle it explicitly inonErrorinconfig.tsxwith a distinct toast (e.g."CRITICAL: save and rollback both failed — config file may be corrupted").Both findings fixed in
96db031.configQuery.data && schemaQuery.dataso Monaco never mounts before the schema resolves. The loading branch was extended toconfigQuery.isLoading || schemaQuery.isLoadingand a siblingconfig-schema-errorbranch was added so we don't flash a blank state if only the schema query fails.api.tswas already forwardingrollback_failedviaObject.assign(line 594) — the actual leak was inonError's type cast omitting it and theelse ifchain having no branch. Addedrollback_failed?: booleanto the cast and a leading branch that surfaces a distinct"CRITICAL: save and rollback both failed — config file may be corrupted"toast.96db031fa1487790c57fBoth round-1 findings resolved correctly. CI green.
configQuery.data && schemaQuery.data, with an explanatory comment —beforeMountcan't fire blind anymore.rollback_failedpropagation:api.tsincludes it inObject.assign;onErrorchecks it first and shows the distinct "CRITICAL: save and rollback both failed" toast.