Writing docs

Write docs that ship with your package and that people and agents find by search or one level at a time.

Add a topic with the CLI

Run integration add doc in your package to add a topic. It writes the topic file and declares the docs root in astryx.integration.mjs.

bash
npx astryx integration add doc deploying
# Read it the way an app will
npx astryx docs deploying
text
doc contribution added
​
[ok] deploying
​
Declare doc root ./docs in astryx.integration.mjs.
​
- docs/deploying.doc.mjs
- astryx.integration.mjs
  • Name the topic in lowercase kebab-case, such as deploying. Readers type the name as a command argument, so it holds only letters, digits, _, and -.
  • Keep the name stable: readers and links find the topic by it.
  • Pick a name that astryx docs does not already list: no Core topic, and no section such as tokens or layout. To take over or add to a Core topic, see astryx docs cli/integrations/building-blocks/docs/extend-or-replace.

Write the sections

A topic is a plain object with type: 'generic', a name, a title, a one-sentence description, and sections. Each section has a title and a list of content blocks.

docs/deploying.doc.mjs
javascript
/** @type {import('@astryxdesign/cli/authoring').ReferenceDoc} */
export default {
type: 'generic',
name: 'deploying',
title: 'Deploying',
description: 'Ship an app built with Acme widgets.',
sections: [{
id: 'build-before-you-ship',
title: 'Build before you ship',
content: [
{type: 'prose', text: 'Build the app, then upload the `dist` folder.'},
{type: 'code', lang: 'bash', code: 'npm run build'},
{type: 'list', style: 'unordered', items: ['Keep `dist` out of git.']},
{type: 'table', headers: ['Variable', 'Value'], rows: [['`NODE_ENV`', '`production`']]},
],
}],
};

The sample sets a section id, so package.json needs the CLI peer "@astryxdesign/cli": ">=0.6.4". Published 0.6.3 and earlier reject id and hide every doc topic your package ships, and integration verify fails without that peer; see astryx docs cli/integrations/ship/versioning.

Pick the doc kind

Every doc is a .doc.mjs file whose type says what it describes. integration add writes the right type and file for each kind.

You document`type`File that `integration add` writes
A guide or topic'generic'docs/deploying.doc.mjs
Your package's docs section'namespace'docs/acme.doc.mjs
A component'component'components/AcmeCarousel.doc.mjs, beside AcmeCarousel.tsx
A template'page' or 'block'templates/acme-dashboard.doc.mjs, beside acme-dashboard.tsx
A theme'theme'themes/ocean/oceanTheme.doc.mjs, beside oceanTheme.ts

Only guides, topics, and your docs section go in the docs root. The others have their own guides: astryx docs cli/integrations/building-blocks/components, astryx docs cli/integrations/building-blocks/templates, and astryx docs cli/integrations/building-blocks/themes.

Keep topics in the docs root

The docs field in astryx.integration.mjs names the folder that holds your topics. The CLI reads every .doc.mjs file under it, in subfolders too.

astryx.integration.mjs
javascript
export default {
docs: './docs'
};
  • Readers open a topic by its name, not its file name. Name the file after the doc, <name>.doc.mjs, so each one is easy to find.
  • A topic with no placement is a flat topic: the docs list shows it under Topics, and readers open it by its name.
  • To give your package its own section in the docs tree instead, see astryx docs cli/integrations/building-blocks/docs/sections-and-placement.

Add a docs section

Give your package its own section in the docs tree with integration add doc <name> --parent <section>. The first run also writes the section's namespace doc.

bash
npx astryx integration add doc deploying --parent acme
# Open your section
npx astryx docs acme
docs/acme.doc.mjs
javascript
/** @type {import('@astryxdesign/cli/authoring').NamespaceDoc} */
export default {
type: 'namespace',
name: 'acme',
title: 'Acme',
summary: 'Guides for Acme.',
slots: {
guides: {title: 'Guides', accepts: {kinds: ['generic']}},
},
};
  • Edit its title and summary: readers see them in the docs list and at the top of your section.
  • The guide, docs/deploying.doc.mjs, gets placement: {parent: 'namespace:acme', slot: 'guides'}. Later runs with --parent acme reuse the namespace doc.
  • package.json gets the optional peer "@astryxdesign/cli": ">=0.6.4", because an older CLI does not read sections; see astryx docs cli/integrations/ship/versioning.

Place a doc

A guide names its one home with placement: a namespace of your package, a slot in it, and an order. Its route is the section name, then the guide name.

javascript
placement: {parent: 'namespace:acme', slot: 'guides', order: 10}, // route: acme/deploying
bash
npx astryx docs acme/deploying

Fix a failed placement

A failed placement hides the doc: it gets no route and does not show in the docs list. doctor integration docs fails with invalid_doc_graph and names what to fix.

bash
npx astryx doctor integration docs
text
severity: [fail]
code: invalid_doc_graph
message: @acme/astryx-widgets/deploying.doc.mjs: placement.parent "namespace:cli" names no namespace; @acme/astryx-widgets declares "acme".

