markform 0.1.4 → 0.1.5

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 (28) hide show
  1. package/README.md +40 -53
  2. package/dist/ai-sdk.d.mts +1 -1
  3. package/dist/ai-sdk.mjs +1 -1
  4. package/dist/{apply-C54EMAJ1.mjs → apply-BCCiJzQr.mjs} +3 -3
  5. package/dist/bin.mjs +3 -3
  6. package/dist/{cli-BhWhn6L9.mjs → cli-D469amuk.mjs} +5 -11
  7. package/dist/cli.mjs +3 -3
  8. package/dist/{coreTypes-cbNTYAcb.d.mts → coreTypes-9XZSNOv6.d.mts} +2 -2
  9. package/dist/index.d.mts +1 -1
  10. package/dist/index.mjs +2 -2
  11. package/dist/{src-BNh7Cx9P.mjs → src-Df0XX7UB.mjs} +19 -6
  12. package/docs/markform-reference.md +19 -19
  13. package/docs/markform-spec.md +20 -20
  14. package/examples/earnings-analysis/earnings-analysis.form.md +86 -808
  15. package/examples/earnings-analysis/earnings-analysis.valid.ts +16 -148
  16. package/examples/movie-research/movie-research-basic.form.md +16 -16
  17. package/examples/movie-research/movie-research-deep.form.md +36 -36
  18. package/examples/movie-research/movie-research-minimal.form.md +4 -4
  19. package/examples/simple/simple-mock-filled.form.md +16 -16
  20. package/examples/simple/simple-skipped-filled.form.md +16 -16
  21. package/examples/simple/simple-with-skips.session.yaml +3 -3
  22. package/examples/simple/simple.form.md +16 -16
  23. package/examples/simple/simple.session.yaml +3 -3
  24. package/examples/startup-deep-research/startup-deep-research.form.md +22 -22
  25. package/examples/startup-research/startup-research-mock-filled.form.md +12 -12
  26. package/examples/startup-research/startup-research.form.md +12 -12
  27. package/package.json +1 -1
  28. package/examples/celebrity-deep-research/celebrity-deep-research.form.md +0 -967
@@ -4,9 +4,9 @@
4
4
  * These validators demonstrate parameterized validation patterns.
5
5
  * Parameters are passed via ctx.params from the validate attribute:
6
6
  *
7
- * validate=[{id: "min_words", min: 50}]
7
+ * validate=[{id: "min_words", min: 20}]
8
8
  *
9
- * The validator receives { min: 50 } in ctx.params.
9
+ * The validator receives { min: 20 } in ctx.params.
10
10
  */
11
11
 
12
12
  import type { ValidatorContext, ValidationIssue } from 'markform';
@@ -28,27 +28,17 @@ function getStringValue(values: Record<string, unknown>, fieldId: string): strin
28
28
  return field?.kind === 'string' && field.value ? field.value : null;
29
29
  }
30
30
 
31
- function getNumberValue(values: Record<string, unknown>, fieldId: string): number | null {
32
- const field = values[fieldId] as { kind: string; value?: number | null } | undefined;
33
- return field?.kind === 'number' && field.value != null ? field.value : null;
34
- }
35
-
36
31
  function getStringListItems(values: Record<string, unknown>, fieldId: string): string[] {
37
32
  const field = values[fieldId] as { kind: string; items?: string[] } | undefined;
38
33
  return field?.kind === 'string_list' && field.items ? field.items : [];
39
34
  }
40
35
 
41
- function getMultiSelectSelections(values: Record<string, unknown>, fieldId: string): string[] {
42
- const field = values[fieldId] as { kind: string; selected?: string[] } | undefined;
43
- return field?.kind === 'multi_select' && field.selected ? field.selected : [];
44
- }
45
-
46
36
  function getSingleSelectValue(values: Record<string, unknown>, fieldId: string): string | null {
47
37
  const field = values[fieldId] as { kind: string; selected?: string | null } | undefined;
48
38
  return field?.kind === 'single_select' && field.selected ? field.selected : null;
49
39
  }
50
40
 
51
- // Parameterized Validators
41
+ // Validators
52
42
 
