Module map: where shared code lives
A discoverability contract for the four "shared code" directories, so future growth has a rule to check against instead of regrowing a second grab-bag. Decide where a new helper goes by its dependencies and its consumers, not by vibe.
src/shared/— the lowest layer of the package: isomorphic, dependency-free code that must run in the browser bundle and Node. Everything else in the package may import it, and it imports nothing else in the package (nocore/,schemas/,connectors/,webapp/, orlib/— enforced by keeping it a leaf; verify withpnpm lint:circular). It may not touchnode:builtins or any server-only dependency. It may encode a small amount of domain knowledge (tool names, card-name parsing, markdoc config, nav routes) as long as it stays bundler-safe. Examples:invariant/assertNever,is-record,error-guards,result,awake-timeout. A helper both the frontend and the backend need lives here — there is no separate re-export shim; the implementation itself moves toshared/and every importer, frontend and backend alike, imports it from there directly (via@shared/<name>in the frontend, a relative import in the backend). Ref/path algebra lives here, in exactly one module:shared/ref-path/core.ts(parseRef,resolveRefPath) owns the 3-form rule and fail-closed containment for every in-box ref, backend and frontend alike — building onshared/attach-path.ts(attach-scope naming) andshared/box-path.ts(the box-relative canonical form). A new consumer imports it; it never re-derives the rules withpath.resolveor a segment split.src/lib/— generic, backend-only cross-cutting utilities that may use Node (node:builtins, server-only deps) and may importshared/(a downward edge, not a cycle), but nevercore/,schemas/,connectors/, orwebapp/. Examples:content-hash,mimetype,file-exists,public-url,atomic-write(writeFileAtomic, crash-safe whole-file replacement for small state and credential stores),git*/paths/box-shape(promoted fromcli/libin the Track G reorg),time(getBoxTime),format(chalk),box-config. Check here before writing your own — a hand-rolled copy of something already inlib/is the regrowth pattern this directory exists to prevent. If alib/helper turns out to be dependency-free and the frontend also needs it, move the implementation down intoshared/rather than reaching across the boundary —lib/never imports upward intoshared/'s consumers, andshared/never imports back intolib/.src/types/— ambient.d.tsdeclarations only: module augmentations and global/ambient types (e.g. thebeebox/view-widgetsspecifier surface). A real module that exports runtime or interface values does not belong here — it goes incore/(domain contract) orlib/(generic).types/views.tswas the counterexample that moved tocore/views/types.tsbecause it importedcore/(a layer inversion).src/cli/lib/— CLI/session-domain helpers coupled tocore/, not generic. What remains after the Track G promotion is session-transcript parsing (session*.ts, which importcore/chat/*) andfetch.ts(importsschemas/). Because these depend upward oncore/schemas, they can't live in the dependency-freelib/. Don't add a generic utility here — that's what re-created the "secondlib/" the reorg collapsed; put generic helpers insrc/lib/.
Rule of thumb: isomorphic and dependency-free → src/shared/; backend-only generic helper → src/lib/; ambient .d.ts → src/types/; core-coupled CLI/session helper → src/cli/lib/.