You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
release: re-cut to finish binding dists after the first publish run cancelled mid-flight
breaking typst: a present date / datetime field lowers to a click-to-edit value-object — (value: datetime(..), display: (..args) => text(value.display(..args))) — instead of a bare Typst datetime, so the rendered glyphs are born at a generated text(..) node carrying a region keyed on the field's schema path: a date placed by a vendored package, or a card's date riding a shared loop variable, is click-to-edit. A blank date stays none, so != none guards hold. Plates migrate two shapes: (data.f.display)("…") (paren form — the stored display is a closure on a dict, not a method) and data.f.value for anything native (comparison, .year()-family components, datetime-consuming packages). The tonguetoquill flagship quills' display-date dispatches on type(date), since their datetime.today() blank-date fallback stays a native datetime (#990)
breaking core,typst,pdfform: split the datetime field type into strict date and datetime (#717/#799 resolved) — date accepts a bare YYYY-MM-DD and rejects any time component; datetime accepts offset-less wall-clock YYYY-MM-DDThh:mm[:ss] (seconds zero-filled) and rejects timezone offsets, the space separator, fractional seconds, and bare dates. Offsets are rejected, never dropped (the engine does no zone math); storage stays verbatim; no truncation in either direction. A date lowers to the three-component Typst datetime(year:, month:, day:) (unchanged emission) and a datetime to the six-component constructor, carrying the wall-clock time. The transform schema marks date as format: "date" (keeping format: "date-time" for datetime), and the blueprint reads date<YYYY-MM-DD> / datetime<YYYY-MM-DDThh:mm[:ss]>. Most deployed datetime fields hold a bare date and migrate to type: date with byte-identical data; the fixtures (usaf_memo, cmu_letter) do so. No deprecation alias — datetime rejecting a bare date is the decided end-state (#991)
breaking python: the binding commits to the typed lanes — field I/O flows through quill.writer(doc) / quill.view(doc) exclusively and Document is quill-free data and structure. Removed (WASM-only by scope, not lag — their audience is not a Python audience): the opaque field store (store_field / store_fields / store_fill and card twins), the content lane (install / revise / apply_change), the quill-free field reads (get / get_card_field), and the module codec fns (import_markdown / export_markdown / rebase / map_pos). The composable $ext / field-remove card twins fold onto a trailing card=None selector and push_card folds into insert_card(card, at=None). Mirrored additions: writer.revise_field (the Delta receipt is not surfaced), writer.add_card(kind, fields=None, body=None, at=None), writer.card(i).kind, and the Writer / CardWriter / View / CardView handle classes exported from the package (#970)
breaking core,wasm,python: complete the RichText → Content residual sweep (#976 folds into #982) — retire the last informal-corpus / model-RichTextidentifiers the mechanical rename missed: CorpusHit → ContentHit, EditError::CorpusApply → EditError::ContentApply, RichtextDecodeError::NotCorpus → NotContent (the codec-specific RichtextDecodeError type itself is kept), storage DTO CanonicalRichText → CanonicalContent (serde is unchanged — no wire migration), and the model-generic Typst emitter emit_richtext / emit_richtext_inline → emit_content / emit_content_inline (they lower any Content, richtext or plaintext). Also fixes the ParseError display strings richtext json … → content json …, the doubled-word find-replace debris (content content model → content model), and stale prose/comments across canon and rustdoc. Schema/codec names are untouched — richtext / plaintext tokens, FieldType::{RichText,PlainText}, field_richtext / FieldRichtext* / apply_field_richtext_change, richtext(inline) — they name codecs, not the model. "corpus" is purged from the tree entirely, including the ordinary-English test-set names that meant a collection of fixtures (fixture_corpus → fixtures, synthetic_corpus → synthetic_inputs) (#982)
breaking core,wasm,python: a schema-bound read view — Quill::view(&doc) / quill.view(doc), the read twin of quill.writer(doc). view.get(addr) interprets each field by its declared type (a richtext field → markdown, a plaintext field → its literal text via the plaintext codec, every other type → its canonical value verbatim), returns absent as undefined / None, and — the authority the quill-free getMarkdown lacks — throws UnknownField for a name the schema does not declare and FieldRichtextDecode for a content field holding an undecodable value. Core TypedReader::get returns a ReadValue (Markdown/Plaintext/Value); view.card(i) is the card cursor; core adds Card::field_plaintext (the to_plaintext twin of field_markdown). getMarkdown's field half retires: getMarkdown / get_markdown / get_card_markdown are now body-only (WASM getMarkdown takes a CardAddr, a present field throws; Python drops the name parameter) — a field's markdown is read through view.get. The quill-free body projection stays on Document (#978)
breaking content: one delta-application contract — implicit trailing retain is try_apply's semantics (a short delta retains the untouched remainder; the error is over-consumption only), apply panics on an over-long delta instead of clamping (clamping is silent corruption), and extend_to_base is removed. split_line / join_line rebase marks through their one-char \n splice with map_pos — the same mapping the text-delta channel uses — so marks no longer drift across line ops and apply_field_change canonicalizes once (a single terminal normalize instead of one per stage); line sync rebuilds in one forward pass instead of per-\nVec splices. Mark ops are specified in final-text coordinates (post-delta, post-line-op — the frame they validate against) (#926, #987)
breaking core: storage blobs tagged @0.81.0 / @0.82.0 fail as an unknown schema version — the read-only V0_81_0 / V0_82_0 DTO trees and their forward migrations are retired (nothing persisted on this lineage predates @0.92.0; 0.82.0 was yanked). V0_92_0 stays the oldest shape read, and its payload types back the current write path. DOCUMENT_STORAGE.md records variant retirement as the policy when no stored population remains (#929)
breaking core,wasm,python: the markdown projection stops appending a trailing newline — to_markdown projects a value, not a file, so field_markdown / body_markdown (WASM getMarkdown / exportMarkdown, Python export_markdown / get_markdown) no longer grow a \n; writer.set("subject", "Hello") reads back as "Hello", not "Hello\n". .qmd files still end in one newline (owned by Document::to_markdown, the file writer) and the content fixed point is unchanged (import is newline-insensitive) (#965)
breaking all: rename the content genus off its codec's name — crate quillmark-richtext → quillmark-content, type RichText → Content (and RichTextLine/RichTextContainer/RichTextMark/RichTextIsland → ContentLine/…), const RICHTEXT_MEDIA_TYPE → CONTENT_MEDIA_TYPE and its wire string application/quillmark-richtext+json → application/quillmark-content+json, #[serde(skip)] companion caches FieldSchema::{default,example}_corpus → _content, SegmentMap.corpus: Range<usize> → .content, Typst-emitter EmittedContent → Emission (it is markup + source map, not a Typst content value). Schema tokens richtext / plaintext, FieldType::{RichText,PlainText} variants, and the codec-specific field_richtext / FieldRichtext* / apply_field_richtext_change / richtext(inline) surface are unchanged — those name codecs, not the model. Canonical body JSON is nameless, so stored documents don't migrate; contentMediaType consumers pin to the new spelling. Retires the informal "corpus" noun to end the code/prose split (#976)
breaking core,wasm,python: getMarkdown / get_markdown / get_card_markdown stop conflating an absent field with a present-but-not-richtext one — a present field that does not decode as richtext (a scalar/array/object a storeField wrote) now throws FieldRichtextDecode instead of reading back undefined / ""; absence still returns the absent shape. Core Card::field_markdown becomes Option<Result<String, RichtextDecodeError>> (the projection twin of field_richtext). Rule: absence returns, mismatch raises; read the raw value with get (#968)
feat(core,wasm): typed, anchor-preserving field revise — TypedWriter::revise_field / CardWriter::revise_field and writer.reviseField / writer.card(i).reviseField wrap core Card::revise_field_checked (diff-rebase surviving anchors, then schema-conform the result); the schema-bound verb lives on the writer, where the schema is (#957, #966)
breaking wasm: the quill-taking Document methods become the hidden ABI under the writer — commitField / commitFields / addCard → _commitField / _commitFields / _addCard, dropped from the .d.ts; remove doc.reviseChecked (no runtime consumer — use writer.reviseField). The visible Document class then carries zero quill-taking methods (#966)
breaking core: rename EditError::BodyImport → EditError::Import (message body import failed: → markdown import failed:) — the variant also fires on field-path imports (revise_field), where "body" misnamed it (#966)
breaking wasm: fold pushCard into insertCard(card, at?) — one insertion verb per lane, absent at appends; insertCard's parameters reorder to (card, at?). Delete the deprecated replaceBody alias (use revise({}, md) or writer.setBody) (#961)
feat(core,wasm): positioned card insert — TypedWriter::add_card / writer.addCard and the addCard ABI take an at position, so a positioned typed insert is one atomic call instead of addCard + moveCard; add TypedWriter::remove_card (mirrors JS writer.removeCard) and a JS CardWriter.kind getter (mirrors core CardWriter::kind()) (#961)
breaking core: Payload::insert / insert_fill now validate the field-name and value-depth invariant at the boundary and return Result<_, FieldViolation>, closing the payload_mut().insert(...) hole that let a direct caller build an invalid document; pre-validated internal callers use the new pub(crate)insert_unchecked / insert_fill_unchecked (#958)
feat(wasm,core): single-card reads — doc.card(i) (throws out of range), doc.cardIndexById(id) (first match; $id is non-unique), and doc.seedOverlay(kind), backed by core Document::card(i) / find_card(id). Reading one card, resolving a $id, or fetching a $seed overlay no longer serializes the whole cards array or main card (#956)
breaking core: parse warnings live only on ParseOutput — the redundant Document::warnings field + warnings() getter are dropped and Document::from_main_and_cards no longer takes a warnings param (DocumentPartialEq is now a plain derive) (#959)
breaking core: collapse the two parse functions into one entry — Document::from_markdown and Document::from_markdown_with_warnings are removed in favor of Document::parse(md) -> Result<Parsed, ParseError>, and ParseOutput is renamed Parsed. A document-only caller writes parse(md)?.document. Bindings are unaffected: WASM Document.fromMarkdown / Python Document.from_markdown keep their names and their doc.warnings getter (#964)
feat(wasm,python): keyed card reads getCardField(index, name) / getCardMarkdown(index, name?) (py get_card_field / get_card_markdown) — the card-indexed twins of get / getMarkdown, mirroring the commitCardField / setCardField write verbs so card reads no longer require a payloadItems walk (#953)
feat(content,wasm,python): LineOp::SetContinues { line, continues } — hard breaks lower op-wise. Split, join, and a text-delta \n all mint continues: false lines, so a within-block hard break (a paragraph hard break, a code fence's interior line) had no op and fell back to a whole-install, losing that edit's identity anchors. Threaded through the wire codec into WASM applyChange (TS union updated) and Python; continues: true on line 0 is rejected with ApplyError::FirstLineContinues before the write, leaving the content untouched (#949)
feat(wasm): the runtime root re-exports the edit vocabulary its own signatures reference — Content / ContentLine / ContentContainer / ContentMark / ContentIsland, Addr / Delta / Assoc / LineOp / MarkOp / ChangeBundle, CardInput / PathStep — as type-only exports (single entry point preserved; no /core subpath), with a presence guard so a dropped re-export fails npm run typecheck (#948)