fe: roster-assistant proposal preview + accept handler (#1215) #1222
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!1222
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "code-lead/1215"
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?
Summary
Replaces the JSON debug block in the roster AI assistant sidebar (#1213) with a structured, editable preview, and wires the accept-to-roster handler.
proposed-agent-card.tsx) with editablename/role/system_prompt/model_tier(+ reason) /skills/tool_permissions/mcp_servers/rationale. Sensitive tools surface an inline warning; MCP chips withsetup_required: trueget a "setup needed" badge.proposal-preview.tsx). Accept button is disabled when nothing is selected.proposal-diff.ts): new (green border + badge), removed (ghost card with strike-through + badge), changed (yellow border + field-level "·" markers). "Show changes only" filter collapses unchanged cards.envelope.catalog_hash(non-blocking).proposal-accept.ts) walks the selected agents and writes each through the manual editor's endpoints:POST /api/config/agent-types(clones a template),PUT /agent-config/system-prompts/:type, per-skillPOST /agents/:type/skills, per-MCPPUT /agent-config/mcp/:id. Per-agent failure surfaces inline with a retry; full success closes the sidebar and invalidates roster query caches.Tests
proposal-diff.test.ts— pure-logic unit tests for the diff (new/removed/changed/unchanged + rename = remove+add + rationale-drift ignored).proposal-preview.test.tsx— Vitest browser-mode component tests covering the deliverables: edit doesn't mutate the original envelope, diff highlights changed/new cards, accept passes only the selected drafts, partial failure surfaces the per-card error, catalog-drift warning, select-all + changes-only filter behaviour.roster-assistant-sidebar.test.tsxadapted to assert the newroster-assistant-previewtestid in place of the retired JSON debug block.Test plan
just qa— typecheck / lint / format / 3417 server tests + 1233 web tests all pass.Closes #1215.
🤖 Generated with Claude Code
Review: fe: roster-assistant proposal preview + accept handler (#1215)
CI ✅ green. Mergeable ✅. Read 100% of the diff.
Overview
Replaces the JSON debug
<pre>block with a real, editable, diffable proposal preview and wires the complete accept-to-roster path. Five new files (~1 000 lines of product code + ~300 lines of tests), all well-documented with module-level JSDoc. The feature covers every deliverable from the issue: edit isolation, diff highlighting, partial-failure retry, catalog-drift warning, select-all, changes-only filter.What's good
makeDraftscreates aMapthat's re-keyed from the props envelope. Field edits produce{ ...agent, [key]: value }spreads, so the originalProposalEnvelopereference is never mutated. The test asserts this directly.computeProposalDiffis clean. Thecanonical()helper correctly sorts primitive arrays (so skill reordering doesn't flag as changed) while leaving object arrays ordered (tool_permissions priority is meaningful). Excludingrationale/model_tier_reasonfrom the compared fields is the right call — those drift every generation.onCardChangere-keys bothdraftsandselectedwhennext.name !== name, andonAcceptClickchecksselected.has(draft.name) || selected.has(original.name)to catch the mid-edit case.lastProposalRefpattern is correct. Using a ref to capture the prior proposal before the state update batches is the right way to carrypreviousProposalforward inrunPropose.Promise.all(tasks)ordering inacceptOne. Step 1 (createAgentType) is awaited before tasks are built, so steps 2–4 can safely parallelise against an already-created type.proposal-diff.test.tscovers all diff kinds + rename + skills reorder + rationale-drift immunity.proposal-preview.test.tsxcovers all six issue deliverables. The sidebar test adapter is minimal and correct.Issues / notes
1. MCP upsert writes a bare stub even for configured entries — verify nulls are inert (medium)
The module header says "we copy them from the catalog's view of the global row when configured", but
CatalogSnapshot.mcp_serversonly carries{ id, name, configured }— no transport details. So every write, even forenabled=trueMCPs, sendscommand: null / url: null.The manual editor uses the same
PUT /agent-config/mcp/:id scope=agent_typeendpoint and works fine, so the server presumably doesn't clobber the global row's transport details with per-type nulls. But the comment is misleading and if someone later adds transport fields toCatalogSnapshotthey'll expect the accept handler to use them. Please update the comment to say explicitly that the per-type row only setsenabledand the global row's transport is inherited unchanged.2. Indeterminate checkbox uses inline callback ref — reactive but non-obvious
This works because React calls inline callback refs on every render (new function reference → old ref called with
null, new ref called with the element). It updatesindeterminatecorrectly but the pattern is non-obvious and slightly wasteful. AuseEffect+useRefwould be cleaner:Not a blocker, but worth tidying.
3. "Show changes only" toggle is visible even when
diff.hasChanges === falseThe control is gated on
previous !== nullbut not ondiff.hasChanges. If the operator refines and the model returns the same proposal, the toggle is visible but checking it collapses every card (all unchanged) and shows the empty-state string. Disabling the checkbox when!diff.hasChangeswould avoid that confusion:Minor UX nit; not blocking.
4. No unit tests for
acceptOneitselfThe write path is exercised indirectly (the preview test mocks
onAcceptat the preview boundary, not atacceptOne). Given partial-failure isolation is a deliverable, a direct test foracceptOnewith mocked API functions would be a good addition in a follow-up — particularly for the skill-skip-if-unknown branch and theenabledderivation logic.Verdict
APPROVED. The core logic is correct, the immutability contract holds, the diff algorithm is sound, and the tests cover the stated deliverables. CI is green. The MCP comment needs a wording fix (item 1) — please land that as a follow-up commit or squash it in before merge; the other items can be tracked as tech-debt. Nothing here blocks shipping.