CanvasRenderer.tsx4.2 KBView on GitHub /**
* CanvasRenderer — Polymorphic canvas dispatcher.
*
* Implements §9.1 of docs/architecture/canvas-architecture.md.
*
* Reads canvas.type and routes to the appropriate renderer:
* 'conversationCanvas' → ConversationCanvasView
* 'report' → ReportCanvasView (stub)
* 'tasks' → TasksCanvasView (stub)
* 'agenda' → AgendaCanvasView (stub)
* 'calendar' → CalendarCanvasView (stub)
*
* When showHeader is true (default), renders CanvasHeader above the canvas
* content — providing an editable title + SaveConfigButton for all types.
*
* Stubs will be replaced with full implementations in Phase 5.
*/
import type { Canvas } from '@/modules/canvas/types/canvas-types';
import { useCedarStore } from '@/modules/store';
import { useEffect } from 'react';
import { CalendarCanvas } from '@/modules/agentCanvas/components/CalendarCanvas';
import { ConversationCanvasView } from './ConversationCanvasView';
import { KanbanConversationView } from './KanbanConversationView';
import { CardListCanvasView } from './CardListCanvasView';
import { TableCanvasView } from './TableCanvasView';
import { ReportCanvasView } from './ReportCanvasView';
import { StatisticsCanvasView } from './StatisticsCanvasView';
import { CanvasHeader } from './CanvasHeader';
interface CanvasRendererProps {
canvas: Canvas;
className?: string;
/** Show the canvas header (title + save buttons). Defaults to true. */
showHeader?: boolean;
}
// ─── Phase 5 stubs ────────────────────────────────────────────────────────────
function TasksCanvasView() {
return (
<div className="flex h-full w-full flex-col items-center justify-center gap-2 p-8">
<p className="text-muted-foreground text-sm">Tasks canvas — coming in Phase 5</p>
</div>
);
}
function CalendarCanvasView() {
return (
<div className="flex h-full w-full flex-col items-center justify-center gap-2 p-8">
<p className="text-muted-foreground text-sm">Calendar canvas — coming in Phase 5</p>
</div>
);
}
// ─── Dispatcher ───────────────────────────────────────────────────────────────
export function CanvasRenderer({ canvas, className, showHeader = true }: CanvasRendererProps) {
// Announce that this canvas is on screen. `mountedCanvasIds` is what the chat context
// reads to decide whether the canvas (and its conversation list) is part of the user's
// current view — being pinned as a HomeView tab is not enough.
const registerMountedCanvas = useCedarStore((s) => s.registerMountedCanvas);
const unregisterMountedCanvas = useCedarStore((s) => s.unregisterMountedCanvas);
useEffect(() => {
registerMountedCanvas(canvas.id);
return () => unregisterMountedCanvas(canvas.id);
}, [canvas.id, registerMountedCanvas, unregisterMountedCanvas]);
const renderContent = () => {
switch (canvas.type) {
case 'conversationCanvas':
return <ConversationCanvasView canvas={canvas} />;
case 'kanbanConversation':
return <KanbanConversationView canvas={canvas} />;
case 'cardList':
return <CardListCanvasView canvas={canvas} />;
case 'table':
return <TableCanvasView canvas={canvas} />;
case 'statistics':
return <StatisticsCanvasView canvas={canvas} />;
case 'report':
return <ReportCanvasView canvas={canvas} />;
case 'tasks':
return <TasksCanvasView />;
case 'agenda':
return <CalendarCanvas />;
case 'calendar':
return <CalendarCanvasView />;
default:
return (
<div className="text-muted-foreground flex h-full w-full items-center justify-center text-sm">
Unknown canvas type
</div>
);
}
};
return (
<div className={`flex flex-col ${className ?? 'h-full w-full'}`}>
{showHeader && canvas.type !== 'report' && <CanvasHeader key=[redacted] canvas={canvas} />}
<div className="min-h-0 flex-1">{renderContent()}</div>
</div>
);
}