markform 0.1.7 → 0.1.9

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 (40) hide show
  1. package/README.md +451 -240
  2. package/dist/ai-sdk.d.mts +1 -1
  3. package/dist/ai-sdk.mjs +2 -2
  4. package/dist/{apply-g23rRn7p.mjs → apply-B2kt6C2z.mjs} +136 -32
  5. package/dist/bin.mjs +1 -1
  6. package/dist/{cli-Bqlm-WWw.mjs → cli-Dt_PlYi_.mjs} +519 -60
  7. package/dist/cli.mjs +1 -1
  8. package/dist/{coreTypes-__Cwxz5q.mjs → coreTypes-B1oI7qvV.mjs} +52 -4
  9. package/dist/{coreTypes-DCvD7feM.d.mts → coreTypes-JCPm418M.d.mts} +265 -9
  10. package/dist/index.d.mts +22 -10
  11. package/dist/index.mjs +5 -5
  12. package/dist/{session-DruaYPZ1.mjs → session-CzCh6JeY.mjs} +1 -1
  13. package/dist/{session-CgCNni0e.mjs → session-Dxqwt0RC.mjs} +3 -3
  14. package/dist/{shared-C9yW5FLZ.mjs → shared-CNqwaxUt.mjs} +1 -1
  15. package/dist/{shared-DQ6y3Ggc.mjs → shared-D3dNi-Gn.mjs} +1 -1
  16. package/dist/{src-BiuxbzF3.mjs → src-DFsC5wwy.mjs} +308 -59
  17. package/docs/markform-apis.md +30 -1
  18. package/docs/markform-reference.md +65 -6
  19. package/docs/markform-spec.md +3 -3
  20. package/examples/movie-research/{movie-research-deep.form.md → movie-deep-research.form.md} +17 -58
  21. package/examples/movie-research/movie-research-demo.form.md +25 -34
  22. package/examples/rejection-test/rejection-test-mock-filled.form.md +41 -0
  23. package/examples/rejection-test/rejection-test-mock-filled.report.md +15 -0
  24. package/examples/rejection-test/rejection-test-mock-filled.schema.json +59 -0
  25. package/examples/rejection-test/rejection-test-mock-filled.yml +13 -0
  26. package/examples/rejection-test/rejection-test.form.md +35 -0
  27. package/examples/rejection-test/rejection-test.session.yaml +534 -0
  28. package/examples/simple/simple-mock-filled.report.md +96 -0
  29. package/examples/simple/simple-mock-filled.schema.json +374 -0
  30. package/examples/simple/simple-mock-filled.yml +87 -0
  31. package/examples/simple/simple-skipped-filled.report.md +90 -0
  32. package/examples/simple/simple-skipped-filled.schema.json +374 -0
  33. package/examples/simple/simple-skipped-filled.yml +77 -0
  34. package/examples/simple/simple-with-skips.session.yaml +1969 -21
  35. package/examples/simple/simple.session.yaml +1982 -21
  36. package/package.json +1 -1
  37. package/examples/earnings-analysis/earnings-analysis.form.md +0 -159
  38. package/examples/earnings-analysis/earnings-analysis.raw.md +0 -801
  39. package/examples/earnings-analysis/earnings-analysis.valid.ts +0 -198
  40. package/examples/movie-research/movie-research-basic.form.md +0 -169
@@ -56,7 +56,7 @@ They are Markdoc syntax, which is a superset of Markdown.
56
56
  ## Field Kinds
57
57
 
58
58
  Markform uses the term **field kind** to refer to the type of a field (e.g., `string`,
59
- `number`, `checkboxes`). The term **data type** refers to the underlying value
59
+ `number`, `checkboxes`, `table`). The term **data type** refers to the underlying value
60
60
  representation. See the Type System section in SPEC.md for full details.
61
61
 
62
62
  ### String Field
@@ -263,19 +263,72 @@ Integer year with optional constraints.
263
263
  2015
264
264
  `````
265
265
  {% /field %}
266
- ````
266
+ `````
267
267
 
268
268
  | Attribute | Type | Description |
269
269
  |-----------|------|-------------|
270
270
  | `min` | number | Minimum year (inclusive) |
271
271
  | `max` | number | Maximum year (inclusive) |
272
272
 
