


Maintain a CSS-in-JS Library That Works on Browser, Server, and Native
Use when an AI coding assistant works on a standards-based CSS-in-JS library. It keeps styles to standard CSS, keeps code working on each runtime, writes tests first from the spec, and sets rules for release notes, docs, and demos that run the real code.
To use this AI rule
## Accept Only Standard CSS in Styles, and Let Configured Defaults Win
Keep the authored style surface aligned to real, standards-compliant CSS. This keeps the mental model portable and stops the API from drifting into an idiosyncratic dialect.
- Accept only standard property names, spellings, and values in authored styles. Never invent non-standard aliases (for example, kebab-case names for platform-only props). Platform-specific style keys belong in an explicit escape hatch (an inline style object, or upstream platform support), not in the template language.
- The native layer's job is to map idiomatic CSS onto equivalent platform primitives and to polyfill what it can until the native runtime catches up. It is not there to expand the accepted input grammar.
### Configured-defaults precedence
- Author-configured attribute defaults always win over props passed by the caller, by design. This is a deliberate contract, not a bug.
- The escape hatch is the function form of the defaults config: it receives the incoming props and can fall back conditionally (keep the caller's value, or supply a default).
- Exception: a caller explicitly passing `undefined` for a prop suppresses the configured default for that prop.
## Use the Project's Package Manager and Commit Style Each Session
- At the start of each session, read the current consumer-facing feature summary so you work against today's supported surface, not remembered behavior.
- Use the project's designated package manager and its associated commands; do not substitute another.
- Never start the dev server yourself. Ask the maintainer to start it if one is needed.
- Use conventional commit prefixes (`feat|fix|chore|refactor|test|docs|style|perf|build|ci`). Default to short messages: title only, or title plus one tight sentence.
- Keep one consistent English spelling convention for all prose (comments, docs, test titles, user-visible strings). Preserve original spelling only inside verbatim quotes from specs or standards.
## Tell Server, Browser, and Native Apart, and Keep DOM Code Out of Native
Code that ships to multiple runtimes must not assume one environment. Detect the environment from several independent signals, and isolate incompatible code at the entry-point boundary.
- Do not collapse environment detection into a single universal flag. Distinguish at least: a build-time server constant (true only in the server build), a runtime server-component condition (some frameworks serve a build without client-only context APIs), and a per-request server flag set by the SSR entry. Each decision gates on the minimum subset of signals that catches the scenarios it cares about. Common trap: suppressing DOM work purely on the build-time server constant breaks the server-build-under-jsdom test path, where DOM mutations still happen. Gate DOM decisions on the runtime/SSR signals instead.
- The native entry must never transitively import DOM/browser code through value imports. Use type-only imports and branded runtime tags (a shared symbol registry) rather than `instanceof`. Verify with a grep asserting zero `document.` references in the built native bundle; some native runtimes fail at module-evaluation time on any `document` reference.
- Framework hooks that do not exist in every runtime (for example, a ref hook that is undefined in server components) must be gated behind the build-time server constant for dead-code elimination. Never guard them with a runtime `typeof hook === 'function'` check, which becomes a conditional hook call.
- Keep a single canonical output-emission path. When you change output, extend the emission and parser/compiler test suites rather than adding a parallel path.
## Strip Code With Build-Time Constants, and Lock Entry-Point Mapping With Tests
Separate what the bundler can eliminate at build time from what only exists at runtime, and be deliberate about entry-point mapping.
- Build-time constants (server build, native build, native-web variant, dev mode) are substituted to literals so bundlers dead-code-eliminate the dead branch. Prefer wrapping the outermost contiguous dev-only region in a single dev guard; do not rely only on a warn helper to skip work, and never construct message strings or warning arguments outside the dev guard.
- A runtime check like `typeof window !== 'undefined'` cannot be tree-shaken. Use build-time constants, not runtime checks, when you need code physically removed from a bundle.
- Entry/subpath mapping is a frequent source of silent breakage. Platform-extension resolution (letting the native bundler pick a per-platform file) is safer than a `browser` object-map that some native bundlers apply on every platform, which can silently route a web bridge onto native. A `.js` fallback should be the safe cross-platform bridge, never the platform-specific engine. Lock these resolutions with a resolution/tree-shake test.
- Prefer a `browser` field over conditional `exports` when the latter breaks type resolution in composite projects. Add every published subpath directory to the package's `files` list.
## Write a Failing Test First, and Benchmark Speedups on Real Workloads
- Strict test-driven development is enforced repo-wide. Before changing production code, add or extend a failing test that encodes the desired contract, then implement only enough to turn it green. No user-visible behavior ships without a test that would fail without the change. Follow the same red-green discipline for non-feature work.
- Always microbenchmark optimizations against a realistic workload, not a synthetic best case. Revert any change that pessimizes the path real callers take, even if it wins in isolation.
- Optimize for low memory pressure and monomorphic functions.
- Ground any spec-backed behavior in the authoritative specification's current editor draft, not summaries or reference sites, and not memory. Re-fetch the relevant section for the session, quote the normative clause verbatim as a comment above the test that locks it, and write one test per rule inside a clearly named spec-compliance block. If the spec is silent or ambiguous, document the deviation next to the test and link the tracking issue. This applies to every spec-touching change.
## Turn Each Spec Rule Into a Test Before Building a Native Polyfill
When the native runtime's style engine cannot understand a CSS feature, polyfill it, but lock the behavior to the spec before refactoring internals.
Procedure for a new or revisited polyfill (also for any spec-backed validation block):
1. Fetch the current editor draft for the session. Clause numbers and wording drift, so never treat an old extract as authoritative.
2. Translate every spec rule into exactly one test, with the verbatim spec text quoted above it, inside a named spec-compliance block in the relevant suite.
3. Write the tests first, and record which currently fail.
4. Implement until they pass. Do not reshape passing tests to match the implementation; the spec is the source of truth.
5. Run the full native and main suites for regressions.
Once the spec block is green, the internals (tokenization, AST, evaluator) are free to refactor; the block locks user-observable behavior.
- Keep the consumer-facing native-compatibility ledger current whenever runtime versions ship, support changes, or polyfills are added, removed, or altered.
- A feature with no viable native workaround must emit a deduplicated warning with a stable code that names the offending construct, why it cannot run, and a concrete alternative, then drop the declaration (bail to null) rather than shipping nonsense.
- Variant parity: where the browser already ships a feature, pass the raw CSS through unchanged; never emit a property the browser does not ship yet; gate native-only prop lifts and enum extensions behind the native-only build constant. Give each polyfill spec block a parity sub-suite that toggles the web variant and locks its visible output, and explicitly waive (with the same rigor as a deviation comment) any normative rule that does not apply on a given variant.
## Write Release Notes Only About What Users See, and Put Each Doc in Its Place
Match the register and content of every artifact to its audience.
- Consumer-facing text (release notes, changelog entries, PR descriptions, public docs) states only user-observable behavior, in words someone who has not read the source understands. Name what is and is not supported and the change's own boundary; link the compatibility matrix rather than cataloging unrelated gaps. No internal API names, mechanisms, or code paths; those live in the commit body and the contributor guide.
- Describe behavior as it currently is. Avoid journey narration ("previously", "no longer", "now", "replaces") for states no released version shipped; fold a fix on a still-unreleased feature into that feature's release note.
- Create release notes only for user-visible changes (fixes, features, breaking changes). Skip internal refactors, tooling, tests, and fixes to code introduced in the same unreleased version.
- Do not edit auto-generated files (changelogs, generated error maps); regenerate them through their tool.
- Keep specific AI-provider names out of contributor-facing docs; use neutral phrasing such as "an AI coding assistant."
- Do not hard-wrap prose in PR bodies, issue bodies, or comments; single-line paragraphs re-flow, and hard wraps look broken in mobile and quoted views. Commit-message bodies may keep the 72-column convention.
### Doc placement
- The contributor guide holds cross-cutting rules, invariants, and earned knowledge with no natural home in source comments; well-described mechanism does not belong there.
- Standalone knowledge docs capture pitfalls and earned knowledge, not feature mechanism walkthroughs; the source is authoritative for mechanism.
- Source-level "why" lives in comments/JSDoc next to the code. Do not restate spec citations in production source or warning messages; anchors, normative quotes, and rationale live in the locking test. Warning messages name the offending construct and the concrete alternative, and nothing else.
## Use Measured Speed Patterns, and Build Demos That Run the Real Code
Microbenchmark-validated patterns on a mainstream JavaScript engine (treat as starting hypotheses; re-measure in context):
- String `+=` concatenation beats `array.push()` + `join()` at all scales.
- Spreading `{...props, extra}` beats `Object.assign` or a `for..in` copy.
- `for..in` beats `Object.keys()` plus a loop.
- Cache compiled RegExps in a map; an `indexOf` pre-check to skip work entirely is a larger win.
- Manual `+` concatenation beats template literals in tight loops.
- Constructing raw element objects (with the framework's element tag) is far faster than the framework's element factory.
- `new Array(n)` creates holey arrays that poison type feedback; avoid pre-sizing hot arrays.
Engine and language gotchas:
- A `private` modifier is not allowed on an anonymous class expression assigned to a const.
- A type-only star import can still trigger bundler module resolution even though the type system strips it.
Demo and visual-QA integrity (for any showcase that claims to prove behavior):
- Zero tolerance for fake demos. Every widget must exercise the real runtime path it claims to demonstrate. If a feature cannot run on a platform, let the cell fail loudly (broken layout, warning, missing glyph); that failure is the demo. Never substitute a mock that mimics the spec, and never hardcode a pass/fail color as a stand-in for the real path firing.
- Use omniplatform components with no platform branches in the showcase; platform-skew workarounds belong in the library, not the app.
- Make working-vs-broken glanceable at arm's length via macro differences (layout shifts, color floods, presence/absence), and make every widget read correctly in both light and dark modes.
