diff --git a/.beads/.gitignore b/.beads/.gitignore index 0acd8c6..dba6914 100644 --- a/.beads/.gitignore +++ b/.beads/.gitignore @@ -1,37 +1,20 @@ -# SQLite databases -*.db -*.db?* -*.db-journal -*.db-wal -*.db-shm +# Dolt database (managed by Dolt, not git) +dolt/ +dolt-access.lock -# Daemon runtime files -daemon.lock -daemon.log -daemon.pid +# Runtime files bd.sock +bd.sock.startlock sync-state.json last-touched # Local version tracking (prevents upgrade notification spam after git ops) .local_version -# Legacy database files -db.sqlite -bd.db - # Worktree redirect file (contains relative path to main repo's .beads/) # Must not be committed as paths would be wrong in other clones redirect -# Merge artifacts (temporary files from 3-way merge) -beads.base.jsonl -beads.base.meta.json -beads.left.jsonl -beads.left.meta.json -beads.right.jsonl -beads.right.meta.json - # Sync state (local-only, per-machine) # These files are machine-specific and should not be shared across clones .sync.lock @@ -39,6 +22,31 @@ beads.right.meta.json sync_base.jsonl export-state/ +# Ephemeral store (SQLite - wisps/molecules, intentionally not versioned) +ephemeral.sqlite3 +ephemeral.sqlite3-journal +ephemeral.sqlite3-wal +ephemeral.sqlite3-shm + +# Legacy files (from pre-Dolt versions) +*.db +*.db?* +*.db-journal +*.db-wal +*.db-shm +db.sqlite +bd.db +daemon.lock +daemon.log +daemon-*.log.gz +daemon.pid +beads.base.jsonl +beads.base.meta.json +beads.left.jsonl +beads.left.meta.json +beads.right.jsonl +beads.right.meta.json + # NOTE: Do NOT add negation patterns (e.g., !issues.jsonl) here. # They would override fork protection in .git/info/exclude, allowing # contributors to accidentally commit upstream issue databases. diff --git a/.beads/config.yaml b/.beads/config.yaml index ff8bc92..68ce17a 100644 --- a/.beads/config.yaml +++ b/.beads/config.yaml @@ -1,67 +1,2 @@ -# Beads Configuration File -# This file configures default behavior for all bd commands in this repository -# All settings can also be set via environment variables (BD_* prefix) -# or overridden with command-line flags - -# Issue prefix for this repository (used by bd init) -# If not set, bd init will auto-detect from directory name -# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc. -# issue-prefix: "" - -# Use no-db mode: load from JSONL, no SQLite, write back after each command -# When true, bd will use .beads/issues.jsonl as the source of truth -# instead of SQLite database -# no-db: false - -# Disable daemon for RPC communication (forces direct database access) -# no-daemon: false - -# Disable auto-flush of database to JSONL after mutations -# no-auto-flush: false - -# Disable auto-import from JSONL when it's newer than database -# no-auto-import: false - -# Enable JSON output by default -# json: false - -# Default actor for audit trails (overridden by BD_ACTOR or --actor) -# actor: "" - -# Path to database (overridden by BEADS_DB or --db) -# db: "" - -# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON) -# auto-start-daemon: true - -# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE) -# flush-debounce: "5s" - -# Export events (audit trail) to .beads/events.jsonl on each flush/sync -# When enabled, new events are appended incrementally using a high-water mark. -# Use 'bd export --events' to trigger manually regardless of this setting. -# events-export: false - -# Git branch for beads commits (bd sync will commit to this branch) -# IMPORTANT: Set this for team projects so all clones use the same sync branch. -# This setting persists across clones (unlike database config which is gitignored). -# Can also use BEADS_SYNC_BRANCH env var for local override. -# If not set, bd sync will require you to run 'bd config set sync.branch '. -# sync-branch: "beads-sync" - -# Multi-repo configuration (experimental - bd-307) -# Allows hydrating from multiple repositories and routing writes to the correct JSONL -# repos: -# primary: "." # Primary repo (where this database lives) -# additional: # Additional repos to hydrate from (read-only) -# - ~/beads-planning # Personal planning repo -# - ~/work-planning # Work planning repo - -# Integration settings (access with 'bd config get/set') -# These are stored in the database, not in this file: -# - jira.url -# - jira.project -# - linear.url -# - linear.api-key -# - github.org -# - github.repo +sync: + mode: dolt-native diff --git a/.beads/metadata.json b/.beads/metadata.json index c787975..9de33ec 100644 --- a/.beads/metadata.json +++ b/.beads/metadata.json @@ -1,4 +1,8 @@ { - "database": "beads.db", - "jsonl_export": "issues.jsonl" + "database": "dolt", + "jsonl_export": "issues.jsonl", + "backend": "dolt", + "dolt_mode": "server", + "dolt_server_port": 3307, + "dolt_database": "beadboard" } \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 87860bf..8e21900 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,6 +10,8 @@ This repo is execution-first, evidence-first, and beads-driven. 4. Evidence before assertions: do not claim fixed/passing/done without fresh command output. 5. Keep language simple in user-facing labels and UI copy. 6. Reuse shared code paths/components; avoid one-off logic drift across pages. +7. Treat BeadBoard as a multi-agent coordination + communication system first; optimize feature decisions for swarm execution clarity before cosmetic/layout preferences. +8. Runtime UI route surface is query-driven from `/` (`view=social|graph|activity`); do not reintroduce direct App Router page sprawl without explicit approval. ## Quick Beads Workflow @@ -63,15 +65,21 @@ npm run test If UI changed, refresh screenshots and record artifact paths. +## Runtime Surface Guardrails + +1. Keep the active runtime page surface minimal under `src/app`. +2. Preserve deprecated/legacy page implementations in `reference/routes/**` when useful for reuse. +3. Maintain backward-compatible redirects in `next.config.ts` when route contracts change. + ## Realtime / Refresh Bug Triage Pattern When status updates are stale or require refresh: 1. Verify source-of-truth parity (`bd show` vs app output). 2. Confirm read path prefers live BD data when needed. -3. Confirm watcher inputs include DB + WAL + touch markers. -4. Confirm SSE fallback compares mtime/timestamps, not only static file content. -5. Add regression tests for watcher/events behavior. +3. Confirm watcher coverage for active project scope roots and relevant agent/message files. +4. Confirm SSE event flow and client subscription behavior across all active views. +5. Add regression tests for watcher/events behavior and scope switching. ## Parallel Agent Pattern @@ -106,6 +114,8 @@ Use parallel agents for independent beads. - Confirm DB + WAL + touch markers are watched and SSE fallback uses mtime/timestamps. 7. Missing test registration: - New test files must be included in `npm run test` script if the suite is explicitly enumerated. +8. Documentation drift: + - Do not claim features in `README.md` that are not currently shipped, unless clearly labeled as roadmap. ## Session Completion (Landing the Plane) @@ -136,3 +146,90 @@ Never claim: - "closed" unless you have run the proving command(s) in the current session and can cite results. + + +## Issue Tracking with bd (beads) + +**IMPORTANT**: This project uses **bd (beads)** for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods. + +### Why bd? + +- Dependency-aware: Track blockers and relationships between issues +- Git-friendly: Auto-syncs to JSONL for version control +- Agent-optimized: JSON output, ready work detection, discovered-from links +- Prevents duplicate tracking systems and confusion + +### Quick Start + +**Check for ready work:** + +```bash +bd ready --json +``` + +**Create new issues:** + +```bash +bd create "Issue title" --description="Detailed context" -t bug|feature|task -p 0-4 --json +bd create "Issue title" --description="What this issue is about" -p 1 --deps discovered-from:bd-123 --json +``` + +**Claim and update:** + +```bash +bd update bd-42 --status in_progress --json +bd update bd-42 --priority 1 --json +``` + +**Complete work:** + +```bash +bd close bd-42 --reason "Completed" --json +``` + +### Issue Types + +- `bug` - Something broken +- `feature` - New functionality +- `task` - Work item (tests, docs, refactoring) +- `epic` - Large feature with subtasks +- `chore` - Maintenance (dependencies, tooling) + +### Priorities + +- `0` - Critical (security, data loss, broken builds) +- `1` - High (major features, important bugs) +- `2` - Medium (default, nice-to-have) +- `3` - Low (polish, optimization) +- `4` - Backlog (future ideas) + +### Workflow for AI Agents + +1. **Check ready work**: `bd ready` shows unblocked issues +2. **Claim your task**: `bd update --status in_progress` +3. **Work on it**: Implement, test, document +4. **Discover new work?** Create linked issue: + - `bd create "Found bug" --description="Details about what was found" -p 1 --deps discovered-from:` +5. **Complete**: `bd close --reason "Done"` + +### Auto-Sync + +bd automatically syncs with git: + +- Exports to `.beads/issues.jsonl` after changes (5s debounce) +- Imports from JSONL when newer (e.g., after `git pull`) +- No manual export/import needed! + +### Important Rules + +- ✅ Use bd for ALL task tracking +- ✅ Always use `--json` flag for programmatic use +- ✅ Link discovered work with `discovered-from` dependencies +- ✅ Check `bd ready` before asking "what should I work on?" +- ❌ Do NOT create markdown TODO lists +- ❌ Do NOT use external issue trackers +- ❌ Do NOT duplicate tracking systems + +For more details, see README.md and docs/QUICKSTART.md. + + diff --git a/README.md b/README.md index afe7406..76805ea 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,205 @@ # BeadBoard +**Work in Progress, please contribute!** +**BeadBoard is a multi-agent swarm coordination system built on [Beads](https://github.com/steveyegge/beads) inspired by [Gastown](https://github.com/steveyegge/gastown).** Thanks [Steve Yegge](https://github.com/steveyegge)! -**The Windows-native Control Center for [Beads](https://github.com/steveyegge/beads).** - -BeadBoard is a high-performance local dashboard for managing your software development tasks. Built on the Beads protocol, it provides a unified, visualization-rich interface over your distributed project landscape. -![alt text](image.png) -## 🚀 Why BeadBoard? -Most task managers are siloes. BeadBoard is a lens over your source code. -- **Source of Truth**: Reads directly from `.beads/issues.jsonl` in your repo. No database sync skew. -- **Windows Optimized**: Built from the ground up to handle Windows paths, drive letters, and filesystem performance. -- **Zero Latency**: Optimistic UI updates make interactions feel instant. - -## ✨ Core Features - -### 1. Multi-Project Registry & Scanner -Stop context switching between repos. -- **Project Registry**: Persist your favorite project roots for one-click access. -- **Auto-Discovery**: Built-in filesystem scanner finds Bead-enabled projects across your drives. -- **Aggregate Mode**: View tasks from *all* registered projects in a single unified board. - -### 2. Interactive Kanban Dashboard (`/`) -Manage your flow state. -![Kanban Dashboard](assets/kanban-hero.png) -- **Live Updates**: Boards refresh automatically when the underlying JSONL files change (e.g., via CLI). -- **Progressive Disclosure**: Task details, metadata, and relations are tucked away until you need them. -- **Smart Filtering**: Filter by priority, assignee, status, or full-text search across thousands of beads. - -### 3. Dependency Graph Explorer (`/graph`) -Understand the "Why" and "What's Next". -![alt text](image-1.png) -- **Epic-Centric Layout**: Automatically groups tasks by Epic for logical clustering. -- **True DAG Visualization**: Uses Dagre layout engine to enforce a strict Left-to-Right dependency flow. - - *Left*: Incoming Blockers - - *Center*: Focus Task - - *Right*: Unlocks / Downstream -- **Focus Mode**: Minimizable dependency strip and deep-linking support for sharing exact views. -- **Smart Metadata**: See bead counts, priorities, and status health at a glance. - -### 4. Agent Sessions Hub (`/sessions`) -Coordinate multi-agent workflows with social-dense visibility. -- **Epic-Grouped Task Feed**: Tasks organized by parent Epic with session state indicators (active, reviewing, needs_input, stale). -- **Cross-Agent Communication**: Built-in messaging with HANDOFF, BLOCKED, and INFO categories. -- **Agent Productivity Metrics**: Real-time stats showing active tasks, completions, and recent wins. -- **Derived Activity Engine**: O(N) snapshot diffing computes project history on-demand without separate event storage. -- **`bb agent` CLI Integration**: Visualizes data from agent registry, reservations, and mailboxes. - -### 5. Chronological Timeline (`/timeline`) -Real-time activity feed for all project events. -- **Live Updates**: Server-Sent Events stream changes instantly. -- **Date Grouping**: Events organized by day (Today, Yesterday, etc.). -- **Polymorphic Cards**: Distinct visual styles for Status, Lifecycle, and Diff events. -- **History Buffer**: Recent events preserved across server restarts. - -## 🛠️ Stack -- **Framework**: Next.js 15 (App Router) -- **UI Engine**: React 19 + Framer Motion -- **Styling**: Tailwind CSS + Custom Design System -- **Type Safety**: Strict TypeScript - -## ⚡ Quick Start -1. **Install**: `npm install` -2. **Run**: `npm run dev` -3. **Explore**: Open `http://localhost:3000` - -## 🤝 Contribution -- **Typecheck**: `npm run typecheck` -- **Test**: `npm run test` +BB is a visual operations layer for running agent teams against real dependency-constrained work. + +--- + +## What This App Is +BeadBoard is not just a task board. It is an execution system for coordinating agents around shared Beads workflows: + +- Agent-to-agent communication with explicit categories (`HANDOFF`, `BLOCKED`, `DECISION`, `INFO`) +- Conversation threads merged from activity events, agent messages, and local interactions +- Graph/topology context for deciding what should move next +- Global project scope switching across single and aggregate workspaces +- Swarm orchestration with archetypes/templates and assignment controls + +--- + +![alt text](image-9.png) + +--- + +## Core Features + +### 1. Agent Communication System +- Structured message lifecycle: `send`, `inbox`, `read`, `ack` +- Message states: `unread`, `read`, `acked` +- Per-task conversation threads combining: + - activity events + - agent mail + - local bd interactions +- Required acknowledgment semantics for high-signal categories (`HANDOFF`, `BLOCKED`) + +### 2. Swarm Coordination Surface +- Agent Pool Monitor with: + - Archetypes + - Templates + - Needs Agent queue + - Pre-assigned queue + - Squad roster +- Assignment workflow through the graph workspace and right panel +![alt text](image-7.png) + +### 3. Graph + Dependency Topology +- DAG-oriented graph workspace for execution decisions +- Task/dependency tab modes for different planning lenses +- Blocker/unblock context surfaced directly in task cards +- Graph analysis support (cycle and blocked-chain context) +![alt text](image-8.png) + +### 4. Global Project Scope + Scanner +- Project registry and scanner-backed discovery +- Single-project and aggregate modes +- Runtime scope switching without leaving the primary workspace + +### 5. Realtime Operations Layer +- Live updates via watchers + SSE +- Activity stream integration with session/task context +- Mutation/writeback feedback integrated into the same operational surface + +--- + +## Runtime Surface + +### Active Route +- `/` + +### View Modes +- `/?view=social` +- `/?view=graph` +- `/?view=activity` + +### Compatibility Redirects +- `/graph` -> `/?view=graph` +- `/sessions` -> `/?view=social` +- `/timeline` -> `/?view=activity` +- `/mockup` -> `/` + +### Archived Route Vault +Legacy route implementations are preserved in `reference/routes/**` and excluded from active runtime validation scope. + +--- + +## Install + +### Prerequisites +- Node.js `18.18+` (Node `20 LTS` recommended) +- npm + +### Clone + Install +```bash +git clone https://github.com/zenchantlive/beadboard.git +cd beadboard +npm install +``` + +--- + +## Quick Start + +```bash +npm run dev +``` + +Open: + +```text +http://localhost:3000 +``` + +--- + +## Configuration +No external service is required for core local usage. + +Runtime behavior is driven by: +- Local Beads project data +- Registered/scanned project roots +- URL query state (`view`, `task`, `swarm`, `agent`, `epic`, `graphTab`, panel state) + +--- + +## Operating Flow + +### 1. Coordinate through Graph + Pool +Open `/?view=graph`, inspect dependency topology, and drive assignment from the pool panel. + +### 2. Communicate in Context +Open a task thread to read merged conversation context and process message acknowledgments. + +### 3. Switch Scope as Work Expands +Use registry/scanner controls to move between local and aggregate project scope. + +### 4. Track Live Signal +Use social/activity views to monitor execution movement and operational events. + +--- + +## Roadmap Notes +- Cross-view assign controls in all major views. +- Social naming/UX evolution (including possible shift toward “swim” terminology). +- Continued expansion of global project config/scanner workflows. + +--- + +## Scripts + +```bash +npm run dev +npm run build +npm run start +npm run typecheck +npm run lint +npm run test +npm run video +npm run video:render +npm run video:thumbnail +``` + +--- + +## Architecture +- **Frontend**: Next.js App Router + React 19 + Tailwind + Framer Motion + Radix +- **Graph stack**: XYFlow + Dagre +- **Core domain**: Beads issue model, graph/kanban/session/social builders +- **Coordination layer**: agent mail + session communication + swarm orchestration state +- **Realtime**: watchers + SSE + snapshot differ + activity persistence +- **Validation/typing**: strict TypeScript + Zod contracts where applicable + +--- + +## Project Structure + +```text +src/ + app/ + page.tsx # active runtime route + api/ # runtime API routes + components/ + shared/ graph/ social/ activity/ sessions/ swarm/ kanban/ + hooks/ + lib/ +reference/ + routes/ # archived route implementations +``` + +--- + +## Contributing +1. Keep active runtime pages in `src/app` minimal. +2. Promote reusable logic into `src/lib`, `src/components`, `src/hooks`. +3. Archive non-runtime route experiments in `reference/routes`. +4. Run quality gates before merge: + +```bash +npm run typecheck +npm run lint +npm run test +``` + +--- + +## License +MIT diff --git a/docs/protocols/operative-protocol-v1.md b/docs/protocols/operative-protocol-v1.md index 73b0c23..a8522e9 100644 --- a/docs/protocols/operative-protocol-v1.md +++ b/docs/protocols/operative-protocol-v1.md @@ -1,7 +1,7 @@ # Operative Protocol v1 (Session Constitution) Date: 2026-02-14 -Status: Approved for implementation +Status: Approved for implementation (superseded for migration planning by `docs/protocols/2026-02-28-bd-audit-coordination-schema.md`) Scope: `bb-u6f.6.1` Applies to: `bb-u6f.6.2`, `bb-u6f.6.3`, `bb-u6f.6.4`, `bb-u6f.6.5` @@ -15,6 +15,10 @@ Boundaries: 3. No direct writes to `.beads/issues.jsonl`. 4. User-facing labels must stay plain language. +Migration note: +1. New work should target the `coord.v1` `bd audit` event model documented in `docs/protocols/2026-02-28-bd-audit-coordination-schema.md`. +2. `bb` coordination semantics are legacy compatibility behavior pending removal after migration sign-off. + ## 2. Normative Language 1. MUST: required behavior. diff --git a/eslint.config.mjs b/eslint.config.mjs index 29a92b5..5ae70d1 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -14,6 +14,7 @@ const eslintConfig = [ '.next/**', '.agents/**', 'skills/**', + 'reference/**', 'next-env.d.ts', ], }, diff --git a/next.config.ts b/next.config.ts index 0076578..05d1bdb 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,6 +4,30 @@ import path from 'node:path'; const nextConfig: NextConfig = { reactStrictMode: true, outputFileTracingRoot: path.join(process.cwd()), + async redirects() { + return [ + { + source: '/graph', + destination: '/?view=graph', + permanent: false, + }, + { + source: '/sessions', + destination: '/?view=social', + permanent: false, + }, + { + source: '/timeline', + destination: '/?view=activity', + permanent: false, + }, + { + source: '/mockup', + destination: '/', + permanent: false, + }, + ]; + }, webpack(config, { dev }) { if (dev) { // Avoid intermittent Windows ENOENT errors from webpack filesystem pack cache. diff --git a/package.json b/package.json index 972b5e6..3f49a2c 100644 --- a/package.json +++ b/package.json @@ -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/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 && node --import tsx --test tests/hooks/use-graph-analysis.test.ts && node --import tsx --test tests/components/graph/smart-dag.test.tsx && node --import tsx --test tests/components/unified-shell.test.tsx && node --import tsx --test tests/components/graph/graph-node-labels.test.tsx && node --import tsx --test tests/components/graph/graph-node-assign.test.tsx", + "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 && node --import tsx --test tests/hooks/use-graph-analysis.test.ts && node --import tsx --test tests/components/graph/smart-dag.test.tsx && node --import tsx --test tests/components/unified-shell.test.tsx && node --import tsx --test tests/components/graph/graph-node-labels.test.tsx && node --import tsx --test tests/components/graph/graph-node-assign.test.tsx && node --import tsx --test tests/lib/coord-schema.test.ts && node --import tsx --test tests/lib/coord-events.test.ts && node --import tsx --test tests/api/coord-events-route.test.ts && node --import tsx --test tests/lib/coord-projections-inbox.test.ts && node --import tsx --test tests/lib/coord-projections-reservations.test.ts && node --import tsx --test tests/components/sessions/conversation-drawer-coord.test.tsx", "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" diff --git a/src/app/api/beads/_shared.ts b/src/app/api/beads/_shared.ts index e5f5c50..b1399fa 100644 --- a/src/app/api/beads/_shared.ts +++ b/src/app/api/beads/_shared.ts @@ -29,7 +29,7 @@ export async function handleMutationRequest(request: Request, operation: Mutatio const payload = validateMutationPayload(operation, body); const result = await executeMutation(operation, payload); - const status = result.ok ? 200 : result.error?.classification === 'not_found' ? 404 : 400; + const status = result.ok ? 200 : result.error?.classification === 'not_found' ? 503 : 400; return NextResponse.json(result, { status }); } catch (error) { if (error instanceof MutationValidationError) { diff --git a/src/app/api/sessions/[beadId]/conversation/route.ts b/src/app/api/sessions/[beadId]/conversation/route.ts index bac7b34..6548b47 100644 --- a/src/app/api/sessions/[beadId]/conversation/route.ts +++ b/src/app/api/sessions/[beadId]/conversation/route.ts @@ -22,7 +22,7 @@ export async function GET( const activity = history.filter((e: ActivityEvent) => e.beadId === beadId); // 2. Get communication for this bead - const summary = await getCommunicationSummary(); + const summary = await getCommunicationSummary(projectRoot); const messages = summary.messages.filter((m: AgentMessage) => m.bead_id === beadId); // 3. Get local bd interactions via CLI @@ -55,4 +55,4 @@ export async function GET( console.error('[API/Sessions/Conversation] Failed:', error); return NextResponse.json({ ok: false, error: String(error) }, { status: 500 }); } -} \ No newline at end of file +} diff --git a/src/app/api/sessions/route.ts b/src/app/api/sessions/route.ts index e9a3395..b018d92 100644 --- a/src/app/api/sessions/route.ts +++ b/src/app/api/sessions/route.ts @@ -41,9 +41,9 @@ export async function GET(request: Request): Promise { try { const issues = await readIssuesFromDisk({ projectRoot, preferBd: true }); const activity = activityEventBus.getHistory(projectRoot); - const communication = await getCommunicationSummary(); + const communication = await getCommunicationSummary(projectRoot); const livenessMap = await getAgentLivenessMap(projectRoot, activity); - const incursions = await calculateIncursions(); + const incursions = await calculateIncursions(projectRoot, livenessMap); const agentsResult = await listAgents({}, { projectRoot }); const feed = buildSessionTaskFeed(issues, activity, communication, livenessMap); diff --git a/src/app/api/swarm/list/route.ts b/src/app/api/swarm/list/route.ts index 37aec33..a26ab08 100644 --- a/src/app/api/swarm/list/route.ts +++ b/src/app/api/swarm/list/route.ts @@ -21,9 +21,10 @@ export async function GET(request: Request): Promise { }); if (!result.success) { + const status = result.classification === 'not_found' ? 503 : 400; return NextResponse.json( { ok: false, error: { classification: result.classification ?? 'unknown', message: result.error ?? result.stderr } }, - { status: 400 }, + { status }, ); } diff --git a/src/app/api/swarm/status/route.ts b/src/app/api/swarm/status/route.ts index 4d7e3b8..6e9a138 100644 --- a/src/app/api/swarm/status/route.ts +++ b/src/app/api/swarm/status/route.ts @@ -29,9 +29,10 @@ export async function GET(request: Request): Promise { }); if (!result.success) { + const status = result.classification === 'not_found' ? 503 : 400; return NextResponse.json( { ok: false, error: { classification: result.classification ?? 'unknown', message: result.error ?? result.stderr } }, - { status: 400 }, + { status }, ); } diff --git a/src/app/globals.css b/src/app/globals.css index a84e8b4..c4d448f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -6,27 +6,27 @@ :root { /* ========== VISUAL-TRUTH UI TOKEN CONTRACT (bb-vt.1.1) ========== */ - /* Warm charcoal aurora palette - DISTINCT LAYERS */ - --ui-bg-app: #181716; /* Darkest - page background */ - --ui-bg-header: #131211; /* Header - darker than sidebar */ - --ui-bg-shell: #1f1e1d; /* Sidebar - distinct from main */ - --ui-bg-panel: #282725; /* Panels/cards within sidebar */ - --ui-bg-main: #242322; /* Main content area - distinct */ - --ui-bg-card: #302e2c; /* Cards - lightest layer */ - --ui-bg-elevated: #3a3836; /* Elevated/selected elements */ - - --ui-border-soft: rgba(180, 175, 165, 0.2); - --ui-border-strong: rgba(180, 175, 165, 0.35); - - --ui-text-primary: #f0eeea; - --ui-text-muted: #a8a49a; - - --ui-accent-ready: #35d98f; - --ui-accent-blocked: #ff4c72; - --ui-accent-warning: #ffb24a; - --ui-accent-info: #35c9ff; - --ui-accent-action-green: #35d98f; - --ui-accent-action-red: #ff4c72; + /* Map legacy --ui-* tokens to theme tokens so all themes work consistently */ + --ui-bg-app: var(--surface-backdrop); + --ui-bg-header: var(--surface-primary); + --ui-bg-shell: var(--surface-primary); + --ui-bg-panel: var(--surface-secondary); + --ui-bg-main: var(--surface-secondary); + --ui-bg-card: var(--surface-elevated); + --ui-bg-elevated: var(--surface-elevated); + + --ui-border-soft: var(--border-subtle); + --ui-border-strong: var(--border-default); + + --ui-text-primary: var(--text-primary); + --ui-text-muted: var(--text-tertiary); + + --ui-accent-ready: var(--accent-success); + --ui-accent-blocked: var(--accent-danger); + --ui-accent-warning: var(--accent-warning); + --ui-accent-info: var(--accent-info); + --ui-accent-action-green: var(--accent-success); + --ui-accent-action-red: var(--accent-danger); --ui-font-sans: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif; --ui-font-mono: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', monospace; @@ -43,30 +43,25 @@ --color-accent-teal: var(--ui-accent-info); --color-text-primary: var(--ui-text-primary); - --color-text-secondary: #c4cfdb; + --color-text-secondary: var(--text-secondary); --color-text-muted: var(--ui-text-muted); - --color-text-on-primary: #10161d; + --color-text-on-primary: var(--text-inverse); --color-border-default: var(--ui-border-strong); --color-border-subtle: var(--ui-border-soft); /* Status colors */ - --status-open: var(--ui-accent-info); - --status-ready: var(--ui-accent-ready); - --status-in-progress: var(--ui-accent-warning); - --status-progress: var(--ui-accent-warning); - --status-blocked: var(--ui-accent-blocked); - --status-blocked-earthy: var(--ui-accent-blocked); - --status-closed: #7f8b98; - --status-closed-earthy: #7f8b98; - --status-deferred: #7f8b98; + --status-open: var(--accent-info); + --status-progress: var(--status-in-progress); + --status-blocked-earthy: var(--status-blocked); + --status-closed-earthy: var(--status-closed); /* Liveness colors */ --liveness-active: var(--ui-accent-ready); --liveness-stale: var(--ui-accent-warning); --liveness-stuck: var(--ui-accent-action-red); --liveness-dead: var(--ui-accent-action-red); - --liveness-idle: #7f8b98; + --liveness-idle: var(--text-tertiary); /* Agent Role Colors */ --agent-role-ui: #6B9BD2; @@ -83,10 +78,10 @@ --priority-p4: #64748b; /* Blocks/Unlocks Section Colors */ - --color-blocks-bg: rgba(212, 165, 116, 0.1); - --color-unlocks-bg: rgba(229, 115, 115, 0.1); - --color-blocks-border: rgba(212, 165, 116, 0.2); - --color-unlocks-border: rgba(229, 115, 115, 0.2); + --color-blocks-bg: var(--status-in-progress); + --color-unlocks-bg: var(--status-blocked); + --color-blocks-border: color-mix(in srgb, var(--accent-warning) 35%, transparent); + --color-unlocks-border: color-mix(in srgb, var(--accent-danger) 35%, transparent); /* ========== RADI ========== */ --radius-sm: 0.375rem; @@ -98,10 +93,8 @@ --radius-pill: 9999px; /* ========== SHADOWS ========== */ - --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.1); - --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.15); - --shadow-soft-lg: 0 10px 30px -10px rgba(0, 0, 0, 0.3); - --shadow-soft-xl: 0 20px 40px -10px rgba(0, 0, 0, 0.4); + --shadow-soft-lg: var(--shadow-md); + --shadow-soft-xl: var(--shadow-lg); /* ========== TYPOGRAPHY ========== */ --font-ui-stack: var(--ui-font-sans); @@ -139,13 +132,12 @@ --sidebar-right-width: 17.5rem; --topbar-height: 3.75rem; - --glass-base: linear-gradient(180deg, - color-mix(in srgb, var(--ui-bg-card) 72%, black), - color-mix(in srgb, var(--ui-bg-panel) 78%, black)); - --edge-top: color-mix(in srgb, var(--ui-border-strong) 80%, white 20%); - --edge-bottom: color-mix(in srgb, var(--ui-border-soft) 75%, black 25%); - --elevation-ambient: 0 20px 40px -16px rgba(0, 0, 0, 0.78); - --elevation-tight: 0 10px 24px -12px rgba(0, 0, 0, 0.7); + --glass-base: linear-gradient(180deg, var(--surface-elevated), var(--surface-secondary)); + --edge-side: var(--ui-border-soft); + --edge-top: color-mix(in srgb, var(--ui-border-strong) 88%, white 12%); + --edge-bottom: color-mix(in srgb, var(--ui-border-soft) 88%, black 12%); + --elevation-ambient: var(--shadow-lg); + --elevation-tight: var(--shadow-md); /* ========== LEGACY COMPATIBILITY TOKENS ========== */ /* For existing components that reference these */ @@ -181,7 +173,7 @@ body { * { scrollbar-width: thin; - scrollbar-color: rgba(148, 163, 184, 0.35) rgba(255, 255, 255, 0.02); + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); } *::-webkit-scrollbar { @@ -190,18 +182,18 @@ body { } *::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.02); + background: var(--scrollbar-track); border-radius: 9999px; } *::-webkit-scrollbar-thumb { - background: linear-gradient(180deg, rgba(156, 163, 175, 0.55), rgba(107, 114, 128, 0.45)); - border: 1px solid rgba(255, 255, 255, 0.1); + background: var(--scrollbar-thumb); + border: 1px solid color-mix(in srgb, var(--scrollbar-thumb) 70%, transparent); border-radius: 9999px; } *::-webkit-scrollbar-thumb:hover { - background: linear-gradient(180deg, rgba(186, 194, 209, 0.72), rgba(124, 136, 156, 0.62)); + background: var(--scrollbar-thumb-hover); } .custom-scrollbar::-webkit-scrollbar { @@ -210,12 +202,12 @@ body { } .custom-scrollbar::-webkit-scrollbar-thumb { - background: rgba(148, 163, 184, 0.15); + background: var(--scrollbar-thumb); border-radius: 20px; } .custom-scrollbar::-webkit-scrollbar-thumb:hover { - background: rgba(148, 163, 184, 0.3); + background: var(--scrollbar-thumb-hover); } .no-scrollbar::-webkit-scrollbar { @@ -229,14 +221,14 @@ body { .workflow-card { - border: 1px solid rgba(255, 255, 255, 0.08); - border-top-color: rgba(255, 255, 255, 0.24); - border-bottom-color: rgba(0, 0, 0, 0.9); - background: linear-gradient(180deg, rgba(42, 44, 52, 0.6) 0%, rgba(22, 23, 28, 0.6) 100%); + border: 1px solid var(--border-default); + border-top-color: color-mix(in srgb, var(--border-strong) 88%, white 12%); + border-bottom-color: color-mix(in srgb, var(--border-subtle) 88%, black 12%); + background: linear-gradient(180deg, var(--surface-quaternary) 0%, var(--surface-secondary) 100%); box-shadow: var(--elevation-ambient), var(--elevation-tight), - inset 0 1px 1px rgba(255, 255, 255, 0.15); + inset 0 1px 1px color-mix(in srgb, var(--alpha-white-high) 40%, transparent); backdrop-filter: blur(24px) saturate(120%); -webkit-backdrop-filter: blur(24px) saturate(120%); transform: translateZ(0); @@ -244,14 +236,14 @@ body { } .workflow-card-selected { - border-color: rgba(96, 165, 250, 0.42); - border-top-color: rgba(96, 165, 250, 0.58); - background: linear-gradient(180deg, rgba(60, 68, 88, 0.7) 0%, rgba(35, 40, 52, 0.7) 100%); + border-color: color-mix(in srgb, var(--accent-info) 42%, var(--border-default)); + border-top-color: color-mix(in srgb, var(--accent-info) 58%, var(--border-strong)); + background: linear-gradient(180deg, color-mix(in srgb, var(--surface-active) 55%, var(--surface-elevated)), var(--surface-secondary)); box-shadow: - 0 20px 48px -8px rgba(0, 0, 0, 0.9), - 0 0 0 1px rgba(96, 165, 250, 0.25), - 0 0 40px rgba(96, 165, 250, 0.15), - inset 0 1px 1px rgba(255, 255, 255, 0.3); + var(--shadow-lg), + 0 0 0 1px color-mix(in srgb, var(--accent-info) 25%, transparent), + var(--glow-info), + inset 0 1px 1px color-mix(in srgb, var(--alpha-white-high) 50%, transparent); } .glass-panel { @@ -265,19 +257,19 @@ body { } .bg-earthy-gradient { - background: linear-gradient(to bottom right, #2D2D2D, #363636); + background: linear-gradient(to bottom right, var(--surface-secondary), var(--surface-tertiary)); } -/* Shared dark form controls to avoid white-on-white browser defaults */ +/* Shared themed form controls */ .ui-field { - border: 1px solid rgba(255, 255, 255, 0.1); + border: 1px solid var(--border-default); border-top-color: var(--edge-top); border-bottom-color: var(--edge-bottom); - background: linear-gradient(180deg, rgba(32, 34, 42, 0.72), rgba(17, 19, 26, 0.72)); + background: linear-gradient(180deg, var(--surface-input), var(--surface-secondary)); color: var(--color-text-strong); box-shadow: - 0 8px 20px -12px rgba(0, 0, 0, 0.85), - inset 0 1px 0 rgba(255, 255, 255, 0.06); + var(--shadow-sm), + inset 0 1px 0 color-mix(in srgb, var(--alpha-white-high) 22%, transparent); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); } @@ -288,10 +280,10 @@ body { .ui-field:focus-visible { outline: none; - border-color: rgba(96, 165, 250, 0.48); + border-color: color-mix(in srgb, var(--accent-info) 48%, var(--border-default)); box-shadow: - 0 0 0 2px rgba(96, 165, 250, 0.2), - inset 0 1px 0 rgba(255, 255, 255, 0.06); + 0 0 0 2px color-mix(in srgb, var(--accent-info) 20%, transparent), + inset 0 1px 0 color-mix(in srgb, var(--alpha-white-high) 22%, transparent); } .ui-select { @@ -326,19 +318,19 @@ body { } .ui-shell-topbar { - background: linear-gradient(180deg, color-mix(in srgb, var(--ui-bg-panel) 92%, black), var(--ui-bg-shell)); + background: linear-gradient(180deg, var(--ui-bg-panel), var(--ui-bg-shell)); border-bottom: 1px solid color-mix(in srgb, var(--ui-accent-info) 22%, var(--ui-border-soft)); - box-shadow: 0 10px 24px -20px rgba(0, 0, 0, 0.9); + box-shadow: var(--shadow-sm); } .ui-shell-middle { - background: linear-gradient(180deg, color-mix(in srgb, var(--ui-bg-app) 74%, black), color-mix(in srgb, var(--ui-bg-app) 90%, black)); + background: linear-gradient(180deg, var(--ui-bg-app), var(--ui-bg-main)); border-left: 1px solid color-mix(in srgb, var(--ui-accent-info) 20%, var(--ui-border-soft)); border-right: 1px solid color-mix(in srgb, var(--ui-accent-info) 20%, var(--ui-border-soft)); } .ui-shell-panel { - background: linear-gradient(180deg, color-mix(in srgb, var(--ui-bg-shell) 86%, black), color-mix(in srgb, var(--ui-bg-panel) 84%, black)); + background: linear-gradient(180deg, var(--ui-bg-shell), var(--ui-bg-panel)); border-left: 1px solid color-mix(in srgb, var(--ui-accent-info) 30%, var(--ui-border-soft)); } @@ -349,8 +341,8 @@ body { .workflow-graph-legend { backdrop-filter: blur(12px); - background: rgba(20, 23, 31, 0.72); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 8px 32px rgba(0, 0, 0, 0.3); + background: color-mix(in srgb, var(--surface-overlay) 78%, transparent); + box-shadow: inset 0 1px 0 color-mix(in srgb, var(--alpha-white-high) 22%, transparent), var(--shadow-md); } .workflow-graph-flow .react-flow__viewport { diff --git a/src/app/graph/page.tsx b/src/app/graph/page.tsx deleted file mode 100644 index dbc23ce..0000000 --- a/src/app/graph/page.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { DependencyGraphPage } from '../../components/graph/dependency-graph-page'; - -export const dynamic = 'force-dynamic'; -import { readIssuesForScope } from '../../lib/aggregate-read'; -import { resolveProjectScope } from '../../lib/project-scope'; -import { listProjects } from '../../lib/registry'; - -interface GraphPageProps { - searchParams?: Promise>; -} - -export default async function GraphPage({ searchParams }: GraphPageProps) { - const params = (await searchParams) ?? {}; - const requestedProjectKey = typeof params.project === 'string' ? params.project : null; - const requestedMode = typeof params.mode === 'string' ? params.mode : null; - const registryProjects = await listProjects(); - const scope = resolveProjectScope({ - currentProjectRoot: process.cwd(), - registryProjects, - requestedProjectKey, - requestedMode, - }); - - const issues = await readIssuesForScope({ - mode: scope.mode, - selected: scope.selected, - scopeOptions: scope.options, - preferBd: true, - }); - return ( - - ); -} diff --git a/src/app/mockup/page.tsx b/src/app/mockup/page.tsx deleted file mode 100644 index b1ff3a7..0000000 --- a/src/app/mockup/page.tsx +++ /dev/null @@ -1,556 +0,0 @@ -"use client" - -import { useCallback, useEffect, useMemo, useState } from "react" -import { useRouter, useSearchParams } from "next/navigation" -import { ArrowLeft, ArrowUpRight, Clock3, Link2, MessageCircle, TriangleAlert, X } from "lucide-react" - -import { Badge } from "@/components/ui/badge" -import { Button } from "@/components/ui/button" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Input } from "@/components/ui/input" -import { ScrollArea } from "@/components/ui/scroll-area" -import { Separator } from "@/components/ui/separator" - -type TaskStatus = "open" | "in_progress" | "blocked" | "deferred" | "closed" - -type Task = { - id: string - title: string - description: string - status: TaskStatus - priority: 0 | 1 | 2 | 3 | 4 - issueType: string - assignee: string - owner: string - labels: string[] - blockedReason: string - updatedAgo: string - dependencyCount: number - blockedByCount: number - commentCount: number - unread: boolean -} - -type Epic = { - id: string - name: string - progress: number - openCount: number - tasks: Task[] -} - -const palette = { - primary: "#F2A62F", - secondary: "#00D1A8", - accent: "#0FC5AE", - eggplant: "#4A2F63", - bg: "#2D2E3C", - surface: "#333341", - border: "#4A4D5C", - text: "#EDEBE5", - textSecondary: "#B8B7B1", - mutedBg: "#2F2F3E", - success: "#0FC5AE", - warning: "#D28A2C", - error: "#D64545", - info: "#00D1A8", - atmosphereWarm: "#5A4632", - atmosphereCool: "#23484D", -} - -const initialEpics: Epic[] = [ - { - id: "bb-ui2", - name: "Unified UX - Earthy Dark Shell", - progress: 69, - openCount: 11, - tasks: [ - { id: "bb-atf", title: "Agent swarm-view-integrator", description: "Integrate swarm view into social workroom shell.", status: "open", priority: 1, issueType: "task", assignee: "sarah.lee", owner: "swarm-team", labels: ["social", "swarm"], blockedReason: "", updatedAgo: "8m", dependencyCount: 1, blockedByCount: 0, commentCount: 3, unread: true }, - { id: "bb-z6s", title: "Agent social-view-integrator", description: "Wire social stream cards and panel routing.", status: "in_progress", priority: 0, issueType: "feature", assignee: "alex.chen", owner: "social-team", labels: ["social", "ui"], blockedReason: "", updatedAgo: "14m", dependencyCount: 2, blockedByCount: 0, commentCount: 7, unread: true }, - { id: "bb-nuy", title: "Agent swarm-card-builder", description: "Build consistent swarm card visuals and metadata.", status: "blocked", priority: 0, issueType: "bug", assignee: "alex.chen", owner: "swarm-team", labels: ["swarm", "cards"], blockedReason: "Waiting on dependency bb-ui2.0", updatedAgo: "35m", dependencyCount: 3, blockedByCount: 1, commentCount: 5, unread: true }, - { id: "bb-3ha", title: "Agent sessions-integrator", description: "Session metrics panel integrated and verified.", status: "closed", priority: 2, issueType: "chore", assignee: "alex.chen", owner: "sessions-team", labels: ["sessions"], blockedReason: "", updatedAgo: "2h", dependencyCount: 0, blockedByCount: 0, commentCount: 4, unread: false }, - ], - }, - { - id: "bb-xhm", - name: "Timeline and Activity Feed", - progress: 80, - openCount: 5, - tasks: [ - { id: "bb-3dv", title: "Agent rightpanel-builder", description: "Implement right rail card stack and compact activity.", status: "open", priority: 2, issueType: "task", assignee: "alex.chen", owner: "layout-team", labels: ["layout", "right-panel"], blockedReason: "", updatedAgo: "11m", dependencyCount: 1, blockedByCount: 0, commentCount: 1, unread: true }, - { id: "bb-dwz", title: "Agent leftpanel-builder", description: "Epic->task navigation with search and metadata icons.", status: "in_progress", priority: 1, issueType: "feature", assignee: "sarah.lee", owner: "layout-team", labels: ["layout", "left-panel"], blockedReason: "", updatedAgo: "19m", dependencyCount: 0, blockedByCount: 0, commentCount: 6, unread: true }, - { id: "bb-5am", title: "Agent topbar-builder", description: "Topbar controls and filter sync.", status: "blocked", priority: 1, issueType: "bug", assignee: "agent-007", owner: "layout-team", labels: ["topbar"], blockedReason: "Navigation contract mismatch", updatedAgo: "41m", dependencyCount: 2, blockedByCount: 1, commentCount: 2, unread: false }, - { id: "bb-z2l", title: "Agent mobile-nav-builder", description: "Mobile drawer flow for three-pane shell.", status: "deferred", priority: 1, issueType: "task", assignee: "sarah.lee", owner: "mobile-team", labels: ["mobile", "navigation"], blockedReason: "", updatedAgo: "52m", dependencyCount: 0, blockedByCount: 0, commentCount: 2, unread: false }, - ], - }, -] - -function statusClasses(status: TaskStatus) { - if (status === "in_progress") return "border-l-[3px] border-l-[#0FC5AE] bg-[linear-gradient(145deg,#333341,#2F2F3E)]" - if (status === "blocked") return "border-l-[3px] border-l-[#D64545] bg-[linear-gradient(145deg,#333341,#302B31)]" - if (status === "deferred") return "border-l-[3px] border-l-[#D28A2C] bg-[linear-gradient(145deg,#333341,#342F29)]" - if (status === "closed") return "border-l-[3px] border-l-[#6D6F7B] bg-[linear-gradient(145deg,#333341,#2F3039)]" - return "border-l-[3px] border-l-[#00D1A8] bg-[linear-gradient(145deg,#333341,#2D313D)]" -} - -function statusBadge(status: TaskStatus) { - if (status === "in_progress") return "bg-[#0FC5AE] text-[#0E2220]" - if (status === "blocked") return "bg-[#D64545] text-white" - if (status === "deferred") return "bg-[#D28A2C] text-[#24190C]" - if (status === "closed") return "bg-[#5A5D6A] text-[#D4D6DE]" - return "bg-[#00D1A8] text-[#07221C]" -} - -const panelClass = "rounded-2xl border shadow-[0_16px_40px_rgba(0,0,0,0.28)] backdrop-blur-[2px]" -const subPanelClass = "rounded-xl border" - -function updateQuery(searchParams: URLSearchParams, updates: Record) { - const next = new URLSearchParams(searchParams.toString()) - for (const [key, value] of Object.entries(updates)) { - if (!value) next.delete(key) - else next.set(key, value) - } - const qs = next.toString() - return qs ? `?${qs}` : "?" -} - -export default function MockupPage() { - const searchParams = useSearchParams() - const router = useRouter() - - const [epics, setEpics] = useState(initialEpics) - const [query, setQuery] = useState("") - const [leftMode, setLeftMode] = useState<"epics" | "tasks">("epics") - - const urlEpic = searchParams.get("epic") - const urlTask = searchParams.get("task") - const urlThread = searchParams.get("thread") === "open" - - const initialEpic = epics.find((epic) => epic.id === urlEpic) ?? epics[0] - const [selectedEpicId, setSelectedEpicId] = useState(initialEpic.id) - const [selectedTaskId, setSelectedTaskId] = useState(urlTask ?? initialEpic.tasks[0].id) - const [threadOpen, setThreadOpen] = useState(urlThread) - const [threadEditMode, setThreadEditMode] = useState(false) - - const [draftTitle, setDraftTitle] = useState("") - const [draftDescription, setDraftDescription] = useState("") - const [draftStatus, setDraftStatus] = useState("open") - const [draftPriority, setDraftPriority] = useState<0 | 1 | 2 | 3 | 4>(2) - const [draftIssueType, setDraftIssueType] = useState("") - const [draftAssignee, setDraftAssignee] = useState("") - const [draftOwner, setDraftOwner] = useState("") - const [draftLabels, setDraftLabels] = useState("") - const [draftBlockedReason, setDraftBlockedReason] = useState("") - const [savePulse, setSavePulse] = useState(false) - - const closeThread = useCallback(() => { - setThreadOpen(false) - setThreadEditMode(false) - }, []) - - useEffect(() => { - const next = updateQuery(searchParams, { - epic: selectedEpicId, - task: selectedTaskId, - thread: threadOpen ? "open" : null, - }) - router.replace(next, { scroll: false }) - }, [router, searchParams, selectedEpicId, selectedTaskId, threadOpen]) - - useEffect(() => { - if (!threadOpen) { - return - } - const onKeyDown = (event: KeyboardEvent) => { - if (event.key === "Escape") { - closeThread() - } - } - window.addEventListener("keydown", onKeyDown) - return () => window.removeEventListener("keydown", onKeyDown) - }, [threadOpen, closeThread]) - - const selectedEpic = epics.find((epic) => epic.id === selectedEpicId) ?? epics[0] - const filteredTasks = useMemo(() => { - const q = query.trim().toLowerCase() - return selectedEpic.tasks.filter((task) => `${task.id} ${task.title}`.toLowerCase().includes(q)) - }, [query, selectedEpic.tasks]) - const selectedTask = filteredTasks.find((task) => task.id === selectedTaskId) ?? filteredTasks[0] - - useEffect(() => { - if (!selectedTask) return - setDraftTitle(selectedTask.title) - setDraftDescription(selectedTask.description) - setDraftStatus(selectedTask.status) - setDraftPriority(selectedTask.priority) - setDraftIssueType(selectedTask.issueType) - setDraftAssignee(selectedTask.assignee) - setDraftOwner(selectedTask.owner) - setDraftLabels(selectedTask.labels.join(", ")) - setDraftBlockedReason(selectedTask.blockedReason) - setThreadEditMode(false) - }, [selectedTask, selectedTask?.id]) - - const saveTaskChanges = () => { - if (!selectedTask) return - const nextLabels = draftLabels - .split(",") - .map((part) => part.trim()) - .filter(Boolean) - setEpics((current) => - current.map((epic) => - epic.id !== selectedEpicId - ? epic - : { - ...epic, - tasks: epic.tasks.map((task) => - task.id !== selectedTask.id - ? task - : { - ...task, - title: draftTitle, - description: draftDescription, - status: draftStatus, - priority: draftPriority, - issueType: draftIssueType, - assignee: draftAssignee, - owner: draftOwner, - labels: nextLabels, - blockedReason: draftBlockedReason, - updatedAgo: "now", - blockedByCount: draftStatus === "blocked" ? Math.max(task.blockedByCount, 1) : 0, - } - ), - } - ) - ) - setSavePulse(true) - setTimeout(() => setSavePulse(false), 900) - } - - return ( -
-
-
-
-
-

