@@ -49,8 +49,19 @@ import { BrowserWsRpcHarness, type NormalizedWsRpcRequestBody } from "../../test
4949import { estimateTimelineMessageHeight } from "./timelineHeight" ;
5050import { DEFAULT_CLIENT_SETTINGS } from "@t3tools/contracts/settings" ;
5151
52+ const { gitStatusStateRef } = vi . hoisted ( ( ) => ( {
53+ gitStatusStateRef : {
54+ current : { data : null , error : null , cause : null , isPending : false } as {
55+ data : unknown ;
56+ error : unknown ;
57+ cause : unknown ;
58+ isPending : boolean ;
59+ } ,
60+ } ,
61+ } ) ) ;
62+
5263vi . mock ( "../lib/gitStatusState" , ( ) => ( {
53- useGitStatus : ( ) => ( { data : null , error : null , cause : null , isPending : false } ) ,
64+ useGitStatus : ( ) => gitStatusStateRef . current ,
5465 useGitStatuses : ( ) => new Map ( ) ,
5566 refreshGitStatus : ( ) => Promise . resolve ( null ) ,
5667 resetGitStatusStateForTests : ( ) => undefined ,
@@ -1458,6 +1469,7 @@ describe("ChatView timeline estimator parity (full app)", () => {
14581469 document . body . innerHTML = "" ;
14591470 wsRequests . length = 0 ;
14601471 customWsRpcResolver = null ;
1472+ gitStatusStateRef . current = { data : null , error : null , cause : null , isPending : false } ;
14611473 useComposerDraftStore . setState ( {
14621474 draftsByThreadKey : { } ,
14631475 draftThreadsByThreadKey : { } ,
@@ -2448,6 +2460,100 @@ describe("ChatView timeline estimator parity (full app)", () => {
24482460 }
24492461 } ) ;
24502462
2463+ it ( "falls back to local send behavior for non-git drafts even when worktree mode is persisted" , async ( ) => {
2464+ gitStatusStateRef . current = {
2465+ data : {
2466+ isRepo : false ,
2467+ hasOriginRemote : false ,
2468+ isDefaultBranch : false ,
2469+ branch : null ,
2470+ hasWorkingTreeChanges : false ,
2471+ workingTree : { files : [ ] , insertions : 0 , deletions : 0 } ,
2472+ hasUpstream : false ,
2473+ aheadCount : 0 ,
2474+ behindCount : 0 ,
2475+ pr : null ,
2476+ } ,
2477+ error : null ,
2478+ cause : null ,
2479+ isPending : false ,
2480+ } ;
2481+ useComposerDraftStore . setState ( {
2482+ draftThreadsByThreadKey : {
2483+ [ THREAD_KEY ] : {
2484+ threadId : THREAD_ID ,
2485+ environmentId : LOCAL_ENVIRONMENT_ID ,
2486+ projectId : PROJECT_ID ,
2487+ logicalProjectKey : PROJECT_DRAFT_KEY ,
2488+ createdAt : NOW_ISO ,
2489+ runtimeMode : "full-access" ,
2490+ interactionMode : "default" ,
2491+ branch : null ,
2492+ worktreePath : null ,
2493+ envMode : "worktree" ,
2494+ } ,
2495+ } ,
2496+ logicalProjectDraftThreadKeyByLogicalProjectKey : {
2497+ [ PROJECT_DRAFT_KEY ] : THREAD_KEY ,
2498+ } ,
2499+ } ) ;
2500+
2501+ const mounted = await mountChatView ( {
2502+ viewport : DEFAULT_VIEWPORT ,
2503+ snapshot : createDraftOnlySnapshot ( ) ,
2504+ resolveRpc : ( body ) => {
2505+ if ( body . _tag === ORCHESTRATION_WS_METHODS . dispatchCommand ) {
2506+ return {
2507+ sequence : fixture . snapshot . snapshotSequence + 1 ,
2508+ } ;
2509+ }
2510+ return undefined ;
2511+ } ,
2512+ } ) ;
2513+
2514+ try {
2515+ useComposerDraftStore . getState ( ) . setPrompt ( THREAD_REF , "Ship it" ) ;
2516+ await waitForLayout ( ) ;
2517+
2518+ const sendButton = await waitForSendButton ( ) ;
2519+ expect ( sendButton . disabled ) . toBe ( false ) ;
2520+ sendButton . click ( ) ;
2521+
2522+ await vi . waitFor (
2523+ ( ) => {
2524+ const dispatchRequest = wsRequests . find (
2525+ ( request ) => request . _tag === ORCHESTRATION_WS_METHODS . dispatchCommand ,
2526+ ) as
2527+ | {
2528+ _tag : string ;
2529+ bootstrap ?: {
2530+ createThread ?: { projectId ?: string } ;
2531+ prepareWorktree ?: unknown ;
2532+ } ;
2533+ }
2534+ | undefined ;
2535+ expect ( dispatchRequest ) . toMatchObject ( {
2536+ _tag : ORCHESTRATION_WS_METHODS . dispatchCommand ,
2537+ bootstrap : {
2538+ createThread : {
2539+ projectId : PROJECT_ID ,
2540+ } ,
2541+ } ,
2542+ } ) ;
2543+ expect ( dispatchRequest ?. bootstrap ?. prepareWorktree ) . toBeUndefined ( ) ;
2544+ expect ( document . querySelector ( 'button[aria-label="Preparing worktree"]' ) ) . toBeNull ( ) ;
2545+ } ,
2546+ { timeout : 8_000 , interval : 16 } ,
2547+ ) ;
2548+
2549+ expect ( useComposerDraftStore . getState ( ) . getDraftThread ( THREAD_REF ) ?. envMode ?? null ) . toBe (
2550+ "local" ,
2551+ ) ;
2552+ } finally {
2553+ await mounted . cleanup ( ) ;
2554+ }
2555+ } ) ;
2556+
24512557 it ( "toggles plan mode with Shift+Tab only while the composer is focused" , async ( ) => {
24522558 const mounted = await mountChatView ( {
24532559 viewport : DEFAULT_VIEWPORT ,
0 commit comments