- We exclusively use
bunto run commands and install packages. Don't usenpmorpnpmornpxor other variants unless there's a specific reason to break from the norm. - Since we use
bunwe can natively run TypeScript without compilation. So even local scripts we run can be.tsfiles. - We use Bun's
catalogfeature for dependencies in order to reduce differences in dependencies across monorepo packages.- CRITICAL: NEVER add a version number directly to a package's
package.json. Always follow this two-step process:- First, add the dependency with its exact version to the root
package.jsonfile insideworkspaces.catalog(e.g.,"new-package": "1.2.3") - Then, in the individual package's
package.json, reference it using"catalog:"(e.g.,"new-package": "catalog:")
- First, add the dependency with its exact version to the root
- NEVER run
bun add <package>inside a package directory - this will add a version number directly which breaks our catalog pattern. - This rule is sometimes broken in packages that are published, in order to
make sure that end-users aren't forced to our specific version.
apps/docswould use the catalog version anddiffsmay choose to use a range.
- CRITICAL: NEVER add a version number directly to a package's
- npm "scripts" should work from inside the folder of the given package, but
common scripts are often "mirrored" into the root
package.json. In general the root scripts should not do something different than the package-level script, it's simply a shortcut to calling it from the root.
We use oxlint at the root of the monorepo rather than per-package lint setups.
Run linting from the monorepo root:
bun run lint
bun run lint:fixFor CSS, we use stylelint:
bun run lint:css
bun run lint:css:fixWe use oxfmt at the root of the monorepo.
Check formatting from the monorepo root:
bun run format:checkApply formatting from the monorepo root:
bun run formatImportant: Always run bun run format from the monorepo root after making
changes to ensure consistent formatting.
- Always preserve trailing newlines at the end of files.
We use TypeScript everywhere possible and prefer fairly strict compiler settings.
All projects should individually respond to bun run tsc for typechecking, but
many of those scripts are implemented with tsgo rather than plain tsc.
Shared compiler options live in the root tsconfig.options.json file.
The root tsconfig.json file is used to manage project references across the
monorepo.
We use project references between packages and apps.
- When adding a new package or app, update the root
tsconfig.jsonreferences. - When a package depends on another
workspace:package, add the dependency to the consuming package'sreferencesblock when needed for accurate and fast typechecking.
- When adding non-trivial helper functions, prefer a short comment directly above the function declaration that explains, in plain language, what the helper does and why it exists.
- Write these comments as if the reader is new to the codepath. Avoid vague shorthand like "snapshot" unless you immediately explain what data is being captured or derived.
- Prefer function-level comments over a lot of inline comments. Use inline comments only when a specific step inside the function is still non-obvious.
- Keep comments concrete and behavior-focused. Good comments usually explain what data is being transformed, what invariant is being checked, or what the helper is protecting against.
CRITICAL: Avoid nested loops and O(n²) operations.
- When iterating over collections, calculate expensive values ONCE before the loop, not inside it
- Never nest loops unless absolutely necessary - it's expensive and usually there's a better way
- If you need to check conditions on remaining elements, scan backwards once upfront instead of checking inside the main loop
Example of BAD code:
for (let i = 0; i < items.length; i++) {
// DON'T DO THIS - nested loop on every iteration
let hasMoreItems = false;
for (let j = i + 1; j < items.length; j++) {
if (items[j].someCondition) {
hasMoreItems = true;
break;
}
}
}Example of GOOD code:
// Calculate once upfront
let lastMeaningfulIndex = items.length - 1;
for (let i = items.length - 1; i >= 0; i--) {
if (items[i].someCondition) {
lastMeaningfulIndex = i;
break;
}
}
// Now iterate efficiently
for (let i = 0; i <= lastMeaningfulIndex; i++) {
const isLast = i === lastMeaningfulIndex;
// ...
}
## Testing
We use Bun's built-in test runner for unit and integration tests. Tests usually
live in a `test/` folder within each package, separate from the source code.
Some packages also include browser-level tests. In particular, `packages/trees`
has Playwright E2E coverage for browser-specific behavior.
### Test Strategy
- Prefer unit/integration tests (`bun test`) by default.
- Add Playwright/browser E2E tests only when behavior cannot be validated
without a real browser engine.
- Good Playwright candidates include computed style checks, shadow DOM
encapsulation boundaries, and browser-only rendering behavior.
- Keep E2E coverage intentionally small and high-value.
- Prefer explicit assertions over broad snapshots.
- Avoid snapshot tests unless they are shallow and narrowly scoped to the exact
behavior under test.
### Running Tests
Examples (run these from the package directory):
```bash
# diffs
cd packages/diffs && bun test
# trees
cd packages/trees && bun test
cd packages/trees && bun run coverage
cd packages/trees && bun run test:e2e
# truncate
cd packages/truncate && bun testUpdate snapshots from the package directory:
bun test -u- Tests use Bun's native
describe,test, andexpectfrombun:test - Snapshot testing is supported natively via
toMatchSnapshot() - Test helpers and fixtures usually live alongside each package's tests