feat(phase0+1): wire URL context to ContextualRightPanel

Phase 0:
- UnifiedShell: pass blockedOnly to SocialPage; wire TopBar with live counts
- thread-drawer: show real issue.status instead of hardcoded "In Progress"
- social-page: fix onJumpToActivity to open right panel (not dead ?view=activity)

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

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

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ZenchantLive 2026-02-28 14:59:09 -08:00
parent fccb2dede7
commit 7d37d02af1
6 changed files with 731 additions and 237 deletions

View file

@ -4,14 +4,19 @@ import React from 'react';
import type { BeadIssue } from '../../lib/types';
import { ActivityPanel } from './activity-panel';
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';
export interface ContextualRightPanelProps {
epicId?: string | null;
taskId?: string | null;
swarmId?: string | null;
issues: BeadIssue[];
projectRoot: string;
}
export function ContextualRightPanel({ epicId, issues, projectRoot }: ContextualRightPanelProps) {
export function ContextualRightPanel({ epicId, taskId, swarmId, issues, projectRoot }: ContextualRightPanelProps) {
if (epicId) {
return (
<SwarmCommandFeed
@ -22,6 +27,31 @@ export function ContextualRightPanel({ epicId, issues, projectRoot }: Contextual
);
}
if (taskId) {
const selectedIssue = issues.find(i => i.id === taskId) ?? null;
return (
<ThreadDrawer
isOpen={true}
embedded={true}
onClose={() => {}}
title={selectedIssue?.title ?? taskId}
id={taskId}
issue={selectedIssue}
projectRoot={projectRoot}
onIssueUpdated={async () => {}}
/>
);
}
if (swarmId) {
return (
<SwarmIdBranch
swarmId={swarmId}
projectRoot={projectRoot}
/>
);
}
// Fallback to Global feed
return (
<ActivityPanel
@ -30,3 +60,24 @@ export function ContextualRightPanel({ epicId, issues, projectRoot }: Contextual
/>
);
}
// Inner component so hooks can be called conditionally via component boundary
function SwarmIdBranch({ swarmId, projectRoot }: { swarmId: string; projectRoot: string }) {
const { swarms } = useSwarmList(projectRoot);
const swarm = swarms.find(s => s.swarmId === swarmId);
// Fall back to swarmId as title while swarm list loads
const missionTitle = swarm?.title ?? swarmId;
// TODO (follow-up): populate assignedAgents from swarm.agents once agent-registry is wired
const assignedAgents = swarm?.agents ?? [];
return (
<MissionInspector
missionId={swarmId}
missionTitle={missionTitle}
projectRoot={projectRoot}
assignedAgents={assignedAgents}
onClose={() => {}}
onAssign={async () => {}}
/>
);
}