authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-09-04 14:32:15-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 14:32:15-04:00
logcbe93e8dfba68ff6fbe8c69e43a633fdc79db62d
tree9785e2cbdaf8afbf648c7a3955782d5df8521603
parentcfc9bf34156ddcc4a98b7f5c67193adb5aedce06
signature Signed by PGP key B5690EEEBB952194

fix(web): show project settings for new threads (#9743)


2 files changed, 34 insertions(+), 3 deletions(-)

apps/web/src/components/ChatView.tsx+15-1
...@@ -1970,6 +1970,18 @@ export default function ChatView(props: ChatViewProps) {...@@ -1970,6 +1970,18 @@ export default function ChatView(props: ChatViewProps) {
1970 const handleNewThreadInActiveProject = useCallback(() => {1970 const handleNewThreadInActiveProject = useCallback(() => {
1971 startNewThreadForProject(activeProjectRef, handleNewThread);1971 startNewThreadForProject(activeProjectRef, handleNewThread);
1972 }, [activeProjectRef, handleNewThread]);1972 }, [activeProjectRef, handleNewThread]);
1973 const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings);
1974 const activeDraftLogicalProjectKey =
1975 !isServerThread && activeProject
1976 ? deriveLogicalProjectKeyFromSettings(activeProject, projectGroupingSettings)
1977 : undefined;
1978 const handleOpenDraftProjectSettings = useCallback(() => {
1979 if (!activeDraftLogicalProjectKey) return;
1980 void navigate({
1981 to: "/projects/$projectKey",
1982 params: { projectKey: activeDraftLogicalProjectKey },
1983 });
1984 }, [activeDraftLogicalProjectKey, navigate]);
1973 const activeEnvironmentShell = useEnvironmentQuery(1985 const activeEnvironmentShell = useEnvironmentQuery(
1974 activeThread ? environmentShell.stateAtom(activeThread.environmentId) : null,1986 activeThread ? environmentShell.stateAtom(activeThread.environmentId) : null,
1975 );1987 );
...@@ -1977,7 +1989,6 @@ export default function ChatView(props: ChatViewProps) {...@@ -1977,7 +1989,6 @@ export default function ChatView(props: ChatViewProps) {
1977 const activeProjectKey = activeProject1989 const activeProjectKey = activeProject
1978 ? `${activeProject.environmentId}:${activeProject.workspaceRoot}`1990 ? `${activeProject.environmentId}:${activeProject.workspaceRoot}`
1979 : null;1991 : null;
1980 const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings);
1981 const clientSettingsHydrated = useClientSettingsHydrated();1992 const clientSettingsHydrated = useClientSettingsHydrated();
1982 const [pendingFileSurfaceIdsByProject, setPendingFileSurfaceIdsByProject] = useState<1993 const [pendingFileSurfaceIdsByProject, setPendingFileSurfaceIdsByProject] = useState<
1983 ReadonlyMap<string, ReadonlySet<string>>1994 ReadonlyMap<string, ReadonlySet<string>>
...@@ -7729,6 +7740,9 @@ export default function ChatView(props: ChatViewProps) {...@@ -7729,6 +7740,9 @@ export default function ChatView(props: ChatViewProps) {
7729 rightPanelOpen={rightPanelOpen}7740 rightPanelOpen={rightPanelOpen}
7730 gitCwd={gitCwd}7741 gitCwd={gitCwd}
7731 onNewThreadInProject={handleNewThreadInActiveProject}7742 onNewThreadInProject={handleNewThreadInActiveProject}
7743 {...(activeDraftLogicalProjectKey
7744 ? { onOpenProjectSettings: handleOpenDraftProjectSettings }
7745 : {})}
7732 onRunProjectScript={runProjectScript}7746 onRunProjectScript={runProjectScript}
7733 onAddProjectScript={saveProjectScript}7747 onAddProjectScript={saveProjectScript}
7734 onUpdateProjectScript={updateProjectScript}7748 onUpdateProjectScript={updateProjectScript}
apps/web/src/components/chat/ChatHeader.tsx+19-2
...@@ -35,6 +35,7 @@ import { useRemoteOpenState, type RemoteOpenMode } from "../../remoteOpen";...@@ -35,6 +35,7 @@ import { useRemoteOpenState, type RemoteOpenMode } from "../../remoteOpen";
35import { usePrimaryEnvironmentId } from "../../state/environments";35import { usePrimaryEnvironmentId } from "../../state/environments";
36import { useT3ProjectFileScripts } from "~/hooks/useT3ProjectFileScripts";36import { useT3ProjectFileScripts } from "~/hooks/useT3ProjectFileScripts";
37import { useThreadActionMenu } from "~/hooks/useThreadActionMenu";37import { useThreadActionMenu } from "~/hooks/useThreadActionMenu";
38import { readLocalApi } from "~/localApi";
38import { threadEnvironment } from "../../state/threads";39import { threadEnvironment } from "../../state/threads";
39import { useAtomCommand } from "../../state/use-atom-command";40import { useAtomCommand } from "../../state/use-atom-command";
40import { observeResponsiveBreakpointFade, usePanelAnimationSettings } from "../../panelAnimations";41import { observeResponsiveBreakpointFade, usePanelAnimationSettings } from "../../panelAnimations";
...@@ -66,6 +67,7 @@ interface ChatHeaderProps {...@@ -66,6 +67,7 @@ interface ChatHeaderProps {
66 gitCwd: string | null;67 gitCwd: string | null;
67 readonly onOpenPullRequest?: ((number: number) => void) | undefined;68 readonly onOpenPullRequest?: ((number: number) => void) | undefined;
68 onNewThreadInProject: () => void;69 onNewThreadInProject: () => void;
70 onOpenProjectSettings?: (() => void) | undefined;
69 onRunProjectScript: (script: ProjectScript) => void;71 onRunProjectScript: (script: ProjectScript) => void;
70 onAddProjectScript: (input: NewProjectScriptInput) => Promise<ProjectScriptActionResult>;72 onAddProjectScript: (input: NewProjectScriptInput) => Promise<ProjectScriptActionResult>;
71 onUpdateProjectScript: (73 onUpdateProjectScript: (
...@@ -137,6 +139,7 @@ export const ChatHeader = memo(function ChatHeader({...@@ -137,6 +139,7 @@ export const ChatHeader = memo(function ChatHeader({
137 gitCwd,139 gitCwd,
138 onOpenPullRequest,140 onOpenPullRequest,
139 onNewThreadInProject,141 onNewThreadInProject,
142 onOpenProjectSettings,
140 onRunProjectScript,143 onRunProjectScript,
141 onAddProjectScript,144 onAddProjectScript,
142 onUpdateProjectScript,145 onUpdateProjectScript,
...@@ -275,15 +278,29 @@ export const ChatHeader = memo(function ChatHeader({...@@ -275,15 +278,29 @@ export const ChatHeader = memo(function ChatHeader({
275 );278 );
276 const handleHeaderContextMenu = useCallback(279 const handleHeaderContextMenu = useCallback(
277 (event: ReactMouseEvent) => {280 (event: ReactMouseEvent) => {
278 if (!isServerThread || renamingTitle !== null) return;281 if (renamingTitle !== null) return;
279 // The right-side controls (git, scripts, open-in) keep their own282 // The right-side controls (git, scripts, open-in) keep their own
280 // behavior; only the breadcrumb area opens the thread menu.283 // behavior; only the breadcrumb area opens the thread menu.
281 if ((event.target as HTMLElement).closest("[data-chat-header-actions]")) return;284 if ((event.target as HTMLElement).closest("[data-chat-header-actions]")) return;
285 if (!isServerThread && onOpenProjectSettings === undefined) return;
282 cancelPendingTitleMenu();286 cancelPendingTitleMenu();
283 event.preventDefault();287 event.preventDefault();
288 if (!isServerThread) {
289 const api = readLocalApi();
290 if (!api) return;
291 void api.contextMenu
292 .show([{ id: "project-settings", label: "Project settings", icon: "settings" }], {
293 x: event.clientX,
294 y: event.clientY,
295 })
296 .then((action) => {
297 if (action === "project-settings") onOpenProjectSettings?.();
298 });
299 return;
300 }
284 openMenu({ x: event.clientX, y: event.clientY });301 openMenu({ x: event.clientX, y: event.clientY });
285 },302 },
286 [cancelPendingTitleMenu, isServerThread, openMenu, renamingTitle],303 [cancelPendingTitleMenu, isServerThread, onOpenProjectSettings, openMenu, renamingTitle],
287 );304 );
288 const handleRenameKeyDown = useCallback(305 const handleRenameKeyDown = useCallback(
289 (event: ReactKeyboardEvent<HTMLInputElement>) => {306 (event: ReactKeyboardEvent<HTMLInputElement>) => {