273
+ ### Table Field
274
+
275
+ Structured tabular data with typed columns. Uses standard markdown table syntax.
276
+
277
+ ````markdown
278
+ {% field kind="table" id="team" label="Team Members" required=true
279
+ columnIds=["name", "title", "start_date"]
280
+ columnLabels=["Name", "Job Title", "Start Date"]
281
+ columnTypes=["string", "string", "date"]
282
+ minRows=1 maxRows=20 %}
283
+ | Name | Job Title | Start Date |
284
+ |------|-----------|------------|
285
+ | Alice Smith | Engineer | 2023-01-15 |
286
+ | Bob Jones | Designer | 2022-06-01 |
287
+ {% /field %}
288
+ `````
289
+
290
+ **Basic table (columnLabels backfilled from header row):**
291
+
292
+ ```markdown
293
+ {% field kind="table" id="items" label="Items"
294
+ columnIds=["name", "quantity", "price"] %}
295
+ | Name | Quantity | Price |
296
+ |------|----------|-------|
297
+ {% /field %}
298
+ ```
299
+
300
+ | Attribute | Type | Required | Description |
301
+ | --- | --- | --- | --- |
302
+ | `columnIds` | string[] | Yes | Array of snake_case column identifiers |
303
+ | `columnLabels` | string[] | No | Display labels (defaults to header row) |
304
+ | `columnTypes` | string[] | No | Column types (defaults to all `string`) |
305
+ | `minRows` | number | No | Minimum row count (default: 0) |
306
+ | `maxRows` | number | No | Maximum row count (default: unlimited) |
307
+
308
+ **Column types:**
309
+
310
+ | Type | Description | Validation |
311
+ | --- | --- | --- |
312
+ | `string` | Any text value | None |
313
+ | `number` | Numeric value | Integer or float |
314
+ | `url` | URL value | Valid URL format |
315
+ | `date` | Date value | ISO 8601 (YYYY-MM-DD) |
316
+ | `year` | Year value | Integer (1000-9999) |
317
+
318
+ **Sentinel values in cells:** Use `%SKIP%` or `%ABORT%` with optional reasons:
319
+
320
+ ```markdown
321
+ | 2017 | I, Tonya | 90 | %SKIP% (Not tracked) |
322
+ ```
323
+
324
+ **Cell escaping:** Use `\|` for literal pipe characters in cell values.
325
+
273
326
  ## Common Attributes
274
327
 
275
328
  All fields support these attributes:
276
329
 
277
330
  | Attribute | Type | Default | Description |
278
- |-----------|------|---------|-------------|
331
+ | --- | --- | --- | --- |
279
332
  | `id` | string | required | Unique snake_case identifier |
280
333
  | `label` | string | required | Human-readable label |
281
334
  | `required` | boolean | false | Must be filled for completion |
@@ -285,7 +338,7 @@ All fields support these attributes:
285
338
  **Text-entry fields only** (string, number, string-list, url, url-list):
286
339
 
287
340
  | Attribute | Type | Description |
288
- |-----------|------|-------------|
341
+ | --- | --- | --- |
289
342
  | `placeholder` | string | Hint text shown in empty fields |
290
343
  | `examples` | string[] | Example values (helps LLMs understand expected format) |
291
344
 
@@ -294,7 +347,8 @@ All fields support these attributes:
294
347
  {% field kind="number" id="revenue" label="Revenue" placeholder="1000000" examples=["500000", "1000000"] %}{% /field %}
295
348
  ```
296
349
 
297
- Note: `placeholder` and `examples` are NOT valid on chooser fields (single-select, multi-select, checkboxes).
350
+ Note: `placeholder` and `examples` are NOT valid on chooser fields (single-select,
351
+ multi-select, checkboxes).
298
352
 
299
353
  ## Documentation Blocks
300
354
 
@@ -316,7 +370,7 @@ Additional context or caveats.
316
370
  {% examples ref="field_id" %}
317
371
  Example values: "AAPL", "GOOGL", "MSFT"
318
372
  {% /examples %}
