// The `views` feature: the operator screen as React rendered INSIDE Studio's // own workflow view — one file per place Studio shows a component: `inline` // inside its node on the canvas, `summary` in the sidebar when the node is // selected, and a `fullscreen` page at /live//fullscreen — rather than as a // standalone SPA (see dashboard.ts for that answer). // // It costs no vite workspace and no dashboards/ directory: the screens are // compiled into the component's info.client.js by copy-yamls. What it does // cost is the browser-bundle constraints — Studio's loader has no import map, // so react is rewritten to a window global and nothing here may reach for a // bare specifier the loader cannot resolve. // // State arrives through Studio's own plumbing (ViewProps.state, updated from // every updates.update() the runtime publishes) and commands go back through // sendCommand, so a view never fetches and never opens a socket. import type { GeneratedFile, ShapeContext } from "../create-product.ts"; const INLINE_TSX = `// PLACEHOLDER — rendered INSIDE the component's node on the canvas. Keep it to a // line or two: the node is small, and without this view the node shows only its // static config. The sidebar is summary.tsx; the full page is fullscreen.tsx. // // \`state\` is pushed by Studio from the component's updates.update() calls; this // file neither fetches nor subscribes. import type { ViewProps } from "@norskvideo/norsk-studio/lib/extension/client-types"; import type { ExampleCommand, ExampleSettings, ExampleState } from "./runtime"; function InlineView({ state }: ViewProps) { return (
{state.ticks} ticks
); } export default InlineView; `; const SUMMARY_TSX = `// PLACEHOLDER — rendered in Studio's SIDEBAR when the component's node is // selected. More room than the node itself (inline.tsx); the fullscreen view is // still where full detail belongs. // // \`state\` is pushed by Studio from the component's updates.update() calls; this // file neither fetches nor subscribes. import type { ViewProps } from "@norskvideo/norsk-studio/lib/extension/client-types"; import type { ExampleCommand, ExampleSettings, ExampleState } from "./runtime"; function SummaryView({ state, config }: ViewProps) { return (
Heartbeats
{state.ticks}
Interval
{config.heartbeatMs ?? 1000} ms
); } export default SummaryView; `; const FULLSCREEN_TSX = `// PLACEHOLDER — the operator screen, reached at /live//fullscreen (and via // the template manifest's runtimeScreenUrl, which points at Studio). This is // where a real product's desk goes: oracle's highlights desk is this file. // // Two things worth keeping when you replace it: // - state comes from ViewProps and nothing else, so the screen cannot drift // from what the runtime actually published; // - commands go back through sendCommand, which Studio routes to the // component's handleCommand — never through an HTTP call of your own. import type { ViewProps } from "@norskvideo/norsk-studio/lib/extension/client-types"; import type { ExampleCommand, ExampleSettings, ExampleState } from "./runtime"; function FullscreenView({ state, config }: ViewProps) { return (

{config.displayName}

Placeholder operator screen — replace it with this product's desk, or delete it with the component.

Heartbeats
{state.ticks}
Interval
{config.heartbeatMs ?? 1000} ms
); } export default FullscreenView; `; export function viewsFiles(_ctx: ShapeContext): GeneratedFile[] { return [ { path: "components/src/library/example/inline.tsx", content: INLINE_TSX }, { path: "components/src/library/example/summary.tsx", content: SUMMARY_TSX }, { path: "components/src/library/example/fullscreen.tsx", content: FULLSCREEN_TSX }, ]; } /** Imports the component's info.ts needs when it registers the three views. Kept * separate because biome sorts them around the ./runtime import between them. */ export const VIEWS_INFO_IMPORTS = { fullscreen: 'import FullscreenView from "./fullscreen";', inline: 'import InlineView from "./inline";', summary: 'import SummaryView from "./summary";', }; /** The `runtime:` entries registering them. */ export const VIEWS_INFO_RUNTIME = ` inline: InlineView, summary: SummaryView, fullscreen: FullscreenView, `; // Studio owns the whole per-instance surface here, so the runtime screen IS // Studio: ctl anchors studio_url_prefix on its own /studio/ base route and the // component's page resolves under it as /instance//studio/live//fullscreen. export const VIEWS_RUNTIME_SCREEN = { url: "/studio/", label: "Open Studio" }; // /live/api/* covers the component's ROUTES; the per-component PAGES and their // state websockets are /live//fullscreen and /live//ws, so the wider // entry is required or the screen 404s through the proxy while working direct. export const VIEWS_PROXY_EXPOSE = "/live/*";