Skip to content

0.86.0 → 0.87.0 — Typed arrays via items, unified datetime, stricter schema load

0.87.0 reshapes how array and datetime fields are declared in a Quill.yaml schema. The changes are hard cutovers at schema-load time: a schema that uses the old forms no longer loads, and the load diagnostics name the exact field and fix. The typed accessors, plate-JSON wire format, and document syntax are unchanged.

If you only render documents against the bundled quills, no action is needed — they are already migrated. This guide is for anyone maintaining their own Quill.yaml, a stored golden schema, or a generator that emits schemas.

Array fields now require items

Arrays previously carried a single untyped Array type. Scalar arrays were never coerced or validated element-wise and were always annotated array<string> regardless of contents; element typing was only possible for objects, via a bare properties: map directly on the array (the "typed table"). 0.87.0 makes every array carry an items element schema, and items is required.

Scalar arrays

Add items with the element's type:

  counts:
    type: array
+   items:
+     type: integer

Elements now coerce and validate against items, failing at the indexed path (e.g. counts[1]). The blueprint annotation reflects the element type (array<integer>), not a blanket array<string>.

Typed tables (the bare-properties-on-array form is removed)

A typed table used to be an array with properties: set directly on it. That form is gone; wrap the object shape in items instead:

  rows:
    type: array
-   properties:
-     name: { type: string }
-     qty:  { type: integer }
+   items:
+     type: object
+     properties:
+       name: { type: string }
+       qty:  { type: integer }

markdown[] and other element types

The same items shape applies to any element type. A markdown list becomes:

notes:
  type: array
  items:
    type: markdown

markdown[] elements are converted to Typst markup on render, and the schema emits contentMediaType: text/markdown on the items recursively.

What the loader now rejects

Diagnostic Cause Fix
quill::array_missing_items type: array with no items add an items element schema
quill::array_properties_not_supported properties: set directly on an array move it under items: { type: object, properties: … }
quill::items_not_supported items on a non-array field remove items, or change type to array

type: date is gone — use type: datetime

FieldType::Date is removed. There is now one temporal type, datetime, and it accepts the full range from a bare calendar date through a full timestamp with offset:

  • YYYY-MM-DD (bare date)
  • YYYY-MM-DDThh:mm:ss and the space-separated form
  • RFC 3339 with timezone offset; seconds and fractional seconds optional
  due:
-   type: date
+   type: datetime

Knock-on effects:

  • Calendar validation — values are now parsed, so an impossible date such as 2026-02-30 is rejected (it was previously accepted by the format check).
  • JSON Schema output — every datetime field emits format: date-time.
  • WASM FieldType union — the "date" member is removed; use "datetime".
  • Blueprint hint — now datetime<YYYY-MM-DD[Thh:mm:ss]>.

A value that was a valid date (YYYY-MM-DD) is still valid under datetime, so existing document values do not need editing — only the schema's type: keyword changes.

Empty properties: {} is rejected

An object field with an empty properties map carries no information — the only conforming value is {} — and is almost always a mistake. It is now treated like a missing properties key and reported as quill::object_empty_properties. Either give the object real properties or drop the field.

Deeper array nesting is rejected

The documented "one level of nesting" contract is now enforced in a single recursive pass, closing a gap where deeper shapes were silently accepted. A typed table row (array<object>) and a typed dictionary (object) may carry scalar columns/properties only. Shapes like array<object<array>> or object<array> now fail with quill::nested_array_not_supported.

If you relied on a deeper shape, flatten it — e.g. lift the inner array to a top-level field, or model the data as a separate card kind.

example: values are now validated

The conformance check for example: and default: literals now recurses into array items and object properties and validates datetime format — capabilities the old load-time path lacked. An example: value that does not match its field's type, enum, or datetime grammar is now caught at load (quill::example_type_mismatch, quill::example_not_in_enum, quill::default_type_mismatch). This surfaces latent mistakes in example: blocks that previously went unchecked; correct the literal to match the declared schema.

Migration checklist

  1. Add items to every array field. Scalar arrays get items: { type: <elem> }; typed tables move properties: under items: { type: object, properties: … }.
  2. Replace type: date with type: datetime. Document values need no change; verify no value is an impossible calendar date.
  3. Remove empty properties: {} maps.
  4. Flatten any array nested more than one level deep.
  5. Re-baseline stored golden schemas and blueprint goldens — array annotations (array<integer>, array<markdown>, …), the typed-table shape, and datetime format: date-time output all change.
  6. WASM consumers: drop "date" from any code that branches on the FieldType union.

Load each migrated quill (Quill::from_path / the CLI validate command); the diagnostics above name the offending field and the corrective action directly.