@quillmark/wasm 0.105.0 → 0.106.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,143 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.106.0 - 2026-08-16
4
+
5
+ - feat(typst,pdfform): a schema address may step one property into a declared
6
+ container, so `form-field(field: "classification.poc")` and
7
+ `field-region("address.city")` name a cell rather than the container holding
8
+ it. Two generated address tables gate the step the way `array_fields` gates
9
+ the index step, and a typed dictionary and a variant container reach both
10
+ alike: `classification.value` addresses the discriminant, `classification.poc`
11
+ a variant cell in any world. The pdfform binder descends a variant container
12
+ to match, so one address binds on either backend where `address.city` bound
13
+ only on pdfform and asserted on Typst. **Region addresses shift** for a plate
14
+ that reads a container property directly: `#data.classification.poc` regions
15
+ as `classification.poc` where it regioned as `classification`, and `fieldAt`
16
+ answers the same. A container read whole is unchanged, as is a read of a key
17
+ the container does not declare.
18
+ - feat(core,wasm)!: an `enum` may declare `variants:`, a per-member field set
19
+ that exists only in the world where the discriminant holds that member. This
20
+ is the DSL's first cross-field shape, and it replaces the `cui_`-prefix
21
+ convention with one the engine checks: `must_fill` inside a variant keeps its
22
+ ordinary `default:`-presence derivation, so it reads *required in this world* —
23
+ a `poc` obliged on a CUI memo and silent on every other one, the thing
24
+ `must_fill` alone could not say. **Breaking**: declaring `variants:` changes
25
+ the field's resting shape at every projection, from a bare string to a
26
+ container, `{value: <member>, …that member's fields}`; the bare scalar
27
+ (`classification: CUI`) is still accepted as the spelling of a world carrying
28
+ no answers, and coercion normalizes both. The wire carries exactly the live
29
+ world, so a plate reads a variant field inside the `values ∪ blank` branch it
30
+ already owes the enum, and inside that branch every declared field is present
31
+ and needs no guard. A value stranded by a discriminant flip is kept and warned
32
+ (`validation::out_of_variant`), never dropped at coercion or gated at render.
33
+ The ceiling is enforced at load, not discovered at render: a variant carries
34
+ plain data only, sits at card level only, and cannot declare `value`. The
35
+ transform schema projects the container with every world's fields flattened
36
+ under `properties`, since a binding built once against a schema must address a
37
+ field today's document has not selected; member scoping stays on the
38
+ declaration view, where `schema()` emits `variants:` keyed by member.
39
+ `FieldSchema` gains `variants` and `variant_field` (the cell a name declares
40
+ under any world); `VariantFields` and `VARIANT_DISCRIMINANT_KEY` are new.
41
+ - feat(fixtures)!: `usaf_memo`'s four `cui_*` fields move under
42
+ `classification`'s `CUI` variant as `controlled_by`, `poc`, `category`, and
43
+ `limited_dissemination`. `controlled_by` and `poc` drop their `default: ""`
44
+ and are therefore obliged — on a CUI memo only, which is what DoDM 5200.48
45
+ actually requires and what the flat spelling could state only in
46
+ `description:` prose. A document writes `classification: {value: CUI, …}` and
47
+ a plate reads `data.classification.value`.
48
+ - feat(typst): `field-region(field, body)` claims the ink `body` draws for a
49
+ schema field, so a plate can tie content it *composes* — a banner keyed on a
50
+ field, a package-built block, a computed table — to `session.regions()` and
51
+ `session.fieldAt(..)`. Layout-neutral: `body` is returned untouched between two
52
+ invisible `metadata` markers. It is a **fallback** claim, never an override:
53
+ ink already tracked to a field keeps that field, so wrapping is purely
54
+ additive and cannot retarget. Each *call* claims independently, so a wrapper
55
+ invoked once per card yields one region per card — the way a card's scalar
56
+ fields get regions at all, reading as they do from a loop variable that carries
57
+ no per-instance identity. The marker stack persists across pages so a claim can
58
+ span a page break, which leaves a claim whose closing marker never reaches a
59
+ frame bounded by nothing: it would take every unattributed piece of ink to the
60
+ end of the document. Those are found before the scan and suppressed in both the
61
+ region and point queries — an unbounded claim yields nothing rather than
62
+ everything — and reported as a `typst::unclosed_field_region` warning naming
63
+ the field, since only the plate author can act on it. Typst does not separate
64
+ the two markers on its own — they are siblings in content flow — but a plate
65
+ emitting the call's return value in parts can.
66
+ - feat(typst,pdf): `form-field` takes `font`, `size`, and `align`, so an
67
+ injected widget's value can be set to match the type around it. A widget was
68
+ fixed at Helvetica, auto-size, left: auto-size makes the rendered size a
69
+ function of both box height and how much the user has typed, and left
70
+ justification cannot be overcome by geometry, because a fillable box is sized
71
+ for the longest plausible value rather than the value in it. A right-aligned
72
+ fill-in — a USAF memo's date, say — was unreachable. `font` is one of
73
+ `"helvetica"`/`"times"`/`"courier"`, a widget being unable to carry a font
74
+ program; `size` is an absolute length or `auto` for the old behavior; `align`
75
+ is `"left"`/`"center"`/`"right"` and lands in `/Q`. All three are rejected on
76
+ `"checkbox"` and `"signature"`, which carry no variable text. `FieldSpec`
77
+ gains `font`, `font_size`, and `align` (`FormFont` and `TextAlign` are new).
78
+ A field that sets none of them stamps byte-identically to before, and
79
+ `pdfform` is untouched: `form.json` still carries no styling, so the flatten
80
+ path and canvas preview are unchanged.
81
+ - fix(fixtures): the `usaf_memo` indorsement date widget is set in the memo's
82
+ own 12pt Times and ends on the right margin, where the date it stands in for
83
+ would have ended. It was auto-sized Helvetica starting at the fill-in rule's
84
+ left end. Sizing it exposed that the rule-width box clips a real date — "28
85
+ September 2026" sets 93pt at 12pt Times against a 72pt box, and a fixed size
86
+ clips where auto-size had silently shrunk — so the widget is now 10em wide
87
+ and hangs off the rule's right edge, overrunning leftwards into the
88
+ whitespace a printed date grows into. Sized in ems of its own face rather
89
+ than inches because `font_size` is a document field with no ceiling: an inch
90
+ width would stay put while the text inside it grew. 10em clears both
91
+ orderings at any body size (DAF's "September 28, 2026" is the widest at
92
+ 8.03em; USAF's "28 September 2026" is 7.78em). `date-placeholder-line` seats
93
+ it with a measured `dx` rather than `place(bottom + right)`, Typst clamping
94
+ an overflowing alignment back to zero, which leaves `right` indistinguishable
95
+ from `left`. That helper draws no rule now and is named `date-placeholder`
96
+ rather than `date-placeholder-line`: the widget carries the date, and a rule
97
+ under a widget wider than it underlines only the fraction of the value narrow
98
+ enough to sit over it. It is package-internal, not exported from `lib.typ`.
99
+ - fix(core): a name two variants declare *differently* is a load error,
100
+ `quill::variant_field_collision`. The name is one cell of the container
101
+ whichever world brings it into play — neither the coercion lookup nor the
102
+ transform schema consults the discriminant to fill it — so two readings of it
103
+ coerced a live value under the other world's type: a document selecting a
104
+ world whose `note` is `integer` had its `42` coerced to `"42"` by a sibling
105
+ world's `string` and then failed `validation::type_mismatch`, undraftable and
106
+ blamed for a string it never wrote. Identical declarations, which is what
107
+ repeating a shared field set or sharing a YAML anchor produces, collapse to
108
+ that one cell without loss and stay legal.
109
+ - fix(core,wasm,python)!: `EditError::UnknownField` carries the in-field path
110
+ `FieldDecode` and `FieldNotContent` carry. A property an `object` field does
111
+ not declare — `get_content_at("address", [Key("zip")])` against an `address`
112
+ with no `zip` — reported `field 'zip' is not declared in the schema`, which
113
+ reads as a claim about a top-level field and collides outright when a real
114
+ top-level field shares the name. It now reports
115
+ `field 'address.zip' is not declared in the schema` and anchors the
116
+ diagnostic at `main.address.zip`, so the caller can tell an undeclared
117
+ property from an undeclared field. **Breaking**: the variant is a struct
118
+ variant, `UnknownField { field, at }`, matching the two siblings; `field`
119
+ stays a bare field name, and the `edit::unknown_field` code, its `field` arg
120
+ and the whole-field message are unchanged.
121
+ - fix(python): declaring `license-files` ships the `LICENSE` the sdist metadata
122
+ names, which PyPI rejected the sdist for lacking. v0.104.0 and v0.105.0 are
123
+ wheels only.
124
+
125
+ <!-- seed: commits since v0.105.0, confirm the entries above cover them, then delete this comment
126
+ - chore(repo): union-merge CHANGELOG.md
127
+ - feat: migrate usaf_memo to enum variants, document the axis
128
+ - feat(core): enum variants — fields that exist only for one enum value
129
+ - fix(fixtures): drop the rule under the indorsement date
130
+ - fix(fixtures): size the indorsement date widget in ems, not inches
131
+ - fix(fixtures): set the memo's indorsement date widget like a date
132
+ - feat(typst,pdf): font, size, and align on injected form fields
133
+ - Make the usaf_memo indorsement's blank date a bound fill-in widget
134
+ - Compress the field-region prose
135
+ - Add `field-region` so plates can tie composed content to a field
136
+ - fix(python): declare license-files so the sdist ships its LICENSE
137
+ - Anchor an undeclared property at the property, not at a bare name
138
+ -->
139
+
140
+
3
141
  ## v0.105.0 - 2026-08-14
4
142
 
5
143
  - feat(core,wasm,python)!: a `Content` nested inside a composite field is
@@ -462,6 +462,11 @@ export interface QuillFieldSchema {
462
462
  /** The closed set of allowed values. Required on `type: "enum"`, and valid
463
463
  * nowhere else. */
464
464
  values?: string[];
465
+ /** Per-member field sets on a card-level `type: "enum"` field, keyed by
466
+ * member: the fields that exist only where the discriminant holds that
467
+ * member. Declaring it makes the field rest as a container,
468
+ * `{value: <member>, …that member's fields}`, rather than a bare string. */
469
+ variants?: Record<string, Record<string, QuillFieldSchema>>;
465
470
  /** Whether a human must author the field. Absent, it derives from
466
471
  * `default`: a defaulted field is unobliged, a defaultless one obliged. */
467
472
  must_fill?: boolean;
Binary file
@@ -462,6 +462,11 @@ export interface QuillFieldSchema {
462
462
  /** The closed set of allowed values. Required on `type: "enum"`, and valid
463
463
  * nowhere else. */
464
464
  values?: string[];
465
+ /** Per-member field sets on a card-level `type: "enum"` field, keyed by
466
+ * member: the fields that exist only where the discriminant holds that
467
+ * member. Declaring it makes the field rest as a container,
468
+ * `{value: <member>, …that member's fields}`, rather than a bare string. */
469
+ variants?: Record<string, Record<string, QuillFieldSchema>>;
465
470
  /** Whether a human must author the field. Absent, it derives from
466
471
  * `default`: a defaulted field is unobliged, a defaultless one obliged. */
467
472
  must_fill?: boolean;
Binary file
package/core/wasm.d.ts CHANGED
@@ -407,6 +407,11 @@ export interface QuillFieldSchema {
407
407
  /** The closed set of allowed values. Required on `type: "enum"`, and valid
408
408
  * nowhere else. */
409
409
  values?: string[];
410
+ /** Per-member field sets on a card-level `type: "enum"` field, keyed by
411
+ * member: the fields that exist only where the discriminant holds that
412
+ * member. Declaring it makes the field rest as a container,
413
+ * `{value: <member>, …that member's fields}`, rather than a bare string. */
414
+ variants?: Record<string, Record<string, QuillFieldSchema>>;
410
415
  /** Whether a human must author the field. Absent, it derives from
411
416
  * `default`: a defaulted field is unobliged, a defaultless one obliged. */
412
417
  must_fill?: boolean;
package/core/wasm_bg.wasm CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillmark/wasm",
3
- "version": "0.105.0",
3
+ "version": "0.106.0",
4
4
  "description": "WebAssembly bindings for Quillmark, a schema-driven document engine",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",