@quillmark/wasm 0.104.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 +244 -27
- package/README.md +72 -206
- package/backends/pdfform/wasm.d.ts +465 -713
- package/backends/pdfform/wasm.js +365 -491
- package/backends/pdfform/wasm_bg.wasm +0 -0
- package/backends/pdfform/wasm_bg.wasm.d.ts +1 -0
- package/backends/typst/wasm.d.ts +465 -713
- package/backends/typst/wasm.js +365 -491
- package/backends/typst/wasm_bg.wasm +0 -0
- package/backends/typst/wasm_bg.wasm.d.ts +1 -0
- package/core/wasm.d.ts +337 -492
- package/core/wasm.js +310 -411
- package/core/wasm_bg.wasm +0 -0
- package/core/wasm_bg.wasm.d.ts +1 -0
- package/package.json +1 -1
- package/runtime/runtime.d.ts +181 -255
- package/runtime/runtime.js +190 -353
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,249 @@
|
|
|
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
|
+
|
|
141
|
+
## v0.105.0 - 2026-08-14
|
|
142
|
+
|
|
143
|
+
- feat(core,wasm,python)!: a `Content` nested inside a composite field is
|
|
144
|
+
readable at its own codec. `TypedReader::get_content_at(name, path)` (and the
|
|
145
|
+
`CardReader` twin, `reader.getContentAt(addr, path)` in JS,
|
|
146
|
+
`reader.get_content_at(name, path)` in Python) walks a `PathSegment` path
|
|
147
|
+
through the field schema — `items` for an index, `properties` for a key — to
|
|
148
|
+
the leaf whose declared type names the codec, then decodes through the same
|
|
149
|
+
dispatch the whole-field read uses. So an `array<richtext>` element, an
|
|
150
|
+
`object`'s content property and a leaf under both each read back the same
|
|
151
|
+
`Content` whatever their resting form, where before every one of them
|
|
152
|
+
answered `FieldNotContent` and the consumer had to decide for itself what the
|
|
153
|
+
stored bytes meant (#1243). The empty path *is* `get_content`. A path naming
|
|
154
|
+
nothing in the stored value reads absent rather than throwing: an editor's
|
|
155
|
+
row index goes stale between derive and read, and that is the axis a repeater
|
|
156
|
+
mutates. `Addr` deliberately gains no element axis — the path is the read's
|
|
157
|
+
own argument, since `storeField` / `isFill` / `applyChange` could not answer
|
|
158
|
+
one. **Breaking**: `EditError::FieldDecode` and `EditError::FieldNotContent`
|
|
159
|
+
each gain an `at: Vec<PathSegment>` field carrying the in-field path, so the
|
|
160
|
+
diagnostic anchors at `main.paragraphs[1]` and parses back to those segments;
|
|
161
|
+
`field` stays a bare field name and `args` is unchanged. `FieldNotContent`
|
|
162
|
+
now names the type *reached*, so a `string[]` element reports `string` rather
|
|
163
|
+
than the field's `array`.
|
|
164
|
+
- feat(core,wasm)!: `must_fill:` on a field declares the **obligation** axis.
|
|
165
|
+
`default:` carried the fill value and the obligation signal on one bit, so
|
|
166
|
+
only that 2x2's diagonal was reachable; a safe value that still wants a
|
|
167
|
+
human's confirmation (`default: UNCLASSIFIED` with `must_fill: true`) and a
|
|
168
|
+
genuinely optional field with nothing to suggest (`must_fill: false`) now
|
|
169
|
+
each have a spelling. Left unset it derives `default.is_none()`, so no
|
|
170
|
+
existing quill's blueprint marker set changes. `Quill::validate` gains a
|
|
171
|
+
second trigger under the one `validation::must_fill` code, named by a
|
|
172
|
+
`trigger` arg: `marker` for a `!must_fill` tag the document carries, and
|
|
173
|
+
`unauthored` where the schema obliges a cell the document leaves absent or
|
|
174
|
+
present-null. The second closes a hole — `validate_fills` walked only the
|
|
175
|
+
payload, so a hand-written or programmatically built document drew no
|
|
176
|
+
completeness signal whatever `Quill.yaml` declared. **Breaking**: a merely
|
|
177
|
+
incomplete document no longer validates clean. Absence is still never
|
|
178
|
+
*malformed* and still never gates render, but a consumer reading "any
|
|
179
|
+
diagnostic ⇒ not done" now sees a warning per unauthored obliged cell on
|
|
180
|
+
documents that were silent. The obligation keys on cell presence rather than
|
|
181
|
+
the resolved source rung, so a must-fill leaf inside a container someone
|
|
182
|
+
touched still warns; a typed dict is never itself a cell and recurses to its
|
|
183
|
+
leaves, while an array is one cell, `[]` being a real answer. Authoring the
|
|
184
|
+
field's blank discharges it and `field: null` does not: null ≡ absent stays
|
|
185
|
+
unqualified on the value ladder, but obligation asks whether a human made a
|
|
186
|
+
call. Seeding stamps the marker on example-seeded obliged cells, so a fresh
|
|
187
|
+
seed and an empty document report the same cells, and the transform schema
|
|
188
|
+
carries `quillmark:must_fill` (#1255).
|
|
189
|
+
- fix(core,wasm,pdfform)!: a field's **blank** — its spelling of "explicitly
|
|
190
|
+
nothing" — is a property of the field rather than a member of its type's
|
|
191
|
+
domain, and an `enum`'s is `""`. The render floor for a defaultless enum
|
|
192
|
+
returned `values.first()`: a choice nobody made, indistinguishable at the
|
|
193
|
+
plate from a deliberate one and reachable from a cosmetic `values:` reorder.
|
|
194
|
+
An unanswered enum now renders `""`, so a reorder is render-safe for every
|
|
195
|
+
document and only removing or renaming a member breaks
|
|
196
|
+
([VERSIONING.md](prose/canon/VERSIONING.md)). **The accepted domain widens to
|
|
197
|
+
`values ∪ blank` for *every* enum**, defaulted ones included: `format: ""`
|
|
198
|
+
was a fatal `EnumViolation` and now coerces, validates and reaches the plate.
|
|
199
|
+
**A plate must therefore branch exhaustively over `values ∪ blank`** — an
|
|
200
|
+
`else` fallback re-opens exactly the fabrication the blank closes, and a
|
|
201
|
+
downstream package that asserts membership fails the compile outright. Note
|
|
202
|
+
`data.at(key, default: X)` is not a guard here: blank-filled render makes
|
|
203
|
+
every declared key present, so its `default:` is dead code and the blank
|
|
204
|
+
flows through. **Breaking**: `zero_value` → `blank`, `FieldSource::Zero` →
|
|
205
|
+
`Blank` and its wire token `"zero"` → `"blank"`; `""` declared in `values:`
|
|
206
|
+
is a load error (`quill::enum_blank_member`), the engine supplying the blank
|
|
207
|
+
instead; and `date: ""` renders blank rather than falling back to a
|
|
208
|
+
`default:`, settling a three-way disagreement between coercion, validation
|
|
209
|
+
and the floor. `default: ""` stays valid and keeps its meaning — `values:`
|
|
210
|
+
enumerates choices, `default:` is a value, and the blank is a legal value
|
|
211
|
+
that is never a choice. Additive: `ui.blank_title` labels an enum's blank and
|
|
212
|
+
rides the transform schema as `quillmark:blank_title`; that schema's `enum:`
|
|
213
|
+
leads with the blank, so a standard JSON-Schema validator accepts what the
|
|
214
|
+
engine accepts, and pdfform Choice widgets lead their options with it too. A
|
|
215
|
+
consumer's picker must keep the blank selectable and re-selectable — returning
|
|
216
|
+
to it is how an author clears a cell back to unset.
|
|
217
|
+
`integer`, `number` and `boolean` keep `0` / `false` as their blank,
|
|
218
|
+
indistinguishable from an authored zero — a permanent seam, since a wire
|
|
219
|
+
`none` would cost the totality the floor exists to buy. Full guide:
|
|
220
|
+
[0.104 → 0.105](docs/migrations/0.104-to-0.105.md) (#1254).
|
|
221
|
+
- fix(fixtures,docs): the three fixture plates that dispatch on `$kind` read it
|
|
222
|
+
with a bare `card.at("$kind")`, which panics on a kindless card, and guarded
|
|
223
|
+
declared fields against an absence blank-filled render makes impossible.
|
|
224
|
+
Plate authors copy the fixtures rather than `PLATE_DATA.md`, so the fixtures
|
|
225
|
+
were teaching both the unsafe metadata read and a dead presence check.
|
|
226
|
+
`classic_resume` carried the live consequence: `url` is declared with no
|
|
227
|
+
`default:`, so the floor delivers `""`, `default: none` never fires, and the
|
|
228
|
+
package's `url != none` test always passes — an empty Courier element where
|
|
229
|
+
the block should have been skipped. Its `subheading-*` guard cost an empty
|
|
230
|
+
grid row the same way. Declared fields now guard their *value*, and
|
|
231
|
+
`docs/quills/typst-backend.md` states the rule as a table over the three key
|
|
232
|
+
kinds. `fixture_quills_render_test` renders every fixture quill's seed
|
|
233
|
+
document — the net that was missing, since `classic_resume`'s plate had no
|
|
234
|
+
test reaching it (#1256, #1257).
|
|
235
|
+
- test(typst): `plaintext` reaches regions and navigation by inheritance — the
|
|
236
|
+
render floor coerces its resting literal to a content object, the backend
|
|
237
|
+
classifies that object by `contentMediaType` alone, and the shared lowering
|
|
238
|
+
emits it with a segment map — and every step was load-bearing and untested,
|
|
239
|
+
with the classification predicates named for richtext so the sharing read as
|
|
240
|
+
a coincidence. Pinned at engine altitude, because a test driving the backend
|
|
241
|
+
directly hand-builds the content object, bypassing the floor's coercion, and
|
|
242
|
+
would stay green through a regression that silently empties `regions()`. The
|
|
243
|
+
predicates are `is_content_field` / `is_content_array_field` /
|
|
244
|
+
`is_inline_content_field`, and `PREVIEW.md` names plaintext beside richtext
|
|
245
|
+
in its producer list (#1247, #1250).
|
|
246
|
+
|
|
3
247
|
## v0.104.0 - 2026-08-13
|
|
4
248
|
|
|
5
249
|
- feat(core,wasm): a quill declares, per body, the block constructs its plate
|
|
@@ -93,33 +337,6 @@
|
|
|
93
337
|
fixture suite reached neither path. A fourth carries a `!must_fill` tag on two
|
|
94
338
|
example-seeded cells through seed → store → load → conform. Refs #1234
|
|
95
339
|
|
|
96
|
-
<!-- seed: commits since v0.103.0, confirm the entries above cover them, then delete this comment
|
|
97
|
-
- Carry the thematic-break and unsupported-construct work into the guide
|
|
98
|
-
- Dense-prose pass over the new comments and docs
|
|
99
|
-
- Let a quill declare the constructs its plate does not typeset
|
|
100
|
-
- Drain the memo's heading buffer instead of dropping it
|
|
101
|
-
- Canonicalize a thematic break to `***`
|
|
102
|
-
- Close the 0.104 release gaps: the migration guide, and four changelog entries
|
|
103
|
-
- Retire the enum: modifier; project a field's domain by capability
|
|
104
|
-
- Pin the render floor's two type-domain edges and the seeded fill tag
|
|
105
|
-
- Raise the Node floor to 24
|
|
106
|
-
- dense-prose pass over the added prose
|
|
107
|
-
- docs: dense-prose pass on the text-type sections
|
|
108
|
-
- docs: give the text-type choice a decision procedure
|
|
109
|
-
- docs: state the backend seam's scope and reserve its namespaces
|
|
110
|
-
- docs: drop the private intra-doc link from pathFor
|
|
111
|
-
- fix(core): state the unspellable-tail conditions instead of linking a private fn
|
|
112
|
-
- chore(wasm): density pass, and gate a core-build dead function
|
|
113
|
-
- docs: dense-prose pass over the geometry-address translation
|
|
114
|
-
- docs: dense-prose pass over the pathFor / cardPath prose
|
|
115
|
-
- test(wasm): execute the static-export exemption instead of listing it
|
|
116
|
-
- feat(wasm): mint a DocPath from an Addr (#1225)
|
|
117
|
-
- fix(core)!: parse geometry addresses segment-wise (#1223)
|
|
118
|
-
- docs: carry the 0.103 CLI break, and the init gate into the quickstart (#1222)
|
|
119
|
-
- SCHEMAS.md: a declared type change rewrites stored values (#1221)
|
|
120
|
-
-->
|
|
121
|
-
|
|
122
|
-
|
|
123
340
|
## v0.103.0 - 2026-08-09
|
|
124
341
|
|
|
125
342
|
- docs: `docs/integration/operations.md`, carrying what the other integration
|