Hyperlinkv0.8.0-beta.28

Documentation

Three kinds of documentation, three different jobs — and each has a shape worth holding to:

  • Doc comments on the public surface — what a symbol is, for a reader on hover or in the API docs.

  • Inline comments — the why behind a line that a competent junior dev couldnt infer from the code.

  • Narrative docs — the guides and reference (pages like this one) and the handoffs that teach the whole picture.

Doc comments

A doc comment has a fixed shape

src

Every exported symbol opens with a /** … */ doc comment in one order: a one-sentence summary (imperative — what it does and why, never a restatement of the signature), then any detail paragraphs, then an @example, then the marker tags. Describe parameters and the return in prose — this codebase does not use @param / @returns; they duplicate the types and rot as signatures change.

/**
 * Wire a served resource `tag` to a remote over http and get a ready client `Layer` — {@link client}
 * plus the batteries-included transport, bundled. `target` is a port (`3009` → `localhost:3009/rpc`)
 * for a runtime on the same machine, or a full url for one across the network.
 *
 * @example
 * Effect.provide(program, Hyperlink.clientHttp(Emails, 3001))
 *
 * @public
 */
export const clientHttp = /* … */

@example code is real code

srcexamples

An @example compiles against the actual API — real symbol names, real signatures, the call written the way a caller would write it. An example that wouldnt type-check is worse than none: it teaches the wrong shape. Lift it from a passing test, or verify it before committing.

// ✅ good — the real call
/** @example const total = yield* combineQuery(peers, (p) => p.size, Combine.sum) */

// ❌ bad — invented API that doesn't exist
/** @example const total = peers.sumBy("size") */

Mark the surface with @public / @internal / @module

src

Every export carries exactly one visibility marker: an app-facing symbol is @public; a package-only one is @internal. A module opens with a @module overview whose name matches the files primary export, so a reader lands with context.

/**
 * The queue worker namespace — Tag, make, layer, serve.
 * @module QueueHyperlink
 */

/** @public */
export const layer = /* … */

/** @internal */
export const makeQueueEffect = /* … */

No @since until 1.0

src

The surface is fluid (see Breaking Changes & Stability) — nothing has a stable version yet, so a pre-1.0 @since claims a release that doesnt exist and reads as a lock the owner never made. Never add @since while pre-1.0. At the 1.0 release, every @public symbol gains @since <release> in one motion, alongside @locked. Until then, a doc comment ends at its visibility marker.

/**
 * …summary…
 * @public
 */

Inline comments

Comment what a junior dev couldnt infer

srcexamples

An inline comment carries what the code cant show a competent-but-junior reader: why this line, not what it does. A type-level trick, an Effect layer-ordering constraint, runtime ownership, a timing subtlety, a non-obvious workaround. If a reader who knows the language would ask wait, why?, answer it. If they wouldnt, stay silent — never narrate what the code already says.

// ✅ good — answers the "why?" a junior would have
// provideMerge, not provide: a bare provide prunes the serve layers off httpServer
const node = Node.httpServer([Counter.serve]).pipe(Layer.provideMerge(deps))

// ❌ bad — restates the obvious
const total = a + b // add a and b

Narrative docs

Narrative pages compound and use a controlled vocabulary. The named goals below are the review language — cite them by name (this violates No Simply). Accuracy, runnable examples, and knowing which mode a page is in are assumed. The meta-lesson: docs compound only as cleanly as the system they describe; incoherent design forces re-teaching no craft can fix.

Existing operational rules (show / verify / handoff / glossary / capitalize) stay below — these goals sit alongside them, not in place of them.

The Spine

docs

Hang nearly every page off one load-bearing mental model, stated up front. New material is a facet of that spine — recontextualization, not a pile of independent topics. Error handling is what that one channel does, not a new chapter identity.

For this book the spine is the Hyperlink: a Tag carrying a Contract, fulfilled by an Implementation, placed by a Layer, used through a Handle that reads the same locally or across a network. If a feature cannot be phrased as a facet of that spine, either the framing is wrong or the feature sits off-model — and both are worth knowing.

