/** * `PluginLayoutCellView` — renders a `customPluginLayout` top-level * {@link LayoutNode} (ticket #43, F4, carnet v2 §3): the plugin's registered * React component when its provider is loaded and declares the type, * otherwise {@link PluginLayoutFallback}. * * State is opaque to the domain (`unknown`) and round-trips through * `LayoutGateway` exactly like any other node field — `setState` here is the * only way a plugin component is meant to persist it (never direct gateway * calls from inside a plugin component). */ import { Component, type ErrorInfo, type ReactNode } from "react"; import type { CustomPluginLayoutCell, PluginAdmin } from "@/domain"; import { usePluginRuntime } from "./PluginRuntimeProvider"; import { PluginLayoutFallback } from "./PluginLayoutFallback"; import { resolvePluginLayoutAvailability } from "./layoutAvailability"; interface PluginLayoutCellViewProps { projectId: string; cell: CustomPluginLayoutCell; installedPlugins?: PluginAdmin[]; onStateChange: (nextState: unknown) => void; onOpenPlugins: () => void; onChooseAnotherLayout: () => void; } interface PluginLayoutErrorBoundaryProps { resetKey: string; fallback: ReactNode; children: ReactNode; } class PluginLayoutErrorBoundary extends Component< PluginLayoutErrorBoundaryProps, { hasError: boolean } > { state = { hasError: false }; static getDerivedStateFromError(): { hasError: boolean } { return { hasError: true }; } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { console.error("[plugin-layout] render failed", error, errorInfo); } componentDidUpdate(prevProps: PluginLayoutErrorBoundaryProps): void { if (prevProps.resetKey !== this.props.resetKey && this.state.hasError) { this.setState({ hasError: false }); } } render(): ReactNode { if (this.state.hasError) return this.props.fallback; return this.props.children; } } export function PluginLayoutCellView({ projectId, cell, installedPlugins, onStateChange, onOpenPlugins, onChooseAnotherLayout, }: PluginLayoutCellViewProps) { const { registry } = usePluginRuntime(); const availability = resolvePluginLayoutAvailability(registry, cell, installedPlugins); const providerDisplayName = registry.get(cell.pluginId)?.displayName ?? installedPlugins?.find((p) => p.id === cell.pluginId)?.displayName; if (availability !== "available") { return ( ); } const Component = registry.layoutComponent(cell.pluginId, cell.layoutType)!; const fallback = ( ); return ( ); }