Skip to main content

Drift policy

Copy-in distribution means every script owns its files and can edit them. That flexibility is the point — but drift you did not choose is how a set of scripts stops looking related.
The asymmetry is deliberate. Identical tokens are what make scripts read as siblings; a one-off button variant in one script is a legitimate escape hatch. Line endings are normalised before comparison, so a Windows checkout will not report false drift. Wire check into CI. It only fetches the items you actually have installed, not all 76.

Doctor

Verifies the things that are easy to get wrong and hard to notice:
  • The @9am registry is configured and the token is set and working
  • index.css imports both a theme and a font stylesheet
  • postcss.config.js still has the oklch conversion
  • Your built CSS contains no surviving oklch()
The checker is not an installable package. Bun resolves github: and git+https: dependencies through the GitHub tarball API, which returns 404 for a private repository even with a token — so it ships through the registry as @9am/tools and reuses the credentials already in components.json.

Repository layout

registry.json is generated. Never hand-edit it. scripts/build-registry.ts derives every item’s npm dependencies and registry dependencies from the real import graph, so the manifest cannot disagree with the code.
Renders every primitive, icon, and the viewport in both themes, importing straight out of registry/ rather than from an installed copy. If a component would break on shadcn add, it breaks here first. Use it to check both palettes before releasing a theme change.

Adding a component

1

Add the file

Drop it in registry/ui/, or registry/icons/ if it exposes the startAnimation() handle.
2

Use consumer-facing import paths

@/lib/utils, @/components/ui/button, @/hooks/useNuiEvent, @/utils/fetchNui, @/i18n.Never use a relative path across directories. That is what the file resolves to once installed — a relative path that works in the kit will break in a script.
3

Declare new packages

Add any new npm dependency to the root package.json first. The generator errors out on an import it cannot find a version for, rather than shipping an item whose package never installs.
4

Show it in the gallery

Add it to apps/preview/src/Gallery.tsx. Icons appear automatically — they are globbed.
5

Build and verify

Adding a user-facing string

Kit components must not hardcode English. Use t("ui.common.…") and add the English fallback to KIT_DEFAULTS in registry/i18n/index.ts and to registry/scaffold/lua/locales/en.json. If the string is domain-specific rather than generic, take it as a prop instead. DataTable’s countLabel exists for exactly this reason — so a coupons table can say “12 coupons” while the shared default stays “12 rows”.

Changing the theme

Token changes land in every 9AM script, so this is the highest-blast-radius edit in the repo.
  • Check both palettes in the preview. Light mode is not a tint of dark mode.
  • Never author a colour outside the token set. A raw hex in a component is drift check cannot see.
  • Fonts: replace the woff2 in registry/theme/fonts/, then run bun run fonts. Never edit 9am-fonts.css — it is generated.

Releasing

r/ is committed on purpose. raw.githubusercontent.com serves committed files, which is what lets a private repository act as a registry with no hosting at all. CI re-runs the build and fails if the committed r/ differs from what the sources generate, so a stale registry cannot reach consumers. Nothing auto-updates. Consumers pick a change up on their next shadcn add … --overwrite, which is deliberate — a script should adopt a token change when someone is watching it, not silently on a Tuesday.