Skip to content

0.124 → 0.125 — regions come from what a plate declares, one decline code, and a typed set reports its drops

Each section below is one concern, those whose breaks no type checker reports first, and the table closing the guide lists every break.

A plate's own print of a value surfaces no region

#data.subject, #upper(data.subject), a let alias's read and every other value the plate prints itself render as before but surface no region and answer no click. 0.124 scanned the plate's syntax for data reads and windowed each one; that scan is gone. Regions come from three producers, each one the plate declares:

  • ink the helper generates: a richtext or plaintext value, and any field printed through qm.ink;
  • a qm.region claim on ink the plate composes;
  • a form widget bound with field:.

Print a field through qm.ink to keep its click target, or claim composed ink with qm.region. quillmark check --regions ./my-quill doc.md lists the filled fields no click reaches, and session.coverage() returns the same list.

A claim takes the text it brackets, and no page chrome, footnote or float

A qm.region claim takes the text between its markers that Typst draws with no source position of its own, such as a string cast to content inside a function argument (text(size: 9pt, "LIT")) and a list's bullets, where 0.124 left them unclaimed. An underline or strike stroke stays with the content it decorates. A claim open across a page break takes none of the header, footer or background it crosses, where it claimed them, and not the page number. A claim takes no footnote or float, wherever it is anchored, where one open across a page break took the notes and floats the page laid out around its text. A claim placed inside a header, a footer, a footnote or a float still takes that ink. A foreground is still taken by a claim open across its page's break; wrap it in pdf.artifact to keep it out.

$ink is gone, and $path is on every typed dictionary

No dictionary carries $ink; qm.ink(dict, key) reads ink by address. data, every card and every typed dictionary or table row carry $path, where a dictionary carried it only beside an $ink or a matrix. A plate comparing two dictionaries, walking keys() or spreading a row into a call sees $path on dictionaries that had no $ key in 0.124; skip $-prefixed keys there. A matrix's own dictionary still carries none.

The unclosed-claim warning is renamed

A claim whose close never reaches a frame warns typst::unclosed_region, where it warned typst::unclosed_field_region. Route on the new code.

The helper is a module imported as qm

A plate imports the helper as a module, at version 0.2.0:

#import "@local/quillmark-helper:0.2.0" as qm

Every other form fails the render with typst::helper_import at the import line: : *, : data, ink, a bare #import, and the 0.1.0 version. The name is the plate's to choose; qm is the convention. Every member is read through it, and two are renamed:

0.124 0.125
data qm.data
ink(x).f, ink(x).at(k) qm.ink(x, "f"), qm.ink(x, k)
display("addr", ..args) qm.ink(qm.data, "addr", ..args)
display(x, "f", ..args) qm.ink(x, "f", ..args)
display(card.at("$path") + "f", ..) qm.ink(card, "f", ..)
field-region(f, body) qm.region(f, body)
roster, form-field, signature-field, elements, tasks the same name under qm.

A plate that wants the bare name binds it: #let data = qm.data.

qm.ink is total over the schema: an address the schema does not have fails the render, a disabled card $body and a roster member's title included, so read a title as if row.held and "title" in row.value { qm.ink(row.value, "title") } else { row.title }, an unheld row's value being none. Blank content, a blank date, a typed dictionary and a matrix give none, and a display pattern on an address that is not a date's fails the render.

A downstream quiver migrates each plate in four steps:

  1. The import line becomes #import "@local/quillmark-helper:0.2.0" as qm.
  2. ink(x).f becomes qm.ink(x, "f"), display(..) becomes qm.ink(..), and field-region becomes qm.region.
  3. data becomes qm.data, and every other helper name takes the qm. prefix.
  4. A bare print of a data value that should stay clickable, #data.subject, becomes #qm.ink(qm.data, "subject").

A declined construct warns under one code

