feat(lifeboard): shell, family profiles and the tap layer - #314
Open
ivanmkc wants to merge 1 commit into
Open
Conversation
Generalises the log from one collection to many. Every later subsystem needs to persist something -- profiles, events, contacts, recipes, packs, jobs -- and a store per collection would give each its own idea of who wrote what and when, which is exactly what provenance cannot be reconstructed without. The shell is three regions that never move: rail, board, console. Only the board area is ever redrawn, so a slow or failed turn cannot leave you without a way to navigate or to type. Profiles scope by space -- shared plus your own -- as a filter over one log rather than separate stores, so moving something to the shared list is a field change and not a migration. A PIN is a speed bump, and the UI says so in those words rather than implying security it does not have. The tap layer keeps two things apart that are easy to conflate: a mutating tap writes the store, redraws synchronously and never calls a model; an asking tap starts a turn. Conflating them is how an app spends money and three seconds on ticking a box. The transcript is persisted as message records, per profile. An undo closure cannot be stored, but the sequence numbers of the entries it would reverse can, and those rebuild it exactly -- so undo still works after a reload. Fixes a write-serialisation bug the e2e caught: two concurrent applies both read the same highest sequence number and minted colliding keys, so IndexedDB rejected the second and the write was silently lost. A tick and the transcript line describing it are exactly that pair. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 2, 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.
The stack
localStoragefor the test suite on Node 25@ivanmkc/termchart-canvas; injectable interact transportEvery branch in the stack was checked out on its own and verified independently:
npm install,tsc --noEmitfor every package,npm testacross the workspace, and the offline e2e where it exists. All twenty are green — no branch depends on a later one to build or pass. Full viewer e2e (11 suites, 106 assertions) was run on #312 as the behaviour-preservation evidence for the extraction.Stack position: 4 of 10. Base:
lifeboard/02-foundation.Generalises the log from one collection to many, and builds the shell on it.
Why the generalisation is here rather than later. Every subsystem after this one needs to persist something — profiles, events, contacts, recipes, packs, jobs. A store per collection would give each its own idea of who wrote what and when, which is exactly what provenance cannot be reconstructed without.
The shell is three regions that never move: rail, board, console. Only the board area is ever redrawn, so a slow or failed turn cannot leave you without a way to navigate or to type.
Profiles scope by space — shared plus your own — as a filter over one log rather than separate stores, so moving something to the shared list is a field change and not a migration. A PIN is a speed bump, and the UI says so in those words rather than implying security it does not have.
The tap layer keeps two things apart that are easy to conflate: a mutating tap writes the store, redraws synchronously and never calls a model; an asking tap starts a turn. Conflating them is how an app spends money and three seconds on ticking a box.
A bug the e2e caught, not a refactor. Two concurrent
applycalls both read the same highest sequence number and minted colliding keys, so IndexedDB rejected the second and the write was silently lost. A tick and the transcript line describing it are exactly that pair. Writes are now serialised, with a regression test.Verified: 82 unit tests, offline e2e extended to cover the console line and undo.
🤖 Generated with Claude Code