import { z } from "zod"; import { WorkerInterfaceSchema } from "../services/service"; import { VIEW_CONTRACT_VERSION, type ViewComponent, type ViewComponentBaseProps, interfaceBaseFields, viewBaseFields, } from "./base"; /* -------------------------------------------------------------------------- */ /* Panel — the dock view type */ /* -------------------------------------------------------------------------- */ /** What an author writes for a `panel` in `unstable_defineApp({ views })`. */ const PanelViewSchema = z.object({ type: z.literal("panel"), ...viewBaseFields, }); /** * The `panel` interface record the application service returns — and what a * panel component receives as its `view`. */ export const PanelInterfaceSchema = z.object({ ...interfaceBaseFields, interface_type: z.literal("panel"), }); export type PanelInterface = z.output; /** * Props a panel component receives: its interface record, minus the * service-assigned `id`/`deployment_id` a local dev server can't provide. * @public */ export type PanelViewProps = ViewComponentBaseProps< Omit >; /** Component slots a panel exposes, in render order — each its own island. */ const PANEL_COMPONENTS = ["title", "panel"] as const; export interface PanelViewComponents { title: ViewComponent; panel: ViewComponent; } /** * A panel's view-component slot — the module-federation expose for one island. * @public */ export type PanelComponent = keyof PanelViewComponents; /* -------------------------------------------------------------------------- */ /* App — the navigable full-page view type */ /* -------------------------------------------------------------------------- */ /** * The `app` interface — an application's navigable full-page view. Not authored * via `unstable_defineView`: the build derives it from `unstable_defineApp`'s * `entry` and reuses the default `./App` federation render path, so there's only * a schema (consumed as the `App` type below). */ const AppInterfaceSchema = z.object({ ...interfaceBaseFields, interface_type: z.literal("app"), }); /* -------------------------------------------------------------------------- */ /* Interface types */ /* -------------------------------------------------------------------------- */ /** * Component slots each interface type exposes, in render order — source of * truth for the build (one render artifact per component) and for * {@link InterfaceType}. Add a type by adding its module and registering it here. * @internal */ export const VIEW_COMPONENTS = { panel: PANEL_COMPONENTS, } as const satisfies Record; /** * Every supported interface type — the first argument to `unstable_defineView`. * @public */ export type InterfaceType = keyof typeof VIEW_COMPONENTS; /** * The components each interface type exposes, keyed by type. * @public */ export interface ViewComponentsByType { panel: PanelViewComponents; } /* -------------------------------------------------------------------------- */ /* Declarations — what an author writes in `unstable_defineApp({ views })` */ /* -------------------------------------------------------------------------- */ /** * The `{ type, name, src }` an app declares, discriminated by `type`. Persisted * to the application service on deploy; never part of the app manifest. * @internal */ export const InterfaceDeclarationSchema = z.discriminatedUnion("type", [ PanelViewSchema, ]); /* -------------------------------------------------------------------------- */ /* Interface records — what the application service stores and returns */ /* -------------------------------------------------------------------------- */ /** * An interface record discriminated on `interface_type` — a `panel` renders in * the dock, a `worker` runs a background service, an `app` is the navigable * full-page view. snake_case matches the service JSON. * @public */ export const InterfaceSchema = z.discriminatedUnion("interface_type", [ PanelInterfaceSchema, WorkerInterfaceSchema, AppInterfaceSchema, ]); /** @public */ export type Interface = z.output; /** * An {@link Interface} without the service-assigned `id`/`deployment_id` a local * dev server can't provide — the shape the workbench renders from. * @public */ export const LocalInterfaceSchema = z.discriminatedUnion("interface_type", [ PanelInterfaceSchema.omit({ id: true, deployment_id: true }), WorkerInterfaceSchema.omit({ id: true, deployment_id: true }), AppInterfaceSchema.omit({ id: true, deployment_id: true }), ]); /** @public */ export type LocalInterface = z.output; /** The interface record for a given `interface_type`. @public */ export type InterfaceOfType = Extract< Interface, { interface_type: T } >; /** The {@link InterfaceOfType} counterpart without `id`/`deployment_id`. @public */ export type LocalInterfaceOfType = Extract; /** @public */ export type Panel = InterfaceOfType<"panel">; /** @public */ export type LocalPanel = LocalInterfaceOfType<"panel">; /** * The navigable full-page view — its presence is the sole signal a branded app * is navigable (spec 002-workbench-extension-api, US5). * @public */ export type App = InterfaceOfType<"app">; /** @public */ export type LocalApp = LocalInterfaceOfType<"app">; /* -------------------------------------------------------------------------- */ /* Authoring — the `unstable_defineView` factory */ /* -------------------------------------------------------------------------- */ /** * The result of `unstable_defineView`: the author's component(s), the view type, * and the internal contract version the build artifact targets. * @public */ export interface DefinedView { readonly type: TType; /** @internal */ readonly version: typeof VIEW_CONTRACT_VERSION; readonly components: ViewComponentsByType[TType]; } /** * Define a Sanity Workbench view. The first argument narrows the component shape * and the props each component receives — `"panel"` yields a `{ title, panel }` * record whose components are typed with the panel props. * * Returns the component(s) tagged with their type and the contract version, for * the CLI build to generate render artifacts from. Used as the default export of * a view's `src` file. * @public */ export function unstable_defineView( type: TType, components: ViewComponentsByType[TType], ): DefinedView { return { type, version: VIEW_CONTRACT_VERSION, components }; }