319
- ````
373
+ ```
320
374
 
321
375
  Place doc blocks after the element they reference.
322
376
 
@@ -549,6 +603,11 @@ markform export form.md --format=json # Export values as JSON
549
603
  markform export form.md --format=yaml # Export values as YAML
550
604
  markform export form.md --format=markdown # Readable markdown (strips tags)
551
605
 
606
+ # Export form structure as JSON Schema
607
+ markform schema form.md # Full schema with x-markform extensions
608
+ markform schema form.md --pure # Pure JSON Schema (no extensions)
609
+ markform schema form.md --draft draft-07 # Specify draft version
610
+
552
611
  # Other commands
553
612
  markform serve form.md # Web UI for browsing
554
613
  markform examples # Try built-in examples
@@ -1385,12 +1385,12 @@ type IssueReason =
1385
1385
  | 'checkbox_incomplete' // Required checkboxes with non-terminal states
1386
1386
  | 'min_items_not_met' // String-list or multi-select below minimum
1387
1387
  // Severity: *recommended* (optional improvements)
1388
- | 'optional_empty'; // Optional field with no value
1388
+ | 'optional_unanswered'; // Optional field not yet addressed
1389
1389
 
1390
1390
  // Mapping from ValidationIssue to InspectIssue:
1391
1391
  // - ValidationIssue.severity='error' → InspectIssue.severity='required'
1392
1392
  // - ValidationIssue.severity='warning'/'info' → InspectIssue.severity='recommended'
1393
- // - Missing optional fields → severity='recommended', reason='optional_empty'
1393
+ // - Unanswered optional fields → severity='recommended', reason='optional_unanswered'
1394
1394
  ```
1395
1395
 
1396
1396
  #### StructureSummary and ProgressSummary
@@ -2695,7 +2695,7 @@ type.
2695
2695
  | `checkbox_incomplete` | 3 (required) / 2 (recommended) |
2696
2696
  | `validation_error` | 2 |
2697
2697
  | `min_items_not_met` | 2 |
2698
- | `optional_empty` | 1 |
2698
+ | `optional_unanswered` | 1 |
2699
2699
 
2700
2700
  **Total Score** = Field Priority Weight + Issue Type Score
2701
2701
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  markform:
3
3
  spec: MF/0.1
4
- title: Movie Research (Deep)
4
+ title: Movie Deep Research
5
5
  description: Comprehensive movie research form with ratings, box office, cast/crew, technical specs, streaming availability, and cultural analysis.
6
6
  run_mode: research
7
7
  roles:
@@ -40,7 +40,7 @@ markform:
40
40
  max_patches_per_turn: 15
41
41
  ---
42
42
 
43
- {% form id="movie_research_deep" title="Movie Research (Deep)" %}
43
+ {% form id="movie_research_deep" title="Movie Deep Research" %}
44
44
 
45
45
  {% description ref="movie_research_deep" %}
46
46
  Comprehensive movie research covering ratings from multiple sources, box office performance, full cast and crew, technical specifications, streaming availability, and cultural impact analysis.
@@ -249,72 +249,31 @@ Example: "Emma Thomas (producer)"
249
249
 
250
250
  ## Ratings
251
251
 
252
- {% group id="imdb_ratings" title="IMDB Ratings" %}
252
+ {% group id="ratings" title="Ratings" %}
253
253
 
