authorgravatar for 51714798+juliusmarminge@users.noreply.github.comJulius Marminge <51714798+juliusmarminge@users.noreply.github.com> 2026-06-12 19:54:08-07:00
committergravatar for me@theo.liTheo Browne <me@theo.li> 2026-06-13 22:18:37-07:00
log1b4317ec35da7cb4fb416d49f7e2a8673d536d05
tree589400839324efdc8cc88d2f83db2eaba8332499
parentf452d63fefdf00958e86b8d1f920aa25eb03f484

Restore terminal drawer header toggle


3 files changed, 47 insertions(+), 44 deletions(-)

apps/web/src/components/ChatView.browser.tsx+14-43
...@@ -23,7 +23,6 @@ import {...@@ -23,7 +23,6 @@ import {
23 ServerConfig as ServerConfigSchema,23 ServerConfig as ServerConfigSchema,
24} from "@t3tools/contracts";24} from "@t3tools/contracts";
25import { scopedThreadKey, scopeThreadRef } from "@t3tools/client-runtime";25import { scopedThreadKey, scopeThreadRef } from "@t3tools/client-runtime";
26import { DEFAULT_RESOLVED_KEYBINDINGS } from "@t3tools/shared/keybindings";
27import { createModelCapabilities, createModelSelection } from "@t3tools/shared/model";26import { createModelCapabilities, createModelSelection } from "@t3tools/shared/model";
28import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";27import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";
29import * as Option from "effect/Option";28import * as Option from "effect/Option";
...@@ -2065,26 +2064,15 @@ describe("ChatView timeline estimator parity (full app)", () => {...@@ -2065,26 +2064,15 @@ describe("ChatView timeline estimator parity (full app)", () => {
2065 targetMessageId: "msg-user-open-empty-terminal-drawer" as MessageId,2064 targetMessageId: "msg-user-open-empty-terminal-drawer" as MessageId,
2066 targetText: "open empty terminal drawer",2065 targetText: "open empty terminal drawer",
2067 }),2066 }),
2068 configureFixture: (nextFixture) => {
2069 nextFixture.serverConfig = {
2070 ...nextFixture.serverConfig,
2071 keybindings: DEFAULT_RESOLVED_KEYBINDINGS.filter(
2072 (binding) => binding.command === "terminal.toggle",
2073 ),
2074 };
2075 },
2076 });2067 });
20772068
2078 try {2069 try {
2079 window.dispatchEvent(2070 const terminalToggle = await waitForElement(
2080 new KeyboardEvent("keydown", {2071 () =>
2081 key: "j",2072 document.querySelector<HTMLButtonElement>('button[aria-label="Toggle terminal drawer"]'),
2082 metaKey: isMacPlatform(navigator.platform),2073 "Unable to find terminal drawer toggle.",
2083 ctrlKey: !isMacPlatform(navigator.platform),
2084 bubbles: true,
2085 cancelable: true,
2086 }),
2087 );2074 );
2075 terminalToggle.click();
20882076
2089 await vi.waitFor(2077 await vi.waitFor(
2090 () => {2078 () => {
...@@ -2116,28 +2104,14 @@ describe("ChatView timeline estimator parity (full app)", () => {...@@ -2116,28 +2104,14 @@ describe("ChatView timeline estimator parity (full app)", () => {
2116 targetMessageId: "msg-user-open-inline-terminal-panel" as MessageId,2104 targetMessageId: "msg-user-open-inline-terminal-panel" as MessageId,
2117 targetText: "open inline terminal panel",2105 targetText: "open inline terminal panel",
2118 }),2106 }),
2119 configureFixture: (nextFixture) => {
2120 nextFixture.serverConfig = {
2121 ...nextFixture.serverConfig,
2122 keybindings: DEFAULT_RESOLVED_KEYBINDINGS.filter(
2123 (binding) =>
2124 binding.command === "rightPanel.toggle" || binding.command === "terminal.toggle",
2125 ),
2126 };
2127 },
2128 });2107 });
21292108
2130 try {2109 try {
2131 window.dispatchEvent(2110 const rightPanelToggle = await waitForElement(
2132 new KeyboardEvent("keydown", {2111 () => document.querySelector<HTMLButtonElement>('button[aria-label="Toggle right panel"]'),
2133 key: "b",2112 "Unable to find right panel toggle.",
2134 altKey: true,
2135 metaKey: isMacPlatform(navigator.platform),
2136 ctrlKey: !isMacPlatform(navigator.platform),
2137 bubbles: true,
2138 cancelable: true,
2139 }),
2140 );2113 );
2114 rightPanelToggle.click();
21412115
2142 const addSurface = await waitForElement(2116 const addSurface = await waitForElement(
2143 () => document.querySelector<HTMLButtonElement>('button[aria-label="Add panel surface"]'),2117 () => document.querySelector<HTMLButtonElement>('button[aria-label="Add panel surface"]'),
...@@ -2213,15 +2187,12 @@ describe("ChatView timeline estimator parity (full app)", () => {...@@ -2213,15 +2187,12 @@ describe("ChatView timeline estimator parity (full app)", () => {
2213 { timeout: 8_000, interval: 16 },2187 { timeout: 8_000, interval: 16 },
2214 );2188 );
22152189
2216 window.dispatchEvent(2190 const drawerToggle = await waitForElement(
2217 new KeyboardEvent("keydown", {2191 () =>
2218 key: "j",2192 document.querySelector<HTMLButtonElement>('button[aria-label="Toggle terminal drawer"]'),
2219 metaKey: isMacPlatform(navigator.platform),2193 "Unable to find terminal drawer toggle.",
2220 ctrlKey: !isMacPlatform(navigator.platform),
2221 bubbles: true,
2222 cancelable: true,
2223 }),
2224 );2194 );
2195 drawerToggle.click();
22252196
2226 await vi.waitFor(() => {2197 await vi.waitFor(() => {
2227 expect(2198 expect(
apps/web/src/components/ChatView.tsx+3
...@@ -4501,6 +4501,8 @@ export default function ChatView(props: ChatViewProps) {...@@ -4501,6 +4501,8 @@ export default function ChatView(props: ChatViewProps) {
4501 }4501 }
4502 keybindings={keybindings}4502 keybindings={keybindings}
4503 availableEditors={availableEditors}4503 availableEditors={availableEditors}
4504 terminalAvailable={Boolean(activeProject)}
4505 terminalOpen={Boolean(terminalUiState.terminalOpen)}
4504 rightPanelAvailable={Boolean(activeProject)}4506 rightPanelAvailable={Boolean(activeProject)}
4505 rightPanelOpen={rightPanelOpen}4507 rightPanelOpen={rightPanelOpen}
4506 gitCwd={gitCwd}4508 gitCwd={gitCwd}
...@@ -4508,6 +4510,7 @@ export default function ChatView(props: ChatViewProps) {...@@ -4508,6 +4510,7 @@ export default function ChatView(props: ChatViewProps) {
4508 onAddProjectScript={saveProjectScript}4510 onAddProjectScript={saveProjectScript}
4509 onUpdateProjectScript={updateProjectScript}4511 onUpdateProjectScript={updateProjectScript}
4510 onDeleteProjectScript={deleteProjectScript}4512 onDeleteProjectScript={deleteProjectScript}
4513 onToggleTerminal={toggleTerminalVisibility}
4511 onToggleRightPanel={toggleRightPanel}4514 onToggleRightPanel={toggleRightPanel}
4512 />4515 />
4513 </header>4516 </header>
apps/web/src/components/chat/ChatHeader.tsx+30-1
...@@ -9,7 +9,7 @@ import { scopeThreadRef } from "@t3tools/client-runtime";...@@ -9,7 +9,7 @@ import { scopeThreadRef } from "@t3tools/client-runtime";
9import { memo } from "react";9import { memo } from "react";
10import GitActionsControl from "../GitActionsControl";10import GitActionsControl from "../GitActionsControl";
11import { type DraftId } from "~/composerDraftStore";11import { type DraftId } from "~/composerDraftStore";
12import { PanelRightIcon } from "lucide-react";12import { PanelBottomIcon, PanelRightIcon } from "lucide-react";
13import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";13import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
14import ProjectScriptsControl, { type NewProjectScriptInput } from "../ProjectScriptsControl";14import ProjectScriptsControl, { type NewProjectScriptInput } from "../ProjectScriptsControl";
15import { Toggle } from "../ui/toggle";15import { Toggle } from "../ui/toggle";
...@@ -29,6 +29,8 @@ interface ChatHeaderProps {...@@ -29,6 +29,8 @@ interface ChatHeaderProps {
29 preferredScriptId: string | null;29 preferredScriptId: string | null;
30 keybindings: ResolvedKeybindingsConfig;30 keybindings: ResolvedKeybindingsConfig;
31 availableEditors: ReadonlyArray<EditorId>;31 availableEditors: ReadonlyArray<EditorId>;
32 terminalAvailable: boolean;
33 terminalOpen: boolean;
32 rightPanelAvailable: boolean;34 rightPanelAvailable: boolean;
33 rightPanelOpen: boolean;35 rightPanelOpen: boolean;
34 gitCwd: string | null;36 gitCwd: string | null;
...@@ -36,6 +38,7 @@ interface ChatHeaderProps {...@@ -36,6 +38,7 @@ interface ChatHeaderProps {
36 onAddProjectScript: (input: NewProjectScriptInput) => Promise<void>;38 onAddProjectScript: (input: NewProjectScriptInput) => Promise<void>;
37 onUpdateProjectScript: (scriptId: string, input: NewProjectScriptInput) => Promise<void>;39 onUpdateProjectScript: (scriptId: string, input: NewProjectScriptInput) => Promise<void>;
38 onDeleteProjectScript: (scriptId: string) => Promise<void>;40 onDeleteProjectScript: (scriptId: string) => Promise<void>;
41 onToggleTerminal: () => void;
39 onToggleRightPanel: () => void;42 onToggleRightPanel: () => void;
40}43}
4144
...@@ -62,6 +65,8 @@ export const ChatHeader = memo(function ChatHeader({...@@ -62,6 +65,8 @@ export const ChatHeader = memo(function ChatHeader({
62 preferredScriptId,65 preferredScriptId,
63 keybindings,66 keybindings,
64 availableEditors,67 availableEditors,
68 terminalAvailable,
69 terminalOpen,
65 rightPanelAvailable,70 rightPanelAvailable,
66 rightPanelOpen,71 rightPanelOpen,
67 gitCwd,72 gitCwd,
...@@ -69,6 +74,7 @@ export const ChatHeader = memo(function ChatHeader({...@@ -69,6 +74,7 @@ export const ChatHeader = memo(function ChatHeader({
69 onAddProjectScript,74 onAddProjectScript,
70 onUpdateProjectScript,75 onUpdateProjectScript,
71 onDeleteProjectScript,76 onDeleteProjectScript,
77 onToggleTerminal,
72 onToggleRightPanel,78 onToggleRightPanel,
73}: ChatHeaderProps) {79}: ChatHeaderProps) {
74 const primaryEnvironmentId = usePrimaryEnvironmentId();80 const primaryEnvironmentId = usePrimaryEnvironmentId();
...@@ -77,6 +83,7 @@ export const ChatHeader = memo(function ChatHeader({...@@ -77,6 +83,7 @@ export const ChatHeader = memo(function ChatHeader({
77 activeThreadEnvironmentId,83 activeThreadEnvironmentId,
78 primaryEnvironmentId,84 primaryEnvironmentId,
79 });85 });
86 const terminalShortcutLabel = shortcutLabelForCommand(keybindings, "terminal.toggle");
80 const rightPanelShortcutLabel = shortcutLabelForCommand(keybindings, "rightPanel.toggle");87 const rightPanelShortcutLabel = shortcutLabelForCommand(keybindings, "rightPanel.toggle");
8188
82 return (89 return (
...@@ -123,6 +130,28 @@ export const ChatHeader = memo(function ChatHeader({...@@ -123,6 +130,28 @@ export const ChatHeader = memo(function ChatHeader({
123 {...(draftId ? { draftId } : {})}130 {...(draftId ? { draftId } : {})}
124 />131 />
125 )}132 )}
133 <Tooltip>
134 <TooltipTrigger
135 render={
136 <Toggle
137 className="shrink-0"
138 pressed={terminalOpen}
139 onPressedChange={onToggleTerminal}
140 aria-label="Toggle terminal drawer"
141 variant="ghost"
142 size="xs"
143 disabled={!terminalAvailable}
144 >
145 <PanelBottomIcon className="size-3.5" />
146 </Toggle>
147 }
148 />
149 <TooltipPopup side="bottom">
150 {terminalAvailable
151 ? `Toggle terminal drawer${terminalShortcutLabel ? ` (${terminalShortcutLabel})` : ""}`
152 : "Terminal drawer is unavailable"}
153 </TooltipPopup>
154 </Tooltip>
126 <Tooltip>155 <Tooltip>
127 <TooltipTrigger156 <TooltipTrigger
128 render={157 render={