53
43
  /**
54
44
  * Validate minimum word count.
@@ -118,45 +108,6 @@ function maxWords(ctx: ValidatorContext): ValidationIssue[] {
118
108
  return [];
119
109
  }
120
110
 
121
- /**
122
- * Validate that specified number fields sum to a target.
123
- * Params: { fields: string[], target?: number, tolerance?: number }
124
- */
125
- function sumTo(ctx: ValidatorContext): ValidationIssue[] {
126
- const fields = ctx.params.fields as string[];
127
- const target = (ctx.params.target as number) ?? 100;
128
- const tolerance = (ctx.params.tolerance as number) ?? 0.1;
129
-
130
- if (!Array.isArray(fields)) {
131
- return [
132
- {
133
- severity: 'error',
134
- message: 'sum_to requires "fields" array parameter',
135
- ref: ctx.targetId,
136
- source: 'code',
137
- },
138
- ];
139
- }
140
-
141
- const values = fields.map((fieldId) => getNumberValue(ctx.values, fieldId) ?? 0);
142
- const sum = values.reduce((a, b) => a + b, 0);
143
-
144
- // Only validate if at least one value is set
145
- if (values.every((v) => v === 0)) return [];
146
-
147
- if (Math.abs(sum - target) > tolerance) {
148
- return [
149
- {
150
- severity: 'error',
151
- message: `Fields must sum to ${target}% (currently ${sum.toFixed(1)}%)`,
152
- ref: fields[0],
153
- source: 'code',
154
- },
155
- ];
156
- }
157
- return [];
158
- }
159
-
160
111
  /**
161
112
  * Validate that string-list items with "Label: XX%" format sum to target.
162
113
  * Params: { target?: number }
@@ -197,117 +148,38 @@ function sumToPercentList(ctx: ValidatorContext): ValidationIssue[] {
197
148
  }
198
149
 
199
150
  /**
200
- * Require field when another field has a value.
201
- * Params: { when: string, then?: string }
151
+ * Require field when another field has any value set.
152
+ * Params: { when: string }
202
153
  */
