diff --git a/.changeset/dashboard-agent-views-441.md b/.changeset/dashboard-agent-views-441.md new file mode 100644 index 0000000..2ecd045 --- /dev/null +++ b/.changeset/dashboard-agent-views-441.md @@ -0,0 +1,5 @@ +--- +"@gemstack/framework": minor +--- + +Let the agent push ad-hoc markdown views into the dashboard right rail. A `show-markdown` block in a turn (non-blocking, unlike a choice gate) becomes a `view` event that renders as a first-class Views tab in the right rail, with a sticky top-nav to jump between views. Re-showing the same title updates that view in place. diff --git a/packages/framework-dashboard/components/RightRail.tsx b/packages/framework-dashboard/components/RightRail.tsx index d84b033..53ef51c 100644 --- a/packages/framework-dashboard/components/RightRail.tsx +++ b/packages/framework-dashboard/components/RightRail.tsx @@ -3,30 +3,42 @@ import type { ChoiceRequest } from '@gemstack/framework' import { DocsPanel } from './DocsPanel.js' import { ProjectLogPanel } from './ProjectLogPanel.js' import { ChoicesRail } from './ChoicesRail.js' +import { ViewsRail } from './ViewsRail.js' +import type { AgentView } from '../lib/live-state.js' import { Badge } from './ui/badge.js' import { Button } from './ui/button.js' import { cn } from '../lib/utils.js' -type Tab = 'choices' | 'docs' | 'log' +type Tab = 'choices' | 'views' | 'docs' | 'log' // The right sidebar (#314 third rail): the interactive choice gates the run parks on -// (#440), the surfaced docs (PLAN/TODO), and the committed project log. Docs/log are -// Telefunc-backed reads of the selected project; the choices come from the live event -// stream, passed down from the shell. The rail jumps to Choices whenever a gate opens so -// the decision is in view. -export function RightRail({ projectId, choices }: { projectId: string | null; choices: ChoiceRequest[] }) { +// (#440), the ad-hoc markdown views the agent pushes (#441), the surfaced docs (PLAN/TODO), +// and the committed project log. Choices/views come from the live event stream, passed +// down from the shell; docs/log are Telefunc-backed reads of the selected project. The rail +// jumps to whatever the run most wants seen: a choice gate first, else a fresh view. +export function RightRail({ + projectId, + choices, + views, +}: { + projectId: string | null + choices: ChoiceRequest[] + views: AgentView[] +}) { const [tab, setTab] = useState('docs') const hasChoices = choices.length > 0 + const hasViews = views.length > 0 - // A gate opening pulls the rail to Choices; when the last one clears, fall back to Docs. + // Pull the rail to the most urgent surface: a choice gate over a view over the docs. useEffect(() => { - setTab(hasChoices ? 'choices' : 'docs') - }, [hasChoices]) + setTab(hasChoices ? 'choices' : hasViews ? 'views' : 'docs') + }, [hasChoices, hasViews]) if (!projectId) return null - const tabs: Tab[] = hasChoices ? ['choices', 'docs', 'log'] : ['docs', 'log'] - const label = (t: Tab) => (t === 'choices' ? 'Choices' : t === 'docs' ? 'Docs' : 'Project log') + const tabs: Tab[] = [...(hasChoices ? ['choices' as const] : []), ...(hasViews ? ['views' as const] : []), 'docs', 'log'] + const label = (t: Tab) => (t === 'choices' ? 'Choices' : t === 'views' ? 'Views' : t === 'docs' ? 'Docs' : 'Log') + const count = (t: Tab) => (t === 'choices' ? choices.length : t === 'views' ? views.length : 0) return (