Deepen the result handoff: one module at the route seam
Unattended architecture survey of mbti-lab, 2026-09-22. Explore + report stages only; no code changed.
No CONTEXT.md and no ADRs exist. docs/superpowers/specs/* are approved feature specs (closest thing to recorded decisions). Git history is one squashed commit, so no hot-spot signal — the whole tree was scanned. npm test: 55 tests / 7 files, all passing (after npm ci; node_modules was absent).
Findings (evidence)
- Dead modules at zero production call sites.
src/utils/formatMbtiType.js and src/utils/resultSummary.js are imported only by their own tests (tests/formatMbtiType.test.js, tests/ResultSummary.test.js — 162 lines testing uncalled code). Grep confirms no src/ import of either. They encode exactly the validation the real read seam lacks: ResultView.vue:46-49 trusts JSON.parse of sessionStorage['mbti_result'], and ResultCard.vue:75 does types[props.type] with no fallback (ShareCard has one, ResultCard does not) — a malformed stored type crashes the template.
- Leaky result-transport seam. Three magic keys across four files:
sessionStorage['mbti_result'] (written TestView.vue:53, read ResultView.vue:46, never cleared), localStorage['mbti_lab_result'] (hidden behind useHistory.js), sessionStorage['mbti_short'] (HomeView.vue:65 → TestView.vue:40). Fresh-then-history precedence, completedAt stamping, and shape knowledge are spread across the views. useHistory is shallow (three pass-through functions; clearResult also has zero call sites); it fails the deletion test — deleting it just inlines three storage calls. Consequence: HomeView's "view last result" link can show a stale session entry instead of the persisted history result.
- The deepest module is untested.
src/composables/useTest.js (scores → dimension percents → ≥50 tie-break → type → result) has small interface, rich implementation — and zero test imports. The suite tests views, share UI, i18n keys, and the dead validators instead.
- Shallow prop shape forces fabricated data.
ResultCard.vue requires percents, so TypesView.vue builds mockPercents (hard-coded 65/35, duplicating the opposite-letter map) just to browse type profiles. Type groups (Analysts/Diplomats/Sentinels/Explorers) and GROUP_COLORS live in the view, split from data/types.js.
- The dimension fact is copied six times (useTest
OPPOSITE, formatMbtiType POSITIONS, resultSummary DIMENSIONS, DimensionBars FIRST/SECOND, RadarChart indicator order, TypesView opp map). Static domain data with no behaviour — consolidating standalone would itself be shallow.
- Share seam plumbing (minor).
ResultView.vue ferries SharePanel's click to ShareCard via :onSaveImage="() => shareCardRef?.capture()" — callback prop plus template-ref escape hatch. The approved spec docs/superpowers/specs/2026-03-28-sharing-design.md pins this prop shape; friction today is cosmetic.
Candidates
| # |
Deepening |
Strength |
Dependency category |
| 1 |
Result store: deepen useHistory into submit(result) / load() / clear(), absorbing keys, JSON, precedence, stamping, and the dead validators' shape checks |
Strong |
local-substitutable (tests stub storage — already done in tests/ResultView.test.js) |
| 2 |
Test the scoring engine through its existing interface (loadQuestions → answer → result): tie-breaks, short-version filter, partial answers, result shape |
Worth exploring |
in-process |
| 3 |
Split ResultCard into TypeProfile (interface: type) and ScoreViz (interface: percents); delete mockPercents; move groups/colors into data/types.js |
Worth exploring |
in-process |
| 4 |
Consolidate the dimension fact |
Speculative — do only as a side effect of 1–3 |
in-process |
| 5 |
SharePanel emits save-image instead of the onSaveImage prop |
Speculative — contradicts approved spec, reopen only if sharing grows |
in-process |
Top recommendation
Candidate 1. It is the only candidate where the deletion test already fails today (dead validators, unused clearResult), where real bugs live at the seam (blind JSON.parse, no fallback in ResultCard, stale-session shadowing), and where the seam is real rather than hypothetical: two adapters (session storage for the fresh handoff, localStorage for history) already sit behind it, plus a third in tests (in-memory stub). One interface, three views simplified, dead code deleted. Full visual report: .ralph/architecture.html.
Acceptance criteria (for candidate 1)
- A single module (e.g.
src/composables/useResultStore.js) exposes at most three functions covering submit, load (with fresh-then-history precedence), and clear; the functions useHistory exports today are gone or absorbed.
- No view file contains the literals
'mbti_result' or 'mbti_lab_result'; key names, JSON encoding/decoding, completedAt stamping, and precedence live behind the store's interface.
load() returns null for malformed entries (invalid type letters — the rule formatMbtiType encodes — or non-finite percents); a corrupted sessionStorage entry renders ResultView's no-result branch instead of crashing ResultCard.
- A stale
sessionStorage entry no longer shadows the persisted history result when arriving via HomeView's "view last result" link (read-and-consume or equivalent precedence fix, covered by a test).
src/utils/formatMbtiType.js and its tests are deleted (validation absorbed) or wired to a production call site; clearResult is either called somewhere or removed from the interface.
- New tests at the store's interface (stubbed storage) cover: precedence, stamping, malformed → null, round-trip; existing
tests/ResultView.test.js, tests/ShareCard.test.js, tests/SharePanel.test.js pass unchanged or with only prop-name adjustments; npm test and npm run build are green.
Out of scope for this survey
Candidates 2–5 need no decision now; the grilling stage (interactive) was intentionally skipped per the unattended brief. No project files were modified — only .ralph/architecture.html and .ralph/architecture.md were written.
Skill source: mattpocock/skills@c55ee46
Run and report artifact
Deepen the result handoff: one module at the route seam
Unattended architecture survey of mbti-lab, 2026-09-22. Explore + report stages only; no code changed.
No CONTEXT.md and no ADRs exist.
docs/superpowers/specs/*are approved feature specs (closest thing to recorded decisions). Git history is one squashed commit, so no hot-spot signal — the whole tree was scanned.npm test: 55 tests / 7 files, all passing (afternpm ci; node_modules was absent).Findings (evidence)
src/utils/formatMbtiType.jsandsrc/utils/resultSummary.jsare imported only by their own tests (tests/formatMbtiType.test.js,tests/ResultSummary.test.js— 162 lines testing uncalled code). Grep confirms nosrc/import of either. They encode exactly the validation the real read seam lacks:ResultView.vue:46-49trustsJSON.parseofsessionStorage['mbti_result'], andResultCard.vue:75doestypes[props.type]with no fallback (ShareCard has one, ResultCard does not) — a malformed stored type crashes the template.sessionStorage['mbti_result'](writtenTestView.vue:53, readResultView.vue:46, never cleared),localStorage['mbti_lab_result'](hidden behinduseHistory.js),sessionStorage['mbti_short'](HomeView.vue:65→TestView.vue:40). Fresh-then-history precedence,completedAtstamping, and shape knowledge are spread across the views.useHistoryis shallow (three pass-through functions;clearResultalso has zero call sites); it fails the deletion test — deleting it just inlines three storage calls. Consequence: HomeView's "view last result" link can show a stale session entry instead of the persisted history result.src/composables/useTest.js(scores → dimension percents → ≥50 tie-break → type → result) has small interface, rich implementation — and zero test imports. The suite tests views, share UI, i18n keys, and the dead validators instead.ResultCard.vuerequirespercents, soTypesView.vuebuildsmockPercents(hard-coded 65/35, duplicating the opposite-letter map) just to browse type profiles. Type groups (Analysts/Diplomats/Sentinels/Explorers) andGROUP_COLORSlive in the view, split fromdata/types.js.OPPOSITE, formatMbtiTypePOSITIONS, resultSummaryDIMENSIONS, DimensionBarsFIRST/SECOND, RadarChart indicator order, TypesViewoppmap). Static domain data with no behaviour — consolidating standalone would itself be shallow.ResultView.vueferries SharePanel's click to ShareCard via:onSaveImage="() => shareCardRef?.capture()"— callback prop plus template-ref escape hatch. The approved specdocs/superpowers/specs/2026-03-28-sharing-design.mdpins this prop shape; friction today is cosmetic.Candidates
useHistoryintosubmit(result) / load() / clear(), absorbing keys, JSON, precedence, stamping, and the dead validators' shape checkstests/ResultView.test.js)loadQuestions → answer → result): tie-breaks, short-version filter, partial answers, result shapeResultCardinto TypeProfile (interface:type) and ScoreViz (interface:percents); deletemockPercents; move groups/colors intodata/types.jssave-imageinstead of theonSaveImagepropTop recommendation
Candidate 1. It is the only candidate where the deletion test already fails today (dead validators, unused
clearResult), where real bugs live at the seam (blindJSON.parse, no fallback in ResultCard, stale-session shadowing), and where the seam is real rather than hypothetical: two adapters (session storage for the fresh handoff, localStorage for history) already sit behind it, plus a third in tests (in-memory stub). One interface, three views simplified, dead code deleted. Full visual report:.ralph/architecture.html.Acceptance criteria (for candidate 1)
src/composables/useResultStore.js) exposes at most three functions covering submit, load (with fresh-then-history precedence), and clear; the functionsuseHistoryexports today are gone or absorbed.'mbti_result'or'mbti_lab_result'; key names, JSON encoding/decoding,completedAtstamping, and precedence live behind the store's interface.load()returnsnullfor malformed entries (invalid type letters — the ruleformatMbtiTypeencodes — or non-finite percents); a corruptedsessionStorageentry renders ResultView's no-result branch instead of crashing ResultCard.sessionStorageentry no longer shadows the persisted history result when arriving via HomeView's "view last result" link (read-and-consume or equivalent precedence fix, covered by a test).src/utils/formatMbtiType.jsand its tests are deleted (validation absorbed) or wired to a production call site;clearResultis either called somewhere or removed from the interface.tests/ResultView.test.js,tests/ShareCard.test.js,tests/SharePanel.test.jspass unchanged or with only prop-name adjustments;npm testandnpm run buildare green.Out of scope for this survey
Candidates 2–5 need no decision now; the grilling stage (interactive) was intentionally skipped per the unattended brief. No project files were modified — only
.ralph/architecture.htmland.ralph/architecture.mdwere written.Skill source: mattpocock/skills@c55ee46
Run and report artifact