@pipelex/mthds-form 0.8.0 → 0.10.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.
Files changed (66) hide show
  1. package/README.md +9 -3
  2. package/dist/{chunk-4BSBBG2T.js → chunk-4JQAJT7C.js} +3 -3
  3. package/dist/{chunk-4BSBBG2T.js.map → chunk-4JQAJT7C.js.map} +1 -1
  4. package/dist/{chunk-T4NSAGOS.js → chunk-4KGQ65TQ.js} +4 -4
  5. package/dist/{chunk-T4NSAGOS.js.map → chunk-4KGQ65TQ.js.map} +1 -1
  6. package/dist/chunk-CAKQ5VFT.js +35 -0
  7. package/dist/chunk-CAKQ5VFT.js.map +1 -0
  8. package/dist/{chunk-IIU3YK4O.js → chunk-DARLDVJM.js} +3 -3
  9. package/dist/{chunk-IIU3YK4O.js.map → chunk-DARLDVJM.js.map} +1 -1
  10. package/dist/{chunk-25I6JDIK.js → chunk-FABY3DEY.js} +2 -2
  11. package/dist/chunk-FABY3DEY.js.map +1 -0
  12. package/dist/chunk-G6BWZUGV.js +2840 -0
  13. package/dist/chunk-G6BWZUGV.js.map +1 -0
  14. package/dist/{chunk-R7B7ID25.js → chunk-IUWLOSAM.js} +4 -4
  15. package/dist/{chunk-R7B7ID25.js.map → chunk-IUWLOSAM.js.map} +1 -1
  16. package/dist/chunk-TID7ZN63.js +198 -0
  17. package/dist/chunk-TID7ZN63.js.map +1 -0
  18. package/dist/{chunk-XYQ7NXQZ.js → chunk-WYH56SOZ.js} +2 -2
  19. package/dist/chunk-WYH56SOZ.js.map +1 -0
  20. package/dist/chunk-XCO6GTEC.js +38 -0
  21. package/dist/chunk-XCO6GTEC.js.map +1 -0
  22. package/dist/{chunk-YEVVR6FE.js → chunk-Y2ND6W4W.js} +3 -3
  23. package/dist/{chunk-YEVVR6FE.js.map → chunk-Y2ND6W4W.js.map} +1 -1
  24. package/dist/{chunk-J4L32LD6.js → chunk-ZB4RYUKV.js} +5 -5
  25. package/dist/chunk-ZB4RYUKV.js.map +1 -0
  26. package/dist/core/contracts.d.ts +4 -4
  27. package/dist/core/contracts.js +1 -1
  28. package/dist/core/derive.d.ts +1 -0
  29. package/dist/core/derive.js +3 -3
  30. package/dist/core/descriptor.d.ts +13 -2
  31. package/dist/core/descriptor.js +1 -1
  32. package/dist/core/file-formats.d.ts +34 -7
  33. package/dist/core/file-formats.js +1 -1
  34. package/dist/core/gate.js +3 -3
  35. package/dist/core/gating-fields.d.ts +1 -0
  36. package/dist/core/gating-fields.js +2 -2
  37. package/dist/core/index.d.ts +3 -2
  38. package/dist/core/index.js +11 -10
  39. package/dist/core/narrow-file-formats.d.ts +50 -0
  40. package/dist/core/narrow-file-formats.js +4 -0
  41. package/dist/core/narrow-file-formats.js.map +1 -0
  42. package/dist/core/native-content.d.ts +75 -19
  43. package/dist/core/native-content.js +1 -1
  44. package/dist/core/output-form.js +2 -2
  45. package/dist/core/readiness.d.ts +1 -0
  46. package/dist/core/stuff-files.d.ts +1 -0
  47. package/dist/core/stuff-files.js +2 -2
  48. package/dist/core/values.d.ts +1 -0
  49. package/dist/core/values.js +4 -4
  50. package/dist/field-renderer-BSU2hMf6.d.ts +34 -0
  51. package/dist/generative/index.d.ts +2106 -0
  52. package/dist/generative/index.js +3093 -0
  53. package/dist/generative/index.js.map +1 -0
  54. package/dist/layout-design.mthds +452 -0
  55. package/dist/react/index.d.ts +107 -45
  56. package/dist/react/index.js +8 -2614
  57. package/dist/react/index.js.map +1 -1
  58. package/dist/styles.css +1 -1
  59. package/package.json +19 -7
  60. package/dist/chunk-25I6JDIK.js.map +0 -1
  61. package/dist/chunk-2JWRG53S.js +0 -33
  62. package/dist/chunk-2JWRG53S.js.map +0 -1
  63. package/dist/chunk-45C2MKK6.js +0 -94
  64. package/dist/chunk-45C2MKK6.js.map +0 -1
  65. package/dist/chunk-J4L32LD6.js.map +0 -1
  66. package/dist/chunk-XYQ7NXQZ.js.map +0 -1