Social Workroom

-

Task-first center. Epic drill-in. Live awareness rail.

-
- mockup route -
- -
- - -
- {leftMode === "tasks" ? ( - - ) : ( - Epics - )} - {selectedEpic.openCount} open -
- Select an epic, then choose a task. -
- - setQuery(event.target.value)} - placeholder={leftMode === "epics" ? "Search epics" : "Search tasks"} - className="mb-3" - style={{ backgroundColor: palette.mutedBg, borderColor: palette.border }} - /> - -
- {leftMode === "epics" - ? epics - .filter((epic) => epic.name.toLowerCase().includes(query.toLowerCase())) - .map((epic) => ( - - )) - : filteredTasks.map((task) => ( - - ))} -
-
-
-
- - - -
-
- {selectedEpic.name} - Task cards + thread context -
- -
-
- - -
- {filteredTasks.map((task) => ( - - ))} -
-
- -
-
-

Conversation: {selectedTask?.id}

- -
-
-
- alex.chen - 2m - Need confirmation that detail strip stays sticky while card grid scrolls. -
-
- sarah.lee - 1m - Approved if right rail remains visible at 1280px breakpoint. -
-
-
-
-
- - - - Live Context - Persistent awareness while working tasks. - - -
-

Live Agents

-
-

swarm-view-integratoronline

