fix: wire conversation panel to DAG nodes with toggle support

- Add MessageSquare icon to GraphNodeCard; prop-thread onConversationOpen
  and selectedTaskId through WorkflowGraph node data (no useUrlState
  inside ReactFlow nodes — avoids context/timing issues)
- Fix ContextualRightPanel: check taskId before epicId so clicking the
  conversation icon always opens ThreadDrawer even when an epic filter
  is active
- setEpicId now clears task from URL so selecting an epic resets any
  open conversation thread
- handleGraphSelect toggles: second click on same node calls setTaskId(null)
  closing the right panel
- Add onSelect to WorkflowGraph flowModel deps to prevent stale callbacks
- Fix ContextualRightPanel onClose no-ops: wired to setTaskId(null) /
  setSwarmId(null) so back button works
- Right panel always visible (removed panel==='open' gate in UnifiedShell)
- SmartDag task grid: horizontal scroll, fixed-width cards, hideClosed=true
- Add <Suspense> in page.tsx for useSearchParams compatibility
- Enable dolt auto-start in .beads/config.yaml
- Add 14 static analysis tests (graph-node-conversation.test.tsx)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ZenchantLive 2026-03-01 13:51:46 -08:00
parent cb83fd92a9
commit 861ae89491
11 changed files with 1279 additions and 1174 deletions

View file

@ -7,6 +7,7 @@ import { SwarmCommandFeed } from './swarm-command-feed';
import { ThreadDrawer } from '../shared/thread-drawer';
import { MissionInspector } from '../mission/mission-inspector';
import { useSwarmList } from '../../hooks/use-swarm-list';
import { useUrlState } from '../../hooks/use-url-state';
export interface ContextualRightPanelProps {
epicId?: string | null;
@ -17,23 +18,16 @@ export interface ContextualRightPanelProps {
}
export function ContextualRightPanel({ epicId, taskId, swarmId, issues, projectRoot }: ContextualRightPanelProps) {
if (epicId) {
return (
<SwarmCommandFeed
epicId={epicId}
issues={issues}
projectRoot={projectRoot}
/>
);
}
const { setTaskId } = useUrlState();
// Task conversation takes priority — user explicitly clicked the conversation icon
if (taskId) {
const selectedIssue = issues.find(i => i.id === taskId) ?? null;
return (
<ThreadDrawer
isOpen={true}
embedded={true}
onClose={() => {}}
onClose={() => setTaskId(null)}
title={selectedIssue?.title ?? taskId}
id={taskId}
issue={selectedIssue}
@ -43,6 +37,16 @@ export function ContextualRightPanel({ epicId, taskId, swarmId, issues, projectR
);
}
if (epicId) {
return (
<SwarmCommandFeed
epicId={epicId}
issues={issues}
projectRoot={projectRoot}
/>
);
}
if (swarmId) {
return (
<SwarmIdBranch
@ -63,6 +67,7 @@ export function ContextualRightPanel({ epicId, taskId, swarmId, issues, projectR
// Inner component so hooks can be called conditionally via component boundary
function SwarmIdBranch({ swarmId, projectRoot }: { swarmId: string; projectRoot: string }) {
const { setSwarmId } = useUrlState();
const { swarms } = useSwarmList(projectRoot);
const swarm = swarms.find(s => s.swarmId === swarmId);
// Fall back to swarmId as title while swarm list loads
@ -76,7 +81,7 @@ function SwarmIdBranch({ swarmId, projectRoot }: { swarmId: string; projectRoot:
missionTitle={missionTitle}
projectRoot={projectRoot}
assignedAgents={assignedAgents}
onClose={() => {}}
onClose={() => setSwarmId(null)}
onAssign={async () => {}}
/>
);