@@ -0,0 +1,452 @@
1
+ domain = "generative"
2
+ description = "Lays out one page for a method - its inputs, or its result - as a json-render spec over a catalog of components, bound to the state the host provides."
3
+ main_pipe = "design_layout"
4
+
5
+ # The designer method. THIS FILE IS THE PROMPT: every paragraph the models
6
+ # read - the output format, the state contract, the design direction, the
7
+ # rules, the seed procedure, and the brief's own prose - is written here, and
8
+ # the only things rendered into it are the inputs. The catalog arrives as DATA
9
+ # (the components with their props signatures and descriptions, the actions),
10
+ # so a component added to the vocabulary appears in the prompts with no change
11
+ # here, and the prose that names components (the direction, the rules) is the
12
+ # method's own reading of the vocabulary it was written for. The brief arrives
13
+ # as data too: the page's paths with their kinds, presence and constraints,
14
+ # which of them are delegated, what gates the run, and on a result page the
15
+ # state of one real run - and `render_brief` below is the template that lays
16
+ # it out as the Markdown both models read.
17
+ #
18
+ # It runs in three stages, and `design_layout` is the sequence of them:
19
+ #
20
+ # - `render_brief` lays the brief out. It is a Jinja2 template over the data,
21
+ # with no model behind it, and its text is what the two stages below read
22
+ # under `@brief_text`. Every sentence of the brief - the headings, how a
23
+ # path line reads, what "delegated" says - is here and nowhere else.
24
+ # - `plan_page` reads the brief and the catalog - and the creative seed, when
25
+ # there is one - and writes a PagePlan: what the page is for, its title,
26
+ # its composition, its regions with the elements and paths each holds, what
27
+ # runs the method, the defaults and what is delegated. The plan is a
28
+ # structured output, so the runtime holds it to the shape declared below.
29
+ # - `emit_page` builds exactly that page: it is handed the plan, the catalog
30
+ # with every props signature, and the brief, and emits the JSONL patches.
31
+ #
32
+ # The split is the point. A single completion asked for a whole page decides
33
+ # the page and writes every patch in the same breath, and nothing in between
34
+ # can be read; the plan can. The seed reaches the planner only, because the
35
+ # plan is where two runs of the same brief diverge, and the builder follows
36
+ # the plan.
37
+ #
38
+ # Neither prompt prescribes a composition. There is no skeleton a page must
39
+ # have, no region it must contain, no place a control must sit, and the prose
40
+ # names none of the catalog's chrome components: what a component is for is
41
+ # in its own description, which travels as data - and the one control that
42
+ # runs the method is named by the brief, as data, for the same reason. The
43
+ # rules that remain are the ones a page is wrong without - the brief's paths,
44
+ # the bindings that fit, delegation, who owns the state, one control that
45
+ # runs the method, heading order, integrity of the tree. The shape of the
46
+ # page is the planner's decision, written into the plan, and the builder's
47
+ # job is to build it.
48
+ #
49
+ # Three things to know before editing the prompts:
50
+ #
51
+ # - A literal dollar sign is written `$$`, because `$name` is the language's
52
+ # inline-substitution shorthand. Every json-render expression key in the
53
+ # prose (`$$state`, `$$bindState`, `$$item`, `$$cond`, `$$template`, ...)
54
+ # is spelled that way and renders as a single `$`, in the brief's template
55
+ # as much as in the prompts.
56
+ # - The component and action lists, and the plan, are Jinja2 loops in the two
57
+ # prompts, which keep every newline they are written with. A loop body ends
58
+ # with its own newline, so the heading that follows a loop sits directly
59
+ # under `{% endfor %}` to render exactly one blank line between the two.
60
+ # - The brief's template is a `markdown` template, and that category trims:
61
+ # a line holding only a `{% %}` tag renders as nothing, so a loop or a
62
+ # condition is written on lines of its own and the text between them keeps
63
+ # its own newlines. Two traps. A line that ENDS with a `{% %}` tag loses
64
+ # its newline with it, so such a tag is closed with `+%}`, which keeps the
65
+ # newline. And every `{% set %}` is written at the top level of the
66
+ # template, before any text, never inside a loop or a condition: the
67
+ # runtime reads a name set inside a block as an input the pipe did not
68
+ # declare, and refuses the method.
69
+ #
70
+ # The output is Text carrying JSONL patches - the format the prompt asks for -
71
+ # rather than a typed structure: a spec is a map of heterogeneous elements
72
+ # whose props are typed per component, which a structure cannot state and which
73
+ # the renderer already validates per component. The harness that runs this
74
+ # method (scripts/generate-fixtures.mjs --specs) compiles and validates the
75
+ # text, and fails loudly on any issue, so a repair is a change HERE, never to
76
+ # the fixture.
77
+ #
78
+ # Both model stages pin the same model. The specs pass takes MODEL=<id> to
79
+ # override every pin at once for a comparative run (and TEMPERATURE=<n> for a
80
+ # model that fixes its own), and records which model produced each fixture.
81
+ #
82
+ # The optional `seed` is the creative seed of the planner's last section: a
83
+ # random string the harness generates, handed over as one line the prompt
84
+ # labels, so two runs of the same brief diverge. Absent, the planner designs
85
+ # from the brief alone, and the guarded reference below prints nothing.
86
+
87
+ [concept.Catalog]
88
+ description = "The vocabulary a page may be made of: the components a renderer implements and the actions a page may bind to an event, each with its contract."
89
+
90
+ [concept.Catalog.structure]
91
+ components = { type = "list", item_type = "concept", item_concept_ref = "Component", description = "Every component a spec may name, in the order the prompt lists them.", required = true }
92
+ actions = { type = "list", item_type = "concept", item_concept_ref = "Action", description = "Every action a spec may bind, the runtime's built-in ones first.", required = true }
93
+
94
+ [concept.Component]
95
+ description = "One component of the catalog: its name, the signature of its props, what it is for, and what it accepts."
96
+
97
+ [concept.Component.structure]
98
+ name = { type = "text", description = "The component's name, exactly as a spec's \"type\" spells it.", required = true }
99
+ props = { type = "text", description = "The signature of its props as one object literal, each prop with its type and a question mark when it may be omitted.", required = true }
100
+ description = { type = "text", description = "What the component is for and how to use it, as its author wrote it." }
101
+ accepts_children = { type = "boolean", description = "Whether the component renders the elements listed in its \"children\".", required = true }
102
+ slots = { type = "list", item_type = "text", description = "Its named slots, when it has any beyond its children." }
103
+ events = { type = "list", item_type = "text", description = "The events it emits, which a spec binds through the element's \"on\" field." }
104
+
105
+ [concept.Action]
106
+ description = "One action a page may bind to an event: its name, what it does, and whether the runtime handles it on its own."
107
+
108
+ [concept.Action.structure]
109
+ name = { type = "text", description = "The action's name, exactly as a binding spells it.", required = true }
110
+ description = { type = "text", description = "What the action does and the params it takes.", required = true }
111
+ built_in = { type = "boolean", description = "True when the runtime handles the action without a handler from the host.", required = true }
112
+
113
+ [concept.Brief]
114
+ description = "The page's half of the prompt, as data: which page it is and for which pipe, every path in the state with what it is, which paths are delegated and what gates the run, and on a result page the state of one real run - rendered from the method's own descriptor and from nothing else."
115
+
116
+ [concept.Brief.structure]
117
+ side = { choices = ["input", "result"], description = "Which page is being designed: the form a run is started from, or what a run produced.", required = true }
118
+ pipe_ref = { type = "text", description = "The pipe the page is for, as `domain.pipe_code`.", required = true }
119
+ description = { type = "text", description = "One sentence on what the page is about: the pipe's own description, or the host's summary of it." }
120
+ name = { type = "text", description = "On an input page, the method's name as the host lists it - the one product name the page may carry. Absent when the host lists none." }
121
+ paths = { type = "list", item_type = "concept", item_concept_ref = "PathEntry", description = "Every path in the state the page binds or reads, in reading order and flattened: a structure's members follow it one level deeper, and a list's item members follow the list, spelled relative to one item.", required = true }
122
+ run_control = { type = "text", description = "On an input page, the catalog component that runs the method, by name; the page has exactly one of it." }
123
+ sample_state = { type = "text", description = "On a result page, the state of one real run as the host loads it, as JSON - what `/result` held after one actual run, so magnitudes and lengths can be judged. Never copied into a page." }
124
+
125
+ [concept.PathEntry]
126
+ description = "One path of the state, as the brief describes it: where it is, what it holds, what it is called, what constrains it, and whether the page lays it out or hands it back to the kernel's own control."
127
+
128
+ [concept.PathEntry.structure]
129
+ depth = { type = "integer", description = "How deep the path sits under the page's root, for the indentation of its line: 0 for a top-level input or the result itself.", required = true }
130
+ path = { type = "text", description = "The path, spelled exactly as the page binds or reads it: absolute (`/inputs/request/city`), or relative to one item of the list above when `relative` is set (`city`, or `teams/<i>/name` inside a nested list).", required = true }
131
+ kind = { type = "text", description = "What the path holds, in words: `text`, `number`, `integer`, `boolean`, `date`, `prose (markdown)`, `choice of \"a\" | \"b\"`, `document (a file)`, `image (a file)`, `structure <concept>`, `list of <kind>`, or the native concept it carries.", required = true }
132
+ name = { type = "text", description = "The member's name as the method spells it. Absent on the result's own root entry, which is the whole result." }
133
+ title = { type = "text", description = "The member's human title, when the method gives one that differs from its name." }
134
+ description = { type = "text", description = "What the member is for, as the method's author wrote it." }
135
+ required = { type = "boolean", description = "On an input page, whether the input must be filled before the run: true for a required input, false for an optional one." }
136
+ gating = { type = "boolean", description = "On an input page, true when the run waits for this input - a top-level fact only." }
137
+ notes = { type = "list", item_type = "text", description = "The constraints and hints in words, each on its own: `min 1`, `at most 80 characters`, `default \"x\"`, `examples ...`, `hints intent=label`." }
138
+ default = { type = "text", description = "The authored default value, as JSON, when the method states one - the seed a `/state` patch sets." }
139
+ delegated = { type = "boolean", description = "True when the page hands the path back to the kernel's own control - MthdsField on an input page, MthdsResult on a result page - because the catalog cannot enter or show it. A delegated structure's members are not listed.", required = true }
140
+ relative = { type = "boolean", description = "True when the path is spelled relative to one item of the list above it, as a DataTable column or `$item` reads it, rather than from the page's root." }
141
+ item_kind = { type = "text", description = "On a list whose items the brief describes: what one item holds, in the same words as `kind`." }
142
+ item_description = { type = "text", description = "On a list whose items the brief describes: what one item is for, as the method's author wrote it." }
143
+ item_laid_out = { type = "boolean", description = "On a list whose items the brief describes: true when the item's members follow this entry, one each, spelled relative to one item." }
144
+
145
+ [concept.PagePlan]
146
+ description = "The designer's plan for one page, settled before any element is written: what the page is for, how it is composed - a decision the plan makes, with no shape given in advance - what each region shows or collects and with which components, what runs the method, and what is delegated."
147
+
148
+ [concept.PagePlan.structure]
149
+ purpose = { type = "text", description = "One sentence: what this page is FOR and who is standing in front of it. It decides the shape, the order, the grouping and the tone of everything below.", required = true }
150
+ title = { type = "text", description = "The page's title, in a person's words: named after the job - \"Plan the trip\", never \"TripRequest\". It is the page's one h1; where it sits and what carries it is the composition's call.", required = true }
151
+ composition = { type = "text", description = "The shape of the whole page in a few sentences, decided for this page alone: how the space is divided, what comes first and largest, what stands beside what, what is grouped with what and why, what is staged or folded away, and where the method is run on an input page. No shape is given in advance; this is where it is decided.", required = true }
152
+ regions = { type = "list", item_type = "concept", item_concept_ref = "PlannedRegion", description = "The regions of the page in reading order, whatever the composition makes them: the parts of a column, the steps of a journey, the tabs, a panel beside the work, a band of figures. Every element the page lays out sits in one of them.", required = true }
153
+ call_to_action = { type = "text", description = "On an input page, the words on the one control that runs the method - naming the job, \"Plan my trip\", never \"Submit\" - and the hint beside it, if one is needed. Empty on a result page." }
154
+ defaults = { type = "list", item_type = "text", description = "On an input page, the defaults the brief lists under \"Defaults\", one per line as a path and its value. Nothing else: no other /state patch is ever emitted." }
155
+ delegated = { type = "list", item_type = "text", description = "Every path the page delegates whole - to MthdsField on an input page, to MthdsResult on a result page - with a few words on why: the brief marks it delegated, or it is a structure the page chooses not to lay out." }
156
+
157
+ [concept.PlannedRegion]
158
+ description = "One region of the page: what it is titled, if it is titled, what it is for, what it is built with, and the elements inside it, each with the path it binds."
159
+
160
+ [concept.PlannedRegion.structure]
161
+ title = { type = "text", description = "The words the region is titled with, in a person's words, when the composition titles it; otherwise empty." }
162
+ purpose = { type = "text", description = "What this region shows or collects, and why it sits where it does.", required = true }
163
+ container = { type = "text", description = "The component the region is built with, by its catalog name, when the composition has settled it; otherwise empty, and the builder chooses one that builds what the composition describes." }
164
+ elements = { type = "list", item_type = "text", description = "The elements inside it in order, one line each: the component by its catalog name, the path it binds or reads spelled exactly as the brief lists it, and the choices it lists when it lists any. A container names what it holds.", required = true }
165
+
166
+ [pipe.design_layout]
167
+ type = "PipeSequence"
168
+ description = "Design one page from the catalog and the brief: lay the brief out, plan the page from it, then build it as the JSONL patches of a spec bound to the host's state."
169
+ inputs = { catalog = "Catalog", brief = "Brief", seed = "Text?" }
170
+ output = "Text"
171
+ steps = [
172
+ { pipe = "render_brief", result = "brief_text" },
173
+ { pipe = "plan_page", result = "plan" },
174
+ { pipe = "emit_page", result = "page" },
175
+ ]
176
+
177
+ [pipe.render_brief]
178
+ type = "PipeCompose"
179
+ description = "Lay the brief out as the Markdown both model stages read: the page and its pipe, the state and every path in it, what is delegated, and - on an input page - the defaults and what runs the method, or - on a result page - the state of one real run."
180
+ inputs = { brief = "Brief" }
181
+ output = "Text"
182
+
183
+ [pipe.render_brief.template]
184
+ category = "markdown"
185
+ template = """
186
+ {% set hatch = "MthdsField" if brief.side == "input" else "MthdsResult" %}
187
+ {% set delegated = brief.paths | selectattr("delegated") | rejectattr("relative") | list %}
188
+ {% set defaults = brief.paths | selectattr("default") | list %}
189
+ {% set gating = brief.paths | selectattr("gating") | list %}
190
+ # {{ "Input" if brief.side == "input" else "Result" }} page: {{ brief.pipe_ref }}
191
+
192
+ {% if brief.description %}
193
+ {{ brief.description }}
194
+ {% elif brief.side == "input" %}
195
+ A method with the inputs below.
196
+ {% else %}
197
+ The result of a method, described below.
198
+ {% endif %}
199
+
200
+ {% if brief.name %}
201
+ ## Name
202
+
203
+ The method is called «{{ brief.name }}», as the host lists it. That is the app's name wherever the layout asks for one; invent no other. Links, if the layout carries any, name this page's own sections. The page states nothing the method does not: no promise about storage, privacy, speed, or what happens after the run.
204
+
205
+ {% endif %}
206
+ ## State
207
+
208
+ {% if brief.side == "input" %}
209
+ The form's values live under `/inputs`, one member per input, exactly as listed. Bind each input with `$$bindState` at its path; a structure's members bind at their own paths beneath it.
210
+ {% else %}
211
+ The host loads the run's result under `/result` before the page renders. Read every value with `$$state`; emit no `/state` patches. Paths:
212
+ {% endif %}
213
+
214
+ {% for entry in brief.paths %}
215
+ {{ " " * entry.depth }}- `{{ entry.path }}` — {{ entry.kind }}{% if brief.side == "input" %}, {{ "required" if entry.required else "optional" }}{% if entry.gating %}, gates the run{% endif %}{% endif %}{% if entry.name %} — {% if entry.title %}{{ entry.title }} (`{{ entry.name }}`){% else %}`{{ entry.name }}`{% endif %}{% endif %}{% if entry.description %}: {{ entry.description }}{% endif %}{% if entry.notes %} ({{ entry.notes | join("; ") }}){% endif %}{% if entry.delegated and entry.relative %} (a DataTable prints it as text; delegate the whole list to {{ hatch }} to render it properly){% elif entry.delegated %} **[delegate: {{ hatch }}]**{% endif +%}
216
+ {% if entry.item_kind %}
217
+ {{ " " * (entry.depth + 1) }}- each item is a {{ entry.item_kind }}{% if entry.item_description %} ({{ entry.item_description }}){% endif %}{% if entry.item_laid_out and entry.relative %}, with:{% elif entry.item_laid_out %}, with these members (paths relative to one item, for DataTable columns or $$item):{% endif +%}
218
+ {% endif %}
219
+ {% endfor %}
220
+
221
+ ## Delegated
222
+
223
+ {% if delegated %}
224
+ {% if brief.side == "input" %}
225
+ Render these with `MthdsField` at the path, and nothing else:
226
+ {% else %}
227
+ Render these with `MthdsResult` at the path - they are kinds the catalog cannot show properly:
228
+ {% endif %}
229
+
230
+ {% for entry in delegated %}
231
+ - `{{ entry.path }}` ({{ entry.kind }})
232
+ {% endfor %}
233
+ {% elif brief.side == "input" %}
234
+ None: every input can be entered with the catalog's own inputs. `MthdsField` remains available for any you would rather not style.
235
+ {% else %}
236
+ None. `MthdsResult` remains available for any structure you choose not to lay out.
237
+ {% endif %}
238
+ {% if brief.side == "input" %}
239
+
240
+ ## Defaults
241
+
242
+ {% if defaults %}
243
+ Seed these, and only these, with `/state` patches (`{"op":"add","path":"/state/inputs/...","value":...}`):
244
+
245
+ {% for entry in defaults %}
246
+ - `{{ entry.path }}` = {{ entry.default }}
247
+ {% endfor %}
248
+ {% else %}
249
+ None. Emit no `/state` patches.
250
+ {% endif %}
251
+
252
+ ## Run
253
+
254
+ {% if gating %}
255
+ The run waits for `{{ gating | map(attribute="name") | join("`, `") }}`; say so near the {{ brief.run_control }}, briefly.
256
+ {% else %}
257
+ Nothing gates the run.
258
+ {% endif %}
259
+ The page has exactly one `{{ brief.run_control }}`, `on.press` bound to `validateForm` then `run`; label it with what the method does, in a person's words.
260
+ {% else %}
261
+
262
+ ## One real run, as loaded
263
+
264
+ The state of one actual run, so you can judge magnitudes and lengths. Bind to the paths; do not copy these values into the spec.
265
+
266
+ ```json
267
+ {{ brief.sample_state }}
268
+ ```
269
+ {% endif %}
270
+ """
271
+
272
+ [pipe.plan_page]
273
+ type = "PipeLLM"
274
+ description = "Read the brief and plan the page: what it is for, how it is composed, what each region holds and with which components, what runs the method, and what is delegated."
275
+ inputs = { catalog = "Catalog", brief_text = "Text", seed = "Text?" }
276
+ output = "PagePlan"
277
+ model = { model = "claude-5-sonnet", temperature = 0.5, max_tokens = 8192 }
278
+ system_prompt = """
279
+ You are a product designer with taste. You plan ONE page for a method - its inputs, or its result - to be built from a fixed catalog of components and bound to state the host provides. The page must read as a small, purpose-built web app, never as a form. Its shape is yours to decide: nothing here prescribes one. You write the plan and a second designer builds the page from it, so every decision you leave open is one they will make without you.
280
+ """
281
+ prompt = """
282
+ YOUR JOB:
283
+ Plan the page; do not build it. Decide what the page is for and name it after the job, decide its composition, list its regions with the elements each holds and the path each element binds, write the words on the control that runs the method, and name what is delegated. Be concrete: a component by its catalog name, a path exactly as the brief spells it, a choice list exactly as the brief lists it. Write nothing the builder would have to guess at, and nothing they do not need.
284
+
285
+ STATE:
286
+ The host owns the state. The brief lists every path in it, with its kind and meaning, and it is the ONLY source of paths: every value the page shows or binds is at a path the brief lists, spelled exactly as written. On a result page the host loads /result before the page renders and the page sets no state of its own. On an input page the form's values live under /inputs, and the only state the page sets is the defaults the brief lists.
287
+
288
+ DESIGN DIRECTION:
289
+ - You are designing a small web app, not a form. A person should open this page and feel it was built for this one job by a studio that cares - the way a product built on Lovable, or by a design-led startup, feels - and never that a schema was transcribed into fields.
290
+ - Start from the feeling. Decide in one sentence what this page is FOR and who is standing in front of it; let that decide the shape, the order, the grouping and the tone. Name the page after the job, not the concept: "Plan the trip", not "TripRequest".
291
+ - The composition is yours. The catalog is the whole vocabulary and no arrangement of it is prescribed: how the space is divided, what comes first and largest, what stands beside what, what is staged or folded away, where the method is run - decide each for this page, from its purpose, and write the decision down. A component is there to be used when it serves the page and left alone when it does not.
292
+ - Prefer the control that feels like an app: Segmented for a choice of up to five options, Radio for a choice that needs a word of explanation, Switch for a yes/no that changes the page, Checkbox for one that does not, NumberInput for a figure, Textarea for prose, Input for a short text. Use Select only when the options do not fit on one line.
293
+ - Restraint reads premium. No label that restates the obvious, no card inside a card, no paragraph that explains the form, no more than two levels of heading. Every element must earn its place; when in doubt, remove it.
294
+ - Write copy a person would say: short labels, one helper sentence where a label is not enough and none where it is. The control that runs names the job ("Plan my trip"), never "Submit".
295
+ - Every value binds to a path the brief lists, and the page reads clearly in both a light and a dark theme.
296
+
297
+ AVAILABLE COMPONENTS ({{ catalog.components | length }}):
298
+
299
+ {% for component in catalog.components -%}
300
+ - {{ component.name }}{% if component.description %}: {{ component.description }}{% endif %}
301
+ {%- if component.accepts_children and component.slots %} [accepts children; slots: {{ component.slots | join(", ") }}]
302
+ {%- elif component.accepts_children %} [accepts children]
303
+ {%- elif component.slots %} [slots: {{ component.slots | join(", ") }}]{% endif %}
304
+ {%- if component.events %} [events: {{ component.events | join(", ") }}]{% endif %}
305
+ {% endfor %}
306
+ PLANNING RULES:
307
+ 1. The brief is the ONLY source of paths. Every element binds a path the brief lists, exactly as written; the page shows nothing the state does not carry, and invents no data.
308
+ 2. Delegate the paths the brief marks as delegated: MthdsField for an input, MthdsResult for a result, at the path and nothing else. Never plan another component over a delegated path.
309
+ 3. Lay out what you understand and delegate what you do not. A structure you choose not to lay out is delegated whole by its path; that is a valid choice, not a failure.
310
+ 4. Bind only what fits: a Segmented, Radio or Select lists exactly the brief's choices, spelled exactly; a NumberInput takes a number path; a Switch or Checkbox a boolean path; an Input a text path; a Textarea a prose path. A component that shows a value - a Metric, a Text, a Heading, a line that restates one - reads a scalar path: text, prose, number, boolean, date or choice, never a structure, a list or a file. A structure is shown through its members, a list through a repeat or a DataTable, a file through the delegated control.
311
+ 5. Every input the run waits for is on the page, in some region: laid out with the catalog's inputs, or delegated with MthdsField at its path. A Step or a Tab hides an input from the eye, never from the run: every input sits in some panel.
312
+ 6. Tabs and Steps hold exactly one panel per tab or step, in the same order, usually a Stack. Their own state - the active tab, the current step, an open Collapsible - is theirs, never the host's.
313
+ 7. An input page has exactly ONE control that runs the method, wherever the composition puts it, and no other Button: a Button bound to nothing does nothing, and navigation is never built out of Buttons, since Steps and Tabs carry their own.
314
+ 8. On an input page the only state the page sets is the defaults the brief lists under "Defaults"; on a result page it sets none.
315
+
316
+ CREATIVE SEED:
317
+ When a creative seed follows the brief, it is your ONLY source of variety. Read it the way a designer reads a found object: its runs of letters, its rare characters, its numbers, its rhythm. Derive from it a creative direction - the composition, the density, the tone of the copy, which controls you favour, where the emphasis sits - and commit to it.
318
+ Never show, quote or mention the seed. Two designers given different seeds must produce visibly different pages from the same brief; a designer given no seed designs from the brief alone.
319
+
320
+ ---
321
+
322
+ Below is the brief for the page you are planning. It is the ONLY source of paths: every value the page shows or binds must be at a path the brief lists, spelled exactly as written.
323
+
324
+ @brief_text
325
+
326
+ ---
327
+
328
+ @?seed
329
+
330
+ ---
331
+
332
+ Plan this page.
333
+ """
334
+
335
+ [pipe.emit_page]
336
+ type = "PipeLLM"
337
+ description = "Build the page the plan describes: emit the JSONL patches of a spec over the catalog, bound to the host's state at the paths the brief lists."
338
+ inputs = { plan = "PagePlan", catalog = "Catalog", brief_text = "Text" }
339
+ output = "Text"
340
+ # The object form, because the default output cap (4096 tokens on this model)
341
+ # is not enough for a page over a deeply nested result: the Company hero's
342
+ # first run spent exactly 4096 tokens and came back empty. The object form
343
+ # requires a temperature; this is the runtime's own default, so the setting
344
+ # the earlier captures ran under is unchanged.
345
+ model = { model = "claude-5-sonnet", temperature = 0.5, max_tokens = 16384 }
346
+ system_prompt = """
347
+ You are a product designer with taste, building ONE page for a method - its inputs, or its result - as a JSON spec over a fixed catalog of components, bound to state the host provides. A designer has planned the page; you build exactly that page, and it must read as a small, purpose-built web app, never as a form.
348
+ """
349
+ prompt = """
350
+ OUTPUT FORMAT (JSONL, RFC 6902 JSON Patch):
351
+ Output ONLY JSONL: one JSON object per line, no prose, no markdown, no code fences. Each line is a patch operation building the spec.
352
+ First set /root, then add each element under /elements/<key>, parents before children, so the page fills in progressively as it streams.
353
+
354
+ Example output (each line is a separate JSON object):
355
+
356
+ {"op":"add","path":"/root","value":"page"}
357
+ {"op":"add","path":"/elements/page","value":{"type":"Stack","props":{"direction":"vertical","gap":"lg"},"children":["title","total"]}}
358
+ {"op":"add","path":"/elements/title","value":{"type":"Heading","props":{"text":{"$$state":"/result/reference"}},"children":[]}}
359
+ {"op":"add","path":"/elements/total","value":{"type":"Metric","props":{"label":"Total","value":{"$$state":"/result/total"},"format":"decimal"},"children":[]}}
360
+
361
+ STATE:
362
+ The host owns the state. The brief you are given lists every path in it, with its kind and meaning. Read a value with { "$$state": "/path" }; bind an input two-way with { "$$bindState": "/path" }. Inside a repeated element, read the current item with { "$$item": "field" } and bind with { "$$bindItem": "field" }.
363
+ Do NOT invent data. On a result page, never emit /state patches: the host loads /result. On an input page, /state patches are for the listed defaults only.
364
+
365
+ DYNAMIC LISTS (repeat field):
366
+ An element with a top-level "repeat": { "statePath": "/path/to/array", "key": "<id field>" } renders its children once per array item. For an array held on the enclosing item, use "repeat": { "statePath": { "$$item": "field" } } - valid only inside another repeat. "repeat" is a field of the element, not of "props".
367
+ A DataTable is the simpler way to show a list of structures: bind its rows once and name the columns. Use repeat when each item deserves its own card.
368
+
369
+ DYNAMIC PROPS:
370
+ 1. Read-only value: { "$$state": "/path" }. 2. Two-way input binding: { "$$bindState": "/path" } on value/checked. 3. Conditional: { "$$cond": <condition>, "$$then": <value>, "$$else": <value> }, where a condition is { "$$state": "/path" } (truthy), with optional "eq", "neq", "gt", "gte", "lt", "lte" or "not": true. 4. Template: { "$$template": "Invoice $${/result/reference}" } interpolates state paths; inside a repeat, bare names read the item.
371
+
372
+ THE PLAN:
373
+ A designer has read the brief and planned this page: its purpose, its title, its composition, and its regions in reading order with the elements each holds. Build the page the plan describes: the composition it wrote is the shape of the page; each region holds the elements it names, with the components it names, bound to the paths it names; where a region names no container, choose the one from the catalog that builds what the composition describes. Where the plan is silent - a label, a helper sentence, a gap, a column count - decide as they would have, from the purpose and the composition they wrote. Do not add a region, drop an element or rebind a path; where the plan names a path the brief does not list, the brief wins.
374
+
375
+ Purpose: {{ plan.purpose }}
376
+ Title (the page's one h1): {{ plan.title }}
377
+ Composition: {{ plan.composition }}
378
+
379
+ REGIONS:
380
+ {% for region in plan.regions %}
381
+ {{ loop.index }}. {% if region.title %}{{ region.title }} - {% endif %}{{ region.purpose }}{% if region.container %} [{{ region.container }}]{% endif %}
382
+ {% for element in region.elements -%}
383
+ - {{ element }}
384
+ {% endfor -%}
385
+ {% endfor -%}
386
+ {% if plan.call_to_action %}
387
+ CALL TO ACTION: {{ plan.call_to_action }}
388
+ {% endif -%}
389
+ {% if plan.defaults %}
390
+ DEFAULTS (the only /state patches):
391
+ {% for line in plan.defaults -%}
392
+ - {{ line }}
393
+ {% endfor -%}
394
+ {% endif -%}
395
+ {% if plan.delegated %}
396
+ DELEGATED:
397
+ {% for line in plan.delegated -%}
398
+ - {{ line }}
399
+ {% endfor -%}
400
+ {% endif %}
401
+ EXECUTION:
402
+ - Restraint reads premium. No label that restates the obvious, no card inside a card, no paragraph that explains the form, no more than two levels of heading. Every element must earn its place; when in doubt, remove it.
403
+ - Write copy a person would say: short labels, one helper sentence where a label is not enough and none where it is. The control that runs names the job ("Plan my trip"), never "Submit".
404
+ - Every value binds to a path the brief lists, and the page reads clearly in both a light and a dark theme.
405
+
406
+ AVAILABLE COMPONENTS ({{ catalog.components | length }}):
407
+
408
+ {% for component in catalog.components -%}
409
+ - {{ component.name }}: {{ component.props }}
410
+ {%- if component.description %} - {{ component.description }}{% endif %}
411
+ {%- if component.accepts_children and component.slots %} [accepts children; slots: {{ component.slots | join(", ") }}]
412
+ {%- elif component.accepts_children %} [accepts children]
413
+ {%- elif component.slots %} [slots: {{ component.slots | join(", ") }}]{% endif %}
414
+ {%- if component.events %} [events: {{ component.events | join(", ") }}]{% endif %}
415
+ {% endfor %}
416
+ AVAILABLE ACTIONS:
417
+
418
+ {% for action in catalog.actions -%}
419
+ - {{ action.name }}: {{ action.description }}{% if action.built_in %} [built-in]{% endif %}
420
+ {% endfor %}
421
+ EVENTS (the `on` field):
422
+ Bind an event to actions with a top-level "on" field on the element: "on": { "press": [{ "action": "validateForm" }, { "action": "run" }] }. A single binding may be an object instead of an array. Never put actions inside "props".
423
+
424
+ VISIBILITY (the `visible` field):
425
+ A top-level "visible" field hides an element unless its condition holds: "visible": { "$$state": "/result/paid", "eq": true }. Conditions take exactly one of eq/neq/gt/gte/lt/lte, or "not": true; an array of conditions is an AND; { "$$or": [...] } is an OR.
426
+
427
+ RULES:
428
+ 1. INTEGRITY: every key in every "children" array must be emitted as its own element. A missing child makes that whole branch of the page invisible. After emitting the tree, walk it from the root and emit any element you referenced but did not define.
429
+ 2. Every element is {"type", "props", "children"}; "children" is an array of element keys, empty for a leaf. "visible", "repeat" and "on" are fields of the ELEMENT, never of "props".
430
+ 3. ONLY use component types from the AVAILABLE COMPONENTS list. Use unique, descriptive element keys ("header", "total-metric", "lines-table").
431
+ 4. The brief is the ONLY source of paths. Bind every value to a path the brief lists, exactly as written, and inline no value that the state carries.
432
+ 5. On a RESULT page, emit no /state patches at all: the host loads /result before the page renders. Read with { "$$state": "/result/..." }.
433
+ 6. On an INPUT page, bind every input with { "$$bindState": "/inputs/..." }, and emit /state patches ONLY for the defaults the brief lists under "Defaults" - nothing else. The page's own UI state (the active tab, the current step, an open section) lives inside Tabs, Steps and Collapsible, never in /state.
434
+ 7. Delegate the paths the brief marks as delegated: MthdsField for an input, MthdsResult for a result, naming the path and nothing else. Never re-describe a delegated field with other components.
435
+ 8. Lay out what you understand and delegate what you do not. A structure you choose not to lay out is delegated whole by its path; that is a valid choice, not a failure.
436
+ 9. An input page has exactly ONE control that runs the method, wherever the plan puts it, and it fires validateForm then run, in that order, on press. No other Button: a Button bound to nothing does nothing, and navigation is never built out of Buttons, since Steps and Tabs carry their own.
437
+ 10. Tabs and Steps take exactly one child per tab or step, in the same order; that child is the panel, usually a Stack. A Step hides an input from the eye, never from the run: every input the run waits for must sit in some panel.
438
+ 11. Bind only what fits: a Segmented, Radio or Select lists exactly the brief's choices, spelled exactly; a NumberInput binds a number path; a Switch or Checkbox binds a boolean path; an Input binds a text path; a Textarea a prose path. A prop that shows a value - a Metric's value, a Text's text, a Heading's text, the value of a line that restates one, an interpolation in a $$template - reads a scalar path: text, prose, number, boolean, date or choice, never a structure, a list or a file. A structure is shown through its members, a list through a repeat or a DataTable, a file through MthdsField or MthdsResult.
439
+ 12. One root element, and it is a container that accepts children.
440
+ 13. The plan's title is on the page as its one h1 - a Heading at level h1, or the headline of a component whose description says it renders an h1 - and no other h1. Below it, heading levels increase by one and never skip: h2, then h3. A component whose description says it renders a heading counts at that level, so a Heading inside it starts one level down.
441
+ 14. Never set a "className" prop. Colour, spacing and type come from the components as they are.
442
+
443
+ ---
444
+
445
+ Below is the brief for the page you are building. It is the ONLY source of paths: every value you show or bind must be at a path the brief lists, spelled exactly as written.
446
+
447
+ @brief_text
448
+
449
+ ---
450
+
451
+ Build this page as the plan describes. Bind every value; inline nothing the state carries. Output only the JSONL patch lines - no prose before or after, no code fence.
452
+ """