Offline docs bundle
The whole documentation corpus as one versioned JSON file, for agents and tools that run without network access.
Overview
Not every assistant has a network. Some run inside a locked-down build, some inside a hospital network that does not resolve arbitrary hostnames, and some inside a container with egress disabled on purpose. Those are the environments where a health design system is most likely to be used and least likely to be reachable.
/r/docs.json is one request that returns as much of the corpus as fits inside
a size budget: the processed markdown of each page it carries, that page's
frontmatter, and the docs version identifying the build it came from. It is
capped, and today it is truncated. The response says so about itself. total,
included, omitted, truncated and truncationNote are top-level fields,
and reading them is the difference between vendoring a corpus and vendoring a
portion of one. Fetch the shards for the sections it dropped.
curl -s https://opsinjs.pensievelabs.org/r/docs.json -o opsinjs-docs.jsonWhat is in it
A wrapper of build and budget metadata, then one entry per page carrying the fields a machine needs in order to decide whether that page answers the question in front of it.
{
name: string
version: string // the docs version this bundle was built from
generator: string
docsVersion: string // the same version, under the name the header uses
generatedAt: string // ISO timestamp of the build
site: string
implemented: boolean // whether any component has source
implementedCount: number
notice: string // the one paragraph a machine must not skip
homepage: string
format: "processed-markdown"
characters: number // the page text carried, in JS characters. The
// budget is applied in that same unit
total: number // pages in the corpus
included: number // pages in this response
omitted: number // pages the budget dropped
truncated: boolean
truncationNote?: string // present only when truncated
sections: Array<{…}> // the sixteen sidebar groups
shards: Array<{…}> // where to fetch what was dropped
componentCounts: Record<string, number> // component pages per phase,
// of the included pages
corpusComponentCounts: Record<string, number> // the same, over the whole
// corpus
conventions: {…} // the twin, catalog, roster and search URLs
pages: Array<{
url: string // canonical page URL
markdownUrl: string // the same page's .md twin
slug: string[]
section: string
title: string
description: string
status?: string // planned | shipped | deprecated. Component pages
// only; absent on every other kind
kind: string // component | health | foundation | …
evidence?: string // health pages declare this
aliases: string[]
implements: string[]
governedBy: string[]
usedIn: string[]
reviewed?: string
markdown: string // processed markdown; documentation components
// arrive as <PascalCase … /> tags
}>
}The page body is markdown, not content, and markdown is the same processed
text the .md twins serve. "Processed" is narrower than it sounds: remark has
run, so imports are gone and tables and code blocks are markdown, but the
documentation components are not rendered. <ComponentPreview name="range-bar" />
arrives as that tag and its attributes are the content. See
Raw markdown API for what survives and what does not.
A page inside the bundle also omits the per-page block quote that a standalone
twin carries about those tags, so nothing in a page body explains them; this
paragraph is the explanation.
componentCounts tallies the component pages this response carried and
corpusComponentCounts tallies every component page in the corpus. Neither
sums to included or to total, and that is not an off-by-one: a release
phase belongs to a component, so every page that is not a component page
declares none and is counted in neither tally. Read corpusComponentCounts for a question about
opsinjs and componentCounts for a question about this response.
The bundle carries the documentation, not the code. Component source lives in
the registry items. /r/<name>.json is the item for one of the sixty built
components, and the catalog is /r/registry.json. Fetch either separately.
Size and freshness
The bundle is most of a corpus and it is not small. Two habits keep it usable:
Index it, do not paste it. It is meant to be searched, chunked or embedded locally. Putting the entire file in a context window spends the window on the retrieval problem you were trying to avoid. If your tool wants a subset, the shards exist for exactly that and are much cheaper.
Pin the version, then check it. The version field is the point of the
whole file. A vendored bundle is a snapshot, and a snapshot that nobody ever
compares to the live version quietly becomes wrong. A weekly job that fetches
the few bytes of the current version string and warns on a mismatch is enough.
Compare included and total at the same time: the set of pages a snapshot
carries moves as the corpus grows, so two bundles at the same version are the
same text and two at different versions may not even be the same pages.
The most important field on a component page in this bundle is status. A built
component reads shipped and the source is in its registry item, and today that
covers every component. Every opsinjs component has been audited against WCAG 2.2
AA by its own authors (see ADR 0025),
which is not an independent accessibility review, and no component has had a
clinical review. shipped means the source installs, and it does not mean an
independent review or a clinical review has happened. Nothing here is for a
production health surface until a clinician signs it. An agent
working from a stale bundle a year from now must be able to discover that the
world has moved. The docs version is how it finds out, and status is what will
have changed.
Using it in an air-gapped build
Vendor it deliberately
Commit it, or store it as a build artefact with a checksum. A file fetched at build time from a network you do not control is not an offline strategy.
Record where it came from
Keep version and generatedAt visible to whatever consumes the bundle, and
surface them in any answer the tool produces. "According to opsinjs docs version
X" is a checkable claim; "according to opsinjs" is not.
Refresh on a schedule you own
Treat it like a dependency: a deliberate update with a diff, not a silent refresh.
Fail loudly when it is missing
A tool that falls back to the model's general knowledge when the bundle is absent will produce confident, invented answers about a health design system. Prefer an error.
Verify it worked
It parses, and you know what it left out
node -e "const d=require('./opsinjs-docs.json'); console.log(d.version, d.included, 'of', d.total, d.truncated ? '(truncated)' : '')"included below total is the budget, not a corrupt download.
truncationNote says so in a sentence and the shards array is where to fetch
the rest.
A known page is present with its status
Look up a component page and confirm it carries a status, which reads
shipped for a built component. A component page present with no status is a
bug worth reporting, and a page that is not a component carries none by design. A
page absent altogether is usually the cap. Check omitted before reporting
anything.
The page bodies are under markdown
Read pages[0].markdown. It is markdown, and it will contain <PascalCase … />
tags; that is the format rather than a fault. Reaching for pages[0].content
returns undefined, which is the quietest way a vendored reader ends up
indexing an empty corpus.
Troubleshooting
The download is truncated. Check truncated and omitted in the payload
first: the server caps this response on purpose and reports the cap in
truncationNote. A proxy body-size limit is only the likely cause if the JSON
will not parse at all. In that case, prefer a client that streams to disk.
The bundle disagrees with the site. Compare version strings. If they match and the content differs, that is a real bug and worth reporting.
I only need the components. Use /llms-components.txt, which is a fraction
of the size.
I need this in an air-gapped environment where even the initial fetch is impossible. Build it yourself: clone the repository and run the site's own generate step. The bundle is produced from the same source you would be cloning.
Next
- llms.txt and the shards covers smaller, cheaper subsets.
- Machine-readable schemas covers the structured data the bundle does not include.
- Raw markdown API serves one page at a time.
Machine-readable schemas
The catalogue, the token set, the status vocabulary and the glossary as JSON at stable paths, so a tool can resolve a fact instead of parsing prose.
Writing docs for agents
The authoring rules that keep a page legible to a machine, without making it worse for the person who has to read it.