254
- {% field kind="number" id="imdb_rating" label="IMDB Rating" role="agent" min=1.0 max=10.0 %}{% /field %}
255
-
256
- {% instructions ref="imdb_rating" %}
257
- IMDB user rating (1.0-10.0 scale).
258
- {% /instructions %}
259
-
260
- {% field kind="number" id="imdb_votes" label="IMDB Vote Count" role="agent" min=0 %}{% /field %}
261
-
262
- {% instructions ref="imdb_votes" %}
263
- Number of IMDB user votes (e.g., 2800000 for a popular film).
264
- {% /instructions %}
265
-
266
- {% /group %}
267
-
268
- {% group id="rotten_tomatoes_ratings" title="Rotten Tomatoes Ratings" %}
269
-
270
- {% field kind="number" id="rt_critics_score" label="Tomatometer (Critics)" role="agent" min=0 max=100 %}{% /field %}
271
-
272
- {% instructions ref="rt_critics_score" %}
273
- Tomatometer percentage (0-100).
274
- {% /instructions %}
275
-
276
- {% field kind="number" id="rt_critics_count" label="Critics Review Count" role="agent" min=0 %}{% /field %}
277
-
278
- {% field kind="number" id="rt_audience_score" label="Audience Score" role="agent" min=0 max=100 %}{% /field %}
279
-
280
- {% instructions ref="rt_audience_score" %}
281
- Audience Score percentage (0-100).
254
+ {% field kind="table" id="ratings_table" label="Ratings" role="agent" required=true
255
+ columnIds=["source", "score", "votes"] columnTypes=["string", "number", "number"]
256
+ minRows=0 maxRows=6 %}
257
+ | Source | Score | Votes |
258
+ |--------|-------|-------|
259
+ {% /field %}
260
+ {% instructions ref="ratings_table" %}
261
+ Fill in scores and vote/review counts from each source:
262
+ - IMDB: Rating (1.0-10.0 scale), vote count
263
+ - RT Critics: Tomatometer (0-100%), review count
264
+ - RT Audience: Audience Score (0-100%), rating count
265
+ - Metacritic: Metascore (0-100)
266
+ - Letterboxd: Rating (0.5-5.0 scale)
267
+ - CinemaScore: Grade (A+ to F), leave votes empty
282
268
  {% /instructions %}
283
269
 
284
270
  {% field kind="string" id="rt_consensus" label="Critics Consensus" role="agent" maxLength=500 %}{% /field %}
285
-
286
271
  {% instructions ref="rt_consensus" %}
287
272
  The official Rotten Tomatoes critics consensus statement, if available.
288
273
  {% /instructions %}
289
274
 
290
275
  {% /group %}
291
276
 
292
- {% group id="metacritic_ratings" title="Metacritic Ratings" %}
293
-
294
- {% field kind="number" id="metacritic_score" label="Metacritic Score" role="agent" min=0 max=100 %}{% /field %}
295
-
296
- {% instructions ref="metacritic_score" %}
297
- Metascore (0-100 scale). Leave empty if not available.
298
- {% /instructions %}
299
-
300
- {% /group %}
301
-
302
- {% group id="additional_ratings" title="Additional Ratings" %}
303
-
304
- {% field kind="number" id="letterboxd_rating" label="Letterboxd Rating" role="agent" min=0.5 max=5.0 %}{% /field %}
305
-
306
- {% instructions ref="letterboxd_rating" %}
307
- Letterboxd average rating (0.5-5.0 scale, in 0.1 increments).
308
- {% /instructions %}
309
-
310
- {% field kind="string" id="cinemascore" label="CinemaScore Grade" role="agent" pattern="^[A-F][+-]?$" %}{% /field %}
311
-
312
- {% instructions ref="cinemascore" %}
313
- Opening weekend audience grade (A+ to F). Only available for theatrical releases.
314
- {% /instructions %}
315
-
316
- {% /group %}
317
-
318
277
  ## Box Office
319
278
 
320
279
  {% group id="box_office" title="Box Office" %}
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  markform:
3
3
  spec: MF/0.1
4
- title: Movie Research (Demo)
5
- description: Quick movie lookup with just the essentials (title, year, ratings, summary).
4
+ title: Movie Research Demo
5
+ description: Movie lookup with ratings from IMDB and Rotten Tomatoes.
6
6
  run_mode: research
7
7
  roles:
8
8
  - user
@@ -10,42 +10,28 @@ markform:
10
10
  role_instructions:
11
11
  user: "Enter the movie title."
12
12
  agent: |
13
- Quickly identify the movie and fill in basic info from IMDB.
14
- This is a demo lookup - just get the core facts.
15
- ---
16
- {% form id="movie_research_demo" title="Movie Research (Demo)" %}
13
+ Identify the movie with web searches and use imdb.com and rottentomatoes.com to fill in the ratings.
17
14
 
18
- ## Movie Research Example
15
+ ---
16
+ {% form id="movie_research_demo" %}
19
17
 
20
- {% group id="movie_input" title="Movie Identification" %}
18
+ {% group id="movie_input" %}
21
19
 
22
- What movie do you want to research? \[*This field is filled in by the user (`role="user"`).*\]
20
+ ## What movie do you want to research?
23
21
 
24
22
  {% field kind="string" id="movie" label="Movie" role="user" required=true minLength=1 maxLength=300 %}{% /field %}
