@westopp/windo 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ npm i -D @westopp/windo zod
16
16
 
17
17
  ### 1. Add a `windo.config.ts`
18
18
 
19
- `defineWindoConfig` is the single entry point. It captures your **groups** (the sidebar sections) and **contexts** (ambient controls + providers), and hands back a `windo` factory whose `group` field is type-checked against your configured slugs.
19
+ `defineWindoConfig` is the single entry point. It captures your **groups** (the sidebar sections), optional **tags** (cross-cutting labels the sidebar filters by), and **contexts** (ambient controls + providers), and hands back a `windo` factory whose `group` and `tags` fields are type-checked against your config.
20
20
 
21
21
  ```ts
22
22
  import { defineWindoConfig, defineContext } from '@westopp/windo'
@@ -28,6 +28,7 @@ export const { config, windo } = defineWindoConfig({
28
28
  { name: 'Primitives', slug: 'primitives' },
29
29
  { name: 'Forms', slug: 'forms', description: 'Inputs and controls' },
30
30
  ],
31
+ tags: ['web-app', 'admin-panel', 'marketing'],
31
32
  contexts: {
32
33
  theme: defineContext({
33
34
  label: 'Theme',
@@ -64,6 +65,7 @@ const props = configurableProps<ButtonProps>()(
64
65
  export default windo<ButtonProps>(w => ({
65
66
  title: 'Button',
66
67
  group: w.groups.primitives.slug,
68
+ tags: ['web-app'],
67
69
  placement: 'center',
68
70
  configurableProps: props,
69
71
  defaultProps: { label: 'Click me', variant: 'primary' },
@@ -108,6 +110,7 @@ The plugin pairs `@vitejs/plugin-react` with windo, serves the chrome + iframe H
108
110
  |-------|------|-------|
109
111
  | `title` | `string` | Display name in the sidebar. **Required.** |
110
112
  | `group` | group slug | Type-checked against configured slugs. **Required.** |
113
+ | `tags` | `Tag[]` | Cross-cutting labels, type-checked against config `tags`. A component carries any number; drives the sidebar filter. |
111
114
  | `component` | `(props, ctx) => ReactNode` | Renders the component. **Required.** |
112
115
  | `defaultProps` | `Props` or `ctx => Props` | Full props incl. functions/JSX; JSON edits merge on top. **Required.** |
113
116
  | `configurableProps` | zod schema | Validator + parser for the JSON-editable prop subset. |
@@ -122,7 +125,7 @@ The plugin pairs `@vitejs/plugin-react` with windo, serves the chrome + iframe H
122
125
 
123
126
  Config-level helpers:
124
127
 
125
- - `defineWindoConfig({ groups, contexts?, include?, title? })` → `{ config, windo }`
128
+ - `defineWindoConfig({ groups, contexts?, tags?, include?, title? })` → `{ config, windo }`
126
129
  - `defineContext({ label?, description?, controls?, provider?, resolve? })`
127
130
  - `configurableProps<Props>()(schema)` — identity helper that constrains the schema to `Partial<Props>`
128
131
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/define-config.ts","../src/descriptor.ts","../src/protocol.ts","../src/types.ts"],"sourcesContent":["// Public authoring API. This is the isomorphic entry point users import from\n// their `*.windo.tsx` and `windo.config.ts` files. No DOM or Node code lives\n// here — the chrome (`/client`), preview (`/preview`), plugin (`/plugin`), and\n// CLI (`bin`) are separate entry points.\n\nexport type { DefineWindoConfigResult } from './define-config'\nexport { configurableProps, defineContext, defineWindoConfig } from './define-config'\nexport { describeSchema } from './descriptor'\nexport type { WindoHostMessage, WindoMessage, WindoPreviewMessage } from './protocol'\nexport {\n isHostMessage,\n isPreviewMessage,\n isWindoMessage,\n WINDO_MSG,\n} from './protocol'\nexport type {\n WindoAction,\n WindoActionMeta,\n WindoActionTrigger,\n WindoConfig,\n WindoContextControlMeta,\n WindoContextDefinition,\n WindoContextMap,\n WindoContextMeta,\n WindoControlDescriptor,\n WindoControlKind,\n WindoControlMap,\n WindoControlSpec,\n WindoControlType,\n WindoControlValues,\n WindoDefaultProps,\n WindoDefinition,\n WindoEnvState,\n WindoFactoryArg,\n WindoFieldError,\n WindoGroup,\n WindoLogEntry,\n WindoLogger,\n WindoManifestEntry,\n WindoModule,\n WindoPlacement,\n WindoPropDoc,\n WindoRenderContext,\n WindoSchemaDescriptor,\n WindoStatus,\n WindoVariant,\n WindoVariantMeta,\n WindoViewport,\n} from './types'\nexport {\n WINDO_ACTION_TRIGGERS,\n WINDO_PLACEMENTS,\n WINDO_STATUSES,\n} from './types'\n","// The authoring API. `defineWindoConfig` is the single entry point: it captures\n// the project's groups + contexts and hands back a `windo` factory whose `group`\n// field is type-checked against the configured slugs.\n\nimport type { ComponentType, ReactNode } from 'react'\nimport type { z } from 'zod'\nimport type { WindoConfig, WindoContextDefinition, WindoContextMap, WindoControlMap, WindoControlValues, WindoDefinition, WindoFactoryArg, WindoGroup, WindoModule, WindoRenderContext } from './types'\n\nexport interface DefineWindoConfigResult<Groups extends readonly WindoGroup[], Contexts extends WindoContextMap> {\n config: WindoConfig<Groups, Contexts>\n windo: <Props, State = Record<string, never>>(factory: (w: WindoFactoryArg<Groups, Contexts>) => WindoDefinition<Props, State, Groups[number]['slug']>) => WindoModule<Props, State>\n}\n\nexport function defineWindoConfig<const Groups extends readonly WindoGroup[], Contexts extends WindoContextMap = Record<string, never>>(\n config: WindoConfig<Groups, Contexts>\n): DefineWindoConfigResult<Groups, Contexts> {\n function windo<Props, State = Record<string, never>>(factory: (w: WindoFactoryArg<Groups, Contexts>) => WindoDefinition<Props, State, Groups[number]['slug']>): WindoModule<Props, State> {\n return {\n __windo: true,\n resolve: w => factory(w as unknown as WindoFactoryArg<Groups, Contexts>),\n }\n }\n return { config, windo }\n}\n\n/**\n * Define a named context. A context can contribute ambient `controls` (values +\n * UI), a `provider` (mounted inside the iframe for components that opt in via\n * `uses`), or both.\n *\n * No cast: `def` is already structurally a `WindoContextDefinition<WindoControlValues<C>,\n * Provided>` (only `controls` widens, covariantly). The unavoidable variance — TS has\n * no existential type to hold contexts heterogeneous in `C` — is absorbed once, in\n * `WindoContextMap`, not here and not at call sites.\n */\nexport function defineContext<C extends WindoControlMap, Provided = WindoControlValues<C>>(def: {\n label?: string\n description?: string\n controls?: C\n provider?: ComponentType<{ children: ReactNode; values: WindoControlValues<C>; ctx: WindoRenderContext }>\n resolve?: (values: WindoControlValues<C>, ctx: WindoRenderContext) => Provided\n}): WindoContextDefinition<WindoControlValues<C>, Provided> {\n return def\n}\n\n/**\n * Bind a zod schema to a component's props. The generic carries the component's\n * prop type so the schema's output stays a subset of it; the returned schema is\n * the runtime validator + parser (`z.input` is the JSON edit surface, `z.output`\n * is what the component receives).\n */\nexport function configurableProps<P>() {\n return <S extends z.ZodType<Partial<P>>>(schema: S): S => schema\n}\n","// Walk a zod schema into a serialisable descriptor that can cross the iframe\n// boundary and render the Controls/Schema UI. We lean on `z.toJSONSchema` (zod\n// v4) for the input shape (enum options, min/max, optionality), then enrich the\n// kind from the zod node's own type tag — some types (Date, Map, Set) are\n// unrepresentable in JSON Schema and come back as `{}`, but their zod `def.type`\n// is exact. The live schema (with transforms/coerce/refine) stays in the iframe\n// and does the actual parsing — only this descriptor travels.\n\nimport { z } from 'zod'\nimport type { WindoControlDescriptor, WindoControlKind, WindoSchemaDescriptor } from './types'\n\ninterface JsonNode {\n type?: string | string[]\n enum?: unknown[]\n const?: unknown\n format?: string\n properties?: Record<string, JsonNode>\n required?: string[]\n items?: JsonNode\n anyOf?: JsonNode[]\n oneOf?: JsonNode[]\n allOf?: JsonNode[]\n minimum?: number\n maximum?: number\n description?: string\n}\n\nexport function describeSchema(schema: z.ZodType | undefined | null): WindoSchemaDescriptor {\n if (!schema) return { fields: [] }\n let json: JsonNode\n try {\n json = z.toJSONSchema(schema, { io: 'input', unrepresentable: 'any' }) as JsonNode\n } catch {\n return { fields: [] }\n }\n const root = unwrap(json)\n const properties = root.properties ?? {}\n const required = new Set(root.required ?? [])\n const zodKinds = shapeKinds(schema)\n const fields: WindoControlDescriptor[] = Object.keys(properties).map(key => fieldFrom(key, properties[key], required.has(key), zodKinds[key]))\n return { fields }\n}\n\n// JSON Schema often wraps the object in anyOf (for optional/nullable). Find the\n// node that actually carries the object's properties.\nfunction unwrap(node: JsonNode): JsonNode {\n if (node.properties) return node\n const branches = node.anyOf ?? node.oneOf ?? node.allOf\n if (branches) {\n const withProps = branches.find(b => b.properties)\n if (withProps) return withProps\n }\n return node\n}\n\nfunction fieldFrom(key: string, node: JsonNode, isRequired: boolean, zodKind?: WindoControlKind): WindoControlDescriptor {\n const inner = collapseNullable(node)\n let kind = kindOf(inner)\n // The zod tag wins for types JSON Schema can't express (date/array/object that\n // came back as {}), and as a fallback whenever the JSON kind is unknown.\n if (zodKind && (kind === 'unknown' || zodKind === 'date')) kind = zodKind\n const descriptor: WindoControlDescriptor = {\n key,\n kind,\n optional: !isRequired || isNullable(node),\n }\n if (inner.description) descriptor.description = inner.description\n const options = enumOptions(inner)\n if (options) descriptor.options = options\n if (typeof inner.minimum === 'number') descriptor.min = inner.minimum\n if (typeof inner.maximum === 'number') descriptor.max = inner.maximum\n return descriptor\n}\n\nfunction isNullable(node: JsonNode): boolean {\n const branches = node.anyOf ?? node.oneOf\n if (!branches) return false\n return branches.some(b => b.type === 'null')\n}\n\n// Strip a `{ anyOf: [T, null] }` wrapper down to T.\nfunction collapseNullable(node: JsonNode): JsonNode {\n const branches = node.anyOf ?? node.oneOf\n if (!branches) return node\n const nonNull = branches.filter(b => b.type !== 'null')\n if (nonNull.length === 1) return nonNull[0]\n return node\n}\n\nfunction enumOptions(node: JsonNode): string[] | undefined {\n if (Array.isArray(node.enum)) return node.enum.map(v => String(v))\n const branches = node.anyOf ?? node.oneOf\n if (branches?.every(b => b.const !== undefined)) {\n return branches.map(b => String(b.const))\n }\n return undefined\n}\n\nfunction kindOf(node: JsonNode): WindoControlKind {\n if (Array.isArray(node.enum) || (node.anyOf?.every(b => b.const !== undefined) ?? false)) return 'enum'\n if (node.format === 'date-time' || node.format === 'date') return 'date'\n const type = Array.isArray(node.type) ? node.type.find(t => t !== 'null') : node.type\n switch (type) {\n case 'string':\n return 'string'\n case 'number':\n case 'integer':\n return 'number'\n case 'boolean':\n return 'boolean'\n case 'array':\n return 'array'\n case 'object':\n return 'object'\n default:\n return 'unknown'\n }\n}\n\n/* ------------------------------------------------------------------ *\n * zod node introspection (the kind source of truth for unrepresentable types)\n * ------------------------------------------------------------------ */\n\ninterface ZodDefLike {\n type?: string\n innerType?: unknown\n}\n\nfunction zodDef(node: unknown): ZodDefLike | undefined {\n const n = node as { def?: ZodDefLike; _zod?: { def?: ZodDefLike } } | null\n if (!n || typeof n !== 'object') return undefined\n return n.def ?? n._zod?.def\n}\n\nconst ZOD_WRAPPERS = new Set(['optional', 'nullable', 'default', 'prefault', 'catch', 'readonly', 'nonoptional', 'lazy'])\n\n// Unwrap optional/nullable/default/... down to the meaningful inner node.\nfunction unwrapZodDef(node: unknown): ZodDefLike | undefined {\n let def = zodDef(node)\n let guard = 0\n while (def && def.innerType && ZOD_WRAPPERS.has(def.type ?? '') && guard++ < 16) {\n def = zodDef(def.innerType)\n }\n return def\n}\n\nfunction mapZodType(type: string | undefined): WindoControlKind | undefined {\n switch (type) {\n case 'date':\n return 'date'\n case 'string':\n return 'string'\n case 'number':\n case 'int':\n case 'bigint':\n return 'number'\n case 'boolean':\n return 'boolean'\n case 'array':\n case 'set':\n case 'tuple':\n return 'array'\n case 'object':\n case 'record':\n case 'map':\n return 'object'\n case 'enum':\n case 'literal':\n return 'enum'\n default:\n return undefined\n }\n}\n\n// Per-field kind read straight from the zod object's shape.\nfunction shapeKinds(schema: z.ZodType): Record<string, WindoControlKind> {\n const shape = (schema as { shape?: Record<string, unknown> }).shape\n if (!shape || typeof shape !== 'object') return {}\n const out: Record<string, WindoControlKind> = {}\n for (const key of Object.keys(shape)) {\n const kind = mapZodType(unwrapZodDef(shape[key])?.type)\n if (kind) out[key] = kind\n }\n return out\n}\n","// The chrome <-> iframe postMessage contract. The schema itself never crosses\n// the boundary: the iframe walks it into a serialisable descriptor, the chrome\n// sends candidate JSON down, the iframe parses and reports back. Every payload\n// here is plain JSON.\n\nimport type { WindoContextMeta, WindoEnvState, WindoFieldError, WindoGroup, WindoLogEntry, WindoManifestEntry, WindoPropDoc, WindoSchemaDescriptor, WindoVariantMeta } from './types'\n\nexport const WINDO_MSG = 'windo'\n\n/** chrome -> iframe */\nexport type WindoHostMessage =\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'request-manifest' }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'select'; id: string }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'set-props'; id: string; json: string }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'set-env'; env: WindoEnvState }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'invoke-action'; id: string; actionId: string }\n\n/** iframe -> chrome */\nexport type WindoPreviewMessage =\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'ready' }\n | {\n source: typeof WINDO_MSG\n dir: 'preview'\n type: 'manifest'\n title: string\n entries: WindoManifestEntry[]\n groups: WindoGroup[]\n contexts: WindoContextMeta[]\n }\n | {\n source: typeof WINDO_MSG\n dir: 'preview'\n type: 'describe'\n id: string\n descriptor: WindoSchemaDescriptor\n props: WindoPropDoc[]\n variants: WindoVariantMeta[]\n defaults: unknown\n code: string | null\n }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'parse-ok'; id: string }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'parse-error'; id: string; errors: WindoFieldError[] }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'log'; entry: WindoLogEntry }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'state'; id: string; state: Record<string, unknown>; actions: { id: string; disabled: boolean }[] }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'render-error'; id: string; message: string; stack?: string }\n\nexport type WindoMessage = WindoHostMessage | WindoPreviewMessage\n\nexport function isWindoMessage(data: unknown): data is WindoMessage {\n return typeof data === 'object' && data !== null && (data as { source?: unknown }).source === WINDO_MSG\n}\n\nexport function isHostMessage(msg: WindoMessage): msg is WindoHostMessage {\n return msg.dir === 'host'\n}\n\nexport function isPreviewMessage(msg: WindoMessage): msg is WindoPreviewMessage {\n return msg.dir === 'preview'\n}\n","// Core type system for windo. Everything — the authoring API, the iframe\n// preview runtime, the chrome UI, and the postMessage protocol — is typed\n// against the contracts in this file.\n\nimport type { ComponentType, ReactNode } from 'react'\nimport type { z } from 'zod'\n\n/* ------------------------------------------------------------------ *\n * Primitives\n * ------------------------------------------------------------------ */\n\nexport type WindoStatus = 'stable' | 'beta' | 'deprecated'\n\nexport const WINDO_STATUSES: readonly WindoStatus[] = ['stable', 'beta', 'deprecated']\n\n/** Anchor a component within the canvas frame. */\nexport type WindoPlacementBase = 'center' | 'fill' | 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n\n/**\n * Where a component renders inside the canvas frame. Placements render flush by\n * default; append `-padding` to inset the component from the frame edges\n * (e.g. `top` sits flush at the top, `top-padding` adds breathing room).\n */\nexport type WindoPlacement = WindoPlacementBase | `${WindoPlacementBase}-padding`\n\nconst WINDO_PLACEMENT_BASE: readonly WindoPlacementBase[] = ['center', 'fill', 'top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right']\n\nexport const WINDO_PLACEMENTS: readonly WindoPlacement[] = [...WINDO_PLACEMENT_BASE, ...WINDO_PLACEMENT_BASE.map(p => `${p}-padding` as WindoPlacement)]\n\n/** A configured group. Components reference a group by its `slug`. */\nexport interface WindoGroup {\n name: string\n slug: string\n description?: string\n}\n\n/* ------------------------------------------------------------------ *\n * Context system\n * ------------------------------------------------------------------ */\n\nexport type WindoControlType = 'enum' | 'boolean' | 'string' | 'number'\n\n/** A single ambient control: a value plus the metadata to render its toggle. */\nexport interface WindoControlSpec<T = unknown> {\n type: WindoControlType\n label?: string\n default: T\n options?: readonly string[]\n min?: number\n max?: number\n step?: number\n}\n\nexport type WindoControlMap = Record<string, WindoControlSpec>\n\n/** Resolve a control map to the value object it produces. */\nexport type WindoControlValues<C extends WindoControlMap> = {\n [K in keyof C]: C[K] extends WindoControlSpec<infer T> ? T : never\n}\n\n/**\n * A named context. Two capabilities, either or both:\n * - `controls` → ambient values + UI toggles (free, no opt-in needed)\n * - `provider` → a React wrapper, mounted inside the iframe around components\n * that opt in via `uses`\n */\nexport interface WindoContextDefinition<Values = unknown, Provided = Values> {\n label?: string\n description?: string\n controls?: WindoControlMap\n provider?: ComponentType<{ children: ReactNode; values: Values; ctx: WindoRenderContext }>\n /** Derive the value exposed on `ctx.contexts[name]`. Defaults to the control values. */\n resolve?: (values: Values, ctx: WindoRenderContext) => Provided\n}\n\n// Contexts are each generic over a different control map `C`; TS has no existential\n// to hold `exists C. WindoContextDefinition<WindoControlValues<C>>`. `Values` sits in\n// contravariant positions (provider props, resolve arg) so no precise common supertype\n// exists — `any` is the single, contained variance hatch for this heterogeneous\n// registry. Mirrors WindoModule. Definition sites stay precise; this only erases at the\n// point of collection.\n// biome-ignore lint/suspicious/noExplicitAny: existential variance hatch for the heterogeneous context registry\nexport type WindoContextMap = Record<string, WindoContextDefinition<any, any>>\n\n/* ------------------------------------------------------------------ *\n * Render-time context\n * ------------------------------------------------------------------ */\n\nexport interface WindoViewport {\n width: number\n height: number\n name: 'mobile' | 'tablet' | 'desktop'\n}\n\n/** Console channel: `ctx.logger.log(…)` posts an entry to the chrome's Console tab. */\nexport interface WindoLogger {\n log: (...args: unknown[]) => void\n}\n\n/** How an action fires. `click` renders a toolbar button; the rest bind to the stage's pointer events. */\nexport type WindoActionTrigger = 'click' | 'enter' | 'exit' | 'hover'\n\nexport const WINDO_ACTION_TRIGGERS: readonly WindoActionTrigger[] = ['click', 'enter', 'exit', 'hover']\n\n/**\n * An out-of-band action that drives a component's state. `click` actions render as\n * toolbar buttons; `enter`/`exit`/`hover` bind to the stage's pointer events. `run`\n * receives the live ctx and an `active` flag — for `hover` it is `true` on\n * pointer-enter and `false` on pointer-leave; for the others it is always `true`.\n */\nexport interface WindoAction<State = unknown> {\n label: string\n /** Defaults to `click`. */\n on?: WindoActionTrigger\n run: (ctx: WindoRenderContext<State>, active: boolean) => void\n /** Greys out a `click` action's toolbar button. Evaluated against the live state. */\n disabled?: (ctx: WindoRenderContext<State>) => boolean\n}\n\n/** Live environment handed to every render-time function inside the iframe. */\nexport interface WindoRenderContext<State = unknown> {\n colorScheme: 'light' | 'dark'\n viewport: WindoViewport\n reducedMotion: boolean\n direction: 'ltr' | 'rtl'\n locale: string\n logger: WindoLogger\n /** Current component-local state, typed by the windo's `State` generic. */\n state: State\n /** Merge a patch into the component-local state and re-render. */\n setState: (patch: Partial<State>) => void\n /** Resolved values of opted-in contexts, keyed by context name. */\n contexts: Record<string, unknown>\n}\n\n/* ------------------------------------------------------------------ *\n * Authoring API\n * ------------------------------------------------------------------ */\n\n/** A variant: a label plus a partial prop patch. Renders in the gallery and is click-to-apply. */\nexport interface WindoVariant<Props> {\n label: string\n props: Partial<Props>\n}\n\n/** A row in the authored Props documentation table. */\nexport interface WindoPropDoc {\n name: string\n type: string\n default?: string\n desc?: string\n}\n\nexport type WindoDefaultProps<Props, State = unknown> = Props | ((ctx: WindoRenderContext<State>) => Props)\n\n/**\n * The object returned by a `windo(...)` factory.\n *\n * Keystone rule: the surrounding factory runs ONCE (definition-time) for static\n * fields (title, group, schema). Every function field below — `defaultProps`,\n * `actions`, `providers`, `component` — runs at render-time with the live `ctx`.\n * Never close over live values in the static factory body.\n */\nexport interface WindoDefinition<Props = unknown, State = unknown, GroupSlug extends string = string> {\n title: string\n group: GroupSlug\n status?: WindoStatus\n description?: string\n deprecation?: string\n placement?: WindoPlacement\n /** Initial component-local state. Its shape is the `State` generic; `ctx.state`/`ctx.setState` derive from it. */\n state?: State\n /** Out-of-band actions that drive state: toolbar buttons (`click`) and stage pointer triggers (`enter`/`exit`/`hover`). */\n actions?: WindoAction<State>[]\n /** zod schema: validator + parser for the JSON-editable prop subset. `z.output ⊆ Props`. */\n configurableProps?: z.ZodType\n /** Full props incl. functions/JSX. The editor's JSON overrides merge on top. */\n defaultProps: WindoDefaultProps<Props, State>\n /** Names of provider contexts this component opts into. */\n uses?: string[]\n variants?: WindoVariant<Props>[]\n /** Authored documentation table (not derived from the schema). */\n props?: WindoPropDoc[]\n /** Optional authored code snippet for the Code tab. */\n code?: (values: Props) => string\n /** A local provider wrapping just this windo (in addition to `uses`). */\n providers?: ComponentType<{ children: ReactNode; ctx: WindoRenderContext<State> }>\n component: (props: Props, ctx: WindoRenderContext<State>) => ReactNode\n}\n\n/** Argument handed to the `windo(w => ...)` factory. */\nexport interface WindoFactoryArg<Groups extends readonly WindoGroup[], Contexts extends WindoContextMap> {\n /** Configured groups keyed by slug. */\n groups: Record<Groups[number]['slug'], WindoGroup>\n contexts: Contexts\n}\n\n/**\n * The default export of a `*.windo.tsx` file. A branded, lazily-resolved\n * definition — the runtime calls `resolve(w)` with the config-derived factory arg.\n */\n// biome-ignore lint/suspicious/noExplicitAny: variance escape hatch for the heterogeneous WindoModule registry\nexport interface WindoModule<Props = any, State = any> {\n readonly __windo: true\n resolve: (w: WindoFactoryArg<readonly WindoGroup[], WindoContextMap>) => WindoDefinition<Props, State>\n}\n\n/* ------------------------------------------------------------------ *\n * Config\n * ------------------------------------------------------------------ */\n\nexport interface WindoConfig<Groups extends readonly WindoGroup[] = readonly WindoGroup[], Contexts extends WindoContextMap = WindoContextMap> {\n /** Configured groups. A component's `group` must be one of these slugs. */\n groups: Groups\n /** Named contexts available to components. */\n contexts?: Contexts\n /** Glob(s) for discovery, relative to project root. Default `**\\/*.windo.tsx`. */\n include?: string | string[]\n /** Title shown in the workbench chrome. */\n title?: string\n}\n\n/* ------------------------------------------------------------------ *\n * Schema descriptor (crosses the iframe boundary; renders the controls)\n * ------------------------------------------------------------------ */\n\nexport type WindoControlKind = 'string' | 'number' | 'boolean' | 'enum' | 'date' | 'array' | 'object' | 'unknown'\n\nexport interface WindoControlDescriptor {\n key: string\n kind: WindoControlKind\n optional: boolean\n options?: string[]\n min?: number\n max?: number\n description?: string\n}\n\nexport interface WindoSchemaDescriptor {\n fields: WindoControlDescriptor[]\n}\n\n/* ------------------------------------------------------------------ *\n * Runtime manifest + protocol payloads\n * ------------------------------------------------------------------ */\n\n/** Serialisable metadata for one action — drives the canvas toolbar. */\nexport interface WindoActionMeta {\n id: string\n label: string\n on: WindoActionTrigger\n}\n\n/** Static, serialisable metadata for one windo — drives the sidebar. */\nexport interface WindoManifestEntry {\n id: string\n title: string\n group: string\n status: WindoStatus\n description?: string\n deprecation?: string\n placement: WindoPlacement\n uses: string[]\n hasVariants: boolean\n actions: WindoActionMeta[]\n hasState: boolean\n}\n\nexport interface WindoVariantMeta {\n label: string\n props: Record<string, unknown>\n}\n\n/** Ambient environment pushed from the chrome down into the iframe. */\nexport interface WindoEnvState {\n colorScheme: 'light' | 'dark'\n viewport: WindoViewport\n reducedMotion: boolean\n direction: 'ltr' | 'rtl'\n locale: string\n /** Per-context control values, keyed by context name then control key. */\n contexts: Record<string, Record<string, unknown>>\n}\n\nexport interface WindoLogEntry {\n ts: number\n args: unknown[]\n}\n\nexport interface WindoFieldError {\n path: string\n message: string\n}\n\n/* ------------------------------------------------------------------ *\n * Context metadata (serialisable; drives the chrome's Context panel)\n * ------------------------------------------------------------------ */\n\nexport interface WindoContextControlMeta {\n key: string\n type: WindoControlType\n label?: string\n options?: string[]\n default: unknown\n min?: number\n max?: number\n step?: number\n}\n\nexport interface WindoContextMeta {\n name: string\n label?: string\n description?: string\n /** True when the context contributes controls (ambient values). */\n ambient: boolean\n /** True when the context mounts a provider (opt-in via `uses`). */\n hasProvider: boolean\n controls: WindoContextControlMeta[]\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/define-config.ts","../src/descriptor.ts","../src/protocol.ts","../src/types.ts"],"sourcesContent":["// Public authoring API. This is the isomorphic entry point users import from\n// their `*.windo.tsx` and `windo.config.ts` files. No DOM or Node code lives\n// here — the chrome (`/client`), preview (`/preview`), plugin (`/plugin`), and\n// CLI (`bin`) are separate entry points.\n\nexport type { DefineWindoConfigResult } from './define-config'\nexport { configurableProps, defineContext, defineWindoConfig } from './define-config'\nexport { describeSchema } from './descriptor'\nexport type { WindoHostMessage, WindoMessage, WindoPreviewMessage } from './protocol'\nexport {\n isHostMessage,\n isPreviewMessage,\n isWindoMessage,\n WINDO_MSG,\n} from './protocol'\nexport type {\n WindoAction,\n WindoActionMeta,\n WindoActionTrigger,\n WindoConfig,\n WindoContextControlMeta,\n WindoContextDefinition,\n WindoContextMap,\n WindoContextMeta,\n WindoControlDescriptor,\n WindoControlKind,\n WindoControlMap,\n WindoControlSpec,\n WindoControlType,\n WindoControlValues,\n WindoDefaultProps,\n WindoDefinition,\n WindoEnvState,\n WindoFactoryArg,\n WindoFieldError,\n WindoGroup,\n WindoLogEntry,\n WindoLogger,\n WindoManifestEntry,\n WindoModule,\n WindoPlacement,\n WindoPropDoc,\n WindoRenderContext,\n WindoSchemaDescriptor,\n WindoStatus,\n WindoVariant,\n WindoVariantMeta,\n WindoViewport,\n} from './types'\nexport {\n WINDO_ACTION_TRIGGERS,\n WINDO_PLACEMENTS,\n WINDO_STATUSES,\n} from './types'\n","// The authoring API. `defineWindoConfig` is the single entry point: it captures\n// the project's groups + contexts and hands back a `windo` factory whose `group`\n// field is type-checked against the configured slugs.\n\nimport type { ComponentType, ReactNode } from 'react'\nimport type { z } from 'zod'\nimport type { WindoConfig, WindoContextDefinition, WindoContextMap, WindoControlMap, WindoControlValues, WindoDefinition, WindoFactoryArg, WindoGroup, WindoModule, WindoRenderContext } from './types'\n\nexport interface DefineWindoConfigResult<Groups extends readonly WindoGroup[], Tags extends readonly string[], Contexts extends WindoContextMap> {\n config: WindoConfig<Groups, Contexts, Tags>\n windo: <Props, State = Record<string, never>>(\n factory: (w: WindoFactoryArg<Groups, Contexts, Tags>) => WindoDefinition<Props, State, Groups[number]['slug'], Tags[number]>\n ) => WindoModule<Props, State>\n}\n\nexport function defineWindoConfig<const Groups extends readonly WindoGroup[], const Tags extends readonly string[] = readonly [], Contexts extends WindoContextMap = Record<string, never>>(\n config: WindoConfig<Groups, Contexts, Tags>\n): DefineWindoConfigResult<Groups, Tags, Contexts> {\n function windo<Props, State = Record<string, never>>(\n factory: (w: WindoFactoryArg<Groups, Contexts, Tags>) => WindoDefinition<Props, State, Groups[number]['slug'], Tags[number]>\n ): WindoModule<Props, State> {\n return {\n __windo: true,\n resolve: w => factory(w as unknown as WindoFactoryArg<Groups, Contexts, Tags>),\n }\n }\n return { config, windo }\n}\n\n/**\n * Define a named context. A context can contribute ambient `controls` (values +\n * UI), a `provider` (mounted inside the iframe for components that opt in via\n * `uses`), or both.\n *\n * No cast: `def` is already structurally a `WindoContextDefinition<WindoControlValues<C>,\n * Provided>` (only `controls` widens, covariantly). The unavoidable variance — TS has\n * no existential type to hold contexts heterogeneous in `C` — is absorbed once, in\n * `WindoContextMap`, not here and not at call sites.\n */\nexport function defineContext<C extends WindoControlMap, Provided = WindoControlValues<C>>(def: {\n label?: string\n description?: string\n controls?: C\n provider?: ComponentType<{ children: ReactNode; values: WindoControlValues<C>; ctx: WindoRenderContext }>\n resolve?: (values: WindoControlValues<C>, ctx: WindoRenderContext) => Provided\n}): WindoContextDefinition<WindoControlValues<C>, Provided> {\n return def\n}\n\n/**\n * Bind a zod schema to a component's props. The generic carries the component's\n * prop type so the schema's output stays a subset of it; the returned schema is\n * the runtime validator + parser (`z.input` is the JSON edit surface, `z.output`\n * is what the component receives).\n */\nexport function configurableProps<P>() {\n return <S extends z.ZodType<Partial<P>>>(schema: S): S => schema\n}\n","// Walk a zod schema into a serialisable descriptor that can cross the iframe\n// boundary and render the Controls/Schema UI. We lean on `z.toJSONSchema` (zod\n// v4) for the input shape (enum options, min/max, optionality), then enrich the\n// kind from the zod node's own type tag — some types (Date, Map, Set) are\n// unrepresentable in JSON Schema and come back as `{}`, but their zod `def.type`\n// is exact. The live schema (with transforms/coerce/refine) stays in the iframe\n// and does the actual parsing — only this descriptor travels.\n\nimport { z } from 'zod'\nimport type { WindoControlDescriptor, WindoControlKind, WindoSchemaDescriptor } from './types'\n\ninterface JsonNode {\n type?: string | string[]\n enum?: unknown[]\n const?: unknown\n format?: string\n properties?: Record<string, JsonNode>\n required?: string[]\n items?: JsonNode\n anyOf?: JsonNode[]\n oneOf?: JsonNode[]\n allOf?: JsonNode[]\n minimum?: number\n maximum?: number\n description?: string\n}\n\nexport function describeSchema(schema: z.ZodType | undefined | null): WindoSchemaDescriptor {\n if (!schema) return { fields: [] }\n let json: JsonNode\n try {\n json = z.toJSONSchema(schema, { io: 'input', unrepresentable: 'any' }) as JsonNode\n } catch {\n return { fields: [] }\n }\n const root = unwrap(json)\n const properties = root.properties ?? {}\n const required = new Set(root.required ?? [])\n const zodKinds = shapeKinds(schema)\n const fields: WindoControlDescriptor[] = Object.keys(properties).map(key => fieldFrom(key, properties[key], required.has(key), zodKinds[key]))\n return { fields }\n}\n\n// JSON Schema often wraps the object in anyOf (for optional/nullable). Find the\n// node that actually carries the object's properties.\nfunction unwrap(node: JsonNode): JsonNode {\n if (node.properties) return node\n const branches = node.anyOf ?? node.oneOf ?? node.allOf\n if (branches) {\n const withProps = branches.find(b => b.properties)\n if (withProps) return withProps\n }\n return node\n}\n\nfunction fieldFrom(key: string, node: JsonNode, isRequired: boolean, zodKind?: WindoControlKind): WindoControlDescriptor {\n const inner = collapseNullable(node)\n let kind = kindOf(inner)\n // The zod tag wins for types JSON Schema can't express (date/array/object that\n // came back as {}), and as a fallback whenever the JSON kind is unknown.\n if (zodKind && (kind === 'unknown' || zodKind === 'date')) kind = zodKind\n const descriptor: WindoControlDescriptor = {\n key,\n kind,\n optional: !isRequired || isNullable(node),\n }\n if (inner.description) descriptor.description = inner.description\n const options = enumOptions(inner)\n if (options) descriptor.options = options\n if (typeof inner.minimum === 'number') descriptor.min = inner.minimum\n if (typeof inner.maximum === 'number') descriptor.max = inner.maximum\n return descriptor\n}\n\nfunction isNullable(node: JsonNode): boolean {\n const branches = node.anyOf ?? node.oneOf\n if (!branches) return false\n return branches.some(b => b.type === 'null')\n}\n\n// Strip a `{ anyOf: [T, null] }` wrapper down to T.\nfunction collapseNullable(node: JsonNode): JsonNode {\n const branches = node.anyOf ?? node.oneOf\n if (!branches) return node\n const nonNull = branches.filter(b => b.type !== 'null')\n if (nonNull.length === 1) return nonNull[0]\n return node\n}\n\nfunction enumOptions(node: JsonNode): string[] | undefined {\n if (Array.isArray(node.enum)) return node.enum.map(v => String(v))\n const branches = node.anyOf ?? node.oneOf\n if (branches?.every(b => b.const !== undefined)) {\n return branches.map(b => String(b.const))\n }\n return undefined\n}\n\nfunction kindOf(node: JsonNode): WindoControlKind {\n if (Array.isArray(node.enum) || (node.anyOf?.every(b => b.const !== undefined) ?? false)) return 'enum'\n if (node.format === 'date-time' || node.format === 'date') return 'date'\n const type = Array.isArray(node.type) ? node.type.find(t => t !== 'null') : node.type\n switch (type) {\n case 'string':\n return 'string'\n case 'number':\n case 'integer':\n return 'number'\n case 'boolean':\n return 'boolean'\n case 'array':\n return 'array'\n case 'object':\n return 'object'\n default:\n return 'unknown'\n }\n}\n\n/* ------------------------------------------------------------------ *\n * zod node introspection (the kind source of truth for unrepresentable types)\n * ------------------------------------------------------------------ */\n\ninterface ZodDefLike {\n type?: string\n innerType?: unknown\n}\n\nfunction zodDef(node: unknown): ZodDefLike | undefined {\n const n = node as { def?: ZodDefLike; _zod?: { def?: ZodDefLike } } | null\n if (!n || typeof n !== 'object') return undefined\n return n.def ?? n._zod?.def\n}\n\nconst ZOD_WRAPPERS = new Set(['optional', 'nullable', 'default', 'prefault', 'catch', 'readonly', 'nonoptional', 'lazy'])\n\n// Unwrap optional/nullable/default/... down to the meaningful inner node.\nfunction unwrapZodDef(node: unknown): ZodDefLike | undefined {\n let def = zodDef(node)\n let guard = 0\n while (def && def.innerType && ZOD_WRAPPERS.has(def.type ?? '') && guard++ < 16) {\n def = zodDef(def.innerType)\n }\n return def\n}\n\nfunction mapZodType(type: string | undefined): WindoControlKind | undefined {\n switch (type) {\n case 'date':\n return 'date'\n case 'string':\n return 'string'\n case 'number':\n case 'int':\n case 'bigint':\n return 'number'\n case 'boolean':\n return 'boolean'\n case 'array':\n case 'set':\n case 'tuple':\n return 'array'\n case 'object':\n case 'record':\n case 'map':\n return 'object'\n case 'enum':\n case 'literal':\n return 'enum'\n default:\n return undefined\n }\n}\n\n// Per-field kind read straight from the zod object's shape.\nfunction shapeKinds(schema: z.ZodType): Record<string, WindoControlKind> {\n const shape = (schema as { shape?: Record<string, unknown> }).shape\n if (!shape || typeof shape !== 'object') return {}\n const out: Record<string, WindoControlKind> = {}\n for (const key of Object.keys(shape)) {\n const kind = mapZodType(unwrapZodDef(shape[key])?.type)\n if (kind) out[key] = kind\n }\n return out\n}\n","// The chrome <-> iframe postMessage contract. The schema itself never crosses\n// the boundary: the iframe walks it into a serialisable descriptor, the chrome\n// sends candidate JSON down, the iframe parses and reports back. Every payload\n// here is plain JSON.\n\nimport type { WindoContextMeta, WindoEnvState, WindoFieldError, WindoGroup, WindoLogEntry, WindoManifestEntry, WindoPropDoc, WindoSchemaDescriptor, WindoVariantMeta } from './types'\n\nexport const WINDO_MSG = 'windo'\n\n/** chrome -> iframe */\nexport type WindoHostMessage =\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'request-manifest' }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'select'; id: string }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'set-props'; id: string; json: string }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'set-env'; env: WindoEnvState }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'invoke-action'; id: string; actionId: string }\n\n/** iframe -> chrome */\nexport type WindoPreviewMessage =\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'ready' }\n | {\n source: typeof WINDO_MSG\n dir: 'preview'\n type: 'manifest'\n title: string\n entries: WindoManifestEntry[]\n groups: WindoGroup[]\n tags: string[]\n contexts: WindoContextMeta[]\n }\n | {\n source: typeof WINDO_MSG\n dir: 'preview'\n type: 'describe'\n id: string\n descriptor: WindoSchemaDescriptor\n props: WindoPropDoc[]\n variants: WindoVariantMeta[]\n defaults: unknown\n code: string | null\n }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'parse-ok'; id: string }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'parse-error'; id: string; errors: WindoFieldError[] }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'log'; entry: WindoLogEntry }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'state'; id: string; state: Record<string, unknown>; actions: { id: string; disabled: boolean }[] }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'render-error'; id: string; message: string; stack?: string }\n\nexport type WindoMessage = WindoHostMessage | WindoPreviewMessage\n\nexport function isWindoMessage(data: unknown): data is WindoMessage {\n return typeof data === 'object' && data !== null && (data as { source?: unknown }).source === WINDO_MSG\n}\n\nexport function isHostMessage(msg: WindoMessage): msg is WindoHostMessage {\n return msg.dir === 'host'\n}\n\nexport function isPreviewMessage(msg: WindoMessage): msg is WindoPreviewMessage {\n return msg.dir === 'preview'\n}\n","// Core type system for windo. Everything — the authoring API, the iframe\n// preview runtime, the chrome UI, and the postMessage protocol — is typed\n// against the contracts in this file.\n\nimport type { ComponentType, ReactNode } from 'react'\nimport type { z } from 'zod'\n\n/* ------------------------------------------------------------------ *\n * Primitives\n * ------------------------------------------------------------------ */\n\nexport type WindoStatus = 'stable' | 'beta' | 'deprecated'\n\nexport const WINDO_STATUSES: readonly WindoStatus[] = ['stable', 'beta', 'deprecated']\n\n/** Anchor a component within the canvas frame. */\nexport type WindoPlacementBase = 'center' | 'fill' | 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n\n/**\n * Where a component renders inside the canvas frame. Placements render flush by\n * default; append `-padding` to inset the component from the frame edges\n * (e.g. `top` sits flush at the top, `top-padding` adds breathing room).\n */\nexport type WindoPlacement = WindoPlacementBase | `${WindoPlacementBase}-padding`\n\nconst WINDO_PLACEMENT_BASE: readonly WindoPlacementBase[] = ['center', 'fill', 'top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right']\n\nexport const WINDO_PLACEMENTS: readonly WindoPlacement[] = [...WINDO_PLACEMENT_BASE, ...WINDO_PLACEMENT_BASE.map(p => `${p}-padding` as WindoPlacement)]\n\n/** A configured group. Components reference a group by its `slug`. */\nexport interface WindoGroup {\n name: string\n slug: string\n description?: string\n}\n\n/* ------------------------------------------------------------------ *\n * Context system\n * ------------------------------------------------------------------ */\n\nexport type WindoControlType = 'enum' | 'boolean' | 'string' | 'number'\n\n/** A single ambient control: a value plus the metadata to render its toggle. */\nexport interface WindoControlSpec<T = unknown> {\n type: WindoControlType\n label?: string\n default: T\n options?: readonly string[]\n min?: number\n max?: number\n step?: number\n}\n\nexport type WindoControlMap = Record<string, WindoControlSpec>\n\n/** Resolve a control map to the value object it produces. */\nexport type WindoControlValues<C extends WindoControlMap> = {\n [K in keyof C]: C[K] extends WindoControlSpec<infer T> ? T : never\n}\n\n/**\n * A named context. Two capabilities, either or both:\n * - `controls` → ambient values + UI toggles (free, no opt-in needed)\n * - `provider` → a React wrapper, mounted inside the iframe around components\n * that opt in via `uses`\n */\nexport interface WindoContextDefinition<Values = unknown, Provided = Values> {\n label?: string\n description?: string\n controls?: WindoControlMap\n provider?: ComponentType<{ children: ReactNode; values: Values; ctx: WindoRenderContext }>\n /** Derive the value exposed on `ctx.contexts[name]`. Defaults to the control values. */\n resolve?: (values: Values, ctx: WindoRenderContext) => Provided\n}\n\n// Contexts are each generic over a different control map `C`; TS has no existential\n// to hold `exists C. WindoContextDefinition<WindoControlValues<C>>`. `Values` sits in\n// contravariant positions (provider props, resolve arg) so no precise common supertype\n// exists — `any` is the single, contained variance hatch for this heterogeneous\n// registry. Mirrors WindoModule. Definition sites stay precise; this only erases at the\n// point of collection.\n// biome-ignore lint/suspicious/noExplicitAny: existential variance hatch for the heterogeneous context registry\nexport type WindoContextMap = Record<string, WindoContextDefinition<any, any>>\n\n/* ------------------------------------------------------------------ *\n * Render-time context\n * ------------------------------------------------------------------ */\n\nexport interface WindoViewport {\n width: number\n height: number\n name: 'mobile' | 'tablet' | 'desktop'\n}\n\n/** Console channel: `ctx.logger.log(…)` posts an entry to the chrome's Console tab. */\nexport interface WindoLogger {\n log: (...args: unknown[]) => void\n}\n\n/** How an action fires. `click` renders a toolbar button; the rest bind to the stage's pointer events. */\nexport type WindoActionTrigger = 'click' | 'enter' | 'exit' | 'hover'\n\nexport const WINDO_ACTION_TRIGGERS: readonly WindoActionTrigger[] = ['click', 'enter', 'exit', 'hover']\n\n/**\n * An out-of-band action that drives a component's state. `click` actions render as\n * toolbar buttons; `enter`/`exit`/`hover` bind to the stage's pointer events. `run`\n * receives the live ctx and an `active` flag — for `hover` it is `true` on\n * pointer-enter and `false` on pointer-leave; for the others it is always `true`.\n */\nexport interface WindoAction<State = unknown> {\n label: string\n /** Defaults to `click`. */\n on?: WindoActionTrigger\n run: (ctx: WindoRenderContext<State>, active: boolean) => void\n /** Greys out a `click` action's toolbar button. Evaluated against the live state. */\n disabled?: (ctx: WindoRenderContext<State>) => boolean\n}\n\n/** Live environment handed to every render-time function inside the iframe. */\nexport interface WindoRenderContext<State = unknown> {\n colorScheme: 'light' | 'dark'\n viewport: WindoViewport\n reducedMotion: boolean\n direction: 'ltr' | 'rtl'\n locale: string\n logger: WindoLogger\n /** Current component-local state, typed by the windo's `State` generic. */\n state: State\n /** Merge a patch into the component-local state and re-render. */\n setState: (patch: Partial<State>) => void\n /** Resolved values of opted-in contexts, keyed by context name. */\n contexts: Record<string, unknown>\n}\n\n/* ------------------------------------------------------------------ *\n * Authoring API\n * ------------------------------------------------------------------ */\n\n/** A variant: a label plus a partial prop patch. Renders in the gallery and is click-to-apply. */\nexport interface WindoVariant<Props> {\n label: string\n props: Partial<Props>\n}\n\n/** A row in the authored Props documentation table. */\nexport interface WindoPropDoc {\n name: string\n type: string\n default?: string\n desc?: string\n}\n\nexport type WindoDefaultProps<Props, State = unknown> = Props | ((ctx: WindoRenderContext<State>) => Props)\n\n/**\n * The object returned by a `windo(...)` factory.\n *\n * Keystone rule: the surrounding factory runs ONCE (definition-time) for static\n * fields (title, group, schema). Every function field below — `defaultProps`,\n * `actions`, `providers`, `component` — runs at render-time with the live `ctx`.\n * Never close over live values in the static factory body.\n */\nexport interface WindoDefinition<Props = unknown, State = unknown, GroupSlug extends string = string, Tag extends string = string> {\n title: string\n group: GroupSlug\n /** Tags this component carries. Each must be one of the config's declared `tags`. Drives the sidebar's tag filter. */\n tags?: Tag[]\n status?: WindoStatus\n description?: string\n deprecation?: string\n placement?: WindoPlacement\n /** Initial component-local state. Its shape is the `State` generic; `ctx.state`/`ctx.setState` derive from it. */\n state?: State\n /** Out-of-band actions that drive state: toolbar buttons (`click`) and stage pointer triggers (`enter`/`exit`/`hover`). */\n actions?: WindoAction<State>[]\n /** zod schema: validator + parser for the JSON-editable prop subset. `z.output ⊆ Props`. */\n configurableProps?: z.ZodType\n /** Full props incl. functions/JSX. The editor's JSON overrides merge on top. */\n defaultProps: WindoDefaultProps<Props, State>\n /** Names of provider contexts this component opts into. */\n uses?: string[]\n variants?: WindoVariant<Props>[]\n /** Authored documentation table (not derived from the schema). */\n props?: WindoPropDoc[]\n /** Optional authored code snippet for the Code tab. */\n code?: (values: Props) => string\n /** A local provider wrapping just this windo (in addition to `uses`). */\n providers?: ComponentType<{ children: ReactNode; ctx: WindoRenderContext<State> }>\n component: (props: Props, ctx: WindoRenderContext<State>) => ReactNode\n}\n\n/** Argument handed to the `windo(w => ...)` factory. */\nexport interface WindoFactoryArg<Groups extends readonly WindoGroup[], Contexts extends WindoContextMap, Tags extends readonly string[] = readonly string[]> {\n /** Configured groups keyed by slug. */\n groups: Record<Groups[number]['slug'], WindoGroup>\n contexts: Contexts\n /** The config's declared tags, in declaration order. */\n tags: Tags\n}\n\n/**\n * The default export of a `*.windo.tsx` file. A branded, lazily-resolved\n * definition — the runtime calls `resolve(w)` with the config-derived factory arg.\n */\n// biome-ignore lint/suspicious/noExplicitAny: variance escape hatch for the heterogeneous WindoModule registry\nexport interface WindoModule<Props = any, State = any> {\n readonly __windo: true\n resolve: (w: WindoFactoryArg<readonly WindoGroup[], WindoContextMap>) => WindoDefinition<Props, State>\n}\n\n/* ------------------------------------------------------------------ *\n * Config\n * ------------------------------------------------------------------ */\n\nexport interface WindoConfig<Groups extends readonly WindoGroup[] = readonly WindoGroup[], Contexts extends WindoContextMap = WindoContextMap, Tags extends readonly string[] = readonly string[]> {\n /** Configured groups. A component's `group` must be one of these slugs. */\n groups: Groups\n /** Named contexts available to components. */\n contexts?: Contexts\n /** The set of tags components may be assigned. A component's `tags` must be drawn from this list; the sidebar filters by them. */\n tags?: Tags\n /** Glob(s) for discovery, relative to project root. Default `**\\/*.windo.tsx`. */\n include?: string | string[]\n /** Title shown in the workbench chrome. */\n title?: string\n}\n\n/* ------------------------------------------------------------------ *\n * Schema descriptor (crosses the iframe boundary; renders the controls)\n * ------------------------------------------------------------------ */\n\nexport type WindoControlKind = 'string' | 'number' | 'boolean' | 'enum' | 'date' | 'array' | 'object' | 'unknown'\n\nexport interface WindoControlDescriptor {\n key: string\n kind: WindoControlKind\n optional: boolean\n options?: string[]\n min?: number\n max?: number\n description?: string\n}\n\nexport interface WindoSchemaDescriptor {\n fields: WindoControlDescriptor[]\n}\n\n/* ------------------------------------------------------------------ *\n * Runtime manifest + protocol payloads\n * ------------------------------------------------------------------ */\n\n/** Serialisable metadata for one action — drives the canvas toolbar. */\nexport interface WindoActionMeta {\n id: string\n label: string\n on: WindoActionTrigger\n}\n\n/** Static, serialisable metadata for one windo — drives the sidebar. */\nexport interface WindoManifestEntry {\n id: string\n title: string\n group: string\n tags: string[]\n status: WindoStatus\n description?: string\n deprecation?: string\n placement: WindoPlacement\n uses: string[]\n hasVariants: boolean\n actions: WindoActionMeta[]\n hasState: boolean\n}\n\nexport interface WindoVariantMeta {\n label: string\n props: Record<string, unknown>\n}\n\n/** Ambient environment pushed from the chrome down into the iframe. */\nexport interface WindoEnvState {\n colorScheme: 'light' | 'dark'\n viewport: WindoViewport\n reducedMotion: boolean\n direction: 'ltr' | 'rtl'\n locale: string\n /** Per-context control values, keyed by context name then control key. */\n contexts: Record<string, Record<string, unknown>>\n}\n\nexport interface WindoLogEntry {\n ts: number\n args: unknown[]\n}\n\nexport interface WindoFieldError {\n path: string\n message: string\n}\n\n/* ------------------------------------------------------------------ *\n * Context metadata (serialisable; drives the chrome's Context panel)\n * ------------------------------------------------------------------ */\n\nexport interface WindoContextControlMeta {\n key: string\n type: WindoControlType\n label?: string\n options?: string[]\n default: unknown\n min?: number\n max?: number\n step?: number\n}\n\nexport interface WindoContextMeta {\n name: string\n label?: string\n description?: string\n /** True when the context contributes controls (ambient values). */\n ambient: boolean\n /** True when the context mounts a provider (opt-in via `uses`). */\n hasProvider: boolean\n controls: WindoContextControlMeta[]\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -115,9 +115,11 @@ type WindoDefaultProps<Props, State = unknown> = Props | ((ctx: WindoRenderConte
115
115
  * `actions`, `providers`, `component` — runs at render-time with the live `ctx`.
116
116
  * Never close over live values in the static factory body.
117
117
  */
118
- interface WindoDefinition<Props = unknown, State = unknown, GroupSlug extends string = string> {
118
+ interface WindoDefinition<Props = unknown, State = unknown, GroupSlug extends string = string, Tag extends string = string> {
119
119
  title: string;
120
120
  group: GroupSlug;
121
+ /** Tags this component carries. Each must be one of the config's declared `tags`. Drives the sidebar's tag filter. */
122
+ tags?: Tag[];
121
123
  status?: WindoStatus;
122
124
  description?: string;
123
125
  deprecation?: string;
@@ -145,10 +147,12 @@ interface WindoDefinition<Props = unknown, State = unknown, GroupSlug extends st
145
147
  component: (props: Props, ctx: WindoRenderContext<State>) => ReactNode;
146
148
  }
147
149
  /** Argument handed to the `windo(w => ...)` factory. */
148
- interface WindoFactoryArg<Groups extends readonly WindoGroup[], Contexts extends WindoContextMap> {
150
+ interface WindoFactoryArg<Groups extends readonly WindoGroup[], Contexts extends WindoContextMap, Tags extends readonly string[] = readonly string[]> {
149
151
  /** Configured groups keyed by slug. */
150
152
  groups: Record<Groups[number]['slug'], WindoGroup>;
151
153
  contexts: Contexts;
154
+ /** The config's declared tags, in declaration order. */
155
+ tags: Tags;
152
156
  }
153
157
  /**
154
158
  * The default export of a `*.windo.tsx` file. A branded, lazily-resolved
@@ -158,11 +162,13 @@ interface WindoModule<Props = any, State = any> {
158
162
  readonly __windo: true;
159
163
  resolve: (w: WindoFactoryArg<readonly WindoGroup[], WindoContextMap>) => WindoDefinition<Props, State>;
160
164
  }
161
- interface WindoConfig<Groups extends readonly WindoGroup[] = readonly WindoGroup[], Contexts extends WindoContextMap = WindoContextMap> {
165
+ interface WindoConfig<Groups extends readonly WindoGroup[] = readonly WindoGroup[], Contexts extends WindoContextMap = WindoContextMap, Tags extends readonly string[] = readonly string[]> {
162
166
  /** Configured groups. A component's `group` must be one of these slugs. */
163
167
  groups: Groups;
164
168
  /** Named contexts available to components. */
165
169
  contexts?: Contexts;
170
+ /** The set of tags components may be assigned. A component's `tags` must be drawn from this list; the sidebar filters by them. */
171
+ tags?: Tags;
166
172
  /** Glob(s) for discovery, relative to project root. Default `**\/*.windo.tsx`. */
167
173
  include?: string | string[];
168
174
  /** Title shown in the workbench chrome. */
@@ -192,6 +198,7 @@ interface WindoManifestEntry {
192
198
  id: string;
193
199
  title: string;
194
200
  group: string;
201
+ tags: string[];
195
202
  status: WindoStatus;
196
203
  description?: string;
197
204
  deprecation?: string;
@@ -244,11 +251,11 @@ interface WindoContextMeta {
244
251
  controls: WindoContextControlMeta[];
245
252
  }
246
253
 
247
- interface DefineWindoConfigResult<Groups extends readonly WindoGroup[], Contexts extends WindoContextMap> {
248
- config: WindoConfig<Groups, Contexts>;
249
- windo: <Props, State = Record<string, never>>(factory: (w: WindoFactoryArg<Groups, Contexts>) => WindoDefinition<Props, State, Groups[number]['slug']>) => WindoModule<Props, State>;
254
+ interface DefineWindoConfigResult<Groups extends readonly WindoGroup[], Tags extends readonly string[], Contexts extends WindoContextMap> {
255
+ config: WindoConfig<Groups, Contexts, Tags>;
256
+ windo: <Props, State = Record<string, never>>(factory: (w: WindoFactoryArg<Groups, Contexts, Tags>) => WindoDefinition<Props, State, Groups[number]['slug'], Tags[number]>) => WindoModule<Props, State>;
250
257
  }
251
- declare function defineWindoConfig<const Groups extends readonly WindoGroup[], Contexts extends WindoContextMap = Record<string, never>>(config: WindoConfig<Groups, Contexts>): DefineWindoConfigResult<Groups, Contexts>;
258
+ declare function defineWindoConfig<const Groups extends readonly WindoGroup[], const Tags extends readonly string[] = readonly [], Contexts extends WindoContextMap = Record<string, never>>(config: WindoConfig<Groups, Contexts, Tags>): DefineWindoConfigResult<Groups, Tags, Contexts>;
252
259
  /**
253
260
  * Define a named context. A context can contribute ambient `controls` (values +
254
261
  * UI), a `provider` (mounted inside the iframe for components that opt in via
@@ -321,6 +328,7 @@ type WindoPreviewMessage = {
321
328
  title: string;
322
329
  entries: WindoManifestEntry[];
323
330
  groups: WindoGroup[];
331
+ tags: string[];
324
332
  contexts: WindoContextMeta[];
325
333
  } | {
326
334
  source: typeof WINDO_MSG;
package/dist/index.d.ts CHANGED
@@ -115,9 +115,11 @@ type WindoDefaultProps<Props, State = unknown> = Props | ((ctx: WindoRenderConte
115
115
  * `actions`, `providers`, `component` — runs at render-time with the live `ctx`.
116
116
  * Never close over live values in the static factory body.
117
117
  */
118
- interface WindoDefinition<Props = unknown, State = unknown, GroupSlug extends string = string> {
118
+ interface WindoDefinition<Props = unknown, State = unknown, GroupSlug extends string = string, Tag extends string = string> {
119
119
  title: string;
120
120
  group: GroupSlug;
121
+ /** Tags this component carries. Each must be one of the config's declared `tags`. Drives the sidebar's tag filter. */
122
+ tags?: Tag[];
121
123
  status?: WindoStatus;
122
124
  description?: string;
123
125
  deprecation?: string;
@@ -145,10 +147,12 @@ interface WindoDefinition<Props = unknown, State = unknown, GroupSlug extends st
145
147
  component: (props: Props, ctx: WindoRenderContext<State>) => ReactNode;
146
148
  }
147
149
  /** Argument handed to the `windo(w => ...)` factory. */
148
- interface WindoFactoryArg<Groups extends readonly WindoGroup[], Contexts extends WindoContextMap> {
150
+ interface WindoFactoryArg<Groups extends readonly WindoGroup[], Contexts extends WindoContextMap, Tags extends readonly string[] = readonly string[]> {
149
151
  /** Configured groups keyed by slug. */
150
152
  groups: Record<Groups[number]['slug'], WindoGroup>;
151
153
  contexts: Contexts;
154
+ /** The config's declared tags, in declaration order. */
155
+ tags: Tags;
152
156
  }
153
157
  /**
154
158
  * The default export of a `*.windo.tsx` file. A branded, lazily-resolved
@@ -158,11 +162,13 @@ interface WindoModule<Props = any, State = any> {
158
162
  readonly __windo: true;
159
163
  resolve: (w: WindoFactoryArg<readonly WindoGroup[], WindoContextMap>) => WindoDefinition<Props, State>;
160
164
  }
161
- interface WindoConfig<Groups extends readonly WindoGroup[] = readonly WindoGroup[], Contexts extends WindoContextMap = WindoContextMap> {
165
+ interface WindoConfig<Groups extends readonly WindoGroup[] = readonly WindoGroup[], Contexts extends WindoContextMap = WindoContextMap, Tags extends readonly string[] = readonly string[]> {
162
166
  /** Configured groups. A component's `group` must be one of these slugs. */
163
167
  groups: Groups;
164
168
  /** Named contexts available to components. */
165
169
  contexts?: Contexts;
170
+ /** The set of tags components may be assigned. A component's `tags` must be drawn from this list; the sidebar filters by them. */
171
+ tags?: Tags;
166
172
  /** Glob(s) for discovery, relative to project root. Default `**\/*.windo.tsx`. */
167
173
  include?: string | string[];
168
174
  /** Title shown in the workbench chrome. */
@@ -192,6 +198,7 @@ interface WindoManifestEntry {
192
198
  id: string;
193
199
  title: string;
194
200
  group: string;
201
+ tags: string[];
195
202
  status: WindoStatus;
196
203
  description?: string;
197
204
  deprecation?: string;
@@ -244,11 +251,11 @@ interface WindoContextMeta {
244
251
  controls: WindoContextControlMeta[];
245
252
  }
246
253
 
247
- interface DefineWindoConfigResult<Groups extends readonly WindoGroup[], Contexts extends WindoContextMap> {
248
- config: WindoConfig<Groups, Contexts>;
249
- windo: <Props, State = Record<string, never>>(factory: (w: WindoFactoryArg<Groups, Contexts>) => WindoDefinition<Props, State, Groups[number]['slug']>) => WindoModule<Props, State>;
254
+ interface DefineWindoConfigResult<Groups extends readonly WindoGroup[], Tags extends readonly string[], Contexts extends WindoContextMap> {
255
+ config: WindoConfig<Groups, Contexts, Tags>;
256
+ windo: <Props, State = Record<string, never>>(factory: (w: WindoFactoryArg<Groups, Contexts, Tags>) => WindoDefinition<Props, State, Groups[number]['slug'], Tags[number]>) => WindoModule<Props, State>;
250
257
  }
251
- declare function defineWindoConfig<const Groups extends readonly WindoGroup[], Contexts extends WindoContextMap = Record<string, never>>(config: WindoConfig<Groups, Contexts>): DefineWindoConfigResult<Groups, Contexts>;
258
+ declare function defineWindoConfig<const Groups extends readonly WindoGroup[], const Tags extends readonly string[] = readonly [], Contexts extends WindoContextMap = Record<string, never>>(config: WindoConfig<Groups, Contexts, Tags>): DefineWindoConfigResult<Groups, Tags, Contexts>;
252
259
  /**
253
260
  * Define a named context. A context can contribute ambient `controls` (values +
254
261
  * UI), a `provider` (mounted inside the iframe for components that opt in via
@@ -321,6 +328,7 @@ type WindoPreviewMessage = {
321
328
  title: string;
322
329
  entries: WindoManifestEntry[];
323
330
  groups: WindoGroup[];
331
+ tags: string[];
324
332
  contexts: WindoContextMeta[];
325
333
  } | {
326
334
  source: typeof WINDO_MSG;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/define-config.ts","../src/descriptor.ts","../src/protocol.ts","../src/types.ts"],"sourcesContent":["// The authoring API. `defineWindoConfig` is the single entry point: it captures\n// the project's groups + contexts and hands back a `windo` factory whose `group`\n// field is type-checked against the configured slugs.\n\nimport type { ComponentType, ReactNode } from 'react'\nimport type { z } from 'zod'\nimport type { WindoConfig, WindoContextDefinition, WindoContextMap, WindoControlMap, WindoControlValues, WindoDefinition, WindoFactoryArg, WindoGroup, WindoModule, WindoRenderContext } from './types'\n\nexport interface DefineWindoConfigResult<Groups extends readonly WindoGroup[], Contexts extends WindoContextMap> {\n config: WindoConfig<Groups, Contexts>\n windo: <Props, State = Record<string, never>>(factory: (w: WindoFactoryArg<Groups, Contexts>) => WindoDefinition<Props, State, Groups[number]['slug']>) => WindoModule<Props, State>\n}\n\nexport function defineWindoConfig<const Groups extends readonly WindoGroup[], Contexts extends WindoContextMap = Record<string, never>>(\n config: WindoConfig<Groups, Contexts>\n): DefineWindoConfigResult<Groups, Contexts> {\n function windo<Props, State = Record<string, never>>(factory: (w: WindoFactoryArg<Groups, Contexts>) => WindoDefinition<Props, State, Groups[number]['slug']>): WindoModule<Props, State> {\n return {\n __windo: true,\n resolve: w => factory(w as unknown as WindoFactoryArg<Groups, Contexts>),\n }\n }\n return { config, windo }\n}\n\n/**\n * Define a named context. A context can contribute ambient `controls` (values +\n * UI), a `provider` (mounted inside the iframe for components that opt in via\n * `uses`), or both.\n *\n * No cast: `def` is already structurally a `WindoContextDefinition<WindoControlValues<C>,\n * Provided>` (only `controls` widens, covariantly). The unavoidable variance — TS has\n * no existential type to hold contexts heterogeneous in `C` — is absorbed once, in\n * `WindoContextMap`, not here and not at call sites.\n */\nexport function defineContext<C extends WindoControlMap, Provided = WindoControlValues<C>>(def: {\n label?: string\n description?: string\n controls?: C\n provider?: ComponentType<{ children: ReactNode; values: WindoControlValues<C>; ctx: WindoRenderContext }>\n resolve?: (values: WindoControlValues<C>, ctx: WindoRenderContext) => Provided\n}): WindoContextDefinition<WindoControlValues<C>, Provided> {\n return def\n}\n\n/**\n * Bind a zod schema to a component's props. The generic carries the component's\n * prop type so the schema's output stays a subset of it; the returned schema is\n * the runtime validator + parser (`z.input` is the JSON edit surface, `z.output`\n * is what the component receives).\n */\nexport function configurableProps<P>() {\n return <S extends z.ZodType<Partial<P>>>(schema: S): S => schema\n}\n","// Walk a zod schema into a serialisable descriptor that can cross the iframe\n// boundary and render the Controls/Schema UI. We lean on `z.toJSONSchema` (zod\n// v4) for the input shape (enum options, min/max, optionality), then enrich the\n// kind from the zod node's own type tag — some types (Date, Map, Set) are\n// unrepresentable in JSON Schema and come back as `{}`, but their zod `def.type`\n// is exact. The live schema (with transforms/coerce/refine) stays in the iframe\n// and does the actual parsing — only this descriptor travels.\n\nimport { z } from 'zod'\nimport type { WindoControlDescriptor, WindoControlKind, WindoSchemaDescriptor } from './types'\n\ninterface JsonNode {\n type?: string | string[]\n enum?: unknown[]\n const?: unknown\n format?: string\n properties?: Record<string, JsonNode>\n required?: string[]\n items?: JsonNode\n anyOf?: JsonNode[]\n oneOf?: JsonNode[]\n allOf?: JsonNode[]\n minimum?: number\n maximum?: number\n description?: string\n}\n\nexport function describeSchema(schema: z.ZodType | undefined | null): WindoSchemaDescriptor {\n if (!schema) return { fields: [] }\n let json: JsonNode\n try {\n json = z.toJSONSchema(schema, { io: 'input', unrepresentable: 'any' }) as JsonNode\n } catch {\n return { fields: [] }\n }\n const root = unwrap(json)\n const properties = root.properties ?? {}\n const required = new Set(root.required ?? [])\n const zodKinds = shapeKinds(schema)\n const fields: WindoControlDescriptor[] = Object.keys(properties).map(key => fieldFrom(key, properties[key], required.has(key), zodKinds[key]))\n return { fields }\n}\n\n// JSON Schema often wraps the object in anyOf (for optional/nullable). Find the\n// node that actually carries the object's properties.\nfunction unwrap(node: JsonNode): JsonNode {\n if (node.properties) return node\n const branches = node.anyOf ?? node.oneOf ?? node.allOf\n if (branches) {\n const withProps = branches.find(b => b.properties)\n if (withProps) return withProps\n }\n return node\n}\n\nfunction fieldFrom(key: string, node: JsonNode, isRequired: boolean, zodKind?: WindoControlKind): WindoControlDescriptor {\n const inner = collapseNullable(node)\n let kind = kindOf(inner)\n // The zod tag wins for types JSON Schema can't express (date/array/object that\n // came back as {}), and as a fallback whenever the JSON kind is unknown.\n if (zodKind && (kind === 'unknown' || zodKind === 'date')) kind = zodKind\n const descriptor: WindoControlDescriptor = {\n key,\n kind,\n optional: !isRequired || isNullable(node),\n }\n if (inner.description) descriptor.description = inner.description\n const options = enumOptions(inner)\n if (options) descriptor.options = options\n if (typeof inner.minimum === 'number') descriptor.min = inner.minimum\n if (typeof inner.maximum === 'number') descriptor.max = inner.maximum\n return descriptor\n}\n\nfunction isNullable(node: JsonNode): boolean {\n const branches = node.anyOf ?? node.oneOf\n if (!branches) return false\n return branches.some(b => b.type === 'null')\n}\n\n// Strip a `{ anyOf: [T, null] }` wrapper down to T.\nfunction collapseNullable(node: JsonNode): JsonNode {\n const branches = node.anyOf ?? node.oneOf\n if (!branches) return node\n const nonNull = branches.filter(b => b.type !== 'null')\n if (nonNull.length === 1) return nonNull[0]\n return node\n}\n\nfunction enumOptions(node: JsonNode): string[] | undefined {\n if (Array.isArray(node.enum)) return node.enum.map(v => String(v))\n const branches = node.anyOf ?? node.oneOf\n if (branches?.every(b => b.const !== undefined)) {\n return branches.map(b => String(b.const))\n }\n return undefined\n}\n\nfunction kindOf(node: JsonNode): WindoControlKind {\n if (Array.isArray(node.enum) || (node.anyOf?.every(b => b.const !== undefined) ?? false)) return 'enum'\n if (node.format === 'date-time' || node.format === 'date') return 'date'\n const type = Array.isArray(node.type) ? node.type.find(t => t !== 'null') : node.type\n switch (type) {\n case 'string':\n return 'string'\n case 'number':\n case 'integer':\n return 'number'\n case 'boolean':\n return 'boolean'\n case 'array':\n return 'array'\n case 'object':\n return 'object'\n default:\n return 'unknown'\n }\n}\n\n/* ------------------------------------------------------------------ *\n * zod node introspection (the kind source of truth for unrepresentable types)\n * ------------------------------------------------------------------ */\n\ninterface ZodDefLike {\n type?: string\n innerType?: unknown\n}\n\nfunction zodDef(node: unknown): ZodDefLike | undefined {\n const n = node as { def?: ZodDefLike; _zod?: { def?: ZodDefLike } } | null\n if (!n || typeof n !== 'object') return undefined\n return n.def ?? n._zod?.def\n}\n\nconst ZOD_WRAPPERS = new Set(['optional', 'nullable', 'default', 'prefault', 'catch', 'readonly', 'nonoptional', 'lazy'])\n\n// Unwrap optional/nullable/default/... down to the meaningful inner node.\nfunction unwrapZodDef(node: unknown): ZodDefLike | undefined {\n let def = zodDef(node)\n let guard = 0\n while (def && def.innerType && ZOD_WRAPPERS.has(def.type ?? '') && guard++ < 16) {\n def = zodDef(def.innerType)\n }\n return def\n}\n\nfunction mapZodType(type: string | undefined): WindoControlKind | undefined {\n switch (type) {\n case 'date':\n return 'date'\n case 'string':\n return 'string'\n case 'number':\n case 'int':\n case 'bigint':\n return 'number'\n case 'boolean':\n return 'boolean'\n case 'array':\n case 'set':\n case 'tuple':\n return 'array'\n case 'object':\n case 'record':\n case 'map':\n return 'object'\n case 'enum':\n case 'literal':\n return 'enum'\n default:\n return undefined\n }\n}\n\n// Per-field kind read straight from the zod object's shape.\nfunction shapeKinds(schema: z.ZodType): Record<string, WindoControlKind> {\n const shape = (schema as { shape?: Record<string, unknown> }).shape\n if (!shape || typeof shape !== 'object') return {}\n const out: Record<string, WindoControlKind> = {}\n for (const key of Object.keys(shape)) {\n const kind = mapZodType(unwrapZodDef(shape[key])?.type)\n if (kind) out[key] = kind\n }\n return out\n}\n","// The chrome <-> iframe postMessage contract. The schema itself never crosses\n// the boundary: the iframe walks it into a serialisable descriptor, the chrome\n// sends candidate JSON down, the iframe parses and reports back. Every payload\n// here is plain JSON.\n\nimport type { WindoContextMeta, WindoEnvState, WindoFieldError, WindoGroup, WindoLogEntry, WindoManifestEntry, WindoPropDoc, WindoSchemaDescriptor, WindoVariantMeta } from './types'\n\nexport const WINDO_MSG = 'windo'\n\n/** chrome -> iframe */\nexport type WindoHostMessage =\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'request-manifest' }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'select'; id: string }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'set-props'; id: string; json: string }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'set-env'; env: WindoEnvState }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'invoke-action'; id: string; actionId: string }\n\n/** iframe -> chrome */\nexport type WindoPreviewMessage =\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'ready' }\n | {\n source: typeof WINDO_MSG\n dir: 'preview'\n type: 'manifest'\n title: string\n entries: WindoManifestEntry[]\n groups: WindoGroup[]\n contexts: WindoContextMeta[]\n }\n | {\n source: typeof WINDO_MSG\n dir: 'preview'\n type: 'describe'\n id: string\n descriptor: WindoSchemaDescriptor\n props: WindoPropDoc[]\n variants: WindoVariantMeta[]\n defaults: unknown\n code: string | null\n }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'parse-ok'; id: string }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'parse-error'; id: string; errors: WindoFieldError[] }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'log'; entry: WindoLogEntry }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'state'; id: string; state: Record<string, unknown>; actions: { id: string; disabled: boolean }[] }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'render-error'; id: string; message: string; stack?: string }\n\nexport type WindoMessage = WindoHostMessage | WindoPreviewMessage\n\nexport function isWindoMessage(data: unknown): data is WindoMessage {\n return typeof data === 'object' && data !== null && (data as { source?: unknown }).source === WINDO_MSG\n}\n\nexport function isHostMessage(msg: WindoMessage): msg is WindoHostMessage {\n return msg.dir === 'host'\n}\n\nexport function isPreviewMessage(msg: WindoMessage): msg is WindoPreviewMessage {\n return msg.dir === 'preview'\n}\n","// Core type system for windo. Everything — the authoring API, the iframe\n// preview runtime, the chrome UI, and the postMessage protocol — is typed\n// against the contracts in this file.\n\nimport type { ComponentType, ReactNode } from 'react'\nimport type { z } from 'zod'\n\n/* ------------------------------------------------------------------ *\n * Primitives\n * ------------------------------------------------------------------ */\n\nexport type WindoStatus = 'stable' | 'beta' | 'deprecated'\n\nexport const WINDO_STATUSES: readonly WindoStatus[] = ['stable', 'beta', 'deprecated']\n\n/** Anchor a component within the canvas frame. */\nexport type WindoPlacementBase = 'center' | 'fill' | 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n\n/**\n * Where a component renders inside the canvas frame. Placements render flush by\n * default; append `-padding` to inset the component from the frame edges\n * (e.g. `top` sits flush at the top, `top-padding` adds breathing room).\n */\nexport type WindoPlacement = WindoPlacementBase | `${WindoPlacementBase}-padding`\n\nconst WINDO_PLACEMENT_BASE: readonly WindoPlacementBase[] = ['center', 'fill', 'top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right']\n\nexport const WINDO_PLACEMENTS: readonly WindoPlacement[] = [...WINDO_PLACEMENT_BASE, ...WINDO_PLACEMENT_BASE.map(p => `${p}-padding` as WindoPlacement)]\n\n/** A configured group. Components reference a group by its `slug`. */\nexport interface WindoGroup {\n name: string\n slug: string\n description?: string\n}\n\n/* ------------------------------------------------------------------ *\n * Context system\n * ------------------------------------------------------------------ */\n\nexport type WindoControlType = 'enum' | 'boolean' | 'string' | 'number'\n\n/** A single ambient control: a value plus the metadata to render its toggle. */\nexport interface WindoControlSpec<T = unknown> {\n type: WindoControlType\n label?: string\n default: T\n options?: readonly string[]\n min?: number\n max?: number\n step?: number\n}\n\nexport type WindoControlMap = Record<string, WindoControlSpec>\n\n/** Resolve a control map to the value object it produces. */\nexport type WindoControlValues<C extends WindoControlMap> = {\n [K in keyof C]: C[K] extends WindoControlSpec<infer T> ? T : never\n}\n\n/**\n * A named context. Two capabilities, either or both:\n * - `controls` → ambient values + UI toggles (free, no opt-in needed)\n * - `provider` → a React wrapper, mounted inside the iframe around components\n * that opt in via `uses`\n */\nexport interface WindoContextDefinition<Values = unknown, Provided = Values> {\n label?: string\n description?: string\n controls?: WindoControlMap\n provider?: ComponentType<{ children: ReactNode; values: Values; ctx: WindoRenderContext }>\n /** Derive the value exposed on `ctx.contexts[name]`. Defaults to the control values. */\n resolve?: (values: Values, ctx: WindoRenderContext) => Provided\n}\n\n// Contexts are each generic over a different control map `C`; TS has no existential\n// to hold `exists C. WindoContextDefinition<WindoControlValues<C>>`. `Values` sits in\n// contravariant positions (provider props, resolve arg) so no precise common supertype\n// exists — `any` is the single, contained variance hatch for this heterogeneous\n// registry. Mirrors WindoModule. Definition sites stay precise; this only erases at the\n// point of collection.\n// biome-ignore lint/suspicious/noExplicitAny: existential variance hatch for the heterogeneous context registry\nexport type WindoContextMap = Record<string, WindoContextDefinition<any, any>>\n\n/* ------------------------------------------------------------------ *\n * Render-time context\n * ------------------------------------------------------------------ */\n\nexport interface WindoViewport {\n width: number\n height: number\n name: 'mobile' | 'tablet' | 'desktop'\n}\n\n/** Console channel: `ctx.logger.log(…)` posts an entry to the chrome's Console tab. */\nexport interface WindoLogger {\n log: (...args: unknown[]) => void\n}\n\n/** How an action fires. `click` renders a toolbar button; the rest bind to the stage's pointer events. */\nexport type WindoActionTrigger = 'click' | 'enter' | 'exit' | 'hover'\n\nexport const WINDO_ACTION_TRIGGERS: readonly WindoActionTrigger[] = ['click', 'enter', 'exit', 'hover']\n\n/**\n * An out-of-band action that drives a component's state. `click` actions render as\n * toolbar buttons; `enter`/`exit`/`hover` bind to the stage's pointer events. `run`\n * receives the live ctx and an `active` flag — for `hover` it is `true` on\n * pointer-enter and `false` on pointer-leave; for the others it is always `true`.\n */\nexport interface WindoAction<State = unknown> {\n label: string\n /** Defaults to `click`. */\n on?: WindoActionTrigger\n run: (ctx: WindoRenderContext<State>, active: boolean) => void\n /** Greys out a `click` action's toolbar button. Evaluated against the live state. */\n disabled?: (ctx: WindoRenderContext<State>) => boolean\n}\n\n/** Live environment handed to every render-time function inside the iframe. */\nexport interface WindoRenderContext<State = unknown> {\n colorScheme: 'light' | 'dark'\n viewport: WindoViewport\n reducedMotion: boolean\n direction: 'ltr' | 'rtl'\n locale: string\n logger: WindoLogger\n /** Current component-local state, typed by the windo's `State` generic. */\n state: State\n /** Merge a patch into the component-local state and re-render. */\n setState: (patch: Partial<State>) => void\n /** Resolved values of opted-in contexts, keyed by context name. */\n contexts: Record<string, unknown>\n}\n\n/* ------------------------------------------------------------------ *\n * Authoring API\n * ------------------------------------------------------------------ */\n\n/** A variant: a label plus a partial prop patch. Renders in the gallery and is click-to-apply. */\nexport interface WindoVariant<Props> {\n label: string\n props: Partial<Props>\n}\n\n/** A row in the authored Props documentation table. */\nexport interface WindoPropDoc {\n name: string\n type: string\n default?: string\n desc?: string\n}\n\nexport type WindoDefaultProps<Props, State = unknown> = Props | ((ctx: WindoRenderContext<State>) => Props)\n\n/**\n * The object returned by a `windo(...)` factory.\n *\n * Keystone rule: the surrounding factory runs ONCE (definition-time) for static\n * fields (title, group, schema). Every function field below — `defaultProps`,\n * `actions`, `providers`, `component` — runs at render-time with the live `ctx`.\n * Never close over live values in the static factory body.\n */\nexport interface WindoDefinition<Props = unknown, State = unknown, GroupSlug extends string = string> {\n title: string\n group: GroupSlug\n status?: WindoStatus\n description?: string\n deprecation?: string\n placement?: WindoPlacement\n /** Initial component-local state. Its shape is the `State` generic; `ctx.state`/`ctx.setState` derive from it. */\n state?: State\n /** Out-of-band actions that drive state: toolbar buttons (`click`) and stage pointer triggers (`enter`/`exit`/`hover`). */\n actions?: WindoAction<State>[]\n /** zod schema: validator + parser for the JSON-editable prop subset. `z.output ⊆ Props`. */\n configurableProps?: z.ZodType\n /** Full props incl. functions/JSX. The editor's JSON overrides merge on top. */\n defaultProps: WindoDefaultProps<Props, State>\n /** Names of provider contexts this component opts into. */\n uses?: string[]\n variants?: WindoVariant<Props>[]\n /** Authored documentation table (not derived from the schema). */\n props?: WindoPropDoc[]\n /** Optional authored code snippet for the Code tab. */\n code?: (values: Props) => string\n /** A local provider wrapping just this windo (in addition to `uses`). */\n providers?: ComponentType<{ children: ReactNode; ctx: WindoRenderContext<State> }>\n component: (props: Props, ctx: WindoRenderContext<State>) => ReactNode\n}\n\n/** Argument handed to the `windo(w => ...)` factory. */\nexport interface WindoFactoryArg<Groups extends readonly WindoGroup[], Contexts extends WindoContextMap> {\n /** Configured groups keyed by slug. */\n groups: Record<Groups[number]['slug'], WindoGroup>\n contexts: Contexts\n}\n\n/**\n * The default export of a `*.windo.tsx` file. A branded, lazily-resolved\n * definition — the runtime calls `resolve(w)` with the config-derived factory arg.\n */\n// biome-ignore lint/suspicious/noExplicitAny: variance escape hatch for the heterogeneous WindoModule registry\nexport interface WindoModule<Props = any, State = any> {\n readonly __windo: true\n resolve: (w: WindoFactoryArg<readonly WindoGroup[], WindoContextMap>) => WindoDefinition<Props, State>\n}\n\n/* ------------------------------------------------------------------ *\n * Config\n * ------------------------------------------------------------------ */\n\nexport interface WindoConfig<Groups extends readonly WindoGroup[] = readonly WindoGroup[], Contexts extends WindoContextMap = WindoContextMap> {\n /** Configured groups. A component's `group` must be one of these slugs. */\n groups: Groups\n /** Named contexts available to components. */\n contexts?: Contexts\n /** Glob(s) for discovery, relative to project root. Default `**\\/*.windo.tsx`. */\n include?: string | string[]\n /** Title shown in the workbench chrome. */\n title?: string\n}\n\n/* ------------------------------------------------------------------ *\n * Schema descriptor (crosses the iframe boundary; renders the controls)\n * ------------------------------------------------------------------ */\n\nexport type WindoControlKind = 'string' | 'number' | 'boolean' | 'enum' | 'date' | 'array' | 'object' | 'unknown'\n\nexport interface WindoControlDescriptor {\n key: string\n kind: WindoControlKind\n optional: boolean\n options?: string[]\n min?: number\n max?: number\n description?: string\n}\n\nexport interface WindoSchemaDescriptor {\n fields: WindoControlDescriptor[]\n}\n\n/* ------------------------------------------------------------------ *\n * Runtime manifest + protocol payloads\n * ------------------------------------------------------------------ */\n\n/** Serialisable metadata for one action — drives the canvas toolbar. */\nexport interface WindoActionMeta {\n id: string\n label: string\n on: WindoActionTrigger\n}\n\n/** Static, serialisable metadata for one windo — drives the sidebar. */\nexport interface WindoManifestEntry {\n id: string\n title: string\n group: string\n status: WindoStatus\n description?: string\n deprecation?: string\n placement: WindoPlacement\n uses: string[]\n hasVariants: boolean\n actions: WindoActionMeta[]\n hasState: boolean\n}\n\nexport interface WindoVariantMeta {\n label: string\n props: Record<string, unknown>\n}\n\n/** Ambient environment pushed from the chrome down into the iframe. */\nexport interface WindoEnvState {\n colorScheme: 'light' | 'dark'\n viewport: WindoViewport\n reducedMotion: boolean\n direction: 'ltr' | 'rtl'\n locale: string\n /** Per-context control values, keyed by context name then control key. */\n contexts: Record<string, Record<string, unknown>>\n}\n\nexport interface WindoLogEntry {\n ts: number\n args: unknown[]\n}\n\nexport interface WindoFieldError {\n path: string\n message: string\n}\n\n/* ------------------------------------------------------------------ *\n * Context metadata (serialisable; drives the chrome's Context panel)\n * ------------------------------------------------------------------ */\n\nexport interface WindoContextControlMeta {\n key: string\n type: WindoControlType\n label?: string\n options?: string[]\n default: unknown\n min?: number\n max?: number\n step?: number\n}\n\nexport interface WindoContextMeta {\n name: string\n label?: string\n description?: string\n /** True when the context contributes controls (ambient values). */\n ambient: boolean\n /** True when the context mounts a provider (opt-in via `uses`). */\n hasProvider: boolean\n controls: WindoContextControlMeta[]\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/define-config.ts","../src/descriptor.ts","../src/protocol.ts","../src/types.ts"],"sourcesContent":["// The authoring API. `defineWindoConfig` is the single entry point: it captures\n// the project's groups + contexts and hands back a `windo` factory whose `group`\n// field is type-checked against the configured slugs.\n\nimport type { ComponentType, ReactNode } from 'react'\nimport type { z } from 'zod'\nimport type { WindoConfig, WindoContextDefinition, WindoContextMap, WindoControlMap, WindoControlValues, WindoDefinition, WindoFactoryArg, WindoGroup, WindoModule, WindoRenderContext } from './types'\n\nexport interface DefineWindoConfigResult<Groups extends readonly WindoGroup[], Tags extends readonly string[], Contexts extends WindoContextMap> {\n config: WindoConfig<Groups, Contexts, Tags>\n windo: <Props, State = Record<string, never>>(\n factory: (w: WindoFactoryArg<Groups, Contexts, Tags>) => WindoDefinition<Props, State, Groups[number]['slug'], Tags[number]>\n ) => WindoModule<Props, State>\n}\n\nexport function defineWindoConfig<const Groups extends readonly WindoGroup[], const Tags extends readonly string[] = readonly [], Contexts extends WindoContextMap = Record<string, never>>(\n config: WindoConfig<Groups, Contexts, Tags>\n): DefineWindoConfigResult<Groups, Tags, Contexts> {\n function windo<Props, State = Record<string, never>>(\n factory: (w: WindoFactoryArg<Groups, Contexts, Tags>) => WindoDefinition<Props, State, Groups[number]['slug'], Tags[number]>\n ): WindoModule<Props, State> {\n return {\n __windo: true,\n resolve: w => factory(w as unknown as WindoFactoryArg<Groups, Contexts, Tags>),\n }\n }\n return { config, windo }\n}\n\n/**\n * Define a named context. A context can contribute ambient `controls` (values +\n * UI), a `provider` (mounted inside the iframe for components that opt in via\n * `uses`), or both.\n *\n * No cast: `def` is already structurally a `WindoContextDefinition<WindoControlValues<C>,\n * Provided>` (only `controls` widens, covariantly). The unavoidable variance — TS has\n * no existential type to hold contexts heterogeneous in `C` — is absorbed once, in\n * `WindoContextMap`, not here and not at call sites.\n */\nexport function defineContext<C extends WindoControlMap, Provided = WindoControlValues<C>>(def: {\n label?: string\n description?: string\n controls?: C\n provider?: ComponentType<{ children: ReactNode; values: WindoControlValues<C>; ctx: WindoRenderContext }>\n resolve?: (values: WindoControlValues<C>, ctx: WindoRenderContext) => Provided\n}): WindoContextDefinition<WindoControlValues<C>, Provided> {\n return def\n}\n\n/**\n * Bind a zod schema to a component's props. The generic carries the component's\n * prop type so the schema's output stays a subset of it; the returned schema is\n * the runtime validator + parser (`z.input` is the JSON edit surface, `z.output`\n * is what the component receives).\n */\nexport function configurableProps<P>() {\n return <S extends z.ZodType<Partial<P>>>(schema: S): S => schema\n}\n","// Walk a zod schema into a serialisable descriptor that can cross the iframe\n// boundary and render the Controls/Schema UI. We lean on `z.toJSONSchema` (zod\n// v4) for the input shape (enum options, min/max, optionality), then enrich the\n// kind from the zod node's own type tag — some types (Date, Map, Set) are\n// unrepresentable in JSON Schema and come back as `{}`, but their zod `def.type`\n// is exact. The live schema (with transforms/coerce/refine) stays in the iframe\n// and does the actual parsing — only this descriptor travels.\n\nimport { z } from 'zod'\nimport type { WindoControlDescriptor, WindoControlKind, WindoSchemaDescriptor } from './types'\n\ninterface JsonNode {\n type?: string | string[]\n enum?: unknown[]\n const?: unknown\n format?: string\n properties?: Record<string, JsonNode>\n required?: string[]\n items?: JsonNode\n anyOf?: JsonNode[]\n oneOf?: JsonNode[]\n allOf?: JsonNode[]\n minimum?: number\n maximum?: number\n description?: string\n}\n\nexport function describeSchema(schema: z.ZodType | undefined | null): WindoSchemaDescriptor {\n if (!schema) return { fields: [] }\n let json: JsonNode\n try {\n json = z.toJSONSchema(schema, { io: 'input', unrepresentable: 'any' }) as JsonNode\n } catch {\n return { fields: [] }\n }\n const root = unwrap(json)\n const properties = root.properties ?? {}\n const required = new Set(root.required ?? [])\n const zodKinds = shapeKinds(schema)\n const fields: WindoControlDescriptor[] = Object.keys(properties).map(key => fieldFrom(key, properties[key], required.has(key), zodKinds[key]))\n return { fields }\n}\n\n// JSON Schema often wraps the object in anyOf (for optional/nullable). Find the\n// node that actually carries the object's properties.\nfunction unwrap(node: JsonNode): JsonNode {\n if (node.properties) return node\n const branches = node.anyOf ?? node.oneOf ?? node.allOf\n if (branches) {\n const withProps = branches.find(b => b.properties)\n if (withProps) return withProps\n }\n return node\n}\n\nfunction fieldFrom(key: string, node: JsonNode, isRequired: boolean, zodKind?: WindoControlKind): WindoControlDescriptor {\n const inner = collapseNullable(node)\n let kind = kindOf(inner)\n // The zod tag wins for types JSON Schema can't express (date/array/object that\n // came back as {}), and as a fallback whenever the JSON kind is unknown.\n if (zodKind && (kind === 'unknown' || zodKind === 'date')) kind = zodKind\n const descriptor: WindoControlDescriptor = {\n key,\n kind,\n optional: !isRequired || isNullable(node),\n }\n if (inner.description) descriptor.description = inner.description\n const options = enumOptions(inner)\n if (options) descriptor.options = options\n if (typeof inner.minimum === 'number') descriptor.min = inner.minimum\n if (typeof inner.maximum === 'number') descriptor.max = inner.maximum\n return descriptor\n}\n\nfunction isNullable(node: JsonNode): boolean {\n const branches = node.anyOf ?? node.oneOf\n if (!branches) return false\n return branches.some(b => b.type === 'null')\n}\n\n// Strip a `{ anyOf: [T, null] }` wrapper down to T.\nfunction collapseNullable(node: JsonNode): JsonNode {\n const branches = node.anyOf ?? node.oneOf\n if (!branches) return node\n const nonNull = branches.filter(b => b.type !== 'null')\n if (nonNull.length === 1) return nonNull[0]\n return node\n}\n\nfunction enumOptions(node: JsonNode): string[] | undefined {\n if (Array.isArray(node.enum)) return node.enum.map(v => String(v))\n const branches = node.anyOf ?? node.oneOf\n if (branches?.every(b => b.const !== undefined)) {\n return branches.map(b => String(b.const))\n }\n return undefined\n}\n\nfunction kindOf(node: JsonNode): WindoControlKind {\n if (Array.isArray(node.enum) || (node.anyOf?.every(b => b.const !== undefined) ?? false)) return 'enum'\n if (node.format === 'date-time' || node.format === 'date') return 'date'\n const type = Array.isArray(node.type) ? node.type.find(t => t !== 'null') : node.type\n switch (type) {\n case 'string':\n return 'string'\n case 'number':\n case 'integer':\n return 'number'\n case 'boolean':\n return 'boolean'\n case 'array':\n return 'array'\n case 'object':\n return 'object'\n default:\n return 'unknown'\n }\n}\n\n/* ------------------------------------------------------------------ *\n * zod node introspection (the kind source of truth for unrepresentable types)\n * ------------------------------------------------------------------ */\n\ninterface ZodDefLike {\n type?: string\n innerType?: unknown\n}\n\nfunction zodDef(node: unknown): ZodDefLike | undefined {\n const n = node as { def?: ZodDefLike; _zod?: { def?: ZodDefLike } } | null\n if (!n || typeof n !== 'object') return undefined\n return n.def ?? n._zod?.def\n}\n\nconst ZOD_WRAPPERS = new Set(['optional', 'nullable', 'default', 'prefault', 'catch', 'readonly', 'nonoptional', 'lazy'])\n\n// Unwrap optional/nullable/default/... down to the meaningful inner node.\nfunction unwrapZodDef(node: unknown): ZodDefLike | undefined {\n let def = zodDef(node)\n let guard = 0\n while (def && def.innerType && ZOD_WRAPPERS.has(def.type ?? '') && guard++ < 16) {\n def = zodDef(def.innerType)\n }\n return def\n}\n\nfunction mapZodType(type: string | undefined): WindoControlKind | undefined {\n switch (type) {\n case 'date':\n return 'date'\n case 'string':\n return 'string'\n case 'number':\n case 'int':\n case 'bigint':\n return 'number'\n case 'boolean':\n return 'boolean'\n case 'array':\n case 'set':\n case 'tuple':\n return 'array'\n case 'object':\n case 'record':\n case 'map':\n return 'object'\n case 'enum':\n case 'literal':\n return 'enum'\n default:\n return undefined\n }\n}\n\n// Per-field kind read straight from the zod object's shape.\nfunction shapeKinds(schema: z.ZodType): Record<string, WindoControlKind> {\n const shape = (schema as { shape?: Record<string, unknown> }).shape\n if (!shape || typeof shape !== 'object') return {}\n const out: Record<string, WindoControlKind> = {}\n for (const key of Object.keys(shape)) {\n const kind = mapZodType(unwrapZodDef(shape[key])?.type)\n if (kind) out[key] = kind\n }\n return out\n}\n","// The chrome <-> iframe postMessage contract. The schema itself never crosses\n// the boundary: the iframe walks it into a serialisable descriptor, the chrome\n// sends candidate JSON down, the iframe parses and reports back. Every payload\n// here is plain JSON.\n\nimport type { WindoContextMeta, WindoEnvState, WindoFieldError, WindoGroup, WindoLogEntry, WindoManifestEntry, WindoPropDoc, WindoSchemaDescriptor, WindoVariantMeta } from './types'\n\nexport const WINDO_MSG = 'windo'\n\n/** chrome -> iframe */\nexport type WindoHostMessage =\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'request-manifest' }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'select'; id: string }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'set-props'; id: string; json: string }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'set-env'; env: WindoEnvState }\n | { source: typeof WINDO_MSG; dir: 'host'; type: 'invoke-action'; id: string; actionId: string }\n\n/** iframe -> chrome */\nexport type WindoPreviewMessage =\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'ready' }\n | {\n source: typeof WINDO_MSG\n dir: 'preview'\n type: 'manifest'\n title: string\n entries: WindoManifestEntry[]\n groups: WindoGroup[]\n tags: string[]\n contexts: WindoContextMeta[]\n }\n | {\n source: typeof WINDO_MSG\n dir: 'preview'\n type: 'describe'\n id: string\n descriptor: WindoSchemaDescriptor\n props: WindoPropDoc[]\n variants: WindoVariantMeta[]\n defaults: unknown\n code: string | null\n }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'parse-ok'; id: string }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'parse-error'; id: string; errors: WindoFieldError[] }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'log'; entry: WindoLogEntry }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'state'; id: string; state: Record<string, unknown>; actions: { id: string; disabled: boolean }[] }\n | { source: typeof WINDO_MSG; dir: 'preview'; type: 'render-error'; id: string; message: string; stack?: string }\n\nexport type WindoMessage = WindoHostMessage | WindoPreviewMessage\n\nexport function isWindoMessage(data: unknown): data is WindoMessage {\n return typeof data === 'object' && data !== null && (data as { source?: unknown }).source === WINDO_MSG\n}\n\nexport function isHostMessage(msg: WindoMessage): msg is WindoHostMessage {\n return msg.dir === 'host'\n}\n\nexport function isPreviewMessage(msg: WindoMessage): msg is WindoPreviewMessage {\n return msg.dir === 'preview'\n}\n","// Core type system for windo. Everything — the authoring API, the iframe\n// preview runtime, the chrome UI, and the postMessage protocol — is typed\n// against the contracts in this file.\n\nimport type { ComponentType, ReactNode } from 'react'\nimport type { z } from 'zod'\n\n/* ------------------------------------------------------------------ *\n * Primitives\n * ------------------------------------------------------------------ */\n\nexport type WindoStatus = 'stable' | 'beta' | 'deprecated'\n\nexport const WINDO_STATUSES: readonly WindoStatus[] = ['stable', 'beta', 'deprecated']\n\n/** Anchor a component within the canvas frame. */\nexport type WindoPlacementBase = 'center' | 'fill' | 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n\n/**\n * Where a component renders inside the canvas frame. Placements render flush by\n * default; append `-padding` to inset the component from the frame edges\n * (e.g. `top` sits flush at the top, `top-padding` adds breathing room).\n */\nexport type WindoPlacement = WindoPlacementBase | `${WindoPlacementBase}-padding`\n\nconst WINDO_PLACEMENT_BASE: readonly WindoPlacementBase[] = ['center', 'fill', 'top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right']\n\nexport const WINDO_PLACEMENTS: readonly WindoPlacement[] = [...WINDO_PLACEMENT_BASE, ...WINDO_PLACEMENT_BASE.map(p => `${p}-padding` as WindoPlacement)]\n\n/** A configured group. Components reference a group by its `slug`. */\nexport interface WindoGroup {\n name: string\n slug: string\n description?: string\n}\n\n/* ------------------------------------------------------------------ *\n * Context system\n * ------------------------------------------------------------------ */\n\nexport type WindoControlType = 'enum' | 'boolean' | 'string' | 'number'\n\n/** A single ambient control: a value plus the metadata to render its toggle. */\nexport interface WindoControlSpec<T = unknown> {\n type: WindoControlType\n label?: string\n default: T\n options?: readonly string[]\n min?: number\n max?: number\n step?: number\n}\n\nexport type WindoControlMap = Record<string, WindoControlSpec>\n\n/** Resolve a control map to the value object it produces. */\nexport type WindoControlValues<C extends WindoControlMap> = {\n [K in keyof C]: C[K] extends WindoControlSpec<infer T> ? T : never\n}\n\n/**\n * A named context. Two capabilities, either or both:\n * - `controls` → ambient values + UI toggles (free, no opt-in needed)\n * - `provider` → a React wrapper, mounted inside the iframe around components\n * that opt in via `uses`\n */\nexport interface WindoContextDefinition<Values = unknown, Provided = Values> {\n label?: string\n description?: string\n controls?: WindoControlMap\n provider?: ComponentType<{ children: ReactNode; values: Values; ctx: WindoRenderContext }>\n /** Derive the value exposed on `ctx.contexts[name]`. Defaults to the control values. */\n resolve?: (values: Values, ctx: WindoRenderContext) => Provided\n}\n\n// Contexts are each generic over a different control map `C`; TS has no existential\n// to hold `exists C. WindoContextDefinition<WindoControlValues<C>>`. `Values` sits in\n// contravariant positions (provider props, resolve arg) so no precise common supertype\n// exists — `any` is the single, contained variance hatch for this heterogeneous\n// registry. Mirrors WindoModule. Definition sites stay precise; this only erases at the\n// point of collection.\n// biome-ignore lint/suspicious/noExplicitAny: existential variance hatch for the heterogeneous context registry\nexport type WindoContextMap = Record<string, WindoContextDefinition<any, any>>\n\n/* ------------------------------------------------------------------ *\n * Render-time context\n * ------------------------------------------------------------------ */\n\nexport interface WindoViewport {\n width: number\n height: number\n name: 'mobile' | 'tablet' | 'desktop'\n}\n\n/** Console channel: `ctx.logger.log(…)` posts an entry to the chrome's Console tab. */\nexport interface WindoLogger {\n log: (...args: unknown[]) => void\n}\n\n/** How an action fires. `click` renders a toolbar button; the rest bind to the stage's pointer events. */\nexport type WindoActionTrigger = 'click' | 'enter' | 'exit' | 'hover'\n\nexport const WINDO_ACTION_TRIGGERS: readonly WindoActionTrigger[] = ['click', 'enter', 'exit', 'hover']\n\n/**\n * An out-of-band action that drives a component's state. `click` actions render as\n * toolbar buttons; `enter`/`exit`/`hover` bind to the stage's pointer events. `run`\n * receives the live ctx and an `active` flag — for `hover` it is `true` on\n * pointer-enter and `false` on pointer-leave; for the others it is always `true`.\n */\nexport interface WindoAction<State = unknown> {\n label: string\n /** Defaults to `click`. */\n on?: WindoActionTrigger\n run: (ctx: WindoRenderContext<State>, active: boolean) => void\n /** Greys out a `click` action's toolbar button. Evaluated against the live state. */\n disabled?: (ctx: WindoRenderContext<State>) => boolean\n}\n\n/** Live environment handed to every render-time function inside the iframe. */\nexport interface WindoRenderContext<State = unknown> {\n colorScheme: 'light' | 'dark'\n viewport: WindoViewport\n reducedMotion: boolean\n direction: 'ltr' | 'rtl'\n locale: string\n logger: WindoLogger\n /** Current component-local state, typed by the windo's `State` generic. */\n state: State\n /** Merge a patch into the component-local state and re-render. */\n setState: (patch: Partial<State>) => void\n /** Resolved values of opted-in contexts, keyed by context name. */\n contexts: Record<string, unknown>\n}\n\n/* ------------------------------------------------------------------ *\n * Authoring API\n * ------------------------------------------------------------------ */\n\n/** A variant: a label plus a partial prop patch. Renders in the gallery and is click-to-apply. */\nexport interface WindoVariant<Props> {\n label: string\n props: Partial<Props>\n}\n\n/** A row in the authored Props documentation table. */\nexport interface WindoPropDoc {\n name: string\n type: string\n default?: string\n desc?: string\n}\n\nexport type WindoDefaultProps<Props, State = unknown> = Props | ((ctx: WindoRenderContext<State>) => Props)\n\n/**\n * The object returned by a `windo(...)` factory.\n *\n * Keystone rule: the surrounding factory runs ONCE (definition-time) for static\n * fields (title, group, schema). Every function field below — `defaultProps`,\n * `actions`, `providers`, `component` — runs at render-time with the live `ctx`.\n * Never close over live values in the static factory body.\n */\nexport interface WindoDefinition<Props = unknown, State = unknown, GroupSlug extends string = string, Tag extends string = string> {\n title: string\n group: GroupSlug\n /** Tags this component carries. Each must be one of the config's declared `tags`. Drives the sidebar's tag filter. */\n tags?: Tag[]\n status?: WindoStatus\n description?: string\n deprecation?: string\n placement?: WindoPlacement\n /** Initial component-local state. Its shape is the `State` generic; `ctx.state`/`ctx.setState` derive from it. */\n state?: State\n /** Out-of-band actions that drive state: toolbar buttons (`click`) and stage pointer triggers (`enter`/`exit`/`hover`). */\n actions?: WindoAction<State>[]\n /** zod schema: validator + parser for the JSON-editable prop subset. `z.output ⊆ Props`. */\n configurableProps?: z.ZodType\n /** Full props incl. functions/JSX. The editor's JSON overrides merge on top. */\n defaultProps: WindoDefaultProps<Props, State>\n /** Names of provider contexts this component opts into. */\n uses?: string[]\n variants?: WindoVariant<Props>[]\n /** Authored documentation table (not derived from the schema). */\n props?: WindoPropDoc[]\n /** Optional authored code snippet for the Code tab. */\n code?: (values: Props) => string\n /** A local provider wrapping just this windo (in addition to `uses`). */\n providers?: ComponentType<{ children: ReactNode; ctx: WindoRenderContext<State> }>\n component: (props: Props, ctx: WindoRenderContext<State>) => ReactNode\n}\n\n/** Argument handed to the `windo(w => ...)` factory. */\nexport interface WindoFactoryArg<Groups extends readonly WindoGroup[], Contexts extends WindoContextMap, Tags extends readonly string[] = readonly string[]> {\n /** Configured groups keyed by slug. */\n groups: Record<Groups[number]['slug'], WindoGroup>\n contexts: Contexts\n /** The config's declared tags, in declaration order. */\n tags: Tags\n}\n\n/**\n * The default export of a `*.windo.tsx` file. A branded, lazily-resolved\n * definition — the runtime calls `resolve(w)` with the config-derived factory arg.\n */\n// biome-ignore lint/suspicious/noExplicitAny: variance escape hatch for the heterogeneous WindoModule registry\nexport interface WindoModule<Props = any, State = any> {\n readonly __windo: true\n resolve: (w: WindoFactoryArg<readonly WindoGroup[], WindoContextMap>) => WindoDefinition<Props, State>\n}\n\n/* ------------------------------------------------------------------ *\n * Config\n * ------------------------------------------------------------------ */\n\nexport interface WindoConfig<Groups extends readonly WindoGroup[] = readonly WindoGroup[], Contexts extends WindoContextMap = WindoContextMap, Tags extends readonly string[] = readonly string[]> {\n /** Configured groups. A component's `group` must be one of these slugs. */\n groups: Groups\n /** Named contexts available to components. */\n contexts?: Contexts\n /** The set of tags components may be assigned. A component's `tags` must be drawn from this list; the sidebar filters by them. */\n tags?: Tags\n /** Glob(s) for discovery, relative to project root. Default `**\\/*.windo.tsx`. */\n include?: string | string[]\n /** Title shown in the workbench chrome. */\n title?: string\n}\n\n/* ------------------------------------------------------------------ *\n * Schema descriptor (crosses the iframe boundary; renders the controls)\n * ------------------------------------------------------------------ */\n\nexport type WindoControlKind = 'string' | 'number' | 'boolean' | 'enum' | 'date' | 'array' | 'object' | 'unknown'\n\nexport interface WindoControlDescriptor {\n key: string\n kind: WindoControlKind\n optional: boolean\n options?: string[]\n min?: number\n max?: number\n description?: string\n}\n\nexport interface WindoSchemaDescriptor {\n fields: WindoControlDescriptor[]\n}\n\n/* ------------------------------------------------------------------ *\n * Runtime manifest + protocol payloads\n * ------------------------------------------------------------------ */\n\n/** Serialisable metadata for one action — drives the canvas toolbar. */\nexport interface WindoActionMeta {\n id: string\n label: string\n on: WindoActionTrigger\n}\n\n/** Static, serialisable metadata for one windo — drives the sidebar. */\nexport interface WindoManifestEntry {\n id: string\n title: string\n group: string\n tags: string[]\n status: WindoStatus\n description?: string\n deprecation?: string\n placement: WindoPlacement\n uses: string[]\n hasVariants: boolean\n actions: WindoActionMeta[]\n hasState: boolean\n}\n\nexport interface WindoVariantMeta {\n label: string\n props: Record<string, unknown>\n}\n\n/** Ambient environment pushed from the chrome down into the iframe. */\nexport interface WindoEnvState {\n colorScheme: 'light' | 'dark'\n viewport: WindoViewport\n reducedMotion: boolean\n direction: 'ltr' | 'rtl'\n locale: string\n /** Per-context control values, keyed by context name then control key. */\n contexts: Record<string, Record<string, unknown>>\n}\n\nexport interface WindoLogEntry {\n ts: number\n args: unknown[]\n}\n\nexport interface WindoFieldError {\n path: string\n message: string\n}\n\n/* ------------------------------------------------------------------ *\n * Context metadata (serialisable; drives the chrome's Context panel)\n * ------------------------------------------------------------------ */\n\nexport interface WindoContextControlMeta {\n key: string\n type: WindoControlType\n label?: string\n options?: string[]\n default: unknown\n min?: number\n max?: number\n step?: number\n}\n\nexport interface WindoContextMeta {\n name: string\n label?: string\n description?: string\n /** True when the context contributes controls (ambient values). */\n ambient: boolean\n /** True when the context mounts a provider (opt-in via `uses`). */\n hasProvider: boolean\n controls: WindoContextControlMeta[]\n}\n"],"mappings":";AAeO,SAAS,kBACd,QACiD;AACjD,WAAS,MACP,SAC2B;AAC3B,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS,OAAK,QAAQ,CAAuD;AAAA,IAC/E;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,MAAM;AACzB;AAYO,SAAS,cAA2E,KAM/B;AAC1D,SAAO;AACT;AAQO,SAAS,oBAAuB;AACrC,SAAO,CAAkC,WAAiB;AAC5D;;;ACjDA,SAAS,SAAS;AAmBX,SAAS,eAAe,QAA6D;AAC1F,MAAI,CAAC,OAAQ,QAAO,EAAE,QAAQ,CAAC,EAAE;AACjC,MAAI;AACJ,MAAI;AACF,WAAO,EAAE,aAAa,QAAQ,EAAE,IAAI,SAAS,iBAAiB,MAAM,CAAC;AAAA,EACvE,QAAQ;AACN,WAAO,EAAE,QAAQ,CAAC,EAAE;AAAA,EACtB;AACA,QAAM,OAAO,OAAO,IAAI;AACxB,QAAM,aAAa,KAAK,cAAc,CAAC;AACvC,QAAM,WAAW,IAAI,IAAI,KAAK,YAAY,CAAC,CAAC;AAC5C,QAAM,WAAW,WAAW,MAAM;AAClC,QAAM,SAAmC,OAAO,KAAK,UAAU,EAAE,IAAI,SAAO,UAAU,KAAK,WAAW,GAAG,GAAG,SAAS,IAAI,GAAG,GAAG,SAAS,GAAG,CAAC,CAAC;AAC7I,SAAO,EAAE,OAAO;AAClB;AAIA,SAAS,OAAO,MAA0B;AACxC,MAAI,KAAK,WAAY,QAAO;AAC5B,QAAM,WAAW,KAAK,SAAS,KAAK,SAAS,KAAK;AAClD,MAAI,UAAU;AACZ,UAAM,YAAY,SAAS,KAAK,OAAK,EAAE,UAAU;AACjD,QAAI,UAAW,QAAO;AAAA,EACxB;AACA,SAAO;AACT;AAEA,SAAS,UAAU,KAAa,MAAgB,YAAqB,SAAoD;AACvH,QAAM,QAAQ,iBAAiB,IAAI;AACnC,MAAI,OAAO,OAAO,KAAK;AAGvB,MAAI,YAAY,SAAS,aAAa,YAAY,QAAS,QAAO;AAClE,QAAM,aAAqC;AAAA,IACzC;AAAA,IACA;AAAA,IACA,UAAU,CAAC,cAAc,WAAW,IAAI;AAAA,EAC1C;AACA,MAAI,MAAM,YAAa,YAAW,cAAc,MAAM;AACtD,QAAM,UAAU,YAAY,KAAK;AACjC,MAAI,QAAS,YAAW,UAAU;AAClC,MAAI,OAAO,MAAM,YAAY,SAAU,YAAW,MAAM,MAAM;AAC9D,MAAI,OAAO,MAAM,YAAY,SAAU,YAAW,MAAM,MAAM;AAC9D,SAAO;AACT;AAEA,SAAS,WAAW,MAAyB;AAC3C,QAAM,WAAW,KAAK,SAAS,KAAK;AACpC,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,SAAS,KAAK,OAAK,EAAE,SAAS,MAAM;AAC7C;AAGA,SAAS,iBAAiB,MAA0B;AAClD,QAAM,WAAW,KAAK,SAAS,KAAK;AACpC,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,UAAU,SAAS,OAAO,OAAK,EAAE,SAAS,MAAM;AACtD,MAAI,QAAQ,WAAW,EAAG,QAAO,QAAQ,CAAC;AAC1C,SAAO;AACT;AAEA,SAAS,YAAY,MAAsC;AACzD,MAAI,MAAM,QAAQ,KAAK,IAAI,EAAG,QAAO,KAAK,KAAK,IAAI,OAAK,OAAO,CAAC,CAAC;AACjE,QAAM,WAAW,KAAK,SAAS,KAAK;AACpC,MAAI,UAAU,MAAM,OAAK,EAAE,UAAU,MAAS,GAAG;AAC/C,WAAO,SAAS,IAAI,OAAK,OAAO,EAAE,KAAK,CAAC;AAAA,EAC1C;AACA,SAAO;AACT;AAEA,SAAS,OAAO,MAAkC;AAChD,MAAI,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,OAAO,MAAM,OAAK,EAAE,UAAU,MAAS,KAAK,OAAQ,QAAO;AACjG,MAAI,KAAK,WAAW,eAAe,KAAK,WAAW,OAAQ,QAAO;AAClE,QAAM,OAAO,MAAM,QAAQ,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK,OAAK,MAAM,MAAM,IAAI,KAAK;AACjF,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAWA,SAAS,OAAO,MAAuC;AACrD,QAAM,IAAI;AACV,MAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,SAAO,EAAE,OAAO,EAAE,MAAM;AAC1B;AAEA,IAAM,eAAe,oBAAI,IAAI,CAAC,YAAY,YAAY,WAAW,YAAY,SAAS,YAAY,eAAe,MAAM,CAAC;AAGxH,SAAS,aAAa,MAAuC;AAC3D,MAAI,MAAM,OAAO,IAAI;AACrB,MAAI,QAAQ;AACZ,SAAO,OAAO,IAAI,aAAa,aAAa,IAAI,IAAI,QAAQ,EAAE,KAAK,UAAU,IAAI;AAC/E,UAAM,OAAO,IAAI,SAAS;AAAA,EAC5B;AACA,SAAO;AACT;AAEA,SAAS,WAAW,MAAwD;AAC1E,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAGA,SAAS,WAAW,QAAqD;AACvE,QAAM,QAAS,OAA+C;AAC9D,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO,CAAC;AACjD,QAAM,MAAwC,CAAC;AAC/C,aAAW,OAAO,OAAO,KAAK,KAAK,GAAG;AACpC,UAAM,OAAO,WAAW,aAAa,MAAM,GAAG,CAAC,GAAG,IAAI;AACtD,QAAI,KAAM,KAAI,GAAG,IAAI;AAAA,EACvB;AACA,SAAO;AACT;;;ACjLO,IAAM,YAAY;AA0ClB,SAAS,eAAe,MAAqC;AAClE,SAAO,OAAO,SAAS,YAAY,SAAS,QAAS,KAA8B,WAAW;AAChG;AAEO,SAAS,cAAc,KAA4C;AACxE,SAAO,IAAI,QAAQ;AACrB;AAEO,SAAS,iBAAiB,KAA+C;AAC9E,SAAO,IAAI,QAAQ;AACrB;;;AC9CO,IAAM,iBAAyC,CAAC,UAAU,QAAQ,YAAY;AAYrF,IAAM,uBAAsD,CAAC,UAAU,QAAQ,OAAO,UAAU,QAAQ,SAAS,YAAY,aAAa,eAAe,cAAc;AAEhK,IAAM,mBAA8C,CAAC,GAAG,sBAAsB,GAAG,qBAAqB,IAAI,OAAK,GAAG,CAAC,UAA4B,CAAC;AA2EhJ,IAAM,wBAAuD,CAAC,SAAS,SAAS,QAAQ,OAAO;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@westopp/windo",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Zero-infra component canvas — point it at *.windo.tsx files and get a resizable preview with live props, variants, and context",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -57,7 +57,8 @@
57
57
  },
58
58
  "files": [
59
59
  "dist",
60
- "src"
60
+ "src",
61
+ "!src/**/*.test.*"
61
62
  ],
62
63
  "scripts": {
63
64
  "build": "tsup",
@@ -11,7 +11,7 @@ import { useWindoBridge } from './bridge'
11
11
  import { Canvas } from './Canvas'
12
12
  import { Inspector } from './Inspector'
13
13
  import { Icons } from './icons'
14
- import type { CanvasGridOpts, ChromeEnv, InspectorPosition, ThemeMode } from './internal-types'
14
+ import type { CanvasGridOpts, ChromeEnv, InspectorPosition, TagMatch, ThemeMode } from './internal-types'
15
15
  import { CANVAS_GRID_DEFAULTS } from './internal-types'
16
16
  import { loadJSON, loadString, saveJSON, saveString } from './persist'
17
17
  import { Sidebar } from './Sidebar'
@@ -33,6 +33,18 @@ function viewportName(width: number): 'mobile' | 'tablet' | 'desktop' {
33
33
  return width < 640 ? 'mobile' : width < 1024 ? 'tablet' : 'desktop'
34
34
  }
35
35
 
36
+ // windo brand mark — red tile, yellow "wn". Fixed colours: brand, not themed.
37
+ function WindoMark() {
38
+ return (
39
+ <svg className="wb-logo-mark" viewBox="0 0 32 32" width="22" height="22" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
40
+ <rect width="32" height="32" rx="9" fill="#d83a2e" />
41
+ <text x="16" y="22" textAnchor="middle" fontSize="16" fontWeight={800} fontFamily="system-ui, -apple-system, Helvetica, Arial, sans-serif" fill="#f5c518">
42
+ wn
43
+ </text>
44
+ </svg>
45
+ )
46
+ }
47
+
36
48
  export function App() {
37
49
  const iframeRef = useRef<HTMLIFrameElement | null>(null)
38
50
  const bridge = useWindoBridge(iframeRef)
@@ -40,6 +52,11 @@ export function App() {
40
52
  const [theme, setTheme] = useState<ThemeMode>(() => loadString('windo:theme', 'light') as ThemeMode)
41
53
  const [selected, setSelected] = useState<string | null>(() => loadJSON<string | null>('windo:selected', null))
42
54
  const [query, setQuery] = useState('')
55
+ const [selectedTags, setSelectedTags] = useState<string[]>(() => {
56
+ const raw = loadJSON<unknown>('windo:tags', [])
57
+ return Array.isArray(raw) ? raw.filter((t): t is string => typeof t === 'string') : []
58
+ })
59
+ const [tagMatch, setTagMatch] = useState<TagMatch>(() => (loadJSON<unknown>('windo:tag-match', 'any') === 'all' ? 'all' : 'any'))
43
60
  const [navCollapsed, setNavCollapsed] = useState(() => loadJSON<boolean>('windo:nav-collapsed', false))
44
61
  const [inspCollapsed, setInspCollapsed] = useState(() => loadJSON<boolean>('windo:insp-collapsed', false))
45
62
  const inspectorPosition: InspectorPosition = 'right'
@@ -78,6 +95,8 @@ export function App() {
78
95
  saveString('windo:theme', theme)
79
96
  }, [theme])
80
97
  useEffect(() => saveJSON('windo:selected', selected), [selected])
98
+ useEffect(() => saveJSON('windo:tags', selectedTags), [selectedTags])
99
+ useEffect(() => saveJSON('windo:tag-match', tagMatch), [tagMatch])
81
100
  useEffect(() => saveJSON('windo:nav-collapsed', navCollapsed), [navCollapsed])
82
101
  useEffect(() => saveJSON('windo:insp-collapsed', inspCollapsed), [inspCollapsed])
83
102
  useEffect(() => saveJSON('windo:width', width), [width])
@@ -272,7 +291,7 @@ export function App() {
272
291
  <div className="wb-app" style={accentVars} data-screen-label="Workbench">
273
292
  <header className="wb-topbar">
274
293
  <span className="wb-logo">
275
- <span className="wb-logo-mark">{title.charAt(0).toUpperCase()}</span>
294
+ <WindoMark />
276
295
  {title}
277
296
  <span className="wb-logo-sub">/ Workbench</span>
278
297
  </span>
@@ -290,7 +309,21 @@ export function App() {
290
309
  </header>
291
310
 
292
311
  <div className="wb-main">
293
- <Sidebar groups={bridge.groups} manifest={bridge.manifest} selected={selected} onSelect={setSelected} query={query} setQuery={setQuery} collapsed={navCollapsed} onToggle={toggleNav} />
312
+ <Sidebar
313
+ groups={bridge.groups}
314
+ tags={bridge.tags}
315
+ manifest={bridge.manifest}
316
+ selected={selected}
317
+ onSelect={setSelected}
318
+ query={query}
319
+ setQuery={setQuery}
320
+ selectedTags={selectedTags}
321
+ setSelectedTags={setSelectedTags}
322
+ tagMatch={tagMatch}
323
+ setTagMatch={setTagMatch}
324
+ collapsed={navCollapsed}
325
+ onToggle={toggleNav}
326
+ />
294
327
  <div className="wb-center">
295
328
  {inspectorRight ? (
296
329
  <div className="wb-center-row">