ascribe.toml: what pages hold
The content model’s note types, phrases, glossary, images, and widgets: what a page can hold beyond its type’s fields.
For AI agents: the documentation index is at llms.txt, and this page is available as Markdown.
Part of the ascribe.toml reference, whose contents list every section. The rules for a loader are in the contract’s what pages hold.
6. [notes.<type>]
Note types (SPEC §4.5), the values of @note’s type attribute. Five are built in: note, tip, important, warning, and caution. A project adds types by declaring them, and can relabel a built-in type. Built-in types can’t be removed.
[notes.security]
label = "Security"
[notes.tip]
label = "Pro tip"<type> is the note type (key rule).
| Key | Type | Default | Description |
|---|---|---|---|
label |
string | built-in types: Note, Tip, Important, Warning, Caution; new types: required |
The display label, used where a note’s type is shown as text, such as the plain-markdown output’s **Tip: …** (SPEC §9.4). |
Under [notes], the inline form security = { label = "Security" } is equivalent.
7. [phrases]
The phrases registry (SPEC §5.1).
[phrases]
product = "Quill"
cloud = "Quill Cloud"
version = "3.4.1"
api = "https://api.quill.dev/v3/"
support-email = "support@quill.dev"
# The current line of [releases.self-managed]: "3.5".
release = { line = "self-managed" }Every key is a phrase key (key rule, the same rule as SPEC Appendix A’s phrase), and every value is a TOML string (model-phrase-value-type). version = 3.4 is a float and an error; that’s the kind of silent type change TOML exists to prevent (SPEC §7.1). Values are inserted as literal text (SPEC §5.1).
A target’s line. A value can instead be a table with one key, line, naming a target whose lines [releases.<target>] (§12.1) lists: release = { line = "self-managed" }. The phrase is then a line as the list writes it: in the output of one line of a build the target versions ([builds.<name>] versions), that line, and everywhere else the target’s current line, its newest live one. So it changes when a line moves to live, and has no second home to keep. The target needs a live line (model-phrase-line), and the table takes no other key (model-unknown-key).
Phrases in frontmatter. SPEC §5.1 lets the content model decide which frontmatter fields accept phrases. A field accepts them when its table form sets phrases = true (§5.2):
[types.guide.frontmatter]
title = { type = "string", phrases = true }
Only string and list(string) fields can accept phrases (model-phrases-field-type). By default, no field does. The setting lives on the field, not in [phrases], so that a phrase key can never collide with a setting’s name.
8. [glossary]
The glossary (SPEC §5.4): the pages that hold its terms, and how occurrences are matched. A term is a heading on a glossary page, marked @term, and its definition is the paragraph under it. Authors don’t mark terms where they’re used; processors link them (SPEC §9.2 step 7).
[glossary]
match = "first"
case-sensitive = false
pages = ["reference/glossary.md"]8.1 Settings
| Key | Type | Default | Description |
|---|---|---|---|
pages |
array of strings (paths) | [] |
The glossary pages, each a path from the content root, written as in a link (SPEC §5.2; a leading / is allowed and means the same). Each MUST be a page, not a fragment. A term is linked to the first of its pages that a build publishes. |
match |
string | "first" |
Which occurrences are linked: "first", the first occurrence of each term on each page, or "every", every occurrence. “Page” means the resolved page for a build, after includes and build modes. A term can override it. |
case-sensitive |
boolean | false |
Whether occurrences must match a term’s case exactly. When false, matching ignores case. A term can override it. |
There’s no terms table: a project that has one gets an error saying to write each term on a glossary page.
8.2 Terms
Each heading on a glossary page with @term directly under it, written on the page or in a fragment it includes, is a term:
## Rollout
@id: rollout
@term {aliases=rollouts}
A plan for turning a flag on for a growing share of users: by percentage, by segment, or on a schedule.
- The term is the heading’s text, and its id is the heading’s id, which the site output’s
data-ascribe-termnames. An@idkeeps the id when the heading’s text changes. - The definition is the paragraph that opens the section, after the heading’s directives, as plain text, with the build’s phrases. A paragraph a fragment brings in counts, and a section that opens with a
@variantgroup has the paragraph each arm opens with, in the builds that keep the arm. The site output shows it as the link’s title, and the editor on hover; the JSON output lists it with each use. Keep it to a sentence or two, and put the rest in a second paragraph. - A term follows its section in each build. A build that removes the section, by
@availableor by a variant, has no such term, and a build that removes only its paragraph leaves it without a definition: either way, the build doesn’t link it. @term’s attributes override the glossary’s settings for the term:
| Attribute | Type | Default | Description |
|---|---|---|---|
aliases |
string | none | Other forms that count as occurrences, such as plurals, separated by |: aliases="API keys|keys". |
case-sensitive |
boolean | the [glossary] setting |
For terms like Go that collide with ordinary words. |
match |
first, every, or marked |
the [glossary] setting |
A marked term isn’t linked automatically at all, for words like build that also have ordinary senses: link the occurrences that mean the term to its heading, as [build](glossary.md#build). marked is a term setting only; [glossary] doesn’t accept it. |
An unused term (glossary-term-unused) is acknowledged with @intended among the directives under its heading.
A link to a term’s heading is a use of the term, wherever it’s written: it gets the term’s marker and its definition as its title, as an automatic link does, and it counts as a use. For a term with match = "first", it’s the page’s first link to the term, so no later occurrence is linked. A link to the glossary page, or to another heading in the term’s section, is an ordinary link.
To set one up, see Set up a glossary.
Matching: a term is found as a whole word, never inside one, and where two terms overlap, as API key and API do, the longer is linked. Only prose is searched, so a term in a heading, link text, code, raw HTML, or a directive’s primary identifier stays as it is. A glossary page doesn’t link the terms it shows, and a build doesn’t link a term whose heading it leaves out, or that has no definition in it.
Rules. A path in pages names a page that’s there, with its name’s case as it is on disk (model-glossary-page). The rest are checked with the project, since terms are read from pages:
- Two terms or aliases with the same text, compared ignoring case unless both terms are case-sensitive, are reported at the second, which isn’t a term (
glossary-term-duplicate); so are two terms with the same id, which two headings with the same text on different pages give them. - A term’s definition is the paragraph that opens its section, so a term whose section opens with anything else has none (
glossary-definition-missing). One over 300 characters in a build, the limit a page’sdescriptionhas, is advice (glossary-definition-long). @termon a pagepagesdoesn’t list, or in a fragment no such page includes, isn’t a term (term-off-glossary-page). Its fix lists the page inpages; the editor applies it, andascribe check --fixdoesn’t, since it links the page’s terms on every page.
9. [images]
Image attributes (SPEC §5.3): which keys may appear in an attribute block after an image, and their types.
[images.attributes]
width = "number?"
height = "number?"
loading = { type = "enum(lazy, eager)", default = "lazy" }
theme = "set(enum(light, dark))?"| Key | Type | Default | Description |
|---|---|---|---|
attributes |
table of attribute types | {} |
Accepted image attribute keys (key rule) and their types (§5). With no entries, an image accepts no attributes, and any attribute block after an image is an error (SPEC §8.2, “Unknown key”). Declaration order is canonical order (SPEC §8.3). A declared default reaches the output: in the site output, every image carries the defaults of attributes it doesn’t write. |
Alt text and titles aren’t attributes; they use CommonMark’s syntax (SPEC §5.3). Presentation choices aren’t image attributes either. Keys HTML already uses on <img> (src, alt, title, and its global attributes) are rejected (model-attribute-reserved), since the site output writes image attributes onto the <img> element (SPEC §7.2).
10. [widgets.<name>]
Project widgets (SPEC §6): directives a documentation set defines. Each declaration is a directive schema with the same parts a built-in directive’s schema has (SPEC §3, §4).
[widgets.quill-labspace]
description = "An embedded, runnable Quill lab."
forms = ["line"]
primary = "none"
binding = "self"
title = "none"
groupable = false
plain-fallback = "Try this in the Quill lab at labs.quill.dev."
[widgets.quill-labspace.attributes]
lab = "string"
height = "number?"<name> is the widget’s name (SPEC A widget-name: lowercase, with at least one hyphen). Names starting with ascribe- are reserved for Ascribe’s element library, and the names HTML reserves for itself (annotation-xml, color-profile, font-face, font-face-src, font-face-uri, font-face-format, font-face-name, missing-glyph) aren’t allowed, because the site output emits a widget as a custom element with the widget’s name (SPEC §9.4).
| Key | Type | Default | Description |
|---|---|---|---|
forms |
array of strings | required | The permitted forms (SPEC §3.5): ["line"], ["container"], or both. |
primary |
string | "none" |
The primary (SPEC §3.4): "none"; "identifier" or "text", which are required; or "identifier?" or "text?", which are optional. |
binding |
string | required when forms includes "line"; not allowed otherwise |
What the line form applies to (SPEC §3.8), one of the values below. |
title |
string | "none" |
Whether the widget takes a title line (SPEC §3.7): "none", "accepted", or "required". |
groupable |
boolean | false |
Whether a run of the widget’s openers forms a group of arms (SPEC §3.6). |
attributes |
table of attribute types | {} |
The attribute schema: keys (key rule) and types (§5). Declaration order is canonical order (SPEC §8.3). The keys heading and primary, HTML’s global attributes (including title), aria- keys, and event-handler attributes such as onclick, are rejected (model-attribute-reserved): the site output writes these attributes onto the widget’s element (SPEC §7.2). |
plain-fallback |
string | none | Plain-text fallback for the plain-markdown output (SPEC §6, §9.4): CommonMark text written in place of the widget. Phrases in it are substituted. It isn’t a template: attribute values aren’t inserted. Without it, the widget itself emits nothing. |
plain-content |
string | "keep" |
For a widget that wraps content, whether the plain-markdown output keeps that content ("keep") after the fallback, or drops it ("drop"). Allowed only when the widget wraps content: it has container form, or its binding is block or heading-or-block. |
description |
string | none | Help text for the editor’s hover and completion. |
Binding values:
| Value | SPEC §3.8 binding | Behaves like |
|---|---|---|
"self" |
Self: its own primary, or nothing | @include |
"heading" |
Preceding heading: at the top of its section | @id |
"block" |
Following block. If the primary is a text primary and it’s given, the primary is the content and nothing else is bound | @steps; with primary = "text?", @note |
"heading-or-block" |
At the top of a section, the section; anywhere else, the following block | @available |
Rules:
- A widget with container form can’t have a required primary, since a container opener’s primary is empty (SPEC §3.5), and a container-only widget’s primary is
"none"(model-widget-container-primary). - A groupable widget is container-only, since the arms of a group are containers (SPEC §3.6) (
model-widget-groupable-form). - A widget with line form needs a
binding, and one without it can’t have one (model-widget-binding).
What the site output writes for a widget, its element’s tag name and attributes, is set by the element contract.
What a widget’s attributes mean is the site’s to check: Ascribe doesn’t know that an attribute names a page, so it doesn’t check that the page exists. Ascribe’s own landing page is laid out with widgets that site/home/ascribe.toml declares.