Business need
Every Beaker Stack fork diverges immediately — that is expected and healthy. Adopters customize branding, marketing, pricing, policies, dashboard UI, and eventually their own database schema. They rarely modify core template features (admin panel, billing package, email packages, waitlist, observability).
The problem today: adopter-owned customization is scattered across the repo (packages/shared, apps/web/src/billing/, apps/web/public/landing/, assets/, mobile mirrors, etc.). When a fork merges a new CalVer template tag, conflicts land in files the adopter edited for branding, demo dashboard, or config — mixed together with template wiring they never intended to own.
This makes template upgrades painful and unpredictable. Release notes cannot reliably say "these paths are safe" vs "expect conflicts here." The rename script walks the entire repo and leaves template code mutated, which further drifts forks from upstream.
What we want: a clear ownership model so fork owners can:
- Customize their adopter app (one app per fork, web + mobile) in a single predictable tree
- Merge upstream template updates with conflicts concentrated in
template/ and packages/, not in their app code
- Rebrand via
npm run rename without touching template code at all
- Add adopter-specific database schema, integration tests, and pgTAP tests without fighting template migrations
This directly supports the product promise in README: "Fork the template; update the packages."
Expected adopter workflow (after this work)
| Adopter customizes |
Lives in |
| Branding, logo, colors, legal |
adopter/config/, adopter/assets/ |
| Landing copy + marketing images |
adopter/config/landing.ts, adopter/assets/landing/ |
| Plan tiers / feature keys |
adopter/config/billing.ts |
| Policies, waitlist copy, email personalization |
adopter/content/, adopter/config/, adopter/email/ |
| Dashboard, custom pages/components (web + mobile) |
adopter/web/, adopter/mobile/ |
| Adopter database schema |
adopter/db/migrations/ |
| Adopter tests (unit, integration, db, e2e) |
adopter/**/__tests__/, adopter/tests/, adopter/e2e/ |
| Adopter rarely edits |
Lives in |
| Admin, billing shells, auth pages, landing section components |
apps/*/src/template/ |
| Framework packages |
packages/ (@beakerstack/*) |
| Template migrations, CI, setup scripts |
supabase/migrations/, .github/, scripts/ |
Typical upgrade after reorg:
git merge 2026.NNN
# conflicts in apps/*/src/template/** and infra — not in adopter/
npm install
supabase db reset && npm run db:apply-adopter
npm run type-check && npm run test
Naming model
Three tiers — avoid confusing "product" (billing tiers) with "adopter app":
| Tier |
Form |
Where |
| Template scope |
@beakerstack/* |
packages/, imports in apps — stays in template code |
| Demo product name |
Beaker Stack, logo, landing copy/images |
adopter/ only |
| Demo app identifiers |
beakerstack, productId: 'beakerstack' |
adopter/config/ only — what npm run rename rewrites |
Template code uses neutral local names (billingConfig, waitlistConfig). No beakerstack* file/export/class names outside adopter/.
Proposed layout (summary)
adopter/ # Entire tree = adopter-owned
config/ # branding, legal, colors, landing, billing, waitlist
content/ # policy markdown
assets/ # icon, landing images
email/ # .personalization.json
web/ # pages, components, routes.tsx
mobile/ # screens, components, navigation.tsx
db/migrations/ # adopter schema (never supabase/migrations/)
tests/integration/ # adopter Jest integration tests
tests/db/ # adopter pgTAP tests
e2e/web/specs/, e2e/mobile/flows/
apps/web/src/template/ # admin, billing wiring, landing components, auth pages
apps/mobile/src/template/ # mirror template wiring
Key structural changes:
configureAdopter() bootstrap — extract branding/legal/colors/policies from @beakerstack/shared into adopter/config/; shared package reads injected config at runtime
- Route/screen registries —
adopter/web/routes.tsx and adopter/mobile/navigation.tsx imported by thin App.tsx / AppNavigator.tsx
- Adopter-only rename —
npm run rename scans only adopter/**; template code stays byte-identical to upstream
- Adopter DB workflow —
supabase db reset (template) → npm run db:apply-adopter (adopter migrations on top)
- Test split — template tests in
packages/, template/**, tests/integration/, supabase/tests/; adopter tests under adopter/
Acceptance criteria
Layout & ownership
Naming & rename
Database
Tests
Upgrade ergonomics
Quality gates
Phased rollout
Phase 1 — Document + physical moves (low risk)
- Create
adopter/ tree; move config files, landing images
- Add
apps/*/src/template/; move admin + billing wiring
- Add
docs/CUSTOMIZING.md
- Begin test moves (adopter config tests →
adopter/config/__tests__/)
Phase 2 — Shared decoupling + naming (medium)
- Extract branding/legal/colors/policies from
@beakerstack/shared
- Add
configureAdopter() bootstrap
- Neutral export names; rewrite rename script (adopter-only)
- Move dashboard demo to
adopter/web + adopter/mobile
Phase 3 — Upgrade ergonomics + test/DB completion (polish)
- Route/screen registries,
.gitattributes, release notes automation
adopter/e2e/, adopter/tests/integration/, adopter/tests/db/
db:apply-adopter, migration:new:adopter, adopter test scripts
Success criteria (outcome)
After shipping, a fork owner who has customized branding, dashboard, and added adopter DB schema can merge a CalVer tag with:
- Zero conflicts in
adopter/** (or auto-preserved via merge driver)
- Conflicts only in
apps/*/src/template/**, supabase/migrations/, and infra they expect to review
- Rebrand without any template code diff
- Day-to-day CI via adopter test slice; full template suite after upgrades
Related docs
Business need
Every Beaker Stack fork diverges immediately — that is expected and healthy. Adopters customize branding, marketing, pricing, policies, dashboard UI, and eventually their own database schema. They rarely modify core template features (admin panel, billing package, email packages, waitlist, observability).
The problem today: adopter-owned customization is scattered across the repo (
packages/shared,apps/web/src/billing/,apps/web/public/landing/,assets/, mobile mirrors, etc.). When a fork merges a new CalVer template tag, conflicts land in files the adopter edited for branding, demo dashboard, or config — mixed together with template wiring they never intended to own.This makes template upgrades painful and unpredictable. Release notes cannot reliably say "these paths are safe" vs "expect conflicts here." The rename script walks the entire repo and leaves template code mutated, which further drifts forks from upstream.
What we want: a clear ownership model so fork owners can:
template/andpackages/, not in their app codenpm run renamewithout touching template code at allThis directly supports the product promise in README: "Fork the template; update the packages."
Expected adopter workflow (after this work)
adopter/config/,adopter/assets/adopter/config/landing.ts,adopter/assets/landing/adopter/config/billing.tsadopter/content/,adopter/config/,adopter/email/adopter/web/,adopter/mobile/adopter/db/migrations/adopter/**/__tests__/,adopter/tests/,adopter/e2e/apps/*/src/template/packages/(@beakerstack/*)supabase/migrations/,.github/,scripts/Typical upgrade after reorg:
Naming model
Three tiers — avoid confusing "product" (billing tiers) with "adopter app":
@beakerstack/*packages/, imports in apps — stays in template codeBeaker Stack, logo, landing copy/imagesadopter/onlybeakerstack,productId: 'beakerstack'adopter/config/only — whatnpm run renamerewritesTemplate code uses neutral local names (
billingConfig,waitlistConfig). Nobeakerstack*file/export/class names outsideadopter/.Proposed layout (summary)
Key structural changes:
configureAdopter()bootstrap — extract branding/legal/colors/policies from@beakerstack/sharedintoadopter/config/; shared package reads injected config at runtimeadopter/web/routes.tsxandadopter/mobile/navigation.tsximported by thinApp.tsx/AppNavigator.tsxnpm run renamescans onlyadopter/**; template code stays byte-identical to upstreamsupabase db reset(template) →npm run db:apply-adopter(adopter migrations on top)packages/,template/**,tests/integration/,supabase/tests/; adopter tests underadopter/Acceptance criteria
Layout & ownership
adopter/directory exists with documented zone map indocs/CUSTOMIZING.mdadopter/apps/web/src/template/andapps/mobile/src/template/adopter/web/andadopter/mobile/as replaceable starterApp.tsxandAppNavigator.tsximport adopter routes/screens via stable registry modulesNaming & rename
billingConfig,waitlistConfig); demoproductId: 'beakerstack'anddisplayName: 'Beaker Stack'live only inadopter/config/@beakerstack/*npm scope unchanged in template codenpm run renamescopes exclusively toadopter/**— no changes toapps/,packages/,supabase/,scripts/,docs/,.github/docs/renaming.mdupdated for adopter-only rename modelDatabase
adopter/db/migrations/established; adopter schema never added tosupabase/migrations/npm run db:apply-adopterapplies adopter migrations after template resetnpm run migration:new:adopterscaffolds adopter migration filesadopter/db/types/separately from template typesTests
adopter/**/__tests__/apps/*/src/template/**/__tests__/andpackages/*tests/integration/adopter/tests/integration/withtest:integration:adoptersupabase/tests/; adopter pgTAP inadopter/tests/db/withtest:db:adopteradopter/e2e/; functional flow E2E stays intests/e2e/web/specs/adopter/config/branding.ts)docs/TESTING.mddecision matrix updatedUpgrade ergonomics
docs/UPGRADING.mddocuments conflict resolution: keepadopter/**, accept upstream fortemplate/**andpackages/**.gitattributesadopter/** merge=oursdocumentednpm run upgrade:checkscriptQuality gates
npm run type-checkpassesPhased rollout
Phase 1 — Document + physical moves (low risk)
adopter/tree; move config files, landing imagesapps/*/src/template/; move admin + billing wiringdocs/CUSTOMIZING.mdadopter/config/__tests__/)Phase 2 — Shared decoupling + naming (medium)
@beakerstack/sharedconfigureAdopter()bootstrapadopter/web+adopter/mobilePhase 3 — Upgrade ergonomics + test/DB completion (polish)
.gitattributes, release notes automationadopter/e2e/,adopter/tests/integration/,adopter/tests/db/db:apply-adopter,migration:new:adopter, adopter test scriptsSuccess criteria (outcome)
After shipping, a fork owner who has customized branding, dashboard, and added adopter DB schema can merge a CalVer tag with:
adopter/**(or auto-preserved via merge driver)apps/*/src/template/**,supabase/migrations/, and infra they expect to reviewRelated docs