From 76874f972cd15516e355fbeec51d536f50ef044f Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Mon, 19 Oct 2026 19:22:23 -0500 Subject: [PATCH 1/3] fix(mobile): await branch refs before choosing an automatic feature branch Wait for the refreshed ref atom instead of returning the previous render's branch list. Stop confirmation on a failed query before creating a branch. Co-Authored-By: GPT-6.1 Sol --- .../features/threads/git/GitConfirmSheet.tsx | 1 + .../use-selected-thread-git-actions.test.tsx | 105 +++++++++++++++++- .../state/use-selected-thread-git-actions.ts | 31 ++++-- 3 files changed, 124 insertions(+), 13 deletions(-) diff --git a/apps/mobile/src/features/threads/git/GitConfirmSheet.tsx b/apps/mobile/src/features/threads/git/GitConfirmSheet.tsx index 216cbfe02949b..f3deeb7f383f5 100644 --- a/apps/mobile/src/features/threads/git/GitConfirmSheet.tsx +++ b/apps/mobile/src/features/threads/git/GitConfirmSheet.tsx @@ -95,6 +95,7 @@ export function GitConfirmSheet(props: GitConfirmSheetProps) { gitState.selectedThreadBranches.length > 0 ? gitState.selectedThreadBranches : await gitActions.refreshSelectedThreadBranches(); + if (branches === null) return; const newBranchName = resolveAutoFeatureBranchName( Arr.filterMap(branches, (branch) => branch.isRemote ? Result.failVoid : Result.succeed(branch.name), diff --git a/apps/mobile/src/state/use-selected-thread-git-actions.test.tsx b/apps/mobile/src/state/use-selected-thread-git-actions.test.tsx index 11374b2e47c9b..6665f5be97185 100644 --- a/apps/mobile/src/state/use-selected-thread-git-actions.test.tsx +++ b/apps/mobile/src/state/use-selected-thread-git-actions.test.tsx @@ -1,7 +1,11 @@ import { act, useEffect, useReducer } from "react"; import { createRoot, type Root } from "react-dom/client"; -import { AsyncResult } from "effect/reactivity"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; +import type { VcsListRefsResult } from "@t3tools/contracts"; +import { executeAtomQuery } from "@t3tools/client-runtime/state/runtime"; +import { resolveAutoFeatureBranchName } from "@t3tools/shared/git"; +import * as Effect from "effect/Effect"; +import { AsyncResult, Atom } from "effect/reactivity"; +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; const harness = vi.hoisted(() => ({ selection: { @@ -13,6 +17,9 @@ const harness = vi.hoisted(() => ({ refreshStatusCalls: [] as Array, renderCount: 0, onRefreshSettled: () => {}, + branchAtom: null as Atom.Atom> | null, + listRefs: vi.fn(), + showGitActionResult: vi.fn(), })); vi.mock("./use-atom-command", () => ({ @@ -26,19 +33,21 @@ vi.mock("./use-atom-command", () => ({ : async () => AsyncResult.success({}), })); vi.mock("./vcs", () => ({ - vcsEnvironment: { refreshStatus: "refreshStatus" }, + vcsEnvironment: { refreshStatus: "refreshStatus", listRefs: harness.listRefs }, vcsActionManager: { runStackedAction: () => "runStackedAction", track: vi.fn() }, })); vi.mock("./threads", () => ({ threadEnvironment: { updateMetadata: "updateMetadata" } })); vi.mock("./queries", () => ({ useBranches: () => ({ data: null, refresh: () => {} }) })); vi.mock("../lib/uuid", () => ({ uuidv4: () => "uuid" })); -vi.mock("./atom-registry", () => ({ appAtomRegistry: {} })); +vi.mock("./atom-registry", async () => ({ + appAtomRegistry: (await import("effect/reactivity")).AtomRegistry.make(), +})); vi.mock("./session", () => ({ readEnvironmentScope: () => true, useEnvironmentScope: () => true, })); vi.mock("./use-remote-environment-registry", () => ({ setPendingConnectionError: () => {} })); -vi.mock("./use-vcs-action-state", () => ({ showGitActionResult: () => {} })); +vi.mock("./use-vcs-action-state", () => ({ showGitActionResult: harness.showGitActionResult })); vi.mock("./use-thread-selection", () => ({ useThreadSelection: () => { harness.renderCount += 1; @@ -53,16 +62,19 @@ vi.mock("./use-selected-thread-worktree", () => ({ })); import { useSelectedThreadGitActions } from "./use-selected-thread-git-actions"; +import { appAtomRegistry } from "./atom-registry"; let root: Root | null = null; let rerender: () => void = () => {}; +let actions: ReturnType; function Consumer() { const [, bump] = useReducer((count: number) => count + 1, 0); + const gitActions = useSelectedThreadGitActions(); useEffect(() => { rerender = bump; + actions = gitActions; }); - useSelectedThreadGitActions(); return null; } @@ -85,6 +97,8 @@ beforeEach(() => { harness.refreshStatusCalls = []; harness.renderCount = 0; harness.onRefreshSettled = () => rerender(); + harness.listRefs.mockReset().mockImplementation(() => harness.branchAtom); + harness.showGitActionResult.mockClear(); }); afterEach(() => { @@ -92,7 +106,86 @@ afterEach(() => { root = null; }); +afterAll(() => appAtomRegistry.dispose()); + describe("useSelectedThreadGitActions", () => { + it.each([false, true])( + "waits for fresh refs before naming a branch, cached: %s", + async (cached) => { + const request = Promise.withResolvers(); + const empty: VcsListRefsResult = { + refs: [], + isRepo: true, + hasPrimaryRemote: true, + nextCursor: null, + totalCount: 0, + }; + let response = cached ? Promise.resolve(empty) : request.promise; + harness.branchAtom = Atom.make(Effect.tryPromise(() => response)).pipe(Atom.keepAlive); + if (cached) { + await executeAtomQuery(appAtomRegistry, harness.branchAtom); + response = request.promise; + } + root = createRoot(document.createElement("div")); + act(() => root!.render()); + + let settled = false; + const refresh = actions.refreshSelectedThreadBranches().then((branches) => { + settled = true; + return branches; + }); + await flush(); + expect(settled).toBe(false); + expect(harness.listRefs).toHaveBeenCalledWith({ + environmentId: "environment-1", + input: { cwd: "/repo", limit: 100 }, + }); + const local = { + name: "feature/update", + isRemote: false, + current: false, + isDefault: false, + worktreePath: null, + }; + request.resolve({ + refs: [ + local, + { ...local, name: "origin/feature/update", isRemote: true, remoteName: "origin" }, + ], + nextCursor: null, + isRepo: true, + hasPrimaryRemote: true, + totalCount: 2, + }); + const branches = await refresh; + expect(branches).toEqual([local]); + expect(resolveAutoFeatureBranchName(branches!.map((branch) => branch.name))).toBe( + "feature/update-2", + ); + }, + ); + + it("reports a failed ref refresh instead of returning an empty list for branch creation", async () => { + const request = Promise.withResolvers(); + void request.promise.catch(() => {}); + harness.branchAtom = Atom.make( + Effect.tryPromise({ + try: () => request.promise, + catch: (error) => error, + }), + ); + root = createRoot(document.createElement("div")); + act(() => root!.render()); + const refresh = actions.refreshSelectedThreadBranches(); + request.reject(new Error("Cannot list refs")); + expect(await refresh).toBeNull(); + expect(harness.showGitActionResult).toHaveBeenCalledWith({ + type: "error", + title: "Git action failed", + description: "Cannot list refs", + }); + }); + it("refreshes once on mount and does not loop when refreshes rerender the consumer", async () => { root = createRoot(document.createElement("div")); act(() => root!.render()); diff --git a/apps/mobile/src/state/use-selected-thread-git-actions.ts b/apps/mobile/src/state/use-selected-thread-git-actions.ts index cb4e2d689dec9..c813310f00f31 100644 --- a/apps/mobile/src/state/use-selected-thread-git-actions.ts +++ b/apps/mobile/src/state/use-selected-thread-git-actions.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useEffectEvent, useMemo } from "react"; import { EnvironmentProject, EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell"; -import type { AtomCommandResult } from "@t3tools/client-runtime/state/runtime"; +import { executeAtomQuery, type AtomCommandResult } from "@t3tools/client-runtime/state/runtime"; import { type GitActionRequestInput, type VcsActionOperation, @@ -218,12 +218,29 @@ export function useSelectedThreadGitActions() { [selectedThread, selectedThreadCwd, selectedThreadProject], ); - const refreshSelectedThreadBranches = useCallback(async (): Promise> => { - branchState.refresh(); - return dedupeRemoteBranchesWithLocalMatches(branchState.data?.refs ?? []).filter( - (branch) => !branch.isRemote, - ); - }, [branchState]); + const refreshSelectedThreadBranches = + useCallback(async (): Promise | null> => { + if (branchTarget.environmentId === null || branchTarget.cwd === null) return null; + const result = await executeAtomQuery( + appAtomRegistry, + vcsEnvironment.listRefs({ + environmentId: branchTarget.environmentId, + input: { cwd: branchTarget.cwd, limit: 100 }, + }), + { refresh: true, reportFailure: false }, + ); + if (AsyncResult.isFailure(result)) { + const error = Cause.squash(result.cause); + const message = error instanceof Error ? error.message : "Failed to refresh branches."; + setPendingConnectionError(message); + showGitActionResult({ type: "error", title: "Git action failed", description: message }); + return null; + } + setPendingConnectionError(null); + return dedupeRemoteBranchesWithLocalMatches(result.value.refs).filter( + (branch) => !branch.isRemote, + ); + }, [branchTarget]); const syncSelectedThreadBranchState = useCallback( async (input: { From 54c1d5256496d7c6749cffb6731f36d6c313b8e3 Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Mon, 19 Oct 2026 20:28:16 -0500 Subject: [PATCH 2/3] fix(mobile): read branch refs with a finite action query Use a one-shot refs query for confirmation instead of waiting for the live refs cache stream to complete. Cover the stream's waiting result so the confirmation must continue after fresh refs arrive. Co-Authored-By: GPT-6.1 Sol --- .../use-selected-thread-git-actions.test.tsx | 32 +++++++++++++++---- .../state/use-selected-thread-git-actions.ts | 4 +-- apps/mobile/src/state/vcs.ts | 8 +++++ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/apps/mobile/src/state/use-selected-thread-git-actions.test.tsx b/apps/mobile/src/state/use-selected-thread-git-actions.test.tsx index 6665f5be97185..a504142b63069 100644 --- a/apps/mobile/src/state/use-selected-thread-git-actions.test.tsx +++ b/apps/mobile/src/state/use-selected-thread-git-actions.test.tsx @@ -4,6 +4,7 @@ import type { VcsListRefsResult } from "@t3tools/contracts"; import { executeAtomQuery } from "@t3tools/client-runtime/state/runtime"; import { resolveAutoFeatureBranchName } from "@t3tools/shared/git"; import * as Effect from "effect/Effect"; +import * as Stream from "effect/Stream"; import { AsyncResult, Atom } from "effect/reactivity"; import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; @@ -18,7 +19,9 @@ const harness = vi.hoisted(() => ({ renderCount: 0, onRefreshSettled: () => {}, branchAtom: null as Atom.Atom> | null, + refsQueryAtom: null as Atom.Atom> | null, listRefs: vi.fn(), + refsQuery: vi.fn(), showGitActionResult: vi.fn(), })); @@ -34,6 +37,7 @@ vi.mock("./use-atom-command", () => ({ })); vi.mock("./vcs", () => ({ vcsEnvironment: { refreshStatus: "refreshStatus", listRefs: harness.listRefs }, + vcsRefsQuery: harness.refsQuery, vcsActionManager: { runStackedAction: () => "runStackedAction", track: vi.fn() }, })); vi.mock("./threads", () => ({ threadEnvironment: { updateMetadata: "updateMetadata" } })); @@ -98,6 +102,7 @@ beforeEach(() => { harness.renderCount = 0; harness.onRefreshSettled = () => rerender(); harness.listRefs.mockReset().mockImplementation(() => harness.branchAtom); + harness.refsQuery.mockReset().mockImplementation(() => harness.refsQueryAtom); harness.showGitActionResult.mockClear(); }); @@ -121,9 +126,18 @@ describe("useSelectedThreadGitActions", () => { totalCount: 0, }; let response = cached ? Promise.resolve(empty) : request.promise; - harness.branchAtom = Atom.make(Effect.tryPromise(() => response)).pipe(Atom.keepAlive); + const fetchRefs = Effect.tryPromise(() => response); + harness.branchAtom = Atom.make( + Stream.concat(Stream.fromEffect(fetchRefs), Stream.never), + ).pipe(Atom.keepAlive); + harness.refsQueryAtom = Atom.make(fetchRefs).pipe(Atom.keepAlive); + appAtomRegistry.get(harness.branchAtom); if (cached) { - await executeAtomQuery(appAtomRegistry, harness.branchAtom); + await vi.waitFor(() => + expect(appAtomRegistry.get(harness.branchAtom)._tag).toBe("Success"), + ); + expect(appAtomRegistry.get(harness.branchAtom).waiting).toBe(true); + await executeAtomQuery(appAtomRegistry, harness.refsQueryAtom); response = request.promise; } root = createRoot(document.createElement("div")); @@ -136,10 +150,6 @@ describe("useSelectedThreadGitActions", () => { }); await flush(); expect(settled).toBe(false); - expect(harness.listRefs).toHaveBeenCalledWith({ - environmentId: "environment-1", - input: { cwd: "/repo", limit: 100 }, - }); const local = { name: "feature/update", isRemote: false, @@ -157,7 +167,16 @@ describe("useSelectedThreadGitActions", () => { hasPrimaryRemote: true, totalCount: 2, }); + await vi.waitFor(() => expect(settled).toBe(true)); + expect(appAtomRegistry.get(harness.branchAtom)).toMatchObject({ + _tag: "Success", + waiting: true, + }); const branches = await refresh; + expect(harness.refsQuery).toHaveBeenCalledWith({ + environmentId: "environment-1", + input: { cwd: "/repo", limit: 100 }, + }); expect(branches).toEqual([local]); expect(resolveAutoFeatureBranchName(branches!.map((branch) => branch.name))).toBe( "feature/update-2", @@ -174,6 +193,7 @@ describe("useSelectedThreadGitActions", () => { catch: (error) => error, }), ); + harness.refsQueryAtom = harness.branchAtom; root = createRoot(document.createElement("div")); act(() => root!.render()); const refresh = actions.refreshSelectedThreadBranches(); diff --git a/apps/mobile/src/state/use-selected-thread-git-actions.ts b/apps/mobile/src/state/use-selected-thread-git-actions.ts index c813310f00f31..68f59b28223e1 100644 --- a/apps/mobile/src/state/use-selected-thread-git-actions.ts +++ b/apps/mobile/src/state/use-selected-thread-git-actions.ts @@ -22,7 +22,7 @@ import { AsyncResult } from "effect/reactivity"; import { useBranches } from "../state/queries"; import { threadEnvironment } from "../state/threads"; -import { vcsActionManager, vcsEnvironment } from "../state/vcs"; +import { vcsActionManager, vcsEnvironment, vcsRefsQuery } from "../state/vcs"; import { uuidv4 } from "../lib/uuid"; import { appAtomRegistry } from "./atom-registry"; import { readEnvironmentScope, useEnvironmentScope } from "./session"; @@ -223,7 +223,7 @@ export function useSelectedThreadGitActions() { if (branchTarget.environmentId === null || branchTarget.cwd === null) return null; const result = await executeAtomQuery( appAtomRegistry, - vcsEnvironment.listRefs({ + vcsRefsQuery({ environmentId: branchTarget.environmentId, input: { cwd: branchTarget.cwd, limit: 100 }, }), diff --git a/apps/mobile/src/state/vcs.ts b/apps/mobile/src/state/vcs.ts index dc8c251149f63..00a612863c96a 100644 --- a/apps/mobile/src/state/vcs.ts +++ b/apps/mobile/src/state/vcs.ts @@ -2,8 +2,16 @@ import { createVcsActionManager, createVcsEnvironmentAtoms, } from "@t3tools/client-runtime/state/vcs"; +import { createEnvironmentRpcQueryAtomFamily } from "@t3tools/client-runtime/state/runtime"; +import { WS_METHODS } from "@t3tools/contracts"; import { connectionAtomRuntime } from "../connection/runtime"; export const vcsEnvironment = createVcsEnvironmentAtoms(connectionAtomRuntime); export const vcsActionManager = createVcsActionManager(connectionAtomRuntime); + +// Action confirmations need a finite request; listRefs stays subscribed for cache updates. +export const vcsRefsQuery = createEnvironmentRpcQueryAtomFamily(connectionAtomRuntime, { + label: "mobile:vcs:refs-query", + tag: WS_METHODS.vcsListRefs, +}); From 9d5625bf20b92d905e50f4a167ad65a91b9040fb Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Mon, 26 Oct 2026 10:22:31 +0000 Subject: [PATCH 3/3] fix(mobile): reuse the finite refs command for branch decisions --- .../use-selected-thread-git-actions.test.ts | 47 ++++++++++--------- .../state/use-selected-thread-git-actions.ts | 19 ++++---- apps/mobile/src/state/vcs.ts | 8 ---- 3 files changed, 32 insertions(+), 42 deletions(-) diff --git a/apps/mobile/src/state/use-selected-thread-git-actions.test.ts b/apps/mobile/src/state/use-selected-thread-git-actions.test.ts index 78668ba7c9073..548ae2d3ce6b0 100644 --- a/apps/mobile/src/state/use-selected-thread-git-actions.test.ts +++ b/apps/mobile/src/state/use-selected-thread-git-actions.test.ts @@ -1,11 +1,9 @@ import { AuthOrchestrationOperateScope, AuthSourceControlWriteScope } from "@t3tools/contracts"; import type { VcsListRefsResult } from "@t3tools/contracts"; -import { executeAtomQuery } from "@t3tools/client-runtime/state/runtime"; import { resolveAutoFeatureBranchName } from "@t3tools/shared/git"; -import * as Effect from "effect/Effect"; import * as Cause from "effect/Cause"; -import { AsyncResult, Atom } from "effect/reactivity"; -import { afterAll, beforeEach, describe, expect, it, vi } from "vite-plus/test"; +import { AsyncResult } from "effect/reactivity"; +import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; const state = vi.hoisted(() => ({ scopes: new Set(), @@ -24,7 +22,8 @@ const state = vi.hoisted(() => ({ results: [] as { type: string; description?: string }[], afterGitAction: undefined as (() => void) | undefined, statusRefreshes: 0, - refsQuery: vi.fn(), + readRefs: vi.fn(), + cachedRefs: [] as VcsListRefsResult["refs"], })); vi.mock("react", () => ({ @@ -51,12 +50,10 @@ vi.mock("./use-selected-thread-worktree", () => ({ }), })); vi.mock("./queries", () => ({ - useBranches: () => ({ data: { refs: [] }, refresh: () => {} }), + useBranches: () => ({ data: { refs: state.cachedRefs }, refresh: () => {} }), })); vi.mock("./use-atom-command", () => ({ useAtomCommand: (command: unknown) => command })); -vi.mock("./atom-registry", async () => ({ - appAtomRegistry: (await import("effect/reactivity")).AtomRegistry.make(), -})); +vi.mock("./atom-registry", () => ({ appAtomRegistry: {} })); vi.mock("./use-remote-environment-registry", () => ({ environmentConnectedAtom: () => null, setPendingConnectionError: () => {}, @@ -85,8 +82,8 @@ vi.mock("./threads", () => ({ }, })); vi.mock("./vcs", () => ({ - vcsRefsQuery: state.refsQuery, vcsEnvironment: { + readRefs: state.readRefs, refreshStatus: async () => { state.statusRefreshes += 1; return AsyncResult.success({ refName: state.branch }); @@ -128,7 +125,6 @@ vi.mock("./vcs", () => ({ })); import { useSelectedThreadGitActions } from "./use-selected-thread-git-actions"; -import { appAtomRegistry } from "./atom-registry"; describe("thread Git mutation permissions", () => { beforeEach(() => { @@ -241,8 +237,6 @@ describe("thread Git mutation permissions", () => { ); }); -afterAll(() => appAtomRegistry.dispose()); - describe("automatic feature branch refs", () => { const empty: VcsListRefsResult = { refs: [], @@ -254,13 +248,18 @@ describe("automatic feature branch refs", () => { it.each([false, true])("awaits fresh refs before naming, cached: %s", async (cached) => { const request = Promise.withResolvers(); - let response = cached ? Promise.resolve(empty) : request.promise; - const query = Atom.make(Effect.tryPromise(() => response)).pipe(Atom.keepAlive); - state.refsQuery.mockReturnValue(query); - if (cached) { - await executeAtomQuery(appAtomRegistry, query); - response = request.promise; - } + state.cachedRefs = cached + ? [ + { + name: "old-branch", + isRemote: false, + current: false, + isDefault: false, + worktreePath: null, + }, + ] + : []; + state.readRefs.mockReturnValue(request.promise.then(AsyncResult.success)); let settled = false; const refresh = useSelectedThreadGitActions() .refreshSelectedThreadBranches() @@ -288,15 +287,17 @@ describe("automatic feature branch refs", () => { const refs = await refresh; expect(refs).toEqual([local]); expect(resolveAutoFeatureBranchName(refs!.map((ref) => ref.name))).toBe("feature/update-2"); - expect(state.refsQuery).toHaveBeenCalledWith({ + expect(state.readRefs).toHaveBeenCalledWith({ environmentId: "environment", - input: { cwd: "/repo", limit: 100 }, + input: { cwd: "/repo", limit: 100, refresh: true }, }); }); it("reports lookup failure instead of returning empty refs", async () => { state.results = []; - state.refsQuery.mockReturnValue(Atom.make(Effect.fail(new Error("Cannot list refs")))); + state.readRefs.mockResolvedValue( + AsyncResult.failure(Cause.fail(new Error("Cannot list refs"))), + ); expect(await useSelectedThreadGitActions().refreshSelectedThreadBranches()).toBeNull(); expect(state.results).toEqual([ { type: "error", title: "Git action failed", description: "Cannot list refs" }, diff --git a/apps/mobile/src/state/use-selected-thread-git-actions.ts b/apps/mobile/src/state/use-selected-thread-git-actions.ts index 3cfcc8f4621a7..0be5d3b97f90f 100644 --- a/apps/mobile/src/state/use-selected-thread-git-actions.ts +++ b/apps/mobile/src/state/use-selected-thread-git-actions.ts @@ -2,7 +2,7 @@ import { useAtomMount } from "@effect/atom-react"; import { useCallback, useMemo } from "react"; import { EnvironmentProject, EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell"; -import { executeAtomQuery, type AtomCommandResult } from "@t3tools/client-runtime/state/runtime"; +import type { AtomCommandResult } from "@t3tools/client-runtime/state/runtime"; import { createVcsStatusRevalidationAtomFamily, type GitActionRequestInput, @@ -24,7 +24,7 @@ import { AsyncResult } from "effect/reactivity"; import { useBranches } from "../state/queries"; import { threadEnvironment } from "../state/threads"; -import { vcsActionManager, vcsEnvironment, vcsRefsQuery } from "../state/vcs"; +import { vcsActionManager, vcsEnvironment } from "../state/vcs"; import { uuidv4 } from "../lib/uuid"; import { appAtomRegistry } from "./atom-registry"; import { readEnvironmentScope, useEnvironmentScope } from "./session"; @@ -57,6 +57,7 @@ export function useSelectedThreadGitActions() { const updateThreadMetadata = useAtomCommand(threadEnvironment.updateMetadata, { reportFailure: false, }); + const readRefs = useAtomCommand(vcsEnvironment.readRefs, { reportFailure: false }); const refreshStatus = useAtomCommand(vcsEnvironment.refreshStatus, { reportFailure: false }); const switchRef = useAtomCommand(vcsEnvironment.switchRef, { reportFailure: false }); const createRef = useAtomCommand(vcsEnvironment.createRef, { reportFailure: false }); @@ -219,14 +220,10 @@ export function useSelectedThreadGitActions() { const refreshSelectedThreadBranches = useCallback(async (): Promise | null> => { if (branchTarget.environmentId === null || branchTarget.cwd === null) return null; - const result = await executeAtomQuery( - appAtomRegistry, - vcsRefsQuery({ - environmentId: branchTarget.environmentId, - input: { cwd: branchTarget.cwd, limit: 100 }, - }), - { refresh: true, reportFailure: false }, - ); + const result = await readRefs({ + environmentId: branchTarget.environmentId, + input: { cwd: branchTarget.cwd, limit: 100, refresh: true }, + }); if (AsyncResult.isFailure(result)) { const error = Cause.squash(result.cause); const message = error instanceof Error ? error.message : "Failed to refresh branches."; @@ -238,7 +235,7 @@ export function useSelectedThreadGitActions() { return dedupeRemoteBranchesWithLocalMatches(result.value.refs).filter( (branch) => !branch.isRemote, ); - }, [branchTarget]); + }, [branchTarget, readRefs]); const syncSelectedThreadBranchState = useCallback( async (input: { diff --git a/apps/mobile/src/state/vcs.ts b/apps/mobile/src/state/vcs.ts index 00a612863c96a..dc8c251149f63 100644 --- a/apps/mobile/src/state/vcs.ts +++ b/apps/mobile/src/state/vcs.ts @@ -2,16 +2,8 @@ import { createVcsActionManager, createVcsEnvironmentAtoms, } from "@t3tools/client-runtime/state/vcs"; -import { createEnvironmentRpcQueryAtomFamily } from "@t3tools/client-runtime/state/runtime"; -import { WS_METHODS } from "@t3tools/contracts"; import { connectionAtomRuntime } from "../connection/runtime"; export const vcsEnvironment = createVcsEnvironmentAtoms(connectionAtomRuntime); export const vcsActionManager = createVcsActionManager(connectionAtomRuntime); - -// Action confirmations need a finite request; listRefs stays subscribed for cache updates. -export const vcsRefsQuery = createEnvironmentRpcQueryAtomFamily(connectionAtomRuntime, { - label: "mobile:vcs:refs-query", - tag: WS_METHODS.vcsListRefs, -});