import { z } from "zod/v4"; /** * Surface declarations (plan §7): where an app renders and with which tier. * * - `tree` — tier 1: app logic returns a declarative UI tree; the host * renders it with its own components on every platform. * - `react` — tier 2: a Coline-built React bundle runs in the sandboxed * client runtime. `entry` is the module path within the app * source. * - `web` — tier 3: an iframe served from the app's external origin. * Requires external hosting; excluded from mobile and preview * composition. */ const surfaceEntrySchema = z.string().trim().min(1).max(500); const surfacePathSchema = z .string() .trim() .min(1) .max(2000) .startsWith("/", "Web surface paths must be origin-relative (start with /)."); export const treeSurfaceSchema = z.object({ tier: z.literal("tree"), }); export const reactSurfaceSchema = z.object({ tier: z.literal("react"), entry: surfaceEntrySchema, }); export const webSurfaceSchema = z.object({ tier: z.literal("web"), path: surfacePathSchema, }); export const surfaceVariantSchema = z.discriminatedUnion("tier", [ treeSurfaceSchema, reactSurfaceSchema, webSurfaceSchema, ]); export type SurfaceVariant = z.infer; export type SurfaceTier = SurfaceVariant["tier"]; /** * A surface with an optional mobile override (plan §13): universal by * default; specialize only when the interaction genuinely differs. */ export const appSurfaceSchema = z.discriminatedUnion("tier", [ treeSurfaceSchema.extend({ mobile: surfaceVariantSchema.optional() }), reactSurfaceSchema.extend({ mobile: surfaceVariantSchema.optional() }), webSurfaceSchema.extend({ mobile: surfaceVariantSchema.optional() }), ]); export type AppSurface = z.infer; /** * The public surface (plan §7.6): a page people outside the workspace can * open — forms, client portals, status pages — served at * `/a/{workspace}/{app}` once a workspace admin turns it on. * * Visitors are guests, not members. Public renders run read-only with the * app principal; visitor interaction reaches the app ONLY through the * tools listed in `tools`, which run with the app principal under their * declared effect and per-visitor rate limits. A react public surface's * sandbox can call those tools and nothing else. */ const publicSurfaceExtension = { title: z.string().trim().min(1).max(120).optional(), description: z.string().trim().max(500).optional(), tools: z.array(z.string().trim().min(1).max(160)).max(20).default([]), }; export const publicSurfaceSchema = z.discriminatedUnion("tier", [ treeSurfaceSchema.extend(publicSurfaceExtension), reactSurfaceSchema.extend(publicSurfaceExtension), ]); export type PublicSurface = z.infer; export const appSurfacesSchema = z .object({ home: appSurfaceSchema.optional(), public: publicSurfaceSchema.optional(), }) .default({}); export type AppSurfaces = z.infer;