Skip to content

ADR-0002: Use a pnpm workspace monorepo for site, packages, and labs

Listen to this page3:29
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.

ADR-0002acceptedAugust 4, 2026

Accepted. pnpm-workspace.yaml and the workspace:* dependencies between packages/* and apps/handbook reflect this decision directly.

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.

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?

  • 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 own package.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.

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.

  • A change to packages/components is immediately visible to apps/handbook on 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.json and a tsconfig.json that extends the root, not re-deriving lint rules.
  • pnpm’s content-addressable store keeps node_modules size sane across packages that share most of their dependency tree (Astro, TypeScript) without pnpm’s workspace tooling, this would be a much larger node_modules footprint 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.
  • pnpm workspaces
  • The existing examples/async-ai-gateway lab and its CI workflow, which predated this ADR and shaped the decision to keep Python tooling independent of the Node workspace.