Commit graph

113 commits

Author SHA1 Message Date
Viktor Barzin
394a771b67 [beadboard] Add Dispatch-to-Agent button and prompt template
## Context

We want a one-click path from the right-panel task detail into the
claude-agent-service runner, without the user copy-pasting the bead id
into a CLI or another tab. The runner expects a self-contained prompt
that restates the bead id, title, description, acceptance criteria, and
the guard rails the agent must operate under (no push, no file edits,
no terraform/kubectl/helm). The prompt template lives in `src/lib/` so
it can be tested and reused from the server-side dispatch route.

The right-panel button needs to:
- Only appear when the bead is actionable (`open` or `in_progress`).
- Disable itself while the claude-agent-service is already busy (the
  service has a global `asyncio.Lock` — parallel dispatches 409).
- Disable itself when the bead lacks acceptance criteria. An agent that
  doesn't know what "done" looks like burns budget and closes nothing.
- Surface the resulting `job_id` or any 409/error back to the user.

The project has no toast library (no `sonner`, no `react-hot-toast`), so
we render status inline under the button rather than pulling in a new
dependency for this single surface.

## This change

- `src/lib/dispatch-prompt.ts` exports `buildDispatchPrompt(bead)` which
  produces the exact prompt the agent runner expects. Bead id, priority
  (`P<n>`), issue type, description, and acceptance criteria are
  interpolated; `<job_id>` stays a literal placeholder because the agent
  only learns its own id at runtime (env var).
- `src/components/shared/dispatch-button.tsx` is a focused client
  component with three responsibilities:
    1. Poll `GET /api/agent-status` every 5 s while the panel is open
       (plus an initial fetch on mount), mirror `busy` into local state.
    2. On click, `POST /api/agent-dispatch` with `{taskId}`; branch on
       200 / 409 / other.
    3. Render an inline status line under the button (`text-xs`, tone
       driven by `ok | info | error`) — no toast dep required.
  The poll interval self-clears on unmount so closing the panel stops
  network traffic.
- `src/components/shared/thread-drawer.tsx` renders `<DispatchButton>`
  alongside the existing "Edit task" button in the summary section,
  wrapped in a `flex-wrap` so the two controls reflow on narrow panes.
- Registers two new tests in `package.json`'s enumerated test script.

## What is NOT in this change

- The `/api/agent-dispatch` and `/api/agent-status` routes themselves —
  those land in the next commit. The button calls them but the server
  side is intentionally a separate step so each commit can be reviewed
  in isolation.
- No real toast system is introduced; inline status is sufficient.
- No change to how task state transitions on dispatch. The agent itself
  is expected to run `bd update --claim` / `bd close` via the prompt's
  operating rules.

## Test Plan

### Automated

```
$ node --import tsx --test tests/lib/dispatch-prompt.test.ts \
    tests/components/shared/dispatch-button.test.tsx
# tests 7 pass 7 fail 0
```

Covers:
- Bead id appears in opening paragraph and in both `bd note` / `bd close`
  commands.
- Priority rendered as `P<n>`, issue type echoed.
- Description and acceptance criteria quoted verbatim when present.
- `(no description)` / `(no acceptance criteria)` fallbacks when null.
- Guard rails block present (no terraform/kubectl/helm, workspace bd
  path, `bd update … --status blocked` fallback).
- DispatchButton module loads and exports both named and default.

`npm run typecheck` shows only the pre-existing `OrchestratorChatMessage`
type gap in `left-panel.tsx` that reproduces on untouched `main`.

### Manual Verification

1. `npm install`
2. `npm run dev`
3. Open `http://localhost:3000/?task=<some-open-bead-id>`
4. Expected: "Dispatch to Agent" button next to "Edit task" in the
   right-panel summary section.