Make the Invisible Visible

docs

Composition shown beats composition described. Find the dynamic the static medium hides — type evolution under combinators, a Handle that stays the same while the Layer changes, work moving through a queue — invent one consistent device to render it, and use that device everywhere the dynamic appears. Readers learn the device once; then it compounds across pages. Prefer three short frames (or three annotated fences) over a paragraph that narrates motion.

In this book the default device is the Twoslash fence with a ---cut--- preamble and inline type comments on the Handle. Stay with that device unless a page has a stronger domain-specific one, and then use that one consistently on that subject.

Earn the Abstraction

docs

Lead with the wall the reader will hit — the try/catch, the hand-rolled client, the untyped Promise — then present the abstraction as the release of pressure they already feel. An unmotivated abstraction reads as ceremony; a motivated one reads as inevitable. This matters most for the best features, where the temptation to open with the fireworks is strongest.

Naming Is Pedagogy

docs

One name per concept, identical in code and prose. Where naming is inconsistent the docs are forced to re-teach and compounding collapses. Treat the vocabulary as a fixed ledger (see The glossary defines what the API docs cant and Capitalize the domain terms). If a page has to stop and disambiguate two names for one thing, that is an API smell surfacing as a docs problem. Fix it upstream.

Minimal Deltas

docs

Each example isolates exactly the one concept the page is teaching, presented as the smallest change from the prior fence — not a fresh full program the reader must diff mentally. A sequence of small deltas teaches the axis of variation; three complete dumps teach less. Prefer ---cut--- / repeated preambles over restarting from imports every time. Deltas over dumps.

Present-Tense, Imperative, Affirmative

docs

Describe behavior in present tense — the app opens, not will open — so the system reads as a stable set of facts. Address the reader directly and tell them to act: Tap Settings, not the user should navigate to. Prefer the affirmative: enter eight or more characters beats dont use fewer than eight. Active voice throughout, with the actor named.

One Door for Tasks, a Staircase to the Machinery

docs

Tutorials and how-tos stay clean of internals — the reader accomplishes the task without learning the mechanism. Explanation and architecture pages carry the mechanism, clearly labeled, because for hard features how does this even work is load-bearing for trust. One front door for tasks; a well-marked staircase downstairs. Do not force the task-doer downstairs, and do not leave the curious reader without a staircase. Link the staircase; do not dump it in the hallway.

No Simply

docs

Ban simply, just, and obviously as hedges that imply the reader is slow when they struggle. For anything conceptually heavy this is corrosive: the concept is hard, and pretending otherwise breaks trust the moment reality disagrees. State the hard thing plainly, in a confident unhedged tone. No apologizing, no padding. Calm authority. (Complements Show, dont tell.)

Sharp Edges, In Place

docs

Document failure modes on the page for the feature that has them, not in a gotchas appendix. A happy-path-only doc set is a trap that costs credibility on the pages where credibility matters most. Honesty about limitations on the hard features earns trust for the easy ones.

No Em Dash

docs

Do not use the em dash (—) or en dash (–) in prose. Prefer a period, comma, colon, parentheses, or a full rewrite of the clause. Do not character-swap an em dash for a hyphen and call it done; rewrite so the pause is unnecessary. Hyphens in compound modifiers (cross-runtime) and in code remain fine. Literal dashes inside code, regexes, or Unicode escapes are exempt.

No AI Fingerprints

docs

Cut patterns that read as machine-smoothed copy rather than a person making choices:

  • Bold-prefix bullets (**Feature:** description)

  • Corrective antithesis (Not X. But Y.)

  • Dramatic pivots (Heres the catch, But heres the thing)

  • Throat-clearing (Lets dive in, In this guide well)

  • Gift-wrapped endings and mid-page scaffolds (In summary, Putting it together)

  • Soft hedges and enthusiasm padding (worth noting, really shines, ensures)

  • Stacks of same-length punch sentences with no rhythm variation

State the point. Vary sentence length. Name concrete actors and verbs. Complements No Simply and Show, dont tell.

Show, dont tell

docs

