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
- The
@9amregistry is configured and the token is set and working index.cssimports both a theme and a font stylesheetpostcss.config.jsstill 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
The preview gallery
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. Uset("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
checkcannot see. - Fonts: replace the woff2 in
registry/theme/fonts/, then runbun run fonts. Never edit9am-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.