Write {@link [provider:]kind:name} in prose, list items, and table cells to link another doc. The CLI prints the command that opens it, so the link keeps working when the doc moves.

javascript
{type: 'prose', text: 'Before you ship, read {@link generic:deploying}.'},
{type: 'list', style: 'unordered', items: ['All guides: {@link namespace:acme}.']},
{type: 'table', headers: ['Task', 'Guide'], rows: [['Ship', '{@link generic:deploying}']]},

Each link reads as a command. This link, astryx docs cli/integrations/building-blocks/docs/extend-or-replace, opens the next guide.

  • The kind is generic for a topic or guide, namespace for a docs section, command or function for a CLI command or API function, schema or enum for a schema or a fixed set of values, component for a component, and page or block for a template.
  • Name a component by its export name and a template by its id. Its link reads as the command that opens it, such as astryx component AcmeCarousel --package @acme/widgets.
  • Inside backticks or a code block, link syntax prints as written.

A link without a provider resolves against your own package. To link the CLI's docs, or another package's, start the target with that package's name, such as @astryxdesign/cli:.

javascript
// Resolves: the CLI's doctor command
{type: 'prose', text: 'Check the app with {@link @astryxdesign/cli:command:doctor}.'},
// Does not resolve: looks for a doctor command in your package
{type: 'prose', text: 'Check the app with {@link command:doctor}.'},
  • Name a CLI command the way you type it, spaces included, such as @astryxdesign/cli:command:doctor integration docs.
  • In a topic that extends another package's topic, your sections still resolve against your package, so a link to the base topic's docs needs its provider.

A link that names no doc prints as written, and doctor integration docs warns. The warning names a search that finds the right target.

bash
npx astryx doctor integration docs
text
severity: [warn]
code: invalid_doc_graph
message: acme/deploying § check-before-you-ship: "command:doctor" names no doc. Find it with `astryx search doctor --type doc`, then name it as `[<provider>:]<kind>:<name>`.

Replace a topic

Set replaces to take over an existing topic, such as Core's getting-started. Readers who open the old name get your topic.

bash
npx astryx integration add doc acme-getting-started --replaces getting-started
# The old name now opens your topic
npx astryx docs getting-started
docs/acme-getting-started.doc.mjs
javascript
export default {
type: 'generic',
name: 'acme-getting-started',
replaces: 'getting-started',
title: 'Acme getting started',
description: 'Install Acme widgets and render your first carousel.',
sections: [/* ... */],
};

The docs list shows your topic in place of the old one. Because your topic has its own name, the old name keeps resolving to it, so links and agents that learned the old name still land on your topic.

Extend a topic

Set extends to merge sections into an existing topic instead of owning it. A section replaces the base section it matches, and a section that matches none is added at the end.

bash
npx astryx integration add doc acme-theming --extends theme
npx astryx docs theme --index
docs/acme-theming.doc.mjs
javascript
export default {
type: 'generic',
name: 'acme-theming',
extends: 'theme',
title: 'Acme theming',
description: 'Theme an app that uses Acme widgets.',
sections: [
{id: 'quick-start', title: 'Quick Start', content: [/* replaces the base section */]},
{id: 'use-the-ocean-theme', title: 'Use the ocean theme', content: [/* added at the end */]},
],
};
  • Read the base topic's keys with --index, and give a section that replaces one that key as its id. A section without an id matches only a base section with exactly its title, or one whose id is the key its title makes, so Integration Themes is added beside a base Integration themes that has no id.
  • The topic keeps the base's title and description, and readers open it by the base's name.
  • Replace the Overview placeholder that integration add writes, or it is added to the base topic.
  • Extend a topic to correct or add to it. A copy made with replaces stops getting the owner's fixes.

Check overlaps with Core topics

A topic sets replaces or extends, never both, and a placed guide sets neither. A topic that uses a Core topic's name with neither is an accidental conflict: apps keep reading the Core topic.

bash
npx astryx doctor integration docs
text
severity: [info]
topic: acme-getting-started
relationship: replaces
coreTopic: getting-started
message: Intentional override: "acme-getting-started" replaces the Core topic "getting-started".
​
severity: [fail]
topic: color
relationship: accidental
coreTopic: color
message: Accidental conflict: "color" is already a Core topic. Rename it, declare replaces: 'color' to take it over, or declare extends: 'color' to merge sections.
  • An intentional overlap prints as [info]. An accidental one fails with exit code 1.
  • A topic that sets both fails as invalid_doc, and so does a placed guide that sets either one.

Keep each read short

Readers open one section at a time, so give each section one idea and keep it to about 30 lines. npx astryx doctor warns on any read over 32 KB.

  • When a section needs a second idea, split it into two sections.
  • Keep a topic to a few sections. When it grows past five, split it into more guides in your docs section.
  • A topic with more than one section reads as its section list. Readers open one section by its key, or the whole topic with --full.