quill.validate(doc) warns backend::declined_construct, the render's own diagnostic, where it warned validation::declined_construct; that code is gone. The two lists are equal, message, hint and args included, so a host that routed the validate code apart from the render's routes one code. A host collecting validate's own findings by the validation:: prefix no longer collects the declines.

validate reads the plate a render compiles, so a document that does not compile, one holding a value its field's type refuses, draws no decline until it compiles; 0.124 read the authored values instead.

A render's declines stand after its backend's other warnings, after typst::unregistered_element under Typst, where they stood before it.

Sections replace ui.group

A card puts fields in titled panels under sections:, and a card spelling the legacy ui.groups registry or a field's ui.group warns quill::deprecated_groups. schema() still emits ui.groups and ui.group, so an editor reading them needs no change. A legacy card loads exactly as its sections: rewrite does, which differs from 0.124 in three places:

  • schema() lists the card's fields in display order, ungrouped first, then each group's in registry order, where it listed them as declared, and seeding writes a new card's keys in that order. A quill declaring its fields in display order sees no change.
  • An object alone in its group carries ui.layout: flat, so an editor draws the group's panel as its frame.
  • A registry entry no field names leaves ui.groups.

To rewrite a card, move each grouped field under sections.<group>.fields in registry order, carry each registry title: to its section, and drop ui.group and ui.groups; 0.126 refuses sections:, and its guide rewrites each group as a top-level object (0.125 → 0.126).

A typed set reports what its markdown drops

writer.set and setAll in WASM, set and set_all in Python and TypedWriter::set / set_all in Rust return a parse::dropped_construct per construct a richtext markdown string drops, at the string's path, such as main.subject or main.tags[0], where the drop was silent. addCard returns its fields' warnings ahead of its body's. A string the render floor wraps into an array, a bare string on an array of richtext, reports at [0], from a set, a conform and validate alike, and a conform reports a string in a variant cell the discriminant does not select.

A table knob outside its set rests as written

A stored table's align, widths or headless holding a value outside its set stays as written and reads as absent, where normalization removed it. align: "middle", headless: "true" and "align": null render as the quill's default, as before, but now ride the stored bytes, so the content hashes over them. A cell's align and valign already rode this way. Write null nowhere a key means its default; leave the key out.

A whole-document revise names each dropped anchor with its line

writer.reviseDocument's droppedAnchors holds DocumentAnchors, { id, path, line }, the shape doc.toAnnotatedMarkdown() lists, where an entry held path and id. A card's anchors list fields first, then its body, and an anchor is matched at its exact address, so one dropped from items[1] is named though items[2] holds the same id.

Markdown

A line opening with three or more backticks whose info string holds a backtick opens no code fence, as CommonMark reads it, so a ~~~ card block under it opens its card. 0.124's card scanner read such a line as a fence and kept the cards below it in the body.

A raw tag inside a footnote definition no longer warns on its own: the definition's one footnote warning covers everything in it.

A newline beside a hard break imports as nothing, so line one<br> above line two stores "line one\nline two", where 0.124 stored a space at the start of the second line and rendered it as an indent. A paragraph or continuation line whose content all drops, such as [](u) under another paragraph, imports as no line. Content stored by 0.124 keeps its space; importing the same markdown again stores none.

U+0000 reads as U+FFFD wherever it enters, as CommonMark reads it: an &#0; in a qm-* attribute and U+0000 in an Op::Insert included, where those stored U+0000.

A placed table aligns its cells as an unplaced one does

A table placed by <qm-table align=…> aligns its cells as it would unplaced under the plate's #set table(align: …) and a vertical #show table.cell: set align(…). 0.124 gave a cell the placement's horizontal alignment under a vertical table.align such as horizon, and top under a vertical table.cell rule. A horizontal alignment set by a table.cell show rule still yields to the alignment around a placed table, as in 0.124; set it by #set table(align: …) instead.

Every break

