All cases

2026-06 · Tools

One scaffold, four apps

Four small multi-tenant apps, a to-do board, a weather board, a trip planner and a countdown, grew up as near copies of each other. I pulled the shared part into one tested scaffold, proved it on a fifth surface first, then moved the four onto it, and every later change landed once instead of four times.

System designLegacy and migrationShipping under constraint

4

board apps on one scaffold

7

surfaces built on makeTenancy today

43

merges in Tools & Shared Boards to date

When

#896#928#960#965#1093#1267#1273#1295#1297#17742026-06-062026-07-04

Part of an arc of 2 merges on trip, 2026-06-15 to 2026-07-01. The whole arc, derived from the record

before

No capture survives of the four hand-rolled accessor files side by side before the migration. The repository keeps the record: the scaffold's own header says the four were about 95 percent identical, and the migration that removed them is cited.

during

The factory every board stands on, tested against a fake database: eighteen tests, no network, no sign-in code inside.

2026-10-11 · Real output of npx vitest run src/lib/tenancy/__tests__/scaffold.test.js --reporter=verbose on a local checkout of main, rendered as text; nothing edited

after

The four boards sold side by side as one family, each created in one click, because underneath they are one scaffold.

2026-10-11 · Headless Chromium, 390 by 844, Playwright, against a local production build of main (next start)

Walkthrough video: coming soon.

/t to-dostasks, columns/w weatherlocations/trip tripsitinerary, crew/cd countdownsdates, .icsmakeTenancy: one tested factoryslugs, members, ownership caps, publishing,boards made before sign-in; fake-database testsalso under the LinkedIn studio (first), an inbox and the family hub

Problem

Each board shipped on its own schedule with its own instances and members tables and its own accessor file: create a board with a friendly slug, add and remove members, cap how many boards one account owns, list an account's boards. The four accessor files were about 95 percent the same code, so every rule meant to hold for all of them had to be carried four times and could drift four ways. Publishing a board, for one, took two separate merges: one for the weather board, one for the other three.

Constraint

All four boards were live, with real boards in them, so the extraction could not regress any one of them. Each board also keeps rules the others do not have, like a trip's itinerary or a weather board's locations, which a shared layer must leave alone.

Decision

Extract the shared part as a pure factory, makeTenancy, that takes a surface's table names and a database getter and returns the whole accessor set, with no sign-in code inside so it tests against a fake database. Prove it on a new surface first, the LinkedIn studio, then migrate the four boards onto it in one change, keeping each board's own accessors layered on top. After that, a cross-board rule goes into the scaffold once.

Rejected

  • Keep four copies and patch them in step

    Publishing already took two merges to reach all four, and every later cross-board rule would cost the same. Four copies of one rule is four chances for one board to get it wrong.

  • Merge the four boards into one generic app

    Their data really differs, and a generic board would have flattened what makes each worth opening. The shared part is tenancy, not the boards.

What shipped

The scaffold with its own tests, proven on the LinkedIn studio, then the four boards moved onto it in one change that also folded publishing and the shared accessors in. Two more surfaces, an inbox and the family hub, are built on it too. The next cross-board rule, listing the boards a signed-out visitor already made, went into the scaffold once and reached all four boards.

What I'd do differently

A shared layer shares mistakes too. The privacy review in October 2026 found that a public weather board printed each location's label, and the labels were people, next to coordinates precise to about 100 meters, so the hidden roster could be rebuilt from a public page. The fix was a public projection that drops labels and rounds coordinates, written once for the weather board, but it was found by a review, not by the scaffold.

Verify it yourself

  • The tools landing, live
  • The scaffoldsrc/lib/tenancy/scaffold.mjs
  • The scaffold's testssrc/lib/tenancy/__tests__/scaffold.test.js
  • Run the test suite, scaffold tests includednpm run test

If you ask me about this

Why prove it on LinkedIn instead of one of the boards?
A new surface had no users to break. Once the scaffold carried a real feature there, moving the boards was a refactor onto tested code instead of a rewrite of live code.
What stays out of the scaffold?
Anything one board has and the others do not: itineraries, locations, countdown dates, each board's page. The scaffold owns who may see and change a board, and nothing about what is on it.

Evidence

  1. Multi-tenant /todos: 1-click boards at /t/[slug] (+ per-user household endpoints)

    #896 · 2026-06-09 · feature · +1,713 −44, 42 files

  2. Add multi-tenant Weather boards (/w) as a marketplace app

    #928 · 2026-06-10 · feature · +1,904 −128, 35 files

  3. Trips Phase 1: the spine — trips_* schema, templates, the home role, module shell, /trip/demo

    #960 · 2026-06-11 · feature · +1,887 −0, 26 files

  4. Countdowns (/cd): the tiniest multi-tenant app — shared "N days until" boards + .ics

    #965 · 2026-06-11 · feature · +1,626 −5, 38 files

  5. linkedin: multi-tenant foundation + shared tenancy scaffold

    #1093 · 2026-06-13 · feature · +1,082 −2, 9 files

  6. Publishable /w weather boards; retire /weather into a public board

    #1267 · 2026-06-15 · feature · +463 −71, 23 files

  7. Publishable boards for /t, /trip, and /cd (the rest of the add-on family)

    #1273 · 2026-06-15 · feature · +835 −74, 31 files

  8. Tenancy: anonymous create → claim on sign-in (all four board surfaces)

    #1295 · 2026-06-15 · feature · +1,332 −137, 36 files

  9. Tenancy: daily cron to purge stale unclaimed anonymous boards

    #1297 · 2026-06-15 · feature · +166 −0, 6 files

  10. refactor(tenancy): migrate /t /w /trip /cd onto makeTenancy (is_public + shared accessors)

    #1774 · 2026-07-01 · refactor · +206 −528, 6 files

Receipts

Checked at build against the published corpus.

  • Every cited PR exists in the published corpus10 / 10
  • Pending PRs are named as pending, never passed off as mergedall merged
  • Cited PRs sit in the case's disciplinesTools, General, Distribution, Household Operations
  • Numbers with no source say so0 unmetered

www.jakelawrence.xyz/research/case-study-library/one-scaffold

Cite: Lawrence, J. (2026). One scaffold, four apps. jakelawrence.xyz case study library.

Need something like this built?

I build the AI tools and automations a team adopts, and the full-stack apps and data pipelines under them, and ship them to production. Tell me the problem in a sentence and I'll give you an honest read on fit within a day.

Work with me →