Ralph Loop: sts2dash Architecture Refactor

Use this prompt for each phase/sub-phase. Ralph should read the relevant files first, then implement, then run tests, confirming before moving on.


Global Rules (Always)


Phase 1: Schema Types

1a. Create schema/types.ts

Look at: _parse.ts (top types), _parse.ts (cleanId function), _stats.ts (uses of RoomType, Character), real run files in __tests__/fixtures/

Deliver: src/pages/tools/sts2dash/schema/types.ts with:

Verify character map against real files: Run bun --eval to check players[0].character values in fixture files. Should be: CHARACTER.IRONCLAD, CHARACTER.SILENT, CHARACTER.REGENT, CHARACTER.NECROBINDER.

Test: No new tests needed yet — just verify existing code still works (bun test)


1b. Create schema/v8.ts

Look at: _parse.ts (parseRuns function body), existing test fixtures in __tests__/fixtures/ (schema 8 files: 1772794581.run, 1773155293.run, 1774008838.run)

Deliver: src/pages/tools/sts2dash/schema/v8.ts with:

Hint: Match the actual field names and nested shapes exactly as they appear in the raw JSON. Check a real schema 8 file with bun --eval to see all unique player_stats keys. Key fields: card_choices, cards_gained, cards_removed, upgraded_cards, ancient_choice, relic_choices, bought_relics, bought_potions, potion_used, rest_site_choices, current_hp, damage_taken, hp_healed, max_hp, gold_*.


1c. Create schema/v9.ts

Look at: _parse.ts (parseRuns function body), existing test fixtures in __tests__/fixtures/ (schema 9 files: 1774708464.run, 1774719367.run, 1775938543.run)

Deliver: src/pages/tools/sts2dash/schema/v9.ts with:

Hint: Compare schema 9 vs 8 — check which fields differ. Key differences: cards_removed items now have floor_added_to_deck; cards_transformed added; relics_removed added; bought_potions replaced by potion_choices.


1d. Create schema/index.ts

Look at: _parse.ts (how it handles missing schema_version)

Deliver: src/pages/tools/sts2dash/schema/index.ts with:


1e. Create __tests__/schema.test.ts using real fixtures

Look at: __tests__/fixtures.test.ts, __tests__/fixtures/ (real .run files)

Deliver: __tests__/schema.test.ts with tests for:

Load fixtures using: JSON.parse(readFileSync(join(__dirname, 'fixtures', filename), 'utf-8')) — do NOT use the old createRun() helper pattern.


Phase 1 checkpoint: Run bun test. All schema tests pass. Then ask before proceeding.


Phase 2: Single Run Interface

2a. Define schema/run.ts

Look at: ParsedRun and RunDetail in _parse.ts, normalizeRoomType function, real run files

Deliver: src/pages/tools/sts2dash/schema/run.ts with:

Hint: The Run interface should contain EVERY field needed by _stats.ts and _app.ts. Check all usages of ParsedRun.* and RunDetail.* to make sure nothing is missed. The final deck cards have current_upgrade_level and enchantment in real v8/v9 files.


Phase 2 checkpoint: Run bun test. Then ask before proceeding.


Phase 3: IDB Stores Raw JSON Directly

3a. Rewrite _idb.ts

Look at: existing _idb.ts, REFACTOR-PLAN.md Phase 3

Deliver: Rewritten _idb.ts with:

WARNING: Bump DB_VERSION to 4. Add migration logic in onupgradeneeded to delete old object stores.


3b. Update callers of _idb.ts

Look at: _app.ts (all IDB calls), index.astro (if any IDB calls)

Deliver: Update _app.ts to:

Hint: After this step, _app.ts should only reference Run objects, never raw JSON directly. The IDB layer returns StoredRun, which gets converted to Run by the parser.


Phase 3 checkpoint: Run bun test. Open the page in browser, verify it loads without errors. Ask before proceeding.


Phase 4: Pure Stats Functions

4a. Create stats/summary.ts

Look at: _stats.tsStatsSummary and the summary-computing portion of computeStats()

Deliver: src/pages/tools/sts2dash/stats/summary.ts with:


4b. Create stats/cards.ts

Look at: _stats.tsCardPickStat and card-stat-computing portion

Deliver: src/pages/tools/sts2dash/stats/cards.ts with:


4c. Create remaining stat modules

Look at: _stats.ts → remaining stat computations

Deliver: Each in its own file:

Each should be a pure function taking Run[], returning typed output.


4d. Create stats/index.ts

Look at: _stats.tsStatsOutput interface

Deliver: src/pages/tools/sts2dash/stats/index.ts with:


Phase 4 checkpoint: Run bun test. All stat function tests pass. Then ask before proceeding.


Phase 5: Schema → Run Conversion

5a. Create schema/toRun.ts

Look at: _parse.ts (parseRuns function body, parseRunDetailForCache function body), the new Run interface

Deliver: src/pages/tools/sts2dash/schema/toRun.ts with:

Hint: This function should be the union of everything parseRuns + parseRunDetailForCache did. Read both carefully.


5b. Add toRun tests

Look at: __tests__/parse.test.ts, the JSON fixtures from Phase 1d

Deliver: __tests__/toRun.test.ts with tests covering:


5c. Remove _parse.ts

Look at: every file that imports from _parse.ts

Deliver:


Phase 5 checkpoint: Run bun test. All tests pass. Ask before proceeding.


Phase 6: Cleanup and Integration

6a. Remove old IDB state tracking

Look at: _app.ts (remaining state fields)

Deliver: Clean up _app.ts state — remove any fields that are no longer needed after Phase 3/4.


6b. Verify full flow

Look at: _app.tsfinishLoad(), loadFromCache(), renderAll()

Deliver: Verify the full flow works:

  1. Load files from disk → parseRuns() (was: dual objects)
  2. Convert to Run[] via toRun()
  3. Save raw JSON to IDB via saveRun()
  4. Load from IDB → convert to Run[] via toRun()
  5. Compute stats via computeStats(Run[])
  6. Render charts and table

All should work without ParsedRun or RunDetail.


6c. Full test suite

Run: bun test --all

Deliver: All tests pass. If any fail, investigate and fix.


Phase 6 checkpoint: Ask before declaring done.


Completion Checklist