- Never use em dashes (
—) or en dashes (–) as punctuation. Not in code comments, commit messages, PR descriptions, docs prose, README copy, or replies to the user. Rewrite the sentence, or use a comma, colon, parenthesis, or full stop instead. - A hyphen inside a compound word (
build-time,zero-runtime) is fine. The rule is about dashes standing in for punctuation. - This applies to text you generate anywhere in this repo and to anything you say about it.
mainis protected. Never commit to or merge intomainlocally.- All work happens on a branch and lands via a pull request, even for one-line
fixes. Push the branch, open the PR with
gh pr create, hand back the URL. - Package manager is pnpm. Never
npmornpx. Node and pnpm versions are pinned inmise.toml; CI installs them viajdx/mise-action. - TypeScript is pinned to 5.9.x. Do not move to 7.x (the Go port) without a
deliberate decision: it generates the
.d.tsevery consumer depends on.
- No override API.
classNameandstyleare omitted from every public prop type and neutralised at runtime. Do not add them back. If someone needs a different button, dowel is the wrong library. That is the point. - Spread
{...props}FIRST, then the result ofstylex.props(...), thenstyle={undefined}. Spreading last lets a consumer strip the generated class and it typechecks clean, because JSX spreads skip excess-property checks. - Author component styles with StyleX. Express variants as local style maps
and boolean entries passed to
stylex.props(). Usedata-*attributes only when state must remain visible to CSS selectors or tests. - Define public theme tokens with
stylex.defineVars()in named.stylex.tsexports. Public custom properties start with--dowel-. Internal generated class names are opaque and must never be treated as API. - Only
border,background-color,color,opacitymay transition. Neverall, never transform or size on hover. - Hairlines are
0.5px. Controls are 28px. Base font weight is 450, UI labels 500, workhorse size 13px. - Hover rules need
:hover:not(:disabled):not([aria-disabled="true"]), since:not(:disabled)is true for an anchor. - No Tailwind and no class-name helper (
cx/clsx). StyleX is the only component styling system. Do not add CSS Modules, styled-components, Emotion, or hand-authored component stylesheets alongside it. - StyleX must compile at build time with runtime injection disabled. The npm
package ships compiled JavaScript and one extracted
dowel.css, so a consumer does not need a StyleX compiler. - Plain CSS is reserved for font faces, document resets, and third-party escape hatches that StyleX cannot represent. Record every such exception in the architecture document.
- jsdom cannot verify styling. It ignores every rule inside
@layerand never substitutesvar(). Do not write assertions about computed colour, geometry or hover: they cannot fail. Verify extracted StyleX rules against the builtdist/dowel.cssand use browser visual tests for rendered appearance. - Vitest intercepts
console. Grepping the run log cannot observeconsole.error. Usevi.spyOn(console, "error"). - Base UI overlays open asynchronously. Use
findByRole/waitFor, never a synchronousgetByRoleafter a click: the synchronous form does not just fail, it can make the whole test pass vacuously. pnpm testrequires a build first (the CSS contract test asserts againstdist/). Apretestscript handles this locally; CI builds before testing.
- changesets. A pending changeset in
.changeset/triggers an npm publish on merge to main. If a change should not release, add an empty changeset. - Secrets
NPM_TOKENandCLOUDFLARE_API_TOKENlive at the karnstack org level with visibility ALL. Never create repo-level copies: a repo secret shadows the org one and silently breaks rotations.