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";
189189import {
190190 primaryServerAvailableEditorsAtom,
191191 primaryServerKeybindingsAtom,
192 primaryServerSettingsAtom,
192193 serverEnvironment,
193194} from "../state/server";
194195import { terminalEnvironment } from "../state/terminal";
......@@ -1153,6 +1154,10 @@ function ChatViewContent(props: ChatViewProps) {
11531154 (store) => store.threadLastVisitedAtById[routeThreadKey],
11541155 );
11551156 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);
11561161 const setStickyComposerModelSelection = useComposerDraftStore(
11571162 (store) => store.setStickyModelSelection,
11581163 );
......@@ -3785,7 +3790,7 @@ function ChatViewContent(props: ChatViewProps) {
37853790 ? (draftThread?.startFromOrigin ?? false)
37863791 : canOverrideServerThreadEnvMode
37873792 ? (pendingServerThreadStartFromOriginByThreadId[activeThread?.id ?? ""] ??
3788 settings.newWorktreesStartFromOrigin)
3793 primaryServerSettings.newWorktreesStartFromOrigin)
37893794 : false;
37903795 const sendEnvMode = resolveSendEnvMode({
37913796 requestedEnvMode: envMode,
......@@ -5254,7 +5259,7 @@ function ChatViewContent(props: ChatViewProps) {
52545259 envMode: mode,
52555260 startFromOrigin: resolveNewDraftStartFromOrigin({
52565261 envMode: mode,
5257 newWorktreesStartFromOrigin: settings.newWorktreesStartFromOrigin,
5262 newWorktreesStartFromOrigin: primaryServerSettings.newWorktreesStartFromOrigin,
52585263 }),
52595264 ...(mode === "worktree" && draftThread?.worktreePath ? { worktreePath: null } : {}),
52605265 });
......@@ -5266,7 +5271,7 @@ function ChatViewContent(props: ChatViewProps) {
52665271 composerDraftTarget,
52675272 draftThread?.worktreePath,
52685273 isLocalDraftThread,
5269 settings.newWorktreesStartFromOrigin,
5274 primaryServerSettings.newWorktreesStartFromOrigin,
52705275 setPendingServerThreadEnvMode,
52715276 scheduleComposerFocus,
52725277 setDraftThreadContext,
apps/web/src/components/Sidebar.tsx+6-8
......@@ -43,7 +43,6 @@ import { restrictToFirstScrollableAncestor, restrictToVerticalAxis } from "@dnd-
4343import { CSS } from "@dnd-kit/utilities";
4444import {
4545 type ContextMenuItem,
46 DEFAULT_SERVER_SETTINGS,
4746 ProjectId,
4847 type ScopedThreadRef,
4948 type ResolvedKeybindingsConfig,
......@@ -81,7 +80,6 @@ import {
8180 readThreadShell,
8281 useProject,
8382 useProjects,
84 useServerConfigs,
8583 useThreadShells,
8684 useThreadShellsForProjectRefs,
8785} from "../state/entities";
......@@ -203,7 +201,7 @@ import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
203201import { useIsMobile } from "~/hooks/useMediaQuery";
204202import { CommandDialogTrigger } from "./ui/command";
205203import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings";
206import { primaryServerKeybindingsAtom } from "../state/server";
204import { primaryServerKeybindingsAtom, primaryServerSettingsAtom } from "../state/server";
207205import {
208206 derivePhysicalProjectKey,
209207 deriveProjectGroupingOverrideKey,
......@@ -1113,7 +1111,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
11131111 (settings) => settings.confirmThreadArchive,
11141112 );
11151113 const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings);
1116 const serverConfigs = useServerConfigs();
1114 const primaryServerSettings = useAtomValue(primaryServerSettingsAtom);
11171115 const deleteProject = useAtomCommand(projectEnvironment.delete, {
11181116 reportFailure: false,
11191117 });
......@@ -1901,10 +1899,10 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
19011899 : null;
19021900 const seedContext = resolveSidebarNewThreadSeedContext({
19031901 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.
19041904 defaultEnvMode: resolveSidebarNewThreadEnvMode({
1905 defaultEnvMode:
1906 serverConfigs.get(member.environmentId)?.settings.defaultThreadEnvMode ??
1907 DEFAULT_SERVER_SETTINGS.defaultThreadEnvMode,
1905 defaultEnvMode: primaryServerSettings.defaultThreadEnvMode,
19081906 }),
19091907 activeThread:
19101908 currentActiveThread && currentActiveThread.projectId === member.id
......@@ -1953,7 +1951,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
19531951 }
19541952 })();
19551953 },
1956 [handleNewThread, isMobile, router, serverConfigs, setOpenMobile],
1954 [handleNewThread, isMobile, primaryServerSettings.defaultThreadEnvMode, router, setOpenMobile],
19571955 );
19581956
19591957 const handleCreateThreadClick = useCallback(
apps/web/src/hooks/useHandleNewThread.ts+13-12
......@@ -1,13 +1,10 @@
1import { useAtomValue } from "@effect/atom-react";
12import {
23 scopedProjectKey,
34 scopeProjectRef,
45 scopeThreadRef,
56} from "@t3tools/client-runtime/environment";
6import {
7 DEFAULT_RUNTIME_MODE,
8 DEFAULT_SERVER_SETTINGS,
9 type ScopedProjectRef,
10} from "@t3tools/contracts";
7import { DEFAULT_RUNTIME_MODE, type ScopedProjectRef } from "@t3tools/contracts";
118import { useParams, useRouter } from "@tanstack/react-router";
129import { useCallback, useMemo } from "react";
1310import {
......@@ -23,15 +20,21 @@ import {
2320 getProjectOrderKey,
2421 selectProjectGroupingSettings,
2522} from "../logicalProject";
26import { readThreadShell, useProjects, useServerConfigs, useThread } from "../state/entities";
23import { readThreadShell, useProjects, useThread } from "../state/entities";
2724import { resolveNewDraftStartFromOrigin } from "../lib/chatThreadActions";
25import { primaryServerSettingsAtom } from "../state/server";
2826import { resolveThreadRouteTarget } from "../threadRoutes";
2927import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore";
3028import { useClientSettings } from "./useSettings";
3129
3230export function useNewThreadHandler() {
3331 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);
3538 const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings);
3639 const router = useRouter();
3740 const getCurrentRouteTarget = useCallback(() => {
......@@ -64,8 +67,6 @@ export function useNewThreadHandler() {
6467 candidate.id === projectRef.projectId &&
6568 candidate.environmentId === projectRef.environmentId,
6669 );
67 const environmentSettings =
68 serverConfigs.get(projectRef.environmentId)?.settings ?? DEFAULT_SERVER_SETTINGS;
6970 const logicalProjectKey = project
7071 ? deriveLogicalProjectKeyFromSettings(project, projectGroupingSettings)
7172 : scopedProjectKey(projectRef);
......@@ -161,7 +162,7 @@ export function useNewThreadHandler() {
161162 const draftId = newDraftId();
162163 const threadId = newThreadId();
163164 const createdAt = new Date().toISOString();
164 const initialEnvMode = options?.envMode ?? environmentSettings.defaultThreadEnvMode;
165 const initialEnvMode = options?.envMode ?? primaryServerSettings.defaultThreadEnvMode;
165166 return (async () => {
166167 setLogicalProjectDraftThreadId(logicalProjectKey, projectRef, draftId, {
167168 threadId,
......@@ -173,7 +174,7 @@ export function useNewThreadHandler() {
173174 options?.startFromOrigin ??
174175 resolveNewDraftStartFromOrigin({
175176 envMode: initialEnvMode,
176 newWorktreesStartFromOrigin: environmentSettings.newWorktreesStartFromOrigin,
177 newWorktreesStartFromOrigin: primaryServerSettings.newWorktreesStartFromOrigin,
177178 }),
178179 runtimeMode: DEFAULT_RUNTIME_MODE,
179180 });
......@@ -186,7 +187,7 @@ export function useNewThreadHandler() {
186187 });
187188 })();
188189 },
189 [getCurrentRouteTarget, projectGroupingSettings, projects, router, serverConfigs],
190 [getCurrentRouteTarget, primaryServerSettings, projectGroupingSettings, projects, router],
190191 );
191192}
192193