authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-07-23 02:06:00-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-23 02:06:00-07:00
log16491a84b9bb2842ad6db41635d087647fc2df5b
treee0275fe2c0679620cd50db5acae33cb7c075fed4
parentb44ed835c63caafae619e303ca019a251c2698b2
signature Signed by PGP key B5690EEEBB952194

fix(web): new-thread defaults ignored for remote environments (#4276)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

3 files changed, 27 insertions(+), 23 deletions(-)

apps/web/src/components/ChatView.tsx+8-3
...@@ -189,6 +189,7 @@ import { useEnvironmentQuery } from "../state/query";...@@ -189,6 +189,7 @@ import { useEnvironmentQuery } from "../state/query";
189import {189import {
190 primaryServerAvailableEditorsAtom,190 primaryServerAvailableEditorsAtom,
191 primaryServerKeybindingsAtom,191 primaryServerKeybindingsAtom,
192 primaryServerSettingsAtom,
192 serverEnvironment,193 serverEnvironment,
193} from "../state/server";194} from "../state/server";
194import { terminalEnvironment } from "../state/terminal";195import { terminalEnvironment } from "../state/terminal";
...@@ -1153,6 +1154,10 @@ function ChatViewContent(props: ChatViewProps) {...@@ -1153,6 +1154,10 @@ function ChatViewContent(props: ChatViewProps) {
1153 (store) => store.threadLastVisitedAtById[routeThreadKey],1154 (store) => store.threadLastVisitedAtById[routeThreadKey],
1154 );1155 );
1155 const settings = useEnvironmentSettings(environmentId);1156 const settings = useEnvironmentSettings(environmentId);
1157 // New-thread defaults live in the primary environment's settings.json (the
1158 // settings UI never writes to remote environments), so read them from the
1159 // primary server rather than the thread's environment.
1160 const primaryServerSettings = useAtomValue(primaryServerSettingsAtom);
1156 const setStickyComposerModelSelection = useComposerDraftStore(1161 const setStickyComposerModelSelection = useComposerDraftStore(
1157 (store) => store.setStickyModelSelection,1162 (store) => store.setStickyModelSelection,
1158 );1163 );
...@@ -3785,7 +3790,7 @@ function ChatViewContent(props: ChatViewProps) {...@@ -3785,7 +3790,7 @@ function ChatViewContent(props: ChatViewProps) {
3785 ? (draftThread?.startFromOrigin ?? false)3790 ? (draftThread?.startFromOrigin ?? false)
3786 : canOverrideServerThreadEnvMode3791 : canOverrideServerThreadEnvMode
3787 ? (pendingServerThreadStartFromOriginByThreadId[activeThread?.id ?? ""] ??3792 ? (pendingServerThreadStartFromOriginByThreadId[activeThread?.id ?? ""] ??
3788 settings.newWorktreesStartFromOrigin)3793 primaryServerSettings.newWorktreesStartFromOrigin)
3789 : false;3794 : false;
3790 const sendEnvMode = resolveSendEnvMode({3795 const sendEnvMode = resolveSendEnvMode({
3791 requestedEnvMode: envMode,3796 requestedEnvMode: envMode,
...@@ -5254,7 +5259,7 @@ function ChatViewContent(props: ChatViewProps) {...@@ -5254,7 +5259,7 @@ function ChatViewContent(props: ChatViewProps) {
5254 envMode: mode,5259 envMode: mode,
5255 startFromOrigin: resolveNewDraftStartFromOrigin({5260 startFromOrigin: resolveNewDraftStartFromOrigin({
5256 envMode: mode,5261 envMode: mode,
5257 newWorktreesStartFromOrigin: settings.newWorktreesStartFromOrigin,5262 newWorktreesStartFromOrigin: primaryServerSettings.newWorktreesStartFromOrigin,
5258 }),5263 }),
5259 ...(mode === "worktree" && draftThread?.worktreePath ? { worktreePath: null } : {}),5264 ...(mode === "worktree" && draftThread?.worktreePath ? { worktreePath: null } : {}),
5260 });5265 });
...@@ -5266,7 +5271,7 @@ function ChatViewContent(props: ChatViewProps) {...@@ -5266,7 +5271,7 @@ function ChatViewContent(props: ChatViewProps) {
5266 composerDraftTarget,5271 composerDraftTarget,
5267 draftThread?.worktreePath,5272 draftThread?.worktreePath,
5268 isLocalDraftThread,5273 isLocalDraftThread,
5269 settings.newWorktreesStartFromOrigin,5274 primaryServerSettings.newWorktreesStartFromOrigin,
5270 setPendingServerThreadEnvMode,5275 setPendingServerThreadEnvMode,
5271 scheduleComposerFocus,5276 scheduleComposerFocus,
5272 setDraftThreadContext,5277 setDraftThreadContext,
apps/web/src/components/Sidebar.tsx+6-8
...@@ -43,7 +43,6 @@ import { restrictToFirstScrollableAncestor, restrictToVerticalAxis } from "@dnd-...@@ -43,7 +43,6 @@ import { restrictToFirstScrollableAncestor, restrictToVerticalAxis } from "@dnd-
43import { CSS } from "@dnd-kit/utilities";43import { CSS } from "@dnd-kit/utilities";
44import {44import {
45 type ContextMenuItem,45 type ContextMenuItem,
46 DEFAULT_SERVER_SETTINGS,
47 ProjectId,46 ProjectId,
48 type ScopedThreadRef,47 type ScopedThreadRef,
49 type ResolvedKeybindingsConfig,48 type ResolvedKeybindingsConfig,
...@@ -81,7 +80,6 @@ import {...@@ -81,7 +80,6 @@ import {
81 readThreadShell,80 readThreadShell,
82 useProject,81 useProject,
83 useProjects,82 useProjects,
84 useServerConfigs,
85 useThreadShells,83 useThreadShells,
86 useThreadShellsForProjectRefs,84 useThreadShellsForProjectRefs,
87} from "../state/entities";85} from "../state/entities";
...@@ -203,7 +201,7 @@ import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";...@@ -203,7 +201,7 @@ import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
203import { useIsMobile } from "~/hooks/useMediaQuery";201import { useIsMobile } from "~/hooks/useMediaQuery";
204import { CommandDialogTrigger } from "./ui/command";202import { CommandDialogTrigger } from "./ui/command";
205import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings";203import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings";
206import { primaryServerKeybindingsAtom } from "../state/server";204import { primaryServerKeybindingsAtom, primaryServerSettingsAtom } from "../state/server";
207import {205import {
208 derivePhysicalProjectKey,206 derivePhysicalProjectKey,
209 deriveProjectGroupingOverrideKey,207 deriveProjectGroupingOverrideKey,
...@@ -1113,7 +1111,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec...@@ -1113,7 +1111,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
1113 (settings) => settings.confirmThreadArchive,1111 (settings) => settings.confirmThreadArchive,
1114 );1112 );
1115 const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings);1113 const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings);
1116 const serverConfigs = useServerConfigs();1114 const primaryServerSettings = useAtomValue(primaryServerSettingsAtom);
1117 const deleteProject = useAtomCommand(projectEnvironment.delete, {1115 const deleteProject = useAtomCommand(projectEnvironment.delete, {
1118 reportFailure: false,1116 reportFailure: false,
1119 });1117 });
...@@ -1901,10 +1899,10 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec...@@ -1901,10 +1899,10 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
1901 : null;1899 : null;
1902 const seedContext = resolveSidebarNewThreadSeedContext({1900 const seedContext = resolveSidebarNewThreadSeedContext({
1903 projectId: member.id,1901 projectId: member.id,
1902 // The default env mode is a user preference stored on the primary
1903 // environment's settings.json; remote environments never carry it.
1904 defaultEnvMode: resolveSidebarNewThreadEnvMode({1904 defaultEnvMode: resolveSidebarNewThreadEnvMode({
1905 defaultEnvMode:1905 defaultEnvMode: primaryServerSettings.defaultThreadEnvMode,
1906 serverConfigs.get(member.environmentId)?.settings.defaultThreadEnvMode ??
1907 DEFAULT_SERVER_SETTINGS.defaultThreadEnvMode,
1908 }),1906 }),
1909 activeThread:1907 activeThread:
1910 currentActiveThread && currentActiveThread.projectId === member.id1908 currentActiveThread && currentActiveThread.projectId === member.id
...@@ -1953,7 +1951,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec...@@ -1953,7 +1951,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
1953 }1951 }
1954 })();1952 })();
1955 },1953 },
1956 [handleNewThread, isMobile, router, serverConfigs, setOpenMobile],1954 [handleNewThread, isMobile, primaryServerSettings.defaultThreadEnvMode, router, setOpenMobile],
1957 );1955 );
19581956
1959 const handleCreateThreadClick = useCallback(1957 const handleCreateThreadClick = useCallback(
apps/web/src/hooks/useHandleNewThread.ts+13-12
...@@ -1,13 +1,10 @@...@@ -1,13 +1,10 @@
1import { useAtomValue } from "@effect/atom-react";
1import {2import {
2 scopedProjectKey,3 scopedProjectKey,
3 scopeProjectRef,4 scopeProjectRef,
4 scopeThreadRef,5 scopeThreadRef,
5} from "@t3tools/client-runtime/environment";6} from "@t3tools/client-runtime/environment";
6import {7import { DEFAULT_RUNTIME_MODE, type ScopedProjectRef } from "@t3tools/contracts";
7 DEFAULT_RUNTIME_MODE,
8 DEFAULT_SERVER_SETTINGS,
9 type ScopedProjectRef,
10} from "@t3tools/contracts";
11import { useParams, useRouter } from "@tanstack/react-router";8import { useParams, useRouter } from "@tanstack/react-router";
12import { useCallback, useMemo } from "react";9import { useCallback, useMemo } from "react";
13import {10import {
...@@ -23,15 +20,21 @@ import {...@@ -23,15 +20,21 @@ import {
23 getProjectOrderKey,20 getProjectOrderKey,
24 selectProjectGroupingSettings,21 selectProjectGroupingSettings,
25} from "../logicalProject";22} from "../logicalProject";
26import { readThreadShell, useProjects, useServerConfigs, useThread } from "../state/entities";23import { readThreadShell, useProjects, useThread } from "../state/entities";
27import { resolveNewDraftStartFromOrigin } from "../lib/chatThreadActions";24import { resolveNewDraftStartFromOrigin } from "../lib/chatThreadActions";
25import { primaryServerSettingsAtom } from "../state/server";
28import { resolveThreadRouteTarget } from "../threadRoutes";26import { resolveThreadRouteTarget } from "../threadRoutes";
29import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore";27import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore";
30import { useClientSettings } from "./useSettings";28import { useClientSettings } from "./useSettings";
3129
32export function useNewThreadHandler() {30export function useNewThreadHandler() {
33 const projects = useProjects();31 const projects = useProjects();
34 const serverConfigs = useServerConfigs();32 // New-thread defaults are a user preference, and the settings UI only ever
33 // edits the primary environment's settings.json. Reading the target
34 // environment's own settings here would silently reset remote projects to
35 // the decoded defaults ("local" mode, current branch), since nothing can
36 // set those values on a remote server.
37 const primaryServerSettings = useAtomValue(primaryServerSettingsAtom);
35 const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings);38 const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings);
36 const router = useRouter();39 const router = useRouter();
37 const getCurrentRouteTarget = useCallback(() => {40 const getCurrentRouteTarget = useCallback(() => {
...@@ -64,8 +67,6 @@ export function useNewThreadHandler() {...@@ -64,8 +67,6 @@ export function useNewThreadHandler() {
64 candidate.id === projectRef.projectId &&67 candidate.id === projectRef.projectId &&
65 candidate.environmentId === projectRef.environmentId,68 candidate.environmentId === projectRef.environmentId,
66 );69 );
67 const environmentSettings =
68 serverConfigs.get(projectRef.environmentId)?.settings ?? DEFAULT_SERVER_SETTINGS;
69 const logicalProjectKey = project70 const logicalProjectKey = project
70 ? deriveLogicalProjectKeyFromSettings(project, projectGroupingSettings)71 ? deriveLogicalProjectKeyFromSettings(project, projectGroupingSettings)
71 : scopedProjectKey(projectRef);72 : scopedProjectKey(projectRef);
...@@ -161,7 +162,7 @@ export function useNewThreadHandler() {...@@ -161,7 +162,7 @@ export function useNewThreadHandler() {
161 const draftId = newDraftId();162 const draftId = newDraftId();
162 const threadId = newThreadId();163 const threadId = newThreadId();
163 const createdAt = new Date().toISOString();164 const createdAt = new Date().toISOString();
164 const initialEnvMode = options?.envMode ?? environmentSettings.defaultThreadEnvMode;165 const initialEnvMode = options?.envMode ?? primaryServerSettings.defaultThreadEnvMode;
165 return (async () => {166 return (async () => {
166 setLogicalProjectDraftThreadId(logicalProjectKey, projectRef, draftId, {167 setLogicalProjectDraftThreadId(logicalProjectKey, projectRef, draftId, {
167 threadId,168 threadId,
...@@ -173,7 +174,7 @@ export function useNewThreadHandler() {...@@ -173,7 +174,7 @@ export function useNewThreadHandler() {
173 options?.startFromOrigin ??174 options?.startFromOrigin ??
174 resolveNewDraftStartFromOrigin({175 resolveNewDraftStartFromOrigin({
175 envMode: initialEnvMode,176 envMode: initialEnvMode,
176 newWorktreesStartFromOrigin: environmentSettings.newWorktreesStartFromOrigin,177 newWorktreesStartFromOrigin: primaryServerSettings.newWorktreesStartFromOrigin,
177 }),178 }),
178 runtimeMode: DEFAULT_RUNTIME_MODE,179 runtimeMode: DEFAULT_RUNTIME_MODE,
179 });180 });
...@@ -186,7 +187,7 @@ export function useNewThreadHandler() {...@@ -186,7 +187,7 @@ export function useNewThreadHandler() {
186 });187 });
187 })();188 })();
188 },189 },
189 [getCurrentRouteTarget, projectGroupingSettings, projects, router, serverConfigs],190 [getCurrentRouteTarget, primaryServerSettings, projectGroupingSettings, projects, router],
190 );191 );
191}192}
192193