-

social-view-integratoraway

-

graph-integratorbusy

-
-
-
-

Recent Activity

-
-

5m · bb-z6s moved to in progress

-

11m · bb-atf received 2 comments

-

18m · bb-3ha marked closed

-

33m · bb-nuy dependency changed

-
-
-
-

Attention

-

2 blocked tasks in selected epic

-
-
-
-
-
-
- - {threadOpen ? ( -
-
event.stopPropagation()} - > -
-
-

Thread · {selectedTask?.id}

-

Bead summary and inline edit mode

-
- -
-
-
-

{threadEditMode ? "Edit task" : "Task summary"}

- - {savePulse ? "saved" : "ready"} - -
- {!threadEditMode ? ( -
-
-

{selectedTask?.id}

-

{selectedTask?.title}

-

{selectedTask?.description}

-
-
-
Status: {selectedTask?.status}
-
Priority: P{selectedTask?.priority}
-
Assignee: {selectedTask?.assignee || "-"}
-
Owner: {selectedTask?.owner || "-"}
-
- Labels: {selectedTask?.labels.join(", ") || "-"} -
-
- Blocked reason: {selectedTask?.blockedReason || "None"} -
-
-
- -
-
- ) : ( - <> -
-
-

Title

- setDraftTitle(event.target.value)} style={{ backgroundColor: "#323342", borderColor: "#585B6D", color: palette.text }} /> -
-
-

Assignee

- setDraftAssignee(event.target.value)} style={{ backgroundColor: "#323342", borderColor: "#585B6D", color: palette.text }} /> -
-
-

Description

- setDraftDescription(event.target.value)} style={{ backgroundColor: "#323342", borderColor: "#585B6D", color: palette.text }} /> -
-
-

