@pipelex/mthds-form 0.30.0 → 0.32.0

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.
@@ -1,13 +1,16 @@
1
1
  "use client";
2
- import { cn, headingClass, checkedAccentClass, focusOutlineClass, actionShadowClass, panelSurfaceClass, panelRaisedShadowClass, linkTextClass, selectedBorderClass, selectedFillClass, fieldControlClass, ResultRoot, ResultField, FieldRenderer, enumLabeler, useFieldPresentation, ToggleGroup, ToggleGroupItem, selectedFillStrongClass, Switch, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, panelFlatShadowClass } from '../chunk-JCFALTTY.js';
2
+ import { cn, headingClass, checkedAccentClass, focusOutlineClass, useResolveUrl, panelSurfaceClass, panelFlatShadowClass, linkTextClass, actionShadowClass, panelRaisedShadowClass, fieldControlClass, ResultRoot, ResultField, FieldRenderer, enumLabeler, useFieldPresentation, ToggleGroup, ToggleGroupItem, selectedFillStrongClass, Switch, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, selectedBorderClass, selectedFillClass, isThenable } from '../chunk-PCNFELNT.js';
3
+ import { baseCatalog, catalog, frameComponents, resultFieldAtPath, segmentsUnder, RESULT_ROOT, inputFieldAtPath } from '../chunk-VBXFGUCG.js';
4
+ export { COMPONENT_NAMES, COMPONENT_RENDERINGS, COMPOSER_COMPONENTS, CUSTOM_COMPONENTS, DESIGNER_BRIEF_CONCEPT, DESIGNER_CATALOG_CONCEPT, FRAMES, FRAME_COMPONENTS, ICON_NAMES, INPUTS_ROOT, METRIC_FORMATS, PICKED_SHADCN, PRODUCT_COMPONENTS, PROMPT_HASH, REPAIR_ATTEMPTS, RESULT_COMPONENTS, RESULT_ROOT, absoluteHatchPath, catalog, componentNamesFor, designerCatalog, fieldAtSegments, findFixture, fixtureId, fixtureLabel, formatProblems, frameCatalog, inputBrief, inputFieldAtPath, isDelegatedInput, isDelegatedResult, joinPath, jsonlLines, layoutCheckProblems, layoutFits, layoutProblems, payloadToState, promptHashSubject, repeatBasePathOf, resultBrief, resultFieldAtPath, seedLine, segmentsUnder, specFromJsonl, specToJsonl, validateAgainstCatalog } from '../chunk-VBXFGUCG.js';
3
5
  import '../chunk-4VAO6BDE.js';
4
6
  export { seedInputs } from '../chunk-VH5CDQDR.js';
5
7
  import '../chunk-L23R3JNQ.js';
6
8
  import '../chunk-56WECFBD.js';
7
9
  import '../chunk-ZX52CQJ4.js';
8
10
  import '../chunk-GHVVULHY.js';
9
- import { isNativeValueNode, readDateContent, formatDateContent, isNativeHtmlNode, isNativeDateNode, isNativeCompositeNode } from '../chunk-QQK6Y55E.js';
10
- export { brandManifestSchema } from '../chunk-QFX332LE.js';
11
+ import { readImageContent, viewableUrl, readDateContent } from '../chunk-QQK6Y55E.js';
12
+ export { brandManifestSchema } from '../chunk-QGFR57OH.js';
13
+ import '../chunk-3GO65HWP.js';
11
14
  import '../chunk-ZB4RYUKV.js';
12
15
  import '../chunk-FABY3DEY.js';
13
16
  import '../chunk-XCO6GTEC.js';
@@ -17,1796 +20,239 @@ import '../chunk-OD3VYXUY.js';
17
20
  import '../chunk-WYH56SOZ.js';
18
21
  import { hasOwnProp } from '../chunk-V3OV2XQT.js';
19
22
  import '../chunk-GKV77RAS.js';
20
- import { defineSchema, defineCatalog, validateSpec, compileSpecStream } from '@json-render/core';
21
- import { z } from 'zod';
22
- import * as React3 from 'react';
23
- import { defineRegistry, useStateValue, useStateStore, useBoundProp, useFieldValidation, useRepeatScope, JSONUIProvider, Renderer } from '@json-render/react';
23
+ import * as React5 from 'react';
24
+ import { defineRegistry, useStateValue, useRepeatScope, useStateStore, useBoundProp, useFieldValidation, JSONUIProvider, Renderer } from '@json-render/react';
24
25
  import { jsx, jsxs } from 'react/jsx-runtime';
25
- import { Send, Pencil, Zap, Lightbulb, Truck, Package, Briefcase, Bookmark, Flag, Shield, Info, AlertCircle, CheckCircle2, ListChecks, Layers, Tag, Phone, Mail, Paperclip, Upload, FileText, BarChart3, TrendingUp, Hash, Percent, Receipt, Coins, Banknote, Wallet, Gift, Ticket, Image, Camera, Music, Coffee, Utensils, Star, Heart, Accessibility, Baby, User, Users, Hourglass, Clock, Calendar, Moon, Sun, Leaf, Mountain, Globe, Landmark, Building2, Home, Hotel, Ship, Car, Train, Plane, Route, MapPin, Map as Map$1, Compass, Rocket, Sparkles } from 'lucide-react';
26
+ import { Clock, ArrowRight, Send, Pencil, Zap, Lightbulb, Truck, Package, Briefcase, Bookmark, Flag, Shield, Info, AlertCircle, CheckCircle2, ListChecks, Layers, Tag, Phone, Mail, Paperclip, Upload, FileText, BarChart3, TrendingUp, Hash, Percent, Receipt, Coins, Banknote, Wallet, Gift, Ticket, Image, Camera, Music, Coffee, Utensils, Star, Heart, Accessibility, Baby, User, Users, Hourglass, Calendar, Moon, Sun, Leaf, Mountain, Globe, Landmark, Building2, Home, Hotel, Ship, Car, Train, Plane, Route, MapPin, Map as Map$1, Compass, Rocket, Sparkles, ChevronDown } from 'lucide-react';
26
27
  import * as TabsPrimitive from '@radix-ui/react-tabs';
28
+ export { createStateStore } from '@json-render/core';
27
29
 
