/** * Fork-authoring helpers: turn a chosen set of catalog blocks into the concrete * files that go into a tenant's forked app repo. The AI app builder calls these * to produce a reviewable change (applied as a PR), so the base template stays * thin — only what's chosen lands in a fork. * * Today: `renderDashboardConfig` writes the home layout (foundry.dashboard.ts) * from built-in widgets. Per-component scaffolding of heavier blocks (calendar, * gantt, …) layers on next, emitting additional ScaffoldFiles. */ import { UI_CATALOG } from './catalog'; import type { ScaffoldFile } from './types'; /** One home widget: a catalog widget id + its per-widget params (type, title…). */ export interface DashboardWidgetConfig { kind: string; [param: string]: unknown; } const WIDGET_IDS = new Set(UI_CATALOG.filter((e) => e.category === 'widget').map((e) => e.id)); /** Which of the chosen widgets are NOT built into the base template (need scaffolding). */ export function widgetsNeedingScaffold(widgets: DashboardWidgetConfig[]): string[] { const builtIn = new Set(UI_CATALOG.filter((e) => e.builtIn).map((e) => e.id)); return [...new Set(widgets.map((w) => w.kind))].filter((k) => WIDGET_IDS.has(k) && !builtIn.has(k)); } export interface RenderResult { ok: boolean; /** Unknown/invalid widget kinds — the AI should fix these before applying. */ errors: string[]; file?: ScaffoldFile; } /** * Render `src/foundry.dashboard.ts` for the given home layout. Validates each * widget `kind` against the catalog's widget blocks; an unknown kind fails the * whole render (so we never write a config referencing a widget the app can't * render). Serializes params as a plain literal — valid TS the owner can hand-edit. */ export function renderDashboardConfig(widgets: DashboardWidgetConfig[]): RenderResult { const errors: string[] = []; if (widgets.length === 0) errors.push('layout is empty — choose at least one widget'); for (const w of widgets) { if (!w.kind) errors.push('a widget is missing its "kind"'); else if (!WIDGET_IDS.has(w.kind)) errors.push(`unknown widget kind "${w.kind}"`); } if (errors.length > 0) return { ok: false, errors }; const body = widgets.map((w) => ` ${JSON.stringify(w)},`).join('\n'); const content = `/** * Your app's home, as data. Generated by the Foundry app builder — safe to * hand-edit. Each entry is a widget (see components/dashboard for the registry); * add/remove/reorder them, or set this to 'auto' to derive a layout from your * schema. Widget shapes live in components/dashboard/config. */ import type { DashboardConfig } from '@/components/dashboard/config'; export const dashboard: DashboardConfig = [ ${body} ]; `; return { ok: true, errors: [], file: { path: 'src/foundry.dashboard.ts', content } }; }