Repository navigation
fix(app): drive Home session list, search and row gestures with explicit state machines - #61927
Conversation
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
There was a problem hiding this comment.
The change looks sound. I couldn't run it in a browser here (it's a web/desktop app change and the review time was limited). The new machine unit tests pass (47/47) and tsc --noEmit on packages/app is clean.
I checked the parts most likely to regress:
- Delete: moving the
home-sessionscache drop to afterDELETEresolves is safe, becausecreateSessionMutationskeeps the row hidden fromremove()until the request fails. - Stale results: the load, search and rename drivers all fence results by attempt and abort or cancel on retarget, timeout and dispose.
- Error and offline states: the unavailable state yields to cached or live sessions, and a failed refresh keeps the list it already shows.
Public API: unchanged. These are internal app props and two new en i18n keys.
No blocking concerns. If you can, please attach a short before/after recording of the connecting → unreachable path (15 s timeout) and of the signed-out state; the screenshots don't cover those.
Home's session list, its search panel and the row menu/rename each run on one explicit state machine now. Each machine has a pure
transition, selectors for the UI, and a thin driver that fences results by attempt.Bugs fixed
enabledonly while connected, so a signed-out, unreachable, incompatible or stuck-connecting server stayedisPending. Home then showed placeholder rows and the search spinner forever. Now:isPendingfalse with no data, so Home showed "Nothing here yet / New session". It now shows "Couldn't load sessions", the error, and Try again. A list request that hangs times out after 30 s.DELETEsettled. The data layer already hides the row onremove()and shows it again on failure, so the cache now drops the row only after the server confirms the delete.setTimeouteffects drove the placeholder rows. The skeleton and reveal are now machine phases, so an error or a cached server shows at once.disabledinput lost focus, andcloseEditor()ignored Escape while saving. The input is now read-only while saving, so it keeps focus and Escape cancels the save (abort). A failed save returns to editing with the draft. A save that times out after 15 s does the same and shows a toast.letvariables, so a session that moved to another date group mid-gesture lost them. The flags now live in the machine at the list root.State
idlepointerdownpressing(500 ms timer)pressingpointerupidlepressingcontextmenumenu { suppressClick: true }menuidle(suppressClickcarried until the release click), focus row unless dismissed outsideeditingeditingrenaming(abortable save, 15 s)renamingidle+ abort; blur is ignoredrenamingeditingwith the draftKept from the query
["home-sessions", conn]with the same options as before: no retry (retry: false), 30 s stale time, andgcTime: Infinity, which keeps it across visits. Fetches go throughqueryClient.fetchQuery, so a fetch the sidebar already started is joined.invalidateQueries, for example from side tabs), and whenonlineManagerreports online while the list is stale. A refresh asked for during a fetch runs once more after it.pointermovechecksselectLongPressMovedbefore it builds an event, so a move allocates nothing unless it cancels a pending long press.Deleted
useQueryin the controller, the[placeholder, setPlaceholder]signal and its two timers, and the pre-requestsetQueryDatainremove.{ value, focused, highlighted, exact, lookingUp }and itslet lookupcounter.rowUIstore and the four per-rowletflags:longPressTimer,longPressStart,suppressClickandmenuInteractedOutside.Product calls and deviations from the design
devthan a blank list, and the rows carry over into the first fetch.disabled, so it takes no focus. Kobalte'sDropdownMenufocuses its trigger on close unless the user interacted outside. That stole focus from the rename editor the Rename item had just focused.Visual