markform 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -42
- package/dist/ai-sdk.d.mts +2 -2
- package/dist/ai-sdk.mjs +5 -5
- package/dist/{apply-BfAGTHMh.mjs → apply-C54EMAJ1.mjs} +383 -26
- package/dist/bin.mjs +6 -6
- package/dist/{cli-B3NVm6zL.mjs → cli-BhWhn6L9.mjs} +456 -141
- package/dist/cli.mjs +6 -6
- package/dist/{coreTypes-BXhhz9Iq.d.mts → coreTypes-cbNTYAcb.d.mts} +1878 -325
- package/dist/{coreTypes-Dful87E0.mjs → coreTypes-pyctKRgc.mjs} +79 -5
- package/dist/index.d.mts +146 -9
- package/dist/index.mjs +5 -5
- package/dist/session-B_stoXQn.mjs +4 -0
- package/dist/{session-Bqnwi9wp.mjs → session-uF0e6m6k.mjs} +9 -5
- package/dist/{shared-N_s1M-_K.mjs → shared-BqPnYXrn.mjs} +82 -1
- package/dist/shared-CZsyShck.mjs +3 -0
- package/dist/{src-BXRkGFpG.mjs → src-BNh7Cx9P.mjs} +801 -121
- package/docs/markform-apis.md +194 -0
- package/{DOCS.md → docs/markform-reference.md} +111 -50
- package/{SPEC.md → docs/markform-spec.md} +342 -91
- package/examples/celebrity-deep-research/celebrity-deep-research.form.md +196 -141
- package/examples/earnings-analysis/earnings-analysis.form.md +236 -226
- package/examples/movie-research/movie-research-basic.form.md +25 -21
- package/examples/movie-research/movie-research-deep.form.md +74 -62
- package/examples/movie-research/movie-research-minimal.form.md +29 -34
- package/examples/simple/simple-mock-filled.form.md +93 -29
- package/examples/simple/simple-skipped-filled.form.md +91 -29
- package/examples/simple/simple-with-skips.session.yaml +93 -25
- package/examples/simple/simple.form.md +74 -20
- package/examples/simple/simple.session.yaml +98 -25
- package/examples/startup-deep-research/startup-deep-research.form.md +108 -81
- package/examples/startup-research/startup-research-mock-filled.form.md +43 -43
- package/examples/startup-research/startup-research.form.md +24 -24
- package/package.json +18 -27
- package/dist/session-DdAtY2Ni.mjs +0 -4
- package/dist/shared-D7gf27Tr.mjs +0 -3
|
@@ -190,7 +190,7 @@ Markform defines its own scoping rules where option IDs are field-scoped.
|
|
|
190
190
|
|
|
191
191
|
- IDs use `snake_case` (e.g., `company_info`, `ten_k`)
|
|
192
192
|
|
|
193
|
-
-
|
|
193
|
+
- The `field` tag uses a `kind` attribute to specify the field kind
|
|
194
194
|
|
|
195
195
|
#### Structural Tags
|
|
196
196
|
|
|
@@ -203,27 +203,31 @@ Markform defines its own scoping rules where option IDs are field-scoped.
|
|
|
203
203
|
Custom tags are defined following [Markdoc tag conventions][markdoc-tags]. See
|
|
204
204
|
[Markdoc Config][markdoc-config] for how to register custom tags.
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
field a `Field` or `FieldValue` represents.
|
|
206
|
+
All fields use the unified `{% field kind="..." %}` syntax. The `kind` attribute
|
|
207
|
+
identifies what type of field a `Field` or `FieldValue` represents.
|
|
209
208
|
(In TypeScript, the type is `FieldKind`.)
|
|
210
209
|
|
|
211
|
-
|
|
|
212
|
-
| --- | --- |
|
|
213
|
-
| `string
|
|
214
|
-
| `number
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
210
|
+
| Kind | Description |
|
|
211
|
+
| --- | --- |
|
|
212
|
+
| `string` | String value; optional `required`, `pattern`, `minLength`, `maxLength` |
|
|
213
|
+
| `number` | Numeric value; optional `min`, `max`, `integer` |
|
|
214
|
+
| `date` | ISO 8601 date (YYYY-MM-DD); optional `min`, `max` date constraints |
|
|
215
|
+
| `year` | Integer year; optional `min`, `max` year constraints |
|
|
216
|
+
| `string_list` | Array of strings (open-ended list); supports `minItems`, `maxItems`, `itemMinLength`, `itemMaxLength`, `uniqueItems` |
|
|
217
|
+
| `single_select` | Select one option from enumerated list |
|
|
218
|
+
| `multi_select` | Select multiple options; supports `minSelections`, `maxSelections` constraints |
|
|
219
|
+
| `checkboxes` | Stateful checklist; supports `checkboxMode` with values `multi` (5 states), `simple` (2 states), or `explicit` (yes/no); optional `minDone` for completion threshold |
|
|
220
|
+
| `url` | Single URL value with built-in format validation |
|
|
221
|
+
| `url_list` | Array of URLs (for citations, sources, references); supports `minItems`, `maxItems`, `uniqueItems` |
|
|
222
|
+
| `table` | Structured tabular data with typed columns; supports `columnIds`, `columnLabels`, `columnTypes`, `minRows`, `maxRows` |
|
|
223
|
+
|
|
224
|
+
**Syntax:** `{% field kind="<kind>" id="..." label="..." %}...{% /field %}`
|
|
221
225
|
|
|
222
226
|
**Note on `pattern`:** The `pattern` attribute accepts a JavaScript-compatible regular
|
|
223
227
|
expression string (without delimiters).
|
|
224
228
|
Example: `pattern="^[A-Z]{1,5}$"` for a ticker symbol.
|
|
225
229
|
|
|
226
|
-
**Common attributes (all field
|
|
230
|
+
**Common attributes (all field kinds):**
|
|
227
231
|
|
|
228
232
|
| Attribute | Type | Description |
|
|
229
233
|
| --- | --- | --- |
|
|
@@ -234,6 +238,32 @@ Example: `pattern="^[A-Z]{1,5}$"` for a ticker symbol.
|
|
|
234
238
|
|
|
235
239
|
The `role` attribute enables multi-actor workflows where different fields are assigned
|
|
236
240
|
to different actors.
|
|
241
|
+
|
|
242
|
+
**Text-entry field attributes (string, number, string-list, url, url-list only):**
|
|
243
|
+
|
|
244
|
+
| Attribute | Type | Description |
|
|
245
|
+
| --- | --- | --- |
|
|
246
|
+
| `placeholder` | string | Hint text shown in empty fields (displayed in UI) |
|
|
247
|
+
| `examples` | string[] | Example values for the field (helps LLMs, shown in prompts) |
|
|
248
|
+
|
|
249
|
+
These attributes are only valid on text-entry field kinds. Using them on chooser fields
|
|
250
|
+
(single-select, multi-select, checkboxes) will result in a parse error.
|
|
251
|
+
|
|
252
|
+
**Example with placeholder and examples:**
|
|
253
|
+
```md
|
|
254
|
+
{% field kind="string" id="company_name" label="Company name" placeholder="Enter company name" examples=["ACME Corp", "Globex Inc"] %}{% /field %}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
For number fields, examples must be valid numbers:
|
|
258
|
+
```md
|
|
259
|
+
{% field kind="number" id="revenue" label="Revenue (USD)" placeholder="1000000" examples=["500000", "1000000", "5000000"] %}{% /field %}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
For URL fields, examples must be valid URLs:
|
|
263
|
+
```md
|
|
264
|
+
{% field kind="url" id="website" label="Website" placeholder="https://example.com" examples=["https://example.com", "https://github.com"] %}{% /field %}
|
|
265
|
+
```
|
|
266
|
+
|
|
237
267
|
When running the fill harness with `targetRoles`, only fields matching those roles are
|
|
238
268
|
considered for completion.
|
|
239
269
|
See **Role-filtered completion** in the ProgressState Definitions section.
|
|
@@ -243,10 +273,10 @@ See **Role-filtered completion** in the ProgressState Definitions section.
|
|
|
243
273
|
Markdoc does **not** natively support GFM-style task list checkbox syntax.
|
|
244
274
|
The `[ ]` and `[x]` markers are **Markform-specific syntax** parsed within tag content.
|
|
245
275
|
|
|
246
|
-
All selection field
|
|
276
|
+
All selection field kinds use checkbox-style markers for broad markdown renderer
|
|
247
277
|
compatibility:
|
|
248
278
|
|
|
249
|
-
| Field
|
|
279
|
+
| Field Kind | Marker | Meaning | Example |
|
|
250
280
|
| --- | --- | --- | --- |
|
|
251
281
|
| `checkboxes` | `[ ]` | Unchecked / todo / unfilled | `- [ ] Item {% #item_id %}` |
|
|
252
282
|
| `checkboxes` | `[x]` | Checked / done | `- [x] Item {% #item_id %}` |
|
|
@@ -255,13 +285,13 @@ compatibility:
|
|
|
255
285
|
| `checkboxes` | `[-]` | Not applicable (multi only) | `- [-] Item {% #item_id %}` |
|
|
256
286
|
| `checkboxes` | `[y]` | Yes (explicit only) | `- [y] Item {% #item_id %}` |
|
|
257
287
|
| `checkboxes` | `[n]` | No (explicit only) | `- [n] Item {% #item_id %}` |
|
|
258
|
-
| `
|
|
259
|
-
| `
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
288
|
+
| `single_select` | `[ ]` | Unselected | `- [ ] Option {% #opt_id %}` |
|
|
289
|
+
| `single_select` | `[x]` | Selected (exactly one) | `- [x] Option {% #opt_id %}` |
|
|
290
|
+
| `multi_select` | `[ ]` | Unselected | `- [ ] Option {% #opt_id %}` |
|
|
291
|
+
| `multi_select` | `[x]` | Selected | `- [x] Option {% #opt_id %}` |
|
|
262
292
|
|
|
263
|
-
**Note:** `
|
|
264
|
-
between `
|
|
293
|
+
**Note:** `single_select` enforces that exactly one option has `[x]`. The distinction
|
|
294
|
+
between `single_select` and `multi_select` is in the `kind` attribute, not the marker syntax.
|
|
265
295
|
|
|
266
296
|
The `{% #id %}` annotation **is** native Markdoc syntax (see
|
|
267
297
|
[Attributes][markdoc-attributes]).
|
|
@@ -325,11 +355,11 @@ Type: `integer`, default: `-1` (require all).
|
|
|
325
355
|
|
|
326
356
|
Example with partial completion allowed:
|
|
327
357
|
```md
|
|
328
|
-
{% checkboxes id="optional_tasks" label="Optional tasks" required=true minDone=1 %}
|
|
358
|
+
{% field kind="checkboxes" id="optional_tasks" label="Optional tasks" required=true minDone=1 %}
|
|
329
359
|
- [ ] Task A {% #task_a %}
|
|
330
360
|
- [ ] Task B {% #task_b %}
|
|
331
361
|
- [ ] Task C {% #task_c %}
|
|
332
|
-
{% /
|
|
362
|
+
{% /field %}
|
|
333
363
|
```
|
|
334
364
|
|
|
335
365
|
**Note:** `minDone` only applies to `simple` mode.
|
|
@@ -361,6 +391,79 @@ The parser enforces this:
|
|
|
361
391
|
- `active` (`[*]`): This item is the current focus right now (useful for showing where
|
|
362
392
|
an agent is in a multi-step workflow)
|
|
363
393
|
|
|
394
|
+
#### Table Fields
|
|
395
|
+
|
|
396
|
+
Table fields enable structured tabular data collection with typed columns.
|
|
397
|
+
They use standard markdown table syntax for values and support validation per column
|
|
398
|
+
type.
|
|
399
|
+
|
|
400
|
+
**Table field attributes:**
|
|
401
|
+
|
|
402
|
+
| Attribute | Type | Required | Description |
|
|
403
|
+
| --- | --- | --- | --- |
|
|
404
|
+
| `columnIds` | string[] | Yes | Array of snake_case column identifiers |
|
|
405
|
+
| `columnLabels` | string[] | No | Array of display labels (backfilled from table header row if omitted) |
|
|
406
|
+
| `columnTypes` | string[] | No | Array of column types (defaults to all `'string'`) |
|
|
407
|
+
| `minRows` | number | No | Minimum row count (default: 0) |
|
|
408
|
+
| `maxRows` | number | No | Maximum row count (default: unlimited) |
|
|
409
|
+
|
|
410
|
+
**Column types and validation:**
|
|
411
|
+
|
|
412
|
+
| Type | Description | Validation |
|
|
413
|
+
| --- | --- | --- |
|
|
414
|
+
| `string` | Any text value | None |
|
|
415
|
+
| `number` | Numeric value | Integer or float |
|
|
416
|
+
| `url` | URL value | Valid URL format |
|
|
417
|
+
| `date` | Date value | ISO 8601 format (YYYY-MM-DD) |
|
|
418
|
+
| `year` | Year value | Integer (1000-9999) |
|
|
419
|
+
|
|
420
|
+
**Per-column required setting:** Column types can optionally specify a `required` flag:
|
|
421
|
+
```md
|
|
422
|
+
columnTypes=[{type: "string", required: true}, "number", "url"]
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**Basic table-field (columnLabels backfilled from header row):**
|
|
426
|
+
```md
|
|
427
|
+
{% field kind="table" id="team" label="Team Members"
|
|
428
|
+
columnIds=["name", "title", "department"] %}
|
|
429
|
+
| Name | Title | Department |
|
|
430
|
+
|------|-------|------------|
|
|
431
|
+
{% /field %}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
**Explicit column labels (when different from header row):**
|
|
435
|
+
```md
|
|
436
|
+
{% field kind="table" id="team" label="Team Members"
|
|
437
|
+
columnIds=["name", "title", "department"]
|
|
438
|
+
columnLabels=["Full Name", "Job Title", "Department"]
|
|
439
|
+
columnTypes=["string", "string", "string"] %}
|
|
440
|
+
| Full Name | Job Title | Department |
|
|
441
|
+
|-----------|-----------|------------|
|
|
442
|
+
{% /field %}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Complete example with types and data:**
|
|
446
|
+
```md
|
|
447
|
+
{% field kind="table" id="films" label="Notable Films" required=true
|
|
448
|
+
columnIds=["release_year", "title", "rt_score", "box_office_m"]
|
|
449
|
+
columnLabels=["Year", "Title", "RT Score", "Box Office ($M)"]
|
|
450
|
+
columnTypes=["year", "string", "number", "number"]
|
|
451
|
+
minRows=1 maxRows=10 %}
|
|
452
|
+
| Year | Title | RT Score | Box Office ($M) |
|
|
453
|
+
|------|-------|----------|-----------------|
|
|
454
|
+
| 2023 | Barbie | 88 | 1441.8 |
|
|
455
|
+
| 2019 | Once Upon a Time in Hollywood | 85 | 374.3 |
|
|
456
|
+
{% /field %}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**Sentinel values in table cells:**
|
|
460
|
+
Cells can use `%SKIP%` and `%ABORT%` sentinels with optional reasons:
|
|
461
|
+
```md
|
|
462
|
+
| 2017 | I, Tonya | 90 | %SKIP% (Box office not tracked) |
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
**Cell escaping:** Use `\|` for literal pipe characters in cell values.
|
|
466
|
+
|
|
364
467
|
#### Documentation Blocks
|
|
365
468
|
|
|
366
469
|
Documentation blocks provide contextual help attached to form elements and each has its
|
|
@@ -416,7 +519,7 @@ without filtering out nested doc blocks.
|
|
|
416
519
|
|
|
417
520
|
#### Field Values
|
|
418
521
|
|
|
419
|
-
Values are encoded differently based on field
|
|
522
|
+
Values are encoded differently based on field kind.
|
|
420
523
|
The `fence` node with `language="value"` is used for scalar values (see
|
|
421
524
|
[Markdoc Nodes][markdoc-nodes] for fence handling).
|
|
422
525
|
|
|
@@ -424,32 +527,32 @@ The `fence` node with `language="value"` is used for scalar values (see
|
|
|
424
527
|
|
|
425
528
|
**Empty:** Omit the body entirely:
|
|
426
529
|
```md
|
|
427
|
-
{%
|
|
530
|
+
{% field kind="string" id="company_name" label="Company name" required=true %}{% /field %}
|
|
428
531
|
```
|
|
429
532
|
|
|
430
533
|
**Filled:** Value in a fenced code block with language `value`:
|
|
431
534
|
````md
|
|
432
|
-
{%
|
|
535
|
+
{% field kind="string" id="company_name" label="Company name" required=true %}
|
|
433
536
|
```value
|
|
434
537
|
ACME Corp
|
|
435
538
|
````
|
|
436
|
-
{% /
|
|
539
|
+
{% /field %}
|
|
437
540
|
````
|
|
438
541
|
|
|
439
542
|
##### Number Fields
|
|
440
543
|
|
|
441
544
|
**Empty:**
|
|
442
545
|
```md
|
|
443
|
-
{%
|
|
546
|
+
{% field kind="number" id="revenue_m" label="Revenue (millions)" %}{% /field %}
|
|
444
547
|
````
|
|
445
548
|
|
|
446
549
|
**Filled:** Numeric value as string in fence (parsed to number):
|
|
447
550
|
````md
|
|
448
|
-
{%
|
|
551
|
+
{% field kind="number" id="revenue_m" label="Revenue (millions)" %}
|
|
449
552
|
```value
|
|
450
553
|
1234.56
|
|
451
554
|
````
|
|
452
|
-
{% /
|
|
555
|
+
{% /field %}
|
|
453
556
|
````
|
|
454
557
|
|
|
455
558
|
##### Single-Select Fields
|
|
@@ -457,11 +560,11 @@ ACME Corp
|
|
|
457
560
|
Values are encoded **inline** via `[x]` marker—at most one option may be selected (if
|
|
458
561
|
`required=true`, exactly one must be selected):
|
|
459
562
|
```md
|
|
460
|
-
{%
|
|
563
|
+
{% field kind="single_select" id="rating" label="Rating" %}
|
|
461
564
|
- [ ] Bullish {% #bullish %}
|
|
462
565
|
- [x] Neutral {% #neutral %}
|
|
463
566
|
- [ ] Bearish {% #bearish %}
|
|
464
|
-
{% /
|
|
567
|
+
{% /field %}
|
|
465
568
|
````
|
|
466
569
|
|
|
467
570
|
Option IDs are scoped to the field—reference as `rating.bullish`, `rating.neutral`, etc.
|
|
@@ -470,41 +573,41 @@ Option IDs are scoped to the field—reference as `rating.bullish`, `rating.neut
|
|
|
470
573
|
|
|
471
574
|
Values are encoded **inline** via `[x]` markers—no separate value fence:
|
|
472
575
|
```md
|
|
473
|
-
{%
|
|
576
|
+
{% field kind="multi_select" id="categories" label="Categories" %}
|
|
474
577
|
- [x] Technology {% #tech %}
|
|
475
578
|
- [ ] Healthcare {% #health %}
|
|
476
579
|
- [x] Finance {% #finance %}
|
|
477
|
-
{% /
|
|
580
|
+
{% /field %}
|
|
478
581
|
```
|
|
479
582
|
|
|
480
583
|
##### Checkboxes Fields
|
|
481
584
|
|
|
482
585
|
Values are encoded **inline** via state markers—no separate value fence:
|
|
483
586
|
```md
|
|
484
|
-
{% checkboxes id="tasks" label="Tasks" %}
|
|
587
|
+
{% field kind="checkboxes" id="tasks" label="Tasks" %}
|
|
485
588
|
- [x] Review docs {% #review %}
|
|
486
589
|
- [/] Write tests {% #tests %}
|
|
487
590
|
- [*] Run CI {% #ci %}
|
|
488
591
|
- [ ] Deploy {% #deploy %}
|
|
489
592
|
- [-] Manual QA {% #manual_qa %}
|
|
490
|
-
{% /
|
|
593
|
+
{% /field %}
|
|
491
594
|
```
|
|
492
595
|
|
|
493
596
|
For simple two-state checkboxes:
|
|
494
597
|
```md
|
|
495
|
-
{% checkboxes id="agreements" label="Agreements" checkboxMode="simple" %}
|
|
598
|
+
{% field kind="checkboxes" id="agreements" label="Agreements" checkboxMode="simple" %}
|
|
496
599
|
- [x] I agree to terms {% #terms %}
|
|
497
600
|
- [ ] Subscribe to newsletter {% #newsletter %}
|
|
498
|
-
{% /
|
|
601
|
+
{% /field %}
|
|
499
602
|
```
|
|
500
603
|
|
|
501
604
|
For explicit yes/no checkboxes (requires answer for each):
|
|
502
605
|
```md
|
|
503
|
-
{% checkboxes id="risk_factors" label="Risk Assessment" checkboxMode="explicit" required=true %}
|
|
606
|
+
{% field kind="checkboxes" id="risk_factors" label="Risk Assessment" checkboxMode="explicit" required=true %}
|
|
504
607
|
- [y] Market volatility risk assessed {% #market %}
|
|
505
608
|
- [n] Regulatory risk assessed {% #regulatory %}
|
|
506
609
|
- [ ] Currency risk assessed {% #currency %}
|
|
507
|
-
{% /
|
|
610
|
+
{% /field %}
|
|
508
611
|
```
|
|
509
612
|
|
|
510
613
|
In this example, `risk_factors.currency` is unfilled (`[ ]`) and will fail validation
|
|
@@ -518,18 +621,18 @@ Items do not have individual IDs—the field has an ID and items are positional
|
|
|
518
621
|
|
|
519
622
|
**Empty:**
|
|
520
623
|
```md
|
|
521
|
-
{%
|
|
624
|
+
{% field kind="string_list" id="key_commitments" label="Key commitments" minItems=1 %}{% /field %}
|
|
522
625
|
```
|
|
523
626
|
|
|
524
627
|
**Filled:** One item per non-empty line in the value fence:
|
|
525
628
|
````md
|
|
526
|
-
{%
|
|
629
|
+
{% field kind="string_list" id="key_commitments" label="Key commitments" minItems=1 %}
|
|
527
630
|
```value
|
|
528
631
|
Ship v1.0 by end of Q1
|
|
529
632
|
Complete security audit
|
|
530
633
|
Migrate legacy users to new platform
|
|
531
634
|
````
|
|
532
|
-
{% /
|
|
635
|
+
{% /field %}
|
|
533
636
|
````
|
|
534
637
|
|
|
535
638
|
**Parsing rules:**
|
|
@@ -558,7 +661,7 @@ Regulatory changes in EU market
|
|
|
558
661
|
Competitor launching similar feature in Q2
|
|
559
662
|
Customer concentration risk (top 3 = 60% revenue)
|
|
560
663
|
````
|
|
561
|
-
{% /
|
|
664
|
+
{% /field %}
|
|
562
665
|
|
|
563
666
|
{% instructions ref="top_risks" %} One risk per line.
|
|
564
667
|
Be specific (company- or product-specific), not generic.
|
|
@@ -575,12 +678,12 @@ serialized on the opening tag:
|
|
|
575
678
|
|
|
576
679
|
**Skipped field (no reason):**
|
|
577
680
|
```md
|
|
578
|
-
{%
|
|
681
|
+
{% field kind="string" id="optional_notes" label="Optional notes" state="skipped" %}{% /field %}
|
|
579
682
|
````
|
|
580
683
|
|
|
581
684
|
**Aborted field (no reason):**
|
|
582
685
|
```md
|
|
583
|
-
{%
|
|
686
|
+
{% field kind="string" id="company_name" label="Company name" required=true state="aborted" %}{% /field %}
|
|
584
687
|
```
|
|
585
688
|
|
|
586
689
|
**State attribute values:**
|
|
@@ -595,19 +698,19 @@ When a field has a skip or abort state AND a reason was provided via the patch,
|
|
|
595
698
|
reason is embedded in the sentinel value using parentheses:
|
|
596
699
|
|
|
597
700
|
````md
|
|
598
|
-
{%
|
|
701
|
+
{% field kind="string" id="competitor_analysis" label="Competitor analysis" state="skipped" %}
|
|
599
702
|
```value
|
|
600
703
|
%SKIP% (Information not publicly available)
|
|
601
704
|
````
|
|
602
|
-
{% /
|
|
705
|
+
{% /field %}
|
|
603
706
|
````
|
|
604
707
|
|
|
605
708
|
```md
|
|
606
|
-
{%
|
|
709
|
+
{% field kind="number" id="projected_revenue" label="Projected revenue" state="aborted" %}
|
|
607
710
|
```value
|
|
608
711
|
%ABORT% (Financial projections cannot be determined from available data)
|
|
609
712
|
````
|
|
610
|
-
{% /
|
|
713
|
+
{% /field %}
|
|
611
714
|
````
|
|
612
715
|
|
|
613
716
|
**Parsing rules:**
|
|
@@ -668,11 +771,11 @@ Analysis completed with partial data due to API limitations.
|
|
|
668
771
|
{% form id="quarterly_earnings" title="Quarterly Earnings Analysis" %}
|
|
669
772
|
|
|
670
773
|
{% field-group id="company_info" title="Company Info" %}
|
|
671
|
-
{%
|
|
774
|
+
{% field kind="string" id="company_name" label="Company name" state="skipped" %}
|
|
672
775
|
```value
|
|
673
776
|
%SKIP% (Not applicable for this analysis type)
|
|
674
777
|
````
|
|
675
|
-
{% /
|
|
778
|
+
{% /field %} {% /field-group %}
|
|
676
779
|
|
|
677
780
|
{% note id="n1" ref="quarterly_earnings" role="agent" %} Analysis completed with partial
|
|
678
781
|
data due to API limitations.
|
|
@@ -709,20 +812,20 @@ function containsMarkdocSyntax(value: string): boolean {
|
|
|
709
812
|
|
|
710
813
|
**Example (process=false required):**
|
|
711
814
|
````md
|
|
712
|
-
{%
|
|
815
|
+
{% field kind="string" id="notes" label="Notes" %}
|
|
713
816
|
```value {% process=false %}
|
|
714
817
|
Use {% tag %} for special formatting.
|
|
715
818
|
````
|
|
716
|
-
{% /
|
|
819
|
+
{% /field %}
|
|
717
820
|
````
|
|
718
821
|
|
|
719
822
|
**Example (process=false not needed):**
|
|
720
823
|
```md
|
|
721
|
-
{%
|
|
824
|
+
{% field kind="string" id="name" label="Name" %}
|
|
722
825
|
```value
|
|
723
826
|
Alice Johnson
|
|
724
827
|
````
|
|
725
|
-
{% /
|
|
828
|
+
{% /field %}
|
|
726
829
|
````
|
|
727
830
|
|
|
728
831
|
See [GitHub Discussion #261][markdoc-process-false] for background on the attribute.
|
|
@@ -744,33 +847,33 @@ Prepare an earnings-call brief by extracting key financials and writing a thesis
|
|
|
744
847
|
{% /description %}
|
|
745
848
|
|
|
746
849
|
{% field-group id="company_info" title="Company Info" %}
|
|
747
|
-
{%
|
|
748
|
-
{%
|
|
749
|
-
{%
|
|
850
|
+
{% field kind="string" id="company_name" label="Company name" required=true %}{% /field %}
|
|
851
|
+
{% field kind="string" id="ticker" label="Ticker" required=true %}{% /field %}
|
|
852
|
+
{% field kind="string" id="fiscal_period" label="Fiscal period" required=true %}{% /field %}
|
|
750
853
|
{% /field-group %}
|
|
751
854
|
|
|
752
855
|
{% field-group id="source_docs" title="Source Documents" %}
|
|
753
|
-
{% checkboxes id="docs_reviewed" label="Documents reviewed" required=true %}
|
|
856
|
+
{% field kind="checkboxes" id="docs_reviewed" label="Documents reviewed" required=true %}
|
|
754
857
|
- [ ] 10-K {% #ten_k %}
|
|
755
858
|
- [ ] 10-Q {% #ten_q %}
|
|
756
859
|
- [ ] Earnings release {% #earnings_release %}
|
|
757
860
|
- [ ] Earnings call transcript {% #call_transcript %}
|
|
758
|
-
{% /
|
|
861
|
+
{% /field %}
|
|
759
862
|
{% /field-group %}
|
|
760
863
|
|
|
761
864
|
{% field-group id="financials" title="Key Financials" %}
|
|
762
|
-
{%
|
|
763
|
-
{%
|
|
764
|
-
{%
|
|
865
|
+
{% field kind="number" id="revenue_m" label="Revenue (USD millions)" required=true %}{% /field %}
|
|
866
|
+
{% field kind="number" id="gross_margin_pct" label="Gross margin (%)" %}{% /field %}
|
|
867
|
+
{% field kind="number" id="eps_diluted" label="Diluted EPS" required=true %}{% /field %}
|
|
765
868
|
{% /field-group %}
|
|
766
869
|
|
|
767
870
|
{% field-group id="analysis" title="Analysis" %}
|
|
768
|
-
{%
|
|
871
|
+
{% field kind="single_select" id="rating" label="Overall rating" required=true %}
|
|
769
872
|
- [ ] Bullish {% #bullish %}
|
|
770
873
|
- [ ] Neutral {% #neutral %}
|
|
771
874
|
- [ ] Bearish {% #bearish %}
|
|
772
|
-
{% /
|
|
773
|
-
{%
|
|
875
|
+
{% /field %}
|
|
876
|
+
{% field kind="string" id="thesis" label="Investment thesis" required=true %}{% /field %}
|
|
774
877
|
{% /field-group %}
|
|
775
878
|
|
|
776
879
|
{% /form %}
|
|
@@ -784,16 +887,16 @@ Hand-authored forms only need the `spec` field.
|
|
|
784
887
|
|
|
785
888
|
````md
|
|
786
889
|
{% field-group id="company_info" title="Company Info" %}
|
|
787
|
-
{%
|
|
890
|
+
{% field kind="string" id="company_name" label="Company name" required=true %}
|
|
788
891
|
```value
|
|
789
892
|
ACME Corp
|
|
790
893
|
````
|
|
791
|
-
{% /
|
|
894
|
+
{% /field %} {% field kind="string" id="ticker" label="Ticker" required=true %}
|
|
792
895
|
```value
|
|
793
896
|
ACME
|
|
794
897
|
```
|
|
795
|
-
{% /
|
|
796
|
-
required=true %}{% /
|
|
898
|
+
{% /field %} {% field kind="string" id="fiscal_period" label="Fiscal period"
|
|
899
|
+
required=true %}{% /field %} {% /field-group %}
|
|
797
900
|
|
|
798
901
|
{% field-group id="source_docs" title="Source Documents" %} {% checkboxes
|
|
799
902
|
id="docs_reviewed" label="Documents reviewed" required=true %}
|
|
@@ -804,7 +907,7 @@ id="docs_reviewed" label="Documents reviewed" required=true %}
|
|
|
804
907
|
|
|
805
908
|
- [/] Earnings release {% #earnings_release %}
|
|
806
909
|
|
|
807
|
-
- [ ] Earnings call transcript {% #call_transcript %} {% /
|
|
910
|
+
- [ ] Earnings call transcript {% #call_transcript %} {% /field %} {% /field-group
|
|
808
911
|
%}
|
|
809
912
|
````
|
|
810
913
|
|
|
@@ -912,7 +1015,7 @@ would create ambiguous or malformed output.
|
|
|
912
1015
|
**Example—Markdown documentation inside a value:**
|
|
913
1016
|
|
|
914
1017
|
```md
|
|
915
|
-
{%
|
|
1018
|
+
{% field kind="string" id="setup_guide" label="Setup Guide" %}
|
|
916
1019
|
~~~value
|
|
917
1020
|
## Installation
|
|
918
1021
|
|
|
@@ -930,7 +1033,7 @@ Then configure:
|
|
|
930
1033
|
}
|
|
931
1034
|
```
|
|
932
1035
|
~~~
|
|
933
|
-
{% /
|
|
1036
|
+
{% /field %}
|
|
934
1037
|
```
|
|
935
1038
|
|
|
936
1039
|
Here the serializer chose tildes (`~~~`) because the content contains backticks. The
|
|
@@ -953,6 +1056,107 @@ language (e.g., Pydantic for Python, JSON Schema for language-agnostic interchan
|
|
|
953
1056
|
The schemas below are normative—conforming implementations must support equivalent
|
|
954
1057
|
data structures.
|
|
955
1058
|
|
|
1059
|
+
#### Type System
|
|
1060
|
+
|
|
1061
|
+
This section formalizes the distinction between **field kinds** (Markform's field
|
|
1062
|
+
classification) and **data types** (the underlying value representation).
|
|
1063
|
+
|
|
1064
|
+
##### Terminology
|
|
1065
|
+
|
|
1066
|
+
| Term | Definition | Examples |
|
|
1067
|
+
| --- | --- | --- |
|
|
1068
|
+
| **Field Kind** | Markform field classification. Determines syntax, validation, and behavior. | `string`, `single_select`, `table` |
|
|
1069
|
+
| **Data Type** | TypeScript/JSON type of the value. | `string`, `number`, `string[]` |
|
|
1070
|
+
| **Value Type** | Complete type expression including nullability. | `string \| null`, `OptionId[]` |
|
|
1071
|
+
| **Scalar Type** | Single atomic value (optionally format-constrained). | `string`, `url`, `date` |
|
|
1072
|
+
| **Column Type** | Type of a cell in a table field (subset of scalar types). | `string`, `number`, `url` |
|
|
1073
|
+
|
|
1074
|
+
##### Data Type Taxonomy
|
|
1075
|
+
|
|
1076
|
+
**Primitive Types** — Base JSON types:
|
|
1077
|
+
|
|
1078
|
+
| Primitive | Description |
|
|
1079
|
+
| --- | --- |
|
|
1080
|
+
| `string` | UTF-8 text |
|
|
1081
|
+
| `number` | IEEE 754 float (includes integers) |
|
|
1082
|
+
| `boolean` | true/false |
|
|
1083
|
+
| `null` | Absence of value |
|
|
1084
|
+
|
|
1085
|
+
**Scalar Types** — Primitives with optional format constraints:
|
|
1086
|
+
|
|
1087
|
+
| Scalar Type | Base Primitive | Format Constraint |
|
|
1088
|
+
| --- | --- | --- |
|
|
1089
|
+
| `string` | `string` | — |
|
|
1090
|
+
| `number` | `number` | — |
|
|
1091
|
+
| `url` | `string` | Valid URL |
|
|
1092
|
+
| `date` | `string` | ISO 8601 (YYYY-MM-DD) |
|
|
1093
|
+
| `year` | `number` | Integer in valid year range |
|
|
1094
|
+
|
|
1095
|
+
**Enum Types** — Values constrained to a defined set:
|
|
1096
|
+
|
|
1097
|
+
| Enum Type | Base Primitive | Values |
|
|
1098
|
+
| --- | --- | --- |
|
|
1099
|
+
| `OptionId` | `string` | One of the field's defined option IDs |
|
|
1100
|
+
| `CheckboxValue` | `string` | State tokens based on `checkboxMode` |
|
|
1101
|
+
|
|
1102
|
+
**Collection Types** — Compound types:
|
|
1103
|
+
|
|
1104
|
+
| Collection Type | Structure |
|
|
1105
|
+
| --- | --- |
|
|
1106
|
+
| `Array<T>` | Ordered list of `T` |
|
|
1107
|
+
| `Record<K, V>` | Key-value map |
|
|
1108
|
+
|
|
1109
|
+
**Structured Types** — Complex domain objects:
|
|
1110
|
+
|
|
1111
|
+
| Structured Type | Definition |
|
|
1112
|
+
| --- | --- |
|
|
1113
|
+
| `TableRow` | `Record<ColumnId, CellValue>` |
|
|
1114
|
+
| `CellValue` | Scalar type determined by column's type |
|
|
1115
|
+
|
|
1116
|
+
##### Field Kind Taxonomy
|
|
1117
|
+
|
|
1118
|
+
Field kinds are organized into four categories:
|
|
1119
|
+
|
|
1120
|
+
```
|
|
1121
|
+
Field Kinds
|
|
1122
|
+
├── Simple Kinds ──────── Single scalar value (nullable)
|
|
1123
|
+
│ ├── string Also usable as column types
|
|
1124
|
+
│ ├── number in table fields
|
|
1125
|
+
│ ├── url
|
|
1126
|
+
│ ├── date
|
|
1127
|
+
│ └── year
|
|
1128
|
+
│
|
|
1129
|
+
├── List Kinds ────────── Ordered array of scalars
|
|
1130
|
+
│ ├── string_list Open-ended (user provides items)
|
|
1131
|
+
│ └── url_list
|
|
1132
|
+
│
|
|
1133
|
+
├── Chooser Kinds ─────── Selection from predefined options
|
|
1134
|
+
│ ├── single_select Pick one
|
|
1135
|
+
│ ├── multi_select Pick many
|
|
1136
|
+
│ └── checkboxes State per option
|
|
1137
|
+
│
|
|
1138
|
+
└── Structured Kinds ──── Complex nested data
|
|
1139
|
+
└── table Rows × typed columns
|
|
1140
|
+
```
|
|
1141
|
+
|
|
1142
|
+
**Simple Kinds** can also be used as column types in table fields.
|
|
1143
|
+
|
|
1144
|
+
##### Kind → Type Mapping
|
|
1145
|
+
|
|
1146
|
+
| Field Kind | Category | Value Type | Base Type | Notes |
|
|
1147
|
+
| --- | --- | --- | --- | --- |
|
|
1148
|
+
| `string` | Simple | `string \| null` | `string` | Plain text |
|
|
1149
|
+
| `number` | Simple | `number \| null` | `number` | Integer or float |
|
|
1150
|
+
| `url` | Simple | `string \| null` | `string` | URL format validated |
|
|
1151
|
+
| `date` | Simple | `string \| null` | `string` | ISO 8601 format |
|
|
1152
|
+
| `year` | Simple | `number \| null` | `number` | Integer year |
|
|
1153
|
+
| `string_list` | List | `string[]` | `Array<string>` | Empty = `[]` |
|
|
1154
|
+
| `url_list` | List | `string[]` | `Array<string>` | URL format validated |
|
|
1155
|
+
| `single_select` | Chooser | `OptionId \| null` | `string` (enum) | One of defined options |
|
|
1156
|
+
| `multi_select` | Chooser | `OptionId[]` | `Array<string>` (enum) | Subset of options |
|
|
1157
|
+
| `checkboxes` | Chooser | `Record<OptionId, CheckboxValue>` | `Record<string, string>` | State per option |
|
|
1158
|
+
| `table` | Structured | `TableRow[]` | `Array<Record<string, CellValue>>` | Typed columns |
|
|
1159
|
+
|
|
956
1160
|
#### Canonical TypeScript Types
|
|
957
1161
|
|
|
958
1162
|
```ts
|
|
@@ -961,7 +1165,7 @@ type Id = string; // validated snake_case, e.g., /^[a-z][a-z0-9_]*$/
|
|
|
961
1165
|
// Validator reference: simple string ID or parameterized object
|
|
962
1166
|
type ValidatorRef = string | { id: string; [key: string]: unknown };
|
|
963
1167
|
|
|
964
|
-
// Answer state for a field - orthogonal to field
|
|
1168
|
+
// Answer state for a field - orthogonal to field kind
|
|
965
1169
|
// Any field can be in any answer state
|
|
966
1170
|
type AnswerState = 'unanswered' | 'answered' | 'skipped' | 'aborted';
|
|
967
1171
|
|
|
@@ -1196,10 +1400,10 @@ type FieldKind = 'string' | 'number' | 'string_list' | 'checkboxes' | 'single_se
|
|
|
1196
1400
|
|
|
1197
1401
|
interface StructureSummary {
|
|
1198
1402
|
groupCount: number; // total field-groups
|
|
1199
|
-
fieldCount: number; // total fields (all
|
|
1403
|
+
fieldCount: number; // total fields (all kinds)
|
|
1200
1404
|
optionCount: number; // total options across all select/checkbox fields
|
|
1201
1405
|
|
|
1202
|
-
fieldCountByKind: Record<FieldKind, number>; // breakdown by field
|
|
1406
|
+
fieldCountByKind: Record<FieldKind, number>; // breakdown by field kind
|
|
1203
1407
|
|
|
1204
1408
|
/** Map of group ID -> 'field_group' (for completeness; groups have one kind) */
|
|
1205
1409
|
groupsById: Record<Id, 'field_group'>;
|
|
@@ -1237,7 +1441,7 @@ Tracks filling progress per field without exposing actual values:
|
|
|
1237
1441
|
type ProgressState = 'empty' | 'incomplete' | 'invalid' | 'complete';
|
|
1238
1442
|
|
|
1239
1443
|
interface FieldProgress {
|
|
1240
|
-
kind: FieldKind; // field
|
|
1444
|
+
kind: FieldKind; // field kind
|
|
1241
1445
|
required: boolean; // whether field has required=true
|
|
1242
1446
|
|
|
1243
1447
|
answerState: AnswerState; // unified answer state (unanswered/answered/skipped/aborted)
|
|
@@ -1390,7 +1594,7 @@ else:
|
|
|
1390
1594
|
For form completion purposes, fields with constraints are treated as implicitly
|
|
1391
1595
|
required:
|
|
1392
1596
|
|
|
1393
|
-
| Field
|
|
1597
|
+
| Field Kind | Implicit Required When |
|
|
1394
1598
|
| --- | --- |
|
|
1395
1599
|
| `string-list` | `minItems > 0` |
|
|
1396
1600
|
| `multi-select` | `minSelections > 0` |
|
|
@@ -1577,10 +1781,10 @@ const MarkformFrontmatterSchema = z.object({
|
|
|
1577
1781
|
Use a `toSnakeCaseDeep()` helper for deterministic conversion at the frontmatter
|
|
1578
1782
|
boundary.
|
|
1579
1783
|
|
|
1580
|
-
#### Comprehensive Field
|
|
1784
|
+
#### Comprehensive Field Kind Reference
|
|
1581
1785
|
|
|
1582
1786
|
This section provides a complete mapping between Markdoc syntax, TypeScript types, and
|
|
1583
|
-
schema representations for all field
|
|
1787
|
+
schema representations for all field kinds.
|
|
1584
1788
|
|
|
1585
1789
|
##### Naming Conventions
|
|
1586
1790
|
|
|
@@ -1593,7 +1797,7 @@ schema representations for all field types.
|
|
|
1593
1797
|
| JSON Schema keywords | camelCase | `minItems`, `maxLength`, `uniqueItems` |
|
|
1594
1798
|
| IDs (values) | snake_case | `company_name`, `ten_k`, `quarterly_earnings` |
|
|
1595
1799
|
| YAML keys (frontmatter, session transcripts) | snake_case | `spec`, `form_summary`, `field_count_by_kind` |
|
|
1596
|
-
| Kind values (field
|
|
1800
|
+
| Kind values (field kinds) | snake_case | `'string'`, `'single_select'` |
|
|
1597
1801
|
| Patch operations | snake_case | `set_string`, `set_single_select` |
|
|
1598
1802
|
|
|
1599
1803
|
**Rationale:** Using camelCase for Markdoc attributes aligns with JSON Schema keywords
|
|
@@ -1605,11 +1809,11 @@ YAML keys use snake_case for readability and consistency with common YAML conven
|
|
|
1605
1809
|
|
|
1606
1810
|
| Property | Used on | Values | Notes |
|
|
1607
1811
|
| --- | --- | --- | --- |
|
|
1608
|
-
| `kind` | `Field`, `FieldValue` | `FieldKind` values | Reserved for field
|
|
1812
|
+
| `kind` | `Field`, `FieldValue` | `FieldKind` values | Reserved for field kind discrimination only |
|
|
1609
1813
|
| `tag` | `DocumentationBlock` | `DocumentationTag` values | Identifies doc block type |
|
|
1610
1814
|
| `nodeType` | `IdIndexEntry` | `'form' \| 'group' \| 'field'` | Identifies structural element type |
|
|
1611
1815
|
|
|
1612
|
-
##### Field
|
|
1816
|
+
##### Field Kind Mappings
|
|
1613
1817
|
|
|
1614
1818
|
**`string-field`** — Single string value
|
|
1615
1819
|
|
|
@@ -1715,6 +1919,45 @@ YAML keys use snake_case for readability and consistency with common YAML conven
|
|
|
1715
1919
|
| Zod | `z.array(z.string().url()).min(n).max(m)` |
|
|
1716
1920
|
| JSON Schema | `{ type: "array", items: { type: "string", format: "uri" }, minItems, maxItems, uniqueItems }` |
|
|
1717
1921
|
|
|
1922
|
+
**`date-field`** — ISO 8601 date value (YYYY-MM-DD)
|
|
1923
|
+
|
|
1924
|
+
| Aspect | Value |
|
|
1925
|
+
| --- | --- |
|
|
1926
|
+
| Markdoc tag | `date-field` |
|
|
1927
|
+
| TypeScript interface | `DateField` |
|
|
1928
|
+
| TypeScript kind | `'date'` |
|
|
1929
|
+
| Attributes | `id`, `label`, `required`, `min`, `max` |
|
|
1930
|
+
| FieldValue | `{ kind: 'date'; value: string \| null }` |
|
|
1931
|
+
| Patch operation | `{ op: 'set_date'; fieldId: Id; value: string \| null }` |
|
|
1932
|
+
| Zod | `z.string().regex(/^\d{4}-\d{2}-\d{2}$/)` |
|
|
1933
|
+
| JSON Schema | `{ type: "string", format: "date" }` |
|
|
1934
|
+
|
|
1935
|
+
**`year-field`** — Integer year value
|
|
1936
|
+
|
|
1937
|
+
| Aspect | Value |
|
|
1938
|
+
| --- | --- |
|
|
1939
|
+
| Markdoc tag | `year-field` |
|
|
1940
|
+
| TypeScript interface | `YearField` |
|
|
1941
|
+
| TypeScript kind | `'year'` |
|
|
1942
|
+
| Attributes | `id`, `label`, `required`, `min`, `max` |
|
|
1943
|
+
| FieldValue | `{ kind: 'year'; value: number \| null }` |
|
|
1944
|
+
| Patch operation | `{ op: 'set_year'; fieldId: Id; value: number \| null }` |
|
|
1945
|
+
| Zod | `z.number().int().min(min).max(max)` |
|
|
1946
|
+
| JSON Schema | `{ type: "integer", minimum: min, maximum: max }` |
|
|
1947
|
+
|
|
1948
|
+
**`table-field`** — Structured tabular data with typed columns
|
|
1949
|
+
|
|
1950
|
+
| Aspect | Value |
|
|
1951
|
+
| --- | --- |
|
|
1952
|
+
| Markdoc tag | `table-field` |
|
|
1953
|
+
| TypeScript interface | `TableField` |
|
|
1954
|
+
| TypeScript kind | `'table'` |
|
|
1955
|
+
| Attributes | `id`, `label`, `required`, `columnIds`, `columnLabels`, `columnTypes`, `minRows`, `maxRows` |
|
|
1956
|
+
| FieldValue | `{ kind: 'table'; rows: TableRowResponse[] }` |
|
|
1957
|
+
| Patch operation | `{ op: 'set_table'; fieldId: Id; rows: PatchTableRow[] }` |
|
|
1958
|
+
| Zod | `z.object({ kind: z.literal('table'), rows: z.array(TableRowResponseSchema) })` |
|
|
1959
|
+
| JSON Schema | `{ type: "object", properties: { kind: { const: "table" }, rows: { type: "array" } } }` |
|
|
1960
|
+
|
|
1718
1961
|
**Note:** `OptionId` values are local to the field (e.g., `"ten_k"`, `"bullish"`). They
|
|
1719
1962
|
are NOT qualified with the field ID in patches or FieldValue—the field context is
|
|
1720
1963
|
implicit.
|
|
@@ -1742,12 +1985,16 @@ Validation happens at two levels: Markdoc syntax validation (see
|
|
|
1742
1985
|
|
|
1743
1986
|
Schema checks (always available, deterministic):
|
|
1744
1987
|
|
|
1745
|
-
| Check | Field
|
|
1988
|
+
| Check | Field Kind | Constraint Source |
|
|
1746
1989
|
| --- | --- | --- |
|
|
1747
1990
|
| Required fields present | All | `required=true` attribute |
|
|
1748
1991
|
| Number parsing success | `number-field` | Built-in |
|
|
1749
1992
|
| Min/max value range | `number-field` | `min`, `max` attributes |
|
|
1750
1993
|
| Integer constraint | `number-field` | `integer=true` attribute |
|
|
1994
|
+
| Date format validation | `date-field` | Built-in (ISO 8601 YYYY-MM-DD) |
|
|
1995
|
+
| Min/max date range | `date-field` | `min`, `max` attributes |
|
|
1996
|
+
| Year integer validation | `year-field` | Built-in (integer) |
|
|
1997
|
+
| Min/max year range | `year-field` | `min`, `max` attributes |
|
|
1751
1998
|
| Pattern match | `string-field` | `pattern` attribute (JS regex) |
|
|
1752
1999
|
| Min/max length | `string-field` | `minLength`, `maxLength` attributes |
|
|
1753
2000
|
| Min/max item count | `string-list` | `minItems`, `maxItems` attributes |
|
|
@@ -1762,13 +2009,17 @@ Output: `ValidationIssue[]`
|
|
|
1762
2009
|
|
|
1763
2010
|
#### Required Field Semantics
|
|
1764
2011
|
|
|
1765
|
-
The `required` attribute has specific semantics for each field
|
|
2012
|
+
The `required` attribute has specific semantics for each field kind.
|
|
1766
2013
|
This section provides normative definitions:
|
|
1767
2014
|
|
|
1768
|
-
| Field
|
|
2015
|
+
| Field Kind | `required=true` means | `required=false` (or omitted) means |
|
|
1769
2016
|
| --- | --- | --- |
|
|
1770
2017
|
| `string-field` | `value !== null && value.trim() !== ""` | Value may be null or empty |
|
|
1771
2018
|
| `number-field` | `value !== null` (and parseable as number) | Value may be null |
|
|
2019
|
+
| `date-field` | `value !== null && isValidDate(value)` | Value may be null |
|
|
2020
|
+
| `year-field` | `value !== null` (and valid integer) | Value may be null |
|
|
2021
|
+
| `url-field` | `value !== null && isValidUrl(value)` | Value may be null |
|
|
2022
|
+
| `url-list` | `items.length >= max(1, minItems)` | Empty array is valid (unless `minItems` constraint) |
|
|
1772
2023
|
| `string-list` | `items.length >= max(1, minItems)` | Empty array is valid (unless `minItems` constraint) |
|
|
1773
2024
|
| `single-select` | Exactly one option must be selected | Zero or one option selected (never >1) |
|
|
1774
2025
|
| `multi-select` | `selected.length >= max(1, minSelections)` | Empty selection valid (unless `minSelections` constraint) |
|
|
@@ -1906,11 +2157,11 @@ export const validators: Record<string, (ctx: ValidatorContext) => ValidationIss
|
|
|
1906
2157
|
|
|
1907
2158
|
<!-- Parameterized: pass min word count as parameter -->
|
|
1908
2159
|
|
|
1909
|
-
{%
|
|
2160
|
+
{% field kind="string" id="thesis" label="Investment thesis" validate=[{id: "min_words", min: 50}] %}{% /field %}
|
|
1910
2161
|
|
|
1911
2162
|
<!-- Multiple validators with different params -->
|
|
1912
2163
|
|
|
1913
|
-
{%
|
|
2164
|
+
{% field kind="string" id="summary" label="Summary" validate=[{id: "min_words", min: 25}, {id: "max_words", max: 100}] %}{% /field %}
|
|
1914
2165
|
|
|
1915
2166
|
<!-- Sum-to validator with configurable target -->
|
|
1916
2167
|
|