Skip to content

fix: vendor web-shared via git subtree, unblock frontend CI#2

Merged
davidfic merged 3 commits intomainfrom
fix/frontend-vitest-transform-errors
Apr 15, 2026
Merged

fix: vendor web-shared via git subtree, unblock frontend CI#2
davidfic merged 3 commits intomainfrom
fix/frontend-vitest-transform-errors

Conversation

@davidfic
Copy link
Copy Markdown
Contributor

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

  1. Creates a new upstream repo `beacon-stack/web-shared` holding the 8 shared React components and their helpers.
  2. Vendors it into pilot via `git subtree add --prefix=web/ui/src/shared https://github.com/beacon-stack/web-shared.git main --squash`. The commit `Merge commit ... as 'web/ui/src/shared'` is the squash-merge of the upstream content.
  3. Rewires the Vite alias from `../../../web-shared` → `./src/shared`.
  4. Rewires the `tsconfig.app.json` `paths` map the same way.

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:

  • Zero build pipeline, zero `exports` map, zero `peerDependencies`.
  • Zero risk of shipping two React instances (no separate `node_modules` tree to walk).
  • Real upstream history: `git subtree pull` to receive bugfixes, `git subtree push` to send fixes made here back upstream.
  • Same CI semantics as any other in-repo source file.

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:

  1. A fourth consumer joins (N×duplication starts to bite)
  2. `web-shared` needs a React-coupled transitive dep (Radix, Framer Motion, react-hook-form) — the singleton hack stops scaling at that point
  3. Two consumers need the same component to behave differently (semver smell)
  4. A non-Beacon project wants to consume `web-shared`

None are true today.

Verification

```
cd web/ui
npm run build # ✓ builds cleanly, 1.51s
npm test # ✓ 69/69 tests pass, 898ms
```

Test plan

  • CI `Test Frontend` job green (previously failing on ErrorBoundary.test.tsx and AllSeasonsView.test.tsx)
  • CI `Build` job green
  • CI `Lint` job green
  • Upstream bugfix flow verified manually: `git subtree pull` from `beacon-stack/web-shared` after a test change

davidfic and others added 3 commits April 15, 2026 07:45
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>
@davidfic davidfic merged commit ab17f35 into main Apr 15, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant