ascribe.toml reference
The content model: content types, dimensions, availability, phrases, widgets, and builds.
ascribe.toml is a project’s content model: its schema (SPEC §7). It sits at the project root and declares the content types and their frontmatter, the dimensions content varies along, phrases, the glossary, project widgets, how the site output fits Astro, and the builds. The editor, ascribe check, and ascribe build all read it, so they can’t disagree about what’s valid.
A file containing only spec = "0.1" is valid; every section has a default (§19). Example files:
examples/content-models/minimal.toml: the smallest valid model.examples/quill/ascribe.toml: a small, complete project.examples/content-models/full.toml: every section and key.
Every problem the loader reports is in the diagnostics reference, with its fix. References to “SPEC” are to the Ascribe specification.
Contents
- Conventions
- Top level:
spec [project][types.<name>]and[fragments]- Field and attribute types
[dimensions.<name>][versions][lifecycle.<state>][features.<key>][notes.<type>][phrases][glossary][images][widgets.<name>][consumer][builds.<name>][editor][sources.<name>]- Defaults: what an absent section means
- Validation
1. Conventions
1.1 The file
-
The content model is a TOML 1.0 file named
ascribe.toml. The directory that contains it is the project root. Every path in the file is relative to the project root unless a key says otherwise. -
The file is UTF-8.
-
TOML’s equivalent spellings are all accepted: a table can be written as a
[header]section, as an inline table (key = { … }), or with dotted keys. This reference shows the most readable form for each section. For example, these are the same:[dimensions.pm] values = ["npm", "pnpm", "yarn"] # same as dimensions.pm.values = ["npm", "pnpm", "yarn"] -
Keys are kebab-case (
content-root,trailing-slash), matching Ascribe’s attribute keys. TOML allows hyphens in bare keys, so they need no quotes. -
Unknown keys are errors, everywhere, with a did-you-mean suggestion. A misspelled key is otherwise a silently ignored setting. The exceptions are the tables whose keys are names the project chooses (
[phrases],[types],[dimensions], and so on); their keys are validated as names instead. -
Declaration order is kept where it matters. The loader preserves the order in which attribute keys are declared (
[images.attributes],[widgets.<name>.attributes]), because canonical form writes attributes in declared order (SPEC §8.3). It also preserves the order of the[dimensions]tables, which is the canonical order of@variantattributes and decides which dimension a tab group syncs on (the element contract, §2). Order is kept elsewhere too, for stable output (for example, the order of dimension values in a tab switcher is the order ofvalues). Nothing else in the model depends on order.
1.2 Names
Several kinds of name appear in the file. Each has a grammar from SPEC Appendix A or from this reference:
| Name | Grammar | Rule | Used for |
|---|---|---|---|
| name-word | SPEC A name-word |
A letter, then letters, digits, _, or - |
Dimension values, lifecycle states, feature keys, frontmatter field names |
| key | SPEC A key |
A lowercase letter, then lowercase letters, digits, or - |
Dimension names, phrase keys, note types, attribute keys, content type names, glossary term ids, source names |
| widget name | SPEC A widget-name |
Lowercase words of letters and digits joined by single hyphens, with at least one hyphen; starting with a letter | Widget names |
| build name | This reference | A letter, then letters, digits, _, -, or . |
Build names |
Dimension names follow the stricter key rule, not just name-word, because they’re also written as attribute keys in @variant {deployment=cloud} (SPEC §3.3, §3.3).
Build names allow . so that names like self-managed-3.3 work. A name containing . must be quoted in a TOML header: [builds."self-managed-3.3"].
Names are case-sensitive and spelled exactly as declared (SPEC §4.4).
1.3 Paths and patterns
-
Paths use
/as the separator on every platform. -
Filesystem paths in
[project]and[sources]are relative to the project root. They must not be absolute, and they may use... -
A glossary term’s
link(§12) is written like a link destination in a document (SPEC §5.2), relative to the content root. -
Patterns (globs) are matched against a source file’s path relative to the content root, including its extension, with
/separators. They are case-sensitive. The syntax:Syntax Matches *Any run of characters within one path segment, not including /**Any number of whole segments, including none. It must be a whole segment ( a/**/b,a/**,**/b)?Any one character except /{a,b}Either alternative. Alternatives don’t nest \Escapes the next character Every other character matches itself. A pattern must not start with
/and must not contain a..segment. Patterns only ever apply to Markdown source files (*.md); other files under the content root are assets. The exception is a source’sincludeandignore(§18), which are matched against any file’s path relative to the source’s folder.
1.4 How this reference describes keys
Each section has a table of keys with these columns:
- Type: the TOML type. “Field type”, “attribute type”, and “availability spec” are strings with the grammars in §5 and SPEC §4.4.
- Default: the value when the key is absent, or required.
- Description.
2. Top level: spec
spec = "0.1"
| Key | Type | Default | Description |
|---|---|---|---|
spec |
string | required | The version of the Ascribe specification this project targets (SPEC §11). It must be quoted: spec = 0.1 is a TOML float and is an error. A processor accepts only the spec versions it implements, compared as exact strings; this reference defines "0.1". |
The only other top-level keys are the tables in §3–§17. Anything else is an unknown key.
3. [project]
Where the source lives and where builds write.
[project]
content-root = "docs"
output-dir = ".ascribe/build"
| Key | Type | Default | Description |
|---|---|---|---|
content-root |
string (path) | "docs" |
The content root (SPEC §2.2): the directory holding every source file. Paths in links and includes that begin with / are relative to it. It must exist and be a directory. |
output-dir |
string (path) | ".ascribe/build" |
Where ascribe build writes output. Each build and emitter writes under <output-dir>/<build>/<emitter>/. It need not exist. |
Rules: both paths are relative (model-path-absolute). The output directory must not be inside the content root, the content root must not be inside the output directory, and they must not be the same directory (model-output-overlaps-content); otherwise a build would read its own output as source, or delete source as stale output. Paths are compared after normalizing . and .. segments and, when both exist, after resolving symbolic links.
The defaults are ".ascribe/build" keeps generated output out of the way of both the source and a consumer’s own dist/.
4. [types.<name>] and [fragments]
4.1 Page types
A content type is a frontmatter schema for a kind of page, plus the files it applies to.
[types.guide]
default = true
[types.guide.frontmatter]
title = "string"
description = "string?"
[types.reference]
files = ["reference/**"]
[types.reference.frontmatter]
title = "string"
api-version = "string"
<name> is the type’s name (key rule). It appears in diagnostics and in generated code (the Zod schema’s name).
| Key | Type | Default | Description |
|---|---|---|---|
files |
array of patterns | [] |
The pages this type applies to (§1.3). |
default |
boolean | false |
Makes this the default type: it applies to every page that no type’s files match. At most one type may be the default. |
frontmatter |
table of fields | required | The frontmatter schema: one entry per field, keyed by field name (name-word). Values are field types (§5). It must declare title as a required string (below). |
Which type applies to a page. For each page (not fragments; see §4.3):
- If exactly one type’s
filesmatch the page, that type applies. - If several types’
filesmatch, it’s an error on the page: the page is ambiguous. There’s no precedence between types. - If none match and a default type exists, the default applies.
- If none match and there’s no default type, it’s an error on the page.
A type with neither files nor default = true could never apply, and is an error (model-type-unreachable). The rules in steps 2 and 4 are document diagnostics, listed in SPEC §8.2.
The page title. Every page type must declare title as a required string field (model-type-title). The frontmatter title is the page’s title wherever the spec needs one, such as the replacement text of an empty link to a page (SPEC §5.2). The field may accept phrases (§11).
Reserved keys. available (SPEC §4.4) and variant (SPEC §4.3) are reserved frontmatter keys. Every page accepts them, with the meaning the spec gives, whether or not its type mentions them, and a type must not declare them (model-field-reserved). Generated consumer schemas include them automatically. Under the astro profile, slug is reserved too: Astro’s content loader uses a page’s frontmatter slug as its entry id in place of its path, which would publish the page at a URL Ascribe never computed, so a type must not declare it (model-field-reserved).
Unknown frontmatter keys. A page whose frontmatter has a key its type doesn’t declare (and that isn’t reserved) is an error on the page.
4.2 Frontmatter values
Frontmatter is YAML. For type checking, processors parse it with the YAML 1.2 core schema: true and false are booleans (yes, no, on, and off are strings), and 3.10 is the number 3.1. So a string field whose value is 3.10 unquoted is a type error, and the message suggests quoting it.
4.3 Fragments
[fragments]
patterns = ["includes/**", "**/*.partial.md"]
[fragments.frontmatter]
owner = "string?"
| Key | Type | Default | Description |
|---|---|---|---|
patterns |
array of patterns | [] |
Additional fragment patterns (SPEC §2.2). A file is a fragment if any segment of its path begins with _, or its path matches one of these patterns. |
frontmatter |
table of fields | {} (no fields) |
The fragment schema (SPEC §2.2): the frontmatter every fragment is validated against. Fields as in §5. title is not required. |
Content types never apply to fragments, even when a type’s files match a fragment’s path.
Reserved keys on fragments. The spec defines available and variant for pages only. A fragment’s frontmatter must not use them (an error on the fragment), and [fragments.frontmatter] must not declare them (model-field-reserved). Use @available inside the fragment instead.
5. Field and attribute types
Two related type languages:
- Field types describe frontmatter fields (
[types.<name>.frontmatter],[fragments.frontmatter]). They cover the SPEC §7.2 set: string, number, boolean, date, enumeration, list, and object, each optional or with a default. - Attribute types describe attributes on images and widgets (
[images.attributes],[widgets.<name>.attributes]). They cover SPEC §3.3’s set: string, enumeration, boolean, and number, plus set-valued keys.
Each entry is written either in short form, a string, or in table form, when it needs a default, a description, phrases, enumeration values that aren’t simple words, or nested fields.
[types.guide.frontmatter]
title = "string" # required string
description = "string?" # optional string
level = "enum(beginner, intermediate, advanced)" # required, one of three
tags = "list(string)?" # optional list of strings
updated = "date?" # optional date
status = { type = "enum(draft, published)", default = "published" }
author = { type = "object?", fields = { name = "string", url = "string?" } }
5.1 Short form
A field is required unless its type ends in ? or it has a default. The same holds for attributes: lab = "string" means every use of the widget must give lab.
| Short form | Field types | Attribute types | Accepts |
|---|---|---|---|
string |
yes | yes | Frontmatter: a YAML string. Attribute: a token or quoted string. |
number |
yes | yes | Frontmatter: a YAML integer or float. Attribute: a token matching ["-"] 1*DIGIT ["." 1*DIGIT] (600, -2, 1.5; not 600px). |
boolean |
yes | yes | Frontmatter: YAML true or false. Attribute: the token true or false (SPEC §3.3). |
date |
yes | no | A calendar date written YYYY-MM-DD that exists (2026-02-30 is an error), quoted or not. Times aren’t accepted. |
enum(a, b, …) |
yes | yes | One of the listed values, compared exactly. Frontmatter: a YAML string. Attribute: a token or quoted string. |
list(T) |
yes | no | A YAML sequence whose items are all of type T, where T is string, number, boolean, date, enum(…), or (table form only) object. An empty sequence is allowed. |
set(T) |
no | yes | A value set (SPEC §3.3), such as platform=cloud|on-prem, or a single token. T is string or enum(…). Members are tokens. Only keys typed set(…) accept |. |
object |
table form only | no | A YAML mapping whose keys are the object’s fields (nested field types). Unknown keys are errors. |
any of the above + ? |
yes | yes | The same type, optional. |
Grammar (ABNF, with the rules of SPEC Appendix A):
field-type = field-base [ "?" ]
field-base = scalar / enum / list-type / "object" ; "object" in table form only
scalar = "string" / "number" / "boolean" / "date"
list-type = "list" "(" OWS ( scalar / enum / "object" ) OWS ")"
; "list(object)" in table form only
attribute-type = attribute-base [ "?" ]
attribute-base = "string" / "number" / "boolean" / enum / set-type
set-type = "set" "(" OWS ( "string" / enum ) OWS ")"
enum = "enum" [ "(" OWS enum-value *( OWS "," OWS enum-value ) OWS ")" ]
; bare "enum" in table form only, with "values"
enum-value = 1*( ALPHA / DIGIT / "-" / "_" / "." )
Spaces are allowed only where OWS appears. The canonical spelling has no spaces except one after each comma: enum(a, b).
5.2 Table form
| Key | Type | Default | Description |
|---|---|---|---|
type |
string | required | A field type or attribute type (§5.1). In table form it may also be object, list(object), or bare enum, with the keys below. |
fields |
table of fields | required when type is object, object?, list(object), or list(object)?; not allowed otherwise |
The nested fields of an object, in the same syntax (short or table form). Field types only. |
values |
array of strings | required when type uses bare enum; not allowed otherwise |
The enumeration’s values, for values that aren’t simple words, such as ["Getting started", "How-to"]. Values must be distinct. Values of a set(enum) attribute must be tokens. |
default |
any TOML value | none | The value used when the field or attribute is absent. It must have the declared type (a TOML string for string and enum, integer or float for number, boolean for boolean, local date for date, array for list, inline table for object, a string or array of strings for set). A field with a default is optional; adding ? as well is allowed and changes nothing. |
phrases |
boolean | false |
Whether phrases (SPEC §5.1) are substituted in this field’s value. Field types only; allowed only on string and list(string) fields (and their optional forms), including fields nested in objects. See §11. |
description |
string | none | Help text shown by the editor (hover and completion) and emitted into generated schemas as documentation. |
Nesting beyond one level of fields is allowed but discouraged; keep frontmatter flat.
6. [dimensions.<name>]
A dimension is an axis content varies along (SPEC §4.3), and whose values are availability targets (SPEC §4.4).
[dimensions.deployment]
label = "Deployment"
values = ["cloud", "self-managed"]
versionless = ["cloud"]
labels = { cloud = "Quill Cloud", self-managed = "Self-managed" }
<name> is the dimension’s name (key rule, §1.2). It’s written as an attribute key in @variant, as a key in variant frontmatter and build selections, and as a target in availability specs, where it stands for all its values.
| Key | Type | Default | Description |
|---|---|---|---|
values |
array of strings | required | The dimension’s values (name-word), in display order. At least one; no duplicates. |
labels |
table of strings | {} |
Display labels for values, keyed by value (SPEC §9.4: “Labels for dimension values come from the content model’s display labels”). A value without a label is displayed as the value itself. Every key must be a declared value. |
label |
string | the dimension’s name | The display label for the dimension itself, used where a dimension name appears as an availability target or names a tab group. |
versionless |
array of strings | [] |
Values that are versionless (SPEC §4.4): availability for them takes a single state and no versions. Every entry must be a declared value. Values not listed are versioned. |
Rules. A value must belong to only one dimension (model-dimension-value-shared); otherwise a bare target in an availability spec would be ambiguous. Dimension names and values also take part in the one-role rule: a name can be a dimension name, a dimension value, a lifecycle state, or a feature key, but only one of them (model-name-multiple-roles).
7. [versions]
How versions in availability specs are compared (SPEC §4.4).
[versions]
scheme = "numeric"
| Key | Type | Default | Description |
|---|---|---|---|
scheme |
string | "numeric" |
The version scheme. Spec 0.1 defines one: "numeric". |
The numeric scheme. A version is any string matching SPEC Appendix A’s version rule: numbers separated by dots (3, 3.4, 3.4.1). Versions compare component by component, numerically, from the left, with missing trailing components treated as 0: 3.4 equals 3.4.0, 3.10 is later than 3.9, and 4 is later than 3.99.1. Leading zeros don’t matter (3.04 equals 3.4). This is semantic versioning’s major.minor.patch ordering. It has no pre-release or build suffixes, because the spec’s grammar doesn’t allow them; express a pre-release with a lifecycle state (preview 3.4) instead.
No other scheme is defined. The table exists so a later spec version can add one without changing the file’s shape.
8. [lifecycle.<state>]
Lifecycle states (SPEC §4.4). Five are built in:
| State | Counts as available | Default label |
|---|---|---|
preview |
yes | preview |
beta |
yes | beta |
ga |
yes | GA |
deprecated |
yes | deprecated |
removed |
no | removed |
A project adds states by declaring them, and may change the available flag or label of a built-in state. Built-in states can’t be removed.
[lifecycle.sunset]
available = false
label = "sunset"
[lifecycle.ga]
label = "Generally available"
<state> is the state’s name (name-word).
| Key | Type | Default | Description |
|---|---|---|---|
available |
boolean | built-in states: as in the table above; new states: required | Whether content in this state counts as available (SPEC §4.4, §8.3). A new state must say so explicitly. |
label |
string | built-in states: as in the table above; new states: the state’s name | The display label used in availability annotations, such as “Available: Quill Cloud (GA); self-managed (preview, 3.4+)” (SPEC §9.4). |
Rules. ga must count as available (model-lifecycle-ga-unavailable), because content with no state is ga (SPEC §4.4). Lifecycle states take part in the one-role rule, including the built-in ones: a dimension value named beta is an error.
9. [features.<key>]
The features registry (SPEC §4.4): named availability specs, so a feature going generally available takes one edit.
[features.streaming-sync]
name = "Streaming sync"
available = "cloud, self-managed preview 3.4"
<key> is the feature key (name-word). Writing it as a whole @available primary, or as the whole available frontmatter value, stands for the spec.
| Key | Type | Default | Description |
|---|---|---|---|
name |
string | required | The feature’s display name, shown on hover and available to emitters. |
available |
string (availability spec) | required | The feature’s availability, in SPEC §4.4’s syntax. |
Rules. The spec is checked like one written in a document: it must parse (model-availability-syntax); every target must be a declared dimension value or dimension name, and every state a declared state (model-availability-unknown-name); versionless targets and dimension names take no versions (model-availability-versionless); histories must be in chronological order (model-availability-history-order). It must not be a feature key itself (model-feature-nested), so features never refer to each other. Feature keys take part in the one-role rule.
10. [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 may 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.
11. [phrases]
The phrases registry (SPEC §5.1).
[phrases]
product = "Quill"
cloud = "Quill Cloud"
version = "3.4.1"
api = "https://api.quill.dev/v3/"
Every key is a phrase key (key rule, the same rule as SPEC Appendix A’s phrase), and every value must be 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).
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.
12. [glossary]
The glossary (SPEC §5.4): terms, their definitions, and how occurrences are matched. Authors don’t mark terms in source; processors link them (SPEC §9.2 step 7).
[glossary]
match = "first"
case-sensitive = false
[glossary.terms.api-key]
term = "API key"
aliases = ["API keys"]
definition = "A secret token that authenticates the Quill agent to Quill Cloud."
link = "/reference/glossary.md#api-key"
12.1 Settings
| Key | Type | Default | Description |
|---|---|---|---|
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. |
case-sensitive |
boolean | false |
Whether occurrences must match a term’s case exactly. When false, matching ignores case. A term can override it. |
terms |
table of terms | {} |
The terms, keyed by term id (key rule). |
12.2 Terms
| Key | Type | Default | Description |
|---|---|---|---|
term |
string | required | The term as it appears in prose. Non-empty. |
aliases |
array of strings | [] |
Other forms that count as occurrences, such as plurals. |
definition |
string | required | A short plain-text definition, shown on hover in the editor and included in the JSON output. |
link |
string (path) | none | Where the full definition lives: a source file path relative to the content root, with an optional #id, written as in a link (SPEC §5.2; a leading / is allowed and means the same). It must name a page, not a fragment. Occurrences are linked here. |
case-sensitive |
boolean | the [glossary] setting |
Overrides case-sensitive for this term, for terms like Go that collide with ordinary words. |
Matching: occurrences match whole words only; the longest matching term wins where terms overlap (API key over API); matching applies to prose only, never to headings, link text, code, raw HTML, or text inside a directive’s primary identifier. A term with no link isn’t linked in the site or plain-markdown output; its definition still reaches the editor and the JSON output.
Rules. No two terms or aliases may be the same text (compared ignoring case when either is case-insensitive; model-glossary-duplicate-term). The link file must exist and not be a fragment (model-glossary-link); its #id is checked with the page-level link checks, since ids depend on parsing.
13. [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" }
| 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).
14. [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, §3).
[widgets.quill-labspace]
description = "An embedded, runnable Quill lab."
forms = ["line"]
binding = "self"
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, §8.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 must not have a required primary, since a container opener’s primary is empty (SPEC §3.5). A container-only widget’s primary must be
"none"(model-widget-container-primary). - A groupable widget must be container-only, since group arms are containers (SPEC §3.6) (
model-widget-groupable-form). bindingis required with line form and not allowed without it (model-widget-binding).
The site output’s element for a widget (tag name and attributes) is defined by the element contract, not here.
15. [consumer]
The consumer profile (SPEC §9.5): how the site output fits a specific consumer. Spec 0.1’s toolchain implements one profile, astro (SPEC §9.6). The profile sets defaults for every other key; a key given here overrides them, within the values the profile supports.
[consumer]
profile = "astro"
site = "https://docs.quill.dev"
base-path = "/"
trailing-slash = "always"
slugger = "github"
| Key | Type | Default | Description |
|---|---|---|---|
profile |
string | "astro" |
The consumer profile. Supported: "astro". |
site |
string (URL) | none | The published site’s origin, such as "https://docs.quill.dev": an http or https URL with no path, query, or fragment. The plain-markdown output needs it to write absolute links (SPEC §9.4). Without it, plain-markdown links are root-relative (they start with base-path) and ascribe build warns. |
base-path |
string | "/" |
Routing. The URL path every route starts with, such as "/docs/", including any locale prefix ("/en/"). It must start with /. A trailing / is optional and doesn’t change the meaning. |
trailing-slash |
string | "always" |
Routing. Whether page URLs end in /: "always" (/guides/setup/) or "never" (/guides/setup). Match the consumer’s own setting (Astro’s trailingSlash and build.format). |
slugger |
string | "github" |
Slugging. The algorithm for heading ids (SPEC §5.5), which must be the one the consumer uses. Supported: "github", a port of github-slugger, which Astro uses. |
html |
boolean | true |
HTML passthrough. Whether the consumer renders raw HTML in markdown. The site output’s custom elements need it, so the astro profile supports only true. |
Heading ids, image attributes, and assets (SPEC §9.5) are part of the profile, not keys. The astro profile has exactly one way to do each:
- Heading ids and image attributes: the site output writes each as a
<ascribe-attributes>marker that the consumer’s markdown plugin applies, so the consumer keeps its own heading, table-of-contents, and image processing. - Assets: copies mirror their source paths inside each output, and images are referenced relatively so Astro’s image processing still applies. Other files a page links to are published under
_ascribe/files/.
A later profile that offers a choice will add a key for it.
How file paths become routes (the astro profile): a page’s route is base-path, then its path relative to the content root with the .md extension removed and each segment slugged the way Astro’s content loader computes entry ids; a final index segment is dropped (guides/index.md → /guides/); then the trailing slash per trailing-slash. The root index.md (Astro’s entry id index) is at base-path, which under trailing-slash = "never" loses its final / unless it’s /. Two pages with one entry id (My File.md and my-file.md, or index.md and index/index.md) can’t both be published, and ascribe build --emit site fails, naming them. The same router answers the reverse question, which page a route-like link names, for the link-route warning and its fix. Source files never contain routes (SPEC §5.2).
The astro profile’s site, base-path, and trailing-slash repeat settings from astro.config. The Astro integration checks that they agree, and fails the build, naming each difference, when they don’t. It compares base-path as a path with a leading and a trailing /, treats Astro’s trailingSlash: "ignore" as agreeing with either value, and compares site by origin when both sides set it.
16. [builds.<name>]
Named builds (SPEC §9.3). Each sets a variant mode and an availability mode. These are SPEC §9.3’s examples, in ascribe.toml:
[builds.site]
variants = "switch"
availability = "badge"
[builds.cloud-pdf]
variants = { deployment = "cloud" }
availability = { filter = "cloud" }
[builds."sm-3.3"]
variants = "switch"
availability = { filter = "self-managed 3.3" }
<name> is the build name (§1.2). It names the build on the command line (ascribe build --build cloud) and in output paths.
| Key | Type | Default | Description |
|---|---|---|---|
variants |
string or table | "switch" |
The variant mode. "switch" keeps every arm and page. A selection is a table from dimension names to a value or an array of values: { deployment = "cloud" }, { pm = ["npm", "pnpm"] }. Every dimension must be declared and every value a value of that dimension. An empty table is an error; write "switch". |
availability |
string or table | "badge" |
The availability mode. "badge" keeps everything and annotates it. { filter = "<target> [<version>]" } removes content not available for the target at the version. The target must be a declared dimension value, not a dimension name. A versioned target must have a version, and a versionless one must not. The version follows the version scheme (§7). |
Rules. Build names must be unique ignoring case (model-build-name-case), since they become directory names and some file systems ignore case. A build that filters for a target its own selection excludes (for example, selecting deployment = "cloud" and filtering for self-managed 3.3) is legal but almost certainly a mistake, so the loader warns (model-build-filter-excluded).
17. [editor]
Settings for the authoring environment (SPEC §10).
[editor]
build = "site"
| Key | Type | Default | Description |
|---|---|---|---|
build |
string | see below | The build whose page-level diagnostics the language server reports by default (SPEC §8.1 checks pages once per build). It must name a declared build. |
Default. If only one build exists, that build. Otherwise, the build named site, if there is one. Otherwise the key is required (model-editor-build-required).
18. [sources.<name>]
A source is a folder outside the project’s content that its pages may take code examples from, with @snippet (SPEC §7.3). It’s the only way a page can read a file outside the project’s folder.
[sources.code]
path = ".." # relative to the folder ascribe.toml is in
include = ["crates/**", "examples/**"] # what's readable; everything else isn't
ignore = ["**/target/**"]
A page names a file through its source as <source>:<path>, with the path relative to the source’s folder: @snippet: code:examples/quill/ascribe.toml#dimensions.
| Key | Type | Default | Description |
|---|---|---|---|
path |
string (path) | required | The source’s folder, relative to the project root. It must exist, be a directory, and be inside the git repository the project is in, when the project is in one. |
include |
array of patterns | every file | The files a snippet may read, matched against their paths relative to path (§1.3). |
ignore |
array of patterns | none | Files left out even when include matches them. |
<name> follows the rules for keys (§1.2). A project can declare several sources.
Rules. path is relative (model-path-absolute); its folder must exist and be a directory (model-source-path-missing) inside the project’s repository (model-source-outside-repository). git and branch are reserved for a source in another repository, and are errors for now (model-source-remote).
19. Defaults: what an absent section means
A file containing only spec = "0.1" is valid. It means:
| Section | When absent |
|---|---|
[project] |
content-root = "docs", output-dir = ".ascribe/build" |
[types] |
One implicit default page type, named page, with frontmatter title = "string" and nothing else. If [types] declares any type, there’s no implicit type. |
[fragments] |
Only paths with a _ segment are fragments. The fragment schema has no fields, so a fragment’s frontmatter can’t have any keys. |
[dimensions] |
No dimensions. Any @variant arm with attributes, or variant frontmatter, is an error. |
[versions] |
scheme = "numeric" |
[lifecycle] |
The five built-in states. |
[features] |
No features. |
[notes] |
The five built-in note types. |
[phrases] |
No phrases. Every {…} is literal text. |
[glossary] |
No glossary. |
[images] |
No image attributes. |
[widgets] |
No project widgets. |
[consumer] |
profile = "astro" with its defaults. |
[builds] |
One implicit build, site, with variants = "switch" and availability = "badge". If [builds] declares any build, there’s no implicit one. |
[editor] |
build as in §17. |
[sources] |
No sources. A page can’t read anything outside the project’s folder. |
20. Validation
A content model with errors is reported, and nothing else is checked, since every other check depends on it. Its warnings are reported with the rest of the diagnostics. Every rule the loader enforces is listed in the diagnostics reference, with its code and how to fix it.