Guidance for AI coding agents working in lib0-like projects — small, isomorphic JavaScript utility packages that follow the conventions pioneered by lib0. Project-specific CLAUDE.md (or equivalent) overrides anything here.
Before writing @ts-ignore, @ts-expect-error, eslint-disable, any-cast, empty catch, widened type, or loosened assertion: stop. State the root cause in one sentence. If you can't, investigate or ask — don't suppress. If suppression is genuinely correct, add // reason: <root cause> on the line above; without it, the suppression is rejected.
Type error → fix the JSDoc or the invariant, don't widen. Failing test → fix the bug or the wrong assumption, don't loosen. Flaky fuzz test → reproduce with --seed <n> and fix.
- ESM only.
"type": "module"inpackage.json. No CommonJS build. - Isomorphic. Runs in Node, browsers, Deno, Bun, React Native. Environment-specific modules ship as
foo.js(default/browser),foo.node.js,foo.deno.js,foo.react-native.js, wired via conditionalexports. - JSDoc, not TypeScript. Source is
.jswith JSDoc (@type,@param,@returns,@template,@typedef).tsconfig.jsonsetsallowJs: true, checkJs: true, strict: true;npm run lintrunstsc --noEmit. If a type is hard to express, write better JSDoc — do not add.tsfiles. - No circular imports.
dpdmenforces this innpm run lint.
- standard: no semicolons, 2-space indent.
- Minimal and correctness-focused. No defensive code, no try/catch around calls that can't throw, no validation beyond what's needed.
constby default;letonly in loops.- Avoid polymorphism. Classes for data shape (stable hidden classes in V8), not method dispatch. The only approved use of methods is when two classes implement the same signature differently (duck-typed dispatch).
- Export factory functions (
createFoo(...)), never classes. Class names don't survive mangling, factory names do. Consumers must never writenew ClassName().
sideEffects: false in package.json is the primary mechanism. Two additional rules:
- Top-level conditional initializers defeat DCE. Wrap them in a
/* @__PURE__ */-annotated IIFE:export const x = /* @__PURE__ */ (() => cond ? A : B)()
/* @__PURE__ */binds only to aCallExpressionorNewExpression— before a ternary, parenthesized expression, or bare identifier it's silently a no-op. Use the space-padded@-prefix form;/*#__PURE__*/failsstandard'sspaced-commentrule (bundlers accept both). - Annotate pure function declarations with
/* @__NO_SIDE_EFFECTS__ */so every call site is drop-if-unused without per-call/* @__PURE__ */. Belt-and-braces for pipelines that ignoresideEffects: false(notably React Native / Metro).
- Don't add
.tsfiles, a bundler, or a build step forsrc/— published files are the source. - Don't add polyfills or runtime feature-detection — use conditional exports for cross-environment modules.
- Don't replace factory functions with exported classes for "ergonomics".
- Don't add semicolons to match "normal" JS —
standardfails the lint.