scripts/lib/

August 11, 2026 ยท View on GitHub

The bottom of the graph. A module is here because more than one script reads it, and the only direction allowed is downward: a gate imports a library, and a library never imports a gate. Across domains the same holds in both directions: core/arena-tokens.ts imports ../arena/css-decls.ts and nothing forbids it, because a domain is a statement about subject matter, not a visibility boundary.

ArenaPlacement

Everywhere else under scripts/, the domain is decided by what a script touches. Most of lib/ touches nothing, being pure functions, so the reads-and-writes test cannot separate them, and a library that touches nothing is placed by the vocabulary it speaks. A module that speaks no vocabulary at all is neither, and lives in flat ../utils/ rather than here.

core/serialize-token.ts opens no file, but every name in it is a DTCG one, so it is core. core/behaviour-compliance.ts is the same, in contracts/behaviour's vocabulary of requirement keys. What is left over is arena, meaning the parsers, the browser harness, layers.ts and repo-root.ts, because it belongs to no layer in particular.

Never place a library by who imports it. behaviour-compliance.ts is read from both framework layers' test harnesses and is still core, because what it speaks is the contract vocabulary, not either layer's.

Runtime reach

Three modules here run somewhere scripts/ does not. core/behaviour-compliance.ts is consumed from three runtimes (plain node, happy-dom under Bun, and the Angular AOT emit), which is why it touches only tagName, getAttribute, hasAttribute and textContent, and takes its id resolver from the caller instead of reaching for a document it may not have. arena/comments.ts is kept dependency-free for the same reason. Both are also why a test under scripts/ may not import a framework layer's .ts or .tsx: check-all.ts runs these suites under plain node too, which cannot resolve the extensionless imports those toolchains expect.

The five domains

domainwhat a module there speaks
arena/belongs to no one layer: parsers, the browser harness, the tree's own facts
core/speaks contracts/: DTCG, behaviour requirements, colour science
tailwind/compiles the Tailwind layer, for the gates that read it
react/emits that layer's half of a playground, in that layer's idiom
angular/the same, for the other layer

Count them rather than reading a figure here, which rots the first time a module lands:

for d in angular arena core react tailwind; do
  printf '%-9s %s\n' "$d" "$(find scripts/lib/$d -name '*.ts' ! -name '*.test.ts' -o -name '*.mjs' | wc -l)"
done

repo-root.ts is the one module whose own move needs care: it is the single place that counts .. segments to find the repository root, so that no other script's depth is part of what it has to get right.