23
+
25
24
  {% instructions ref="movie" %}Enter the movie title (add year or details for disambiguation).{% /instructions %}
26
25
 
27
26
  {% /group %}
28
27
 
29
- ## About the Movie
30
-
31
28
  {% group id="about_the_movie" title="About the Movie" %}
32
29
 
33
- **Title:**
34
-
35
- {% field kind="string" id="full_title" label="Full Title" role="agent" required=true %}{% /field %}
36
- {% instructions ref="full_title" %}Official title, including subtitle if any.{% /instructions %}
37
-
38
- **Release year:**
30
+ ## Movie Ratings
39
31
 
40
- {% field kind="number" id="year" label="Release Year" role="agent" required=true min=1888 max=2030 %}{% /field %}
32
+ Here are the ratings for the movie:
41
33
 
42
- **IMDB:**
43
-
44
- {% field kind="url" id="imdb_url" label="IMDB URL" role="agent" required=true %}{% /field %}
45
-
46
- **MPAA rating:**
47
-
48
- {% field kind="single_select" id="mpaa_rating" label="MPAA Rating" role="agent" %}
34
+ {% field kind="single_select" id="mpaa_rating" role="agent" label="MPAA Rating" %}
49
35
  - [ ] G {% #g %}
50
36
  - [ ] PG {% #pg %}
51
37
  - [ ] PG-13 {% #pg_13 %}
@@ -54,16 +40,21 @@ What movie do you want to research? \[*This field is filled in by the user (`rol
54
40
  - [ ] NR/Unrated {% #nr %}
55
41
  {% /field %}
56
42
 
57
- **IMDB rating:**
58
-
59
- {% field kind="number" id="imdb_rating" label="IMDB Rating" role="agent" min=1.0 max=10.0 %}{% /field %}
60
- {% instructions ref="imdb_rating" %}IMDB user rating (1.0-10.0 scale).{% /instructions %}
61
-
62
- **Summary:**
43
+ {% field kind="table" id="ratings_table" role="agent"
44
+ label="Ratings" required=true
45
+ columnIds=["source", "score", "votes"] columnTypes=["string", "number", "number"]
46
+ minRows=0 maxRows=3 %}
47
+ | Source | Score | Votes |
48
+ |--------|-------|-------|
49
+ {% /field %}
63
50
 
64
- {% field kind="string" id="logline" label="One-Line Summary" role="agent" maxLength=300 %}{% /field %}
65
- {% instructions ref="logline" %}Brief plot summary in 1-2 sentences, no spoilers.{% /instructions %}
51
+ {% instructions ref="ratings_table" %}
52
+ Fill in scores and vote counts from each source:
53
+ - IMDB: Rating (1.0-10.0 scale), vote count
54
+ - RT Critics: Tomatometer (0-100%), review count
55
+ - RT Audience: Audience Score (0-100%), rating count
56
+ {% /instructions %}
66
57
 
67
58
  {% /group %}
68
59
 
69
- {% /form %}
60
+ {% /form %}
@@ -0,0 +1,41 @@
1
+ ---
2
+ markform:
3
+ spec: MF/0.1
4
+ title: Rejection Test Form
5
+ description: "Tests type mismatch rejection and recovery behavior"
6
+ roles:
7
+ - agent
8
+ ---
9
+
10
+ {% form id="rejection_test" title="Rejection Test Form" %}
11
+
12
+ {% description ref="rejection_test" %}
13
+ A form to test patch rejection scenarios - verifies that type mismatch
14
+ errors are properly recorded and recovery works.
15
+ {% /description %}
16
+
17
+ {% group id="fields" title="Test Fields" %}
18
+
19
+ {% field kind="table" id="ratings" label="Ratings" required=true minRows=1 maxRows=5
20
+ columnIds=["source", "score", "votes"]
21
+ columnLabels=["Source", "Score", "Votes"]
22
+ columnTypes=["string", "number", "number"] %}
23
+ | Source | Score | Votes |
24
+ |--------|-------|-------|
25
+ | IMDB | 85 | 12500 |
26
+ | Rotten Tomatoes | 92 | 450 |
27
+ {% /field %}
28
+
29
+ {% instructions ref="ratings" %}
30
+ Enter rating data with source name, score (0-100), and vote count.
31
+ {% /instructions %}
32
+
33
+ {% field kind="string" id="title" label="Title" required=true %}
34
+ ```value
35
+ Test Movie
36
+ ```
37
+ {% /field %}
38
+
39
+ {% /group %}
40
+
41
+ {% /form %}
@@ -0,0 +1,15 @@
1
+ # Rejection Test Form
2
+
3
+ A form to test patch rejection scenarios - verifies that type mismatch
4
+ errors are properly recorded and recovery works.
5
+
6
+ ## Test Fields
7
+
8
+ **Ratings:**
9
+ | Source | Score | Votes |
10
+ | --- | --- | --- |
11
+ | IMDB | 85 | 12500 |
12
+ | Rotten Tomatoes | 92 | 450 |
13
+
14
+ **Title:**
15
+ Test Movie
@@ -0,0 +1,59 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "rejection_test",
4
+ "type": "object",
5
+ "properties": {
6
+ "ratings": {
7
+ "type": "array",
8
+ "items": {
9
+ "type": "object",
10
+ "properties": {
11
+ "source": {
12
+ "title": "Source",
13
+ "type": "string"
14
+ },
15
+ "score": {
16
+ "title": "Score",
17
+ "type": "number"
18
+ },
19
+ "votes": {
20
+ "title": "Votes",
21
+ "type": "number"
22
+ }
23
+ }
24
+ },
25
+ "title": "Ratings",
26
+ "minItems": 1,
27
+ "maxItems": 5,
28
+ "x-markform": {
29
+ "role": "agent",
30
+ "group": "fields"
31
+ }
32
+ },
33
+ "title": {
34
+ "type": "string",
35
+ "title": "Title",
36
+ "x-markform": {
37
+ "role": "agent",
38
+ "group": "fields"
39
+ }
40
+ }
41
+ },
42
+ "title": "Rejection Test Form",
43
+ "description": "A form to test patch rejection scenarios - verifies that type mismatch\nerrors are properly recorded and recovery works.",
44
+ "required": ["ratings", "title"],
45
+ "x-markform": {
46
+ "spec": "MF/0.1",
47
+ "roles": ["user", "agent"],
48
+ "roleInstructions": {
49
+ "user": "Fill in the fields you have direct knowledge of.",
50
+ "agent": "Complete the remaining fields based on the provided context."
51
+ },
52
+ "groups": [
53
+ {
54
+ "id": "fields",
55
+ "title": "Test Fields"
56
+ }
57
+ ]
58
+ }
59
+ }
@@ -0,0 +1,13 @@
1
+ values:
2
+ ratings:
3
+ state: answered
4
+ value:
5
+ - source: IMDB
6
+ score: 85
7
+ votes: 12500
8
+ - source: Rotten Tomatoes
9
+ score: 92
10
+ votes: 450
11
+ title:
12
+ state: answered
13
+ value: Test Movie
@@ -0,0 +1,35 @@
1
+ ---
2
+ markform:
3
+ spec: MF/0.1
4
+ title: Rejection Test Form
5
+ description: "Tests type mismatch rejection and recovery behavior"
6
+ roles:
7
+ - agent
8
+ ---
9
+
10
+ {% form id="rejection_test" title="Rejection Test Form" %}
11
+
12
+ {% description ref="rejection_test" %}
13
+ A form to test patch rejection scenarios - verifies that type mismatch
14
+ errors are properly recorded and recovery works.
15
+ {% /description %}
16
+
17
+ {% group id="fields" title="Test Fields" %}
18
+
19
+ {% field kind="table" id="ratings" label="Ratings" required=true minRows=1 maxRows=5
20
+ columnIds=["source", "score", "votes"]
21
+ columnLabels=["Source", "Score", "Votes"]
22
+ columnTypes=["string", "number", "number"] %}
23
+ | Source | Score | Votes |
24
+ |--------|-------|-------|
25
+ {% /field %}
26
+
27
+ {% instructions ref="ratings" %}
28
+ Enter rating data with source name, score (0-100), and vote count.
29
+ {% /instructions %}
30
+
31
+ {% field kind="string" id="title" label="Title" required=true %}{% /field %}
32
+
33
+ {% /group %}
34
+
35
+ {% /form %}