28
- var generativeSchema = defineSchema(
29
- (s) => ({
30
- spec: s.object({
31
- root: s.string(),
32
- elements: s.record(
33
- s.object({
34
- type: s.ref("catalog.components"),
35
- props: s.propsOf("catalog.components"),
36
- children: s.array(s.string()),
37
- slots: { ...s.record(s.array(s.string())), ...s.optional() },
38
- visible: { ...s.any(), ...s.optional() },
39
- repeat: { ...s.any(), ...s.optional() }
40
- })
41
- )
42
- }),
43
- catalog: s.object({
44
- components: s.map({
45
- props: s.zod(),
46
- slots: s.array(s.string()),
47
- description: s.string(),
48
- example: s.any()
49
- }),
50
- actions: s.map({
51
- params: s.zod(),
52
- description: s.string()
53
- })
54
- })
55
- }),
56
- {
57
- // The runtime handles these four without a registry handler; the names and
58
- // the wording are the React schema's, so a model that has seen json-render
59
- // before recognises them. The validator reads them to know which actions
60
- // need no handler, and `designerCatalog()` lists them first, marked
61
- // built-in, so the method's prompt names them beside the catalog's own.
62
- builtInActions: [
63
- {
64
- name: "setState",
65
- description: "Update a value in the state model at the given statePath. Params: { statePath: string, value: any }"
66
- },
67
- {
68
- name: "pushState",
69
- description: "Append an item to an array in state. Params: { statePath: string, value: any, clearStatePath?: string }."
70
- },
71
- {
72
- name: "removeState",
73
- description: "Remove an item from an array in state by index. Params: { statePath: string, index: number }"
74
- },
75
- {
76
- name: "validateForm",
77
- description: "Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation."
78
- }
79
- ]
80
- }
81
- );
82
- var validationCheckSchema = z.array(
83
- z.object({
84
- type: z.string(),
85
- message: z.string(),
86
- args: z.record(z.string(), z.unknown()).optional()
87
- })
88
- ).nullable();
89
- var validateOnSchema = z.enum(["change", "blur", "submit"]).nullable();
90
- var shadcnComponentDefinitions = {
91
- // ── Layout ────────────────────────────────────────────────────────────────
92
- Card: {
93
- props: z.object({
94
- title: z.string().nullable(),
95
- description: z.string().nullable(),
96
- maxWidth: z.enum(["sm", "md", "lg", "full"]).nullable(),
97
- centered: z.boolean().nullable(),
98
- className: z.string().nullable().describe("Additional CSS classes")
99
- }),
100
- slots: ["default"],
101
- description: "Container card for content sections. Use for forms/content boxes, NOT for page headers.",
102
- example: { title: "Overview", description: "Your account summary" }
103
- },
104
- Stack: {
105
- props: z.object({
106
- direction: z.enum(["horizontal", "vertical"]).nullable(),
107
- gap: z.enum(["none", "sm", "md", "lg", "xl"]).nullable(),
108
- align: z.enum(["start", "center", "end", "stretch"]).nullable(),
109
- justify: z.enum(["start", "center", "end", "between", "around"]).nullable(),
110
- className: z.string().nullable().describe("Additional CSS classes")
111
- }),
112
- slots: ["default"],
113
- description: "Flex container for layouts",
114
- example: { direction: "vertical", gap: "md" }
115
- },
116
- Grid: {
117
- props: z.object({
118
- columns: z.number().nullable(),
119
- gap: z.enum(["sm", "md", "lg", "xl"]).nullable(),
120
- className: z.string().nullable().describe("Additional CSS classes")
121
- }),
122
- slots: ["default"],
123
- description: "Grid layout (1-6 columns)",
124
- example: { columns: 3, gap: "md" }
125
- },
126
- Separator: {
127
- props: z.object({
128
- orientation: z.enum(["horizontal", "vertical"]).nullable()
129
- }),
130
- description: "Visual separator line"
131
- },
132
- Collapsible: {
133
- props: z.object({
134
- title: z.string(),
135
- defaultOpen: z.boolean().nullable()
136
- }),
137
- slots: ["default"],
138
- description: "Collapsible section with trigger. Children render inside."
139
- },
140
- // ── Content ───────────────────────────────────────────────────────────────
141
- Heading: {
142
- props: z.object({
143
- text: z.string(),
144
- level: z.enum(["h1", "h2", "h3", "h4"]).nullable()
145
- }),
146
- description: "Heading text (h1-h4)",
147
- example: { text: "Welcome", level: "h1" }
148
- },
149
- Text: {
150
- props: z.object({
151
- text: z.string(),
152
- variant: z.enum(["body", "caption", "muted", "lead", "code"]).nullable()
153
- }),
154
- description: "Paragraph text",
155
- example: { text: "Hello, world!" }
156
- },
157
- Avatar: {
158
- props: z.object({
159
- src: z.string().nullable(),
160
- name: z.string(),
161
- size: z.enum(["sm", "md", "lg"]).nullable()
162
- }),
163
- description: "User avatar with fallback initials",
164
- example: { name: "Jane Doe", size: "md" }
165
- },
166
- Badge: {
167
- props: z.object({
168
- text: z.string(),
169
- variant: z.enum(["default", "secondary", "destructive", "outline"]).nullable()
170
- }),
171
- description: "Status badge",
172
- example: { text: "Active", variant: "default" }
173
- },
174
- Alert: {
175
- props: z.object({
176
- title: z.string(),
177
- message: z.string().nullable(),
178
- type: z.enum(["info", "success", "warning", "error"]).nullable()
179
- }),
180
- description: "Alert banner",
181
- example: {
182
- title: "Note",
183
- message: "Your changes have been saved.",
184
- type: "success"
185
- }
186
- },
187
- Progress: {
188
- props: z.object({
189
- value: z.number(),
190
- max: z.number().nullable(),
191
- label: z.string().nullable()
192
- }),
193
- description: "Progress bar (value 0-100)",
194
- example: { value: 65, max: 100, label: "Upload progress" }
195
- },
196
- // ── Inputs ────────────────────────────────────────────────────────────────
197
- Input: {
198
- props: z.object({
199
- label: z.string(),
200
- name: z.string(),
201
- type: z.enum(["text", "email", "password", "number"]).nullable(),
202
- placeholder: z.string().nullable(),
203
- value: z.string().nullable(),
204
- checks: validationCheckSchema,
205
- validateOn: validateOnSchema
206
- }),
207
- events: ["submit", "focus", "blur"],
208
- description: "Text input field. Use { $bindState } on value for two-way binding. Use checks for validation (e.g. required, email, minLength). validateOn controls timing (default: blur).",
209
- example: {
210
- label: "Email",
211
- name: "email",
212
- type: "email",
213
- placeholder: "you@example.com"
214
- }
215
- },
216
- Textarea: {
217
- props: z.object({
218
- label: z.string(),
219
- name: z.string(),
220
- placeholder: z.string().nullable(),
221
- rows: z.number().nullable(),
222
- value: z.string().nullable(),
223
- checks: validationCheckSchema,
224
- validateOn: validateOnSchema
225
- }),
226
- description: "Multi-line text input. Use { $bindState } on value for binding. Use checks for validation. validateOn controls timing (default: blur)."
227
- },
228
- Select: {
229
- props: z.object({
230
- label: z.string(),
231
- name: z.string(),
232
- options: z.array(z.string().min(1)),
233
- placeholder: z.string().nullable(),
234
- value: z.string().nullable(),
235
- checks: validationCheckSchema,
236
- validateOn: validateOnSchema
237
- }),
238
- events: ["change"],
239
- description: "Dropdown select input. Use { $bindState } on value for binding. Use checks for validation. validateOn controls timing (default: change)."
240
- },
241
- Radio: {
242
- props: z.object({
243
- label: z.string(),
244
- name: z.string(),
245
- options: z.array(z.string().min(1)),
246
- value: z.string().nullable(),
247
- checks: validationCheckSchema,
248
- validateOn: validateOnSchema
249
- }),
250
- events: ["change"],
251
- description: "Radio button group. Use { $bindState } on value for binding. Use checks for validation. validateOn controls timing (default: change)."
252
- },
253
- Checkbox: {
254
- props: z.object({
255
- label: z.string(),
256
- name: z.string(),
257
- checked: z.boolean().nullable(),
258
- checks: validationCheckSchema,
259
- validateOn: validateOnSchema
260
- }),
261
- events: ["change"],
262
- description: "Checkbox input. Use { $bindState } on checked for binding. Use checks for validation. validateOn controls timing (default: change)."
263
- },
264
- Switch: {
265
- props: z.object({
266
- label: z.string(),
267
- name: z.string(),
268
- checked: z.boolean().nullable(),
269
- checks: validationCheckSchema,
270
- validateOn: validateOnSchema
271
- }),
272
- events: ["change"],
273
- description: "Toggle switch. Use { $bindState } on checked for binding. Use checks for validation. validateOn controls timing (default: change)."
274
- },
275
- // ── Actions ───────────────────────────────────────────────────────────────
276
- Button: {
277
- props: z.object({
278
- label: z.string(),
279
- variant: z.enum(["primary", "secondary", "danger"]).nullable(),
280
- disabled: z.boolean().nullable()
281
- }),
282
- events: ["press"],
283
- description: "Clickable button. Bind on.press for handler.",
284
- example: { label: "Submit", variant: "primary" }
285
- }
286
- };
287
-
288
- // src/generative/components.ts
289
- var shadcn = shadcnComponentDefinitions;
290
- var PICKED_SHADCN = [
291
- "Card",
292
- "Stack",
293
- "Grid",
294
- "Separator",
295
- "Heading",
296
- "Text",
297
- "Badge",
298
- "Alert",
299
- "Collapsible",
300
- "Progress",
301
- "Avatar",
302
- "Input",
303
- "Textarea",
304
- "Select",
305
- "Radio",
306
- "Checkbox",
307
- "Switch",
308
- "Button"
309
- ];
310
- var CUSTOM_COMPONENTS = [
311
- "Split",
312
- "Tabs",
313
- "Steps",
314
- "Icon",
315
- "Segmented",
316
- "NumberInput",
317
- "MthdsField",
318
- "MthdsResult",
319
- "DataTable",
320
- "Metric"
321
- ];
322
- var METRIC_FORMATS = ["plain", "integer", "decimal", "compact"];
323
- var ICON_NAMES = [
324
- "Sparkles",
325
- "Rocket",
326
- "Compass",
327
- "Map",
328
- "MapPin",
329
- "Route",
330
- "Plane",
331
- "Train",
332
- "Car",
333
- "Ship",
334
- "Hotel",
335
- "Home",
336
- "Building2",
337
- "Landmark",
338
- "Globe",
339
- "Mountain",
340
- "Leaf",
341
- "Sun",
342
- "Moon",
343
- "Calendar",
344
- "Clock",
345
- "Hourglass",
346
- "Users",
347
- "User",
348
- "Baby",
349
- "Accessibility",
350
- "Heart",
351
- "Star",
352
- "Utensils",
353
- "Coffee",
354
- "Music",
355
- "Camera",
356
- "Image",
357
- "Ticket",
358
- "Gift",
359
- "Wallet",
360
- "Banknote",
361
- "Coins",
362
- "Receipt",
363
- "Percent",
364
- "Hash",
365
- "TrendingUp",
366
- "BarChart3",
367
- "FileText",
368
- "Upload",
369
- "Paperclip",
370
- "Mail",
371
- "Phone",
372
- "Tag",
373
- "Layers",
374
- "ListChecks",
375
- "CheckCircle2",
376
- "AlertCircle",
377
- "Info",
378
- "Shield",
379
- "Flag",
380
- "Bookmark",
381
- "Briefcase",
382
- "Package",
383
- "Truck",
384
- "Lightbulb",
385
- "Zap",
386
- "Pencil",
387
- "Send"
388
- ];
389
- var components = {
390
- // ── Layout (shadcn) ────────────────────────────────────────────────────
391
- Card: {
392
- ...shadcn.Card,
393
- props: shadcn.Card.props.omit({ className: true, maxWidth: true, centered: true }),
394
- description: 'Container card for content sections. Use for forms/content boxes, NOT for page headers. Its optional "title" renders as an h3, so a titled Card sits under an h2.'
395
- },
396
- Stack: { ...shadcn.Stack, props: shadcn.Stack.props.omit({ className: true }) },
397
- Grid: { ...shadcn.Grid, props: shadcn.Grid.props.omit({ className: true }) },
398
- Separator: shadcn.Separator,
399
- Collapsible: shadcn.Collapsible,
400
- // ── Content (shadcn) ───────────────────────────────────────────────────
401
- Heading: shadcn.Heading,
402
- Text: shadcn.Text,
403
- Badge: shadcn.Badge,
404
- Alert: shadcn.Alert,
405
- Progress: shadcn.Progress,
406
- Avatar: {
407
- ...shadcn.Avatar,
408
- description: `A round avatar showing the initials of "name" - bind it to a person's name in the state ({ "$state" } or { "$item" }); never set "src".`
409
- },
410
- // ── Inputs (shadcn) ────────────────────────────────────────────────────
411
- Input: shadcn.Input,
412
- Textarea: shadcn.Textarea,
413
- Select: shadcn.Select,
414
- Radio: shadcn.Radio,
415
- Checkbox: shadcn.Checkbox,
416
- Switch: shadcn.Switch,
417
- Button: shadcn.Button,
418
- // ── Layout (ours) ──────────────────────────────────────────────────────
419
- Split: {
420
- props: z.object({
421
- ratio: z.enum(["1:1", "1:2", "2:1"]).nullable(),
422
- gap: z.enum(["md", "lg", "xl"]).nullable()
423
- }),
424
- slots: ["default"],
425
- description: 'Two columns side by side, from exactly two children: the first is the left column. "1:2" puts a narrow rail (a summary, the essentials, the run) beside the work; "2:1" the reverse; "1:1" two halves. Columns stack on a narrow screen.',
426
- example: { ratio: "1:2", gap: "lg" }
427
- },
428
- Tabs: {
429
- props: z.object({
430
- tabs: z.array(z.object({ label: z.string(), value: z.string() })).min(2)
431
- }),
432
- slots: ["default"],
433
- description: "Concerns of equal weight, one visible at a time. Exactly one child per tab, in the same order - each child is that tab's panel (usually a Stack). The first tab opens. The active tab is the component's own state, never /state.",
434
- example: {
435
- tabs: [
436
- { label: "The trip", value: "trip" },
437
- { label: "Who is going", value: "people" }
438
- ]
439
- }
440
- },
441
- Steps: {
442
- props: z.object({
443
- steps: z.array(z.string()).min(2),
444
- nextLabel: z.string().nullable(),
445
- backLabel: z.string().nullable()
446
- }),
447
- slots: ["default"],
448
- description: "A journey with a natural order: a numbered indicator, one panel at a time, and its own Back and Next. Exactly one child per step, in order - each child is that step's panel. Put the Button that runs inside the LAST panel; Next disappears there. The current step is the component's own state, never /state.",
449
- example: { steps: ["Where", "Who", "How"], nextLabel: "Continue" }
450
- },
451
- Icon: {
452
- props: z.object({
453
- name: z.enum(ICON_NAMES),
454
- size: z.enum(["sm", "md", "lg"]).nullable()
455
- }),
456
- slots: [],
457
- description: "A decorative icon, one of the listed lucide names, to give a section or a figure a face: put it in a horizontal Stack beside a Heading or a Metric. It carries no meaning on its own and no text.",
458
- example: { name: "Plane", size: "md" }
459
- },
460
- // ── Inputs (ours) ──────────────────────────────────────────────────────
461
- Segmented: {
462
- props: z.object({
463
- label: z.string(),
464
- name: z.string(),
465
- options: z.array(z.string().min(1)).min(2).max(6),
466
- value: z.string().nullable()
467
- }),
468
- slots: [],
469
- description: `A choice of two to six options as a row of pills, one selected - the app's answer to a short Select. "options" are EXACTLY the brief's choices, spelled exactly; bind "value" with { "$bindState": "/inputs/..." }.`,
470
- example: {
471
- label: "Pace",
472
- name: "pace",
473
- options: ["slow", "balanced", "packed"],
474
- value: { $bindState: "/inputs/request/pace" }
475
- }
476
- },
477
- NumberInput: {
478
- props: z.object({
479
- label: z.string(),
480
- name: z.string(),
481
- value: z.number().nullable(),
482
- placeholder: z.string().nullable(),
483
- unit: z.string().nullable(),
484
- min: z.number().nullable(),
485
- max: z.number().nullable(),
486
- step: z.number().nullable()
487
- }),
488
- slots: [],
489
- description: 'A figure the person types: writes a NUMBER to the bound path, which Input type "number" does not (it writes text). Bind "value" with { "$bindState": "/inputs/..." }. "unit" is a short suffix ONLY when the brief states one; "min", "max" and "step" only when the brief states them.',
490
- example: {
491
- label: "Budget",
492
- name: "budget",
493
- value: { $bindState: "/inputs/request/budget" },
494
- placeholder: "2500"
495
- }
496
- },
497
- // ── The escape hatches and the bound equivalents (ours) ────────────────
498
- MthdsField: {
499
- props: z.object({
500
- path: z.string()
501
- }),
502
- slots: [],
503
- description: `The kernel's own control for ONE input, bound two-way at "path" (a /inputs path from the brief). Use it for every path the brief marks as delegated - a file, a date, a list, a structure you would rather not lay out - and for any scalar you would rather not style. It renders its own label and description. Inside a repeat, "path" is the item's field name relative to the item ("lines"), never an $item expression.`,
504
- example: { path: "/inputs/source" }
505
- },
506
- MthdsResult: {
507
- props: z.object({
508
- path: z.string(),
509
- hideLabel: z.boolean().nullable()
510
- }),
511
- slots: [],
512
- description: `The kernel's own read-only rendering of ONE result subtree at "path" (a /result path from the brief). Use it for every path the brief marks as delegated - a file, a date, a document, prose, markup - and for any structure you choose not to lay out. It renders its own label unless hideLabel is true. Inside a repeat, "path" is the item's field name relative to the item ("teams"), never an $item expression - the way to delegate one subtree of every item.`,
513
- example: { path: "/result/issued_on" }
514
- },
515
- DataTable: {
516
- props: z.object({
517
- rows: z.any(),
518
- columns: z.array(z.object({ path: z.string(), label: z.string() })),
519
- caption: z.string().nullable()
520
- }),
521
- slots: [],
522
- description: 'A table over a list of structures. "rows" MUST be { "$state": "/path/to/the/list" } (never inline rows); each column names a field of one item by its relative path ("label", "unit_price") and the header to show for it.',
523
- example: {
524
- rows: { $state: "/result/lines" },
525
- columns: [
526
- { path: "label", label: "Item" },
527
- { path: "quantity", label: "Qty" }
528
- ]
529
- }
530
- },
531
- Metric: {
532
- props: z.object({
533
- label: z.string(),
534
- value: z.any(),
535
- unit: z.string().nullable(),
536
- format: z.enum(METRIC_FORMATS).nullable()
537
- }),
538
- slots: [],
539
- description: 'A labelled figure - the one number a reader looks for first. "value" is bound with { "$state": "/path" } (never inline); "unit" is a short suffix and ONLY one the brief states (a currency the structure carries, "%" for a rate, "M" when the brief says "in millions") - never invent a currency the state does not carry; "format" is plain, integer, decimal (two places) or compact.',
540
- example: { label: "Total", value: { $state: "/result/total" }, unit: "EUR", format: "decimal" }
541
- }
542
- };
543
- var actions = {
544
- run: {
545
- params: z.object({}),
546
- description: "Start the run with the current /inputs. The host handles it; an input page binds it to its one Button after validateForm."
547
- }
548
- };
549
- defineCatalog(generativeSchema, { components, actions });
550
- var generativeComponents = components;
551
- var generativeActions = actions;
552
-
553
- // src/generative/catalog.ts
554
- var PRODUCT_COMPONENTS = [
555
- "AppBar",
556
- "Hero",
557
- "Workspace",
558
- "Section",
559
- "Rail",
560
- "SummaryRow",
561
- "Cta",
562
- "Footer"
563
- ];
564
- var product = {
565
- AppBar: {
566
- props: z.object({
567
- app: z.string(),
568
- links: z.array(z.string()).nullable(),
569
- tag: z.string().nullable()
570
- }),
571
- slots: [],
572
- description: "A bar across the top of the page: the brand's logo, the name of this app beside it, a few muted links, and a small mono tag at the right (the method behind the page). It renders as the page's banner, so at most one.",
573
- example: { app: "Trip planner", links: ["Methods", "Runs"], tag: "plan_trip" }
574
- },
575
- Hero: {
576
- props: z.object({
577
- headline: z.string(),
578
- lede: z.string().nullable(),
579
- eyebrow: z.string().nullable()
580
- }),
581
- slots: [],
582
- description: "An opening: one bold headline that says what happens here, one muted line under it at most, and an optional small eyebrow in the accent colour above. The headline renders as an h1, so at most one on a page.",
583
- example: { headline: "Plan a trip worth taking.", lede: "Tell us where and who is coming." }
584
- },
585
- Workspace: {
586
- props: z.object({ rail: z.enum(["right", "left"]).nullable() }),
587
- slots: ["default"],
588
- description: 'Two children side by side on a wide screen and stacked on a narrow one: the work, and beside it a narrower panel that stays sticky while the work scrolls. Exactly two children; "rail" says which side the panel takes, right by default.',
589
- example: { rail: "right" }
590
- },
591
- Section: {
592
- props: z.object({
593
- number: z.string().nullable(),
594
- title: z.string(),
595
- lede: z.string().nullable()
596
- }),
597
- slots: ["default"],
598
- description: "A titled stage of the work: an optional small mono number in the accent colour, an h2 title, an optional muted line, then its children - flat, spaced, separated from the next Section by a hairline, never boxed.",
599
- example: { number: "01", title: "Where and when" }
600
- },
601
- Rail: {
602
- props: z.object({ title: z.string() }),
603
- slots: ["default"],
604
- description: "A raised, boxed panel: a small uppercase h2 title, then its children in a column. Made to stand beside the work as the panel of a Workspace, and the one boxed surface in this vocabulary.",
605
- example: { title: "Your trip" }
606
- },
607
- SummaryRow: {
608
- props: z.object({
609
- label: z.string(),
610
- value: z.any().nullable(),
611
- detail: z.any().nullable(),
612
- separator: z.string().nullable(),
613
- placeholder: z.string().nullable()
614
- }),
615
- slots: [],
616
- description: 'A line that restates a value: a muted label at the left, a bound value at the right. "value" and the optional "detail" are read with { "$state": "/inputs/..." } and joined by "separator" (a space by default); each is a scalar path - text, number, boolean, date or choice - never a structure, a list or a file. While both are empty the row shows "placeholder", or a dash.',
617
- example: {
618
- label: "Budget",
619
- value: { $state: "/inputs/request/budget" },
620
- detail: { $state: "/inputs/request/currency" }
621
- }
622
- },
623
- Cta: {
624
- props: z.object({ label: z.string(), hint: z.string().nullable() }),
625
- slots: [],
626
- events: ["press"],
627
- description: "A call to action: a full-width button in the accent colour that runs the method, with an optional one-line hint under it. Bind on.press to validateForm then run.",
628
- example: { label: "Plan my trip", hint: "Nothing runs until the request is complete." }
629
- },
630
- Footer: {
631
- props: z.object({ text: z.string(), tag: z.string().nullable() }),
632
- slots: [],
633
- description: "A closing line, muted, with an optional mono tag at the right. It renders as the page's contentinfo, so at most one.",
634
- example: { text: "Runs on Pipelex.", tag: "MTHDS" }
635
- }
636
- };
637
- var COMPONENT_RENDERINGS = {
638
- Hero: { heading: { level: 1 } },
639
- Section: { heading: { level: 2 } },
640
- Rail: { heading: { level: 2 } },
641
- Card: { heading: { level: 3, when: "title" } },
642
- AppBar: { once: "the page's banner" },
643
- Footer: { once: "the page's contentinfo" },
644
- Split: { children: { count: 2, roles: "left, right" } },
645
- Workspace: { children: { count: 2, roles: "work, rail" } },
646
- Tabs: { panels: { prop: "tabs", noun: "tab" } },
647
- Steps: { panels: { prop: "steps", noun: "step" } }
648
- };
649
- var catalog = Object.assign(
650
- defineCatalog(generativeSchema, {
651
- components: { ...generativeComponents, ...product },
652
- actions: generativeActions
653
- }),
654
- { renders: COMPONENT_RENDERINGS }
655
- );
656
- var COMPONENT_NAMES = [
657
- ...PICKED_SHADCN,
658
- ...CUSTOM_COMPONENTS,
659
- ...PRODUCT_COMPONENTS
660
- ];
661
-
662
- // src/generative/props-signature.ts
663
- function definitionOf(schema) {
664
- return schema._zod.def;
665
- }
666
- function propsSignature(schema) {
667
- const def = definitionOf(schema);
668
- switch (def.type) {
669
- case "string":
670
- return "string";
671
- case "number":
672
- return "number";
673
- case "boolean":
674
- return "boolean";
675
- case "literal":
676
- return JSON.stringify(def.values?.[0]);
677
- case "enum":
678
- return Object.values(def.entries ?? {}).map((value) => `"${String(value)}"`).join(" | ");
679
- case "array":
680
- return `Array<${def.element ? propsSignature(def.element) : "unknown"}>`;
681
- case "object": {
682
- const props = Object.entries(def.shape ?? {}).map(([key, value]) => {
683
- const inner = definitionOf(value).type;
684
- const optional = inner === "optional" || inner === "nullable";
685
- return `${key}${optional ? "?" : ""}: ${propsSignature(value)}`;
686
- });
687
- return `{ ${props.join(", ")} }`;
688
- }
689
- case "optional":
690
- case "nullable":
691
- case "default":
692
- return def.innerType ? propsSignature(def.innerType) : "unknown";
693
- case "union":
694
- return def.options ? def.options.map((option) => propsSignature(option)).join(" | ") : "unknown";
695
- case "record": {
696
- const key = def.keyType ? propsSignature(def.keyType) : "string";
697
- const value = def.valueType ? propsSignature(def.valueType) : "unknown";
698
- return `Record<${key}, ${value}>`;
699
- }
700
- default:
701
- return "unknown";
702
- }
703
- }
704
-
705
- // src/generative/designer-catalog.ts
706
- var DESIGNER_CATALOG_CONCEPT = "generative.Catalog";
707
- function designerCatalog() {
708
- const definitions = catalog.data.components;
709
- const components3 = COMPONENT_NAMES.map((name) => {
710
- const definition = definitions[name];
711
- if (!definition) throw new Error(`the catalog defines no component named '${name}'`);
712
- const slots = definition.slots ?? [];
713
- return {
714
- name,
715
- props: propsSignature(definition.props),
716
- description: definition.description,
717
- accepts_children: slots.includes("default"),
718
- slots: slots.filter((slot) => slot !== "default"),
719
- events: [...definition.events ?? []]
720
- };
721
- });
722
- const builtIn = (generativeSchema.builtInActions ?? []).map((action) => ({
723
- name: action.name,
724
- description: action.description,
725
- built_in: true
726
- }));
727
- const own = Object.entries(catalog.data.actions).map(
728
- ([name, action]) => ({
729
- name,
730
- description: action.description,
731
- built_in: false
732
- })
733
- );
734
- return { components: components3, actions: [...builtIn, ...own] };
735
- }
736
-
737
- // src/generative/prompt-hash.ts
738
- var PROMPT_HASH = "2b2325fd1231";
739
-
740
- // src/generative/paths.ts
741
- var INPUTS_ROOT = "/inputs";
742
- var RESULT_ROOT = "/result";
743
- function joinPath(root, ...segments) {
744
- return segments.reduce((path, segment) => `${path}/${escapeSegment(segment)}`, root);
745
- }
746
- function escapeSegment(segment) {
747
- return segment.replace(/~/g, "~0").replace(/\//g, "~1");
748
- }
749
- function unescapeSegment(segment) {
750
- return segment.replace(/~1/g, "/").replace(/~0/g, "~");
751
- }
752
- function segmentsUnder(root, path) {
753
- if (path === root) return [];
754
- if (!path.startsWith(`${root}/`)) return void 0;
755
- return path.slice(root.length + 1).split("/").map(unescapeSegment);
756
- }
757
- function fieldAtSegments(fields, segments) {
758
- const [head, ...rest] = segments;
759
- if (head === void 0) return void 0;
760
- const field = fields.find((candidate) => candidate.name === head);
761
- if (!field) return void 0;
762
- return rest.length === 0 ? field : descend(field, rest);
763
- }
764
- function descend(field, segments) {
765
- const [head, ...rest] = segments;
766
- if (head === void 0) return field;
767
- if (field.kind === "object") return fieldAtSegments(field.fields, segments);
768
- if (field.kind === "list" && /^\d+$/.test(head)) return descend(field.item, rest);
769
- return void 0;
770
- }
771
- function inputFieldAtPath(fields, path) {
772
- const segments = segmentsUnder(INPUTS_ROOT, path);
773
- return segments && segments.length > 0 ? fieldAtSegments(fields, segments) : void 0;
774
- }
775
- function resultFieldAtPath(field, path) {
776
- const segments = segmentsUnder(RESULT_ROOT, path);
777
- if (!segments) return void 0;
778
- return segments.length === 0 ? field : descend(field, segments);
779
- }
780
- function parentMapOf(spec) {
781
- const parents = /* @__PURE__ */ new Map();
782
- for (const [parentKey, element] of Object.entries(spec.elements)) {
783
- for (const child of element.children ?? []) parents.set(child, parentKey);
784
- }
785
- return parents;
786
- }
787
- function repeatBasePathOf(spec, key, parents = parentMapOf(spec)) {
788
- const chain = [];
789
- const seen = /* @__PURE__ */ new Set();
790
- let current = key;
791
- while (current !== void 0 && !seen.has(current)) {
792
- seen.add(current);
793
- chain.unshift(current);
794
- current = parents.get(current);
795
- }
796
- let base;
797
- for (const elementKey of chain) {
798
- const repeat = spec.elements[elementKey]?.repeat;
799
- if (!repeat) continue;
800
- const statePath = repeat.statePath;
801
- if (typeof statePath === "string") {
802
- base = `${statePath}/0`;
803
- continue;
804
- }
805
- const item = statePath?.$item;
806
- if (base === void 0 || typeof item !== "string") return void 0;
807
- base = `${joinPath(base, item)}/0`;
808
- }
809
- return base;
810
- }
811
- function absoluteHatchPath(spec, key, path, parents) {
812
- if (path.startsWith("/")) return path;
813
- const base = repeatBasePathOf(spec, key, parents);
814
- return base ? `${base}/${path}` : void 0;
815
- }
816
- function repeatListPathOf(spec, key, parents = parentMapOf(spec)) {
817
- const statePath = spec.elements[key]?.repeat?.statePath;
818
- if (typeof statePath === "string") return statePath;
819
- const item = statePath?.$item;
820
- const parent = parents.get(key);
821
- const outer = parent === void 0 ? void 0 : repeatBasePathOf(spec, parent, parents);
822
- return outer !== void 0 && typeof item === "string" ? joinPath(outer, item) : void 0;
823
- }
824
-
825
- // src/generative/brief.ts
826
- var DESIGNER_BRIEF_CONCEPT = "generative.Brief";
827
- var RUN_COMPONENT = "Cta";
828
- function isDelegatedInput(field) {
829
- return field.kind === "document" || field.kind === "image" || field.kind === "date" || field.kind === "list" || field.kind === "unknown" || field.kind === "enum" && field.options.includes("");
830
- }
831
- function isDelegatedResult(field) {
832
- if (isNativeValueNode(field)) return true;
833
- if (field.kind === "list" && isNativeValueNode(field.item)) return true;
834
- return field.kind === "document" || field.kind === "image" || field.kind === "date" || field.kind === "prose" || field.kind === "unknown";
835
- }
836
- function kindLabel(field) {
837
- if (isNativeHtmlNode(field)) return "markup (native.Html)";
838
- if (isNativeDateNode(field)) return "date (native.Date)";
839
- if (isNativeCompositeNode(field)) return "composite (native.Composite)";
840
- switch (field.kind) {
841
- case "text":
842
- return "text";
843
- case "prose":
844
- return "prose (markdown)";
845
- case "number":
846
- return field.integer ? "integer" : "number";
847
- case "boolean":
848
- return "boolean";
849
- case "enum":
850
- return `choice of ${field.options.map((option) => JSON.stringify(option)).join(" | ")}`;
851
- case "date":
852
- return field.datetime ? "date with time" : "date";
853
- case "document":
854
- return "document (a file)";
855
- case "image":
856
- return "image (a file)";
857
- case "object":
858
- return `structure${field.conceptRef ? ` ${field.conceptRef}` : ""}`;
859
- case "list":
860
- return `list of ${kindLabel(field.item)}`;
861
- case "unknown":
862
- return "unknown";
863
- default:
864
- return field;
865
- }
30
+ var BrandContext = React5.createContext(null);
31
+ function BrandProvider({
32
+ manifest,
33
+ children
34
+ }) {
35
+ useWebfont(manifest.webfont);
36
+ return /* @__PURE__ */ jsx(BrandContext.Provider, { value: manifest, children });
866
37
  }
867
- function constraints(field) {
868
- const notes = [];
869
- if (field.kind === "number") {
870
- if (field.min !== void 0) notes.push(`min ${field.min}`);
871
- if (field.max !== void 0) notes.push(`max ${field.max}`);
872
- }
873
- if (field.kind === "text" || field.kind === "prose") {
874
- if (field.minLength !== void 0) notes.push(`at least ${field.minLength} characters`);
875
- if (field.maxLength !== void 0) notes.push(`at most ${field.maxLength} characters`);
876
- if (field.format) notes.push(`format ${field.format}`);
877
- }
878
- if (field.kind === "list") {
879
- if (field.itemCount !== void 0) notes.push(`at least ${field.itemCount} items`);
880
- if (field.maxItemCount !== void 0) notes.push(`at most ${field.maxItemCount} items`);
881
- }
882
- if (field.defaultValue !== void 0) notes.push(`default ${JSON.stringify(field.defaultValue)}`);
883
- if (field.examples && field.examples.length > 0) {
884
- notes.push(`examples ${field.examples.map((example) => JSON.stringify(example)).join(", ")}`);
885
- }
886
- if (field.hints && Object.keys(field.hints).length > 0) {
887
- notes.push(
888
- `hints ${Object.entries(field.hints).map(([key, value]) => `${key}=${value}`).join(", ")}`
38
+ function useBrand() {
39
+ const manifest = React5.useContext(BrandContext);
40
+ if (!manifest)
41
+ throw new Error(
42
+ "useBrand: no brand in scope. Pass `brand` to GenerativePage, or wrap the tree in BrandProvider."
889
43
  );
890
- }
891
- return notes;
892
- }
893
- function entryOf(field, path, depth, side, relative, seeded) {
894
- const notes = constraints(field);
895
- return {
896
- depth,
897
- path,
898
- kind: kindLabel(field),
899
- name: field.name,
900
- title: field.title && field.title !== field.name ? field.title : void 0,
901
- description: field.description,
902
- required: side === "input" ? field.required : void 0,
903
- gating: side === "input" && field.gating ? true : void 0,
904
- notes: notes.length > 0 ? notes : void 0,
905
- default: seeded || field.defaultValue === void 0 ? void 0 : JSON.stringify(field.defaultValue),
906
- delegated: side === "input" ? isDelegatedInput(field) : isDelegatedResult(field),
907
- relative: relative ? true : void 0
908
- };
909
- }
910
- function describe(field, path, depth, side, entries, seeded = false) {
911
- const entry = entryOf(field, path, depth, side, false, seeded);
912
- entries.push(entry);
913
- if (entry.delegated) return;
914
- if (field.kind === "object") {
915
- const seededBelow = seeded || field.defaultValue !== void 0 || side === "input" && !field.required;
916
- for (const child of field.fields) {
917
- describe(child, joinPath(path, child.name), depth + 1, side, entries, seededBelow);
918
- }
919
- }
920
- if (field.kind === "list" && side === "result") {
921
- const item = field.item;
922
- entry.item_kind = kindLabel(item);
923
- entry.item_description = item.description;
924
- if (item.kind === "object" && !isDelegatedResult(item)) {
925
- entry.item_laid_out = true;
926
- for (const child of item.fields) {
927
- describeRelative(child, child.name, depth + 2, entries);
928
- }
929
- }
930
- }
931
- }
932
- function describeRelative(field, relativePath, depth, entries) {
933
- const entry = entryOf(field, relativePath, depth, "result", true, false);
934
- entries.push(entry);
935
- if (entry.delegated) return;
936
- if (field.kind === "object") {
937
- for (const child of field.fields) {
938
- describeRelative(child, joinPath(relativePath, child.name), depth + 1, entries);
939
- }
940
- }
941
- if (field.kind === "list") {
942
- const item = field.item;
943
- entry.item_kind = kindLabel(item);
944
- entry.item_description = item.description;
945
- if (item.kind === "object" && !isDelegatedResult(item)) {
946
- entry.item_laid_out = true;
947
- for (const child of item.fields) {
948
- describeRelative(child, joinPath(relativePath, "<i>", child.name), depth + 2, entries);
949
- }
950
- }
951
- }
952
- }
953
- function inputBrief(subject, fields) {
954
- const paths = [];
955
- for (const field of fields) describe(field, joinPath(INPUTS_ROOT, field.name), 0, "input", paths);
956
- return {
957
- side: "input",
958
- pipe_ref: subject.pipeRef,
959
- description: subject.description,
960
- name: subject.name,
961
- paths,
962
- run_control: RUN_COMPONENT
963
- };
964
- }
965
- function resultBrief(subject, field, state) {
966
- const paths = [];
967
- if (field.kind === "object" && !isDelegatedResult(field)) {
968
- paths.push({
969
- depth: 0,
970
- path: RESULT_ROOT,
971
- kind: kindLabel(field),
972
- description: field.description,
973
- delegated: false
974
- });
975
- for (const child of field.fields) {
976
- describe(child, joinPath(RESULT_ROOT, child.name), 1, "result", paths);
977
- }
978
- } else {
979
- describe(field, RESULT_ROOT, 0, "result", paths);
980
- }
981
- return {
982
- side: "result",
983
- pipe_ref: subject.pipeRef,
984
- description: subject.description,
985
- paths,
986
- sample_state: JSON.stringify(state, null, 2)
987
- };
44
+ return manifest;
988
45
  }
989
- var EXPRESSION_KEYS = [
990
- "$state",
991
- "$item",
992
- "$index",
993
- "$bindState",
994
- "$bindItem",
995
- "$cond",
996
- "$computed",
997
- "$template"
998
- ];
999
- var HATCHES = /* @__PURE__ */ new Set(["MthdsField", "MthdsResult"]);
1000
- function isExpression(value) {
1001
- return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).some((key) => EXPRESSION_KEYS.includes(key));
1002
- }
1003
- function isClosedVocabulary(schema) {
1004
- let current = schema;
1005
- for (let depth = 0; depth < 8; depth += 1) {
1006
- const def = current.def;
1007
- if (!def) return false;
1008
- if (def.type === "enum" || def.type === "literal") return true;
1009
- if (def.innerType === void 0) return false;
1010
- current = def.innerType;
1011
- }
1012
- return false;
1013
- }
1014
- function definitionOf2(catalog2, type) {
1015
- const components3 = catalog2.data.components;
1016
- return hasOwnProp(components3, type) ? components3[type] : void 0;
1017
- }
1018
- function propsSchemaOf(catalog2, type) {
1019
- const props = definitionOf2(catalog2, type)?.props;
1020
- return props && typeof props === "object" && "shape" in props ? props : void 0;
1021
- }
1022
- function eventsOf(catalog2, type) {
1023
- const definition = definitionOf2(catalog2, type);
1024
- if (!definition) return void 0;
1025
- return Array.isArray(definition.events) ? definition.events.filter((event) => typeof event === "string") : [];
1026
- }
1027
- var MAX_ELEMENT_DEPTH = 512;
1028
- function childKeysOf(spec, key) {
1029
- const element = spec.elements[key];
1030
- if (!element) return [];
1031
- const children = Array.isArray(element.children) ? element.children : [];
1032
- const slots = typeof element.slots === "object" && element.slots !== null ? Object.values(element.slots).flatMap(
1033
- (slot) => Array.isArray(slot) ? slot : []
1034
- ) : [];
1035
- return [...children, ...slots].filter((child) => typeof child === "string");
1036
- }
1037
- function deepestChain(spec) {
1038
- const below = /* @__PURE__ */ new Map();
1039
- const onPath = /* @__PURE__ */ new Set();
1040
- let deepest = 0;
1041
- for (const start of Object.keys(spec.elements)) {
1042
- if (below.has(start)) continue;
1043
- const stack = [start];
1044
- while (stack.length > 0) {
1045
- const key = stack[stack.length - 1];
1046
- if (below.has(key)) {
1047
- onPath.delete(key);
1048
- stack.pop();
1049
- continue;
1050
- }
1051
- const children = childKeysOf(spec, key).filter((child) => !onPath.has(child));
1052
- const pending = children.filter((child) => !below.has(child));
1053
- if (pending.length > 0) {
1054
- onPath.add(key);
1055
- stack.push(...pending);
1056
- continue;
1057
- }
1058
- below.set(
1059
- key,
1060
- children.reduce((tallest, child) => Math.max(tallest, below.get(child) ?? 0), 0) + 1
1061
- );
1062
- onPath.delete(key);
1063
- stack.pop();
46
+ var WEIGHTS = "400;500;600;700";
47
+ function useWebfont(webfont) {
48
+ const provider = webfont?.provider;
49
+ const family = webfont?.family;
50
+ React5.useEffect(() => {
51
+ if (!provider || !family) return;
52
+ const key = `${provider}:${family}`;
53
+ if (document.head.querySelector(`link[data-brand-webfont="${CSS.escape(key)}"]`)) return;
54
+ for (const origin of ["https://fonts.googleapis.com", "https://fonts.gstatic.com"]) {
55
+ if (document.head.querySelector(`link[rel="preconnect"][href="${origin}"]`)) continue;
56
+ const preconnect = document.createElement("link");
57
+ preconnect.rel = "preconnect";
58
+ preconnect.href = origin;
59
+ if (origin.endsWith("gstatic.com")) preconnect.crossOrigin = "anonymous";
60
+ document.head.appendChild(preconnect);
1064
61
  }
1065
- deepest = Math.max(deepest, below.get(start) ?? 0);
1066
- }
1067
- return deepest;
62
+ const link = document.createElement("link");
63
+ link.rel = "stylesheet";
64
+ const encoded = encodeURIComponent(family).replace(/%20/g, "+");
65
+ link.href = `https://fonts.googleapis.com/css2?family=${encoded}:wght@${WEIGHTS}&display=swap`;
66
+ link.dataset.brandWebfont = key;
67
+ document.head.appendChild(link);
68
+ }, [provider, family]);
1068
69
  }
1069
- function validateAgainstCatalog(spec, catalog2 = catalog) {
1070
- try {
1071
- return checkAgainstCatalog(spec, catalog2);
1072
- } catch (error) {
1073
- return {
1074
- ok: false,
1075
- problems: [{ message: `the layout is not a well-formed spec: ${String(error)}` }]
1076
- };
70
+ var ReportingContext = React5.createContext(false);
71
+ var ElementBoundary = class extends React5.Component {
72
+ constructor(props) {
73
+ super(props);
74
+ this.state = { failed: false, error: void 0, resetOn: props.resetOn };
1077
75
  }
1078
- }
1079
- function checkAgainstCatalog(spec, catalog2) {
1080
- const problems = [];
1081
- const depth = deepestChain(spec);
1082
- if (depth > MAX_ELEMENT_DEPTH) {
1083
- return {
1084
- ok: false,
1085
- problems: [
1086
- {
1087
- message: `the element tree is ${depth} deep; this entry renders at most ${MAX_ELEMENT_DEPTH}.`
1088
- }
1089
- ]
1090
- };
76
+ static getDerivedStateFromError(error) {
77
+ return { failed: true, error };
1091
78
  }
1092
- const structure = validateSpec(spec, { checkOrphans: true });
1093
- for (const issue of structure.issues) {
1094
- if (issue.severity === "error" || issue.code === "orphaned_element") {
1095
- problems.push({ elementKey: issue.elementKey, message: issue.message });
1096
- }
79
+ static getDerivedStateFromProps(props, state) {
80
+ if (Object.is(props.resetOn, state.resetOn)) return null;
81
+ return { failed: false, error: void 0, resetOn: props.resetOn };
1097
82
  }
1098
- const cyclic = /* @__PURE__ */ new Set();
1099
- const state = /* @__PURE__ */ new Map();
1100
- const descend2 = (key) => {
1101
- const mark = state.get(key);
1102
- if (mark === "done") return;
1103
- if (mark === "open") {
1104
- cyclic.add(key);
1105
- return;
1106
- }
1107
- state.set(key, "open");
1108
- const element = spec.elements[key];
1109
- for (const child of element?.children ?? []) descend2(child);
1110
- for (const slot of Object.values(element?.slots ?? {}))
1111
- for (const child of slot) descend2(child);
1112
- state.set(key, "done");
1113
- };
1114
- for (const key of Object.keys(spec.elements)) descend2(key);
1115
- for (const key of cyclic) {
1116
- problems.push({
1117
- elementKey: key,
1118
- message: `"${key}" is its own descendant: an element tree may not contain a cycle.`
83
+ componentDidCatch(error, info) {
84
+ if (!this.props.reporting) return;
85
+ const componentStack = info.componentStack ?? void 0;
86
+ this.props.report.current?.(error, {
87
+ type: this.props.type,
88
+ ...componentStack ? { componentStack } : {}
1119
89
  });
1120
90
  }
1121
- const known = new Set(catalog2.componentNames);
1122
- for (const [key, element] of Object.entries(spec.elements)) {
1123
- if (!known.has(element.type)) {
1124
- problems.push({ elementKey: key, message: `unknown component type "${element.type}"` });
1125
- }
91
+ // Whether to report or to throw the error on is decided as the boundary
92
+ // renders its failure, from whether the host passes a handler in this very
93
+ // render: decided later, it cost a blank commit and a second record of the
94
+ // same error, and decided from the ref, it read the previous render's
95
+ // callback. No host to tell: the error goes on, in the same render, to
96
+ // json-render's boundary or the host's around the page, which record it
97
+ // once, as if this one were not here.
98
+ render() {
99
+ if (!this.state.failed) return this.props.children;
100
+ if (!this.props.reporting) throw this.state.error;
101
+ return null;
1126
102
  }
1127
- for (const [key, element] of Object.entries(spec.elements)) {
1128
- const schema = propsSchemaOf(catalog2, element.type);
1129
- if (!schema) continue;
1130
- const shape = schema.shape;
1131
- const props = element.props ?? {};
1132
- for (const [name, value] of Object.entries(props)) {
1133
- const propSchema = shape[name];
1134
- if (!propSchema) {
1135
- problems.push({
1136
- elementKey: key,
1137
- message: `${element.type} has no prop "${name}" (known: ${Object.keys(shape).join(", ")})`
1138
- });
1139
- continue;
1140
- }
1141
- if (isExpression(value)) {
1142
- if (HATCHES.has(element.type) && name === "path") {
1143
- problems.push({
1144
- elementKey: key,
1145
- message: `${element.type}.path must be a literal string - absolute, or the item's field name inside a repeat - never an expression.`
1146
- });
1147
- } else if (isClosedVocabulary(propSchema)) {
1148
- problems.push({
1149
- elementKey: key,
1150
- message: `${element.type}.${name} is one of a fixed set of names, so it must be written as a literal, never as an expression.`
1151
- });
1152
- }
1153
- continue;
1154
- }
1155
- const parsed = propSchema.safeParse(value);
1156
- if (!parsed.success) {
1157
- problems.push({
1158
- elementKey: key,
1159
- message: `${element.type}.${name}: ${parsed.error.issues.map((issue) => issue.message).join("; ")}`
1160
- });
1161
- }
1162
- }
1163
- for (const [name, propSchema] of Object.entries(shape)) {
1164
- if (name in props) continue;
1165
- if (propSchema.safeParse(void 0).success || propSchema.safeParse(null).success) continue;
1166
- problems.push({
1167
- elementKey: key,
1168
- message: `${element.type} is missing required prop "${name}"`
1169
- });
1170
- }
1171
- }
1172
- const renders = catalog2.renders ?? {};
1173
- const renderingOf = (type) => hasOwnProp(renders, type) ? renders[type] : void 0;
1174
- for (const [key, element] of Object.entries(spec.elements)) {
1175
- const panelled = renderingOf(element.type)?.panels;
1176
- if (!panelled) continue;
1177
- const props = element.props ?? {};
1178
- const listed = props[panelled.prop];
1179
- if (!Array.isArray(listed)) continue;
1180
- const panels = listed.length;
1181
- const children = element.children?.length ?? 0;
1182
- if (children !== panels) {
1183
- problems.push({
1184
- elementKey: key,
1185
- message: `${element.type} declares ${panels} panel${panels === 1 ? "" : "s"} but has ${children} child${children === 1 ? "" : "ren"}: exactly one child per ${panelled.noun}, in order.`
1186
- });
1187
- }
1188
- const runs = (element.children ?? []).filter(
1189
- (child) => spec.elements[child]?.type === "Button"
1190
- );
1191
- if (runs.length > 0) {
1192
- problems.push({
1193
- elementKey: key,
1194
- message: `${element.type} has a Button as a direct child; a panel is a container (a Stack), and the Button belongs inside the last one.`
1195
- });
1196
- }
1197
- }
1198
- const present2 = (value) => value !== void 0 && value !== null && value !== "";
1199
- const levels = [];
1200
- const onlyOnce = /* @__PURE__ */ new Map();
1201
- const seen = /* @__PURE__ */ new Set();
1202
- const walk = (key) => {
1203
- if (seen.has(key)) return;
1204
- seen.add(key);
1205
- const element = spec.elements[key];
1206
- if (!element) return;
1207
- const props = element.props ?? {};
1208
- const rendering = renderingOf(element.type);
1209
- if (element.type === "Heading") {
1210
- const level = String(props.level ?? "h2");
1211
- const match = /^h([1-4])$/.exec(level);
1212
- if (match) levels.push({ key, type: "Heading", level: Number(match[1]) });
1213
- } else if (rendering?.heading) {
1214
- const rendered = rendering.heading;
1215
- const has = rendered.when === void 0 || present2(props[rendered.when]);
1216
- if (has) levels.push({ key, type: element.type, level: rendered.level });
1217
- }
1218
- if (rendering?.once !== void 0) {
1219
- const entry = onlyOnce.get(element.type) ?? { reason: rendering.once, keys: [] };
1220
- entry.keys.push(key);
1221
- onlyOnce.set(element.type, entry);
1222
- }
1223
- for (const child of element.children ?? []) walk(child);
1224
- for (const slot of Object.values(element.slots ?? {})) for (const child of slot) walk(child);
1225
- };
1226
- if (spec.root) walk(spec.root);
1227
- const describe2 = (heading) => heading.type === "Heading" ? `h${heading.level}` : `${heading.type}'s h${heading.level}`;
1228
- let previous;
1229
- for (const heading of levels) {
1230
- if (previous !== void 0 && heading.level > previous.level + 1) {
1231
- problems.push({
1232
- elementKey: heading.key,
1233
- message: `${describe2(heading)} jumps after ${describe2(previous)}: heading levels increase by one, never by more.`
1234
- });
1235
- }
1236
- previous = heading;
1237
- }
1238
- const firsts = levels.filter((heading) => heading.level === 1);
1239
- const h1Ways = [
1240
- ...known.has("Heading") ? ["as a Heading at level h1"] : [],
1241
- ...Object.entries(renders).filter(([, rendering]) => rendering.heading?.level === 1).map(([type]) => `as the heading a ${type} renders`)
1242
- ];
1243
- if (firsts.length === 0 && spec.root && h1Ways.length > 0) {
1244
- problems.push({
1245
- elementKey: spec.root,
1246
- message: `the page has no h1: exactly one, ${h1Ways.join(" or ")}.`
1247
- });
1248
- }
1249
- for (const extra of firsts.slice(1)) {
1250
- problems.push({
1251
- elementKey: extra.key,
1252
- message: `a second h1 (${describe2(extra)}, after ${describe2(firsts[0])} at [${firsts[0].key}]): exactly one on a page.`
1253
- });
1254
- }
1255
- for (const [type, { reason, keys }] of onlyOnce) {
1256
- for (const extra of keys.slice(1)) {
1257
- problems.push({
1258
- elementKey: extra,
1259
- message: `a second ${type} (after [${keys[0]}]): at most one on a page, since it renders as ${reason}.`
1260
- });
1261
- }
1262
- }
1263
- for (const [key, element] of Object.entries(spec.elements)) {
1264
- const rule = renderingOf(element.type)?.children;
1265
- if (!rule) continue;
1266
- const children = element.children?.length ?? 0;
1267
- if (children !== rule.count) {
1268
- problems.push({
1269
- elementKey: key,
1270
- message: `${element.type} takes exactly ${rule.count} children (${rule.roles}); this one has ${children}.`
1271
- });
1272
- }
1273
- }
1274
- const declared = [
1275
- ...catalog2.actionNames ?? [],
1276
- ...(catalog2.schema?.builtInActions ?? []).map((action) => action.name)
1277
- ];
1278
- const knownActions = declared.length > 0 ? new Set(declared) : void 0;
1279
- const STATE_WRITES = {
1280
- setState: [{ param: "statePath" }],
1281
- pushState: [{ param: "statePath" }, { param: "clearStatePath", optional: true }],
1282
- removeState: [{ param: "statePath" }],
1283
- validateForm: [{ param: "statePath", optional: true }]
1284
- };
1285
- const OWNED_ROOTS = [INPUTS_ROOT, RESULT_ROOT];
1286
- const ownedRootOf = (written) => {
1287
- const statePath = written.startsWith("/") ? written : `/${written}`;
1288
- return OWNED_ROOTS.find((root) => statePath === root || statePath.startsWith(`${root}/`));
1289
- };
1290
- const bindingsOf = (binding) => Array.isArray(binding) ? binding : [binding];
1291
- const refusal = (owned, statePath) => `a layout may not write into ${owned}, which the host fills. Bind the value with { "$bindState": "${statePath}" } or delegate it with MthdsField instead.`;
1292
- const checkBinding = (key, site, entry) => {
1293
- const action = entry?.action;
1294
- if (typeof action !== "string") {
1295
- problems.push({ elementKey: key, message: `${site} has an entry with no "action" name.` });
1296
- return;
1297
- }
1298
- if (knownActions && !knownActions.has(action)) {
1299
- problems.push({
1300
- elementKey: key,
1301
- message: `${site} names the unknown action "${action}" (known: ${[...knownActions].join(", ")}); nothing would handle it.`
1302
- });
1303
- return;
1304
- }
1305
- const writes = hasOwnProp(STATE_WRITES, action) ? STATE_WRITES[action] : void 0;
1306
- if (writes) {
1307
- const params = entry.params;
1308
- for (const { param, optional } of writes) {
1309
- const written = typeof params === "object" && params !== null ? params[param] : void 0;
1310
- if (written === void 0 || written === null) {
1311
- if (!optional) {
1312
- problems.push({
1313
- elementKey: key,
1314
- message: `${site} calls ${action} without a literal "${param}".`
1315
- });
1316
- }
1317
- continue;
1318
- }
1319
- if (typeof written !== "string") {
1320
- problems.push({
1321
- elementKey: key,
1322
- message: `${site} calls ${action} with a "${param}" that is not a literal string; where an action writes must be named, never computed.`
1323
- });
1324
- continue;
1325
- }
1326
- const owned = ownedRootOf(written);
1327
- if (owned) {
1328
- const where = param === "statePath" ? `on "${written}"` : `with ${param} "${written}"`;
1329
- problems.push({
1330
- elementKey: key,
1331
- message: `${site} calls ${action} ${where}: ${refusal(owned, written.startsWith("/") ? written : `/${written}`)}`
1332
- });
1333
- }
1334
- }
1335
- }
1336
- for (const hook of ["onSuccess", "onError"]) {
1337
- const follow = entry[hook];
1338
- if (follow === void 0 || follow === null) continue;
1339
- const hookSite = `${site}.${hook}`;
1340
- if (typeof follow !== "object" || Array.isArray(follow)) {
1341
- problems.push({
1342
- elementKey: key,
1343
- message: `${hookSite} must be an object: a { "set" } of state paths to values, or an { "action" } binding.`
1344
- });
1345
- continue;
1346
- }
1347
- const record = follow;
1348
- if (record.set !== void 0 && record.set !== null) {
1349
- if (typeof record.set !== "object" || Array.isArray(record.set)) {
1350
- problems.push({
1351
- elementKey: key,
1352
- message: `${hookSite}.set must be an object mapping a state path to its value.`
1353
- });
1354
- } else {
1355
- for (const written of Object.keys(record.set)) {
1356
- const owned = ownedRootOf(written);
1357
- if (owned) {
1358
- problems.push({
1359
- elementKey: key,
1360
- message: `${hookSite} sets "${written}": ${refusal(owned, written.startsWith("/") ? written : `/${written}`)}`
1361
- });
1362
- }
1363
- }
1364
- }
1365
- }
1366
- if (record.action !== void 0) checkBinding(key, hookSite, record);
1367
- }
1368
- };
1369
- for (const [key, element] of Object.entries(spec.elements)) {
1370
- const on = element.on;
1371
- if (on !== void 0 && on !== null) {
1372
- if (typeof on !== "object" || Array.isArray(on)) {
1373
- problems.push({
1374
- elementKey: key,
1375
- message: '"on" must be an object mapping an event name to its actions.'
1376
- });
1377
- } else {
1378
- const emitted = eventsOf(catalog2, element.type);
1379
- for (const [event, binding] of Object.entries(on)) {
1380
- if (emitted && !emitted.includes(event)) {
1381
- problems.push({
1382
- elementKey: key,
1383
- message: emitted.length > 0 ? `on.${event}: ${element.type} never emits "${event}" (it emits: ${emitted.join(", ")}), so nothing bound there would ever fire.` : `on.${event}: ${element.type} emits no events, so nothing bound there would ever fire.`
1384
- });
1385
- }
1386
- for (const entry of bindingsOf(binding)) checkBinding(key, `on.${event}`, entry);
1387
- }
1388
- }
1389
- }
1390
- const watch = element.watch;
1391
- if (watch !== void 0 && watch !== null) {
1392
- if (typeof watch !== "object" || Array.isArray(watch)) {
1393
- problems.push({
1394
- elementKey: key,
1395
- message: '"watch" must be an object mapping a state path to the actions its change fires.'
1396
- });
1397
- } else {
1398
- for (const [path, binding] of Object.entries(watch)) {
1399
- for (const entry of bindingsOf(binding)) checkBinding(key, `watch.${path}`, entry);
1400
- }
1401
- }
1402
- }
1403
- }
1404
- return { ok: problems.length === 0, problems };
1405
- }
1406
- function formatProblems(problems) {
1407
- return problems.map(
1408
- (problem) => problem.elementKey ? `[${problem.elementKey}] ${problem.message}` : problem.message
1409
- ).join("\n");
1410
- }
1411
-
1412
- // src/generative/layout-fits.ts
1413
- function isUnder(root, path) {
1414
- return path === root || path.startsWith(`${root}/`);
1415
- }
1416
- function itemPathResolver(spec, key, parents) {
1417
- let base;
1418
- let resolvedBase = false;
1419
- return (field) => {
1420
- if (!resolvedBase) {
1421
- base = repeatBasePathOf(spec, key, parents);
1422
- resolvedBase = true;
1423
- }
1424
- if (base === void 0) return void 0;
1425
- const under = field.startsWith("/") ? field.slice(1) : field;
1426
- return under === "" ? base : `${base}/${under}`;
1427
- };
1428
- }
1429
- function interpolatedPaths(template, itemPath) {
1430
- return [...template.matchAll(/\$\{([^}]+)\}/g)].flatMap((match) => {
1431
- const raw = match[1];
1432
- if (raw === void 0) return [];
1433
- if (raw.startsWith("/")) return [raw];
1434
- return [itemPath(raw) ?? `/${raw}`];
1435
- });
1436
- }
1437
- function mentionedPaths(spec, parents) {
1438
- const found = { bound: [], read: [], adrift: [] };
1439
- for (const [key, element] of Object.entries(spec.elements)) {
1440
- const itemPath = itemPathResolver(spec, key, parents);
1441
- const walk = (value) => {
1442
- if (Array.isArray(value)) {
1443
- value.forEach(walk);
1444
- return;
1445
- }
1446
- if (typeof value !== "object" || value === null) return;
1447
- const record = value;
1448
- if (typeof record.$bindState === "string") found.bound.push(record.$bindState);
1449
- if (typeof record.$state === "string") found.read.push(record.$state);
1450
- if (typeof record.$template === "string") {
1451
- found.read.push(...interpolatedPaths(record.$template, itemPath));
1452
- }
1453
- if (typeof record.$bindItem === "string") {
1454
- const path = itemPath(record.$bindItem);
1455
- if (path === void 0) found.adrift.push(key);
1456
- else found.bound.push(path);
1457
- }
1458
- if (typeof record.$item === "string") {
1459
- const path = itemPath(record.$item);
1460
- if (path === void 0) found.adrift.push(key);
1461
- else found.read.push(path);
1462
- }
1463
- Object.values(record).forEach(walk);
1464
- };
1465
- walk(element.props);
1466
- walk(element.visible);
1467
- }
1468
- return found;
1469
- }
1470
- function staleReads(read, root, resolves) {
1471
- const under = read.filter((path) => isUnder(root, path));
1472
- return [...new Set(under)].filter((path) => !resolves(path));
1473
- }
1474
- function staleRepeats(spec, parents, root, resolves, side) {
1475
- const problems = [];
1476
- for (const [key, element] of Object.entries(spec.elements)) {
1477
- if (!element.repeat) continue;
1478
- const listPath = repeatListPathOf(spec, key, parents);
1479
- if (listPath === void 0 || !isUnder(root, listPath)) continue;
1480
- if (!resolves(listPath)) {
1481
- problems.push(`${key}: repeats over ${listPath}, which no ${side} has`);
1482
- }
1483
- }
1484
- return problems;
1485
- }
1486
- var NOT_A_VALUE = {
1487
- object: { what: "a structure", instead: "a structure is shown through its members" },
1488
- list: { what: "a list", instead: "a list is laid out as a repeat or a DataTable" },
1489
- document: { what: "a file", instead: "a file is delegated to the kernel's own control" },
1490
- image: { what: "a file", instead: "a file is delegated to the kernel's own control" }
1491
103
  };
1492
- var NOT_VALUES = {
1493
- object: "structures",
1494
- list: "lists",
1495
- document: "files",
1496
- image: "files"
1497
- };
1498
- var listPropsCache;
1499
- function listProps() {
1500
- if (listPropsCache) return listPropsCache;
1501
- const found = /* @__PURE__ */ new Map([
1502
- ["DataTable", /* @__PURE__ */ new Map([["rows", "anything"]])]
1503
- ]);
1504
- const components3 = catalog.data.components;
1505
- for (const [type, definition] of Object.entries(components3)) {
1506
- const shape = definition.props?.shape;
1507
- for (const [prop, schema] of Object.entries(shape ?? {})) {
1508
- const items = listItemsOf(schema);
1509
- if (items === void 0) continue;
1510
- if (!found.has(type)) found.set(type, /* @__PURE__ */ new Map());
1511
- found.get(type).set(prop, items);
104
+ function reportingRegistry(registry, report, made) {
105
+ const wrapped = {};
106
+ for (const [type, Renderer2] of Object.entries(registry)) {
107
+ const kept = made.get(type);
108
+ if (kept && kept.renderer === Renderer2) {
109
+ wrapped[type] = kept.wrapper;
110
+ continue;
1512
111
  }
112
+ const Component2 = Renderer2;
113
+ const Reported = (props) => /* @__PURE__ */ jsx(ElementBoundary, { type, report, reporting: React5.useContext(ReportingContext), children: /* @__PURE__ */ jsx(Component2, { ...props }) });
114
+ Reported.displayName = `Reported(${type})`;
115
+ made.set(type, { renderer: Renderer2, wrapper: Reported });
116
+ wrapped[type] = Reported;
1513
117
  }
1514
- listPropsCache = found;
1515
- return found;
118
+ return wrapped;
1516
119
  }
1517
- var VALUE_SCHEMAS = /* @__PURE__ */ new Set(["string", "number", "boolean", "enum", "literal"]);
1518
- function unwrapped(schema) {
1519
- let current = schema;
1520
- for (let depth = 0; depth < 8; depth += 1) {
1521
- const def = current.def;
1522
- if (!def) return void 0;
1523
- if (def.innerType === void 0) return def;
1524
- current = def.innerType;
1525
- }
1526
- return void 0;
1527
- }
1528
- function listItemsOf(schema) {
1529
- const def = unwrapped(schema);
1530
- if (def?.type !== "array") return void 0;
1531
- const element = unwrapped(def.element);
1532
- return element?.type !== void 0 && VALUE_SCHEMAS.has(element.type) ? "values" : "anything";
1533
- }
1534
- function nonScalarReads(spec, parents, root, fieldAt) {
1535
- const problems = [];
1536
- const lists = listProps();
1537
- for (const [key, element] of Object.entries(spec.elements)) {
1538
- const itemPath = itemPathResolver(spec, key, parents);
1539
- const listy = lists.get(element.type);
1540
- const check = (prop, path) => {
1541
- if (!isUnder(root, path)) return;
1542
- const field = fieldAt(path);
1543
- if (!field) return;
1544
- const wrong = hasOwnProp(NOT_A_VALUE, field.kind) ? NOT_A_VALUE[field.kind] : void 0;
1545
- if (!wrong) return;
1546
- problems.push(
1547
- `${key}: ${prop} shows ${path}, which is ${wrong.what} and not a value; a prop that shows a value takes a scalar path, and ${wrong.instead}`
1548
- );
1549
- };
1550
- const checkList = (prop, path, items) => {
1551
- if (!isUnder(root, path)) return;
1552
- const field = fieldAt(path);
1553
- if (!field || field.kind === "unknown") return;
1554
- if (field.kind !== "list") {
1555
- const what = hasOwnProp(NOT_A_VALUE, field.kind) ? NOT_A_VALUE[field.kind].what : "a value";
1556
- problems.push(
1557
- `${key}: ${prop} takes a list, and ${path} is ${what}; a prop that takes a list reads a list path`
1558
- );
1559
- return;
1560
- }
1561
- if (items !== "values") return;
1562
- const of = hasOwnProp(NOT_VALUES, field.item.kind) ? NOT_VALUES[field.item.kind] : void 0;
1563
- if (!of) return;
1564
- problems.push(
1565
- `${key}: ${prop} takes a list of values, and ${path} is a list of ${of}; a list of ${of} is laid out as a repeat or a DataTable`
1566
- );
1567
- };
1568
- const walk = (prop, value) => {
1569
- if (Array.isArray(value)) {
1570
- value.forEach((entry) => walk(prop, entry));
1571
- return;
1572
- }
1573
- if (typeof value !== "object" || value === null) return;
1574
- const record = value;
1575
- if (typeof record.$state === "string") check(prop, record.$state);
1576
- if (typeof record.$item === "string") {
1577
- const path = itemPath(record.$item);
1578
- if (path !== void 0) check(prop, path);
1579
- }
1580
- if (typeof record.$template === "string") {
1581
- for (const path of interpolatedPaths(record.$template, itemPath)) check(prop, path);
1582
- }
1583
- for (const [name, entry] of Object.entries(record)) {
1584
- if (name !== "$cond") walk(prop, entry);
1585
- }
1586
- };
1587
- for (const [prop, value] of Object.entries(element.props ?? {})) {
1588
- const items = listy?.get(prop);
1589
- if (items === void 0 || Array.isArray(value)) {
1590
- walk(prop, value);
1591
- continue;
1592
- }
1593
- if (typeof value !== "object" || value === null) continue;
1594
- const record = value;
1595
- if (typeof record.$state === "string") checkList(prop, record.$state, items);
1596
- if (typeof record.$item === "string") {
1597
- const path = itemPath(record.$item);
1598
- if (path !== void 0) checkList(prop, path, items);
120
+ var CONTAINER = "mx-auto w-full max-w-6xl px-6 sm:px-8";
121
+ function BrandBar({ app, links, tag }) {
122
+ const brand = useBrand();
123
+ return /* @__PURE__ */ jsx("header", { className: "border-b border-border", children: /* @__PURE__ */ jsxs("div", { className: cn(CONTAINER, "flex h-16 items-center gap-5"), children: [
124
+ /* @__PURE__ */ jsx(
125
+ "img",
126
+ {
127
+ src: brand.logo.onLight,
128
+ alt: brand.name,
129
+ referrerPolicy: "no-referrer",
130
+ className: "h-7 w-auto dark:hidden"
1599
131
  }
1600
- }
1601
- }
1602
- return problems;
1603
- }
1604
- function boundProblems(bound, inputs) {
1605
- const problems = [];
1606
- const underInputs = bound.filter((path) => isUnder(INPUTS_ROOT, path));
1607
- if (!inputs) {
1608
- if (underInputs.length > 0) {
1609
- problems.push(`a result page binds ${underInputs.join(", ")}; a result page writes nothing`);
1610
- }
1611
- } else {
1612
- for (const path of underInputs) {
1613
- if (!inputFieldAtPath(inputs, path)) {
1614
- problems.push(`${path} is bound, and no input has it`);
132
+ ),
133
+ /* @__PURE__ */ jsx(
134
+ "img",
135
+ {
136
+ src: brand.logo.onDark,
137
+ alt: brand.name,
138
+ referrerPolicy: "no-referrer",
139
+ className: "hidden h-7 w-auto dark:block"
1615
140
  }
1616
- }
1617
- }
1618
- for (const path of bound) {
1619
- if (isUnder(RESULT_ROOT, path)) {
1620
- problems.push(
1621
- `${path} is bound; a layout may not write into ${RESULT_ROOT}, which the run fills`
1622
- );
1623
- }
1624
- }
1625
- return problems;
1626
- }
1627
- function repeatPaths(spec) {
1628
- const found = [];
1629
- for (const element of Object.values(spec.elements)) {
1630
- const statePath = element.repeat?.statePath;
1631
- if (typeof statePath === "string") found.push(statePath);
1632
- }
1633
- return found;
1634
- }
1635
- function isOffered(path, offered) {
1636
- if (offered.bound.has(path) || offered.repeated.has(path)) return true;
1637
- return offered.delegated.some((at) => path === at || path.startsWith(`${at}/`));
141
+ ),
142
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "h-5 w-px bg-border" }),
143
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground/80", children: app }),
144
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex min-w-0 items-center gap-6", children: [
145
+ links && links.length > 0 ? /* @__PURE__ */ jsx("nav", { "aria-label": "Site", className: "hidden items-center gap-6 sm:flex", children: links.map((link, index) => /* @__PURE__ */ jsx(
146
+ "a",
147
+ {
148
+ href: "#",
149
+ onClick: (event) => event.preventDefault(),
150
+ className: "rounded-sm text-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-hidden",
151
+ children: link
152
+ },
153
+ `${index}-${link}`
154
+ )) }) : null,
155
+ tag ? /* @__PURE__ */ jsx(
156
+ "span",
157
+ {
158
+ className: cn(
159
+ "min-w-0 truncate rounded-lg border px-2.5 py-1 font-mono text-[11px]",
160
+ selectedBorderClass,
161
+ selectedFillClass,
162
+ linkTextClass
163
+ ),
164
+ children: tag
165
+ }
166
+ ) : null
167
+ ] })
168
+ ] }) });
1638
169
  }
1639
- function uncoveredUnder(field, path, offered) {
1640
- if (isOffered(path, offered)) return [];
1641
- if (field.kind === "object") {
1642
- return field.fields.filter((child) => child.required).flatMap((child) => uncoveredUnder(child, joinPath(path, child.name), offered));
1643
- }
1644
- return [path];
170
+
171
+ // src/generative/declared-props.ts
172
+ function definitionOf(schema) {
173
+ return schema._zod?.def;
1645
174
  }
1646
- function layoutProblems(descriptor, spec) {
1647
- try {
1648
- return collectLayoutProblems(descriptor, spec);
1649
- } catch (error) {
1650
- return [`the layout is not a well-formed spec: ${String(error)}`];
175
+ function unwrapped(schema) {
176
+ let def = definitionOf(schema);
177
+ for (let depth = 0; depth < 8 && def; depth += 1) {
178
+ if (def.type !== "nullable" && def.type !== "optional" && def.type !== "default") return def;
179
+ def = def.innerType ? definitionOf(def.innerType) : void 0;
1651
180
  }
181
+ return def;
182
+ }
183
+ function absentValueOf(schema) {
184
+ if (schema.safeParse(null).success) return null;
185
+ if (schema.safeParse(void 0).success) return void 0;
186
+ const type = unwrapped(schema)?.type;
187
+ if (type === "string") return "";
188
+ if (type === "array") return [];
189
+ return void 0;
1652
190
  }
1653
- function collectLayoutProblems(descriptor, spec) {
1654
- const problems = [];
1655
- const delegated = [];
1656
- const parents = parentMapOf(spec);
1657
- for (const [key, element] of Object.entries(spec.elements)) {
1658
- const written = element.props?.path;
1659
- const path = typeof written === "string" ? absoluteHatchPath(spec, key, written, parents) : void 0;
1660
- if (element.type === "MthdsField") {
1661
- if (!path || !inputFieldAtPath(descriptor.inputs ?? [], path)) {
1662
- problems.push(`${key}: MthdsField delegates ${String(written)}, which no input has`);
1663
- } else {
1664
- delegated.push(path);
1665
- }
1666
- }
1667
- if (element.type === "MthdsResult") {
1668
- if (!path || !descriptor.result || !resultFieldAtPath(descriptor.result, path)) {
1669
- problems.push(`${key}: MthdsResult delegates ${String(written)}, which no result has`);
1670
- }
1671
- }
191
+ function conformValue(schema, value) {
192
+ if (value === void 0 || value === null) {
193
+ return schema.safeParse(value).success ? value : absentValueOf(schema);
1672
194
  }
1673
- const { bound, read, adrift } = mentionedPaths(spec, parents);
1674
- for (const key of new Set(adrift)) {
1675
- problems.push(`${key}: reads the current item, and no repeat above it has one`);
1676
- }
1677
- problems.push(...boundProblems(bound, descriptor.inputs));
1678
- if (!descriptor.inputs) {
1679
- const result = descriptor.result;
1680
- const fieldAt2 = (path) => result ? resultFieldAtPath(result, path) : void 0;
1681
- const resolves2 = (path) => fieldAt2(path) !== void 0;
1682
- problems.push(...staleRepeats(spec, parents, RESULT_ROOT, resolves2, "result"));
1683
- for (const path of staleReads(read, RESULT_ROOT, resolves2)) {
1684
- problems.push(`${path} is read, and no result has it`);
1685
- }
1686
- problems.push(...nonScalarReads(spec, parents, RESULT_ROOT, fieldAt2));
1687
- return problems;
1688
- }
1689
- const inputs = descriptor.inputs;
1690
- const fieldAt = (path) => inputFieldAtPath(inputs, path);
1691
- const resolves = (path) => fieldAt(path) !== void 0;
1692
- problems.push(...staleRepeats(spec, parents, INPUTS_ROOT, resolves, "input"));
1693
- for (const path of staleReads(read, INPUTS_ROOT, resolves)) {
1694
- problems.push(`${path} is read, and no input has it`);
1695
- }
1696
- problems.push(...nonScalarReads(spec, parents, INPUTS_ROOT, fieldAt));
1697
- const offered = {
1698
- bound: new Set(bound),
1699
- repeated: new Set(repeatPaths(spec)),
1700
- delegated
1701
- };
1702
- for (const field of inputs) {
1703
- if (!field.required) continue;
1704
- for (const path of uncoveredUnder(field, joinPath(INPUTS_ROOT, field.name), offered)) {
1705
- problems.push(`${path} is required, and the layout offers nowhere to enter it`);
1706
- }
195
+ if (schema.safeParse(value).success) return value;
196
+ const def = unwrapped(schema);
197
+ if (def?.type === "string" && (typeof value === "number" || typeof value === "boolean")) {
198
+ return String(value);
1707
199
  }
1708
- return problems;
1709
- }
1710
- function layoutFits(descriptor, spec) {
1711
- return layoutProblems(descriptor, spec).length === 0;
1712
- }
1713
- function specToJsonl(spec) {
1714
- const lines = [];
1715
- const push = (op, path, value) => lines.push(JSON.stringify({ op, path, value }));
1716
- push("add", "/root", spec.root);
1717
- const emitted = /* @__PURE__ */ new Set();
1718
- const queue = [spec.root];
1719
- while (queue.length > 0) {
1720
- const key = queue.shift();
1721
- if (emitted.has(key)) continue;
1722
- const element = spec.elements[key];
1723
- if (!element) continue;
1724
- emitted.add(key);
1725
- push("add", `/elements/${escapeSegment(key)}`, element);
1726
- for (const child of element.children ?? []) queue.push(child);
1727
- for (const slotChildren of Object.values(element.slots ?? {})) queue.push(...slotChildren);
200
+ if (def?.type === "array" && def.element && Array.isArray(value)) {
201
+ const element = def.element;
202
+ return value.filter((entry) => element.safeParse(entry).success);
1728
203
  }
1729
- for (const key of Object.keys(spec.elements)) {
1730
- if (!emitted.has(key)) push("add", `/elements/${escapeSegment(key)}`, spec.elements[key]);
204
+ return absentValueOf(schema);
205
+ }
206
+ function conformProps(shape, props, asWritten = []) {
207
+ let conformed;
208
+ for (const [name, schema] of Object.entries(shape)) {
209
+ if (asWritten.includes(name)) continue;
210
+ const value = props[name];
211
+ const held = conformValue(schema, value);
212
+ if (held === value) continue;
213
+ conformed ?? (conformed = { ...props });
214
+ conformed[name] = held;
1731
215
  }
1732
- for (const [key, value] of Object.entries(spec.state ?? {})) {
1733
- push("add", `/state/${escapeSegment(key)}`, value);
216
+ return conformed ?? props;
217
+ }
218
+ function withDeclaredProps(definitions, renderers, asWritten = {}) {
219
+ const wrapped = {};
220
+ for (const [name, render] of Object.entries(renderers)) {
221
+ const schema = definitions[name]?.props;
222
+ const shape = schema?.shape;
223
+ wrapped[name] = shape ? (ctx) => render({
224
+ ...ctx,
225
+ props: conformProps(shape, ctx.props, asWritten[name])
226
+ }) : render;
1734
227
  }
1735
- return `${lines.join("\n")}
1736
- `;
1737
- }
1738
- var FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1739
- function pointerReachesPrototype(pointer) {
1740
- return typeof pointer === "string" && pointer.split("/").some((segment) => FORBIDDEN_SEGMENTS.has(unescapeSegment(segment)));
228
+ return wrapped;
1741
229
  }
1742
- function reachesPrototype(line) {
1743
- let parsed;
230
+ function textOf(value) {
231
+ if (value === void 0 || value === null) return "";
232
+ if (typeof value !== "object") return String(value);
1744
233
  try {
1745
- parsed = JSON.parse(line);
234
+ return String(value);
1746
235
  } catch {
1747
- return false;
1748
- }
1749
- const patch = parsed;
1750
- return pointerReachesPrototype(patch?.path) || pointerReachesPrototype(patch?.from);
1751
- }
1752
- function specFromJsonl(jsonl) {
1753
- let spec = { root: "", elements: {} };
1754
- for (const line of jsonl.split("\n")) {
1755
- if (reachesPrototype(line)) continue;
1756
236
  try {
1757
- spec = compileSpecStream(line, spec);
237
+ return JSON.stringify(value) ?? "";
1758
238
  } catch {
1759
- continue;
239
+ return "";
1760
240
  }
1761
241
  }
1762
- return spec;
1763
- }
1764
- function jsonlLines(jsonl) {
1765
- return jsonl.split("\n").filter((line) => line.trim().length > 0);
1766
242
  }
1767
- var BrandContext = React3.createContext(null);
1768
- function BrandProvider({
1769
- manifest,
243
+ var FrameHostContext = React5.createContext({});
244
+ function FrameHostProvider({
245
+ host,
1770
246
  children
1771
247
  }) {
1772
- useWebfont(manifest.webfont);
1773
- return /* @__PURE__ */ jsx(BrandContext.Provider, { value: manifest, children });
248
+ return /* @__PURE__ */ jsx(FrameHostContext.Provider, { value: host, children });
1774
249
  }
1775
- function useBrand() {
1776
- const manifest = React3.useContext(BrandContext);
1777
- if (!manifest)
1778
- throw new Error(
1779
- "useBrand: no brand in scope. Pass `brand` to GenerativePage, or wrap the tree in BrandProvider."
1780
- );
1781
- return manifest;
1782
- }
1783
- var WEIGHTS = "400;500;600;700";
1784
- function useWebfont(webfont) {
1785
- const provider = webfont?.provider;
1786
- const family = webfont?.family;
1787
- React3.useEffect(() => {
1788
- if (!provider || !family) return;
1789
- const key = `${provider}:${family}`;
1790
- if (document.head.querySelector(`link[data-brand-webfont="${CSS.escape(key)}"]`)) return;
1791
- for (const origin of ["https://fonts.googleapis.com", "https://fonts.gstatic.com"]) {
1792
- if (document.head.querySelector(`link[rel="preconnect"][href="${origin}"]`)) continue;
1793
- const preconnect = document.createElement("link");
1794
- preconnect.rel = "preconnect";
1795
- preconnect.href = origin;
1796
- if (origin.endsWith("gstatic.com")) preconnect.crossOrigin = "anonymous";
1797
- document.head.appendChild(preconnect);
1798
- }
1799
- const link = document.createElement("link");
1800
- link.rel = "stylesheet";
1801
- const encoded = encodeURIComponent(family).replace(/%20/g, "+");
1802
- link.href = `https://fonts.googleapis.com/css2?family=${encoded}:wght@${WEIGHTS}&display=swap`;
1803
- link.dataset.brandWebfont = key;
1804
- document.head.appendChild(link);
1805
- }, [provider, family]);
250
+ function useFrameHost() {
251
+ return React5.useContext(FrameHostContext);
1806
252
  }
1807
253
  function useBoundField(literal, binding, empty, { checks, validateOn }) {
1808
254
  const [boundValue, setBoundValue] = useBoundProp(literal ?? void 0, binding);
1809
- const [localValue, setLocalValue] = React3.useState(literal ?? empty);
255
+ const [localValue, setLocalValue] = React5.useState(literal ?? empty);
1810
256
  const isBound = Boolean(binding);
1811
257
  const hasValidation = Boolean(binding && checks?.length);
1812
258
  const when = validateOn ?? "change";
@@ -1846,11 +292,16 @@ function Card({
1846
292
  return (
1847
293
  // A style's panel: a host writes the style's field fill onto `--input`
1848
294
  // here, as on its own panel, so the fields inside do not sink into it.
295
+ // A panel's corner is one and a half times a control's, as the rail's is.
1849
296
  /* @__PURE__ */ jsxs(
1850
297
  "div",
1851
298
  {
1852
299
  "data-style-panel": "",
1853
- className: cn("rounded-lg p-6 text-card-foreground", panelSurfaceClass, panelFlatShadowClass),
300
+ className: cn(
301
+ "rounded-[calc(var(--radius,0.5rem)*1.5)] p-6 text-card-foreground",
302
+ panelSurfaceClass,
303
+ panelFlatShadowClass
304
+ ),
1854
305
  children: [
1855
306
  props.title || props.description ? /* @__PURE__ */ jsxs("div", { className: "mb-4 space-y-1.5", children: [
1856
307
  props.title ? /* @__PURE__ */ jsx("h3", { className: cn("text-lg leading-none", headingClass(600)), children: props.title }) : null,
@@ -1940,7 +391,16 @@ function Collapsible({
1940
391
  open: props.defaultOpen ?? false,
1941
392
  "data-slot": "collapsible",
1942
393
  children: [
1943
- /* @__PURE__ */ jsx("summary", { className: "cursor-pointer list-none rounded-md px-4 py-3 text-sm font-medium select-none focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", children: props.title }),
394
+ /* @__PURE__ */ jsxs("summary", { className: "flex min-h-11 cursor-pointer list-none items-center justify-between gap-3 rounded-md px-4 py-3 text-sm font-medium select-none focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset [&::-webkit-details-marker]:hidden", children: [
395
+ /* @__PURE__ */ jsx("span", { children: props.title }),
396
+ /* @__PURE__ */ jsx(
397
+ ChevronDown,
398
+ {
399
+ "aria-hidden": "true",
400
+ className: "size-4 shrink-0 text-muted-foreground transition-transform [details[open]>summary_&]:rotate-180"
401
+ }
402
+ )
403
+ ] }),
1944
404
  /* @__PURE__ */ jsx("div", { className: "border-t border-border px-4 py-3", children })
1945
405
  ]
1946
406
  }
@@ -2064,7 +524,7 @@ function Progress({
2064
524
  ] });
2065
525
  }
2066
526
  function useControlId(name) {
2067
- return `jr-${name}-${React3.useId()}`;
527
+ return `jr-${name}-${React5.useId()}`;
2068
528
  }
2069
529
  function Input({
2070
530
  props,
@@ -2243,9 +703,9 @@ function Button({
2243
703
  }
2244
704
  );
2245
705
  }
2246
- var DescriptorContext = React3.createContext({});
706
+ var DescriptorContext = React5.createContext({});
2247
707
  function useDescriptorScope() {
2248
- return React3.useContext(DescriptorContext);
708
+ return React5.useContext(DescriptorContext);
2249
709
  }
2250
710
  function DescriptorProvider({
2251
711
  scope,
@@ -2292,13 +752,28 @@ function useAbsolutePath(path) {
2292
752
  if (path.startsWith("/")) return path;
2293
753
  return repeat ? `${repeat.basePath}/${path}` : void 0;
2294
754
  }
2295
- function MthdsField({ props }) {
2296
- const scope = React3.useContext(DescriptorContext);
755
+ function withoutOptionalMark(label) {
756
+ return label.replace(/\s*\(optional\)\s*$/i, "") || label;
757
+ }
758
+ function MthdsField({
759
+ props
760
+ }) {
761
+ const scope = React5.useContext(DescriptorContext);
2297
762
  const path = useAbsolutePath(props.path) ?? "";
2298
- const field = scope.inputs && path ? inputFieldAtPath(scope.inputs, path) : void 0;
763
+ const found = scope.inputs && path ? inputFieldAtPath(scope.inputs, path) : void 0;
764
+ const field = React5.useMemo(
765
+ () => found && (props.label || props.hint) ? {
766
+ ...found,
767
+ // The control marks an optional field itself, so a label that
768
+ // says "(optional)" too would say it twice.
769
+ title: props.label ? withoutOptionalMark(props.label) : found.title,
770
+ description: props.hint || found.description
771
+ } : found,
772
+ [found, props.label, props.hint]
773
+ );
2299
774
  const value = useStateValue(path);
2300
775
  const { set } = useStateStore();
2301
- const onChange = React3.useCallback((next) => set(path, next), [set, path]);
776
+ const onChange = React5.useCallback((next) => set(path, next), [set, path]);
2302
777
  if (!field) return /* @__PURE__ */ jsx(Unresolved, { path: props.path, side: "input" });
2303
778
  return /* @__PURE__ */ jsx(
2304
779
  FieldRenderer,
@@ -2312,7 +787,7 @@ function MthdsField({ props }) {
2312
787
  );
2313
788
  }
2314
789
  function MthdsResult({ props }) {
2315
- const scope = React3.useContext(DescriptorContext);
790
+ const scope = React5.useContext(DescriptorContext);
2316
791
  const path = useAbsolutePath(props.path) ?? "";
2317
792
  const field = scope.result && path ? resultFieldAtPath(scope.result, path) : void 0;
2318
793
  const value = useStateValue(path);
@@ -2325,7 +800,7 @@ function AccessibleSelect({ props, bindings, emit }) {
2325
800
  props.value ?? void 0,
2326
801
  bindings?.value
2327
802
  );
2328
- const [localValue, setLocalValue] = React3.useState("");
803
+ const [localValue, setLocalValue] = React5.useState("");
2329
804
  const isBound = Boolean(bindings?.value);
2330
805
  const value = isBound ? boundValue ?? "" : localValue;
2331
806
  const setValue = isBound ? setBoundValue : setLocalValue;
@@ -2374,6 +849,21 @@ function formatCell(value) {
2374
849
  if (Array.isArray(value)) return value.map(formatCell).join(", ");
2375
850
  return JSON.stringify(value);
2376
851
  }
852
+ function formatColumnCell(value, format) {
853
+ const written = format === "date" ? readDateContent(value)?.date : void 0;
854
+ const day = written === void 0 ? null : /^\d{4}-\d{2}-\d{2}/.exec(written);
855
+ if (day) {
856
+ const date = /* @__PURE__ */ new Date(`${day[0]}T12:00:00Z`);
857
+ if (!Number.isNaN(date.getTime()))
858
+ return date.toLocaleDateString("en-GB", {
859
+ day: "numeric",
860
+ month: "short",
861
+ year: "numeric",
862
+ timeZone: "UTC"
863
+ });
864
+ }
865
+ return formatCell(value);
866
+ }
2377
867
  function DataTable({ props }) {
2378
868
  const rows = Array.isArray(props.rows) ? props.rows : [];
2379
869
  const columns = props.columns ?? [];
@@ -2388,7 +878,7 @@ function DataTable({ props }) {
2388
878
  },
2389
879
  column.path
2390
880
  )) }) }),
2391
- /* @__PURE__ */ jsx("tbody", { children: rows.map((row, index) => /* @__PURE__ */ jsx("tr", { className: "border-b border-border/60 last:border-b-0", children: columns.map((column) => /* @__PURE__ */ jsx("td", { className: "px-2.5 py-1.5 align-top text-foreground", children: formatCell(valueAt(row, column.path)) }, column.path)) }, index)) })
881
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row, index) => /* @__PURE__ */ jsx("tr", { className: "border-b border-border/60 last:border-b-0", children: columns.map((column) => /* @__PURE__ */ jsx("td", { className: "px-2.5 py-1.5 align-top text-foreground", children: formatColumnCell(valueAt(row, column.path), column.format) }, column.path)) }, index)) })
2392
882
  ] }) });
2393
883
  }
2394
884
  function numberFormat(format) {
@@ -2407,7 +897,7 @@ function numberFormat(format) {
2407
897
  }
2408
898
  function Metric({ props }) {
2409
899
  const value = props.value;
2410
- const text = typeof value === "number" ? numberFormat(props.format ?? "plain").format(value) : value === null || value === void 0 || value === "" ? "\u2014" : String(value);
900
+ const text = typeof value === "number" ? numberFormat(props.format ?? "plain").format(value) : value === null || value === void 0 || value === "" ? "\u2014" : textOf(value);
2411
901
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
2412
902
  /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: props.label }),
2413
903
  /* @__PURE__ */ jsxs("span", { className: "text-2xl font-semibold tabular-nums text-foreground", children: [
@@ -2440,8 +930,8 @@ function Split({
2440
930
  }
2441
931
  function Tabs({ props, children }) {
2442
932
  const tabs = props.tabs ?? [];
2443
- const panels = React3.Children.toArray(children);
2444
- const [value, setValue] = React3.useState(tabs[0]?.value ?? "");
933
+ const panels = React5.Children.toArray(children);
934
+ const [value, setValue] = React5.useState(tabs[0]?.value ?? "");
2445
935
  return /* @__PURE__ */ jsxs(TabsPrimitive.Root, { value, onValueChange: setValue, className: "w-full", children: [
2446
936
  /* @__PURE__ */ jsx(TabsPrimitive.List, { className: "inline-flex h-10 items-center justify-start rounded-md bg-muted p-1 text-muted-foreground max-sm:h-auto max-sm:flex-wrap", children: tabs.map((tab) => /* @__PURE__ */ jsx(
2447
937
  TabsPrimitive.Trigger,
@@ -2471,8 +961,8 @@ var SECONDARY_BUTTON = cn(
2471
961
  );
2472
962
  function Steps({ props, children }) {
2473
963
  const steps = props.steps ?? [];
2474
- const panels = React3.Children.toArray(children);
2475
- const [index, setIndex] = React3.useState(0);
964
+ const panels = React5.Children.toArray(children);
965
+ const [index, setIndex] = React5.useState(0);
2476
966
  const last = steps.length - 1;
2477
967
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-5", children: [
2478
968
  /* @__PURE__ */ jsx("ol", { className: "flex flex-wrap items-center gap-x-5 gap-y-2", children: steps.map((label, position) => /* @__PURE__ */ jsxs(
@@ -2601,7 +1091,7 @@ function Segmented({ props, bindings, emit }) {
2601
1091
  props.value ?? void 0,
2602
1092
  bindings?.value
2603
1093
  );
2604
- const [localValue, setLocalValue] = React3.useState("");
1094
+ const [localValue, setLocalValue] = React5.useState("");
2605
1095
  const isBound = Boolean(bindings?.value);
2606
1096
  const value = isBound ? boundValue ?? "" : localValue;
2607
1097
  const setValue = isBound ? setBoundValue : setLocalValue;
@@ -2641,7 +1131,7 @@ function NumberInput({ props, bindings, emit }) {
2641
1131
  props.value ?? void 0,
2642
1132
  bindings?.value
2643
1133
  );
2644
- const [localValue, setLocalValue] = React3.useState(void 0);
1134
+ const [localValue, setLocalValue] = React5.useState(void 0);
2645
1135
  const isBound = Boolean(bindings?.value);
2646
1136
  const value = isBound ? boundValue : localValue;
2647
1137
  const setValue = isBound ? setBoundValue : setLocalValue;
@@ -2675,7 +1165,7 @@ function NumberInput({ props, bindings, emit }) {
2675
1165
  ] })
2676
1166
  ] });
2677
1167
  }
2678
- var components2 = {
1168
+ var components = {
2679
1169
  Card: (ctx) => /* @__PURE__ */ jsx(Card, { ...ctx }),
2680
1170
  // A vertical Stack stretches its children unless the spec says otherwise:
2681
1171
  // hugging the content is the one layout a result page never wants, since a
@@ -2714,67 +1204,356 @@ var components2 = {
2714
1204
  DataTable: (ctx) => /* @__PURE__ */ jsx(DataTable, { ...ctx }),
2715
1205
  Metric: (ctx) => /* @__PURE__ */ jsx(Metric, { ...ctx })
2716
1206
  };
2717
- var generativeRenderers = components2;
2718
- var ResultSlotContext = React3.createContext(null);
2719
- function ResultSlotProvider({
2720
- slot,
2721
- children
2722
- }) {
2723
- return /* @__PURE__ */ jsx(ResultSlotContext.Provider, { value: slot, children });
1207
+ var generativeRenderers = withDeclaredProps(
1208
+ baseCatalog.data.components,
1209
+ components
1210
+ );
1211
+ var RADIUS = "rounded-[calc(var(--radius,0.5rem)*1.5)]";
1212
+ function useAbsolute(path) {
1213
+ const repeat = useRepeatScope();
1214
+ if (path.startsWith("/")) return path;
1215
+ if (!repeat) return path;
1216
+ return path === "" ? repeat.basePath : `${repeat.basePath}/${path}`;
1217
+ }
1218
+ function asDate(value) {
1219
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}/.test(value)) return void 0;
1220
+ const date = /* @__PURE__ */ new Date(`${value.slice(0, 10)}T12:00:00Z`);
1221
+ return Number.isNaN(date.getTime()) ? void 0 : date;
1222
+ }
1223
+ function Composer({ props, children, emit }) {
1224
+ const host = useFrameHost();
1225
+ const busy = useDescriptorScope().env?.disabled ?? false;
1226
+ const two = props.columns === "2";
1227
+ return /* @__PURE__ */ jsxs(
1228
+ "div",
1229
+ {
1230
+ className: cn("mx-auto flex w-full flex-col gap-7", two ? "max-w-[980px]" : "max-w-[740px]"),
1231
+ children: [
1232
+ /* @__PURE__ */ jsxs("div", { children: [
1233
+ /* @__PURE__ */ jsx(
1234
+ "h1",
1235
+ {
1236
+ className: cn(
1237
+ "text-[32px] leading-[1.1] text-balance text-foreground sm:text-[38px] sm:leading-[1.08]",
1238
+ headingClass(700, true)
1239
+ ),
1240
+ children: props.headline
1241
+ }
1242
+ ),
1243
+ props.lede ? /* @__PURE__ */ jsx("p", { className: "mt-3.5 max-w-[62ch] text-[17px] leading-relaxed text-muted-foreground", children: props.lede }) : null
1244
+ ] }),
1245
+ /* @__PURE__ */ jsxs(
1246
+ "div",
1247
+ {
1248
+ "data-style-panel": "",
1249
+ className: cn(RADIUS, panelSurfaceClass, panelFlatShadowClass, "p-7 max-sm:p-5"),
1250
+ children: [
1251
+ /* @__PURE__ */ jsx(
1252
+ "div",
1253
+ {
1254
+ className: cn(
1255
+ "grid gap-8",
1256
+ two && "md:block md:columns-2 md:gap-x-10 md:[&>*+*]:mt-8 md:[&>*>h2]:break-after-avoid md:[&>*>*:not(h2)]:break-inside-avoid"
1257
+ ),
1258
+ children
1259
+ }
1260
+ ),
1261
+ /* @__PURE__ */ jsxs("div", { className: "sticky bottom-0 z-[2] -mx-7 -mb-7 mt-7 flex items-center justify-between gap-4 rounded-b-[calc(var(--radius,0.5rem)*1.5)] border-t border-border [background-color:var(--panel-fill,var(--card,hsl(0_0%_100%)))] px-7 pt-[18px] pb-7 max-sm:-mx-5 max-sm:-mb-5 max-sm:flex-col-reverse max-sm:items-stretch max-sm:px-5 max-sm:pb-5", children: [
1262
+ host.eta ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 text-sm text-muted-foreground max-sm:justify-center", children: [
1263
+ /* @__PURE__ */ jsx(Clock, { className: "size-4", "aria-hidden": "true" }),
1264
+ " ",
1265
+ host.eta
1266
+ ] }) : /* @__PURE__ */ jsx("span", {}),
1267
+ /* @__PURE__ */ jsxs(
1268
+ "button",
1269
+ {
1270
+ type: "button",
1271
+ disabled: busy,
1272
+ onClick: () => emit("press"),
1273
+ className: cn(
1274
+ "inline-flex min-h-11 items-center justify-center gap-2 rounded-[var(--radius,0.5rem)] bg-primary px-[22px] text-[15px] font-semibold text-primary-foreground transition-all hover:bg-primary/90",
1275
+ actionShadowClass,
1276
+ focusOutlineClass,
1277
+ "disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:bg-primary"
1278
+ ),
1279
+ children: [
1280
+ props.runLabel,
1281
+ " ",
1282
+ /* @__PURE__ */ jsx(ArrowRight, { className: "size-4", "aria-hidden": "true" })
1283
+ ]
1284
+ }
1285
+ )
1286
+ ] })
1287
+ ]
1288
+ }
1289
+ ),
1290
+ props.notes && props.notes.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "grid gap-x-8 gap-y-2.5 px-1 sm:grid-cols-2", children: props.notes.map((note, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2.5 text-sm text-muted-foreground", children: [
1291
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: linkTextClass, children: "\u2713" }),
1292
+ /* @__PURE__ */ jsx("span", { className: "min-w-0", children: note })
1293
+ ] }, index)) }) : null
1294
+ ]
1295
+ }
1296
+ );
2724
1297
  }
2725
- function useResultSlot() {
2726
- return React3.useContext(ResultSlotContext);
1298
+ function FieldGroup({ props, children }) {
1299
+ return /* @__PURE__ */ jsxs("section", { className: "flex min-w-0 flex-col gap-5", children: [
1300
+ props.title ? /* @__PURE__ */ jsx("h2", { className: cn("text-[17px] leading-snug text-foreground", headingClass(600)), children: props.title }) : null,
1301
+ children
1302
+ ] });
2727
1303
  }
2728
- var CONTAINER = "mx-auto w-full max-w-6xl px-6 sm:px-8";
2729
- var LABEL2 = "block text-[13.5px] leading-none font-medium text-foreground";
2730
- function AppBar({ props }) {
2731
- const brand = useBrand();
2732
- return /* @__PURE__ */ jsx("header", { className: "border-b border-border", children: /* @__PURE__ */ jsxs("div", { className: cn(CONTAINER, "flex h-16 items-center gap-5"), children: [
1304
+ function ResultHeader({ props }) {
1305
+ const host = useFrameHost();
1306
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-6 max-sm:flex-col", children: [
1307
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1308
+ props.eyebrow ? /* @__PURE__ */ jsx("p", { className: cn("mb-1.5 text-[13px] font-semibold", linkTextClass), children: props.eyebrow }) : null,
1309
+ /* @__PURE__ */ jsx(
1310
+ "h1",
1311
+ {
1312
+ className: cn(
1313
+ "text-[28px] leading-[1.12] text-balance text-foreground sm:text-[32px]",
1314
+ headingClass(700, true)
1315
+ ),
1316
+ children: props.title
1317
+ }
1318
+ ),
1319
+ props.subtitle ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-[15px] text-muted-foreground", children: props.subtitle }) : null
1320
+ ] }),
1321
+ host.actions && host.actions.length > 0 && host.onAction ? /* @__PURE__ */ jsx("div", { className: "flex shrink-0 gap-2", children: host.actions.map((label, i) => /* @__PURE__ */ jsx(
1322
+ "button",
1323
+ {
1324
+ type: "button",
1325
+ onClick: () => host.onAction?.(label),
1326
+ className: cn(
1327
+ "inline-flex min-h-11 items-center rounded-[var(--radius,0.5rem)] px-4 text-sm font-semibold sm:min-h-9",
1328
+ focusOutlineClass,
1329
+ i === 0 ? "bg-primary text-primary-foreground" : "border border-border bg-background text-foreground"
1330
+ ),
1331
+ children: label
1332
+ },
1333
+ `${i}-${label}`
1334
+ )) }) : null
1335
+ ] });
1336
+ }
1337
+ function segmentMix(index, count) {
1338
+ return count <= 1 ? 100 : Math.round(100 - index * 75 / (count - 1));
1339
+ }
1340
+ function segmentColor(index, count) {
1341
+ return `color-mix(in oklab, var(--link, var(--primary, hsl(240 5.9% 10%))) ${segmentMix(index, count)}%, var(--muted, hsl(240 4.8% 95.9%)))`;
1342
+ }
1343
+ function amount(value, unit) {
1344
+ if (unit && /^[A-Z]{3}$/.test(unit)) {
1345
+ try {
1346
+ return new Intl.NumberFormat("en-GB", {
1347
+ style: "currency",
1348
+ currency: unit,
1349
+ maximumFractionDigits: 0
1350
+ }).format(value);
1351
+ } catch {
1352
+ }
1353
+ }
1354
+ const n = new Intl.NumberFormat("en-GB", { maximumFractionDigits: 2 }).format(value);
1355
+ return unit ? `${n} ${unit}` : n;
1356
+ }
1357
+ function ProportionBar({ props }) {
1358
+ const rows = (Array.isArray(props.items) ? props.items : []).filter(
1359
+ (row) => typeof row === "object" && row !== null && !Array.isArray(row)
1360
+ );
1361
+ const amountOf = (raw) => typeof raw === "object" && raw !== null ? Number.NaN : Number(raw ?? 0);
1362
+ const parts = rows.map((row) => ({
1363
+ label: textOf(row[props.labelKey]),
1364
+ value: amountOf(row[props.valueKey])
1365
+ })).filter((p) => Number.isFinite(p.value) && p.value > 0);
1366
+ const sum = parts.reduce((s, p) => s + p.value, 0);
1367
+ const total = typeof props.total === "number" ? props.total : sum;
1368
+ const scale = Math.max(sum, total, typeof props.of === "number" ? props.of : 0) || 1;
1369
+ const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
1370
+ return /* @__PURE__ */ jsxs(
1371
+ "div",
1372
+ {
1373
+ "data-style-panel": "",
1374
+ className: cn(
1375
+ RADIUS,
1376
+ panelSurfaceClass,
1377
+ panelFlatShadowClass,
1378
+ "flex flex-col gap-3.5 px-6 py-5"
1379
+ ),
1380
+ children: [
1381
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2.5", children: [
1382
+ /* @__PURE__ */ jsx("span", { className: "text-[15px] text-muted-foreground", children: props.label }),
1383
+ /* @__PURE__ */ jsx("strong", { className: "text-2xl font-semibold tabular-nums text-foreground", children: amount(total, props.unit) }),
1384
+ typeof props.of === "number" ? /* @__PURE__ */ jsxs("span", { className: "text-[15px] text-muted-foreground", children: [
1385
+ "of ",
1386
+ amount(props.of, props.unit)
1387
+ ] }) : null
1388
+ ] }),
1389
+ /* @__PURE__ */ jsx("div", { className: "flex h-2 gap-0.5 overflow-hidden rounded-full bg-muted", "aria-hidden": "true", children: parts.map((p, i) => /* @__PURE__ */ jsx(
1390
+ "span",
1391
+ {
1392
+ style: {
1393
+ width: `${p.value / scale * 100}%`,
1394
+ background: segmentColor(i, parts.length)
1395
+ }
1396
+ },
1397
+ `${i}-${p.label}`
1398
+ )) }),
1399
+ /* @__PURE__ */ jsx("ul", { className: "flex flex-wrap gap-x-5 gap-y-1", children: parts.map((p, i) => /* @__PURE__ */ jsxs(
1400
+ "li",
1401
+ {
1402
+ className: "inline-flex items-center gap-2 text-sm text-muted-foreground",
1403
+ children: [
1404
+ /* @__PURE__ */ jsx(
1405
+ "span",
1406
+ {
1407
+ className: "size-2 rounded-[2px]",
1408
+ style: {
1409
+ background: segmentColor(i, parts.length)
1410
+ },
1411
+ "aria-hidden": "true"
1412
+ }
1413
+ ),
1414
+ cap(p.label),
1415
+ " ",
1416
+ amount(p.value, props.unit)
1417
+ ]
1418
+ },
1419
+ `${i}-${p.label}`
1420
+ )) })
1421
+ ]
1422
+ }
1423
+ );
1424
+ }
1425
+ var COLOR_VALUE = /^(?:#[0-9a-f]{3,8}|(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch)\([\w\s.,%/+-]*\)|[a-z]+)$/i;
1426
+ function Swatches({ props }) {
1427
+ const colors = (props.colors ?? []).filter(
1428
+ (c) => typeof c === "string" && COLOR_VALUE.test(c.trim())
1429
+ );
1430
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: colors.map((color, i) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
2733
1431
  /* @__PURE__ */ jsx(
2734
- "img",
1432
+ "span",
2735
1433
  {
2736
- src: brand.logo.onLight,
2737
- alt: brand.name,
2738
- referrerPolicy: "no-referrer",
2739
- className: "h-7 w-auto dark:hidden"
1434
+ className: "h-7 w-11 rounded-[3px] border border-border",
1435
+ style: { backgroundColor: color.trim() }
2740
1436
  }
2741
1437
  ),
1438
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-muted-foreground", children: color })
1439
+ ] }, `${color}-${i}`)) });
1440
+ }
1441
+ var ASPECT = {
1442
+ "16:9": "aspect-video",
1443
+ "4:3": "aspect-[4/3]",
1444
+ "1:1": "aspect-square"
1445
+ };
1446
+ function Picture({
1447
+ props
1448
+ }) {
1449
+ const written = typeof props.path === "string" ? props.path : void 0;
1450
+ const path = useAbsolute(written ?? "");
1451
+ const value = useStateValue(path);
1452
+ const resolve = useResolveUrl();
1453
+ const content = written === void 0 ? void 0 : readImageContent(value);
1454
+ const src = content ? viewableUrl(resolve?.(content.url)) ?? viewableUrl(content.publicUrl) ?? viewableUrl(content.url) : void 0;
1455
+ const aspect = props.aspect && props.aspect !== "auto" ? ASPECT[props.aspect] : void 0;
1456
+ if (!src) return null;
1457
+ return /* @__PURE__ */ jsx(
1458
+ "img",
1459
+ {
1460
+ src,
1461
+ alt: props.alt ?? content?.caption ?? "",
1462
+ referrerPolicy: "no-referrer",
1463
+ className: cn(
1464
+ "block w-full rounded-[var(--radius,0.5rem)] border border-border bg-muted object-cover",
1465
+ aspect
1466
+ )
1467
+ }
1468
+ );
1469
+ }
1470
+ function present(value) {
1471
+ return value !== void 0 && value !== null && value !== "";
1472
+ }
1473
+ function Fact({
1474
+ props
1475
+ }) {
1476
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 rounded-[var(--radius,0.5rem)] border border-border bg-background px-4 py-3", children: [
1477
+ /* @__PURE__ */ jsx("span", { className: "text-[13px] text-muted-foreground", children: props.label }),
1478
+ /* @__PURE__ */ jsx("span", { className: "text-[16px] font-medium text-foreground tabular-nums", children: present(props.value) ? textOf(props.value) : "\u2014" }),
1479
+ props.detail ? /* @__PURE__ */ jsx("span", { className: "text-[13px] text-muted-foreground", children: props.detail }) : null
1480
+ ] });
1481
+ }
1482
+ function Detail({ props }) {
1483
+ if (!present(props.value)) return null;
1484
+ return (
1485
+ // A rule between the rows of a list of details, never under one set in a
1486
+ // row: there it read as an underline on every detail but the last.
1487
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[minmax(0,8.5rem)_minmax(0,1fr)] gap-3 border-b border-border py-2.5 text-[14px] last:border-b-0 [.flex-row>&]:border-b-0 [.flex-row>&]:grid-cols-[auto_minmax(0,1fr)]", children: [
1488
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: props.label }),
1489
+ /* @__PURE__ */ jsx("span", { className: "text-foreground", children: textOf(props.value) })
1490
+ ] })
1491
+ );
1492
+ }
1493
+ var DATE_STYLES = {
1494
+ long: { weekday: "long", day: "numeric", month: "long", year: "numeric", timeZone: "UTC" },
1495
+ short: { day: "numeric", month: "short", year: "numeric", timeZone: "UTC" },
1496
+ weekday: { weekday: "short", day: "numeric", month: "long", timeZone: "UTC" }
1497
+ };
1498
+ function DateText({ props }) {
1499
+ const date = asDate(props.value);
1500
+ if (!date) return null;
1501
+ const tone = props.tone === "accent" ? cn("text-[13px] font-semibold", linkTextClass) : props.tone === "muted" ? "text-sm text-muted-foreground" : "text-[15px] text-foreground";
1502
+ return /* @__PURE__ */ jsx("span", { className: cn("block", tone), children: date.toLocaleDateString("en-GB", DATE_STYLES[props.style ?? "long"]) });
1503
+ }
1504
+ function TextList({ props }) {
1505
+ const items = Array.isArray(props.items) ? props.items.filter((x) => typeof x === "string" && x.trim()) : [];
1506
+ if (items.length === 0) return null;
1507
+ const marker = props.marker ?? "bullet";
1508
+ const List2 = marker === "number" ? "ol" : "ul";
1509
+ return /* @__PURE__ */ jsx(List2, { className: "flex flex-col gap-2.5", children: items.map((text, index) => /* @__PURE__ */ jsxs("li", { className: "flex gap-3 text-[15px] leading-relaxed text-foreground", children: [
2742
1510
  /* @__PURE__ */ jsx(
2743
- "img",
1511
+ "span",
2744
1512
  {
2745
- src: brand.logo.onDark,
2746
- alt: brand.name,
2747
- referrerPolicy: "no-referrer",
2748
- className: "hidden h-7 w-auto dark:block"
1513
+ "aria-hidden": "true",
1514
+ className: cn(
1515
+ "mt-[0.55em] shrink-0",
1516
+ marker === "bullet" && "size-1.5 rounded-full bg-[color:var(--link,var(--primary,hsl(240_5.9%_10%)))]",
1517
+ marker === "number" && cn("mt-0 min-w-5 text-sm font-semibold tabular-nums", linkTextClass),
1518
+ marker === "check" && cn("mt-0", linkTextClass)
1519
+ ),
1520
+ children: marker === "number" ? `${index + 1}.` : marker === "check" ? "\u2713" : null
2749
1521
  }
2750
1522
  ),
2751
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "h-5 w-px bg-border" }),
2752
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground/80", children: props.app }),
2753
- /* @__PURE__ */ jsxs("div", { className: "ml-auto flex min-w-0 items-center gap-6", children: [
2754
- props.links && props.links.length > 0 ? /* @__PURE__ */ jsx("nav", { "aria-label": "Site", className: "hidden items-center gap-6 sm:flex", children: props.links.map((link) => /* @__PURE__ */ jsx(
2755
- "a",
2756
- {
2757
- href: "#",
2758
- onClick: (event) => event.preventDefault(),
2759
- className: "rounded-sm text-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-hidden",
2760
- children: link
2761
- },
2762
- link
2763
- )) }) : null,
2764
- props.tag ? /* @__PURE__ */ jsx(
2765
- "span",
2766
- {
2767
- className: cn(
2768
- "min-w-0 truncate rounded-lg border px-2.5 py-1 font-mono text-[11px]",
2769
- selectedBorderClass,
2770
- selectedFillClass,
2771
- linkTextClass
2772
- ),
2773
- children: props.tag
2774
- }
2775
- ) : null
2776
- ] })
2777
- ] }) });
1523
+ /* @__PURE__ */ jsx("span", { className: "min-w-0", children: String(text) })
1524
+ ] }, index)) });
1525
+ }
1526
+ var frameRenderers = withDeclaredProps(
1527
+ frameComponents,
1528
+ {
1529
+ TextList: (ctx) => /* @__PURE__ */ jsx(TextList, { ...ctx }),
1530
+ DateText: (ctx) => /* @__PURE__ */ jsx(DateText, { ...ctx }),
1531
+ Composer: (ctx) => /* @__PURE__ */ jsx(Composer, { ...ctx }),
1532
+ FieldGroup: (ctx) => /* @__PURE__ */ jsx(FieldGroup, { ...ctx }),
1533
+ ResultHeader: (ctx) => /* @__PURE__ */ jsx(ResultHeader, { ...ctx }),
1534
+ ProportionBar: (ctx) => /* @__PURE__ */ jsx(ProportionBar, { ...ctx }),
1535
+ Swatches: (ctx) => /* @__PURE__ */ jsx(Swatches, { ...ctx }),
1536
+ Picture: (ctx) => /* @__PURE__ */ jsx(Picture, { ...ctx }),
1537
+ Fact: (ctx) => /* @__PURE__ */ jsx(Fact, { ...ctx }),
1538
+ Detail: (ctx) => /* @__PURE__ */ jsx(Detail, { ...ctx })
1539
+ },
1540
+ // A refused `path` reaches the renderer as refused: its nearest value, the
1541
+ // empty text, is the item itself inside a repeat.
1542
+ { Picture: ["path"] }
1543
+ );
1544
+ var ResultSlotContext = React5.createContext(null);
1545
+ function ResultSlotProvider({
1546
+ slot,
1547
+ children
1548
+ }) {
1549
+ return /* @__PURE__ */ jsx(ResultSlotContext.Provider, { value: slot, children });
1550
+ }
1551
+ function useResultSlot() {
1552
+ return React5.useContext(ResultSlotContext);
1553
+ }
1554
+ var LABEL2 = "block text-[13.5px] leading-none font-medium text-foreground";
1555
+ function AppBar({ props }) {
1556
+ return /* @__PURE__ */ jsx(BrandBar, { app: props.app, links: props.links, tag: props.tag });
2778
1557
  }
2779
1558
  function Hero({ props }) {
2780
1559
  return /* @__PURE__ */ jsxs("section", { className: "pt-12 pb-10 sm:pt-14 sm:pb-12", children: [
@@ -2799,7 +1578,7 @@ function Hero({ props }) {
2799
1578
  ] });
2800
1579
  }
2801
1580
  function Workspace({ props, children }) {
2802
- const [work, rail] = React3.Children.toArray(children);
1581
+ const [work, rail] = React5.Children.toArray(children);
2803
1582
  const railLeft = props.rail === "left";
2804
1583
  return /* @__PURE__ */ jsxs(
2805
1584
  "div",
@@ -2871,22 +1650,24 @@ function Rail({ props, children }) {
2871
1650
  )
2872
1651
  );
2873
1652
  }
2874
- function present(value) {
1653
+ function present2(value) {
2875
1654
  return value !== void 0 && value !== null && value !== "";
2876
1655
  }
2877
1656
  function SummaryRow({ props }) {
2878
- const text = [props.value, props.detail].filter(present).map(String).join(props.separator ?? " ");
1657
+ const text = [props.value, props.detail].filter(present2).map(textOf).join(props.separator ?? " ");
2879
1658
  return /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-4 border-b border-border/60 py-3 first:pt-0 last:border-b-0", children: [
2880
1659
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[13px] text-muted-foreground", children: props.label }),
2881
1660
  text ? /* @__PURE__ */ jsx("span", { className: "text-right text-[13px] font-medium text-foreground", children: text }) : /* @__PURE__ */ jsx("span", { className: "text-[13px] text-muted-foreground/50", children: props.placeholder ?? "\u2014" })
2882
1661
  ] });
2883
1662
  }
2884
1663
  function Cta({ props, emit }) {
1664
+ const busy = useDescriptorScope().env?.disabled ?? false;
2885
1665
  return /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
2886
1666
  /* @__PURE__ */ jsx(
2887
1667
  "button",
2888
1668
  {
2889
1669
  type: "button",
1670
+ disabled: busy,
2890
1671
  onClick: () => emit("press"),
2891
1672
  className: cn(
2892
1673
  "inline-flex h-12 w-full items-center justify-center rounded-lg bg-primary px-6 text-sm font-semibold text-primary-foreground transition-all hover:bg-primary/90",
@@ -2894,7 +1675,8 @@ function Cta({ props, emit }) {
2894
1675
  // it had. Focus is an outline, which no shadow a style sets can
2895
1676
  // replace or hide.
2896
1677
  actionShadowClass,
2897
- focusOutlineClass
1678
+ focusOutlineClass,
1679
+ "disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:bg-primary"
2898
1680
  ),
2899
1681
  children: props.label
2900
1682
  }
@@ -2925,7 +1707,7 @@ function useTextField(props, bindings) {
2925
1707
  props.value ?? void 0,
2926
1708
  bindings?.value
2927
1709
  );
2928
- const [localValue, setLocalValue] = React3.useState("");
1710
+ const [localValue, setLocalValue] = React5.useState("");
2929
1711
  const isBound = Boolean(bindings?.value);
2930
1712
  const value = isBound ? boundValue ?? "" : localValue;
2931
1713
  const setValue = isBound ? setBoundValue : setLocalValue;
@@ -3005,16 +1787,21 @@ function BrandTextarea({
3005
1787
  var { registry: generativeRegistry } = defineRegistry(catalog, {
3006
1788
  components: {
3007
1789
  ...generativeRenderers,
3008
- Input: (ctx) => /* @__PURE__ */ jsx(BrandInput, { ...ctx }),
3009
- Textarea: (ctx) => /* @__PURE__ */ jsx(BrandTextarea, { ...ctx }),
3010
- AppBar: (ctx) => /* @__PURE__ */ jsx(AppBar, { ...ctx }),
3011
- Hero: (ctx) => /* @__PURE__ */ jsx(Hero, { ...ctx }),
3012
- Workspace: (ctx) => /* @__PURE__ */ jsx(Workspace, { ...ctx }),
3013
- Section: (ctx) => /* @__PURE__ */ jsx(Section, { ...ctx }),
3014
- Rail: (ctx) => /* @__PURE__ */ jsx(Rail, { ...ctx }),
3015
- SummaryRow: (ctx) => /* @__PURE__ */ jsx(SummaryRow, { ...ctx }),
3016
- Cta: (ctx) => /* @__PURE__ */ jsx(Cta, { ...ctx }),
3017
- Footer: (ctx) => /* @__PURE__ */ jsx(Footer, { ...ctx })
1790
+ // Held to their definitions as the base and frame renderers are
1791
+ // (`declared-props.ts`).
1792
+ ...withDeclaredProps(catalog.data.components, {
1793
+ Input: (ctx) => /* @__PURE__ */ jsx(BrandInput, { ...ctx }),
1794
+ Textarea: (ctx) => /* @__PURE__ */ jsx(BrandTextarea, { ...ctx }),
1795
+ AppBar: (ctx) => /* @__PURE__ */ jsx(AppBar, { ...ctx }),
1796
+ Hero: (ctx) => /* @__PURE__ */ jsx(Hero, { ...ctx }),
1797
+ Workspace: (ctx) => /* @__PURE__ */ jsx(Workspace, { ...ctx }),
1798
+ Section: (ctx) => /* @__PURE__ */ jsx(Section, { ...ctx }),
1799
+ Rail: (ctx) => /* @__PURE__ */ jsx(Rail, { ...ctx }),
1800
+ SummaryRow: (ctx) => /* @__PURE__ */ jsx(SummaryRow, { ...ctx }),
1801
+ Cta: (ctx) => /* @__PURE__ */ jsx(Cta, { ...ctx }),
1802
+ Footer: (ctx) => /* @__PURE__ */ jsx(Footer, { ...ctx })
1803
+ }),
1804
+ ...frameRenderers
3018
1805
  }
3019
1806
  });
3020
1807
  function GenerativePage({
@@ -3024,109 +1811,85 @@ function GenerativePage({
3024
1811
  onRun,
3025
1812
  loading,
3026
1813
  registry = generativeRegistry,
3027
- brand
1814
+ brand,
1815
+ onElementError
3028
1816
  }) {
3029
1817
  const { inputs, result, env, idPrefix } = scope;
3030
- const stableScope = React3.useMemo(
1818
+ const stableScope = React5.useMemo(
3031
1819
  () => ({ inputs, result, env, idPrefix }),
3032
1820
  [inputs, result, env, idPrefix]
3033
1821
  );
3034
- const latest = React3.useRef({ onRun, store });
3035
- React3.useEffect(() => {
1822
+ const latest = React5.useRef({ onRun, store });
1823
+ React5.useEffect(() => {
3036
1824
  latest.current = { onRun, store };
3037
1825
  });
3038
- const [handlers] = React3.useState(() => ({
3039
- run: () => {
3040
- latest.current.onRun?.(latest.current.store.getSnapshot());
3041
- }
3042
- }));
3043
- const page = /* @__PURE__ */ jsx(DescriptorProvider, { scope: stableScope, children: /* @__PURE__ */ jsx(JSONUIProvider, { registry, store, handlers, children: /* @__PURE__ */ jsx(Renderer, { spec, registry, loading }) }) });
3044
- return brand ? /* @__PURE__ */ jsx(BrandProvider, { manifest: brand, children: page }) : page;
3045
- }
3046
- function useStoreSnapshot(store) {
3047
- return React3.useSyncExternalStore(
3048
- store.subscribe,
3049
- store.getSnapshot,
3050
- store.getServerSnapshot ?? store.getSnapshot
1826
+ const report = React5.useRef(onElementError);
1827
+ React5.useInsertionEffect(() => {
1828
+ report.current = onElementError;
1829
+ });
1830
+ const [made] = React5.useState(() => /* @__PURE__ */ new Map());
1831
+ const pageRegistry = React5.useMemo(
1832
+ () => reportingRegistry(registry, report, made),
1833
+ [registry, made]
3051
1834
  );
3052
- }
3053
-
3054
- // src/generative/state.ts
3055
- function isRecord(value) {
3056
- return typeof value === "object" && value !== null && !Array.isArray(value);
3057
- }
3058
- function payloadToState(field, payload) {
3059
- if (payload === void 0 || payload === null) return payload;
3060
- if (isNativeValueNode(field)) return payload;
3061
- switch (field.kind) {
3062
- case "date": {
3063
- const content = readDateContent(payload);
3064
- return content ? formatDateContent(content) : payload;
3065
- }
3066
- case "list": {
3067
- const items = Array.isArray(payload) ? payload : isRecord(payload) && Array.isArray(payload.items) ? payload.items : void 0;
3068
- if (!items) return payload;
3069
- return items.map((item) => payloadToState(field.item, item));
3070
- }
3071
- case "object": {
3072
- if (!isRecord(payload)) return payload;
3073
- const out = { ...payload };
3074
- for (const child of field.fields) {
3075
- if (hasOwnProp(payload, child.name)) {
3076
- out[child.name] = payloadToState(child, payload[child.name]);
1835
+ const rootType = spec?.elements?.[spec.root]?.type ?? "root";
1836
+ const specContent = React5.useMemo(() => contentOf(spec), [spec]);
1837
+ const reporting = onElementError !== void 0;
1838
+ const running = React5.useRef(false);
1839
+ const [handlers] = React5.useState(() => {
1840
+ const release = () => {
1841
+ setTimeout(() => {
1842
+ running.current = false;
1843
+ }, 0);
1844
+ };
1845
+ return {
1846
+ run: () => {
1847
+ if (running.current) return void 0;
1848
+ running.current = true;
1849
+ let pending;
1850
+ try {
1851
+ pending = latest.current.onRun?.(latest.current.store.getSnapshot());
1852
+ } catch (error) {
1853
+ release();
1854
+ throw error;
1855
+ }
1856
+ if (isThenable(pending)) {
1857
+ const settled = Promise.resolve(pending);
1858
+ settled.then(release, release);
1859
+ return settled;
3077
1860
  }
1861
+ release();
1862
+ return void 0;
3078
1863
  }
3079
- return out;
1864
+ };
1865
+ });
1866
+ const page = /* @__PURE__ */ jsx(DescriptorProvider, { scope: stableScope, children: /* @__PURE__ */ jsx(JSONUIProvider, { registry: pageRegistry, store, handlers, children: /* @__PURE__ */ jsx(ReportingContext.Provider, { value: reporting, children: /* @__PURE__ */ jsx(
1867
+ ElementBoundary,
1868
+ {
1869
+ type: rootType,
1870
+ report,
1871
+ reporting,
1872
+ resetOn: specContent,
1873
+ children: /* @__PURE__ */ jsx(Renderer, { spec, registry: pageRegistry, loading })
3080
1874
  }
3081
- case "document":
3082
- case "image":
3083
- case "text":
3084
- case "prose":
3085
- case "number":
3086
- case "boolean":
3087
- case "enum":
3088
- case "unknown":
3089
- return payload;
3090
- default:
3091
- return field;
3092
- }
1875
+ ) }) }) });
1876
+ return brand ? /* @__PURE__ */ jsx(BrandProvider, { manifest: brand, children: page }) : page;
3093
1877
  }
3094
-
3095
- // src/generative/fixture.ts
3096
- function fixtureId(fixture) {
3097
- return [
3098
- fixture.producer,
3099
- fixture.model,
3100
- fixture.seed ? "seeded" : null,
3101
- fixture.critic ? "critic" : null
3102
- ].filter((part) => part !== null).join("--");
3103
- }
3104
- var PRODUCER_LABEL = {
3105
- "pipelex-method": "Pipelex method",
3106
- "claude-code-subagent": "Claude Code subagent",
3107
- "claude-code-session": "Claude Code session, by hand"
3108
- };
3109
- function fixtureLabel(fixture) {
3110
- const parts = [PRODUCER_LABEL[fixture.producer], fixture.model];
3111
- if (fixture.seed) parts.push("with a seed");
3112
- if (fixture.critic) {
3113
- parts.push(
3114
- `${fixture.critic.model} critic \xD7 ${fixture.critic.rounds} round${fixture.critic.rounds === 1 ? "" : "s"}`
3115
- );
1878
+ function contentOf(spec) {
1879
+ try {
1880
+ return JSON.stringify(spec);
1881
+ } catch {
1882
+ return spec;
3116
1883
  }
3117
- return parts.join(" \xB7 ");
3118
1884
  }
3119
- function findFixture(fixtures, pipeRef, id) {
3120
- const found = fixtures.find(
3121
- (fixture) => fixture.pipeRef === pipeRef && fixtureId(fixture) === id
1885
+ function useStoreSnapshot(store) {
1886
+ return React5.useSyncExternalStore(
1887
+ store.subscribe,
1888
+ store.getSnapshot,
1889
+ store.getServerSnapshot ?? store.getSnapshot
3122
1890
  );
3123
- if (!found) {
3124
- const known = fixtures.filter((fixture) => fixture.pipeRef === pipeRef).map(fixtureId).join(", ");
3125
- throw new Error(`No spec fixture ${id} for ${pipeRef}. Known: ${known || "none"}.`);
3126
- }
3127
- return found;
3128
1891
  }
3129
1892
 
3130
- export { BrandProvider, COMPONENT_NAMES, COMPONENT_RENDERINGS, CUSTOM_COMPONENTS, DESIGNER_BRIEF_CONCEPT, DESIGNER_CATALOG_CONCEPT, DescriptorProvider, GenerativePage, ICON_NAMES, INPUTS_ROOT, METRIC_FORMATS, PICKED_SHADCN, PRODUCT_COMPONENTS, PROMPT_HASH, RESULT_ROOT, ResultSlotProvider, absoluteHatchPath, catalog, designerCatalog, fieldAtSegments, findFixture, fixtureId, fixtureLabel, formatProblems, generativeRegistry, generativeRenderers, inputBrief, inputFieldAtPath, isDelegatedInput, isDelegatedResult, joinPath, jsonlLines, layoutFits, layoutProblems, pathFromDomId, payloadToState, repeatBasePathOf, resultBrief, resultFieldAtPath, segmentsUnder, specFromJsonl, specToJsonl, useBrand, useDescriptorScope, useResultSlot, useStoreSnapshot, validateAgainstCatalog };
1893
+ export { BrandBar, BrandProvider, DescriptorProvider, FrameHostProvider, GenerativePage, ResultSlotProvider, domIdFor, generativeRegistry, generativeRenderers, pathFromDomId, useBrand, useDescriptorScope, useFrameHost, useResultSlot, useStoreSnapshot };
3131
1894
  //# sourceMappingURL=index.js.map
3132
1895
  //# sourceMappingURL=index.js.map