bash
# The section list
npx astryx docs acme/deploying
# One section
npx astryx docs acme/deploying check-before-you-ship
# Everything
npx astryx docs acme/deploying --full

Lead with the summary

A section's first prose block, or its first list item, is its summary in section lists and search results. Make it answer the section's question in one or two sentences.

  • The summary is cut at about 240 characters.
  • A code block first does not count: the summary comes from the next prose block.
  • Open with the answer, not with background.
text
build-before-you-ship Build before you ship - Build the app, then upload the `dist` folder.

Make docs findable

In a topic with more than one section, search ranks a section whose title matches the query, or that names it in backticks, above words in body text. Title each section with the task a reader searches for.

  • Name the task in the words a reader types, such as "Deploy to production". Avoid titles such as "Overview" or "Details".
  • Write field names, file names, and error codes in backticks, such as deployTarget: in a topic with more than one section, search treats each one as a keyword.
  • Other words in the summary and body match too, but rank below titles and identifiers. The summary shows under each hit, so make it answer the query.

Test a doc the way a new reader finds it: search for the question, and check that the first hit answers it. Quote a query of more than one word.

bash
npx astryx search "place a doc" --type doc

Run the docs check

Run doctor integration docs in your package to check the docs tree, every link, and overlaps with Core topics. Pass a package name to check an installed package.

bash
npx astryx doctor integration docs
# In an app, check an installed package
npx astryx doctor integration docs @acme/astryx-widgets
text
Checking integration docs: @acme/astryx-widgets@1.0.0
​
[ok] The docs tree and every link in these docs check out.
​
[ok] No doc topics overlap with Core.

Its arguments and exit codes are in astryx docs cli/commands/doctor-integration-docs.

Know what fails the check

A doc that does not load, an accidental Core overlap, a reference block that names no doc, or a failed namespace or placement fails the check with exit code 1. A link that names no doc only warns, and the exit code stays 0.

ProblemReported asExit code
A doc that does not load, such as an unknown section field or block type[fail] invalid_doc1
A topic with a Core topic's name and no replaces or extends[fail] accidental1
A placement that fails, which hides the doc[fail] invalid_doc_graph1
A reference block whose target names no doc[fail] invalid_doc_reference1
A link that names no doc[warn] invalid_doc_graph0
A topic that sets replaces or extends[info] replaces or extends0

Read the warnings before you publish. With --json, they are in data.issues, with severity: "warning".

Check each doc against its type

doctor integration validate reads every doc your package ships against the type @astryxdesign/cli/authoring publishes for its kind, the way tsc would: a hidden doc and a second doc for one name too. Test fixtures under __tests__ in the components or docs root, which those readers skip, are counted, not checked. It reports a field the type does not have (unknown_field), a missing required field (missing_field), and a value outside the field's type (wrong_type).

bash
npx astryx doctor integration validate
text
Checking 1 doc(s) against the authoring types (TypeScript 6.0.3)
​
severity: [warn]
doc: components/AcmeCarousel.doc.mjs
line: 7
export: default
type: ComponentDoc
field: summary
problem: unknown_field
message: Object literal may only specify known properties, and 'summary' does not exist in type 'SingleComponentDoc'.
fix: Remove `summary`: SingleComponentDoc has no field `summary`.
  • The folder decides the type: ComponentDoc in your components root, IntegrationTemplateDoc in your templates root, ThemeDoc in your themes root, and in your docs root the type the doc's type names. Keep the @type comment above each doc's export too, so your editor shows the same problems as you type.
  • Every finding is a warning, so the exit code stays 0. With --json, they are in data.docTypes.findings.
  • It uses the typescript your package installs. Without it, it prints [skip] Docs not type-checked and the reason.
  • Every field of every doc type is in astryx docs authoring.

Check read size

npx astryx doctor also measures every read and warns on one over 32 KB. Run it in your package or in an app that installs it; doctor integration docs does not check size.

bash
npx astryx doctor
text
id: docs-progressive-disclosure
status: [warn]
label: Documentation navigation and size
message: acme/deploying build-before-you-ship: 46 KB, over the 32 KB one read may return

Split a section over the limit into smaller ones, each with its own key. See astryx docs cli/commands/doctor.

Check the CLI peer

integration verify fails a package that ships a docs section, a placed guide, or a doc section with an id without an @astryxdesign/cli peer of >=0.6.4. It does not run the docs check, so run both.

bash
npx astryx integration verify
text
- [fail] The package ships a namespace doc or a placed guide but declares no @astryxdesign/cli peer. A stable CLI before 0.6.4 does not read the docs tree, and can hide every doc topic the package ships. Declare "@astryxdesign/cli": ">=0.6.4" in peerDependencies (optional in peerDependenciesMeta, if the CLI is not required).
  • integration add doc --parent writes the peer for you.
  • integration verify passes a hidden guide and an accidental Core overlap; only doctor integration docs catches them.
  • Everything else it checks is in astryx docs cli/commands/integration-verify and astryx docs cli/integrations/ship/checks.