Agents guide for the xstyled monorepo. Tool-agnostic. Update when a convention changes.
Repo
- pnpm workspaces, Changesets for release. Seven packages under
packages/:util,prop-types,system,core,emotion,styled-components,babel-preset-emotion-css-prop. scripts/holds repo-level tooling.website/is the Gatsby docs site.benchmarks/compares againststyled-system.
Commands
pnpm install --frozen-lockfilefor a CI-equivalent install.pnpm buildbeforepnpm check:types; the augmentation test resolves@xstyled/systemthroughpackages/system/dist/index.d.ts.pnpm lint,pnpm test,pnpm check:types,pnpm bench:types,pnpm bench:runtime.
Type tests
- Per package:
src/__type-tests__/*.test-d.ts(x)+tsconfig.types.json(excluded from main tsconfig and from jest). - Module-augmentation test is isolated in
packages/system/type-tests-aug/with its owntsconfig.aug.jsonbecausedeclare moduleis project-wide. - Pair
Expect<Equal | Assignable | NotAssignable<...>>positives with@ts-expect-errorvalue-level negatives. - Hook calls inside type tests go inside a
_useFooAssertions()helper soreact-hooks/rules-of-hooksdoesn't fire. scripts/check-types.mjschains all five projects.
Perf benches
scripts/bench-types.mjs: synthetic themes throughtsc --extendedDiagnostics. Defaults to 50/200/500 color tokens;--sizesto override.scripts/bench-runtime.mjs:compose()init andsystem(propsBag)throughput. Rerun 3x and take the median.- Hot paths:
packages/system/src/style.ts,packages/util/src/index.ts(merge/assign/flattenStrings/get),packages/core/src/transform.ts,packages/emotion/src/createCssFunction.ts.
Module augmentation (consumer contract)
declare module '@xstyled/system' { export interface Theme { β¦ } }fromstyled.d.ts. Augmenting any other module specifier won't reach the system types.
Type gotchas
useThemein@xstyled/emotionreturns@emotion/react'sTheme, not@xstyled/system's. Emotion users augment@emotion/reactseparately.Space<T>etc. union with{}viaThemeNamespaceValue, so negative tests on them are not enforceable.Color<T>is strict and negation tests work._(base/no-breakpoint key) lives inscreens, notstates.ThemeVariants<T>doesThemeScreens<T> & Omit<ThemeStates<T>, '_'>to avoid thescreens._: numbervsstates._: nullcollision.SynthesizedPath<T, D = 6>is depth-bounded; past depth 6 the result widens tostring. Numeric keys are preserved.
Commits and PRs
- Conventional Commits (
chore:,fix:,feat:,refactor:,perf:,test:,review:); scope optional. - Changesets drives versioning and changelogs. Run
pnpm changesetto add one for any user-visible change to a public package; commit the generated.changeset/*.mdwith the change. Internal-only / repo-tooling changes don't need one. - Releases publish via
.github/workflows/release.yml(changesets/action) using npm trusted publishing (OIDC). There is noNPM_TOKEN; the workflow needsid-token: write. Fixes #nonly when the diff genuinely resolves the issue.Refsfor things mitigated, regression-locked by tests, or touched.- Don't open a PR unless asked. Pushing a branch is not opening a PR.
- Be frugal with PR replies. Only post when a reply is genuinely needed; the diff is the record for routine fixes.
Voice
- Machine-oriented markdown (this file, project notes, agent instructions): dashes for lists, no headers, no bold, no tables.
- Human-facing markdown (READMEs, PRs, changesets, release notes): written for a human, no internal specifics, avoid point-in-time numbers that will go stale.
- No em-dashes. Use a period, comma, parenthetical, or appropriate unicode punctuation.
- Reference code with
file_path:line_number. - Don't put "Generated by Claude" or any AI attribution on commits, PRs, files, or output.
- Don't proactively create
*.mdfiles unless asked; this file is the exception.
How to think
- Scientific method: hypothesis, hold variables constant, test one variable at a time, update.
- Meticulous: observe, note, plan, act, reconcile against the plan. Keep a to-do list; append new requests unless told otherwise.
- Gardener: handle unobjectionable cleanup as you pass through.
- Question complexity. Propose simpler alternatives. Question assumptions; don't accept overengineering.
- Don't silently work around issues; log them in a backlog rather than deferring invisibly.
- Verify against first-party docs and current code before deciding. Training-data recall is a hypothesis, not a source of truth.
How to plan
- Ask clarifying multiple-choice questions until the ask fits in one paragraph the user agrees with.
- Present 2-3 directions with research-backed opinions, not a single decided plan.
- Never give time estimates.
How to ship
- Types are law. No
as any, non-null assertions, or@ts-expect-errorescape hatches except as deliberate negative-behavior tests, clearly differentiated. - Tests ship with features. Aim for branch coverage above 80% (no tooling threshold; aspirational). Cover happy paths, edge cases, failure modes. Prefer inline snapshots seeded empty. Use red/green to validate fix and test together. Local tests run well under 30s.
- Comments explain non-obvious current behavior. Never narrate change history.
- Hot-path code earns microbenchmarks before claiming optimization.
- Run the build to verify changes; don't start a dev server unless asked.
- Backward compatibility applies only to public API surfaces; internal code is rewritable when complexity warrants.
- Errors carry typed metadata for context but never leak internals to users; full traces stay in server logs.
- Dry-run destructive or batch operations and verify targeting before executing.
- Subscribe to PR events and handle review comments and failing builds automatically.
Git discipline
- Treat the working tree as ephemeral; commit when work is at a stable point.
git push -u origin <branch>; on transient network failure retry with exponential backoff, diagnose anything that fails repeatedly.- Confirm before destructive ops (
push --force,reset --hard,branch -D, amending or rebasing pushed commits). Never force-pushmain. - Never bypass hooks (
--no-verify) or disable signing (--no-gpg-sign). Fix the underlying failure. - Investigate unexpected state before deleting or overwriting; it may be the user's in-progress work.
Don't
- Push to
maindirectly. - Bump versions or publish to npm by hand; the release workflow does it from merged changesets.
- Add new features in a modernize/triage PR.
CI
.github/workflows/ci.ymlon push tomainand PRs tomainornext. Steps: checkout v4, pnpm/action-setup v4, setup-node v4 (.nvmrc, currently 22,cache: pnpm),pnpm install --frozen-lockfile,pnpm build,pnpm lint,pnpm check:types,pnpm test --coverage, Codecov and bundlewatch ascontinue-on-error..github/workflows/release.ymlon push tomain. Useschangesets/action@v1withpublish: pnpm releaseandid-token: writefor npm trusted publishing..github/dependabot.ymlgroups npm and GitHub Actions updates.