fix: vendor web-shared via git subtree, unblock frontend CI#2
Merged
Conversation
git-subtree-dir: web/ui/src/shared git-subtree-split: a5195d42958310e8d1cbc14a7d5349d30115b251
Previously web/ui/vite.config.ts and tsconfig.app.json resolved
@beacon-shared/* via a path relative to a sibling directory outside
the pilot repo:
"@beacon-shared": "../../../web-shared"
That worked on the author's laptop (where the beacon monorepo checkout
sits with all four app repos alongside a web-shared dir) but broke
every CI run, since GitHub runners only check out pilot and the
sibling dir doesn't exist. The consequence was `npm run build` and
`npm run test` both dying on `Failed to resolve import
"@beacon-shared/ErrorBoundary"` and every dependent test failing.
Vendors web-shared into pilot's own tree at web/ui/src/shared/ via
`git subtree add --prefix=web/ui/src/shared
https://github.com/beacon-stack/web-shared.git main --squash` (the
parent commit on this branch), then rewires the Vite alias and the
tsconfig path map to point at the vendored copy instead.
Why vendoring and not a published npm package:
- web-shared ships .tsx source, not compiled JS. Publishing requires
a build step (tsup or tsc), an exports map, peerDependencies for
react/react-dom, and a GitHub Packages publish flow. That's half a
day of work, not 45 minutes.
- The existing React singleton hack in vite.config.ts (forcing react
and react-dom to the service's own node_modules to avoid shipping
two copies) is fragile and would get worse as an installed package
— any React-coupled transitive dep in a published web-shared could
bypass it.
- Keeping the "@beacon-shared/*" alias means zero import changes in
any consumer file. Every existing `import X from "@beacon-shared/Y"`
still resolves correctly.
Upstream bugfix flow: `git subtree pull --prefix=web/ui/src/shared
https://github.com/beacon-stack/web-shared.git main --squash`. A fix
made in a pilot component can be pushed back upstream with `git
subtree push`.
Graduates to a real published package when any of these fire:
(1) a fourth consumer joins, (2) web-shared needs a React-coupled
transitive dep (Radix, Framer Motion, react-hook-form), (3) two
consumers need the same component to behave differently, (4) a
non-Beacon project wants to consume it. None are true today.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This was referenced Apr 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Unblocks pilot's frontend test job which was failing in CI with:
```
Failed to resolve import "@beacon-shared/ErrorBoundary" from "src/components/ErrorBoundary.tsx".
```
The cause was a sibling-directory path alias in web/ui/vite.config.ts that resolved `@beacon-shared/*` to `../../../web-shared/` — a directory that only exists on the original dev's laptop and not on GitHub runners. Identical structural issue to the Go `replace` directives we cleared earlier with pulse v0.1.0.
What this PR does
No import changes anywhere in the pilot codebase — every existing `import X from "@beacon-shared/Y"` still resolves correctly. ~15 files referencing `@beacon-shared` are untouched.
Why `git subtree` instead of publishing `web-shared` as a real npm package
Debated this with both a devil's advocate and an architect agent. Option B (publish via git dep or GitHub Packages) is the long-term-right answer, but requires `web-shared` to grow a build step (tsup → `dist/`), an `exports` map, `peerDependencies` for react/react-dom, and a publish flow — half a day minimum, not 45 minutes. The existing Vite React singleton hack also gets harder to reason about once the dep is installed via npm instead of aliased in-tree, because resolution depends on `peerDependencies` that haven't been written.
Vendoring with subtree gives us:
The React singleton dedupe hack in `vite.config.ts` is kept as free insurance.
Canaries for graduating to a real published package
Per the architect review, upgrade to Option B when any of these fire:
None are true today.
Verification
```
cd web/ui
npm run build # ✓ builds cleanly, 1.51s
npm test # ✓ 69/69 tests pass, 898ms
```
Test plan