feat(web): BreakdownRulesSection for architect Routing fields (AR-2) #1208
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/agent-hooks!1208
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "code-lead/1202"
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?
Closes #1202.
Summary
When the operator opens the type-editor drawer for
architect, theRouting accordion now renders a
/breakdownrules editor instead ofthe worker-oriented chip lists. Worker types (
dev,reviewer, …)still mount the original
RoutingSection. Only the architect branchis replaced — every other section continues to work uniformly since AR-1.
The new
BreakdownRulesSectionedits the four architect-specificfields that drive the
/breakdownskill:example_prompts—<ChipInput>over free-text prompts. Enter / comma commit.label_rules— table of{ pattern, exclude_pattern?, label }. Pattern + Exclude are regex with inlinetry { new RegExp() }validation; invalid rows flagaria-invalid+ an error caption but still mirror the draft (server-sideagents-config-schema.tsis the hard gate at the wire boundary).assignee_rules— table of{ when_label, assign_to }, both autocompleting fromrepoLabels/agentUsersvia<datalist>.default_assignee— single-line autocomplete fromagentUsers, with the factory fallback hinted in the placeholder.All four flow through
onChange({ ...config, <field>: next })to landin the page's
typesDraft→ debounced autosave →putAgentConfig→sub-resource splitter.
example_prompts+default_assigneeride theidentity endpoint (they live on the
agent_typerow server-side);label_rules+assignee_rulesride the routing endpoint.What changed
apps/web/src/features/agents/sections/breakdown-rules-section.tsx— new component.apps/web/src/features/agents/sections/section-pane.tsx— Routing branch picksBreakdownRulesSectionwhentypeName === "architect".apps/web/src/features/agents/sections/index.ts— barrel export.apps/web/src/lib/api/agent-config-fleet.ts— extendAgentTypeConfigwith the four fields + theLabelRule/AssigneeRuletypes; teachcomposeAgentTypeConfigto read them out of the resolver response; wireexample_prompts+default_assigneeintoIDENTITY_FIELDSandlabel_rules+assignee_rulesintoROUTING_FIELD_PAIRS.Test plan
breakdown-rules-section.test.tsx— 17 tests covering Enter-to-commit chips, label_rules add/edit/exclude/remove, invalid-regex caption, assignee_rules autocomplete, default_assignee placeholder + datalist, payload integrity.agents.roster.test.tsx— AR-2 integration suite: architect drawer mountsBreakdownRulesSection(RoutingSection is absent); editing a label rule debounces aPUTcarryinglabel_rules; worker (dev) drawer still mounts the originalRoutingSection.just qa— 3362 server tests, 41 sections tests, format / lint / SQL-layer / paraglide / i18n-string / flow-schema all green.🤖 Generated with Claude Code
Review — AR-2: BreakdownRulesSection (feat/web, #1202)
CI ✅ green. APPROVED — solid implementation with good test coverage. A few non-blocking observations below.
What the PR does
Replaces the
RoutingSectionbody with a newBreakdownRulesSectionwhen the drawer is opened for thearchitecttype. Four architect-specific fields are now editable:example_prompts,label_rules,assignee_rules, anddefault_assignee. The splitter correctly routesexample_prompts/default_assigneethrough the identity endpoint andlabel_rules/assignee_rulesthrough the routing endpoint.All other types continue to use the original
RoutingSection— the change is surgically isolated to thearchitectbranch ofsectionBody().Correctness
composeAgentTypeConfig— type-safe reads.default_assigneeandexample_promptsalready existed inAgentTypeSettingsResponse.resolvedonmain; the PR correctly reads them fromrrather thanr.routing.label_rules/assignee_rulesare added to the routing sub-object and read fromr.routing. Both paths land on the right wire endpoints inputAgentConfig. ✓Splitter diff guard. The routing body only includes fields that changed (
routingBody[dst] = n[src] ?? []is gated insideif (!eq(...))) — so a worker type savingroutes_labelswon't spuriously sendlabel_rules: []to its routing endpoint. ✓exclude_patternclean-up. Stripping the key when the trimmed value is""keeps the wire payload clean and matches the schema's optional semantics. ✓default_assigneeclean-up. Destructuring and omitting the key on clear is consistent with how the server falls back to the factory default. ✓isValidRegex. Treating""as valid is intentional and correct — the rule is simply inert. Inlinearia-invalid+aria-describedbywiring is accessible. ✓Non-blocking observations
1. Empty
when_label/assign_torows go straight to the autosave debounce.Adding an assignee rule row initialises it as
{ when_label: "", assign_to: "" }. After 800 ms the debouncer fires and the splitter detects a diff vs. the server state, sending that empty row. The server schema is the hard gate (as the PR description states), so this will surface as a toast error if the operator doesn't fill the row quickly enough. The same applies tolabel_rules. Neither is new behaviour introduced in isolation by this PR — it's the same design trade-off as the rest of the table-based sections — but both tables appear together here for the first time, so it's worth keeping in mind when the UX is reviewed end-to-end (e.g. debounce could be skipped when any row is empty).2.
when_patternin the type but absent from the UI.AssigneeRuledeclareswhen_pattern?: string, and the JSDoc says "Eitherwhen_labelorwhen_patternis set". The component only renders awhen_labelinput — a rule that was created withwhen_pattern(not currently possible since this is new) would be round-tripped opaquely on save but can't be edited or cleared from the UI. Fine for AR-2 scope; just worth tracking ifwhen_patternrouting is implemented later.3. No test for invalid
exclude_patternregex.The
patternfield has an explicit"[unclosed"test (witharia-invalid+ error caption assertions). Theexclude_patternfield uses identical validation logic but has no symmetric test. The code is obviously correct by symmetry, but an explicit test would close the gap.4. Hardcoded
"(default: dev)"placeholder.The factory fallback is inlined in the component. If the server default is ever changed the UI hint will silently go stale. Fetching it from the derived config or a constant would be more resilient — low priority given how stable the factory default is.
Tests
17 unit tests covering every field + the invalid-regex and clear-strips-key edge cases, plus 3 integration tests verifying the drawer swap, debounced PUT payload, and the worker fallback. Coverage is thorough. The
vi.useFakeTimers()/vi.useRealTimers()fencing around the debounce test is well-structured.LGTM. The observations above are all non-blocking. Merging when ready.