| name | technical-writer |
| description | Write or edit pages on the Drash documentation site (Nextra 4 + MDX under docs/). Use when adding a page, restructuring the sidebar, editing docs copy, or reviewing documentation for accuracy and voice. |
Technical Writer
Documentation for Drash lives in docs/, a Nextra 4 site built with Next.js and
exported statically. Follow this when writing or editing any page under
docs/content/.
Voice
- Concrete over persuasive. Name the mechanism, not the feeling. "A strict
resource interface forces separation of concerns" beats "clean, maintainable
architecture."
- No filler. Cut "powerful", "seamless", "simply", "just", "easily". If a
sentence survives deleting the adjective, delete it.
- Concede real limits. A stated trade-off is more convincing than an
unqualified claim. Saying the only lock-in is your runtime's is stronger than
claiming there is none.
- Second person for instructions, present tense for behaviour. "You hand the
application a request." "The application rejects on error."
Assume the reader knows nothing
Write for someone meeting the subject for the first time. They have not read the
other pages, they do not know Drash's vocabulary, and they will not infer what
you left out — they will leave.
- Define a term the first time a page uses it, or link to where it is
defined. "Resource", "handler", "chain", "middleware", "entry point", and
"resource group" are Drash words, not English ones. A page may use them freely
after it has said what they mean.
- Say what a thing does before naming it.
ResourcesIndex and
AbstractChainBuilder are labels for machinery the reader has never seen.
"The handler that matches a request URL to a resource (ResourcesIndex)"
teaches; "ResourcesIndex caches by fully-qualified URL" does not.
- Never write "as described above" across pages. Each page is someone's
first. Link instead — the reader chooses whether to follow it.
- Show the whole thing, then take it apart. A complete, runnable sample
first; the explanation of each piece after. A reader who cannot see the shape
of the finished thing cannot place the parts.
- Spell out the commands. Do not assume the reader knows the package
manager, the runtime's flags, or that
--allow-net is why the server can
listen. Show the command that works and say what it needs.
- State the prerequisite instead of implying it. If a step only works with a
runtime installed or a file already created, say so at that step.
- "Simply", "just", "obviously", "of course" are banned — the No filler rule
covers them, but the reason here is different. To a reader who is stuck, those
words say the difficulty is theirs.
The test: could someone who has never used Drash read this page start to finish
and end up with something that runs, without needing a tab you did not link?
Verify before asserting
Docs that describe code must be checked against the code.
- API surface: read
src/ — do not infer from another doc page. Entry-point
exports live in src/modules/http.native.ts and http.polyfill.ts; the chain
builder in src/modules/builders/RequestChainBuilder.ts.
- Versions: take them from CI (
.github/workflows/*.yml) or package.json.
If a number cannot be sourced, say what is tested rather than inventing a
minimum.
- The published package lags the source. Docs show the source API:
@drashland/drash/modules/http.{native,polyfill}.js, exporting Application.
The last published build still uses the older modules/chains/RequestChain/mod.*
path and exports Chain, so anything resolving against npm or esm.sh — the apps
under examples/ — will not match the docs until the next release. Write the
source API and keep a page internally consistent.
- Error messages: quote them from source, not memory.
One fact, one page
The site splits by intent. Putting the same explanation in two places means both
rot.
| Section | Holds |
|---|
content/docs/concepts/ | Why it works that way |
content/docs/quickstart/ | A complete app per runtime |
content/docs/*.mdx | A single top-level subject (Error Handling) |
content/reference/ | API surface, by Core / Standard / Modules |
content/examples/ | Finished apps you can run |
Cross-link instead of repeating. When merging pages, delete the duplicate rather
than keeping both phrasings.
Nextra mechanics
Components are global — no imports needed. mdx-components.js injects
Callout, Cards, Compare, CompareItem, FileTree, SeeAlso, Steps,
Tabs, and the table elements.
<Callout type="info" | "warning" | "gray"> — gray is a local component.
<Cards> — title-only on this site. Passing children switches the card to
a filled style and breaks visual consistency with every other card.
<Tabs items={[…]}> — put shared headings outside the group. A ### inside
four tabs produces four TOC entries (#steps, #steps-1, …). Two groups can
be synced with a matching storageKey, but one group is better when the
reader should choose once.
_meta.js controls order and labels. Keys are file basenames; order in the
file is order in the sidebar.
- A folder that appears first in its parent
_meta.js must contain an
index.mdx. The breadcrumb resolves to the folder's own route, and a folder
with no index 404s.
display: "hidden" keeps a page out of the sidebar; omit type to keep it out
of the top bar too.
- Use
"quoted-keys" for hyphenated basenames, matching the other _meta.js
files.
Code fences support filename=, showLineNumbers, and highlight ranges:
```ts filename="app.ts" showLineNumbers {2,13-17}
Inside a list item, indent the fence 4 spaces so it stays in the item. When a
step adds to the previous one, highlight only the added lines. Never delete
explanatory comments from a sample to make it shorter, and keep trailing comments
aligned to one column per block.
Links
- Site-relative, no origin:
/docs/concepts/chains.
- Verify the route exists. Folder routes without an
index.mdx are not
pages — /reference/core 404s while /reference/core/http-error works.
- Anchors are slugified with punctuation stripped:
.urlPatternClass(x) becomes
#urlpatternclassurlpatternclass. Confirm the id in the built HTML.
- There are no redirects. The site is
output: "export", so renaming or
moving a page breaks every old URL. When moving one, rewrite every inbound
link in the same change.
Before calling it done
cd docs && pnpm build
deno fmt --check && deno lint
deno fmt does not cover .mdx, so table alignment there is cosmetic only.
Then run the link check over the built output — it catches the moved-page and
missing-index cases that a grep will not:
cd docs && python3 - <<'PY'
import re, os, glob
def exists(p):
p = p.split('#')[0].split('?')[0]; r = p.strip('/')
if r == '': return os.path.isfile('out/index.html')
return any(os.path.isfile(c) for c in
(f'out/{r}/index.html', f'out/{r}', f'out/{r}.html'))
broken, total = {}, 0
for f in glob.glob('out/**/*.html', recursive=True):
h = open(f, encoding='utf-8', errors='ignore').read()
for href in set(re.findall(r'(?<!data-)href="(/[^"]*)"', h)):
if href.startswith('//') or href.startswith('/_next'): continue
total += 1
if not exists(href): broken.setdefault(href, set()).add(f)
print(f"links={total}", "-> 0 broken" if not broken
else "-> BROKEN: " + str({k: len(v) for k, v in broken.items()}))
PY
Report the counts. A page that builds is not a page that works.