This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Beam CSS is a Rust-fast, utility-first CSS framework. It gives humans and coding agents a small, regular styling grammar that compiles to atomic CSS under cascade layers. The pitch: Tailwind's authoring speed, without the wall of classes. Tagline: focused styles, zero scatter.
Key differentiators over Tailwind: variant grouping (hover:(bg-accent text-base)), utility grouping (padding:(16 top:24)), config composition (shortcuts, recipes, presets, utility modules), and dynamic values (w-(--var) → var(--var)).
Read docs/beam-css-spec.md for the grammar (the class-string syntax is the public API) and docs/ROADMAP.md for what's done and what's next.
# Install dependencies
pnpm install
# Build everything (all JS packages + Rust workspace)
pnpm build
# Run all tests (JS + Rust)
pnpm test
# Type-check only
pnpm typecheck
# Lint
pnpm lint
# Run benchmarks
pnpm benchmarkRust-only:
cargo test --workspace
cargo audit
cargo build -p beam_cli --releaseRun a single Rust test:
cargo test -p beam_core <test_name>
cargo test -p beam_cli <test_name>Run a single JS package's tests:
pnpm --filter beamcss test
pnpm --filter @beamcss/vite testBuild the native Node binding:
pnpm --filter beamcss build:nativeCLI smoke tests:
cargo run -p beam_cli --bin beam -- build \
--config examples/walking-skeleton/beam.config.ts \
--content examples/walking-skeleton \
--out /tmp/beam-walking.css
cargo run -p beam_cli --bin beam -- check \
--config examples/walking-skeleton/beam.config.ts \
--content examples/walking-skeleton \
--format json
cargo run -p beam_cli --bin beam -- explain "flex direction-column gap-4 hover:(bg-accent text-on-accent)" \
--config examples/walking-skeleton/beam.config.ts \
--format jsonFuzzing:
cargo fuzz run fuzz_compileThis is a pnpm + Cargo monorepo. The JS packages live in packages/, the Rust crates in crates/, and example apps in examples/.
beam_core— parser and compiler. Entry points:compile(config, class_strings) -> CompileResultandexplain(config, class_strings) -> ExplainResult. This is where the grammar lives. Output is atomic CSS under@layer beam.reset, beam.tokens, beam.base, beam.utilities.beam_node— napi-rs Node binding exposingbeam_coreas a native.nodeaddon (same approach as Lightning CSS / Tailwind Oxide).beam_cli— standalone binary. Commands:beam init,beam build,beam dev(watch),beam check,beam explain. Scans source files forclass=/className=attributes and delegates tobeam_core.
The workspace enforces #![forbid(unsafe_code)] globally.
beamcss— umbrella package users install. Exports:index.ts—defineConfig,BeamConfigtypesnative.ts— loads the.nodebinding via napi-rs; falls back gracefullycli-runner.ts—buildCss()tries native binding first, then shells out tocargo run(dev-only fallback for use inside this repo)scanner.ts— JS implementation of class-string extraction from source filescodemod.ts— Tailwind-to-Beam codemodlanguage.ts— language server / completions datacli.ts— thin Node CLI wrapper
@beamcss/vite— Vite plugin. IntegratesbuildCssinto the Vite build pipeline with HMR.@beamcss/postcss— PostCSS plugin for non-Vite bundlers.@beamcss/mcp— MCP server (agent-native surface, Phase 4).
packages/beamcss/src/native.ts attempts to load a prebuilt .node addon from packages/beamcss/native/. If unavailable, cli-runner.ts's buildCssWithRustCli() locates the repo root by walking up until it finds a Cargo.toml that references crates/beam_cli, then shells out to cargo run.
Gotcha: the prebuilt
.nodeinpackages/beamcss/native/is committed and does not rebuild fromcargo test. After anybeam_core/beam_nodechange, runpnpm --filter beamcss build:nativebefore the JS/plugin tests (@beamcss/vite,@beamcss/postcss, codemod) — otherwise they run against the stale binding and either miss new behavior or fail on it. The Rust tests use the source directly, so they pass even when the binding is stale; the two can silently diverge.
beam.config.ts uses a TypeScript object literal. Both the Rust CLI and the Node packages share the same extraction logic: find defineConfig( or export default, extract the brace-balanced object, then parse it as JSON5. This is intentional — no TS execution needed.
- The class-string grammar (spec §8) is the public API. Treat changes to it with semver seriousness.
#![forbid(unsafe_code)]is enforced at the workspace level — don't remove it.- No
postinstallscripts anywhere. Supply-chain security is a first-class concern (cargo audit+npm auditrun in CI). - Every grammar feature must be used in
examples/before it's "done." - Golden-file snapshot tests for every compiler output change.
- Conventional Commits.
These apply to every agent session. Violations require explicit user re-approval, not a judgment call.
- No new Rust crates or top-level npm packages without explicit approval in the task plan.
- Config parsing contract is JSON5-only.
beam.config.tsis extracted via brace-balanced JSON5 parsing — do not add TypeScript execution (ts-node,tsx, dynamicrequire, etc.). - napi-rs binding interface (
crates/beam_node/src/lib.rs) public signatures must stay in sync withpackages/beamcss/src/native.ts. Change both together, never one alone. @layerorder is fixed:beam.reset, beam.tokens, beam.base, beam.utilities. Do not reorder — it controls specificity.- Committed
.nodebinary (packages/beamcss/native/) is not rebuilt automatically. After anybeam_core/beam_nodechange, runpnpm --filter beamcss build:native. JS plugin tests (@beamcss/vite,@beamcss/postcss) test against this binary — if it's stale, they silently test old behavior. - All compiler output changes require snapshot test updates. Never delete a golden file without replacing it.
Cargo.tomlfiles (adding/removing crates or dependencies).github/(CI configuration)packages/beamcss/native/(committed.nodebinaries — rebuild, don't edit)fuzz/targets- Any semver-breaking grammar change without a version bump plan
Every task runs as a series of small, auditable slices. Each slice has a frozen contract before any code is written.
Before starting a slice, state:
- Intended slice (what this pass does)
- Allowed files/functions (exhaustive list)
- Tests that must pass
- Explicit non-goals (what this slice must NOT change)
After completing a slice, emit a receipt:
- intended slice:
- allowed files/functions:
- actual files/functions changed:
- behavior added:
- behavior removed:
- tests added/updated:
- explicit non-goals:
- rollback path:
Two-gate review (in order):
- Authorization gate — does "actual files changed" match "allowed files"? If not, revert and re-scope. Don't review quality until authorization passes.
- Quality gate — is the code correct and clean?
If a slice touches Cargo.toml, .github/, shared types, or the napi binding interface → stop and get explicit re-approval before proceeding.