Issue type

- setDraftIssueType(event.target.value)} style={{ backgroundColor: "#323342", borderColor: "#585B6D", color: palette.text }} /> -
-
-

Owner

- -
-
-

Labels (comma separated)

- setDraftLabels(event.target.value)} style={{ backgroundColor: "#323342", borderColor: "#585B6D", color: palette.text }} /> -
-
-
-

Status

-
- {(["open", "in_progress", "blocked", "deferred", "closed"] as TaskStatus[]).map((status) => ( - - ))} -
-
-
-

Priority

-
- {([0, 1, 2, 3, 4] as const).map((priority) => ( - - ))} -
-
-
-

Blocked reason

- setDraftBlockedReason(event.target.value)} style={{ backgroundColor: "#323342", borderColor: "#585B6D", color: palette.text }} /> -
-
- - -
- - )} -
-
-
- ) : null} -
- ) -} diff --git a/src/app/page-old.tsx b/src/app/page-old.tsx deleted file mode 100644 index dbbce2a..0000000 --- a/src/app/page-old.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { KanbanPage } from '../components/kanban/kanban-page'; -import { readIssuesForScope } from '../lib/aggregate-read'; -import { resolveProjectScope } from '../lib/project-scope'; -import { listProjects } from '../lib/registry'; - -interface PageProps { - searchParams?: Promise>; -} - -export default async function Page({ searchParams }: PageProps) { - const params = (await searchParams) ?? {}; - const requestedProjectKey = typeof params.project === 'string' ? params.project : null; - const requestedMode = typeof params.mode === 'string' ? params.mode : null; - const registryProjects = await listProjects(); - const scope = resolveProjectScope({ - currentProjectRoot: process.cwd(), - registryProjects, - requestedProjectKey, - requestedMode, - }); - - const issues = await readIssuesForScope({ - mode: scope.mode, - selected: scope.selected, - scopeOptions: scope.options, - preferBd: true, - }); - return ( - - ); -} diff --git a/src/app/sessions/page.tsx b/src/app/sessions/page.tsx deleted file mode 100644 index 83a3837..0000000 --- a/src/app/sessions/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from 'next/navigation'; - -export default function SessionsRedirectPage() { - redirect('/?view=social'); -} diff --git a/src/app/timeline/page.tsx b/src/app/timeline/page.tsx deleted file mode 100644 index da94814..0000000 --- a/src/app/timeline/page.tsx +++ /dev/null @@ -1,60 +0,0 @@ -'use client'; - -import { useEffect } from 'react'; -import { TimelineFeed } from '../../components/timeline/timeline-feed'; -import { useTimelineStore } from '../../components/timeline/timeline-store'; - -export default function TimelinePage() { - return ( -
-
-

Activity Timeline

-

Real-time stream of project mutations.

-
- - - - -
- ); -} - -function TimelineControls() { - return ( -
- {/* Placeholder for future filters */} -
Showing all activity
-
- ); -} - -function TimelineSubscription() { - const { addEvent, setHistory } = useTimelineStore(); - - useEffect(() => { - // 1. Fetch history - fetch('/api/activity') - .then(res => { - if (!res.ok) throw new Error('History fetch failed'); - return res.json(); - }) - .then(data => setHistory(data)) - .catch(err => console.error('Failed to load history', err)); - - // 2. Subscribe to SSE - const es = new EventSource('/api/events'); - - es.addEventListener('activity', (e) => { - try { - const event = JSON.parse(e.data); - addEvent(event); - } catch (err) { - console.error('Failed to parse activity event', err); - } - }); - - return () => es.close(); - }, [setHistory, addEvent]); - - return null; -} \ No newline at end of file diff --git a/src/components/activity/activity-panel.tsx b/src/components/activity/activity-panel.tsx index 8df8023..89744b9 100644 --- a/src/components/activity/activity-panel.tsx +++ b/src/components/activity/activity-panel.tsx @@ -111,25 +111,25 @@ export function formatRelativeTime(timestamp: string): string { function getAgentTone(status: AgentStatus): AgentTone { const tones: Record = { active: { - cardClass: 'bg-[#173126]', + cardClass: 'bg-[var(--status-ready)]', labelClass: 'text-[#7CB97A]', ringClass: 'ring-[#7CB97A]/45', glowClass: 'bg-[#7CB97A]/30', }, stale: { - cardClass: 'bg-[#322817]', + cardClass: 'bg-[var(--status-in-progress)]', labelClass: 'text-[#D4A574]', ringClass: 'ring-[#D4A574]/45', glowClass: 'bg-[#D4A574]/30', }, stuck: { - cardClass: 'bg-[#341a1f]', + cardClass: 'bg-[var(--status-blocked)]', labelClass: 'text-[#C97A7A]', ringClass: 'ring-[#C97A7A]/45', glowClass: 'bg-[#C97A7A]/30', }, dead: { - cardClass: 'bg-[#2b232b]', + cardClass: 'bg-[var(--surface-primary)]', labelClass: 'text-[#A78A94]', ringClass: 'ring-[#A78A94]/40', glowClass: 'bg-[#A78A94]/25', @@ -147,84 +147,84 @@ export function getEventTone(kind: string): EventTone { label: 'Created', labelClass: 'text-[#7CB97A]', dotClass: 'bg-[#7CB97A]', - cardClass: 'bg-[#182f25]', + cardClass: 'bg-[var(--status-ready)]', idClass: 'text-[#9ACB98]', }, opened: { label: 'Opened', labelClass: 'text-[#7CB97A]', dotClass: 'bg-[#7CB97A]', - cardClass: 'bg-[#182f25]', + cardClass: 'bg-[var(--status-ready)]', idClass: 'text-[#9ACB98]', }, closed: { label: 'Closed', labelClass: 'text-[#D4A574]', dotClass: 'bg-[#D4A574]', - cardClass: 'bg-[#332716]', + cardClass: 'bg-[var(--status-in-progress)]', idClass: 'text-[#DAB891]', }, reopened: { label: 'Reopened', labelClass: 'text-[#5B95E8]', dotClass: 'bg-[#5B95E8]', - cardClass: 'bg-[#1b2b43]', + cardClass: 'bg-[var(--surface-primary)]', idClass: 'text-[#8DB4EF]', }, status_changed: { label: 'Status changed', labelClass: 'text-[#D4A574]', dotClass: 'bg-[#D4A574]', - cardClass: 'bg-[#2f2518]', + cardClass: 'bg-[var(--status-in-progress)]', idClass: 'text-[#DAB891]', }, priority_changed: { label: 'Priority changed', labelClass: 'text-[#D4A574]', dotClass: 'bg-[#D4A574]', - cardClass: 'bg-[#2f2518]', + cardClass: 'bg-[var(--status-in-progress)]', idClass: 'text-[#DAB891]', }, assignee_changed: { label: 'Assigned', labelClass: 'text-[#D4A574]', dotClass: 'bg-[#D4A574]', - cardClass: 'bg-[#2f2518]', + cardClass: 'bg-[var(--status-in-progress)]', idClass: 'text-[#DAB891]', }, dependency_added: { label: 'Dependency added', labelClass: 'text-[#D4A574]', dotClass: 'bg-[#D4A574]', - cardClass: 'bg-[#2f2518]', + cardClass: 'bg-[var(--status-in-progress)]', idClass: 'text-[#DAB891]', }, dependency_removed: { label: 'Dependency removed', labelClass: 'text-[#C97A7A]', dotClass: 'bg-[#C97A7A]', - cardClass: 'bg-[#321b21]', + cardClass: 'bg-[var(--status-blocked)]', idClass: 'text-[#D9A9A9]', }, heartbeat: { label: 'Heartbeat', labelClass: 'text-[#5BA8A0]', dotClass: 'bg-[#5BA8A0]', - cardClass: 'bg-[#173034]', + cardClass: 'bg-[var(--surface-primary)]', idClass: 'text-[#8BC9C1]', }, commented: { label: 'Commented', labelClass: 'text-[#5BA8A0]', dotClass: 'bg-[#5BA8A0]', - cardClass: 'bg-[#173034]', + cardClass: 'bg-[var(--surface-primary)]', idClass: 'text-[#8BC9C1]', }, comment_added: { label: 'Commented', labelClass: 'text-[#5BA8A0]', dotClass: 'bg-[#5BA8A0]', - cardClass: 'bg-[#173034]', + cardClass: 'bg-[var(--surface-primary)]', idClass: 'text-[#8BC9C1]', }, }; @@ -234,7 +234,7 @@ export function getEventTone(kind: string): EventTone { label: normalized.replace(/_/g, ' '), labelClass: 'text-[#5BA8A0]', dotClass: 'bg-[#5BA8A0]', - cardClass: 'bg-[#173034]', + cardClass: 'bg-[var(--surface-primary)]', idClass: 'text-[#8BC9C1]', } ); @@ -299,7 +299,7 @@ export function ActivityPanel({ issues, collapsed = false, projectRoot }: Activi const activeAgents = agentRoster.filter(a => a.status === 'active').length; if (collapsed) { return ( -
+
{/* Collapsed Agent Icons with ZFC Rings */}
{agentRoster.slice(0, 6).map(agent => ( @@ -316,7 +316,7 @@ export function ActivityPanel({ issues, collapsed = false, projectRoot }: Activi agent.status === 'stale' ? 'ring-[#D4A574]/45' : agent.status === 'stuck' ? 'ring-[#C97A7A]/45' : 'ring-[#A78A94]/40' )}> - + {getInitials(agent.name)} @@ -340,9 +340,9 @@ export function ActivityPanel({ issues, collapsed = false, projectRoot }: Activi } return ( -
+
{/* AGENT ROSTER SECTION */} -
+
@@ -368,7 +368,7 @@ export function ActivityPanel({ issues, collapsed = false, projectRoot }: Activi getAgentTone(agent.status).glowClass )} /> - + {getInitials(agent.name)} diff --git a/src/components/activity/swarm-command-feed.tsx b/src/components/activity/swarm-command-feed.tsx index 5976f14..b54aedc 100644 --- a/src/components/activity/swarm-command-feed.tsx +++ b/src/components/activity/swarm-command-feed.tsx @@ -76,9 +76,9 @@ export function SwarmCommandFeed({ epicId, issues, projectRoot }: SwarmCommandFe }, [projectRoot, contextBeadIds]); return ( -
+
{/* SQUAD ROSTER SECTION */} -
+
@@ -96,7 +96,7 @@ export function SwarmCommandFeed({ epicId, issues, projectRoot }: SwarmCommandFe ) : (
{rosterEntries.map((agent, i) => ( -
+
diff --git a/src/components/graph/assignment-panel.tsx b/src/components/graph/assignment-panel.tsx index 055aa2b..c481d68 100644 --- a/src/components/graph/assignment-panel.tsx +++ b/src/components/graph/assignment-panel.tsx @@ -18,6 +18,7 @@ export interface AssignmentPanelProps { projectRoot: string; issues: BeadIssue[]; epicId?: string; + onIssueUpdated?: () => void; } function hasAgentLabel(labels: string[]): boolean { @@ -38,13 +39,17 @@ function truncateTitle(title: string, maxLength: number = 30): string { } function getTemplateId(issue: BeadIssue): string | null { + const templateLabel = issue.labels?.find(l => l.startsWith('template:')); + if (templateLabel) { + return templateLabel.replace('template:', ''); + } if (issue.metadata?.templateId && typeof issue.metadata.templateId === 'string') { return issue.metadata.templateId; } return issue.templateId; } -export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }: AssignmentPanelProps) { +export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId, onIssueUpdated }: AssignmentPanelProps) { const [inspectingArchetypeId, setInspectingArchetypeId] = useState(null); const [inspectingTemplateId, setInspectingTemplateId] = useState(null); const [showArchetypeList, setShowArchetypeList] = useState(false); @@ -127,21 +132,28 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }: const handleApplyTemplateToEpic = async (templateId: string, targetEpicId: string) => { try { + const epic = issues.find(issue => issue.id === targetEpicId); + const currentLabels = epic?.labels || []; + const newLabels = [...currentLabels.filter(l => !l.startsWith('template:')), `template:${templateId}`]; + const res = await fetch('/api/beads/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ projectRoot, id: targetEpicId, - metadata: { templateId } + labels: newLabels }) }); if (!res.ok) { - throw new Error('Failed to apply template'); + const errorData = await res.json(); + console.error('Template API error:', errorData); + throw new Error(errorData?.error?.message || 'Failed to apply template'); } console.log('Template applied successfully:', { templateId, epicId: targetEpicId }); + onIssueUpdated?.(); } catch (error) { console.error('Failed to apply template:', error); } @@ -149,21 +161,28 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }: const handleRemoveTemplateFromEpic = async (targetEpicId: string) => { try { + const epic = issues.find(issue => issue.id === targetEpicId); + const currentLabels = epic?.labels || []; + const newLabels = currentLabels.filter(l => !l.startsWith('template:')); + const res = await fetch('/api/beads/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ projectRoot, id: targetEpicId, - metadata: { templateId: null } + labels: newLabels }) }); if (!res.ok) { - throw new Error('Failed to remove template'); + const errorData = await res.json(); + console.error('Template API error:', errorData); + throw new Error(errorData?.error?.message || 'Failed to remove template'); } console.log('Template removed successfully'); + onIssueUpdated?.(); } catch (error) { console.error('Failed to remove template:', error); } diff --git a/src/components/kanban/kanban-page.tsx b/src/components/kanban/kanban-page.tsx index 8923ace..7e3540d 100644 --- a/src/components/kanban/kanban-page.tsx +++ b/src/components/kanban/kanban-page.tsx @@ -107,6 +107,7 @@ export function KanbanPage({ ); const graphHref = useMemo(() => { const params = new URLSearchParams(); + params.set('view', 'graph'); if (projectScopeMode !== 'single') { params.set('mode', projectScopeMode); } @@ -114,7 +115,7 @@ export function KanbanPage({ params.set('project', projectScopeKey); } const query = params.toString(); - return query ? `/graph?${query}` : '/graph'; + return query ? `/?${query}` : '/?view=graph'; }, [projectScopeKey, projectScopeMode]); const allowMutations = projectScopeMode === 'single'; const blockedTree = useMemo( diff --git a/src/components/sessions/conversation-drawer.tsx b/src/components/sessions/conversation-drawer.tsx index ca002fd..ef013fe 100644 --- a/src/components/sessions/conversation-drawer.tsx +++ b/src/components/sessions/conversation-drawer.tsx @@ -14,6 +14,47 @@ interface ThreadItem { data: any; } +export type CoordMessageAction = 'read' | 'ack'; + +export function buildCoordMessageActionEvent(params: { + action: CoordMessageAction; + message: AgentMessage; + beadId: string; + projectRoot: string; + nowIso?: string; +}): Record { + const now = params.nowIso ?? new Date().toISOString(); + const eventType = params.action === 'read' ? 'READ' : 'ACK'; + const compactNow = now.replace(/[-:.TZ]/g, ''); + return { + version: 'coord.v1', + kind: 'coord_event', + issue_id: params.beadId, + actor: params.message.to_agent, + timestamp: now, + data: { + event_type: eventType, + event_id: `evt_${eventType.toLowerCase()}_${compactNow}_${params.message.message_id}`, + event_ref: params.message.message_id, + project_root: params.projectRoot, + payload: {}, + }, + }; +} + +export function buildCommentMutationBody(params: { + projectRoot: string; + text: string; + actor?: string; +}): Record { + const actor = params.actor?.trim(); + return { + projectRoot: params.projectRoot, + text: params.text, + ...(actor ? { actor } : {}), + }; +} + interface ConversationDrawerProps { beadId: string | null; bead: BeadIssue | null; @@ -44,6 +85,7 @@ export function ConversationDrawer({ const [thread, setThread] = useState([]); const [loading, setLoading] = useState(false); const [commentText, setCommentText] = useState(''); + const [commentActor, setCommentActor] = useState(''); const [submitting, setSubmitting] = useState(false); const [metrics, setMetrics] = useState(null); const [showSummary, setShowSummary] = useState(false); @@ -77,6 +119,16 @@ export function ConversationDrawer({ } }, [agentId, projectRoot]); + useEffect(() => { + if (typeof window === 'undefined') return; + try { + const stored = window.localStorage.getItem('bb.humanActor'); + if (stored) setCommentActor(stored); + } catch { + // ignore storage failures + } + }, []); + useEffect(() => { if (open) { if (beadId) fetchConversation({ silent: refreshTrigger > 0 }); @@ -107,9 +159,16 @@ export function ConversationDrawer({ const res = await fetch(`/api/sessions/${beadId}/comment`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ projectRoot, text: commentText }) + body: JSON.stringify(buildCommentMutationBody({ projectRoot, text: commentText, actor: commentActor })), }); if (res.ok) { + try { + if (commentActor.trim()) { + window.localStorage.setItem('bb.humanActor', commentActor.trim()); + } + } catch { + // ignore storage failures + } setCommentText(''); await fetchConversation(); onActivity?.(); @@ -127,8 +186,16 @@ export function ConversationDrawer({ if (!message) return; try { - const res = await fetch(`/api/sessions/${beadId}/messages/${messageId}/${action}?agent=${encodeURIComponent(message.to_agent)}`, { - method: 'POST' + const event = buildCoordMessageActionEvent({ + action, + message, + beadId, + projectRoot, + }); + const res = await fetch('/api/coord/events', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ projectRoot, event }), }); if (res.ok) { await fetchConversation(); @@ -259,6 +326,12 @@ export function ConversationDrawer({ {beadId && !showSummary && (