@@ -356,6 +356,25 @@ function withProjectScripts(
356356 } ;
357357}
358358
359+ function setDraftThreadWithoutWorktree ( ) : void {
360+ useComposerDraftStore . setState ( {
361+ draftThreadsByThreadId : {
362+ [ THREAD_ID ] : {
363+ projectId : PROJECT_ID ,
364+ createdAt : NOW_ISO ,
365+ runtimeMode : "full-access" ,
366+ interactionMode : "default" ,
367+ branch : null ,
368+ worktreePath : null ,
369+ envMode : "local" ,
370+ } ,
371+ } ,
372+ projectDraftThreadIdByProjectId : {
373+ [ PROJECT_ID ] : THREAD_ID ,
374+ } ,
375+ } ) ;
376+ }
377+
359378function createSnapshotWithLongProposedPlan ( ) : OrchestrationReadModel {
360379 const snapshot = createSnapshotForTargetUser ( {
361380 targetMessageId : "msg-user-plan-target" as MessageId ,
@@ -1011,30 +1030,162 @@ describe("ChatView timeline estimator parity (full app)", () => {
10111030 ) ;
10121031
10131032 it ( "opens the project cwd for draft threads without a worktree path" , async ( ) => {
1014- useComposerDraftStore . setState ( {
1015- draftThreadsByThreadId : {
1016- [ THREAD_ID ] : {
1017- projectId : PROJECT_ID ,
1018- createdAt : NOW_ISO ,
1019- runtimeMode : "full-access" ,
1020- interactionMode : "default" ,
1021- branch : null ,
1022- worktreePath : null ,
1023- envMode : "local" ,
1033+ setDraftThreadWithoutWorktree ( ) ;
1034+
1035+ const mounted = await mountChatView ( {
1036+ viewport : DEFAULT_VIEWPORT ,
1037+ snapshot : createDraftOnlySnapshot ( ) ,
1038+ configureFixture : ( nextFixture ) => {
1039+ nextFixture . serverConfig = {
1040+ ...nextFixture . serverConfig ,
1041+ availableEditors : [ "vscode" ] ,
1042+ } ;
1043+ } ,
1044+ } ) ;
1045+
1046+ try {
1047+ const openButton = await waitForElement (
1048+ ( ) =>
1049+ Array . from ( document . querySelectorAll ( "button" ) ) . find (
1050+ ( button ) => button . textContent ?. trim ( ) === "Open" ,
1051+ ) as HTMLButtonElement | null ,
1052+ "Unable to find Open button." ,
1053+ ) ;
1054+ openButton . click ( ) ;
1055+
1056+ await vi . waitFor (
1057+ ( ) => {
1058+ const openRequest = wsRequests . find (
1059+ ( request ) => request . _tag === WS_METHODS . shellOpenInEditor ,
1060+ ) ;
1061+ expect ( openRequest ) . toMatchObject ( {
1062+ _tag : WS_METHODS . shellOpenInEditor ,
1063+ cwd : "/repo/project" ,
1064+ editor : "vscode" ,
1065+ } ) ;
10241066 } ,
1067+ { timeout : 8_000 , interval : 16 } ,
1068+ ) ;
1069+ } finally {
1070+ await mounted . cleanup ( ) ;
1071+ }
1072+ } ) ;
1073+
1074+ it ( "opens the project cwd with VS Code Insiders when it is the only available editor" , async ( ) => {
1075+ setDraftThreadWithoutWorktree ( ) ;
1076+
1077+ const mounted = await mountChatView ( {
1078+ viewport : DEFAULT_VIEWPORT ,
1079+ snapshot : createDraftOnlySnapshot ( ) ,
1080+ configureFixture : ( nextFixture ) => {
1081+ nextFixture . serverConfig = {
1082+ ...nextFixture . serverConfig ,
1083+ availableEditors : [ "vscode-insiders" ] ,
1084+ } ;
10251085 } ,
1026- projectDraftThreadIdByProjectId : {
1027- [ PROJECT_ID ] : THREAD_ID ,
1086+ } ) ;
1087+
1088+ try {
1089+ const openButton = await waitForElement (
1090+ ( ) =>
1091+ Array . from ( document . querySelectorAll ( "button" ) ) . find (
1092+ ( button ) => button . textContent ?. trim ( ) === "Open" ,
1093+ ) as HTMLButtonElement | null ,
1094+ "Unable to find Open button." ,
1095+ ) ;
1096+ openButton . click ( ) ;
1097+
1098+ await vi . waitFor (
1099+ ( ) => {
1100+ const openRequest = wsRequests . find (
1101+ ( request ) => request . _tag === WS_METHODS . shellOpenInEditor ,
1102+ ) ;
1103+ expect ( openRequest ) . toMatchObject ( {
1104+ _tag : WS_METHODS . shellOpenInEditor ,
1105+ cwd : "/repo/project" ,
1106+ editor : "vscode-insiders" ,
1107+ } ) ;
1108+ } ,
1109+ { timeout : 8_000 , interval : 16 } ,
1110+ ) ;
1111+ } finally {
1112+ await mounted . cleanup ( ) ;
1113+ }
1114+ } ) ;
1115+
1116+ it ( "filters the open picker menu and opens VSCodium from the menu" , async ( ) => {
1117+ setDraftThreadWithoutWorktree ( ) ;
1118+
1119+ const mounted = await mountChatView ( {
1120+ viewport : DEFAULT_VIEWPORT ,
1121+ snapshot : createDraftOnlySnapshot ( ) ,
1122+ configureFixture : ( nextFixture ) => {
1123+ nextFixture . serverConfig = {
1124+ ...nextFixture . serverConfig ,
1125+ availableEditors : [ "vscode-insiders" , "vscodium" ] ,
1126+ } ;
10281127 } ,
10291128 } ) ;
10301129
1130+ try {
1131+ const menuButton = await waitForElement (
1132+ ( ) => document . querySelector ( 'button[aria-label="Copy options"]' ) ,
1133+ "Unable to find Open picker button." ,
1134+ ) ;
1135+ ( menuButton as HTMLButtonElement ) . click ( ) ;
1136+
1137+ await waitForElement (
1138+ ( ) =>
1139+ Array . from ( document . querySelectorAll ( '[data-slot="menu-item"]' ) ) . find ( ( item ) =>
1140+ item . textContent ?. includes ( "VS Code Insiders" ) ,
1141+ ) ?? null ,
1142+ "Unable to find VS Code Insiders menu item." ,
1143+ ) ;
1144+
1145+ expect (
1146+ Array . from ( document . querySelectorAll ( '[data-slot="menu-item"]' ) ) . some ( ( item ) =>
1147+ item . textContent ?. includes ( "Zed" ) ,
1148+ ) ,
1149+ ) . toBe ( false ) ;
1150+
1151+ const vscodiumItem = await waitForElement (
1152+ ( ) =>
1153+ Array . from ( document . querySelectorAll ( '[data-slot="menu-item"]' ) ) . find ( ( item ) =>
1154+ item . textContent ?. includes ( "VSCodium" ) ,
1155+ ) ?? null ,
1156+ "Unable to find VSCodium menu item." ,
1157+ ) ;
1158+ ( vscodiumItem as HTMLElement ) . click ( ) ;
1159+
1160+ await vi . waitFor (
1161+ ( ) => {
1162+ const openRequest = wsRequests . find (
1163+ ( request ) => request . _tag === WS_METHODS . shellOpenInEditor ,
1164+ ) ;
1165+ expect ( openRequest ) . toMatchObject ( {
1166+ _tag : WS_METHODS . shellOpenInEditor ,
1167+ cwd : "/repo/project" ,
1168+ editor : "vscodium" ,
1169+ } ) ;
1170+ } ,
1171+ { timeout : 8_000 , interval : 16 } ,
1172+ ) ;
1173+ } finally {
1174+ await mounted . cleanup ( ) ;
1175+ }
1176+ } ) ;
1177+
1178+ it ( "falls back to the first installed editor when the stored favorite is unavailable" , async ( ) => {
1179+ localStorage . setItem ( "t3code:last-editor" , "vscodium" ) ;
1180+ setDraftThreadWithoutWorktree ( ) ;
1181+
10311182 const mounted = await mountChatView ( {
10321183 viewport : DEFAULT_VIEWPORT ,
10331184 snapshot : createDraftOnlySnapshot ( ) ,
10341185 configureFixture : ( nextFixture ) => {
10351186 nextFixture . serverConfig = {
10361187 ...nextFixture . serverConfig ,
1037- availableEditors : [ "vscode" ] ,
1188+ availableEditors : [ "vscode-insiders " ] ,
10381189 } ;
10391190 } ,
10401191 } ) ;
@@ -1057,7 +1208,7 @@ describe("ChatView timeline estimator parity (full app)", () => {
10571208 expect ( openRequest ) . toMatchObject ( {
10581209 _tag : WS_METHODS . shellOpenInEditor ,
10591210 cwd : "/repo/project" ,
1060- editor : "vscode" ,
1211+ editor : "vscode-insiders " ,
10611212 } ) ;
10621213 } ,
10631214 { timeout : 8_000 , interval : 16 } ,
0 commit comments