Break Surface Action
A value the plate prints itself, #data.subject and every scanned read, surfaces no region session.regions(), fieldAt, RenderResult.regions Print through qm.ink, or claim with qm.region; quillmark check --regions lists what no click reaches
A claim takes the detached text between its markers, list bullets included, and no marginal it crosses, page number included, and no footnote or float session.regions(), fieldAt Claim a note or float inside it
No dictionary carries $ink; data, every card and every typed dictionary carry $path the plate's data Print with qm.ink; skip $-prefixed keys in a key walk or a comparison
typst::unclosed_field_region is typst::unclosed_region session warnings, RenderResult.warnings Route on typst::unclosed_region
The helper is version 0.2.0, imported as qm; any other import fails as typst::helper_import every Typst plate Import as qm and read members through it
field-region is qm.region, and ink(x).f and display(..) are qm.ink(x, "f", ..) every Typst plate Rename per the table above
Quill::validate warns backend::declined_construct, equal to the render's, where it warned validation::declined_construct, which is gone Quill::validate, WASM and Python validate, quillmark check, quillmark validate Route on backend::declined_construct
A document whose value fails to compile draws no decline from validate Quill::validate Fix the value; the declines follow
A render's declines follow typst::unregistered_element session warnings, RenderResult.warnings None
A typed set returns its richtext strings' parse::dropped_construct, at each string's path; addCard returns its fields' ahead of its body's WASM set / setAll / addCard, Python set / set_all / add_card, Rust TypedWriter, CardWriter Read the returned list
A string the floor wraps into an array reports its drops at [0], and a conform reports a string in an unselected variant cell Quill::conform, quill.parse, Quill::validate None
A table align, widths or headless outside its set, null included, rests as written and reads as absent stored documents, overwrite, the op wire Leave a default key out
droppedAnchors entries are DocumentAnchor { id, path, line }, a card's fields ahead of its body, matched at the exact address WASM reviseDocument, Rust DocumentRevised Read line; DroppedAnchor is DocumentAnchor
A backtick line whose info string holds a backtick opens no fence for the card scanner Document::parse, Document.fromMarkdown, quill.parse None
A raw tag inside a footnote definition warns nothing beside the footnote Parsed.warnings, doc.warnings None
A newline beside a hard break imports as no space, and a line whose content all drops as no line Document::parse, Document.fromMarkdown, quill.parse, reviseDocument None
&#0; in a qm-* attribute and U+0000 in an Op::Insert read as U+FFFD Document::parse, the op wire None
A placed table aligns its cells as an unplaced one does under the plate's table.align and a vertical table.cell show rule Typst rendered output None
A card spelling ui.group or ui.groups warns quill::deprecated_groups Quill::warnings, every render's warnings, quillmark validate Move the fields under sections:, or for 0.126 under a top-level object (0.125 → 0.126)
A grouped card lists its fields in display order, an object alone in its group carries ui.layout: flat, and a registry entry no field names is dropped QuillConfig::schema, WASM and Python schema, quillmark schema, seeding None
ImportWarning is { construct, count }; Dropped::Table / TableAttr are Element("table") / ElementAttr { element: "table", .. } quillmark_content Destructure the struct
Container::Element { element, instance }, element a carrier::Element that Element::new builds, refusing a name outside the grammar or reserved; Invariant::BadElement and carrier::modeled are gone quillmark_content Build through Element::new; read name() and attrs()
TypedWriter::set / set_all and CardWriter::set / set_all return Vec<Diagnostic> Rust Read or bind the warnings
A config built with QuillConfig::new prints its blueprint in declaration order, whatever its ui.groups Rust Declare the fields in display order
DocumentRevised.dropped_anchors is Vec<DocumentAnchor>; DroppedAnchor is gone Rust Read .path, .id, .line
QuillConfig::declined_in_plate reads the backend off the config; backend::declined_in is gone; BlockConstruct::count_in is tally Rust Drop the backend argument
SessionHandle::update takes the session's &QuillConfig, and LiveSession::new the plate data it opens on; a backend's session no longer mints backend::declined_construct backend implementations Take the config; drop the decline