203
- function requiredIf(ctx: ValidatorContext): ValidationIssue[] {
154
+ function requiredIfSet(ctx: ValidatorContext): ValidationIssue[] {
204
155
  const triggerField = ctx.params.when as string;
205
- const targetField = (ctx.params.then as string) ?? ctx.targetId;
206
156
 
207
157
  if (!triggerField) {
208
158
  return [
209
159
  {
210
160
  severity: 'error',
211
- message: 'required_if requires "when" parameter',
161
+ message: 'required_if_set requires "when" parameter',
212
162
  ref: ctx.targetId,
213
163
  source: 'code',
214
164
  },
215
165
  ];
216
166
  }
217
167
 
218
- const trigger = ctx.values[triggerField] as Record<string, unknown> | undefined;
219
- const target = ctx.values[targetField] as Record<string, unknown> | undefined;
220
-
168
+ // Check if trigger field has a value
221
169
  const triggerHasValue =
222
- (trigger?.kind === 'string' && (trigger.value as string)?.trim()) ||
223
- (trigger?.kind === 'number' && trigger.value != null) ||
224
- (trigger?.kind === 'multi_select' && (trigger.selected as string[])?.length > 0);
170
+ getStringValue(ctx.values, triggerField) !== null ||
171
+ getSingleSelectValue(ctx.values, triggerField) !== null ||
172
+ getStringListItems(ctx.values, triggerField).length > 0;
225
173
 
226
- const targetEmpty =
227
- !target ||
228
- (target.kind === 'string' && !(target.value as string)?.trim()) ||
229
- (target.kind === 'number' && target.value == null);
174
+ // Check if target field is empty
175
+ const targetValue = getStringValue(ctx.values, ctx.targetId);
176
+ const targetEmpty = !targetValue || targetValue.trim().length === 0;
230
177
 
231
178
  if (triggerHasValue && targetEmpty) {
232
179
  return [
233
180
  {
234
181
  severity: 'error',
235
- message: `This field is required when ${triggerField} has a value`,
236
- ref: targetField,
237
- source: 'code',
238
- },
239
- ];
240
- }
241
- return [];
242
- }
243
-
244
- /**
245
- * Require field when another field equals a specific value.
246
- * Params: { when: string, equals: string, then?: string }
247
- */
248
- function requiredIfEquals(ctx: ValidatorContext): ValidationIssue[] {
249
- const triggerField = ctx.params.when as string;
250
- const expectedValue = ctx.params.equals as string;
251
- const targetField = (ctx.params.then as string) ?? ctx.targetId;
252
-
253
- if (!triggerField || expectedValue === undefined) {
254
- return [
255
- {
256
- severity: 'error',
257
- message: 'required_if_equals requires "when" and "equals" parameters',
258
- ref: ctx.targetId,
259
- source: 'code',
260
- },
261
- ];
262
- }
263
-
264
- const triggerValue = getSingleSelectValue(ctx.values, triggerField);
265
- const target = getStringValue(ctx.values, targetField);
266
-
267
- if (triggerValue === expectedValue && (!target || target.trim().length === 0)) {
268
- return [
269
- {
270
- severity: 'error',
271
- message: `This field is required when ${triggerField} is "${expectedValue}"`,
272
- ref: targetField,
273
- source: 'code',
274
- },
275
- ];
276
- }
277
- return [];
278
- }
279
-
280
- /**
281
- * Validate that list items match a regex pattern.
282
- * Params: { pattern: string, example?: string }
283
- */
284
- function itemFormat(ctx: ValidatorContext): ValidationIssue[] {
285
- const pattern = ctx.params.pattern as string;
286
- const example = (ctx.params.example as string) ?? '';
287
-
288
- if (!pattern) {
289
- return [
290
- {
291
- severity: 'error',
292
- message: 'item_format requires "pattern" parameter',
293
- ref: ctx.targetId,
294
- source: 'code',
295
- },
296
- ];
297
- }
298
-
299
- const items = getStringListItems(ctx.values, ctx.targetId);
300
- if (items.length === 0) return [];
301
-
302
- const regex = new RegExp(pattern);
303
- const malformed = items.filter((item) => !regex.test(item));
304
-
305
- if (malformed.length > 0) {
306
- const hint = example ? ` Expected format: "${example}"` : '';
307
- return [
308
- {
309
- severity: 'warning',
310
- message: `${malformed.length} item(s) don't match expected format.${hint}`,
182
+ message: `This field is required when "${triggerField}" has a value`,
311
183
  ref: ctx.targetId,
312
184
  source: 'code',
313
185
  },
@@ -319,12 +191,8 @@ function itemFormat(ctx: ValidatorContext): ValidationIssue[] {
319
191
  // Exported Validators Registry
320
192
 
321
193
  export const validators: Record<string, (ctx: ValidatorContext) => ValidationIssue[]> = {
322
- // Parameterized validators
323
194
  min_words: minWords,
324
195
  max_words: maxWords,
325
- sum_to: sumTo,
326
196
  sum_to_percent_list: sumToPercentList,
327
- required_if: requiredIf,
328
- required_if_equals: requiredIfEquals,
329
- item_format: itemFormat,
197
+ required_if_set: requiredIfSet,
330
198
  };
@@ -34,7 +34,7 @@ markform:
34
34
  Standard research form for gathering ratings and key statistics for any film. Pulls from IMDB, Rotten Tomatoes, and Metacritic.
35
35
  {% /description %}
36
36
 
37
- {% field-group id="movie_input" title="Movie Identification" %}
37
+ {% group id="movie_input" title="Movie Identification" %}
38
38
 
39
39
  {% field kind="string" id="movie" label="Movie" role="user" required=true minLength=1 maxLength=300 %}{% /field %}
40
40
 
@@ -42,9 +42,9 @@ Standard research form for gathering ratings and key statistics for any film. Pu
42
42
  Enter the movie title (add any details to help identify, like "Barbie 2023" or "the Batman movie with Robert Pattinson")
43
43
  {% /instructions %}
44
44
 
45
- {% /field-group %}
45
+ {% /group %}
46
46
 
47
- {% field-group id="title_identification" title="Title Identification" %}
47
+ {% group id="title_identification" title="Title Identification" %}
48
48
 
49
49
  {% field kind="string" id="full_title" label="Full Title" role="agent" required=true %}{% /field %}
50
50
 
@@ -52,9 +52,9 @@ Enter the movie title (add any details to help identify, like "Barbie 2023" or "
52
52
  Look up what film the user had in mind and fill in the official title including subtitle if any (e.g., "The Lord of the Rings: The Fellowship of the Ring").
53
53
  {% /instructions %}
54
54
 
55
- {% /field-group %}
55
+ {% /group %}
56
56
 
57
- {% field-group id="sources" title="Sources" %}
57
+ {% group id="sources" title="Sources" %}
58
58
 
59
59
  {% field kind="url" id="imdb_url" label="IMDB URL" role="agent" required=true %}{% /field %}
60
60
 
@@ -74,9 +74,9 @@ Direct link to the movie's Rotten Tomatoes page.
74
74
  Direct link to the movie's Metacritic page.
75
75
  {% /instructions %}
76
76
 
77
- {% /field-group %}
77
+ {% /group %}
78
78
 
79
- {% field-group id="basic_details" title="Basic Details" %}
79
+ {% group id="basic_details" title="Basic Details" %}
80
80
 
81
81
  {% field kind="number" id="year" label="Release Year" role="agent" required=true min=1888 max=2030 %}{% /field %}
82
82
 
@@ -97,9 +97,9 @@ One director per line. Most films have one; some have two or more co-directors.
97
97
  - [ ] NR/Unrated {% #nr %}
98
98
  {% /field %}
99
99
 
100
- {% /field-group %}
100
+ {% /group %}
101
101
 
102
- {% field-group id="imdb_ratings" title="IMDB Ratings" %}
102
+ {% group id="imdb_ratings" title="IMDB Ratings" %}
103
103
 
104
104
  {% field kind="number" id="imdb_rating" label="IMDB Rating" role="agent" min=1.0 max=10.0 %}{% /field %}
105
105
 
@@ -113,9 +113,9 @@ IMDB user rating (1.0-10.0 scale).
113
113
  Number of IMDB user votes (e.g., 2800000 for a popular film).
114
114
  {% /instructions %}
115
115
 
116
- {% /field-group %}
116
+ {% /group %}
117
117
 
118
- {% field-group id="rotten_tomatoes_ratings" title="Rotten Tomatoes Ratings" %}
118
+ {% group id="rotten_tomatoes_ratings" title="Rotten Tomatoes Ratings" %}
119
119
 
120
120
  {% field kind="number" id="rt_critics_score" label="Tomatometer (Critics)" role="agent" min=0 max=100 %}{% /field %}
121
121
 
@@ -131,9 +131,9 @@ Tomatometer percentage (0-100).
131
131
  Audience Score percentage (0-100).
132
132
  {% /instructions %}
133
133
 
134
- {% /field-group %}
134
+ {% /group %}
135
135
 
136
- {% field-group id="metacritic_ratings" title="Metacritic Ratings" %}
136
+ {% group id="metacritic_ratings" title="Metacritic Ratings" %}
137
137
 
138
138
  {% field kind="number" id="metacritic_score" label="Metacritic Score" role="agent" min=0 max=100 %}{% /field %}
139
139
 
@@ -141,9 +141,9 @@ Audience Score percentage (0-100).
141
141
  Metascore (0-100 scale). Leave empty if not available.
142
142
  {% /instructions %}
143
143
 
144
- {% /field-group %}
144
+ {% /group %}
145
145
 
146
- {% field-group id="summary" title="Summary" %}
146
+ {% group id="summary" title="Summary" %}
147
147
 
148
148
  {% field kind="string" id="logline" label="One-Line Summary" role="agent" maxLength=300 %}{% /field %}
149
149
 
@@ -163,6 +163,6 @@ Major awards won.
163
163
  Example: Oscar | Best Picture | 1995
164
164
  {% /instructions %}
165
165
 
166
- {% /field-group %}
166
+ {% /group %}
167
167
 
168
168
  {% /form %}
@@ -45,7 +45,7 @@ markform:
45
45
  Comprehensive movie research covering ratings from multiple sources, box office performance, full cast and crew, technical specifications, streaming availability, and cultural impact analysis.
46
46
  {% /description %}
47
47
 
48
- {% field-group id="movie_input" title="Movie Identification" %}
48
+ {% group id="movie_input" title="Movie Identification" %}
49
49
 
50
50
  {% field kind="string" id="movie" label="Movie" role="user" required=true minLength=1 maxLength=300 %}{% /field %}
51
51
 
@@ -53,11 +53,11 @@ Comprehensive movie research covering ratings from multiple sources, box office
53
53
  Enter the movie title (add any details to help identify, like "Barbie 2023" or "the Batman movie with Robert Pattinson").
54
54
  {% /instructions %}
55
55
 
56
- {% /field-group %}
56
+ {% /group %}
57
57
 
58
58
  ## Title Identification
59
59
 
60
- {% field-group id="title_identification" title="Title Identification" %}
60
+ {% group id="title_identification" title="Title Identification" %}
61
61
 
62
62
  {% field kind="string" id="full_title" label="Full Title" role="agent" required=true %}{% /field %}
63
63
 
@@ -67,11 +67,11 @@ Official title including subtitle if any (e.g., "The Lord of the Rings: The Fell
67
67
 
68
68
  {% field kind="number" id="year" label="Release Year" role="agent" required=true min=1888 max=2030 %}{% /field %}
69
69
 
70
- {% /field-group %}
70
+ {% /group %}
71
71
 
72
72
  ## Sources
73
73
 
74
- {% field-group id="primary_sources" title="Primary Sources" %}
74
+ {% group id="primary_sources" title="Primary Sources" %}
75
75
 
76
76
  {% field kind="url" id="imdb_url" label="IMDB URL" role="agent" required=true %}{% /field %}
77
77
 
@@ -92,9 +92,9 @@ Direct link to the movie's Rotten Tomatoes page.
92
92
  Direct link to the movie's Metacritic page.
93
93
  {% /instructions %}
94
94
 
95
- {% /field-group %}
95
+ {% /group %}
96
96
 
97
- {% field-group id="box_office_sources" title="Box Office Sources" %}
97
+ {% group id="box_office_sources" title="Box Office Sources" %}
98
98
 
99
99
  {% field kind="url" id="boxofficemojo_url" label="Box Office Mojo URL" role="agent" %}{% /field %}
100
100
 
@@ -109,9 +109,9 @@ Format: https://www.boxofficemojo.com/title/tt0111161/
109
109
  Alternative/supplementary box office data and profitability analysis.
110
110
  {% /instructions %}
111
111
 
112
- {% /field-group %}
112
+ {% /group %}
113
113
 
114
- {% field-group id="availability_sources" title="Availability Sources" %}
114
+ {% group id="availability_sources" title="Availability Sources" %}
115
115
 
116
116
  {% field kind="url" id="justwatch_url" label="JustWatch URL" role="agent" %}{% /field %}
117
117
 
@@ -120,9 +120,9 @@ Best source for current streaming availability. Use US region.
120
120
  Format: https://www.justwatch.com/us/movie/the-shawshank-redemption
121
121
  {% /instructions %}
122
122
 
123
- {% /field-group %}
123
+ {% /group %}
124
124
 
125
- {% field-group id="additional_sources" title="Additional Sources" %}
125
+ {% group id="additional_sources" title="Additional Sources" %}
126
126
 
127
127
  {% field kind="url" id="letterboxd_url" label="Letterboxd URL" role="agent" %}{% /field %}
128
128
 
@@ -142,11 +142,11 @@ For production history, cultural impact, comprehensive awards list.
142
142
  Studio or film's official website, if still active.
143
143
  {% /instructions %}
144
144
 
145
- {% /field-group %}
145
+ {% /group %}
146
146
 
147
147
  ## Basic Details
148
148
 
149
- {% field-group id="basic_details" title="Basic Details" %}
149
+ {% group id="basic_details" title="Basic Details" %}
150
150
 
151
151
  {% field kind="string_list" id="directors" label="Director(s)" role="agent" required=true %}{% /field %}
152
152
 
@@ -202,11 +202,11 @@ Select all applicable genres from IMDB (up to 5).
202
202
  Production countries, one per line.
203
203
  {% /instructions %}
204
204
 
205
- {% /field-group %}
205
+ {% /group %}
206
206
 
207
207
  ## Cast & Crew
208
208
 
209
- {% field-group id="cast_crew" title="Cast & Crew" %}
209
+ {% group id="cast_crew" title="Cast & Crew" %}
210
210
 
211
211
  {% field kind="table" id="lead_cast" label="Lead Cast" role="agent" minRows=1 maxRows=10
212
212
  columnIds=["actor_name", "character_name"]
@@ -244,11 +244,11 @@ Format: Name (producer type)
244
244
  Example: "Emma Thomas (producer)"
245
245
  {% /instructions %}
246
246
 
247
- {% /field-group %}
247
+ {% /group %}
248
248
 
249
249
  ## Ratings
250
250
 
251
- {% field-group id="imdb_ratings" title="IMDB Ratings" %}
251
+ {% group id="imdb_ratings" title="IMDB Ratings" %}
252
252
 
253
253
  {% field kind="number" id="imdb_rating" label="IMDB Rating" role="agent" min=1.0 max=10.0 %}{% /field %}
254
254
 
@@ -262,9 +262,9 @@ IMDB user rating (1.0-10.0 scale).
262
262
  Number of IMDB user votes (e.g., 2800000 for a popular film).
263
263
  {% /instructions %}
264
264
 
265
- {% /field-group %}
265
+ {% /group %}
266
266
 
267
- {% field-group id="rotten_tomatoes_ratings" title="Rotten Tomatoes Ratings" %}
267
+ {% group id="rotten_tomatoes_ratings" title="Rotten Tomatoes Ratings" %}
268
268
 
269
269
  {% field kind="number" id="rt_critics_score" label="Tomatometer (Critics)" role="agent" min=0 max=100 %}{% /field %}
270
270
 
@@ -286,9 +286,9 @@ Audience Score percentage (0-100).
286
286
  The official Rotten Tomatoes critics consensus statement, if available.
287
287
  {% /instructions %}
288
288
 
289
- {% /field-group %}
289
+ {% /group %}
290
290
 
291
- {% field-group id="metacritic_ratings" title="Metacritic Ratings" %}
291
+ {% group id="metacritic_ratings" title="Metacritic Ratings" %}
292
292
 
293
293
  {% field kind="number" id="metacritic_score" label="Metacritic Score" role="agent" min=0 max=100 %}{% /field %}
294
294
 
@@ -296,9 +296,9 @@ The official Rotten Tomatoes critics consensus statement, if available.
296
296
  Metascore (0-100 scale). Leave empty if not available.
297
297
  {% /instructions %}
298
298
 
299
- {% /field-group %}
299
+ {% /group %}
300
300
 
301
- {% field-group id="additional_ratings" title="Additional Ratings" %}
301
+ {% group id="additional_ratings" title="Additional Ratings" %}
302
302
 
303
303
  {% field kind="number" id="letterboxd_rating" label="Letterboxd Rating" role="agent" min=0.5 max=5.0 %}{% /field %}
304
304
 
@@ -312,11 +312,11 @@ Letterboxd average rating (0.5-5.0 scale, in 0.1 increments).
312
312
  Opening weekend audience grade (A+ to F). Only available for theatrical releases.
313
313
  {% /instructions %}
314
314
 
315
- {% /field-group %}
315
+ {% /group %}
316
316
 
317
317
  ## Box Office
318
318
 
319
- {% field-group id="box_office" title="Box Office" %}
319
+ {% group id="box_office" title="Box Office" %}
320
320
 
321
321
  {% field kind="number" id="budget_millions" label="Budget ($M)" role="agent" min=0 %}{% /field %}
322
322
 
@@ -342,11 +342,11 @@ Global theatrical gross in millions USD.
342
342
  US opening weekend gross in millions USD.
343
343
  {% /instructions %}
344
344
 
345
- {% /field-group %}
345
+ {% /group %}
346
346
 
347
347
  ## Technical Specifications
348
348
 
349
- {% field-group id="technical_specs" title="Technical Specifications" %}
349
+ {% group id="technical_specs" title="Technical Specifications" %}
350
350
 
351
351
  {% field kind="single_select" id="aspect_ratio" label="Aspect Ratio" role="agent" %}
352
352
  - [ ] 1.33:1 (Academy) {% #ratio_133 %}
@@ -381,11 +381,11 @@ Primary sound format (e.g., "Dolby Atmos", "DTS", "Dolby Digital").
381
381
  Primary camera system used (e.g., "Arri Alexa 65", "IMAX 15-perf", "Panavision Panaflex").
382
382
  {% /instructions %}
383
383
 
384
- {% /field-group %}
384
+ {% /group %}
385
385
 
386
386
  ## Streaming Availability
387
387
 
388
- {% field-group id="streaming_availability" title="Streaming Availability (US)" %}
388
+ {% group id="streaming_availability" title="Streaming Availability (US)" %}
389
389
 
390
390
  {% field kind="multi_select" id="streaming_subscription" label="Streaming (Subscription)" role="agent" %}
391
391
  - [ ] Netflix {% #netflix %}
@@ -414,11 +414,11 @@ Select all platforms where this film is currently available to stream (subscript
414
414
  - [ ] 4K UHD available {% #uhd_4k %}
415
415
  {% /field %}
416
416
 
417
- {% /field-group %}
417
+ {% /group %}
418
418
 
419
419
  ## Content & Themes
420
420
 
421
- {% field-group id="content_themes" title="Content & Themes" %}
421
+ {% group id="content_themes" title="Content & Themes" %}
422
422
 
423
423
  {% field kind="checkboxes" id="content_warnings" label="Content Warnings" role="agent" checkboxMode="simple" %}
424
424
  - [ ] Intense violence {% #violence %}
@@ -441,11 +441,11 @@ Check any content warnings that apply. Use IMDB Parents Guide as reference.
441
441
  Major themes explored in the film (e.g., "redemption", "family", "identity", "war").
442
442
  {% /instructions %}
443
443
 
444
- {% /field-group %}
444
+ {% /group %}
445
445
 
446
446
  ## Summary & Legacy
447
447
 
448
- {% field-group id="summary" title="Summary" %}
448
+ {% group id="summary" title="Summary" %}
449
449
 
450
450
  {% field kind="string" id="logline" label="One-Line Summary" role="agent" maxLength=300 %}{% /field %}
451
451
 
@@ -476,9 +476,9 @@ Example: Oscar | Best Picture | 1995
476
476
  2-3 memorable critic quotes that capture reception.
477
477
  {% /instructions %}
478
478
 
479
- {% /field-group %}
479
+ {% /group %}
480
480
 
481
- {% field-group id="cultural_legacy" title="Cultural Legacy" %}
481
+ {% group id="cultural_legacy" title="Cultural Legacy" %}
482
482
 
483
483
  {% field kind="string" id="cultural_impact" label="Cultural Impact" role="agent" maxLength=500 %}{% /field %}
484
484
 
@@ -493,6 +493,6 @@ Leave empty for recent releases without established legacy.
493
493
  Films with similar themes, style, or appeal. One per line.
494
494
  {% /instructions %}
495
495
 
496
- {% /field-group %}
496
+ {% /group %}
497
497
 
498
498
  {% /form %}
@@ -16,18 +16,18 @@ markform:
16
16
 
17
17
  ## Movie Research Example
18
18
 
19
- {% field-group id="movie_input" title="Movie Identification" %}
19
+ {% group id="movie_input" title="Movie Identification" %}
20
20
 
21
21
  What movie do you want to research? \[*This field is filled in by the user (`role="user"`).*\]
22
22
 
23
23
  {% field kind="string" id="movie" label="Movie" role="user" required=true minLength=1 maxLength=300 %}{% /field %}
24
24
  {% instructions ref="movie" %}Enter the movie title (add year or details for disambiguation).{% /instructions %}
25
25
 
26
- {% /field-group %}
26
+ {% /group %}
27
27
 
28
28
  ## About the Movie
29
29
 
30
- {% field-group id="about_the_movie" title="About the Movie" %}
30
+ {% group id="about_the_movie" title="About the Movie" %}
31
31
 
32
32
  **Title:**
33
33
 
@@ -63,6 +63,6 @@ What movie do you want to research? \[*This field is filled in by the user (`rol
63
63
  {% field kind="string" id="logline" label="One-Line Summary" role="agent" maxLength=300 %}{% /field %}
64
64
  {% instructions ref="logline" %}Brief plot summary in 1-2 sentences, no spoilers.{% /instructions %}
65
65
 
66
- {% /field-group %}
66
+ {% /group %}
67
67
 
68
68
  {% /form %}