Skip to content

Architecture: Deepen the result handoff: one module at the route seam #32

Description

@Kingson4Wu

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)

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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)

  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.
  2. 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.
  3. 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.
  4. 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).
  5. 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.
  6. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions