| name | write-docs |
| description | Write or refresh an Ably documentation page in this repo. Use when creating a new MDX page, rewriting an existing one, or applying the page-design principles to docs work. Captures the principles, page-type templates, writing standards, and code-accuracy checks distilled from the April 2026 wireframe workings used for the AI Transport refresh. |
How to use this skill
This skill is the distilled rulebook for writing Ably docs in this repo. Read it before starting a draft. Apply the per-page-type template to your draft. Run the verification checks before review.
The principles are aimed at the human reader first and the AI agent second. The writing standards are aimed at not sounding like AI.
Required reading
Read these in-repo references before you draft. The skill does not duplicate them.
CLAUDE.md — repository conventions: MDX components, frontmatter, code blocks, <Aside>, <Tiles>, <Table>, language toggles, nav files. The "Content Formatting (MDX)" and "Writing Style" sections are load-bearing.
writing-style-guide.md — Ably house style: International English, present tense, active voice, second person, imperative headings, sentence case, no em dashes, no bold-prefix bullets, no Latin abbreviations, no subjective phrases, no vague modals, no slang, no pricing detail in technical docs.
When this skill conflicts with CLAUDE.md or writing-style-guide.md, those win. Flag the conflict.
Principles (read these once; apply them every time)
Eighteen principles govern Ably docs pages, distilled from the AI Transport wireframe workings (April 2026). Three groups: general (apply to every page), page-specific (depend on page type), and mechanical (binary checks).
General
- Humans first, agents second. Narrative and outcome up top; technical detail and API specifics lower on the page where agents still find them.
- Progressive disclosure. Each page has clear tiers — Understand → See it → Build it → Go deep. A reader can stop at any tier and have gotten value.
- Minimal first code sample. Contrived, no setup boilerplate, no auth scaffolding. Demonstrate the one capability. Runnable implementation comes later.
- Link to concepts, don't require them. Reference concepts inline with one or two lines of explanation plus a "learn more" link. A reader should not need to leave the page to understand it.
- Primary audience: developers and senior engineers. Product readers are secondary — accessible enough to evaluate, written for engineers.
- We enable user experience. Show what the API makes possible for end users, not just what the API exposes. Avoid pure feature-listing.
- Visual type matches page type. Feature pages use UX interaction sketches. Concept, positioning, and infrastructure pages use architectural/flow/state diagrams. UX visuals on a concept page read as marketing and undermine credibility.
Page-specific
- Feature pages lead with the mechanism. The first heading is "How it works", not "the problem with X". Lead with how Ably solves the job; problem framing is a lightweight aside or lives on the concept page.
- Concept pages convey what the layer requires. The pattern is problem → model → what this layer requires → code proof. Name the technical properties (ordering, persistence, accumulation, fan-out, presence) and why each matters.
- Concept pages do not teach features. One minimal code-proof sample max. No API surface walkthrough. If a concept page drifts into method-by-method content, it stops being a concept page and the feature pages around it feel thin.
- Features map to jobs to be done. Each feature page maps to what a developer is trying to accomplish ("barge-in" = "let my users change direction mid-response"). Page names and intros use the JTBD framing.
- Framework pages: intentional design framing, not "missing features". Frame the framework's scope as a deliberate boundary the framework chose, and Ably as the layer that fills the gap. Never "the framework can't do X" — always "the framework intentionally doesn't do X". Sibling framework pages (for example UI vs Core) use verbatim-aligned capability bullets.
- Positioning pages: open problem-first, turn positive. "Why X" pages may lead with the problem, but must pivot to the capability section by mid-page. Pages that stay defensive end-to-end become gripe lists, not pitches.
Mechanical (binary, agent-checkable)
- Layer 0 hook in the first 5 seconds. Every page opens with a hook that answers "what is this, why should I care?". The
intro: frontmatter feeds the auto-generated PageHeader; the body's first paragraph reinforces it. On feature pages, the hook is two sentences: outcome first ("Your users can change direction mid-response"), mechanism second ("AI Transport's session layer lets a client cancel and re-prompt without breaking the stream"). Per-interface API reference pages are the one exception: they drop intro: and let the opening paragraph carry the hook. Navigational API pages (the API reference hub, the errors page) keep intro: like every other page type. See the ## API reference pages section for the full standard.
- Cover unhappy paths. Every feature page has an edge-cases section. Race conditions, timeouts, network drops, capability-missing failures, what happens when the LLM errors. This is what separates trusted docs from marketing.
- FAQ with three to five real entries on feature pages. Surface what developers actually ask. Do not pad to meet a count.
- No API keys in client code — but server-side agent code is the opposite. In client (browser) code, never show an API key; use
authUrl: '/auth' as the placeholder and link out to concepts/authentication (or the equivalent setup page) once. In server-side or agent code (durable-execution activities, agent routes, workers, anything that runs on your own infrastructure), do the reverse: construct the Realtime client with new Ably.Realtime({ key: process.env.ABLY_API_KEY }). An authUrl on the server is wrong — there is no browser to fetch a token, and the key is already trusted in that environment. When you see authUrl in an agent/server snippet, that is a bug to fix, not a rule to preserve. (The line 380 verification grep excludes process.env-sourced keys, so this pattern passes it.)
- Visual rhythm. Diagram, code block, card layout, or icon table every few paragraphs. No walls of text.
Cross-product orientation
AI Transport is the testbed for the new principles. Other Ably products will align over time. Diverge from Chat / LiveObjects / Pub-Sub patterns where these principles demand it; do not contort an AIT page to match an older Pub-Sub convention. Note the divergence in the PR description so the wider docs can track.
Page-type templates
Pick the template that matches the page you are writing.
Templates describe structure, not headings. The numbered items below are the section shape — what each section covers and roughly in what order. They are not the literal H2 text for the page. A concept-page template item like "Problem statement" becomes a descriptive imperative on the page itself: ## Why Runs exist, ## Why sessions exist. "Model" becomes ## Understand the Run lifecycle, ## Understand sessions and channels. "Code proof" becomes ## Trigger a Run, ## Attach to a session. Adapt the heading to the specific concept; never copy the template label verbatim. Use imperative or descriptive verb phrases per the writing-style guide.
Feature page
A feature page maps to one developer JTBD. The section order below distils the structural pattern feature pages follow.
- Layer-0 hook. Two sentences, outcome → mechanism.
- UX sketch + JTBD one-liner. Include the sketch where there is a discernible UX pattern; prose-only is acceptable when there is not (an architectural feature like concurrent-turns may not have a UX sketch).
- Minimal code sample. Contrived, no auth boilerplate.
authUrl: '/auth' placeholder where any auth is shown.
- "How it works" mechanism section. Positive-forward. Architectural diagram if it earns its place.
- Implementation. Server code, client code, verification-in-prose ("open the channel in the Ably dashboard to see…"), durable-execution guidance inline where applicable.
- Configuration / inline API reference. Where applicable.
- Edge cases and unhappy paths. Bullet list. Cover races, timeouts, drops, capability failures, partial state.
- FAQ. Three to five real questions, each with a short answer.
- Related features. Two to three hand-picked. No autogeneration.
Concept page
Concept pages convey the mental model. The pattern is problem → model → what the layer requires → code proof.
- Intro + opening claim. What this concept is, in two or three sentences.
- Problem statement (one paragraph). The shape of the difficulty this concept solves.
- Model. How the concept maps to Ably's primitives. Diagrams welcome.
- What this layer requires. A properties table (ordering, persistence, accumulation, fan-out, multiplexing, bidirectional, mutable state — whichever apply). One row per property, with a "why it matters" cell.
- One code-proof sample. Five to ten lines. Demonstrates the concept; does not teach the API surface. If you find yourself walking through method-by-method, move that to a feature or API reference page.
- Sub-sections as needed for the concept (lifecycle, persistence model, sharing, materialisation, and so on).
- Read next. Three to five links — adjacent concepts, the feature pages that build on this, the API reference for the primitive.
Framework page
A page that explains how Ably composes with a third-party framework (Vercel AI SDK, Temporal, Inngest, and so on).
- What the framework brings. A capability table — what the framework owns by design.
- What Ably adds. A capability table for what Ably layers on. Sibling framework pages are pages in the same
frameworks/ sub-tree that compete for the same developer JTBD (for example, Vercel AI SDK UI alongside a future Temporal page). Align bullets across siblings where the capability genuinely overlaps so a reader can compare like for like. Introduce framework-specific rows where the integration shape differs. Do not force parity when one framework simply does not own that capability. Comparability is the goal, not forced parity.
- Where they connect. The minimal integration code. The plug-in point (
ChatTransport, codec, adapter).
- Scope and trade-offs. Frame the framework's scope as intentional design. Never "the framework can't…".
- Read next. Getting-started, sibling framework page, API reference for the integration.
Positioning page
"Why X" or product-overview pages. They may lead with the problem, but they must turn positive by mid-page.
- One-sentence opening. The shape of the product, not the problem.
- Problem statement (one or two paragraphs). What current solutions miss.
- Pivot. A clearly-marked "How Ably solves this" or equivalent capability section. Everything below the pivot is positive-forward.
- Comparison table (optional). Direct HTTP vs durable sessions, default cache vs Ably, and so on. Each row is checkable.
- How Ably implements this. The primitives that back the model — link out to concepts.
- Read next. Getting-started + concepts.
Getting started page
A walkthrough that ends with a working app.
- What you build. Bulleted outcomes — "tokens stream over a durable session", "second tab joins the same session", "stop button cancels the turn". The reader knows what they will have at the end.
- Prerequisites. Versions, accounts, API keys required.
- Install dependencies.
- Set up authentication. Link out to the auth concept/setup page. Show only the
authUrl: '/auth' placeholder in client code.
- Server-side code. The Ably-specific code, clean. No 40-line auth boilerplate.
- Client-side code. The Ably-specific code, clean.
- Wire it together. The minimal app shell.
- What is happening. A four-bullet recap of the flow.
- Architecture pointer. Two sentences linking out to the framework page or concept overview.
- Explore next. Five links into features.
Guide
End-to-end scenarios that combine multiple features ("building support chat", "building a copilot"). Larger than a feature page; assumes getting-started complete.
- What you build. Bulleted outcomes — concrete UX behaviours.
- Step 1 to N. Each step focuses on one feature or one piece of wiring. Cross-link to feature pages for depth.
- Production hardening. A few production-specific notes for this scenario.
- Edge cases. Scenario-specific edge cases.
- Read next. Sibling guides, going-to-production, concept pages.
Troubleshooting
Organised by subsystem (connection, auth, codec, turn lifecycle, and so on). Each page covers symptoms, root causes, confirmation steps, and fixes.
- Symptom-led headings. "Channel will not attach", "Client keeps reconnecting", "Tokens delivered but in wrong order".
- For each symptom: what the user sees → how to confirm the diagnosis → the fix.
- Cross-references to the relevant feature/concept page at the end.
Going to production
A single checklist page. Items inline. Cross-cutting only.
- Pricing pointer (link to
/docs/platform/pricing; never restate numbers — see writing-style-guide).
- Limits and quotas.
- Monitoring and observability.
- Auth hardening.
- Data shipping / retention.
- Compliance.
- Deployment notes.
- Cross-cutting edge-cases pointer — "when adopting in production, also consider…" with links back to per-feature edge-case sections.
API reference page
API reference pages have their own complete standard — they do not follow the other page-type templates. See the ## API reference pages section below for the full rules: frontmatter, page structure, anchor convention, visible-vs-hidden type tables, React hook sub-template, and verification.
Internals page
For curious senior engineers. Register: precise, technical, no hand-holding. Diagrams + code + protocol detail.
- Intro. What this internal subsystem is.
- Diagrams. ASCII or PNG/SVG; depict the data flow, sequence, or structure.
- Mechanism walkthrough.
- Cross-references to the wire-protocol or codec reference where applicable.
API reference pages
API reference pages have their own complete standard, distinct from every other page type. The rules in this section apply only to API reference pages — they do not modify feature, concept, framework, positioning, getting-started, guide, troubleshooting, or internals pages. Those page types keep their templates exactly as written above.
The canonical live reference is the Chat API at src/pages/docs/chat/api/javascript/ (chat-client.mdx, messages.mdx, room.mdx, connection.mdx). When this section and those pages disagree, the pages win — re-verify against them.
File layout and frontmatter
- Pages live at
src/pages/docs/{product}/api/{language}/{page-slug}.mdx. Multi-SDK products use src/pages/docs/{product}/api/{language}/{sdk-variant}/{page-slug}.mdx (e.g. pub-sub/api/javascript/realtime/). Each page covers one interface, class, or hook.
- Frontmatter on per-interface pages:
title, meta_description, meta_keywords only. No intro:. No redirect_from unless the URL is changing.
- Frontmatter on navigational / cross-cutting pages (the API reference hub, the errors page): keep
intro: per principle 14.
- No body H1. The title comes from frontmatter.
- Opening: one or two paragraphs describing the interface and how to access it, then an access-example
<Code> block.
Page structure (canonical order)
- Intro paragraph(s) + access-pattern
<Code> block.
## Properties H2 with a 3-column table Property | Description | Type (combined instance and static properties; note "Accessed on the class" in the Description cell for static properties).
- Hidden tables for any types referenced by Properties, placed inline immediately after the section that first references them. Never collect hidden tables at the bottom.
- One H2 per method, in roughly the order users discover them (constructor → setup → queries → teardown).
- Per-method shape:
- H2 heading using a descriptive verb phrase (
## Create a client transport, ## Subscribe to messages, ## Get message history), not the bare method name. The <a id="..."/> anchor carries the programmatic name.
<MethodSignature>{signature}</MethodSignature> immediately under the heading. The template-literal form ({ … }) is required when the signature contains < or > (most signatures do — generics, Promise<T>, etc.). Bare form only for signatures without angle brackets.
- Multi-line description.
<Code> usage example.
### Parameters <a id="{slug}-params"/> H3 (only if the method has parameters), with a 4-column Parameter | Required | Description | Type table.
### Returns <a id="{slug}-returns"/> H3, with the backticked return type then one prose sentence. Skip entirely if the method returns void / Unit.
- Hidden tables for types referenced by each method, inline next to that method.
- Visible H2 sections only for the four cases in "Visible vs hidden type tables" below.
- End with
## Example showing end-to-end usage. Required on every per-interface page.
Anchor convention
Anchors are shared across languages (even though the product may be JS-only today). The rule holds so future language additions can share anchors and cross-language deep links work.
- Method anchor = lowercase kebab-case of the JS method name.
subscribeAll → #subscribe-all. batchPublish → #batch-publish. Single-word names just lowercase: publish → #publish.
- If a non-JS SDK renames the method, the heading body uses the language's native name, but the
<a id="..."/> value stays the JS-canonical kebab anchor.
- Sub-section anchors under a method:
{method-slug}-params, {method-slug}-returns.
- Helper-method anchors on a returned/nested type:
{TypeName}-{method}, e.g. PaginatedResult-hasNext.
- Properties don't get individual anchors (they live inside the Properties table). If a specific property genuinely needs to be linked from elsewhere, give it its own H3 with an anchor.
Visible vs hidden type tables
Three table forms:
<Table id='X'> — visible inline render. Also registers for nesting.
<Table id='X' hidden> — registered only; renders nothing until referenced.
<Table id='X'/> — self-closing reference; renders an expandable inline pointer to a registered X table.
Default to hidden. Type definitions live as <Table id='X' hidden> and are referenced inline via <Table id='X'/> from the Type column of the table that first uses them.
Promote a type to visible only if at least one applies:
- Canonical error type (
ErrorInfo or equivalent). Visible H2 on the page that's the error type's home; lowercase anchor (errorinfo). Every method's Returns links to it cross-page.
- Type has methods that need their own anchors. Three sub-patterns:
- Pattern A — child of Returns: when returned by exactly one method on the page. Type lives under that method's
### Returns. Two variants:
- A.1: Intermediate
#### TypeName <a id="TypeName"/> heading, properties table, then methods as ##### {Method name} H5s with <MethodSignature> and short description. Use when the type has properties or 3+ methods.
- A.2: No intermediate type heading; methods sit as
#### {Method name} H4s directly under ### Returns. Use only when the type has no properties and 1–2 self-explanatory methods.
- Pattern B — standalone H2: when the type is returned by multiple methods, or is a first-class named type.
## TypeName <a id="TypeName"/> with ### Properties and ### {method} H3s anchored TypeName-method. The ### Properties H3 heading is required whenever the type has methods.
- Pattern C — per-method duplication: when two methods return the same single-method type. Each method's Returns duplicates the type docs with collision-avoiding anchors.
- Return type with no Type-column home, exactly one such table. Use the "visible-without-H2" pattern: a visible
<Table id='X'> directly inside the relevant ### Returns subsection, no H2 heading.
- Pragmatic size exception (e.g.
ClientOptions with 30+ properties). Visible H2 once on the client page; referenced elsewhere via inline <Table id='X'/> when the type is also hidden-duplicated on the other page.
Everything else is hidden.
Cross-page type sharing: duplicate, don't link
For a type referenced from a page other than its owner, duplicate the hidden table definition on the consuming page. Do not cross-page-link to a hidden anchor — it has no DOM target. Pay the drift cost in exchange for self-contained pages.
Prose conventions
- Don't write same-page markdown anchor links to type anchors (
[X](#X)). Backtick the type name (`ChannelStateChange`) — readers find the inline-expandable definition via the Type column.
- Cross-page link to the type's own page (not anchor) when the type has its own page:
[Message](/docs/{product}/api/{language}/message).
- Cross-page link to the canonical error type uses the lowercase anchor:
[ErrorInfo](/docs/{product}/api/{language}/errors#errorinfo).
- Returns prose pattern: backticked type then one sentence linking the error case. Example: "
Promise<Message>. Returns a promise. The promise is fulfilled with the sent Message, or rejected with an ErrorInfo object."
- Backtick type names whenever they appear in prose. Don't make them links unless the link points to the type's own page.
Table conventions
| Table purpose | Columns |
|---|
| Properties on an interface/class/hook | Property | Description | Type (3 cols) |
| Method or hook parameters | Parameter | Required | Description | Type (4 cols) |
| Enum / status values | Value | Description (2 cols) |
| Hook return objects | Property | Description | Type (3 cols) |
Table IDs are PascalCase and describe the content: {InterfaceName}Properties for the main properties table, {MethodSlug}Parameters for method params, {TypeName} for nested type definitions (no suffix). Every <Table id='X'> must be unique within a page.
React hook pages
React hook pages use a distinct sub-template. One page per hook (e.g. api/react/use-view.mdx), plus a providers.mdx for the providers (TransportProvider, ChatTransportProvider). react/ is a sibling of javascript/ under api/, not nested inside it — the canonical layout is api/{javascript,react,kotlin,...}/.
Each per-hook page shape:
- Opening prose + access-pattern
<Code> block.
- Provider-prerequisite note immediately after the access block: "This hook must be used within a
TransportProvider."
## Parameters H2 right after the intro, documenting the hook's options object (4-column table).
- Single top-level
## Returns H2 with a 3-column property table for the return object.
- One H2 per function or value in the return object, with
<MethodSignature> describing the function shape.
## Example at the bottom.
Code fence is react, not tsx or jsx.
Hooks don't have a "Properties" section — they don't have properties. The ## Parameters and ## Returns sections at the top replace it.
React-specific shared types (like ErrorInfo) live on the relevant non-hook page. Reference via cross-page link.
Client / constructor pages
Same skeleton as other API reference pages, with these specifics:
- Intro + access example.
## Properties H2 (combined instance and static properties; "Accessed on the class" for static).
- Constructor as
## Create a new {client} <a id="constructor"/> with <MethodSignature> per overload. For Kotlin / Swift factory functions, write "factory function" rather than "constructor".
ClientOptions (or equivalent) as pragmatic-size visible H2 if large. Place after the constructor.
- Hidden tables for nested types referenced by
ClientOptions, inline below it. If ClientOptions references types defined on another page, duplicate them as hidden tables here.
- Other return types from methods on this page use the visible-without-H2 under Returns pattern where the type has no natural Type-column home.
## Example at the bottom.
Mandatory audit before review
Every hidden table must have at least one inline reference on the same page, and every inline reference must have a matching definition. A simple Node script catches both:
node -e "
const fs = require('fs'), path = require('path');
const dir = 'src/pages/docs/{product}/api/{language}';
for (const f of fs.readdirSync(dir)) {
const content = fs.readFileSync(path.join(dir, f), 'utf8');
const hidden = [...content.matchAll(/<Table id='([^']+)' hidden>/g)].map(m => m[1]);
const visible = [...content.matchAll(/<Table id='([^']+)'>/g)].map(m => m[1]);
const refs = [...content.matchAll(/<Table id='([^']+)'\\/>/g)].map(m => m[1]);
for (const h of hidden) if (!refs.includes(h)) console.log(f + ': hidden ' + h + ' unreferenced');
for (const r of refs) if (!hidden.includes(r) && !visible.includes(r)) console.log(f + ': ' + r + ' undefined');
}"
Expected: zero output. A hidden table with no inline reference renders nothing. A reference with no definition is a silent broken link.
Common pitfalls
- Promoting types to visible because something happened to need a link. Fix the prose first (drop the link, use backticks). Only promote when a criterion in "Visible vs hidden type tables" applies.
- Forgetting to duplicate cross-page-referenced types. A hidden type on page A needed inline on page B must be defined as a hidden table on both pages.
- Per-language anchors instead of shared. Use the JS-canonical kebab anchor on every language page, even when the method has a different native name.
- Table ID collisions. Every
<Table id='X'> must be unique within a page.
- Markdown links to hidden table IDs. They produce a text link with no DOM anchor. Rewrite to backticks or page-level links.
- Skipping heading levels (no H2 → H4) breaks the visual hierarchy and the auto-generated page nav.
Navigation pattern
Multi-language API references use a per-language sub-tree in the nav (JavaScript, React, Kotlin) with plain { name, link } entries. Do not add a languages: [...] array — those are only used by the legacy multi-language section. For products with multiple SDK variants (Realtime vs REST), the nav uses JavaScript (Realtime), JavaScript (REST), etc. as the sub-tree labels.
Code accuracy: verify against the SDK, not against prior docs
Prior docs drift. The SDK is the source of truth.
Workflow before writing any code snippet
- Find the SDK version this docs page targets in
src/data/languages/languageData.ts. That file is the canonical version registry, keyed by product and language. Use that exact version, not whatever npm currently labels latest. The page header reads from the same registry, so the code samples and the version badge stay aligned.
- Pull the package at that version:
npm pack <package>@<version> into a temp dir, extract, read the .d.ts files.
- Cross-check the signatures, option shapes, and return types your snippet uses.
- Where the SDK exposes both a core and a framework-bound entry point (for example
@ably/ai-transport and @ably/ai-transport/vercel), pick the entry point your snippet's audience uses and follow its option shape.
- After drafting, re-grep your file for any old patterns the SDK no longer supports.
Documenting a pre-release SDK
If the docs page targets an SDK version that is not yet on npm:
- Bump the version in
src/data/languages/languageData.ts to the upcoming release.
- Read the
.d.ts files straight from the SDK repo. Check out the matching git tag or release branch (or use a local checkout if one is on disk), then read dist/ or src/.
- Write the docs against that source.
- Flag the PR as gated on the SDK release. Merge only once the SDK version exists on npm and
languageData.ts matches.
Common drift patterns to check
Whenever an SDK has had a major refactor, sweep the docs for these patterns:
- Renamed factories (for example
createTransport → createClientTransport).
- Hooks that changed from factory to context reader (for example
useClientTransport going from (options) => Transport to (options) => { transport, transportError }).
- Methods that moved between objects (for example
transport.send → view.send).
- Removed convenience hooks (for example
useEdit/useRegenerate/useSend consolidated into methods on a ViewHandle returned by useView).
- Renamed option keys (for example
id → chatId).
- Renamed enum/literal values (for example
TurnEndReason going from 'completed' | 'failed' | 'cancelled' to 'complete' | 'error' | 'cancelled').
Writing standards
The canonical source is writing-style-guide.md. You must read it before making any changes to the docs. It covers International English, present tense, active voice, second person, imperative headings, sentence case, banned patterns (em dashes, bold-prefix bullets, Latin abbreviations, subjective phrases, vague modals, slang, "we" as subject, pricing detail, API keys in client code), the Layer-0 hook pattern, MDX asides, AI-fingerprint patterns to strip from drafts, and the counter-rules to keep.
The style guide takes precedence over anything else in this skill. If a rule here ever conflicts with the style guide, the style guide wins.
Spelling to enforce on every page:
- "realtime", one word. Never "real time" or "real-time" when describing Ably delivery ("sees the same conversation in realtime", "realtime messaging"). This is Ably house spelling. Sweep with
grep -rn "real[- ]time" "$P" and expect zero hits.
Per-page workflow
- Identify the page type. Pick the matching template above.
- Draft the prose against the template. Keep it tight.
- Verify every code snippet against the actual SDK. See the code-accuracy section.
- Strip AI-writing patterns. Re-read against the "Avoid AI-generated content fingerprints" section of
writing-style-guide.md.
- Self-check against the verification greps below.
- Open the PR. Reviewer (not the writer) reviews against this skill and the writing-style guide.
Verification (run from the repo root before review)
These greps cover the standards. Replace <your-path> with the page or section you wrote. The expected result is zero hits on each.
P=<your-path>
for field in title meta_description meta_keywords intro; do
echo "Pages missing $field:"
find "$P" -name "*.mdx" -exec grep -L "^$field:" {} \;
done
grep -rn -E "key\s*:\s*['\"][^'\"]+['\"]" "$P" | grep -v authUrl
grep -rni "under construction\|coming soon\|todo:\|wip\b" "$P"
for f in "$P"/*.mdx; do
total=$(grep -c "^## " "$f")
anchored=$(grep -cE '^## .* <a id="[^"]+"' "$f")
[ "$total" != "$anchored" ] && echo "$f: $anchored/$total H2s anchored"
done
grep -rn "—" "$P"
grep -rn "real[- ]time" "$P"
grep -rnE "^\s*[\*\-]\s+\*\*[^*]+:\*\*" "$P"
grep -rnE "\bwe\b" "$P" | grep -v "code\|//"
grep -rnE "\b(e\.g\.|i\.e\.|etc\.)" "$P"
grep -rniE "easily|simple to|it's as easy|fire up" "$P"
grep -rn "OLD_PATH" "$P" | grep -v redirect_from
for f in "$P"/*.mdx; do
count=$(awk '/^## FAQ/,/^## /' "$f" | grep -c "^### ")
echo "$f: $count FAQ entries"
done
For API reference pages only, run these additional checks:
A=<api-ref-path>
for f in "$A"/*.mdx; do
case "$(basename $f)" in
index.mdx|errors.mdx) continue ;;
esac
if grep -q "^intro:" "$f"; then
echo "$f: per-interface API ref page should not have intro:"
fi
done
node -e "
const fs = require('fs'), path = require('path');
const dir = process.env.A;
for (const f of fs.readdirSync(dir)) {
if (!f.endsWith('.mdx')) continue;
const content = fs.readFileSync(path.join(dir, f), 'utf8');
const hidden = [...content.matchAll(/<Table id='([^']+)' hidden>/g)].map(m => m[1]);
const visible = [...content.matchAll(/<Table id='([^']+)'>/g)].map(m => m[1]);
const refs = [...content.matchAll(/<Table id='([^']+)'\\/>/g)].map(m => m[1]);
for (const h of hidden) if (!refs.includes(h)) console.log(f + ': hidden ' + h + ' unreferenced');
for (const r of refs) if (!hidden.includes(r) && !visible.includes(r)) console.log(f + ': ' + r + ' undefined');
}" 2>/dev/null
grep -rnE "^## [a-z][a-zA-Z0-9]*\s*<" "$A"
grep -rn "ErrorInfo" "$A" | grep -v "errors#errorinfo" | grep -v errors.mdx
Reviewer checklist
When reviewing someone else's docs PR (or your own at PR time), walk this list. Each item is a yes/no check.
Page shape
- The page type is identifiable from the structure (feature / concept / framework / positioning / getting-started / guide / troubleshooting / API reference / internals).
- Layer-0 hook present and not Mad Libs.
intro frontmatter present and matches the page-header sentence — except on per-interface API reference pages, which drop intro:.
- Section order matches the template for this page type.
- Edge cases / unhappy paths section present (feature pages).
- FAQ has three to five real questions (feature pages).
- Concept page shows at most one code-proof sample and does not drift into API teaching.
- Visual rhythm — diagram, code, or card every few paragraphs. No walls of text.
Writing standards
- Page complies with the rules in
writing-style-guide.md. Use the verification greps above to spot the common violations (em dashes, bold-prefix bullets, Latin abbreviations, subjective phrases, vague modals, "we" as the subject, AI-fingerprint patterns).
- Author ran
/deslop (or equivalent AI-pattern check).
Code accuracy
- Every code snippet matches the actual SDK at the latest tagged release (factory names, option shapes, return shapes, method locations, enum values).
- No API keys in client code.
authUrl: '/auth' used for the placeholder where auth is referenced.
Mechanics
- Frontmatter complete:
title, meta_description, meta_keywords, intro, redirect_from where the page moved or merged. (Per-interface API reference pages omit intro.)
- Every H2 carries an inline anchor —
## Heading <a id="slug"/>, per the CLAUDE.md convention. Use the H2-anchor grep above.
- Section headings are descriptive imperatives or noun phrases tied to the specific content, not template labels copied verbatim (no
## The model, ## The problem X solves, ## Code proof).
- New page added to the relevant nav file under
src/data/nav/.
<Aside> used sparingly and load-bearing. No decorative asides.
- Internal links resolve. No references to URLs that only exist in
redirect_from frontmatter.
API reference pages only (additional checks)
- Method H2s use descriptive verb phrases (
## Create a client transport), not bare method names. Anchor is JS-canonical kebab-case.
- Every method H2 is followed by
<MethodSignature>{ … }</MethodSignature> (template-literal form for signatures with < or >).
- Every method with parameters has
### Parameters <a id="{slug}-params"/> with a 4-column Parameter | Required | Description | Type table.
- Every method has
### Returns <a id="{slug}-returns"/> unless it returns void/Unit.
- Type tables default to hidden (
<Table id='X' hidden>) referenced via <Table id='X'/>. Visible types only when they meet one of the four promotion criteria.
- Hidden table audit passes. Every hidden has at least one inline reference; every reference has a definition.
- Error type referenced via cross-page link to its canonical home (lowercase
#errorinfo anchor).
- Page ends with
## Example showing end-to-end usage.
- No same-page markdown anchor links to type anchors (
[X](#X)). Type names in prose are backticked; cross-page references go to the type's own page.
When the SDK or product changes shape
When a major SDK change lands (renamed factories, moved methods, removed hooks, changed enum values), do a sweep across the relevant docs section using the patterns in the code-accuracy section above. Drift is the single biggest source of incorrect docs.