refactor: BaseCard hard style shadow, SocialCard blocking lists, AgentAvatar ZFC states
This commit is contained in:
parent
54729c72f6
commit
c74a4098e7
9 changed files with 231 additions and 85 deletions
|
|
@ -35,7 +35,6 @@
|
|||
{"id":"bb-3wy","title":"Postmortem: stale bead status refresh regression and SSE recovery","description":"Reference record for stale status issue where BeadBoard required manual refresh after bd updates. Captures root causes, applied fixes, and verification commands for future triage.","acceptance_criteria":"Bead contains root cause timeline, exact files changed, and reproducible verification steps.","notes":"Root cause timeline:\\n1) Data freshness drift: UI read path consumed .beads/issues.jsonl, but bd updates could be newer in DB before JSONL sync.\\n2) Live update gap: SSE depended on file watcher events that did not reliably fire for external bd updates.\\n3) Fallback bug: last-touched polling compared file content; repeated updates on same issue kept content stable while only mtime changed.\\n\\nApplied fixes:\\n1) Prefer live bd reads with fallback to JSONL: src/lib/read-issues.ts, src/lib/aggregate-read.ts, src/app/page.tsx, src/app/graph/page.tsx, src/app/api/beads/read/route.ts.\\n2) Expand watcher targets to include .beads/beads.db-wal and .beads/last-touched: src/lib/watcher.ts.\\n3) Add /api/events fallback poll on last-touched mtime (not content): src/app/api/events/route.ts.\\n4) Add regression tests: tests/lib/watcher.test.ts (db + wal events).\\n\\nVerification commands:\\n- npm run typecheck\\n- npm run lint\\n- npm run test\\n- End-to-end probe: connect to /api/events then run \bd update bb-dcv.2 -s \u003cstatus\u003e and confirm \u001bvent: issues.\\n- Manual UI check: Kanban open, run bd update status toggles, confirm no full page refresh needed.\\n\\nOperational note for future agents:\\nIf behavior appears unchanged after patching /api/events, restart dev server to load route changes.","status":"closed","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-13T15:36:09.8136541-08:00","created_by":"zenchantlive","updated_at":"2026-02-13T15:36:29.3940253-08:00","closed_at":"2026-02-13T15:36:29.3940253-08:00","close_reason":"Postmortem captured for stale status refresh regression, including root cause timeline, code-level fixes, verification commands, and operational restart note.","labels":["postmortem","realtime","sse","status"]}
|
||||
{"id":"bb-54x","title":"Agent: graph-integrator","status":"open","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-15T23:02:49.3962836-08:00","created_by":"zenchantlive","updated_at":"2026-02-15T23:03:12.0341891-08:00","labels":["gt:agent","role:graph"],"agent_state":"working","last_activity":"2026-02-15T23:03:12.0336623-08:00"}
|
||||
{"id":"bb-5am","title":"Agent: topbar-builder","status":"open","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-15T22:12:53.3731186-08:00","created_by":"zenchantlive","updated_at":"2026-02-15T22:29:37.4527304-08:00","labels":["gt:agent","role:ui"],"agent_state":"working","last_activity":"2026-02-15T22:29:37.4522038-08:00"}
|
||||
{"id":"bb-5ho","title":"pulse:bb-silver-castle:1771120187848","status":"open","priority":2,"issue_type":"event","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T17:49:48.5077401-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T17:49:48.5077401-08:00","ephemeral":true}
|
||||
{"id":"bb-5pw","title":"test-swarm-1","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-15T23:56:16.2214116-08:00","created_by":"zenchantlive","updated_at":"2026-02-15T23:56:16.2214116-08:00","labels":["gt:agent","swarm:test-swarm-1"]}
|
||||
{"id":"bb-6aj","title":"Project Registry and Multi-Project Scanner","description":"Deliver a Windows-first multi-project registry and discovery pipeline: persist project roots in the user profile, expose add/remove/list APIs, and scan safe roots to find .beads directories. Normalize all paths to stable identity keys and support aggregate views without full-drive traversal by default.","acceptance_criteria":"Projects can be added/removed/listed and discovered via scanner with deterministic normalization.","notes":"UI productization backlog added (2026-02-12): bb-6aj.6 design gate -\u003e bb-6aj.7 shared scope state -\u003e bb-6aj.8 project manager panel + bb-6aj.9 scanner UX + bb-6aj.10 scoped reads -\u003e bb-6aj.11 aggregate mode -\u003e bb-6aj.12 verification evidence. This sequence turns existing backend scanner/registry foundations into end-user multi-project workflows.\n2026-02-13 epic completion: UI productization chain complete (bb-6aj.6 -\u003e .7 -\u003e .8/.9/.10 -\u003e .11 -\u003e .12). Multi-project scope selection, registry manager, scanner discover/import, mode-aware reads, aggregate mode with project badges, and full verification evidence are now in place.","status":"closed","priority":0,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:47.7205517-08:00","created_by":"zenchantlive","updated_at":"2026-02-12T22:35:21.1595002-08:00","closed_at":"2026-02-12T22:35:21.1595002-08:00","close_reason":"multi-project-scanner-epic-complete","labels":["multi-project","scanner"],"dependencies":[{"issue_id":"bb-6aj","depends_on_id":"bb-92d","type":"blocks","created_at":"2026-02-11T17:12:19.6374139-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-6aj.1","title":"Persist project registry in %USERPROFILE%\\\\.beadboard\\\\projects.json","description":"Implement read/write management for registry file in user profile path, isolated from repository files and safe for local machine usage.","acceptance_criteria":"Registry file is created lazily and survives app restarts.","status":"closed","priority":0,"issue_type":"task","assignee":"agent-a","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:48.5403111-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:53:17.2085722-08:00","closed_at":"2026-02-11T17:53:17.2085722-08:00","close_reason":"Implemented %USERPROFILE%/.beadboard/projects.json registry persistence with Windows-safe normalization and dedupe.","labels":["config","registry"],"dependencies":[{"issue_id":"bb-6aj.1","depends_on_id":"bb-6aj","type":"parent-child","created_at":"2026-02-11T17:11:48.5419102-08:00","created_by":"zenchantlive"}]}
|
||||
|
|
@ -133,7 +132,6 @@
|
|||
{"id":"bb-bvn.2","title":"Implement React Flow graph view with pan/zoom/select interactions","description":"Implement deterministic React Flow graph UI (non-chaotic workspace mode).\n\nScope:\n- New graph page/view with React Flow canvas.\n- Deterministic auto-layout (DAG style) for stable mental model:\n - selected node centered in focus mode\n - upstream blockers left, downstream dependents right\n- Use card-like nodes (not bubbles) with minimal status accent.\n- Edge styling by dependency type:\n - blocks: solid\n - parent: thicker muted\n - relates_to: dashed\n - duplicates/supersedes: distinct but subtle styles\n\nInteraction:\n- Click node opens shared detail panel.\n- Controls: hop depth switch (1/2/full), collapse closed, fit-to-selection.\n- Disable freeform drag by default to avoid n8n-like chaos (optional manual toggle can be deferred).\n\nResponsive behavior:\n- Desktop/tablet: full canvas + detail panel split.\n- Mobile: simplified dependency focus mode (selected + immediate blockers/dependents list) instead of dense full canvas.\n\nIntegration:\n- Read-only against graph model from bb-bvn.1.\n- No writeback from graph lane.\n\nTest/verification:\n- Component tests for control toggles and selected-node behavior.\n- Guard test for responsive fallback contract.\n- Playwright screenshots: mobile/tablet/desktop graph view.\r\n","acceptance_criteria":"- Graph renders with deterministic layout and typed edges.\n- Default depth is 2 hops with controls for 1/2/full.\n- Node selection opens detail panel and fit-to-selection works.\n- Mobile shows simplified focus view (no unusable dense canvas).\n- Visual verification screenshots captured for mobile/tablet/desktop.\r\n","notes":"Full visual buff and relationship clarity pass complete. 1) Implemented modern aurora surface theme with refined typography and rhythm. 2) Fixed invisible relationship lines by increasing edge contrast, width, and adding animations for 'blocks' paths. 3) Refined layout to ensure 'Dependency Flow' is fully scrollable and correctly prioritized. 4) Improved mobile UX with a simplified overview and toggleable graph view. 5) Implemented groundwork for bb-bvn.3 (analyzeBlockedChain, detectDependencyCycles) to satisfy tests. Verified via npm run test, typecheck, and captured screenshots in artifacts/.","status":"closed","priority":1,"issue_type":"task","assignee":"zenchantlive","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:10.8683725-08:00","created_by":"zenchantlive","updated_at":"2026-02-12T18:57:24.4716865-08:00","closed_at":"2026-02-12T18:57:24.4716865-08:00","close_reason":"Implemented React Flow graph workspace with deterministic layout, interaction controls, responsive fallback, and visual verification artifacts; tests/typecheck are green.","labels":["graph","ui"],"dependencies":[{"issue_id":"bb-bvn.2","depends_on_id":"bb-bvn","type":"parent-child","created_at":"2026-02-11T17:12:10.8694189-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bvn.2","depends_on_id":"bb-bvn.1","type":"blocks","created_at":"2026-02-11T17:12:36.8736785-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bvn.2","depends_on_id":"bb-bvn.4","type":"blocks","created_at":"2026-02-11T20:10:04.4783802-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-bvn.3","title":"Add blocked-chain highlighting and cycle anomaly signaling","description":"Add analysis overlays for blocker triage and anomaly visibility.\n\nScope:\n- Compute and highlight blocked chains from selected node.\n- Show concise blocker summary:\n - open blocker count\n - in-progress blocker count\n - first actionable blocker\n- Cycle/anomaly signaling:\n - detect cycles in dependency graph\n - mark involved nodes/edges with warning style and explanation text\n\nUI behavior:\n- \"Show blocking path only\" toggle to reduce noise.\n- Hovering a node/edge highlights direct dependency chain.\n- Keep styling subtle and readable; avoid visual overload.\n\nRules:\n- Analysis is read-only and derived from current graph model.\n- Must not fail hard on malformed dependency data; degrade with warnings.\n\nTest plan:\n- Unit tests for blocked-chain derivation and cycle detection logic.\n- UI tests for toggle behavior and warning visibility.\n- Screenshot verification for normal and anomaly cases.\r\n","acceptance_criteria":"- Selected issue can display clear blocked-chain context.\n- Cycle/anomaly conditions are detected and visibly flagged.\n- Blocking-path-only mode materially reduces graph noise.\n- Analysis features remain performant and do not break base graph rendering.\n- Tests and screenshots verify normal + anomaly paths.\r\n","notes":"Addressed review P1 in detectDependencyCycles: removed early-return DFS behavior that leaked recStack/path state; traversal now always unwinds and collects cycles without contaminating predecessor nodes. Added regression test in tests/lib/graph-view.test.ts: detectDependencyCycles does not mark non-cycle predecessor as cyclic. Verification: node --import tsx --test tests/lib/graph-view.test.ts (pass), npm run typecheck (pass), npm run test (pass).","status":"closed","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:11.687878-08:00","created_by":"zenchantlive","updated_at":"2026-02-12T18:57:24.8694169-08:00","closed_at":"2026-02-12T18:57:24.8694169-08:00","close_reason":"Implemented blocked-chain analysis, blocking-path emphasis, and cycle anomaly signaling with regression coverage; tests/typecheck are green.","labels":["analysis","graph"],"dependencies":[{"issue_id":"bb-bvn.3","depends_on_id":"bb-bvn","type":"parent-child","created_at":"2026-02-11T17:12:11.6890831-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bvn.3","depends_on_id":"bb-bvn.2","type":"blocks","created_at":"2026-02-11T17:12:37.378326-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bvn.3","depends_on_id":"bb-bvn.4","type":"blocks","created_at":"2026-02-11T20:10:03.6326727-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-bvn.4","title":"Epic Design Gate: scope, decisions, and acceptance contract","description":"Design/discovery gate for bb-bvn before further implementation.\n\nMust capture:\n- Product intent and user outcomes for this epic\n- Explicit architecture decisions and tradeoffs\n- API/data contracts and edge cases\n- Windows-specific constraints and path/process assumptions\n- Test strategy and verification commands\n- Non-goals and out-of-scope boundaries\n\nCompletion rule:\nDo not start new implementation tasks in this epic until this gate is closed with agreed decisions.","acceptance_criteria":"A written execution-grade plan exists for this epic and all child task descriptions are updated with concrete implementation details, dependencies, and testable acceptance criteria.","notes":"Graph design gate completed: agreed React Flow deterministic UX, default 2-hop depth controls, mobile simplified fallback, typed edge semantics, and verification contract (tests + screenshots + smoke). Child tasks bb-bvn.1/.2/.3 updated with execution-grade details.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T20:09:40.290642-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:59:12.4823711-08:00","closed_at":"2026-02-11T20:59:12.4823711-08:00","close_reason":"Design gate complete: bb-bvn child tasks now contain concrete scope, contracts, dependencies, and testable acceptance criteria.","dependencies":[{"issue_id":"bb-bvn.4","depends_on_id":"bb-bvn","type":"parent-child","created_at":"2026-02-11T20:09:40.2922349-08:00","created_by":"zenchantlive"}],"comments":[{"id":9,"issue_id":"bb-bvn.4","author":"zenchantlive","text":"yo","created_at":"2026-02-14T06:58:38Z"}]}
|
||||
{"id":"bb-byx","title":"pulse:bb-silver-castle:1771120191727","status":"open","priority":2,"issue_type":"event","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T17:49:52.3889824-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T17:49:52.3889824-08:00","ephemeral":true}
|
||||
{"id":"bb-cgj","title":"1.4 LeftPanel Component: Channel tree navigation","description":"GOAL:\nCreate the left sidebar with channel tree navigation for filtering by epic/project.\n\nPROBLEM:\nNeed a left panel that shows:\n- 'All' option to show everything\n- List of epics/swarms for filtering\n- Project scope controls (reuse existing)\n- Collapsible on tablet/mobile\n\nACCEPTANCE CRITERIA:\n1. LeftPanel component created in src/components/shared/left-panel.tsx\n2. Shows channel tree with epics\n3. Click on epic filters current view\n4. Project scope controls integrated\n5. Responsive: collapses on tablet, hidden on mobile\n6. npm run typecheck passes\n7. npm run lint passes\n\nIMPLEMENTATION STEPS:\n1. Create LeftPanel component\n2. Build channel tree from issues data\n3. Integrate ProjectScopeControls from existing\n4. Add collapse toggle for responsive\n5. Style with earthy-dark tokens\n\nFILES TO CREATE:\n- src/components/shared/left-panel.tsx\n\nCOMPONENT INTERFACE:\n\n```typescript\ninterface LeftPanelProps {\n epics: BeadIssue[];\n selectedEpicId: string | null;\n onSelectEpic: (id: string | null) =\u003e void;\n projectScopeKey: string;\n projectScopeMode: 'single' | 'aggregate';\n projectScopeOptions: ProjectScopeOption[];\n collapsed?: boolean;\n onToggleCollapse?: () =\u003e void;\n}\n```\n\nLAYOUT:\n```\n┌──────────────┐\n│ Scope: local │\n│ ──────────── │\n│ ▼ All │\n│ ▼ bb-buff │\n│ bb-buff.1 │\n│ bb-buff.2 │\n│ ▼ bb-u6f │\n│ bb-u6f.1 │\n└──────────────┘\n```\n\nREUSE:\n- src/components/shared/project-scope-controls.tsx (existing)\n\nSKILLS TO USE:\n- verification-before-completion\n- linus-beads-discipline\n\nDEPENDENCIES:\n- Requires: 1.2 (UnifiedShell to integrate into)\n\nVERIFICATION:\n```bash\nnpm run typecheck\nnpm run lint\n# Visual check: channel tree renders\n```\n\nEVIDENCE TO CAPTURE:\n- npm run typecheck output\n- Screenshot of LeftPanel","acceptance_criteria":"LeftPanel component created with channel tree; Epic filtering works; Project scope controls integrated; Responsive collapse works; npm run typecheck passes; npm run lint passes","status":"tombstone","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-15T18:34:46.4717456-08:00","created_by":"zenchantlive","updated_at":"2026-02-15T18:41:07.9786849-08:00","deleted_at":"2026-02-15T18:41:07.9786849-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
|
||||
{"id":"bb-daemon-test-mln8e4tf","title":"Agent: daemon-test-mln8e4tf","status":"open","priority":0,"issue_type":"task","created_at":"2026-02-14T20:14:28.7404746-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T20:14:37.095831-08:00","labels":["gt:agent","role:tester","swarm:daemon-test"],"agent_state":"idle","last_activity":"2026-02-14T20:14:28.7490465-08:00"}
|
||||
{"id":"bb-dcv","title":"Agent Communication \u0026 Coordination Patterns","description":"Agents need a standardized way to coordinate (handoffs, help requests, blockers) without breaking flow. We are opting for a **Issue-Centric** communication model (using Comments) rather than an Inbox-Centric model. This epic defines the protocols, patterns, and tool support needed to make that robust.\n\nGoals:\n- Define 'Protocol' for agent-to-agent comments (prefixes, structure).\n- Establish Identity standards (how agents refer to themselves).\n- Ensure CLI support for all protocol actions (commenting, signaling).\n\nDeliverables:\n- RFC-001: Agent Coordination Protocol.\n- Skill: beadboard-driver (teaching the protocol).\n\nThis epic blocks bb-u6f (Agent Sessions) because session attribution relies on the Identity standards defined here.","notes":"RETROSPECTIVE (2026-02-14): First-principles analysis revealed the agent-registry.ts created in this epic should consolidate to bd agent beads (bb-1y7). The ~/.beadboard/agent/*.json storage is not git-synced, violating Iron Law #1 (single source of truth). Future refactor will wrap bd agent beads instead. agent-mail.ts and agent-reservations.ts remain custom (no bd equivalent). Bug bb-79b fixed scope normalization bug in reservations.","status":"closed","priority":1,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-12T21:35:07.1826787-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T12:17:47.5198134-08:00","closed_at":"2026-02-13T19:02:22.3262564-08:00","close_reason":"Agent Communication \u0026 Coordination Patterns deliverables completed and verified end-to-end."}
|
||||
|
|
@ -196,7 +194,6 @@
|
|||
{"id":"bb-q1s.2","title":"Kanban detail integration of shared editor","description":"Integrate shared editor into Kanban detail panel (desktop + mobile drawer).\n\nIncludes:\n- Edit button and mode switch\n- Save/Cancel\n- optimistic update + rollback via existing mutation path\n- inline error handling","acceptance_criteria":"- Kanban detail can edit and save core fields.\n- Cancel restores non-saved edits.\n- Save errors show clear inline message.","notes":"Integrated shared editor into Kanban detail panel (desktop and mobile drawer) with edit mode, save/cancel, inline validation and save errors, and post-save refresh callback.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-12T20:50:32.2815939-08:00","created_by":"zenchantlive","updated_at":"2026-02-12T21:11:00.5718636-08:00","closed_at":"2026-02-12T21:11:00.5718636-08:00","close_reason":"Kanban detail integration complete with shared edit behavior and verification.","labels":["editing","kanban","ui"],"dependencies":[{"issue_id":"bb-q1s.2","depends_on_id":"bb-q1s","type":"parent-child","created_at":"2026-02-12T20:50:32.2836956-08:00","created_by":"zenchantlive"},{"issue_id":"bb-q1s.2","depends_on_id":"bb-q1s.1","type":"blocks","created_at":"2026-02-12T20:50:47.1937109-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-q1s.3","title":"Graph detail integration of shared editor","description":"Integrate same shared editor into graph detail panel container.\n\nIncludes:\n- identical field behavior/validation\n- identical save/cancel semantics\n- deep-link context preserved after save","acceptance_criteria":"- Graph detail can edit and save same fields as Kanban.\n- Behavior matches Kanban editing semantics.","notes":"Integrated same shared editor path into Graph task details drawer by reusing KanbanDetail and passing projectRoot/onIssueUpdated hooks; refresh wired via router.refresh().","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-12T20:50:32.9165031-08:00","created_by":"zenchantlive","updated_at":"2026-02-12T21:11:01.2178741-08:00","closed_at":"2026-02-12T21:11:01.2178741-08:00","close_reason":"Graph detail integration complete with shared edit semantics.","labels":["editing","graph","ui"],"dependencies":[{"issue_id":"bb-q1s.3","depends_on_id":"bb-q1s","type":"parent-child","created_at":"2026-02-12T20:50:32.9234917-08:00","created_by":"zenchantlive"},{"issue_id":"bb-q1s.3","depends_on_id":"bb-q1s.1","type":"blocks","created_at":"2026-02-12T20:50:47.795674-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-q1s.4","title":"Cross-surface verification + UX polish for edit flows","description":"Finalize edit experience and verify both surfaces end-to-end.\n\nIncludes:\n- responsive polish\n- keyboard/focus behavior\n- guard/unit test updates\n- mutation smoke checks","acceptance_criteria":"- Typecheck and tests pass.\n- Guards confirm edit controls render on both surfaces.\n- No write boundary regressions.","notes":"Verification complete: npm run typecheck, npm run test, guard tests, and screenshots (artifacts/kanban-mobile-after.png, artifacts/kanban-tablet-after.png, artifacts/kanban-desktop-after.png, artifacts/graph-next-1440.png, artifacts/graph-next-768.png, artifacts/graph-next-390-overview.png, artifacts/graph-next-390-flow.png). Also adjusted screenshot script to use domcontentloaded due SSE/networkidle hang.","status":"closed","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-12T20:50:33.598391-08:00","created_by":"zenchantlive","updated_at":"2026-02-12T21:11:29.5913307-08:00","closed_at":"2026-02-12T21:11:29.5913307-08:00","close_reason":"Cross-surface verification and polish completed with fresh evidence.","labels":["editing","ux","verification"],"dependencies":[{"issue_id":"bb-q1s.4","depends_on_id":"bb-q1s","type":"parent-child","created_at":"2026-02-12T20:50:33.601069-08:00","created_by":"zenchantlive"},{"issue_id":"bb-q1s.4","depends_on_id":"bb-q1s.2","type":"blocks","created_at":"2026-02-12T20:50:48.3822381-08:00","created_by":"zenchantlive"},{"issue_id":"bb-q1s.4","depends_on_id":"bb-q1s.3","type":"blocks","created_at":"2026-02-12T20:50:48.9933212-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-q89","title":"heartbeat-test","status":"open","priority":2,"issue_type":"event","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T14:18:23.6682374-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T14:18:23.6682374-08:00","ephemeral":true}
|
||||
{"id":"bb-review-cleanup","title":"Code review cleanup: fix DEBUG logs and type safety","acceptance_criteria":"Remove DEBUG console.log statements from agent-registry.ts; Fix type safety in extendActivityLease return type; All tests pass; Typecheck passes; Lint passes","notes":"FIXES APPLIED:\n1. tools/bb.ts: Removed unused imports (joinSwarm, leaveSwarm, getSwarmMembers, SwarmCommandResponse)\n2. src/hooks/use-beads-subscription.ts: Added eslint-disable for intentional onUpdate dep exclusion\n\nVERIFICATION:\n- npm run typecheck: PASS (0 errors)\n- npm run lint: PASS (0 errors, 0 warnings)","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T21:42:50.8639049-08:00","created_by":"zenchantlive","updated_at":"2026-02-15T13:10:40.0800904-08:00","closed_at":"2026-02-15T13:10:40.0800904-08:00","close_reason":"Closed","labels":["code-quality"]}
|
||||
{"id":"bb-rk4","title":"0.3 Base Primitives: Shared UI components for unified shell","description":"GOAL:\nCreate the base reusable primitive components that will be used across all views (Social, Graph, Swarm).\n\nPROBLEM:\nWe need shared components for the unified shell: card base, agent avatar with status, progress bar, view-jump icons, and last activity display. These should use shadcn/ui primitives and the new earthy-dark tokens.\n\nACCEPTANCE CRITERIA:\n1. BaseCard component created with consistent styling\n2. AgentAvatar component with liveness status glow\n3. ProgressBar component for swarm progress visualization\n4. ViewJumpIcons component ([≡] [◊] [≋] buttons)\n5. LastActivity component for activity preview\n6. All components have TypeScript types\n7. Unit tests for each component\n8. npm run typecheck passes\n9. npm run lint passes\n10. npm run test passes\n\nIMPLEMENTATION STEPS:\n1. Create src/components/shared/ directory\n2. Implement BaseCard using shadcn Card\n3. Implement AgentAvatar with status glow CSS\n4. Implement ProgressBar with Tailwind\n5. Implement ViewJumpIcons with icons\n6. Implement LastActivity with timestamp formatting\n7. Write unit tests for each\n\nFILES TO CREATE:\n- src/components/shared/base-card.tsx\n- src/components/shared/agent-avatar.tsx\n- src/components/shared/progress-bar.tsx\n- src/components/shared/view-jump-icons.tsx\n- src/components/shared/last-activity.tsx\n- src/components/shared/index.ts (exports)\n- tests/components/shared/base-card.test.tsx\n- tests/components/shared/agent-avatar.test.tsx\n- tests/components/shared/progress-bar.test.tsx\n\nCOMPONENT INTERFACES:\n\n```typescript\n// BaseCard\ninterface BaseCardProps {\n children: React.ReactNode;\n isSelected?: boolean;\n onClick?: () =\u003e void;\n className?: string;\n}\n\n// AgentAvatar \ninterface AgentAvatarProps {\n agentId: string;\n liveness: 'active' | 'stale' | 'evicted' | 'idle' | 'stuck' | 'dead';\n currentTask?: { id: string; title: string } | null;\n size?: 'sm' | 'md' | 'lg';\n}\n\n// ProgressBar\ninterface ProgressBarProps {\n completed: number;\n total: number;\n showLabel?: boolean;\n className?: string;\n}\n\n// ViewJumpIcons\ninterface ViewJumpIconsProps {\n onGraph?: () =\u003e void;\n onSwarm?: () =\u003e void;\n onActivity?: () =\u003e void;\n}\n\n// LastActivity\ninterface LastActivityProps {\n message: string;\n author: string;\n timestamp: string;\n compact?: boolean;\n}\n```\n\nSKILLS TO USE:\n- verification-before-completion: Run all verification commands\n- test-driven-development: Write tests first, then implement\n- linus-beads-discipline: Claim, plan, execute, verify, close\n\nDEPENDENCIES: None (can run in parallel with 0.1 and 0.2, but tests require shadcn components)\n\nTEST STRATEGY:\n1. Write failing tests for each component's expected behavior\n2. Implement components to pass tests\n3. Run full test suite\n\nVERIFICATION:\n```bash\nnpm run typecheck\nnpm run lint\nnpm run test\n```\n\nEVIDENCE TO CAPTURE:\n- Test output showing all tests pass\n- File list of created components\n- npm run typecheck output\n- npm run lint output","acceptance_criteria":"BaseCard, AgentAvatar, ProgressBar, ViewJumpIcons, LastActivity components created in src/components/shared/; All components have TypeScript interfaces; Unit tests pass for each component; npm run typecheck passes; npm run lint passes; npm run test passes","status":"tombstone","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-15T18:32:26.3183766-08:00","created_by":"zenchantlive","updated_at":"2026-02-15T18:41:07.9786849-08:00","deleted_at":"2026-02-15T18:41:07.9786849-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
|
||||
{"id":"bb-silver-castle","title":"Agent: silver-castle","notes":"TRIAGE: Investigating persistent refresh bug despite SSE telemetry filtering. Previous fix claimed complete but user reports text still wiping during typing.","status":"in_progress","priority":0,"issue_type":"task","created_at":"2026-02-14T13:06:45.5062641-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T18:07:22.5141622-08:00","labels":["gt:agent","role:backend"],"agent_state":"working","last_activity":"2026-02-14T18:07:22.513631-08:00"}
|
||||
|
|
@ -300,7 +297,6 @@
|
|||
{"id":"bb-vhy","title":"1.1 URL State Hook: URL as single source of truth for UI state","description":"GOAL:\nCreate a React hook that synchronizes UI state with URL parameters, ensuring the URL is always the single source of truth.\n\nPROBLEM:\nWe need to preserve view selection, task selection, swarm selection, and panel state in the URL so that:\n- Users can bookmark/share specific views\n- Browser back/forward works correctly\n- State persists across page refreshes\n- No race conditions between local state and URL\n\nACCEPTANCE CRITERIA:\n1. useUrlState hook created\n2. Hook reads from useSearchParams and provides typed state\n3. Hook provides setters that update URL via router.push\n4. State types: view, taskId, swarmId, agentId, panel, graphTab\n5. Unit tests for hook behavior\n6. npm run typecheck passes\n7. npm run lint passes\n8. npm run test passes\n\nIMPLEMENTATION STEPS:\n1. Create src/hooks/use-url-state.ts\n2. Define URL state interface\n3. Implement read from useSearchParams\n4. Implement setters with URL updates\n5. Handle edge cases (invalid params, missing params)\n6. Write unit tests\n\nFILES TO CREATE:\n- src/hooks/use-url-state.ts\n- tests/hooks/use-url-state.test.ts\n\nINTERFACE:\n\n```typescript\ninterface UrlState {\n // View\n view: 'social' | 'graph' | 'swarm';\n setView: (view: UrlState['view']) =\u003e void;\n \n // Selection\n taskId: string | null;\n setTaskId: (id: string | null) =\u003e void;\n swarmId: string | null;\n setSwarmId: (id: string | null) =\u003e void;\n agentId: string | null;\n setAgentId: (id: string | null) =\u003e void;\n \n // Panel\n panel: 'open' | 'closed';\n setPanel: (state: UrlState['panel']) =\u003e void;\n togglePanel: () =\u003e void;\n \n // Graph-specific\n graphTab: 'flow' | 'overview';\n setGraphTab: (tab: UrlState['graphTab']) =\u003e void;\n \n // Utilities\n clearSelection: () =\u003e void;\n getUrl: () =\u003e string;\n}\n\nfunction useUrlState(): UrlState;\n```\n\nDEFAULTS:\n- view: 'social'\n- taskId: null\n- swarmId: null \n- agentId: null\n- panel: 'closed'\n- graphTab: 'flow'\n\nURL FORMAT:\n`/?view=social\u0026task=bb-buff.1\u0026panel=open`\n\nSKILLS TO USE:\n- verification-before-completion: Run all verification commands\n- test-driven-development: Write tests first\n- linus-beads-discipline: Single source of truth is critical here\n\nDEPENDENCIES: None\n\nTEST CASES:\n1. Default values when no URL params\n2. Reading from URL params\n3. Setting values updates URL\n4. Clearing selection\n5. Invalid params fall back to defaults\n6. Multiple params in single update\n\nVERIFICATION:\n```bash\nnpm run typecheck\nnpm run lint\nnpm run test\n```\n\nEVIDENCE TO CAPTURE:\n- Test output showing all cases pass\n- npm run typecheck output","acceptance_criteria":"useUrlState hook created in src/hooks/use-url-state.ts; Hook provides typed state and setters for view, taskId, swarmId, agentId, panel, graphTab; Unit tests pass for all cases; URL is single source of truth (no local state drift); npm run typecheck passes; npm run lint passes; npm run test passes","status":"tombstone","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-15T18:33:03.8544174-08:00","created_by":"zenchantlive","updated_at":"2026-02-15T18:41:07.9786849-08:00","deleted_at":"2026-02-15T18:41:07.9786849-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
|
||||
{"id":"bb-vl8","title":"0.2 shadcn/ui Setup: Initialize and install base components","description":"GOAL:\nInitialize shadcn/ui in the project and install the base component set needed for the unified shell.\n\nPROBLEM:\nWe need a modern, accessible component library to replace the custom Aero Chrome components. shadcn/ui provides well-designed primitives that work with Tailwind v3.\n\nACCEPTANCE CRITERIA:\n1. shadcn/ui initialized with correct configuration\n2. Base components installed: button, card, badge, avatar, input, scroll-area, separator, tooltip, dropdown-menu\n3. components.json configured correctly\n4. npm run typecheck passes\n5. npm run lint passes\n6. npm run dev starts without errors\n\nIMPLEMENTATION STEPS:\n1. Run `npx shadcn@latest init` with options:\n - Style: Default\n - Base color: Slate (will override with custom tokens)\n - CSS variables: Yes\n2. Run `npx shadcn@latest add button card badge avatar input scroll-area separator tooltip dropdown-menu`\n3. Verify components are created in src/components/ui/\n4. Update components.json if needed to match project structure\n5. Test that imported components render\n\nFILES TO CREATE:\n- components.json (root)\n- src/components/ui/button.tsx\n- src/components/ui/card.tsx\n- src/components/ui/badge.tsx\n- src/components/ui/avatar.tsx\n- src/components/ui/input.tsx\n- src/components/ui/scroll-area.tsx\n- src/components/ui/separator.tsx\n- src/components/ui/tooltip.tsx\n- src/components/ui/dropdown-menu.tsx\n\nCOMMANDS TO RUN:\n```bash\nnpx shadcn@latest init\n# Select: Default style, Slate base, CSS variables: Yes\n\nnpx shadcn@latest add button card badge avatar input scroll-area separator tooltip dropdown-menu\n```\n\nSKILLS TO USE:\n- verification-before-completion: Run all verification commands\n- test-driven-development: Not required for library setup\n- linus-beads-discipline: Claim before starting, close with evidence\n\nDEPENDENCIES: None (can run in parallel with 0.1 and 0.3)\n\nRISKS:\n- shadcn/ui may conflict with existing globals.css CSS variables\n- Solution: Run init, then check for conflicts, merge carefully\n\nVERIFICATION:\n```bash\nnpm run typecheck\nnpm run lint\nnpm run dev\n# Check that shadcn components render in a test page\n```\n\nEVIDENCE TO CAPTURE:\n- components.json content\n- List of files created in src/components/ui/\n- npm run typecheck output\n- npm run lint output","acceptance_criteria":"shadcn/ui initialized with components.json; Base components (button, card, badge, avatar, input, scroll-area, separator, tooltip, dropdown-menu) installed in src/components/ui/; npm run typecheck passes; npm run lint passes; npm run dev starts without errors","status":"tombstone","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-15T18:31:46.5010373-08:00","created_by":"zenchantlive","updated_at":"2026-02-15T18:41:07.9786849-08:00","deleted_at":"2026-02-15T18:41:07.9786849-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
|
||||
{"id":"bb-vyf","title":"Frontend: Social-Dense Hub Overlays and Audit Rendering","description":"Update the Sessions Hub to visualize stale states, scope incursions, and the new protocol event schema.","status":"closed","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:44:01.9147924-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T09:45:02.7780466-08:00","closed_at":"2026-02-14T09:45:02.7780466-08:00","close_reason":"Deleted: created before plan approval"}
|
||||
{"id":"bb-xad","title":"pulse:bb-silver-castle:1771120188515","status":"open","priority":2,"issue_type":"event","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T17:49:49.2620246-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T17:49:49.2620246-08:00","ephemeral":true}
|
||||
{"id":"bb-xhm","title":"Timeline and Activity Feed","description":"EPIC ARCHITECTURAL MANIFESTO: Implementation of the High-Signal Derived Activity Engine. This epic represents a fundamental shift in how BeadBoard handles historical context. We explicitly rejected the traditional 'Event Sourcing' model which requires a separate, mutable database. Instead, we implemented a 'Derived Event' architecture. In this model, the system's history is not stored but computed. By performing high-performance, memory-resident diffs between sequential snapshots of the issues.jsonl source of truth, we generate a rich social timeline that is mathematically guaranteed to be 100% consistent with the underlying git-backed Beads. This ensures that the 'Storytelling' layer of the application never drifts from the 'Authority' layer. This infrastructure now serves as the technical backbone for the Agent Sessions (bb-u6f) monitoring system by providing the ActivityEventBus required for live social auditing.","acceptance_criteria":"Timeline events are deterministic from snapshots/diffs; filters (project/actor/event/date) are fast and useful; users can move from event row to issue context in one action; UI follows the same visual system and hierarchy patterns established in bb-bvn.","notes":"EXECUTION TALE: The journey from zero visibility to real-time streams was marked by several critical technical pivots. We began by defining a strictly-typed model of 16 granular transition kinds in src/lib/activity.ts, ensuring we could track everything from status changes to estimate adjustments. The heart of the implementation is the O(N) snapshot-differ algorithm, which we performance-tuned to handle project scales of 200+ beads with sub-10ms latency. A significant 'Dark Moment' occurred during development when we realized Next.js Hot Module Replacement (HMR) was purging our in-memory activity ring buffer, effectively wiping the project's history on every code save. We solved this by implementing a persistence layer in src/lib/activity-persistence.ts that mirrors the buffer to a file-backed store at .beadboard/activity.json. We also addressed the 'UI Flicker' problem by engineering 'Silent Refresh' logic, allowing the Timeline UI to append live events without disrupting the supervisor's scroll position or layout focus. Verified end-to-end via automated diffing smoke tests.","status":"closed","priority":1,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:05.8525088-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T00:15:14.06285-08:00","closed_at":"2026-02-13T20:31:27.142701-08:00","close_reason":"Epic complete. Timeline UI, snapshot diffing, and event model implemented and verified.","labels":["activity","timeline"],"dependencies":[{"issue_id":"bb-xhm","depends_on_id":"bb-tpc","type":"blocks","created_at":"2026-02-11T17:12:22.1602338-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm","depends_on_id":"bb-bvn","type":"blocks","created_at":"2026-02-12T12:45:52.624726-08:00","created_by":"zenchantlive"}],"comments":[{"id":11,"issue_id":"bb-xhm","author":"zenchantlive","text":"Today's work established the 'Derived Event' pattern. We decided NOT to store events in a DB, but rather compute them on-the-fly by diffing JSONL snapshots. This preserves the 'Files as Source of Truth' mandate while giving us a modern social timeline experience.","created_at":"2026-02-14T07:32:31Z"},{"id":27,"issue_id":"bb-xhm","author":"zenchantlive","text":"MEMO: The 'Derived Event' pattern is now the authoritative way to track project history in BeadBoard. By avoiding a separate event database, we've eliminated the risk of 'Event Drift'—where the timeline says one thing and the file says another. The diffing engine acts as a pure function of the issues.jsonl state transitions. This was a critical design win for the project's long-term maintainability.","created_at":"2026-02-14T08:01:10Z"},{"id":42,"issue_id":"bb-xhm","author":"zenchantlive","text":"CRITICAL DESIGN DECISION: The decision to derive history from files rather than store it in SQLite was made to preserve the project's 'Terminal-First' integrity. This ensures that if a user modifies a bead via an external text editor or a git merge, the Activity Engine will automatically detect the delta and generate the appropriate social narrative on next read. Drift is technically impossible in this architecture.","created_at":"2026-02-14T08:15:14Z"}]}
|
||||
{"id":"bb-xhm.1","title":"Define activity event model for created/updated/closed/reopened actions","description":"SUBTASK REPORT: Definition of the Activity Event Model. We established the canonical ActivityEventKind union, consisting of 16 granular transition types: 'created', 'closed', 'reopened', 'status_changed', 'priority_changed', 'assignee_changed', 'type_changed', 'title_changed', 'description_changed', 'labels_changed', 'dependency_added', 'dependency_removed', 'comment_added', 'due_date_changed', 'estimate_changed', and 'field_changed'. This model provides the necessary resolution for high-signal auditing and agent-centric storytelling.","acceptance_criteria":"Event model supports all required timeline activity types.","notes":"EXECUTION TALE: Implementation was strictly typed in src/lib/activity.ts. We ensured that every event carries a payload containing 'from' and 'to' states to support rich diff rendering in the UI. A unit test suite (tests/lib/activity.test.ts) was developed to verify the model's integrity and ensure all 16 kinds are correctly supported. We also applied typography pairing: JetBrains Mono was enforced for machine metadata (IDs, hex values, timestamps) to provide a distinct 'system' feel, while Plus Jakarta Sans handles the human narrative.","status":"closed","priority":1,"issue_type":"task","assignee":"green-falcon","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:06.6781387-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T00:01:31.6199266-08:00","closed_at":"2026-02-13T19:40:27.4104667-08:00","close_reason":"Model defined and verified with tests.","labels":["model","timeline"],"dependencies":[{"issue_id":"bb-xhm.1","depends_on_id":"bb-xhm","type":"parent-child","created_at":"2026-02-11T17:12:06.6791721-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.1","depends_on_id":"bb-xhm.4","type":"blocks","created_at":"2026-02-11T20:10:05.9709567-08:00","created_by":"zenchantlive"}],"comments":[{"id":12,"issue_id":"bb-xhm.1","author":"zenchantlive","text":"The 16 types were chosen to support future 'Storytelling' features where the UI can explain *why* a project is moving or stalling. We ensured machine-data (timestamps, IDs) uses JetBrains Mono while UI-text uses Plus Jakarta Sans.","created_at":"2026-02-14T07:32:33Z"},{"id":28,"issue_id":"bb-xhm.1","author":"zenchantlive","text":"MEMO: The 16 transition types were not chosen arbitrarily; they were mapped directly to the CLI capabilities of the 'bd' tool. This ensure that any mutation performable via the terminal is correctly interpreted and visualized by the Timeline engine.","created_at":"2026-02-14T08:01:32Z"}]}
|
||||
{"id":"bb-xhm.2","title":"Implement snapshot diffing for derived timeline events","description":"SUBTASK REPORT: Implementation of the O(N) Snapshot Differ. We built the logic engine responsible for identifying project delta in src/lib/snapshot-differ.ts. The engine transforms the incoming BeadIssue array into a Map for constant-time (O(1)) lookups and compares it against the previous in-memory state. It correctly identifies identity transitions (created/deleted), property changes (status, priority, title), and collection mutations (labels, dependencies).","acceptance_criteria":"Diff engine emits deterministic event records for relevant field changes.","notes":"EXECUTION TALE: The primary technical challenge was managing the initial server state. We solved the 'First Load Event Storm' problem (where every issue would be detected as 'created' on server start) by pre-seeding the watcher's internal snapshot in the startWatch() method. This ensures that the first disk read sets the baseline and subsequent changes produce real activity events. We also implemented noise-filtering to ensure that metadata-only updates (like 'updated_at' bumps without data change) do not emit events. Verified with 9 distinct test cases in tests/lib/snapshot-differ.test.ts.","status":"closed","priority":2,"issue_type":"task","assignee":"green-falcon","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:07.5007059-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T00:01:54.1739009-08:00","closed_at":"2026-02-13T19:56:44.3701518-08:00","close_reason":"Snapshot diffing engine implemented and verified.","labels":["diff","timeline"],"dependencies":[{"issue_id":"bb-xhm.2","depends_on_id":"bb-xhm","type":"parent-child","created_at":"2026-02-11T17:12:07.501756-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.2","depends_on_id":"bb-xhm.1","type":"blocks","created_at":"2026-02-11T17:12:35.3430513-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.2","depends_on_id":"bb-tpc.2","type":"blocks","created_at":"2026-02-11T17:12:35.8495336-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.2","depends_on_id":"bb-xhm.4","type":"blocks","created_at":"2026-02-11T20:10:07.6688195-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.2","depends_on_id":"bb-2mx","type":"blocks","created_at":"2026-02-14T00:16:52.051166-08:00","created_by":"zenchantlive"}],"comments":[{"id":13,"issue_id":"bb-xhm.2","author":"zenchantlive","text":"Technical challenge: Preventing 'Event Storms' on first load. We solved this by pre-populating the snapshot map in startWatch() so the first read is treated as the baseline, not a 'create all' event. We also implemented noise-filtering to ignore 'updated_at' changes that don't affect actual data.","created_at":"2026-02-14T07:32:35Z"},{"id":29,"issue_id":"bb-xhm.2","author":"zenchantlive","text":"MEMO: The diffing engine was performance-tuned to handle large projects. In our benchmarks, diffing 200 beads against a 200-bead previous state completes in under 10ms, well within our SSE latency targets.","created_at":"2026-02-14T08:01:54Z"}]}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"start": "next start",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "node --test tests/bootstrap.test.mjs && node --import tsx --test tests/components/sessions/sessions-header.test.ts && node --import tsx --test tests/components/sessions/agent-station-logic.test.ts && node --import tsx --test tests/lib/parser.test.ts && node --import tsx --test tests/lib/pathing.test.ts && node --import tsx --test tests/components/shared/left-panel.test.tsx && node --import tsx --test tests/components/shared/top-bar.test.tsx && node --import tsx --test tests/components/shared/mobile-nav.test.tsx && node --import tsx --test tests/components/swarm/swarm-card.test.tsx && node --import tsx --test tests/hooks/url-state-integration.test.ts",
|
||||
"test": "node --test tests/bootstrap.test.mjs && node --import tsx --test tests/components/shared/base-card.test.tsx && node --import tsx --test tests/components/shared/agent-avatar.test.tsx && node --import tsx --test tests/components/sessions/sessions-header.test.ts && node --import tsx --test tests/components/sessions/agent-station-logic.test.ts && node --import tsx --test tests/lib/parser.test.ts && node --import tsx --test tests/lib/pathing.test.ts && node --import tsx --test tests/components/shared/left-panel.test.tsx && node --import tsx --test tests/components/shared/top-bar.test.tsx && node --import tsx --test tests/components/shared/mobile-nav.test.tsx && node --import tsx --test tests/components/swarm/swarm-card.test.tsx && node --import tsx --test tests/hooks/url-state-integration.test.ts",
|
||||
"video": "remotion preview src/video/index.ts",
|
||||
"video:render": "remotion render src/video/index.ts Main out/video.mp4",
|
||||
"video:thumbnail": "remotion still src/video/index.ts Main out/thumbnail.png --frame=60"
|
||||
|
|
|
|||
|
|
@ -1,21 +1,38 @@
|
|||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
type AgentStatus = 'active' | 'stale' | 'stuck' | 'dead';
|
||||
export type AgentStatus = 'idle' | 'spawning' | 'running' | 'working' | 'stuck' | 'done' | 'stopped' | 'dead' | 'active' | 'stale';
|
||||
export type AgentRole = 'ui' | 'graph' | 'orchestrator' | 'agent' | 'researcher';
|
||||
type AvatarSize = 'sm' | 'md' | 'lg';
|
||||
|
||||
interface AgentAvatarProps {
|
||||
name: string;
|
||||
status: AgentStatus;
|
||||
role?: AgentRole;
|
||||
src?: string;
|
||||
size?: AvatarSize;
|
||||
}
|
||||
|
||||
const STATUS_GLOW: Record<AgentStatus, string> = {
|
||||
const ROLE_BORDER_COLORS: Record<AgentRole, string> = {
|
||||
ui: 'border-l-[var(--agent-role-ui)]',
|
||||
graph: 'border-l-[var(--agent-role-graph)]',
|
||||
orchestrator: 'border-l-[var(--agent-role-orchestrator)]',
|
||||
agent: 'border-l-[var(--agent-role-agent)]',
|
||||
researcher: 'border-l-[var(--agent-role-researcher)]',
|
||||
};
|
||||
|
||||
const STATUS_VISUALS: Record<AgentStatus, string> = {
|
||||
idle: 'shadow-none ring-1 ring-white/10',
|
||||
spawning: 'animate-pulse shadow-[0_0_12px_rgba(91,168,160,0.4)] ring-2 ring-teal-500/40',
|
||||
running: 'shadow-[0_0_12px_rgba(124,185,122,0.4)] ring-2 ring-emerald-500/40',
|
||||
working: 'animate-pulse shadow-[0_0_15px_rgba(124,185,122,0.6)] ring-2 ring-emerald-500/50',
|
||||
stuck: 'shadow-none ring-2 ring-amber-500/50 after:content-[""] after:absolute after:-bottom-0.5 after:-right-0.5 after:w-2.5 after:h-2.5 after:bg-[#D4A574] after:rounded-full after:border-2 after:border-[#363636]',
|
||||
done: 'shadow-[0_0_10px_rgba(124,185,122,0.3)] ring-1 ring-emerald-500/30',
|
||||
stopped: 'shadow-none ring-1 ring-white/5 opacity-80',
|
||||
dead: 'shadow-[0_0_8px_rgba(201,122,122,0.4)] ring-2 ring-rose-900/40 opacity-60',
|
||||
// Legacy mappings for safety
|
||||
active: 'shadow-[0_0_12px_rgba(74,222,128,0.4)] ring-2 ring-emerald-500/40',
|
||||
stale: 'shadow-[0_0_10px_rgba(251,191,36,0.3)] ring-2 ring-amber-500/30',
|
||||
stuck: 'shadow-[0_0_12px_rgba(248,113,113,0.4)] ring-2 ring-rose-500/40',
|
||||
dead: 'shadow-[0_0_8px_rgba(127,29,29,0.4)] ring-2 ring-red-900/40 opacity-60',
|
||||
};
|
||||
|
||||
const SIZE_CLASSES: Record<AvatarSize, string> = {
|
||||
|
|
@ -33,11 +50,18 @@ function getInitials(name: string): string {
|
|||
.slice(0, 2);
|
||||
}
|
||||
|
||||
export function AgentAvatar({ name, status, src, size = 'md' }: AgentAvatarProps) {
|
||||
export function AgentAvatar({ name, status, role, src, size = 'md' }: AgentAvatarProps) {
|
||||
const roleClass = role ? cn('border-l-[3px]', ROLE_BORDER_COLORS[role]) : '';
|
||||
|
||||
return (
|
||||
<Avatar className={cn(SIZE_CLASSES[size], STATUS_GLOW[status], 'transition-all duration-200')}>
|
||||
{src && <AvatarImage src={src} alt={name} />}
|
||||
<AvatarFallback className="bg-surface-muted text-text-body text-xs font-semibold">
|
||||
<Avatar className={cn(
|
||||
SIZE_CLASSES[size],
|
||||
STATUS_VISUALS[status],
|
||||
roleClass,
|
||||
'relative overflow-visible transition-all duration-200 bg-surface-muted'
|
||||
)}>
|
||||
{src && <AvatarImage src={src} alt={name} className="object-cover rounded-full overflow-hidden" />}
|
||||
<AvatarFallback className="bg-transparent text-text-body text-xs font-semibold rounded-full overflow-hidden">
|
||||
{getInitials(name)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,32 @@
|
|||
import type { ReactNode, MouseEventHandler } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn } from '../../lib/utils';
|
||||
import type { SocialCardStatus } from '../../lib/social-cards';
|
||||
|
||||
interface BaseCardProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
selected?: boolean;
|
||||
status?: SocialCardStatus;
|
||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export function BaseCard({ children, className, selected = false, onClick }: BaseCardProps) {
|
||||
const selectedClass = selected
|
||||
? 'ring-1 ring-amber-200/20 shadow-[0_24px_48px_-18px_rgba(0,0,0,0.88),0_0_26px_rgba(251,191,36,0.14)]'
|
||||
: 'shadow-[0_18px_38px_-18px_rgba(0,0,0,0.82),0_6px_18px_-10px_rgba(0,0,0,0.72)] hover:shadow-[0_24px_52px_-16px_rgba(0,0,0,0.9),0_10px_26px_-10px_rgba(0,0,0,0.78)]';
|
||||
const STATUS_BORDERS: Record<SocialCardStatus, string> = {
|
||||
ready: 'border-[var(--status-ready)]',
|
||||
in_progress: 'border-[var(--status-in-progress)]',
|
||||
blocked: 'border-[var(--status-blocked)]',
|
||||
closed: 'border-[var(--status-closed)]',
|
||||
};
|
||||
|
||||
export function BaseCard({
|
||||
children,
|
||||
className,
|
||||
selected = false,
|
||||
status,
|
||||
onClick
|
||||
}: BaseCardProps) {
|
||||
const borderClass = status
|
||||
? STATUS_BORDERS[status]
|
||||
: 'border-white/[0.06]';
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -25,9 +40,11 @@ export function BaseCard({ children, className, selected = false, onClick }: Bas
|
|||
}
|
||||
}}
|
||||
className={cn(
|
||||
'rounded-xl border border-white/[0.06] bg-[#363636] px-3.5 py-3 transition duration-200 shadow-[inset_0_1px_0_rgba(255,255,255,0.06)]',
|
||||
'rounded-[var(--radius-card)] border bg-[var(--color-bg-card)] px-3.5 py-3 transition duration-200',
|
||||
'shadow-[0_4px_24px_rgba(0,0,0,0.35),inset_0_1px_0_rgba(255,255,255,0.06)] hover:shadow-[0_8px_30px_rgba(0,0,0,0.4)]',
|
||||
borderClass,
|
||||
onClick && 'cursor-pointer hover:border-white/[0.10]',
|
||||
selectedClass,
|
||||
selected && 'ring-1 ring-amber-500/30 shadow-md',
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -13,58 +13,20 @@ interface SocialCardProps {
|
|||
onJumpToKanban?: (id: string) => void;
|
||||
}
|
||||
|
||||
const RELATIONSHIP_COLORS = {
|
||||
// NEW: unlocks = what blocks ME (rose)
|
||||
unlocks: 'text-rose-400',
|
||||
// NEW: blocks = what I block (amber)
|
||||
blocks: 'text-amber-400',
|
||||
};
|
||||
|
||||
const DOT_COLORS = {
|
||||
// NEW: unlocks = what blocks ME (rose)
|
||||
unlocks: 'bg-rose-400',
|
||||
// NEW: blocks = what I block (amber)
|
||||
blocks: 'bg-amber-400',
|
||||
};
|
||||
|
||||
function Dot({ color }: { color: 'unlocks' | 'blocks' }) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'inline-block h-1.5 w-1.5 rounded-full mr-1.5',
|
||||
DOT_COLORS[color]
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function RelationshipSection({
|
||||
label,
|
||||
items,
|
||||
color,
|
||||
}: {
|
||||
label: string;
|
||||
items: string[];
|
||||
color: 'unlocks' | 'blocks';
|
||||
}) {
|
||||
if (items.length === 0) return null;
|
||||
function RelationshipItem({ id, color }: { id: string; color: 'unlocks' | 'blocks' }) {
|
||||
const dotColor = color === 'unlocks' ? 'bg-rose-400' : 'bg-amber-400';
|
||||
const borderColor = color === 'unlocks' ? 'border-rose-500/20' : 'border-amber-500/20';
|
||||
const hoverBorder = color === 'unlocks' ? 'group-hover:border-rose-500/40' : 'group-hover:border-amber-500/40';
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 text-[11px]">
|
||||
<span className={cn('font-medium', RELATIONSHIP_COLORS[color])}>
|
||||
{label}:
|
||||
</span>
|
||||
<div className="flex flex-wrap gap-x-2">
|
||||
{items.slice(0, 3).map((id) => (
|
||||
<span key={id} className="text-text-muted flex items-center">
|
||||
<Dot color={color} />
|
||||
{id}
|
||||
</span>
|
||||
))}
|
||||
{items.length > 3 && (
|
||||
<span className="text-text-muted">+{items.length - 3}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={cn(
|
||||
"group flex items-center gap-2 rounded border bg-white/5 px-2.5 py-2 transition-colors",
|
||||
borderColor,
|
||||
hoverBorder,
|
||||
"hover:bg-white/10"
|
||||
)}>
|
||||
<span className={cn("h-1.5 w-1.5 rounded-full shrink-0", dotColor)} />
|
||||
<span className="font-mono text-[10px] text-text-muted">{id}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -158,9 +120,10 @@ export function SocialCard({
|
|||
<BaseCard
|
||||
className={cn('min-w-[220px] max-w-[320px]', className)}
|
||||
selected={selected}
|
||||
status={data.status}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-teal-400 font-mono text-sm font-medium">
|
||||
{data.id}
|
||||
|
|
@ -179,22 +142,42 @@ export function SocialCard({
|
|||
</h3>
|
||||
|
||||
{(hasBlocks || hasUnblocks) && (
|
||||
<div className="space-y-1">
|
||||
{/* UNLOCKS: tasks blocking THIS task (rose) - what blocks me */}
|
||||
<RelationshipSection label="UNLOCKS" items={data.unblocks} color="unlocks" />
|
||||
{/* BLOCKS: tasks THIS task blocks (amber) - what I block */}
|
||||
<RelationshipSection label="BLOCKS" items={data.blocks} color="blocks" />
|
||||
<div className="space-y-2 pt-1">
|
||||
{/* BLOCKED BY: tasks blocking THIS task (rose) */}
|
||||
{hasUnblocks && (
|
||||
<div className="rounded-lg bg-black/20 p-2 border border-white/5">
|
||||
<p className="mb-1.5 text-[9px] font-bold uppercase tracking-widest text-rose-400/80 pl-0.5">Blocked By</p>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{data.unblocks.map((id) => (
|
||||
<RelationshipItem key={id} id={id} color="unlocks" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* BLOCKING: tasks THIS task blocks (amber) */}
|
||||
{hasBlocks && (
|
||||
<div className="rounded-lg bg-black/20 p-2 border border-white/5">
|
||||
<p className="mb-1.5 text-[9px] font-bold uppercase tracking-widest text-amber-400/80 pl-0.5">Blocking</p>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{data.blocks.map((id) => (
|
||||
<RelationshipItem key={id} id={id} color="blocks" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<div className="flex items-center justify-between pt-1">
|
||||
<div className="flex items-center justify-between pt-2 border-t border-white/5">
|
||||
<div className="flex items-center gap-1">
|
||||
{data.agents.slice(0, 3).map((agent) => (
|
||||
<AgentAvatar
|
||||
key={agent.name}
|
||||
name={agent.name}
|
||||
status={agent.status as AgentStatus}
|
||||
role={agent.role}
|
||||
size="sm"
|
||||
/>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import type { BeadIssue } from './types';
|
||||
export type { AgentStatus, AgentRole } from '../components/shared/agent-avatar';
|
||||
import type { AgentStatus, AgentRole } from '../components/shared/agent-avatar';
|
||||
|
||||
export type SocialCardStatus = 'ready' | 'in_progress' | 'blocked' | 'closed';
|
||||
|
||||
export type AgentStatus = 'active' | 'stale' | 'stuck' | 'dead';
|
||||
|
||||
export interface AgentInfo {
|
||||
name: string;
|
||||
status: AgentStatus;
|
||||
role?: AgentRole;
|
||||
}
|
||||
|
||||
export type SocialCardPriority = 'P0' | 'P1' | 'P2' | 'P3' | 'P4';
|
||||
|
|
@ -57,7 +58,17 @@ function extractAgents(bead: BeadIssue): AgentInfo[] {
|
|||
typeof bead.metadata?.agentStatus === 'string'
|
||||
? (bead.metadata.agentStatus as AgentStatus)
|
||||
: 'active';
|
||||
agents.push({ name: bead.assignee, status: agentStatus });
|
||||
|
||||
const agentRole: AgentRole | undefined =
|
||||
typeof bead.metadata?.agentRole === 'string'
|
||||
? (bead.metadata.agentRole as AgentRole)
|
||||
: undefined;
|
||||
|
||||
agents.push({
|
||||
name: bead.assignee,
|
||||
status: agentStatus,
|
||||
role: agentRole
|
||||
});
|
||||
}
|
||||
return agents;
|
||||
}
|
||||
|
|
|
|||
56
tests/components/shared/agent-avatar.test.tsx
Normal file
56
tests/components/shared/agent-avatar.test.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { describe, it, before } from 'node:test';
|
||||
import assert from 'node:assert';
|
||||
import React from 'react';
|
||||
|
||||
// Shim React for the test environment
|
||||
before(() => {
|
||||
// @ts-ignore
|
||||
global.React = React;
|
||||
});
|
||||
|
||||
describe('AgentAvatar Component Contract', () => {
|
||||
it('exports AgentAvatar component', async () => {
|
||||
const mod = await import('../../../src/components/shared/agent-avatar');
|
||||
assert.ok(mod.AgentAvatar, 'AgentAvatar should be exported');
|
||||
});
|
||||
});
|
||||
|
||||
describe('AgentAvatar Role Styling', () => {
|
||||
it('applies correct role color class for "ui" role', async () => {
|
||||
const { AgentAvatar } = await import('../../../src/components/shared/agent-avatar');
|
||||
// @ts-ignore - role prop not yet implemented
|
||||
const element = AgentAvatar({ name: 'Test', status: 'active', role: 'ui' }) as any;
|
||||
|
||||
const className = element.props.className || '';
|
||||
assert.ok(
|
||||
className.includes('border-l-[3px]') && className.includes('border-l-[var(--agent-role-ui)]'),
|
||||
`Expected role-colored left border for ui, but got: ${className}`
|
||||
);
|
||||
});
|
||||
|
||||
it('applies correct role color class for "orchestrator" role', async () => {
|
||||
const { AgentAvatar } = await import('../../../src/components/shared/agent-avatar');
|
||||
// @ts-ignore - role prop not yet implemented
|
||||
const element = AgentAvatar({ name: 'Test', status: 'active', role: 'orchestrator' }) as any;
|
||||
|
||||
const className = element.props.className || '';
|
||||
assert.ok(
|
||||
className.includes('border-l-[var(--agent-role-orchestrator)]'),
|
||||
`Expected role-colored left border for orchestrator, but got: ${className}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AgentAvatar ZFC States', () => {
|
||||
it('applies working pulse glow', async () => {
|
||||
const { AgentAvatar } = await import('../../../src/components/shared/agent-avatar');
|
||||
// @ts-ignore - working status not yet implemented
|
||||
const element = AgentAvatar({ name: 'Test', status: 'working' }) as any;
|
||||
|
||||
const className = element.props.className || '';
|
||||
assert.ok(
|
||||
className.includes('animate-pulse') || className.includes('shadow-[0_0_12px_rgba(124,185,122,0.4)]'),
|
||||
`Expected working pulse glow, but got: ${className}`
|
||||
);
|
||||
});
|
||||
});
|
||||
59
tests/components/shared/base-card.test.tsx
Normal file
59
tests/components/shared/base-card.test.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { describe, it, before } from 'node:test';
|
||||
import assert from 'node:assert';
|
||||
import React from 'react';
|
||||
|
||||
// Shim React for the test environment to support JSX execution
|
||||
before(() => {
|
||||
// @ts-ignore
|
||||
global.React = React;
|
||||
});
|
||||
|
||||
describe('BaseCard Component Contract', () => {
|
||||
it('exports BaseCard component', async () => {
|
||||
const mod = await import('../../../src/components/shared/base-card');
|
||||
assert.ok(mod.BaseCard, 'BaseCard should be exported');
|
||||
assert.equal(typeof mod.BaseCard, 'function', 'BaseCard should be a function/component');
|
||||
});
|
||||
});
|
||||
|
||||
describe('BaseCard Styling Logic', () => {
|
||||
it('should be possible to import the component', async () => {
|
||||
const mod = await import('../../../src/components/shared/base-card');
|
||||
assert.ok(mod.BaseCard);
|
||||
});
|
||||
|
||||
it('applies correct status border class for "ready" status', async () => {
|
||||
const { BaseCard } = await import('../../../src/components/shared/base-card');
|
||||
// @ts-ignore - status prop not yet implemented in production code
|
||||
const element = BaseCard({ children: 'test', status: 'ready' }) as any;
|
||||
|
||||
const className = element.props.className || '';
|
||||
assert.ok(
|
||||
className.includes('border-[var(--status-ready)]') || className.includes('border-teal-500'), // Temporary check
|
||||
`Expected className to contain status-ready border, but got: ${className}`
|
||||
);
|
||||
});
|
||||
|
||||
it('applies correct status border class for "blocked" status', async () => {
|
||||
const { BaseCard } = await import('../../../src/components/shared/base-card');
|
||||
// @ts-ignore - status prop not yet implemented
|
||||
const element = BaseCard({ children: 'test', status: 'blocked' }) as any;
|
||||
|
||||
const className = element.props.className || '';
|
||||
assert.ok(
|
||||
className.includes('border-[var(--status-blocked)]') || className.includes('border-amber-500'),
|
||||
`Expected className to contain status-blocked border, but got: ${className}`
|
||||
);
|
||||
});
|
||||
|
||||
it('applies selection ring when selected prop is true', async () => {
|
||||
const { BaseCard } = await import('../../../src/components/shared/base-card');
|
||||
const element = BaseCard({ children: 'test', selected: true }) as any;
|
||||
|
||||
const className = element.props.className || '';
|
||||
assert.ok(
|
||||
className.includes('ring-1') && className.includes('ring-amber-500/30'),
|
||||
`Expected className to contain selection ring, but got: ${className}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -75,7 +75,7 @@ test('buildSocialCards maps priority correctly', () => {
|
|||
assert.equal(cards[6].priority, 'P4');
|
||||
});
|
||||
|
||||
test('buildSocialCards computes unlocks (outgoing blocks)', () => {
|
||||
test('buildSocialCards computes unblocks (outgoing blocks)', () => {
|
||||
const beads = [
|
||||
issue({ id: 'bb-1', dependencies: [dep('blocks', 'bb-2'), dep('blocks', 'bb-3')] }),
|
||||
issue({ id: 'bb-2' }),
|
||||
|
|
@ -85,7 +85,7 @@ test('buildSocialCards computes unlocks (outgoing blocks)', () => {
|
|||
const cards = buildSocialCards(beads);
|
||||
const card1 = cards.find((c) => c.id === 'bb-1')!;
|
||||
|
||||
assert.deepEqual(card1.unlocks.sort(), ['bb-2', 'bb-3']);
|
||||
assert.deepEqual(card1.unblocks.sort(), ['bb-2', 'bb-3']);
|
||||
assert.deepEqual(card1.blocks, []);
|
||||
});
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ test('buildSocialCards computes blocks (incoming blocks)', () => {
|
|||
const card1 = cards.find((c) => c.id === 'bb-1')!;
|
||||
|
||||
assert.deepEqual(card1.blocks.sort(), ['bb-2', 'bb-3']);
|
||||
assert.deepEqual(card1.unlocks, []);
|
||||
assert.deepEqual(card1.unblocks, []);
|
||||
});
|
||||
|
||||
test('buildSocialCards ignores missing targets for blocks', () => {
|
||||
|
|
@ -111,7 +111,7 @@ test('buildSocialCards ignores missing targets for blocks', () => {
|
|||
const cards = buildSocialCards(beads);
|
||||
|
||||
assert.equal(cards.length, 1);
|
||||
assert.deepEqual(cards[0].unlocks, []);
|
||||
assert.deepEqual(cards[0].unblocks, []);
|
||||
assert.deepEqual(cards[0].blocks, []);
|
||||
});
|
||||
|
||||
|
|
@ -178,6 +178,6 @@ test('buildSocialCards ignores non-blocks dependencies', () => {
|
|||
|
||||
const cards = buildSocialCards(beads);
|
||||
|
||||
assert.deepEqual(cards[0].unlocks, []);
|
||||
assert.deepEqual(cards[0].unblocks, []);
|
||||
assert.deepEqual(cards[0].blocks, []);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue