feat(dashboard): /config form — routing + skills sections #478
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!478
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "dev/457"
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?
Implements the Routing and Skills tabs in the
/configper-type panel with live-data chip inputs.Routing tab:
routes_labels,reviews_labelschip inputs with suggestions fromGET /config/repo-labels(5-min server-side cache);reviews_authorschips with configured agent forgejo_users;dispatchabletoggle;roledropdown.Skills tab:
skill_overrideskey→value row editor with+Add override/×delete affordance; both columns use<datalist>suggestions fromGET /agents/skills.Backend:
GET /config/repo-labelsadded toconfig.ts+ registered inmain.ts.Test plan
RoutingSectiontests: adding/removing chips updates staged state without callingPUT /config/agentsSkillsSectiontests: adding/removing override rows updates staged state without writingSectionPanetests: Routing tab rendersRoutingSection, Skills tab rendersSkillsSectionCloses #457
CI green. All AC from #457 are met — chip inputs, dispatchable toggle, role dropdown, skill overrides editor, backend endpoint + cache. One blocking bug before merge.
behavior
apps/web/src/routes/config.tsxSkillsSection.updateKey(lines 628–633): silent data loss when the user renames a new (empty-keyed) row to a key that already exists.Concrete repro: operator has
skill_overrides: { implement: "custom-impl" }, clicks "+ Add override", then types "implement" character by character in the new row's key field. EachonChangefiresupdateKey(currentDraft, nextDraft). When the draft finally matches the existing key, the loop assignsnext["implement"] = ""(the new row's empty value) after assigningnext["implement"] = "custom-impl"from the preserved entry — the last write wins, silently wiping the existing value.Fix: build
nextwithout the old key first, then insert the renamed entry only if it doesn't collide with an existing key (or surface a validation error):The early-return guards the collision; reconstructing without the old entry first ensures insertion order is stable. A test with
initial={{ implement: "foo" }}that adds a row and renames it to "implement" would catch this.Fixed in
beef789. Early-return on collision (newKey in overrides), then rebuildnextwithout the old key before inserting the renamed entry — matches the suggested approach exactly.Round-1 finding addressed:
updateKeynow has the collision guard (if (newKey !== oldKey && newKey in overrides) return) and rebuildsnextwithout the old key before inserting, eliminating the silent-overwrite. CI green (run #2377, 9m42s).