## Context
The left-panel task list surfaces every bead regardless of whether the task
has been specified thoroughly enough for an agent to pick it up. Tasks with
empty `acceptance_criteria` or very short descriptions are low-signal noise
that muddles the navigation spine — especially now that we plan to dispatch
tasks to Claude agents (which need concrete instructions to succeed).
Epics are deliberately exempt: their role is grouping, not execution, so
requiring acceptance criteria / long descriptions on epics would hide the
entire backbone of the navigation.
## This change
- Plumbs `acceptance_criteria` end-to-end: adds the optional field on
`BeadIssue`, and reads it both from `.beads/issues.jsonl` (parser) and
Dolt SQL (`read-issues-dolt.ts` row shape + normalizer).
- Extends `LeftPanelFilters` with `hideNoAcceptance` and
`hideShortDescription` (both default `true`) in the source-of-truth
`hooks/use-url-state.ts` and in the re-exported shadow type on
`components/shared/left-panel.tsx`.
- Updates `isTaskMatch` in both `left-panel.tsx` and `left-panel-new.tsx`
to skip non-epic tasks lacking acceptance criteria or with a description
shorter than `SHORT_DESCRIPTION_MIN_LENGTH` (200 chars). Epics bypass
both filters via the `issue_type === 'epic'` guard.
- Exposes `isTaskMatch` from `left-panel.tsx` so the filter tests can
assert behavior directly (previously only `shouldHideEpicEntry` was
exported).
- Adds two checkboxes under the existing "Hide Closed" button in both
left-panel variants (legacy `left-panel.tsx` and the one unified-shell
currently wires up — `left-panel-new.tsx`).
- Seeds both new filter flags as `true` in the `UnifiedShell` default
state so fresh sessions see the high-signal view without toggling.
## What is NOT in this change
- No mutation of `bd` / CLI behavior. Filters are purely UI-level.
- No localStorage persistence for the two new flags — existing
`hideClosed` is also React-only, so parity is preserved. If/when we
persist any of these, all three move together.
- No change to the `metadata.acceptance` path used by `kanban.ts`'s
`hasQualitySignal` — that's a separate signal with its own callers.
## Test Plan
### Automated
Tests run from `/home/wizard/code/beadboard`:
```
$ node --import tsx --test tests/components/shared/left-panel-filtering.test.ts
# tests 15
# pass 15
# fail 0
# duration_ms 604
```
All 15 filter cases pass: 6 pre-existing `shouldHideEpicEntry` cases plus
9 new `isTaskMatch` cases covering acceptance-criteria-empty hides,
acceptance-criteria-disabled shows, epic exemption, 199/200-char
description boundary, null description, and short-description flag
disabled.
Related suites still green:
```
$ node --import tsx --test tests/components/shared/left-panel.test.tsx \
tests/components/shared/unified-shell-hide-closed-contract.test.ts
# tests 7 pass 7 fail 0
```
Pre-existing failures in `tests/hooks/url-state-integration.test.ts`
(`view=activity` cases) and one pre-existing typecheck error in
`left-panel.tsx` thread prop are unrelated — both reproduce on `main`
before this change.
### Manual Verification
1. `npm install`
2. `npm run dev`
3. Open `http://localhost:3000` with bd project loaded.
4. Expected: two new checkboxes appear under "Hide Closed" in the left
sidebar — "Hide tasks without acceptance criteria" and
"Hide tasks with short description (<200 chars)". Both checked by
default.
5. Toggle each off. Expected: additional beads appear in epic expansions
(tasks that were previously hidden because they lack quality signal).
6. Confirm epics remain visible regardless of the checkbox state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move leftSidebarMode from URL state to local useState in unified-shell,
avoiding force-dynamic router round-trip that made the button appear broken - Replace fileURLToPath(new URL(..., import.meta.url)) with process.cwd()
in bb-pi-bootstrap.ts — import.meta.url is a webpack:// URL in Next.js,
causing cross-realm TypeError when passed to Node.js fileURLToPath()
- Removed broken LaunchSwarmDialog (formula-based) from TopBar/LeftPanel
- All Rocket buttons (TopBar, LeftPanel, DAG nodes, social cards) now open
AssignmentPanel (archetype-based) which actually works
- Every Rocket clears taskId first so assignMode && !taskId condition passes
- Conversation button priority: taskId always shows conversation, not assign panel
- Added TelemetryStrip: minimized right sidebar with status dots when non-telemetry
panel (conversation/assignment) is active
- Live feed has minimize button → restores last taskId or assignMode
- DAG nodes: Signal icon → restores telemetry feed
- Social button on DAG nodes: single router.push to avoid race (setView + setTaskId)
- Fixed social card message button: opens right panel with drawer:closed (no popup)
Co-Authored-By: Oz <oz-agent@warp.dev>
- Add MessageSquare icon to GraphNodeCard; prop-thread onConversationOpen
and selectedTaskId through WorkflowGraph node data (no useUrlState
inside ReactFlow nodes — avoids context/timing issues)
- Fix ContextualRightPanel: check taskId before epicId so clicking the
conversation icon always opens ThreadDrawer even when an epic filter
is active
- setEpicId now clears task from URL so selecting an epic resets any
open conversation thread
- handleGraphSelect toggles: second click on same node calls setTaskId(null)
closing the right panel
- Add onSelect to WorkflowGraph flowModel deps to prevent stale callbacks
- Fix ContextualRightPanel onClose no-ops: wired to setTaskId(null) /
setSwarmId(null) so back button works
- Right panel always visible (removed panel==='open' gate in UnifiedShell)
- SmartDag task grid: horizontal scroll, fixed-width cards, hideClosed=true
- Add <Suspense> in page.tsx for useSearchParams compatibility
- Enable dolt auto-start in .beads/config.yaml
- Add 14 static analysis tests (graph-node-conversation.test.tsx)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Various supporting changes made during the assign archetypes feature development:
- Added contextual-right-panel.tsx and swarm-command-feed.tsx
- Updated activity-panel.tsx with new features
- UI improvements to left-panel, mobile-nav
- Test updates for url-state-integration, mobile-nav, top-bar
- Package.json updates for dependencies
- Global CSS refinements
These changes support the main assign archetypes feature but are
not directly part of its core functionality.
This commit includes the new SwarmWorkspace with its 3 sub-tabs, the LeftPanel mission picker, and the comprehensive Operations Command Dashboard featuring the live interactive DAG telemetry and task assignment prep flow.
- Created ThreadDrawer component (24rem) that slides from right edge of middle
- RightPanel now reserved for Activity Feed + Agent roster (bb-ui2.29)
- Updated URL state: added drawer and agentId params
- Thread shows in drawer when card selected
Architecture now matches PRD:
- Right Panel (17rem): Activity Feed + Agent roster
- Thread Drawer (24rem): Opens from middle when card clicked
Beads: bb-ui2.31 thread drawer created, bb-ui2.13 closed
STORY:
The Unified UX needs URL as the single source of truth for view state.
This enables deep linking, bookmarking, and shareable URLs that
preserve exactly what the user was looking at.
COLLABORATION:
Created useUrlState hook using Next.js useSearchParams and useRouter:
Interface:
- view: 'social' | 'graph' | 'swarm' (default: social)
- taskId: selected task ID (for detail panel)
- swarmId: selected swarm ID
- panel: 'open' | 'closed' (detail panel state)
- graphTab: 'flow' | 'overview' (graph view mode)
URL Patterns:
- /?view=social
- /?view=social&task=bb-buff.1&panel=open
- /?view=swarm&swarm=bb-buff
- /?view=graph&task=bb-buff.1&graphTab=flow
The hook uses router.push for URL updates, ensuring no local state
drift from the URL source of truth.
DELIVERABLES:
- src/hooks/use-url-state.ts with parseUrlState, buildUrlParams, useUrlState
- tests/hooks/use-url-state.test.ts with 18+ tests
VERIFICATION:
- npm run typecheck: PASS
- npm run lint: PASS
- npm run test: PASS (all tests including new ones)
CLOSES: bb-ui2.4
BLOCKS: bb-ui2.5