From 9412c19ed697e99f4a0f33d46aae95dabaaba923 Mon Sep 17 00:00:00 2001 From: Amine Ilidrissi <38422328+aminevg@users.noreply.github.com> Date: Fri, 6 Nov 2026 09:21:29 +0900 Subject: [PATCH] fix(client): keep completed worktree setup hidden after edits --- .../src/features/threads/ThreadRouteScreen.tsx | 2 +- .../src/features/threads/use-worktree-setup.ts | 4 ++-- .../threads/worktree-setup-state.test.ts | 16 ++++++++++++---- apps/web/src/components/chat/useWorktreeSetup.ts | 2 +- packages/client-runtime/src/worktreeSetup.ts | 6 +++--- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/apps/mobile/src/features/threads/ThreadRouteScreen.tsx b/apps/mobile/src/features/threads/ThreadRouteScreen.tsx index 62c1b039540de..b5f28e3181b0a 100644 --- a/apps/mobile/src/features/threads/ThreadRouteScreen.tsx +++ b/apps/mobile/src/features/threads/ThreadRouteScreen.tsx @@ -842,7 +842,7 @@ function ThreadRouteContent( selectedThread?.worktreePath != null || (selectedThreadCreation?.message.creation?.workspaceMode === "worktree" && selectedThreadCreation.outcome == null), - turnStarted: setupTurnStartedAt !== null, + runs: selectedThreadDetail?.runs ?? [], followUpSent: composer.selectedThreadFeed.filter( (entry) => entry.type === "message" && entry.message.role === "user", diff --git a/apps/mobile/src/features/threads/use-worktree-setup.ts b/apps/mobile/src/features/threads/use-worktree-setup.ts index 5972dc7b28180..bce827b725413 100644 --- a/apps/mobile/src/features/threads/use-worktree-setup.ts +++ b/apps/mobile/src/features/threads/use-worktree-setup.ts @@ -9,7 +9,7 @@ export function useWorktreeSetup(input: { environmentId: EnvironmentId | null; threadId: ThreadId | null; preparing: boolean; - turnStarted: boolean; + runs: Parameters[0]["runs"]; followUpSent: boolean; }) { const key = JSON.stringify([input.environmentId, input.threadId]); @@ -35,7 +35,7 @@ export function useWorktreeSetup(input: { visible: resolveVisibleWorktreeSetup({ live: snapshot, recorded: null, - turnStarted: input.turnStarted, + runs: input.runs, followUpSent: input.followUpSent, }), }; diff --git a/apps/mobile/src/features/threads/worktree-setup-state.test.ts b/apps/mobile/src/features/threads/worktree-setup-state.test.ts index 3598f0000023e..096be8dd7f460 100644 --- a/apps/mobile/src/features/threads/worktree-setup-state.test.ts +++ b/apps/mobile/src/features/threads/worktree-setup-state.test.ts @@ -1,6 +1,7 @@ import { ThreadId, type WorktreeSetupSnapshot } from "@t3tools/contracts"; import { resolveVisibleWorktreeSetup } from "@t3tools/client-runtime/worktree-setup"; import { describe, expect, it } from "vite-plus/test"; +import * as DateTime from "effect/DateTime"; import { resolveWorktreeSetupSnapshot } from "./worktree-setup-state"; const threadId = ThreadId.make("setup-thread"); @@ -27,7 +28,7 @@ describe("mobile worktree setup handoff", () => { resolveVisibleWorktreeSetup({ live: held, recorded: null, - turnStarted: false, + runs: [], followUpSent: false, }), ).toBe(done); @@ -35,10 +36,17 @@ describe("mobile worktree setup handoff", () => { resolveVisibleWorktreeSetup({ live: held, recorded: null, - turnStarted: true, + runs: [{ startedAt: DateTime.makeUnsafe(running.startedAt) }], followUpSent: false, }), ).toBeNull(); + const afterFirstMessageEdit = { + live: held, + recorded: null, + followUpSent: false, + runs: [{ startedAt: DateTime.makeUnsafe(running.startedAt) }, { startedAt: null }], + }; + expect(resolveVisibleWorktreeSetup(afterFirstMessageEdit)).toBeNull(); expect(resolveWorktreeSetupSnapshot(threadId, undefined, held)).toBe(done); }); @@ -53,7 +61,7 @@ describe("mobile worktree setup handoff", () => { resolveVisibleWorktreeSetup({ live: held, recorded: null, - turnStarted: false, + runs: [], followUpSent: false, }), ).toBe(settled); @@ -61,7 +69,7 @@ describe("mobile worktree setup handoff", () => { resolveVisibleWorktreeSetup({ live: held, recorded: null, - turnStarted: false, + runs: [], followUpSent: true, }), ).toBeNull(); diff --git a/apps/web/src/components/chat/useWorktreeSetup.ts b/apps/web/src/components/chat/useWorktreeSetup.ts index c2a5703235d44..34ba5ce494fb0 100644 --- a/apps/web/src/components/chat/useWorktreeSetup.ts +++ b/apps/web/src/components/chat/useWorktreeSetup.ts @@ -100,7 +100,7 @@ export function useWorktreeSetup(input: { const worktreeSetup = resolveVisibleWorktreeSetup({ live: liveSnapshot, recorded: null, - turnStarted: activityRun?.startedAt != null, + runs: serverProjection?.runs ?? [], followUpSent, }); // After agent handoff, sends queue behind setup without awaiting its scripts. diff --git a/packages/client-runtime/src/worktreeSetup.ts b/packages/client-runtime/src/worktreeSetup.ts index 0992f63fbc036..f6411cfa7a4ef 100644 --- a/packages/client-runtime/src/worktreeSetup.ts +++ b/packages/client-runtime/src/worktreeSetup.ts @@ -1,9 +1,9 @@ -import type { WorktreeSetupSnapshot } from "@t3tools/contracts"; +import type { OrchestrationV2Run, WorktreeSetupSnapshot } from "@t3tools/contracts"; export function resolveVisibleWorktreeSetup(input: { live: WorktreeSetupSnapshot | null; recorded: WorktreeSetupSnapshot | null; - turnStarted: boolean; + runs: ReadonlyArray>; followUpSent: boolean; }): WorktreeSetupSnapshot | null { const snapshot = @@ -14,7 +14,7 @@ export function resolveVisibleWorktreeSetup(input: { if (snapshot.phase === "running") return snapshot; if (input.followUpSent) return null; if (snapshot.phase !== "done") return snapshot; - if (!input.turnStarted) return snapshot; + if (!input.runs.some((run) => run.startedAt !== null)) return snapshot; return snapshot.stages.some((stage) => stage.status === "failed") ? snapshot : null; }