Plans were split across three trees: plans/, web/plans/, and android/plans/. Merge them into plans/ at the repo root. Sweep the shipped Android experience pass and the two reports for it, carrying the residual forward into plans/todo.md: the device QA checklist (open only because this environment has no device or emulator), the clipped web PWA icons defect, and the untested native assumptions. Merge android/plans/todo.md in the same pass, dropping items the experience pass already delivered -- haptics, back button, safe-area insets, launcher icon, and splash -- each verified against MainActivity.java, web/src/app.css, and the res/ tree. Correct two stale claims while merging: web/ is a plain directory in this monorepo, not a git submodule, and the serialize-javascript override is held by @rollup/plugin-terser rather than workbox-build. The retired native Kotlin/Compose port plan moves as-is; it is superseded by the Capacitor wrapper but not yet swept.
3.7 KiB
Phase 05 — Extract auto-tick into pure helper (OPTIONAL)
Context
Upstream commit 3e6cb90 (2026-04-28). Pulled the bus-driven auto-tick
effect body out of PlayerBoard.svelte into src/lib/auto-tick.js so
the dedup-by-at invariant — which already caught a P0 — is unit-testable
without mounting the component.
Android equivalent: PlayerBoardViewModel.consumeBusEvent. It already
encodes the dedup invariant via lastConsumedEventId. Behavior is
already correct. This phase is a refactor for testability parity.
Priority
P3 — optional. Skip without consequence; the invariant is already
exercised in PlayerBoardViewModelTest.
Status
pending.
Decision
Recommend defer this phase unless a test gap emerges during phase 06. The Kotlin VM-level test already covers monotonic-id dedup — extracting a pure helper just shuffles the test boundary.
If we later want test parity:
Implementation outline (if adopted)
-
Create
app/src/main/java/com/miti99/loto/state/AutoTick.kt:package com.miti99.loto.state import com.miti99.loto.settings.SettingsState data class AutoTickResult( val crossed: List<List<Boolean>>, val lastConsumedEventId: Long, val changed: Boolean, ) /** * Pure auto-tick rule — port of upstream `processAutoTick`. * Always advances `lastConsumedEventId` on any NEW event (id > last) * even when no cell flips, so reactive re-runs of cross/grid changes * don't re-fire the same event. */ fun processAutoTick( grid: List<List<Int>>?, crossed: List<List<Boolean>>, event: DrawEvent?, lastConsumedEventId: Long, mode: SettingsState.Mode, ): AutoTickResult { if (event == null || event.id <= lastConsumedEventId) { return AutoTickResult(crossed, lastConsumedEventId, false) } val advanced = event.id if (mode != SettingsState.Mode.BOTH) { return AutoTickResult(crossed, advanced, false) } if (grid == null || crossed.isEmpty()) { return AutoTickResult(crossed, advanced, false) } var hit = false val updated = crossed.mapIndexed { r, line -> line.mapIndexed { c, v -> if (!hit && grid[r][c] == event.num && !v) { hit = true; true } else v } } return if (hit) AutoTickResult(updated, advanced, true) else AutoTickResult(crossed, advanced, false) } -
Refactor
PlayerBoardViewModel.consumeBusEventto call it:private fun consumeBusEvent(event: DrawEvent) { val cur = _state.value val mode = currentSettings().mode val r = processAutoTick( grid = cur.grid, crossed = cur.crossed, event = event, lastConsumedEventId = cur.lastConsumedEventId, mode = mode, ) if (r.changed) applyCrossedChange(r.crossed, r.lastConsumedEventId) else _state.update { it.copy(lastConsumedEventId = r.lastConsumedEventId) } } -
Tests in
app/src/test/java/com/miti99/loto/state/AutoTickTest.kt: port the 8 vitest cases fromauto-tick.test.js(NEW draw, dedup, re-cross after manual untick, mode=master/player ignored, off-board, null event, null grid, advance even on miss).
Todo
- Create AutoTick.kt
- Refactor consumeBusEvent
- Port 8 tests
- Compile + tests pass
Success criteria
- All existing PlayerBoardViewModelTest cases still pass.
- New AutoTickTest passes 8/8.
consumeBusEventbody shrinks to ≤ 12 lines.
Risks
- None — pure refactor. Behavior change is impossible (function returns
exactly the same
(crossed, lastConsumedEventId, changed)tuple).
Next
Phase 06 (tests + verification).