5. Button disabled on beads with `status in {closed, blocked, deferred}`
   (they don't render the button at all).
6. Button disabled on beads missing acceptance criteria, with tooltip
   "Task is missing acceptance criteria — cannot dispatch.".
7. Click: UI flips to "Dispatching…"; once the next commit is merged,
   the agent-dispatch route will surface a `job_id` (today it returns
   404 which renders as "Dispatch failed (HTTP 404)").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:04:34 +00:00
Viktor Barzin
845e90d7c0 [beadboard] Add quality filters for acceptance criteria and description length
## Context

The left-panel task list surfaces every bead regardless of whether the task
has been specified thoroughly enough for an agent to pick it up. Tasks with
empty `acceptance_criteria` or very short descriptions are low-signal noise
that muddles the navigation spine — especially now that we plan to dispatch
tasks to Claude agents (which need concrete instructions to succeed).

Epics are deliberately exempt: their role is grouping, not execution, so
requiring acceptance criteria / long descriptions on epics would hide the
entire backbone of the navigation.

## This change

- Plumbs `acceptance_criteria` end-to-end: adds the optional field on
  `BeadIssue`, and reads it both from `.beads/issues.jsonl` (parser) and
  Dolt SQL (`read-issues-dolt.ts` row shape + normalizer).
- Extends `LeftPanelFilters` with `hideNoAcceptance` and
  `hideShortDescription` (both default `true`) in the source-of-truth
  `hooks/use-url-state.ts` and in the re-exported shadow type on
  `components/shared/left-panel.tsx`.
- Updates `isTaskMatch` in both `left-panel.tsx` and `left-panel-new.tsx`
  to skip non-epic tasks lacking acceptance criteria or with a description
  shorter than `SHORT_DESCRIPTION_MIN_LENGTH` (200 chars). Epics bypass
  both filters via the `issue_type === 'epic'` guard.
- Exposes `isTaskMatch` from `left-panel.tsx` so the filter tests can
  assert behavior directly (previously only `shouldHideEpicEntry` was
  exported).
- Adds two checkboxes under the existing "Hide Closed" button in both
  left-panel variants (legacy `left-panel.tsx` and the one unified-shell
  currently wires up — `left-panel-new.tsx`).
- Seeds both new filter flags as `true` in the `UnifiedShell` default
  state so fresh sessions see the high-signal view without toggling.

## What is NOT in this change

- No mutation of `bd` / CLI behavior. Filters are purely UI-level.
- No localStorage persistence for the two new flags — existing
  `hideClosed` is also React-only, so parity is preserved. If/when we
  persist any of these, all three move together.
- No change to the `metadata.acceptance` path used by `kanban.ts`'s
  `hasQualitySignal` — that's a separate signal with its own callers.

## Test Plan

### Automated

Tests run from `/home/wizard/code/beadboard`:

```
$ node --import tsx --test tests/components/shared/left-panel-filtering.test.ts
# tests 15
# pass 15
# fail 0
# duration_ms 604
```

All 15 filter cases pass: 6 pre-existing `shouldHideEpicEntry` cases plus
9 new `isTaskMatch` cases covering acceptance-criteria-empty hides,
acceptance-criteria-disabled shows, epic exemption, 199/200-char
description boundary, null description, and short-description flag
disabled.

Related suites still green:

```
$ node --import tsx --test tests/components/shared/left-panel.test.tsx \
    tests/components/shared/unified-shell-hide-closed-contract.test.ts
# tests 7 pass 7 fail 0
```

Pre-existing failures in `tests/hooks/url-state-integration.test.ts`
(`view=activity` cases) and one pre-existing typecheck error in
`left-panel.tsx` thread prop are unrelated — both reproduce on `main`
before this change.

### Manual Verification

1. `npm install`
2. `npm run dev`
3. Open `http://localhost:3000` with bd project loaded.
4. Expected: two new checkboxes appear under "Hide Closed" in the left
   sidebar — "Hide tasks without acceptance criteria" and
   "Hide tasks with short description (<200 chars)". Both checked by
   default.
5. Toggle each off. Expected: additional beads appear in epic expansions
   (tasks that were previously hidden because they lack quality signal).
6. Confirm epics remain visible regardless of the checkbox state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:00:51 +00:00
zenchantlive
fabd748833 docs: rewrite README + add under-construction banner to orchestrator panel
README.md:
- Remove duplicate sections, stale info, and broken markdown
- Add bb-pi orchestrator section with honest WIP status and known issues
- Clarify bd vs bb/beadboard CLI distinction
- Add cross-platform support section (Windows, macOS, Linux)
- Note Dolt as optional, document JSONL fallback
- Reference Pi SDK and community tools listing

orchestrator-panel.tsx:
- Add amber "Under construction" banner with link to track progress
- Visible on every orchestrator panel render

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:33:15 -05:00
zenchantlive
d335e5bf71 fix: orchestrator button + Pi SDK session error
- Move leftSidebarMode from URL state to local useState in unified-shell,
    avoiding force-dynamic router round-trip that made the button appear broken                                           - Replace fileURLToPath(new URL(..., import.meta.url)) with process.cwd()
    in bb-pi-bootstrap.ts — import.meta.url is a webpack:// URL in Next.js,
    causing cross-realm TypeError when passed to Node.js fileURLToPath()
2026-03-24 19:02:04 -05:00
zenchantlive
18fbafdce4 refactor: extract agent bounded context + fix SSE comments + cleanup unused
- Extract src/lib/agent/ bounded context with types, registry, messaging
- Add comments_count to BeadIssue for SSE comment detection
- Create batch endpoints for mail/reservations APIs
- Add memory validation to session-preflight
- Remove unused empty dirs (mockup, sessions, timeline)
- Move stashes to docs/references, gitignore them
2026-03-04 22:06:40 -08:00
ZenchantLive
85898a433a feat(ui): show coordination inbox and reservation context 2026-03-03 18:41:39 -08:00
ZenchantLive
b5db7a7753 checkpoint: pre-split branch cleanup 2026-03-03 16:43:42 -08:00
zenchantlive
64a5129412 WIP: investigating frontend/Dolt data mismatch
- Added memory-anchor filter to left-panel.tsx
- Removed issues.jsonl fallback in read-issues.ts (Dolt-only)
- Frontend still shows stale data despite these changes
- Root cause NOT identified - see NEXT_SESSION_PROMPT.md for details
2026-03-02 19:13:20 -08:00
zenchantlive
835018c183 Add beads: Skill v4 epic (1bg), Quality gates (n1h), Brainstorm epics (jq5, 2e6), memory nodes 2026-03-01 22:56:18 -08:00
zenchantlive
87ce7dea10 fix: extract agent name from title/labels; add navigation to blocked modal 2026-03-01 21:38:05 -08:00
zenchantlive
922d574a5a fix: count derived blocked tasks in criticalAlerts 2026-03-01 21:31:12 -08:00
zenchantlive
79f4bacb5c fix: use theme CSS variables in BlockedTriageModal 2026-03-01 21:29:02 -08:00
zenchantlive
c8c91736b8 fix: remove buildProjectContext usage causing build error 2026-03-01 21:22:46 -08:00
zenchantlive
842f931f71 Wire BlockedTriageModal to UnifiedShell and TopBar
- Add import for BlockedTriageModal in unified-shell.tsx
- Add blockedTriageOpen state and handlers
- Pass onOpenBlockedTriage prop to TopBar
- Render BlockedTriageModal at end of UnifiedShell
- Add onOpenBlockedTriage prop to TopBarProps interface
- Update blocked items button onClick to use onOpenBlockedTriage
2026-03-01 21:19:11 -08:00
zenchantlive
29eefaf7ec feat: add BlockedTriageModal component with tests
- Create BlockedTriageModal component at src/components/shared/blocked-triage-modal.tsx
- Implements modal with blocked task triage functionality
- Uses deriveBlockedIds and buildBlockedByTree from kanban lib
- Each row shows blocker chain and has inline archetype picker
- Modal is scrollable and closes via Escape/close button
- Add corresponding tests at tests/components/blocked-triage-modal.test.tsx
- Register test in package.json test script
2026-03-01 21:12:46 -08:00
zenchantlive
114bb18188 fix(telemetry-strip): use real activity events from /api/activity + SSE
TelemetryStrip now fetches from the same /api/activity endpoint and
subscribes to the same /api/events SSE stream as ActivityPanel. The
minimized dots use getEventTone() colors matching the full feed exactly
(created=green, closed=amber, reopened=blue, etc.) instead of derived
task status counts.

Co-Authored-By: Oz <oz-agent@warp.dev>
2026-03-01 18:23:53 -08:00
zenchantlive
a0787f85de fix(ux): remove dup Signal btn, add minimize to all feeds, strip shows recent events
- Remove duplicate Signal (telemetry) button from DAG nodes
- Add minimize (ChevronLeft) button to Epic Command Feed view, not just global feed
- TelemetryStrip now shows 8 most recently updated tasks as status-colored dots
  instead of static status counts — reflects live activity like the full feed does
- Each dot is colored by task status (blocked=red, active=amber, ready=green)
  with hover tooltip showing task id, title, and status

Co-Authored-By: Oz <oz-agent@warp.dev>
2026-03-01 18:20:49 -08:00
zenchantlive
c246ceaf21 feat(ux): consolidate Launch Swarm + telemetry UX with minimized strip
- Removed broken LaunchSwarmDialog (formula-based) from TopBar/LeftPanel
- All Rocket buttons (TopBar, LeftPanel, DAG nodes, social cards) now open
  AssignmentPanel (archetype-based) which actually works
- Every Rocket clears taskId first so assignMode && !taskId condition passes
- Conversation button priority: taskId always shows conversation, not assign panel
- Added TelemetryStrip: minimized right sidebar with status dots when non-telemetry
  panel (conversation/assignment) is active
- Live feed has minimize button → restores last taskId or assignMode
- DAG nodes: Signal icon → restores telemetry feed
- Social button on DAG nodes: single router.push to avoid race (setView + setTaskId)
- Fixed social card message button: opens right panel with drawer:closed (no popup)

Co-Authored-By: Oz <oz-agent@warp.dev>
2026-03-01 18:17:58 -08:00
zenchantlive
b82c176e12 fix: null-safe formulas, constrain assign row to card, fix TopBar dialog mounting
Co-Authored-By: Oz <oz-agent@warp.dev>
2026-03-01 17:30:01 -08:00
zenchantlive
d6f88517b7 feat(8ij.5): wire ?swarm=X URL param to highlight tasks in both views
Co-Authored-By: Oz <oz-agent@warp.dev>
2026-03-01 17:25:08 -08:00
zenchantlive
ae7f13c3af feat(8ij.4): add LaunchSwarmDialog to TopBar as global action
Co-Authored-By: Oz <oz-agent@warp.dev>
2026-03-01 17:22:25 -08:00
zenchantlive
6b8aa408c8 feat(8ij.3): add Launch Swarm action to LeftPanel epic rows
Co-Authored-By: Oz <oz-agent@warp.dev>
2026-03-01 17:21:19 -08:00
zenchantlive
6d560b6c49 feat(8ij.2): add inline assign affordance to SocialCard
Co-Authored-By: Oz <oz-agent@warp.dev>
2026-03-01 17:18:13 -08:00
zenchantlive
b996d889d5 feat(8ij.1): extract useArchetypePicker hook from AssignmentPanel
Co-Authored-By: Oz <oz-agent@warp.dev>
2026-03-01 17:15:30 -08:00
ZenchantLive
861ae89491 fix: wire conversation panel to DAG nodes with toggle support
- Add MessageSquare icon to GraphNodeCard; prop-thread onConversationOpen
  and selectedTaskId through WorkflowGraph node data (no useUrlState
  inside ReactFlow nodes — avoids context/timing issues)
- Fix ContextualRightPanel: check taskId before epicId so clicking the
  conversation icon always opens ThreadDrawer even when an epic filter
  is active
- setEpicId now clears task from URL so selecting an epic resets any
  open conversation thread
- handleGraphSelect toggles: second click on same node calls setTaskId(null)
  closing the right panel
- Add onSelect to WorkflowGraph flowModel deps to prevent stale callbacks
- Fix ContextualRightPanel onClose no-ops: wired to setTaskId(null) /
  setSwarmId(null) so back button works
- Right panel always visible (removed panel==='open' gate in UnifiedShell)
- SmartDag task grid: horizontal scroll, fixed-width cards, hideClosed=true
- Add <Suspense> in page.tsx for useSearchParams compatibility
- Enable dolt auto-start in .beads/config.yaml
- Add 14 static analysis tests (graph-node-conversation.test.tsx)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 13:51:46 -08:00
ZenchantLive
7d37d02af1 feat(phase0+1): wire URL context to ContextualRightPanel
Phase 0:
- UnifiedShell: pass blockedOnly to SocialPage; wire TopBar with live counts
- thread-drawer: show real issue.status instead of hardcoded "In Progress"
- social-page: fix onJumpToActivity to open right panel (not dead ?view=activity)

Phase 1:
- contextual-right-panel: add taskId branch (ThreadDrawer embedded) and swarmId
  branch (MissionInspector via SwarmIdBranch inner component); ActivityPanel
  remains the no-selection fallback

All 207 tests pass; no new typecheck errors.
Closes beadboard-r1i (Phase 1: Contextual Right Panel)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 14:59:09 -08:00
zenchantlive
fccb2dede7 fix(theme): overhaul light mode, remove silly themes
Light mode fixes:
- Darker surfaces (not pure white)
- Proper text colors (dark slate, not white)
- More opaque status colors for visibility
- Grey backdrop instead of harsh white

Removed themes (keeping only 3 good ones):
- Midnight (looked silly)
- Forest (looked silly)
- Dusk (looked silly)

Kept themes:
- Aurora (northern lights)
- Contrast (high contrast neon)
- Light (soft grey, now fixed)
2026-02-26 16:42:23 -08:00
zenchantlive
958230f2ef feat(theme): complete theme overhaul with 6 polished themes
- Aurora: Real northern lights colors (teal/violet on deep blue-black)
- Midnight: Deep ocean blues with violet and cyan
- Forest: Rich forest greens with golden sunlight
- Dusk: Warm sunset oranges and corals
- Contrast: Pure black with neon accents
- Light: Clean professional light mode (NEW)

All themes have:
- Consistent status colors (green/amber/red)
- Distinct surface layers for visual hierarchy
- Theme-appropriate accent glows
- Improved contrast and accessibility
2026-02-26 16:40:09 -08:00
zenchantlive
8a1dc2039a fix(graph): themable backgrounds for graph-section
- Replace hardcoded blue radial gradient with theme tokens
2026-02-26 16:34:52 -08:00
zenchantlive
f2cd6fa11c fix(graph): make DAG background themable and improve contrast
- Replace hardcoded blue radial gradient with theme variable
- Update graph-view to use new token system
- Ensure status colors (red/green/amber) remain consistent across all themes
- DAG background now changes with theme while maintaining card visibility
2026-02-26 16:34:13 -08:00
zenchantlive
756a1dec2b fix(theme): increase contrast in aurora theme surface layers
- Make surface layers more distinct (0f0e0d → 1a1816 → 252320 → 302e2a → 3c3934)
- Increase border visibility (18%, 35%, 55% opacity)
- Ensure header, sidebar, main content are visually distinct
2026-02-26 16:27:14 -08:00
zenchantlive
a47f21e769 feat(theme): add theme toggle dropdown to header
- Create ThemeToggle component with dropdown menu
- Shows all 5 themes with descriptions
- Persists choice to localStorage
- Updates data-theme attribute instantly
- Add palette icon to top bar
2026-02-26 16:12:59 -08:00
zenchantlive
6564272987 refactor(theme): migrate high-priority components to new token system
- social-card.tsx: status colors, borders, surfaces
- assignment-panel.tsx: all hardcoded hex colors replaced
- graph-node-card.tsx: node styles, borders, glows, text colors
2026-02-26 16:04:19 -08:00
zenchantlive
fcc1482967 feat(theme): create unified theme system with semantic tokens and migrate core layout 2026-02-26 15:49:44 -08:00
zenchantlive
b31e8ddade refactor(theme): distinct warm charcoal layers - header darker, sidebar/main/card all different 2026-02-26 15:33:08 -08:00
zenchantlive
fc328adf31 refactor(theme): lighten backgrounds, simplify header/sidebar/panel styling 2026-02-26 15:27:21 -08:00
zenchantlive
227012ba1a refactor(theme): simplify gradients while preserving status colors on cards 2026-02-26 15:23:42 -08:00
zenchantlive
a7a0a66c4d refactor(kanban): simplify column backgrounds to flat colors with top borders 2026-02-26 15:21:36 -08:00
zenchantlive
f1eae0c8b0 refactor(ui): simplify status badges to flat colors with accent borders 2026-02-26 15:20:49 -08:00
openhands
a8079813b8 Merge origin/main into feature/assign-archetypes-to-tasks-ui
Resolved conflicts:
- .gitignore: kept both bd.sock.startlock and .beadboard/ entries
- package.json: kept feature branch test script (explicit enumeration)
- API routes: kept dynamic export + isValidProjectRoot from main
- globals.css: kept HEAD slideInFromRight animation
- use-beads-subscription.ts: kept HEAD onopen handler
- realtime.ts: kept main console.log in emit()
- snapshot-differ.ts: kept main type-aware dependency diff

Blue colors preserved from feature branch.
2026-02-26 18:50:18 +00:00
zenchantlive
5ff4469dd5 chore: misc updates and fixes
- lib/utils.ts: Add getArchetypeDisplayChar helper (duplicate of src/lib)
- globals.css: Style updates
- swarm-command-feed.tsx: Component updates
- telemetry-grid.tsx: Use getArchetypeDisplayChar
- test-watcher.ts: Test utility updates
2026-02-26 10:25:31 -08:00
zenchantlive
784fe6e88f fix: add templateId default to issue creation paths
- parser.ts: Add templateId: null to default BeadIssue
- read-issues.ts: Add templateId: null when reading issues
- swarm-workspace.tsx: Update mock data with templateId
2026-02-26 10:23:00 -08:00
zenchantlive
e69efdf5e1 feat(ui): add epic-level template assignment to assignment panel
- Add Epic Template section when an epic is selected
- Show template name, description, and team roster
- Template picker applies template to epic via metadata
- Add Remove Template functionality
- getTemplateId helper reads from metadata.templateId
- Task Assignment section only shows for non-epic tasks
- Collapsible sections for Needs Agent, Pre-assigned, Squad Roster
2026-02-26 10:22:51 -08:00
zenchantlive
6250335dc8 feat(ui): add resizable sidebar panels with persistence
- use-panel-resize hook: Drag-to-resize with localStorage persistence
- ResizeHandle component: Visual drag handle with hover effects
- UnifiedShell: Flexbox layout with resizable left/right panels
- RightPanel: Removed internal width handling (now controlled by shell)
2026-02-26 10:22:33 -08:00
zenchantlive
ebd3ffcbbe feat(ui): add picker modal components for archetype and template selection
- ArchetypePicker: Full-screen modal with backdrop blur, 2-column grid,
  Select/Edit/Create actions, 800px width for readability
- TemplatePicker: Same pattern with team size indicator and built-in badge
- Both pickers support backdrop click-to-close and keyboard navigation
2026-02-26 10:22:21 -08:00
zenchantlive
7b27f673fe feat(ui): enhance archetype and template inspectors with customization
- Add getArchetypeDisplayChar() and getTemplateDisplayChar() utils
- Add getTemplateColor() utility function
- ArchetypeInspector: Add color palette (30 presets), emoji selector (40 emojis),
  capability autocomplete, live preview, clone functionality
- TemplateInspector: Same enhancements plus team composition editor
2026-02-26 10:22:11 -08:00
zenchantlive
211e503409 feat(graph): enforce single archetype per task
## Design Decision
Per bd (bead) system design, a task should have only ONE agent archetype
assigned at a time. This provides clear ownership and simpler mental model.

## What Changed
When assigning a new archetype:
1. Remove any existing agent: labels first (DELETE API)
2. Then add the new agent: label (POST API)
3. Optimistic UI updates to match

## Why This Makes Sense
- Clear ownership: 'Who's working on this?'
- Simpler coordination between tasks
- Matches how bd/agent orchestration is intended to work
- Reassigning is still possible (just click a different archetype)

## UI Behavior
- If task has 'coder' assigned, clicking 'architect' will:
  1. Remove 'coder' label
  2. Add 'architect' label
- Dropdown shows 'Assigned' badge on current archetype
- X button still available to unassign completely

## Test Coverage
Added graph-node-single-archetype.test.tsx with 5 tests:
- Removes existing labels before adding new
- Calls DELETE before POST
- Only allows one archetype per task
- Preserves non-agent labels
- Returns early if same archetype clicked
2026-02-24 17:04:44 -08:00
zenchantlive
bd3b3da30a fix(graph): prevent SSE overwrites of optimistic label updates
## The Bug
User reported: 'An archetype can only exist on one task at a time - when
I try to make the next task have the same arch, it deleted the one I
added prior.'

## Root Cause
The SSE subscription (useBeadsSubscription) refreshes data from the server
whenever ANY change happens. When user assigns an archetype:

1. User clicks assign -> optimistic update adds label locally
2. SSE fires (from previous operation or heartbeat) -> fetches fresh data
3. useEffect syncs localLabels with data.labels (which doesn't have the
   new label yet)
4. Label disappears from UI
5. Eventually API completes and another SSE refresh brings it back

This race condition causes labels to flicker or disappear entirely.

## The Fix
Track pending optimistic labels in a useRef Set, and merge them with
incoming server data during sync:

1. pendingOptimisticLabels = useRef<Set<string>>(new Set())
2. When optimistically adding: add to pending set
3. useEffect merge: combine server labels + pending labels
4. After API completes: remove from pending set

This ensures optimistic labels survive SSE refreshes.

## Test Coverage
Added graph-node-labels-optimistic.test.tsx with 10 tests:
- Uses useRef for tracking
- Tracks in a Set
- Preserves labels during sync
- Adds/removes from pending set
- Handles multiple concurrent operations
- Per-node state isolation

## Verification
- typecheck: pass
- lint: pass (0 errors)
- test: all pass
2026-02-24 16:48:16 -08:00
zenchantlive
fbfe393f6d chore: checkpoint related UI improvements and supporting components
Various supporting changes made during the assign archetypes feature development:

- Added contextual-right-panel.tsx and swarm-command-feed.tsx
- Updated activity-panel.tsx with new features
- UI improvements to left-panel, mobile-nav
- Test updates for url-state-integration, mobile-nav, top-bar
- Package.json updates for dependencies
- Global CSS refinements

These changes support the main assign archetypes feature but are
not directly part of its core functionality.
2026-02-24 16:25:45 -08:00
zenchantlive
93b3c33976 feat(core): add SmartDag and supporting infrastructure for assign mode
## Context
This commit adds the supporting infrastructure that makes the assign
feature work end-to-end.

## Components Added/Modified

### SmartDag
- Main view component for graph-based task management
- Integrates TaskCardGrid and WorkflowGraph
- Has 'Assign' mode toggle button
- Passes archetypes and assignMode to WorkflowGraph
- Manages filter state (hideClosed, sortReadyFirst, etc.)

### useGraphAnalysis Hook
- Extracted graph analysis logic for reuse
- Returns: actionableNodeIds, cycleNodeIdSet, blockerTooltipMap, etc.
- Used by both SmartDag and AssignmentPanel
- Ensures consistent 'actionable' definition across components

### UnifiedShell
- Added assignMode state
- Added selectedAssignIssue state
- Renders AssignmentPanel when in graph view + assign mode
- Wires up onAssignModeChange and onSelectedIssueChange callbacks

## Design Philosophy
- Shared hook means single source of truth for 'actionable'
- Clean separation between view (SmartDag) and sidebar (AssignmentPanel)
- URL state preserved for navigation

## Test Coverage
- SmartDag tests: 12 tests covering buttons, callbacks, imports
- useGraphAnalysis tests: 6 tests covering cycle detection, blockers
- UnifiedShell tests: 9 tests covering state and rendering
2026-02-24 16:16:10 -08:00