Noctis

Agents

Noctis ships its own documentation inside the package. A coding agent working in node_modules reads the version the app installed, offline, instead of fetching a site that tracks main — and the failures that used to cost the most are the ones it can now see.

Why the package carries its own docs

An agent learning a library reads what is on disk. It opens the import, jumps to the .d.ts, greps node_modules, and reads whatever prose the package ships. Documentation that lives only on a website is documentation it either cannot reach — no network, no permission, a round trip per component — or reaches at the wrong version, because the site describes main while the app has an older release pinned.

So the same pages this site serves are generated into the package at build time. Whatever version an app installs, the docs beside it describe that version.

What ships

PathWhat it is
AGENTS.mdThe rules for consuming the system: imports, the two setup steps, tokens, logical properties, the data-slot seam, composition
agent/llms.txtThe index — every page, with each component's import path and one-line summary
agent/catalog.jsonThe same index, machine-readable: import path, exported symbols, parts, variants, slots, component tokens, and example names for all 55 components
agent/components/<name>.mdOne component's full reference and every worked example
agent/foundations/<name>.mdColour, tokens, layers, theme engine, customization, i18n — and this page
SLOTS.mdThe data-slot catalogue, the stable styling seam
COMPONENTS.mdThe authoring contract, for building components on the same substrate
dist/components/<name>/<name>.cssEach component's own stylesheet, as readable reference

The per-component CSS is deliberately absent from exports: it is there to be read, not imported. @stridge/noctis/styles.css remains the one stylesheet an app loads.

Reading order

The pack rewards reading one file, not all of them. agent/ is roughly 2 MB — loading it wholesale wastes context that the task needs.

  1. AGENTS.md — the rules. Always first, and short enough to read whole.
  2. agent/catalog.json — what exists. Read before building any primitive by hand; 55 components ship, and the easy ones to miss are Meter, Rail, Toolbar, OtpField, Filters, Command, Toc, PreviewCard, Menubar, NavigationMenu, SearchDialog.
  3. agent/components/<name>.md — the single component page you need.
  4. agent/foundations/<name>.md — before touching theme, token, elevation, or locale code.

The CLI

The same content, if a shell is more convenient than a file read:

Shell
npx noctis rules            # print AGENTS.md
npx noctis catalog          # the whole catalogue
npx noctis catalog button   # one component's entry
npx noctis docs dialog      # one component's full page
npx noctis agents init      # install a Noctis skill into .claude/skills/

agents init is the piece that makes the rest get read. Nothing auto-discovers documentation inside node_modules, so it writes a small skill in the repo that points at the pack. It stays a pointer rather than a copy, so it cannot drift from the installed version.

The catalogue

catalog.json is the file to read once when you do not yet know what the system offers. Each entry is derived from the package's own exports map, the built type declarations, the token graph, and the docs examples — never a hand-maintained second list.

JSON
{
    "button": {
        "import": "@stridge/noctis/button",
        "exports": ["Button", "ButtonDataAttributes"],
        "parts": ["Button"],
        "variants": {
            "size": ["xs", "sm", "md", "lg"],
            "variant": ["primary", "accent", "secondary", "outline", "ghost", "ghost-danger", "danger", "link", "link-foreground"]
        },
        "slots": ["noctis-button", "noctis-button-spinner"],
        "componentTokens": ["--noctis-button-height", "--noctis-button-padding-inline"],
        "examples": ["sizes", "variants", "loading", "icon-only"],
        "doc": "./components/button.md"
    }
}

slots are the override seam, componentTokens the sanctioned per-component knobs, and variants the closed vocabulary each axis accepts — the three things an agent otherwise guesses at.

The type surface carries the part tree

Types answer "what props exist" well and "how do these parts go together" not at all. Nothing in the type of Dialog.Content says it already bundles the portal and the backdrop, or that Dialog.Close expects a render.

So every namespace root carries a worked @example — one canonical composition, opening with the subpath import. It reaches the emitted .d.ts, which means an editor hover, an LSP jump, and a file read all show the same thing:

TSX
import { Dialog } from "@stridge/noctis/dialog";
import { Button } from "@stridge/noctis/button";

<Dialog.Root>
    <Dialog.Trigger render={<Button variant="outline">Open dialog</Button>} />
    <Dialog.Content>
        <Dialog.Header>
            <Dialog.Title>Confirm changes</Dialog.Title>
            <Dialog.CloseButton />
        </Dialog.Header>
        <Dialog.Body>Your edits will be published immediately.</Dialog.Body>
        <Dialog.Footer>
            <Dialog.Close render={<Button variant="secondary">Cancel</Button>} />
        </Dialog.Footer>
    </Dialog.Content>
</Dialog.Root>;

stridge/namespace-example fails the build on a component whose root has none, so the guarantee holds as the system grows.

Enforce the rules, do not review for them

@stridge/noctis-lint is published, so an app enforces the same hard rules the components were built under instead of catching them by eye. Two ready-made sets:

SetRulesNeeds the token manifest
consumersemantic-tokens, no-hardcoded-strings, logical-utilitiesno
authoringall ten, for building components on this substrateyes, for three of them
JavaScript
// oxlint.config.js
import { consumer } from "@stridge/noctis-lint/configs";

export default {
    jsPlugins: ["@stridge/noctis-lint"],
    overrides: [{ files: ["src/**"], rules: { ...consumer } }],
};

A lint error is worth more than a paragraph here: it arrives at the moment the mistake is made, names the fix, and costs no context to carry around.

Failures the surface removes

Most of what makes a design system hard to adopt is invisible to a process with no eyes on the screen.

FailureSymptom without the surfaceWhat catches it now
Stylesheet never importedEvery component renders unstyled. No error, no warning, no failed requestNoctisProvider logs a development console error naming the exact import
Provider missingLocale and theme silently unresolvedThe i18n hooks throw a named error
Importing from the root barrelButton is not exported thereA type error, plus the import line on every @example
Rebuilding a component that existsDuplicated, off-system UI that passes reviewcatalog.json, read once
Guessing a part treeRuntime errors, or a subtly wrong compositionThe @example on every namespace root
An override that will not applyThe prop "does not work"SLOTS.md, the per-component CSS, and the customization ladder
Hand-picked colours and physical propertiesOff-palette UI that breaks in RTL or light mode@stridge/noctis-lint's consumer set

How it is generated

pnpm build assembles the pack from the same committed data this site renders from, so the two cannot disagree. The output is gitignored and rebuilt per version, which keeps the generated markdown out of the repository while the published tarball always carries a copy matching the code beside it.

Adding a component therefore means it appears in catalog.json after a build, with its slots, tokens, and variants filled in from the token graph — no second registry to update, and nothing to forget.