ADR-0002: Use a pnpm workspace monorepo for site, packages, and labs
Read the transcript
1. Three kinds of code, one problem
Host: Welcome back. Today we’re digging into ADR-0002, which is really about the shape of a single-maintainer OSS platform before we even get to the tooling decision. There’s the handbook site itself, there are the reusable pieces it leans on like design tokens and MDX components, and then there’s this whole separate world of Python labs that the docs reference but don’t build with.
Guest: Right, and one of those labs is important to call out — the async AI gateway example wasn’t born inside this repo, it was already its own standalone Python project with its own CI before the platform showed up. So you’ve got three kinds of code that all need to feel coherent to a solo maintainer, but they absolutely should not share a build. The real question driving this decision is how to let a shared component change without forcing a publish-and-bump cycle every time, while the labs just keep doing their own Python thing untouched.
2. Weighing the layouts
Host: So walk me through the actual options you weighed here, because I imagine separate repos was the first instinct — it’s the classic clean-boundaries move. Why didn’t that hold up?
Guest: It’s clean right up until you need a cross-cutting change, like a new doc component the site and some future second app both need — then you’re publishing a package and bumping a version just to unblock yourself, alone, with no one else’s PRs to coordinate against. The next option, a flat repo with relative imports like dot-dot-slash packages slash ui slash src, is even more tempting because it’s zero setup, and it actually works fine until a package needs its own package.json or its own test runner — then that missing workspace tooling turns into friction on every single addition. That’s what pushed me to a pnpm workspace, with labs deliberately left out of it since it’s Python and shouldn’t be something this workspace’s tooling ever tries to build.
3. The decision and its trade-offs
Host: So the actual decision landed exactly where you were pointing: a pnpm workspace with pnpm-workspace.yaml covering apps and packages, workspace-star dependencies between them, and labs pointedly living outside all of that with its own pyproject.toml, pytest, ruff, mypy, and its own CI job. What does that buy you day to day?
Guest: The concrete payoff is that a change to packages slash components shows up in apps slash handbook on the next dev server reload — no publish, no version bump, no registry round trip. Root config for ESLint, Prettier, Vitest, and the base TypeScript setup is defined once and extended per package, so a fourth package means adding a package.json and a tsconfig that extends the root, not re-deriving lint rules from scratch, and pnpm’s content-addressable store keeps node_modules sane across packages that all share Astro and TypeScript instead of duplicating that tree everywhere. The one honest cost is that someone who only wants the Python labs still clones the whole repository — there’s no slicing that out — but since the whole point of this project is that the platform and the labs are meant to be read together, that’s a cost I accepted going in, not something I overlooked.
Generated from this page by Claude Sonnet 5 on , spoken by Kokoro-82M running locally. Two synthetic voices, not a recorded conversation. Every claim is drawn from this page — where it differs from the text above, the text is correct.
Status
Section titled “Status”Accepted. pnpm-workspace.yaml and the workspace:* dependencies between packages/* and
apps/handbook reflect this decision directly.
Context
Section titled “Context”The platform has three kinds of TypeScript/Astro code that need to share conventions without
sharing a build: the documentation site itself (apps/handbook), reusable pieces it depends on
(design tokens, MDX doc components, the Mermaid wrapper), and — separately — Python production labs
under labs/ that the docs reference but don’t build with. examples/async-ai-gateway already
existed as a standalone Python project with its own CI workflow before this platform did.
Problem
Section titled “Problem”How should the handbook site, its reusable component packages, and the (Python, not Node) labs be laid out so that a change to a shared component doesn’t require publishing a package and bumping a version, while the labs keep their own independent tooling and CI?
Options
Section titled “Options”- Separate repositories for the site, the component packages, and each lab. Clean ownership boundaries, but every cross-cutting change (e.g. a new doc component used by both the site and a future second app) means coordinating a publish across repos before the consuming repo can pick it up. For a single-maintainer OSS project at this stage, that overhead has no offsetting benefit.
- A single flat repository with no workspace tooling, importing shared code via relative paths
(
../../packages/ui/src/...). Works until a package needs its ownpackage.json, dependencies, or test runner — at which point the lack of workspace tooling becomes the thing fighting every addition. - A pnpm workspace monorepo, with
labs/deliberately excluded from the workspace since it’s Python, not a package this workspace’s tooling should try to build.
Decision
Section titled “Decision”Use a pnpm workspace (pnpm-workspace.yaml covering apps/* and packages/*) with
workspace:* protocol dependencies between packages. labs/ stays outside the workspace — it’s
addressed by path from documentation (repoPath frontmatter, LabCallout) but has its own
per-language tooling (pyproject.toml, pytest, ruff, mypy) and its own CI job, unrelated to
the Node toolchain.
Consequences
Section titled “Consequences”- A change to
packages/componentsis immediately visible toapps/handbookon the next dev server reload — no publish step, no version bump, no registry. - Root-level tooling (ESLint, Prettier, Vitest, TypeScript’s base config) is defined once and
extended per package, so adding a fourth package means adding a
package.jsonand atsconfig.jsonthat extends the root, not re-deriving lint rules. pnpm’s content-addressable store keepsnode_modulessize sane across packages that share most of their dependency tree (Astro, TypeScript) without pnpm’s workspace tooling, this would be a much largernode_modulesfootprint per package.- The trade-off: anyone who wants only the labs (Python, no interest in the docs site) still clones the whole repository. Given the project’s stated goal — the platform and the labs are meant to be read together — that’s an acceptable cost, not an oversight.
References
Section titled “References”- pnpm workspaces
- The existing
examples/async-ai-gatewaylab and its CI workflow, which predated this ADR and shaped the decision to keep Python tooling independent of the Node workspace.