From a2e32401fc4b99ddbfa3801204aa11dd7a525152 Mon Sep 17 00:00:00 2001
From: Joshua Leshnick
Date: Fri, 24 Jul 2026 16:26:13 -0500
Subject: [PATCH 1/4] refactor: remove dead save-session concept and dead-code
cleanup
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Delete SaveSlot interface (types.ts) — was never imported or used
- Remove Ctrl+S autosave-reminder handler (App.tsx) and its shortcut entry (SettingsDialog)
- Fix stale SettingsDialog copy referencing non-existent '.json saved game files'
- Correct docs (CLAUDE.md, CONTEXT.md, ARCHITECTURE.md) to remove labyrinth_saved_slots_list / named-slots claims; describe actual single-slot autosave
- Remove unused _atlasUrl param from scanBoard() (boardScanner.ts) and update call site (BoardScanModal.tsx)
- Replace undefined stone-850/stone-750 shades with valid stone-700/stone-800; drop inconsistent dark: variant prefixes (app uses [data-theme], not Tailwind dark:)
Co-Authored-By: Claude Sonnet 4.6
---
CLAUDE.md | 2 +-
CONTEXT.md | 2 +-
docs/ARCHITECTURE.md | 2 +-
src/App.tsx | 5 -----
src/components/board/Board.tsx | 4 ++--
src/components/modals/BoardScanModal.tsx | 2 +-
src/components/modals/SettingsDialog.tsx | 8 +++-----
src/components/panels/SetupPanel.tsx | 4 ++--
src/components/panels/SolverPanel.tsx | 2 +-
src/lib/boardScanner.ts | 1 -
src/types.ts | 6 ------
11 files changed, 12 insertions(+), 26 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 50157c4..1079d27 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -20,7 +20,7 @@ This is the project root for Labyrinth Game Solver, a responsive, mobile-friendl
- **Mobile Standalone / PWA**: Configured with Apple Web App meta tags to allow launching full-screen from the iPhone home screen. Max viewport bounds prevent bouncing and double-tap zoom.
- **Path Resolving**: In `vite.config.ts`, `base: './'` is configured so that files compile with relative links to support hosting on GitHub Pages subpaths. Alias `@/` maps to the `src/` folder.
- **Safe Deep Copy**: `useLabyrinthHistory.ts` uses a custom `deepClone` (via `JSON.parse(JSON.stringify(...))`) instead of solver's `cloneBoard()` to avoid crash errors with `null` grid values.
-- **Auto-Save / Game Storage**: The state synchronizes to the browser's `localStorage` database under `labyrinth_saved_slots_list` and `labyrinth_strategist_state`.
+- **Auto-Save / Game Storage**: The board layout and game state are autosaved to the browser's `localStorage` under `labyrinth_strategist_state` and restored on reload. User preferences (theme, accent color, mute, 3D, active players) use separate keys (`labyrinth_theme`, `labyrinth_accent_color`, etc.).
## Release Pipeline
diff --git a/CONTEXT.md b/CONTEXT.md
index 45e7aa2..9eaade3 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -42,4 +42,4 @@ The Labyrinth project manages its interface states directly inside `App.tsx` and
- **[src/components/Tile.tsx](src/components/Tile.tsx)**: Generates CSS-based corridors (straight, corner, t-junction) based on rotation degrees, and overlays pawn tokens.
- **[src/solver.js](src/solver.js)**: Legacy pathfinder algorithm (implements graph BFS and slide shifts).
- **[src/constants.ts](src/constants.ts)**: Configures diagnostic starts (colors, diagonal alignment presets, named treasures).
-- **[src/hooks/useLabyrinthStorage.ts](src/hooks/useLabyrinthStorage.ts)**: Interacts with the browser's `LocalStorage` database to save and load named slots and the auto-save.
+- **[src/hooks/useLabyrinthStorage.ts](src/hooks/useLabyrinthStorage.ts)**: Autosaves board and game state to `localStorage` (`labyrinth_strategist_state`) and restores it on reload.
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index e4cb603..b0ead2b 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -50,7 +50,7 @@ Complex pathfinder searches run inside `solver.worker.js` (Web Worker).
### 2. State & History Synchronization
- **`useLabyrinthHistory`**: Keeps deep cloned snapshots of `AppGameState` to manage custom undo/redo actions.
-- **`useLabyrinthStorage`**: Provides quick read/write tools to standard `localStorage` to save game slots and sync setup options.
+- **`useLabyrinthStorage`**: Autosaves board and game state to `localStorage` (`labyrinth_strategist_state`) and restores it on reload.
### 3. Native Web Audio Synth
Audio feedback is synthesized dynamically using the Web Audio API in `src/utils/audio.ts` (minimizing app footprint by not packing static mp3/wav files).
diff --git a/src/App.tsx b/src/App.tsx
index 63a3259..0dea10c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -309,11 +309,6 @@ export default function App() {
game.handleRedo();
return;
}
- if (ctrl && e.key === "s") {
- e.preventDefault();
- showToast("Autosave keeps your layout ready next time ✨");
- return;
- }
if (e.key === "?" && !ctrl) {
e.preventDefault();
setIsSettingsOpen(true);
diff --git a/src/components/board/Board.tsx b/src/components/board/Board.tsx
index 5489873..6eab376 100644
--- a/src/components/board/Board.tsx
+++ b/src/components/board/Board.tsx
@@ -272,9 +272,9 @@ export const Board: React.FC = ({
className={cn(
"w-full h-full max-w-[70%] max-h-[70%] mx-auto p-0.5 rounded-lg transition-all focus:outline-none flex items-center justify-center",
isForbidden
- ? "opacity-20 cursor-not-allowed border border-stone-850 text-stone-600 bg-stone-950/60"
+ ? "opacity-20 cursor-not-allowed border border-stone-700 text-stone-600 bg-stone-950/60"
: turnPhase === "move"
- ? "opacity-25 cursor-not-allowed border border-stone-850 text-stone-600 bg-stone-950/60"
+ ? "opacity-25 cursor-not-allowed border border-stone-700 text-stone-600 bg-stone-950/60"
: isStaged
? "neo-brutalism-button border-stone-950 bg-theme-primary text-stone-950 scale-105 translate-x-[1px] translate-y-[1px] shadow-[1px_1px_0_0_#000000] cursor-pointer"
: isHighlighted
diff --git a/src/components/modals/BoardScanModal.tsx b/src/components/modals/BoardScanModal.tsx
index 5b40063..86dfe7a 100644
--- a/src/components/modals/BoardScanModal.tsx
+++ b/src/components/modals/BoardScanModal.tsx
@@ -324,7 +324,7 @@ export function BoardScanModal({ open, onClose, onApply }: Props) {
y: c.y * scanH,
})) as [CornerPoint, CornerPoint, CornerPoint, CornerPoint];
- const scanResults = await scanBoard(scanSource, absCorners, "", setProgress);
+ const scanResults = await scanBoard(scanSource, absCorners, setProgress);
setResults(scanResults);
setStep("results");
} catch (err) {
diff --git a/src/components/modals/SettingsDialog.tsx b/src/components/modals/SettingsDialog.tsx
index 6fab235..0e37c43 100644
--- a/src/components/modals/SettingsDialog.tsx
+++ b/src/components/modals/SettingsDialog.tsx
@@ -44,7 +44,6 @@ const KEYBOARD_SHORTCUTS = [
shortcuts: [
{ keys: ["Ctrl", "Z"], desc: "Undo last action" },
{ keys: ["Ctrl", "Y"], desc: "Redo last action" },
- { keys: ["Ctrl", "S"], desc: "Show autosave reminder" },
],
},
{
@@ -529,9 +528,8 @@ export function SettingsDialog({
Troubleshooting
- If you encounter issues with saved games or stale settings, clearing the
- app cache will reset all local data. Your disk-saved game files (.json)
- remain untouched.
+ If you encounter issues with stale settings or a corrupted board layout,
+ clearing the app cache will reset your saved board and all preferences.
Labyrinth Game Solver processes and stores all data locally on your
- system. No board data, saved games, settings, or metadata are ever uploaded
+ system. No board data, settings, or metadata are ever uploaded
to remote servers.
diff --git a/src/components/panels/SetupPanel.tsx b/src/components/panels/SetupPanel.tsx
index 11307a8..6813c66 100644
--- a/src/components/panels/SetupPanel.tsx
+++ b/src/components/panels/SetupPanel.tsx
@@ -128,7 +128,7 @@ export function SetupPanel({
className={`flex items-center justify-center gap-1.5 px-3 md:px-4 py-2 md:py-1.5 min-h-11 md:min-h-0 rounded-lg text-xs md:text-sm font-bold transition-all cursor-pointer border-2 ${
setupTab === tab.id
? "bg-theme-primary text-stone-950 border-stone-950 shadow-[2px_2px_0_0_#000000]"
- : "text-stone-500 hover:text-foreground hover:bg-stone-100 dark:hover:bg-stone-850 border-transparent"
+ : "text-stone-500 hover:text-foreground hover:bg-stone-800 border-transparent"
}`}
>
{tab.icon}
@@ -285,7 +285,7 @@ export function SetupPanel({
size="sm"
variant={alreadyInHand ? "secondary" : "outline"}
onClick={() => (alreadyInHand ? onRemoveCard(t.id) : onAddCard(t.id))}
- className={`text-[10px] md:text-xs py-1 justify-start h-11 md:h-9 lg:h-8 px-2 truncate neo-brutalism-button bg-card hover:bg-stone-100 dark:hover:bg-stone-850 text-foreground border-2 border-stone-950 shadow-[2px_2px_0_0_#000000] ${alreadyInHand ? "bg-theme-primary text-stone-950 shadow-[1px_1px_0_0_#000000] translate-x-[1px] translate-y-[1px]" : ""}`}
+ className={`text-[10px] md:text-xs py-1 justify-start h-11 md:h-9 lg:h-8 px-2 truncate neo-brutalism-button bg-card hover:bg-stone-800 text-foreground border-2 border-stone-950 shadow-[2px_2px_0_0_#000000] ${alreadyInHand ? "bg-theme-primary text-stone-950 shadow-[1px_1px_0_0_#000000] translate-x-[1px] translate-y-[1px]" : ""}`}
>
{t.name}
diff --git a/src/components/panels/SolverPanel.tsx b/src/components/panels/SolverPanel.tsx
index f933b0d..45dc399 100644
--- a/src/components/panels/SolverPanel.tsx
+++ b/src/components/panels/SolverPanel.tsx
@@ -332,7 +332,7 @@ export function SolverPanel({
key={p.id}
variant={activePawn === p.id ? "default" : "outline"}
onClick={() => { if (!isMuted) playClickSound(); setActivePawn(p.id); }}
- className={`neo-brutalism-button bg-card hover:bg-stone-100 dark:hover:bg-stone-850 text-foreground border-stone-950 h-11 md:h-9 ${activePawn === p.id ? p.colorClass + " text-stone-950 font-extrabold translate-x-[1px] translate-y-[1px] shadow-[1px_1px_0_0_#000000]" : ""}`}
+ className={`neo-brutalism-button bg-card hover:bg-stone-100 hover:bg-stone-800 text-foreground border-stone-950 h-11 md:h-9 ${activePawn === p.id ? p.colorClass + " text-stone-950 font-extrabold translate-x-[1px] translate-y-[1px] shadow-[1px_1px_0_0_#000000]" : ""}`}
>
{p.name}
diff --git a/src/lib/boardScanner.ts b/src/lib/boardScanner.ts
index b32fe0b..1bd47d5 100644
--- a/src/lib/boardScanner.ts
+++ b/src/lib/boardScanner.ts
@@ -262,7 +262,6 @@ export function isFixedCell(row: number, col: number): boolean {
export async function scanBoard(
boardImageElement: HTMLImageElement | HTMLCanvasElement,
corners: [CornerPoint, CornerPoint, CornerPoint, CornerPoint],
- _atlasUrl: string, // kept for API compatibility, no longer used
onProgress?: (pct: number) => void
): Promise {
const templates = await loadTileTemplates();
diff --git a/src/types.ts b/src/types.ts
index 1babc83..a73bbad 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -48,12 +48,6 @@ export interface AppGameState {
pawnPositions: PawnPositions;
}
-export interface SaveSlot {
- name: string;
- key: string;
- timestamp: number;
-}
-
export interface BoardScanCell {
row: number;
col: number;
From 4f11c61939d3581d809c36fd011443a56889e434 Mon Sep 17 00:00:00 2001
From: Joshua Leshnick
Date: Fri, 24 Jul 2026 16:31:53 -0500
Subject: [PATCH 2/4] =?UTF-8?q?feat:=20UX=20improvements=20=E2=80=94=20Sta?=
=?UTF-8?q?ts=20button,=20tablet=20toolbar,=20split=20timer,=20turn-model?=
=?UTF-8?q?=20guide?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- AppHeader: lower desktop action toolbar breakpoint from lg: to md: so tablets
(iPad, small laptops) no longer lose undo/redo/rotate/mute controls
- AppHeader: Stats is now reachable on desktop — adds a BarChart2 button in the
game-mode toolbar wired to onToggleStats (was previously aliased unused _onToggleStats;
the only working open path was the mobile compact panel Gauge button)
- AppHeader: split timer control out of the overloaded Play/pause/resume step-nav
button — Play Game now only starts/ends the game; timer shows as a separate pill
with its own pause/resume toggle and Pause/Clock icon
- AppHeader: activate showStats prop to highlight the Stats button when open
- SetupPanel: swap RotateCw → RefreshCcw for Reset Layout to disambiguate from the
Rotate Board button in the header (RotateCw now unambiguously means board rotation)
- WelcomeGuide: add step 4 explaining the 2-phase slide→move turn model and how to
set a custom target by tapping any board tile
Co-Authored-By: Claude Sonnet 4.6
---
src/components/AppHeader.tsx | 59 +++++++++++++++++---------
src/components/modals/WelcomeGuide.tsx | 20 ++++++---
src/components/panels/SetupPanel.tsx | 4 +-
3 files changed, 56 insertions(+), 27 deletions(-)
diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx
index 88ba3a2..b07c864 100644
--- a/src/components/AppHeader.tsx
+++ b/src/components/AppHeader.tsx
@@ -19,6 +19,8 @@ import {
Settings2,
HelpCircle,
Clock,
+ Pause,
+ BarChart2,
} from "lucide-react";
export interface AppHeaderProps {
@@ -81,7 +83,7 @@ export function AppHeader({
canUndo,
canRedo,
isMuted,
- showStats: _showStats,
+ showStats,
baseTheme: _baseTheme,
activePlayers,
activePawn,
@@ -96,7 +98,7 @@ export function AppHeader({
onRedo,
onOpenHistory,
onRotateBoard,
- onToggleStats: _onToggleStats,
+ onToggleStats,
onStartGame,
onEndGame,
onToggleMute,
@@ -135,30 +137,23 @@ export function AppHeader({
- {/* Center — Step Nav (timer baked into Play Game button) */}
+ {/* Center — Step Nav + timer pill */}