0.125 → 0.126 — a quill's font collections load, status_report stores enum ids, a $quill name may carry a collection, an acroform quill's field spec is form.yaml, a Typst quill's plate is plate.typ by name and its typst: section is strict, a card groups fields under a top-level object where it declared sections:, a quill's version is MAJOR.MINOR.PATCH and a $quill selector @MAJOR.MINOR or exact, a blueprint prints a blank default: as no default, a text field is text or richtext, one line unless it declares multiline: true, and script links, runaway YAML expansion and bodies nested past 12 levels are refused¶
The first break nothing reports: a Typst quill holding plate.typ at its
root and declaring no typst.plate_file renders that plate, where it rendered
a blank page. The backend finds the plate by name, so a plate.typ the quill
carried without wiring it up now compiles. Every render of such a quill
changes, and no diagnostic says so. A plate.typ there that does not compile,
a stale draft or one importing a file the quill no longer holds, fails open
and quillmark validate. Delete or fix it, or declare the plate the quill
means as typst.plate_file.
The second break nothing reports: a .ttc or .otc under assets/fonts/ or
inside a vendored package loads every face it holds, up to 256, where the
backend skipped it. One declaring more loads none and warns
typst::font_collection_skipped. A plate naming a collection's family renders in it, where it rendered in
a fallback face and warned unknown font family. A quill whose only fonts are
collections renders in them, where it rendered in the embedded Roboto, so a
plate there naming Roboto warns unknown font family. A collection's faces
also join the glyph fallback for a character the plate's fonts lack, even one
inside a vendored package the plate never imports.
The third break: the status_report fixture quill's state stores
on_track, at_risk and blocked, where it stored on track, at risk and
blocked. The quill is version: 0.2.0, where it was 0.1.0, so a document
pinned status_report@0.1 fails quill::version_mismatch. A document holding
state: at risk fails validation::enum_violation and does not render.
Rewrite on track to on_track and at risk to at_risk, and the pin to
status_report@0.2; the banner prints the same text.
A text field is text or richtext, and either is one line unless it
declares multiline: true. type: plaintext is type: text, type: string
is retired in favor of text, and inline: and ui.multiline give way to the
one key multiline:. Each retired spelling fails the load as
quill::field_parse_error, its message naming the replacement, and a richtext
must spell multiline: either way for this release, so none of this migrates
silently. Rewrite each declaration:
| Was | Now |
|---|---|
type: plaintext with inline: true |
type: text |
type: plaintext |
type: text with multiline: true |
type: string |
type: text, plus multiline: true where a value takes more than one line |
type: richtext with inline: true |
type: richtext with multiline: false |
type: richtext |
type: richtext with multiline: true |
ui: { multiline: true } |
multiline: true on a text or richtext; delete it elsewhere |
string?, items: { type: string } |
text?, items: { type: text } |
No Quill.yaml declaring a text field loads on both releases. 0.125
refuses type: text and a field-level multiline:, and 0.126 refuses
string, plaintext and a richtext without multiline:. A host upgrades a
quill and the engine together, cutting both over in one deploy.
A stored former string value holding an interior line break renders on
one line. string, plaintext and text all rest as the verbatim string,
so no stored row is rewritten, but string admitted any string and text is
one line unless it declares multiline: true. A one-line value ignores
trailing whitespace, so a YAML | block's trailing newline passes; a line
break inside the value renders as a space and warns
validation::line_break_folded, and a write refuses it as
edit::field_not_single_line. On 0.126.0 the render failed instead, with
validation::coercion_failed, target text(single_line). To find these
values, run quillmark check --strict with the rewritten quill over the
stored documents, or validate in a binding. Declare multiline: true on a
former string field whose breaks carry meaning, such as an address.
The blueprint prints each text type by its new name and line shape:
| 0.125 annotation | 0.126 annotation |
|---|---|
string |
text<plain>, or text(multiline)<plain> where the field declares multiline: true |
plaintext(inline)<plain> |
text<plain> |
plaintext<plain> |
text(multiline)<plain> |
richtext(inline)<markdown> |
richtext<markdown> |
richtext<markdown> |
richtext(multiline)<markdown> |
richtext<markdown> names a one-line field, where it named a block one, and
neither (inline) annotation is printed.
A former string value reaches a Typst plate as content, where it was a
str, so it prints with nav and click-to-edit. A plate that computes with it
reads the value through the new qm.str(..), which returns its string
(qm.data.memo_for.map(qm.str) for an array). A split, a len,
set document(author: ..) or an image(..) path fails to compile until it
does. Printing it, #qm.data.title, needs no change. A matrix added item's
title is a one-line text cell: a stored document whose added item holds a
multi-line title fails validation::not_single_line. A qm.roster row's
title stays a str.
Four changes nothing reports:
-
A comparison or
upperon a formerstringvalue runs withoutqm.strand answers wrong.qm.data.status == "final"isfalsefor every value, so the plate takes the other branch, andupper(qm.data.office)returns content. Search the plate for==,!=,inandupper(on atextfield and wrap the value inqm.str(..). -
schema()in WASM and Python, andquillmark schema, carrymultilinewhere a field carriedinline: true. The echo is the declaration:stringandplaintextaretext, everyrichtextcarriesmultiline: trueorfalse, atextcarriesmultiline: truewhere it admits more than one line, and theinlinekey is gone. The polarity inverts: a consumer that read the absence ofinlineas "block" now reads every one-line field as a block. Branch onmultilineinstead. WASMQuillFieldUihas nomultiline; read the field's own. - The transform schema a Typst plate's helper reads,
build_transform_schemain Rust, emitsquillmark:multiline: trueon a field that admits more than one line and on every$body, and noquillmark:inline. A formerstringfield there is{type: object, contentMediaType: application/quillmark-content+json, quillmark:plain: true}, where it was{type: string}.QUILLMARK_INLINE_KEYisQUILLMARK_MULTILINE_KEY. - An acroform text widget is multiline exactly where its field declares
multiline: true, where it readui.multiline. A blockrichtextwithout that hint bound a one-line widget, which a value holding a newline widened at render (#2217); it now binds multiline.
validation::not_inline, edit::field_not_inline,
conform::field_not_inline and quill::seed_not_inline are
validation::not_single_line, edit::field_not_single_line,
conform::field_not_single_line and quill::seed_not_single_line, and their
codec argument names text where it named plaintext.
A link whose target's scheme is javascript:, vbscript:, data: or file:
renders as its text alone under Typst, where it rendered a live link in PDF and
SVG. A card-yaml block whose expanded aliases and tags pass twice the block's
bytes plus 4 KiB of scalars, or the block's bytes plus 4 KiB of nodes, fails its
YAML parse, where it parsed.
A body nests at most 12 levels deep, where it nested 100. The list items,
quotes and elements open at one point of a body count toward the bound, and its
marks count nothing (markdown spec §8). A body past it fails
parse::body_import at the body's path, main.body or
cards.<kind>[i].body, where it imported and failed to render once Typst's
show-rule depth ran out, by 16 quotes or 32 lists. Flatten the deepest levels.
A stored row holding such a body fails to load, uncoded, its message naming
NestingTooDeep: open it with a 0.125 build and flatten it before upgrading.
MAX_NESTING_DEPTH is 12, and ParseError::BodyImport holds
{ path, message }.
A quill declaring a field named body, on main or a card kind, fails to
load with quill::reserved_field_name, where it loaded and
the field shared its path, main.body or cards.<kind>[i].body, with the
card's body. Rename the field, and its key in every document.
An acroform quill's field spec is form.yaml, where it was form.json.
A quill holding form.json, alone or beside form.yaml, fails to open as
acroform::legacy_form_json. Rename the file: JSON is YAML 1.2, so its
contents load unchanged, and the schema tag stays quillmark/form@0.2.0. A
generator may keep writing JSON, under the new name. Three contents fail as
acroform::invalid_form_spec where form.json loaded:
- A string holding DEL, a C1 control other than U+0085, U+FFFE or U+FFFF
written raw, which the parser refuses. Escape it, as
\u007f. - A key written twice in one object, which
form.jsonread as its last value when the key was one the spec does not know. Delete the duplicate. - A
rectvalue pastf32range, such as1e39, whichform.jsonread as infinity.
Any spec that fails to parse is acroform::invalid_form_spec, where it was
acroform::invalid_form_json, now located at its line and column in
form.yaml. A quill with no spec is acroform::missing_form_spec, where it
was acroform::missing_form_json.
A Typst quill's plate is plate.typ at the quill root, found by name, and
the typst: section holds only plate_file, which names another path. Two
kinds of quill that rendered now fail, and no type checker sees either:
- A quill holding no
plate.typand declaring notypst.plate_filefails to open astypst::plate_missing, its hint naming both, where it rendered a blank page and reported nothing.quillmark validatereports it, under--no-rendertoo. Aplate_filethat is not a string, such as5or an emptyplate_file:, or that names no path, such as""or./, fails astypst::plate_path_invalid, where it rendered the same blank page or failed astypst::plate_missing. Ship the plate asplate.typ, or name it. - A key under
typst:other thanplate_filefails the load asquill::unknown_backend_key, carryingbackendandkeyinargsand a hint listing the valid keys, where the quill loaded and every render and session warnedtypst::unknown_key, which is retired.typst.packagesis the common one: delete it, since a quill vendors its packages underpackages/. Anacroform:section holding any key fails the same way, since that backend reads none.
A backend section that is a scalar or a sequence fails as
quill::invalid_backend_section, where it was ignored; an empty one, typst:
alone, reads as no keys. A quill whose
typst: { plate_file: plate.typ } names the default loads unchanged, and may
drop the section. quillmark_typst::plate_file, re-exported as
quillmark::typst_plate_file, returns Result<String, RenderError>, where its
Ok held an Option<String>.
Under WASM, a render that traps rejects as runtime::backend_trapped, the
WebAssembly.RuntimeError on its cause, where it rejected as an uncoded
attempted to take ownership of Rust value while it was borrowed. A native
stack overflow rejects under the same code, its RangeError (V8,
JavaScriptCore) or InternalError (Firefox) on cause, where it rejected as
that error itself: a host catching instanceof RangeError reads
cause instead. A host matching either routes on the code, and discards the
realm.
A card declaring sections: fails to load with quill::invalid_card_schema,
its hint naming the top-level object. A card groups fields by declaring a
top-level object keyed by the group's id, with the group's title and its
fields as properties
(Grouping). The object is data:
documents nest each group's keys under its id, and a plate reads
data.<group>.<field>. To keep every address, move each section's fields up
under the card's fields: instead, ungrouped. ui.group / ui.groups load as
before, with quill::deprecated_groups hinting the object rewrite.
quill::empty_section, quill::invalid_section_id,
quill::mixed_sectioning and quill::duplicate_field, which named a field
declared twice across fields: and the sections, are retired with the key. The
usaf_memo fixture declares its fields ungrouped, so its schema emits no
ui.groups or ui.group; its addresses are unchanged.
A $quill name may carry a collection, borb.usaf_memo@1.2, and no longer
starts with _. A quill name and a collection name are each
[a-z][a-z0-9_]*, at most 64 characters; quill.name in Quill.yaml holds to
the same cap. The engine pairs a document with a quill by the part after the
.. QuillReference carries collection: Option<String>.
A blueprint's $kind: main line carries main.title and
main.description, and nothing where neither is declared, where it fell back
to quill.description. quill.description describes the package, for a gallery
or a quill picker, and never reaches the blueprint. A quill that wants the line
kept copies its quill.description to main.description. Documents are
unaffected.
A blueprint prints a blank default: ("", [] or {}) as no default
does: notes: # text<plain>, where it printed notes: "" # text<plain>. The field
renders its blank either way, so the cell says nothing the empty one does not.
A matrix's # e.g. line shows such a column as a defaultless one, its type.
0 and false print as before. A required typed table without a
default: prints [] # array<object>, and an optional one an empty cell,
r: # array<object>?, each led by its synthetic row commented out, where it
printed the row live; a blueprint shipped unedited renders that table empty,
or none where optional, where it rendered one blank row. A consumer that read "" as "an author may
skip this" reads the field's description. The blank default: stays legal
and draws no diagnostic, so a quill deletes it at its own pace. Deleting one
drops it from Quill::schema and moves the field's resolve() source from
default to blank, which an editor may read: @quillmark/svelte marks a
field without a default: as one an author is expected to fill. Documents are
unaffected.
A $quill selector is @MAJOR.MINOR or @MAJOR.MINOR.PATCH, so
$quill: memo@1 fails parse::invalid_quill_reference, where it matched any
1.x.x. A quill's version ranks a bump by what it costs a stored document, and
a MINOR bump needs each one rewritten, so a lone major names no compatible
series (Quill Versioning). Rewrite the reference to
the series the document was written against, memo@1.2, or drop the selector
to match any version. VersionSelector::Major is removed.
A stored row whose $quill is a lone major fails to load, and 0.126 cannot
open it to fix it. No storage door reports parse::invalid_quill_reference.
WASM fromStored / loadStored and Python Document.from_stored throw one
uncoded diagnostic whose message holds invalid quill reference "memo@1". In
Rust, Document::try_from(StoredDocument) returns
StorageError::InvalidQuillReference { value, reason }, and
serde_json::from_str::<Document> a serde_json::Error carrying the same
message. Rewrite such rows before upgrading. A row is JSON, so a text match
finds it:
grep -lE '"type":[[:space:]]*"quill",[[:space:]]*"value":[[:space:]]*"[a-z_][a-z0-9_]*@[0-9]+"' <rows>
With a 0.125 build, open each row, set the series and save it: fromStored,
setQuillRef("memo@1.2"), toStored (Python from_stored, set_quill_ref,
to_stored). Without one, rewrite the string in place; nothing else in the row
changes:
A document the quill creates pins its MAJOR.MINOR, name@1.2, where it
pinned the exact version, name@1.2.0. The seeded, empty and example
documents carry it (seed_document, empty_document, example_document, WASM
seedDocument, emptyDocument, exampleDocument), and so does the
blueprint's $quill line. Every patch in the series reads such a document,
where a patch release failed it with quill::version_mismatch. A host
comparing a created document's quillRef / quill_ref to name@version
compares to name@MAJOR.MINOR; Python's Quill.quill_ref stays
name@MAJOR.MINOR.PATCH, so it no longer equals one. A document an earlier
build created keeps its exact pin: rewrite it to name@MAJOR.MINOR for a later
patch to read it.
A quill's version is MAJOR.MINOR.PATCH, so version: "1.2" fails to load
with quill::invalid_version, where it loaded. Write 1.2.0. An unquoted
version: 1.10, which read as 1.10, fails the same way: write 1.10.0.
| Break | Surface | Action |
|---|---|---|
A Typst quill holding plate.typ and declaring no typst.plate_file renders that plate, where it rendered a blank page; one that does not compile fails open and quillmark validate |
Typst rendered output, open / render, quillmark validate |
Delete or fix a stale plate.typ, or declare the plate as typst.plate_file |
A .ttc or .otc the quill ships loads every face, up to 256, where it was skipped |
Typst rendered output, quillmark workspace fonts |
None |
| A quill whose only fonts are collections no longer carries the embedded Roboto | Typst rendered output | Ship Roboto beside the collections to keep it |
A character the page draws as an empty box, because no loaded font covers it, warns typst::missing_glyph, naming the characters; quillmark validate fails an example.md that draws one |
Typst session warnings, RenderResult.warnings, quillmark validate |
Bundle a font that covers them |
status_report is 0.2.0, and its state values are on_track, at_risk, blocked |
documents against status_report@0.1 |
Rewrite on track → on_track, at risk → at_risk, and pin status_report@0.2 |
A javascript:, vbscript:, data: or file: link renders as its text |
Typst rendered output | Link over https: |
| A block whose expansion passes 2× its bytes + 4 KiB of scalars, or its bytes + 4 KiB of nodes, fails to parse | Document parse, every surface |
Write the values out, or alias less |
A body nested past 12 levels fails parse::body_import, at the body's path |
Document parse, every surface |
Flatten the deepest quotes and lists |
| A stored row whose body nests past 12 levels fails to load with an uncoded diagnostic | WASM fromStored / loadStored, Python from_stored, Rust Document deserialize |
Flatten it with a 0.125 build before upgrading |
A one-line text value drops its trailing whitespace before the single-line check, and the not_single_line diagnostics carry no trailingNewline arg |
validation::not_single_line, edit::field_not_single_line, conform::field_not_single_line args; Rust ValidationError::NotSingleLine / EditError::FieldNotSingleLine |
Drop any branch on trailingNewline and the trailing_newline field |
| A stored row holding more than 1,000 cards fails to load with an uncoded diagnostic | WASM fromStored / loadStored, Python from_stored, Rust Document deserialize |
Split it with a 0.125 build before upgrading |
Placing a 1,001st card fails edit::too_many_cards; EditError::TooManyCards { max } is a new variant |
WASM insertCard / addCard, Python insert_card / add_card, Rust push_card / insert_card |
Split the document, or match the variant |
MAX_NESTING_DEPTH is 12; ParseError::BodyImport holds { path, message } |
quillmark-content, quillmark-core Rust API |
Match the struct variant |
usaf_memo's schema emits no ui.groups / ui.group |
schema() of the fixture quill |
None |
A card declaring sections: fails quill::invalid_card_schema |
Quill.yaml load, every surface |
Make each section a top-level object, nesting its keys in documents and plates; or lift its fields into fields: |
A field named body fails quill::reserved_field_name |
Quill.yaml load, every surface |
Rename the field in the quill and its documents |
A form.json fails acroform::legacy_form_json |
acroform quills, open / render, every surface |
Rename it to form.yaml, contents unchanged |
acroform::invalid_form_json is acroform::invalid_form_spec; acroform::missing_form_json is acroform::missing_form_spec |
acroform open / render diagnostics |
Match the new codes |
A Typst quill with no plate.typ and no typst.plate_file fails typst::plate_missing, where it rendered a blank page |
Typst open / render, quillmark validate |
Ship plate.typ, or declare typst.plate_file |
A typst.plate_file that is not a string, or names no path ("", ., ./), fails typst::plate_path_invalid |
Typst open / render, quillmark validate |
Write the plate's path, or delete the key |
A key the backend does not read under its section fails quill::unknown_backend_key; typst::unknown_key is retired |
Quill.yaml load, every surface |
Delete the key; vendor a typst.packages entry under packages/ |
A backend section that is a scalar or a sequence fails quill::invalid_backend_section |
Quill.yaml load, every surface |
Write a mapping, or delete the section |
plate_file returns Result<String, RenderError> |
quillmark-typst, quillmark Rust API (typst_plate_file) |
Drop the Option |
QuillConfig has public license and homepage fields, so a struct literal naming neither fails to compile |
quillmark-core, quillmark Rust API |
Name both fields, None where undeclared |
A WASM trap rejects as runtime::backend_trapped, and so does a native stack overflow, where it rejected as the bare RangeError (V8, JavaScriptCore) or InternalError (Firefox), so an instanceof RangeError check no longer matches it |
@quillmark/wasm Engine.render / open, LiveSession.update / render / paint |
Route on the code, the original error on cause; discard the realm |
A quill.name over 64 characters fails quill::invalid_name |
Quill.yaml load, every surface |
Shorten the name, and every $quill naming it |
$quill: _name, or a name or collection over 64 characters, fails parse::invalid_quill_reference |
Document parse, StoredDocument load (StorageError::InvalidQuillReference), card-wire input, every surface |
Rewrite such a $quill to a valid name before upgrading; no quill could carry it |
QuillReference has a collection field |
quillmark-core Rust API |
Construct with QuillReference::new, or name the field |
A blueprint's $kind: main line no longer falls back to quill.description |
blueprint(), every surface |
Copy quill.description to main.description to keep the line |
A blank default: prints as an empty cell, and a defaultless typed table as [], or an empty cell where optional, under its commented-out row |
blueprint(), every surface |
Read whether a field wants an answer from its description; optionally delete blank defaults |
$quill: name@N, a lone major, fails parse::invalid_quill_reference |
Document parse, card-wire input, every surface |
Rewrite it to name@N.M, or drop the selector |
A stored row whose $quill is name@N fails to load with an uncoded diagnostic |
WASM fromStored / loadStored, Python from_stored; Rust StorageError::InvalidQuillReference |
Rewrite the row before upgrading, with a 0.125 build or in its JSON |
A two-segment Quill.yaml version, "1.2" or unquoted 1.10, fails quill::invalid_version |
Quill.yaml load, every surface |
Write all three segments, 1.2.0 |
A created document's $quill is name@1.2, where it was name@1.2.0 |
seeded, empty and example documents, the blueprint, every surface | Compare a created document's quillRef / quill_ref to name@MAJOR.MINOR; rewrite an exact pin an earlier build created |
VersionSelector::Major is removed |
quillmark-core Rust API |
Match Minor or Exact |
type: plaintext, type: string, inline: and ui.multiline fail quill::field_parse_error; a richtext without multiline: does too |
Quill.yaml load, every surface |
Declare text / richtext with multiline: per the table above |
A former string value is content in a Typst plate |
Typst plates | Read it through qm.str(..) wherever the plate computes with it |
No Quill.yaml declaring a text field loads on both 0.125 and 0.126 |
Quill.yaml load, every surface |
Upgrade each quill and the engine together |
A stored former string value holding an interior line break renders it as a space and warns validation::line_break_folded (on 0.126.0 its render failed validation::coercion_failed); a write refuses it; a one-line value ignores trailing whitespace |
quillmark check, validate, render, writes, every surface |
Run quillmark check --strict over the stored documents; declare multiline: true on a field whose breaks carry meaning |
schema() carries multiline where it carried inline: true; string and plaintext are text; the inline key is gone |
WASM and Python schema(), WASM QuillFieldSchema, quillmark schema |
Branch on multiline; read it off the field, not ui |
The transform schema carries quillmark:multiline, not quillmark:inline; a text field is a content object |
Rust build_transform_schema, the Typst plate helper |
Branch on quillmark:multiline |
The blueprint prints text<plain>, text(multiline)<plain>, richtext<markdown> and richtext(multiline)<markdown>; richtext<markdown> is one line |
blueprint(), every surface |
Read the annotations per the table above |
validation::not_inline, edit::field_not_inline, conform::field_not_inline, quill::seed_not_inline end in not_single_line; the codec arg names text |
diagnostics, every surface | Match the new codes and value |
The target of validation::coercion_failed, edit::field_coercion_failed and conform::field_coercion_failed is text(single_line), richtext(single_line) or text, where it was plaintext(inline), richtext(inline) or plaintext |
diagnostics, every surface | Match the new values |
FieldType::String is removed and PlainText { inline } is Text { multiline }; RichText { inline } is RichText { multiline } |
quillmark-core Rust API |
Match the new variants |
ValidationError::NotInline is NotSingleLine; EditError::FieldNotInline is FieldNotSingleLine |
quillmark-core and quillmark Rust API |
Match the new variants |
QUILLMARK_INLINE_KEY is QUILLMARK_MULTILINE_KEY; document::edit::CODEC_PLAINTEXT is CODEC_TEXT |
quillmark-core Rust API |
Use the new constants |
UiFieldSchema::multiline is removed |
quillmark-core Rust API |
Read the line shape off FieldSchema::r#type |