refactor(session): extract useFileSession, fixing in-place state mutation - #27
Merged
Conversation
…tion App.tsx owned SessionState (files/currentIndex/deletedFiles/keptFiles/ folderPath/undoStack) via useState, mutating arrays in place inside setState updaters (.push()/.pop() on the previous state's arrays after only a shallow copy) — a real correctness bug, not just a style issue. FileViewer.tsx co-owned the "perform an action" flow with no single place owning what happens on delete. Extract src/hooks/useFileSession.ts: a 5-method interface (startSession, keep, deleteFile, undo, reset) that owns the whole session and does immutable state transitions throughout. deleteFile is the one async method — it awaits moveToTrash and only commits the "deleted" transition on success, so a failed trash operation can no longer be recorded as if it succeeded. A failure propagates as a rejection for FileViewer to catch and show as an alert, instead of the previous fire-and-forget .catch(console.error). The hook also exposes a derived isComplete flag, so App.tsx no longer reaches into currentIndex/files.length itself to guess whether the session just finished; it reacts to isComplete via an effect instead. Adds src/hooks/useFileSession.test.ts, testing the hook's public interface via renderHook, including the core fix: state stays byte-for- byte unchanged when moveToTrash rejects. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
App.tsxownedSessionStateviauseState, mutating arrays in place insidesetStateupdaters (.push()/.pop()on the previous state's arrays after only a shallow copy) — a real correctness bug, not just style.FileViewer.tsxco-owned the "perform an action" flow with no single place owning what happens on delete.src/hooks/useFileSession.ts: a 5-method interface (startSession/keep/deleteFile/undo/reset) owning the whole session, doing immutable state transitions throughout.deleteFileis the one async method — it awaitsmoveToTrashand only commits the "deleted" transition on success, so a failed trash operation can no longer be recorded as if it succeeded. Failure propagates as a rejection;FileViewercatches it and shows an alert instead of the previous fire-and-forget.catch(console.error).isCompleteflag;App.tsxreacts to it via an effect instead of re-deriving "did we finish" from rawcurrentIndex/files.lengthitself (a Feature Envy finding from review, fixed pre-commit).CONTRIBUTING.md's stale test-coverage note.Test plan
npm run typecheckpasses (all 3 configs)npm test— 50/50 pass (12 new:useFileSession.test.tscovers all 5 methods viarenderHook, including the core fix — state is byte-for-byte unchanged whenmoveToTrashrejects — and the newisCompletederivation)npm run build— Vite + Electron compile cleanlynpx biome checkclean on changed filesgit diffthatCompletionScreen.tsx,playActionSound, the confirm dialog, and keyboard shortcuts are untouchedwindow.electronAPI(only present via Electron's preload bridge) — running it under plain Vite crashes immediately on an unrelated pre-existing component (UpdateNotifier), confirmed this isn't a regression by reproducing the same crash's cause. No tooling available here to drive a real Electron window, so this change is verified via the automated test suite only, not a manual click-through.🤖 Generated with Claude Code