0021. Repository layout, linting and architecture rules
Status
Section titled “Status”Accepted
2026-10-25
Context
Section titled “Context”The repository is designed to hold three different things: the browser itself with a strictly layered architecture (overview), the website with Astro, Starlight and TypeScript 6 (ADR 0020), and tooling for generators, code generation, releases, feeds and package repositories. Layer boundaries that are only conventions erode under time pressure, and a team of three cannot review every import by hand. Checks have to be fast enough to run on every save and every push, and dependencies with install scripts are a supply chain risk.
Decision
Section titled “Decision”Astela is a pnpm 12 workspace with three packages, app, website and tools, on Node 24 LTS, with architecture rules enforced by the compiler, dependency-cruiser and the linter.
-
Workspace: the root holds the shared configuration and the
pnpm verifyscript; the generators (pnpm new:*) andpnpm release:preparewill be added there. There is no Turborepo or Nx. Thewebsitepackage keeps TypeScript 6 and Astro dependencies away from the app. -
Runtime: Node 24 LTS, the same major version as the Node 24.22 inside Electron 45, pinned in
.node-version; pnpm is pinned throughpackageManager. -
Dependencies: exact versions only.
allowBuildsinpnpm-workspace.yamlis the allowlist of packages that may run install scripts; all others are blocked. -
Layout of the browser package:
app/src/shared/ pure TypeScript: IPC contracts, ids, result, events, utilitieskernel/ DI container, module system, events, lifecycle, logging APIdomain/ entities, services and ports without Electron, Node or DOMplatform/ adapters: electron/, node/, os/{windows,macos,linux}/modules/ one folder per feature module: manifest.ts, main/, ui/, api/, locales/main/ composition root, bootstrap, boot guardpreload/ capability-scoped bridges per view typeui/ ui-kit/ (base element, tokens, controls), shell/, overlay/, pages/tests/ e2e/, perf/, smoke/, updater/, screenshots/, fixtures/build/ electron-builder configuration, icons, entitlements, installer and Linux files, public keys -
TypeScript project references per layer, each with
composite:shared,kernel,domain,platform,modules-main,modules-ui,preload,uiandmain. An import from a project that is not referenced fails the type check.libandtypesare restricted per project:shared,kernelanddomainsee neither DOM nor Node types;ui,modules-uiandpreloadsee no Node types. -
dependency-cruiser enforces the layer direction and forbids cycles:
mainmay use modules,platform,domainandkernel; modules usedomain,kernelandshared;platformusesdomainandkernel;domainuseskernel;kernelusesshared;ui,modules/*/uiandpreloaduse onlyshared. In addition,electronmay be imported only inplatform/,main/andpreload/;modules/amay importmodules/bonly throughmodules/b/api;ui/never imports browser-process code; module manifests may import only data files (*.contrib.ts); and each layer has a list of forbidden packages. -
Linting: Oxlint with type-aware rules, including
no-floating-promises, plusno-restricted-importsrules that mirror the layer rules for fast feedback in the editor. Prettier formats all files, markdownlint checks Markdown, and ShellCheck checks shell scripts. -
Commits: lefthook runs commitlint on every commit message locally; in CI, the
pr-titlecheck runs commitlint on the pull request title, which becomes the squash commit message, and thecommitsjob rejects trailers and more than one author (WORKFLOW.md). -
Generators:
pnpm new:module,new:setting,new:theme,new:policy,new:adrandnew:localecreate files with test and documentation stubs that already satisfy these rules. -
One command:
pnpm verifyruns the same checks as the CIverifyjob.
Consequences
Section titled “Consequences”Positive:
- Boundary violations are expected to fail at type-check time or in CI instead of being found in review.
- Electron stays behind the
platformfacade, so Electron upgrades remain local changes. - Linting and formatting are fast enough to run on every save and in the pre-commit hook.
- The app’s toolchain stays free of website dependencies.
- The install-script allowlist reduces the impact of a compromised dependency.
Negative:
- Nine TypeScript projects and the dependency-cruiser configuration need maintenance when layers change; such changes need an ADR (ADR 0001).
- Project references, dependency-cruiser and lint rules overlap on purpose; a rule change has to be made in all of them.
- Oxlint has fewer plugins than the ESLint ecosystem, so some niche rules are unavailable.
- pnpm’s
minimumReleaseAgedelays new versions by one day by default, which also delays urgent Electron patches; an Electron security patch is exempted by its exact version, as WORKFLOW.md describes.
Alternatives considered
Section titled “Alternatives considered”- npm or Yarn workspaces: no per-package allowlist for install scripts and weaker protection against undeclared dependencies.
- Turborepo or Nx: task orchestration and caching that three packages do not need, at the price of extra configuration.
- ESLint with typescript-eslint: a mature option, but type-aware linting is much slower.
- Biome: fast, but its type-aware rules are still in the nursery group.
- A single package: TypeScript 6 and Astro dependencies would mix with the app’s TypeScript 7 toolchain.
- Layer rules by convention only: erode as the code grows and cannot be checked by a small team.