mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
* fix(mobile): stop re-creating a terminal when the session tab list empties The session route treated "zero session tabs" as "this workspace has never had anything" and auto-created a terminal. Closing the last tab prunes sessionTabs and nulls activeHandle locally, which is exactly that state, so the close was immediately followed by a brand-new terminal — and the guard re-arms on every route mount, so it recurs across visits (#9717, #7345). Gate the auto-create on whether this route has ever published a non-empty tab list for the workspace. A cold hydrate still gets its first terminal; an emptied list gets the empty state and its create button. Extracted to a hook because the route file sits at its max-lines cap; the call site is 4 counted lines smaller than the effect it replaces. * fix(mobile): keep emptied workspaces empty across visits * fix(mobile): reach the auto-create callbacks without a render-time ref write The hook kept `consumeCreationRoute`/`createTerminal` out of the effect deps by writing latest-refs during render. React can replay or discard a render, so the write can leak from UI that never commits — React Doctor flags it as a blocking "Ref mutated during render" error, which failed PR Checks' static analysis. useEffectEvent (React 19.2, already used in SourceControl.tsx) gives the same stable-callback-outside-deps behaviour with no render-time mutation. Retire the deprecated `MutableRefObject` for `RefObject` in the same pass. Retargets the source pin at the new wiring; test counts unchanged. * docs(mobile): document the two per-route reset contracts Both exported helpers exist for a non-obvious reason — they must be re-created or re-derived per worktree, or a reused route inherits the previous workspace's hydration state and the resurrection guard silently disarms. * fix(mobile): preserve terminal creation through reconnect
47 lines
1.9 KiB
TypeScript
47 lines
1.9 KiB
TypeScript
// Mirrors the desktop `shouldAutoCreateInitialTerminal` gate: a newly created
|
|
// workspace that hydrates empty gets one terminal so the session isn't blank.
|
|
// Kept pure (no react-native imports) so the gate is unit-testable.
|
|
|
|
export type InitialSessionTerminalAutoCreateInput = {
|
|
/** Navigation came directly from creating this workspace. */
|
|
newlyCreatedWorkspace: boolean
|
|
/** Host connection is up and an RPC client is attached. */
|
|
connected: boolean
|
|
/** At least one session-tab snapshot has been applied on this route. */
|
|
tabsLoaded: boolean
|
|
/** Session tabs currently rendered on the phone. */
|
|
visibleTabCount: number
|
|
/** A terminal is still streaming even though the tab list reads empty. */
|
|
hasActiveTerminalHandle: boolean
|
|
/** A terminal, browser, or markdown create is already in flight. */
|
|
createInFlight: boolean
|
|
/** This route has shown at least one session tab for this workspace. */
|
|
sawSessionTabs: boolean
|
|
/** This route already auto-created a terminal for this workspace. */
|
|
autoCreatedForWorktree: boolean
|
|
}
|
|
|
|
/**
|
|
* Whether to auto-create the first terminal of a newly created mobile session.
|
|
*
|
|
* `sawSessionTabs` is the resurrection guard: emptiness that *follows* a
|
|
* populated tab list was produced by a close (the user's, or the host dropping
|
|
* a dead pane), and re-creating there spawns a brand-new terminal the user
|
|
* never asked for — issues #9717 / #7345. Only a workspace that has shown
|
|
* nothing since its creation route opened is eligible.
|
|
*/
|
|
export function shouldAutoCreateInitialSessionTerminal(
|
|
input: InitialSessionTerminalAutoCreateInput
|
|
): boolean {
|
|
if (!input.newlyCreatedWorkspace || !input.connected || !input.tabsLoaded) {
|
|
return false
|
|
}
|
|
if (input.visibleTabCount > 0 || input.hasActiveTerminalHandle) {
|
|
return false
|
|
}
|
|
if (input.createInFlight || input.autoCreatedForWorktree) {
|
|
return false
|
|
}
|
|
return !input.sawSessionTabs
|
|
}
|