A guide earns trust with real, working code a reader can run — not adjectives about how clean or powerful the thing is. Lead with the code doing the job and let it carry the claim; cut effortless, elegant, simply. If a sentence would survive being replaced by a code block, replace it.

// ✅ good — the feature, shown
const worker = QueueHyperlink.serve(Emails, { effect: sendEmail }).pipe(nodeServer(3001))

// ❌ bad — telling, not showing
// "hyperlink-ts makes serving a queue across runtimes effortless and elegant."

Code in prose is verified, like any example

docs

A snippet in a guide is held to the same bar as an @example: it compiles against the real API before it ships. A reader will copy it verbatim — a snippet that doesnt type-check teaches the wrong shape and burns the trust the guide is built on. (How we standardize examples end to end is still being settled — see the note below.)

A handoff is self-contained requirements for its reader

docs

A handoff is written for the person who will do the work, not as a first-person letter about what you did. State what they must build and know — paths, constraints, the real gotchas, the acceptance bar — so they never have to reconstruct your session to act. If it only makes sense to someone who was there, it isnt a handoff.

The glossary defines what the API docs cant

docs

The API reference documents symbols — every exported function and type. The glossary documents concepts: Tag, Service, Contract, cross-runtime service — the vocabulary a reader needs that no single export names. Define each such term once, in the glossary, and link to it (/docs/glossary#term) the first time it matters on a page. A term a doc comment already defines belongs in the API docs, not here.

Capitalize the domain terms

docs

The toolkits concepts are proper terms — Tag, Service, Contract, Hyperlink, Layer, Handle, Node, Implementation — and read as such: capitalized, so a Tag (the concept) is distinct from the ordinary word. The glossary is the list of what counts; a lowercase tag in prose reads as a mistake. Where a word is genuinely generic — reach it through an HTTP client — leave it be; capitalize the term when it names the concept, not every time the letters appear.

Authoring Djot (prototype)

Prototype — provisional. The authoring format below is documented as it stands today. LSP support for the docs is coming and will change it — read this as a description of the current shape, not a locked contract. It firms up into enforced rules once the LSP lands.

Our docs are Djot, and the format doubles as data — the standards manifest is parsed straight from the blocks, so a page is both prose and a machine contract. The conventions today:

  • Page block. Every page opens with {#id title="…"} on the line above the H1. id matches the slug and the filename; title is the single source — nav and the manifest derive it, never repeat it.

  • Rule block. A standard is {#id .severity appliesTo="…"} followed by an H2 whose text is the rules title. severity is a closed set: must / should / may.

  • appliesTo is a rule concern. It scopes a rule to code trees — src / examples / test / docs / process, with all = src examples test, space-joined for multiples. It belongs on rule blocks only; on a page block the parser discards it (see rough edges).

  • Fenced code names its language — the opening fence is three backticks, a space, then the language (ts, bash, …).

  • {.note} marks a callout, like this one.

  • Draft status (content-side). Live book pages that are not Standards and not the Introduction open with status="draft" until tip-stable. The Glossary page is draft, and every glossary term is marked with a {.draft} callout above its heading. Standards and the Introduction omit status="draft" (they are tip guidance / book home). Optional done="…" is a space-joined checklist of what has been tip-checked (api, previews, types, verified). When a page is ported from docs/legacy/**, keep status="draft" and put a {.draft} callout immediately under the H1 until a tip-check clears it:

    {.draft}
    **Draft** — ported from the pre-site corpus; tip-check before treating as SSOT.

    After tip-check: remove the {.draft} callout; set done= honestly. Do not invent site CSS / nav badges for Draft — that is lettered-agent / Agent B work (see handoff Phase 3).

Known rough edges — for the LSP work to resolve, not to hand-fix now: - order=N on standards pages duplicates nav.tss ordering — two sources for one fact. - Page-level appliesTo is parsed-but-discarded, yet still written on every page (including the non-rule guides, which have no rules to scope) — dead metadata that reads as load-bearing.

Examples are an open question. How the real examples/ apps are sourced, kept building, and used as the canonical demos is still being decided — a separate discussion from this chapter.

Edit this page on GitHub