Skip to content

Commit 925ce08

Browse files
dorlugasigalCopilot
andcommitted
fix(frontend): iOS keyboard flicker, chrome blending, and TouchBar polish
- Stop fighting iOS focus-reveal scroll on keyboardOpen (root cause of bottom-half quick-tap keyboard flicker) - All textarea.focus() calls now use { preventScroll: true } - Guard synthetic xterm mousedown against tap-focus race - Halve home-indicator safe-area cap (4px to 2px) across the app - Rework SessionsHub footer to bleed bg into safe area with capped padding - Add gradients to top nav bar and TouchBar that blend with iOS chrome - TouchBar background goes transparent when keyboard is open so iOS accessory bar blends seamlessly with body bg - Layered drop shadow on TouchBar buttons so they read as floating chips - Body bg switched to --bg so areas behind iOS chrome read dark Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 61b4fb8 commit 925ce08

11 files changed

Lines changed: 250 additions & 77 deletions

File tree

src/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
name="viewport"
77
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content"
88
/>
9-
<meta name="theme-color" content="#1e1e1e" />
9+
<meta name="theme-color" content="#252526" />
1010
<meta name="apple-mobile-web-app-capable" content="yes" />
1111
<meta name="mobile-web-app-capable" content="yes" />
1212
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

src/frontend/src/App.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import LoginPage from '@/components/LoginPage/LoginPage';
44
import SessionsHub from '@/components/SessionsHub/SessionsHub';
55
import { TerminalApp } from '@/components/TerminalApp/TerminalApp';
66
import CodeViewer from '@/components/CodeViewer/CodeViewer';
7+
import { useThemeStore } from '@/stores/themeStore';
8+
import { THEMES } from '@/themes/terminalThemes';
79

810
function getPath() {
911
return window.location.pathname;
@@ -26,6 +28,24 @@ function normalizeSessionParam() {
2628
}
2729
}
2830

31+
/**
32+
* Update the iOS browser-chrome color (status-bar + home-indicator zone) to
33+
* match the visible content of the current screen, so there's no visible
34+
* seam between our content and the OS chrome.
35+
* - Terminal screen: surface (TouchBar bottom color)
36+
* - Sessions hub / Code viewer: bg (page background)
37+
*/
38+
function useChromeColor(screen: 'terminal' | 'main') {
39+
const themeId = useThemeStore((s) => s.themeId);
40+
useEffect(() => {
41+
const theme = THEMES.find((t) => t.id === themeId) ?? THEMES[0]!;
42+
const color = screen === 'terminal' ? theme.surface : theme.bg;
43+
const meta = document.querySelector('meta[name="theme-color"]');
44+
if (meta) meta.setAttribute('content', color);
45+
document.body.style.background = color;
46+
}, [screen, themeId]);
47+
}
48+
2949
export default function App() {
3050
const { authenticated, passwordRequired, login, loading } = useAuth();
3151
const [path, setPath] = useState(getPath);
@@ -40,6 +60,10 @@ export default function App() {
4060
return () => window.removeEventListener('popstate', onPopState);
4161
}, []);
4262

63+
const codeSessionId = getCodeSessionId();
64+
const isTerminalScreen = path === '/terminal' && !codeSessionId;
65+
useChromeColor(isTerminalScreen ? 'terminal' : 'main');
66+
4367
// Still checking auth
4468
if (authenticated === null) {
4569
return (
@@ -100,12 +124,11 @@ export default function App() {
100124
}
101125

102126
// Authenticated — route by pathname
103-
const codeSessionId = getCodeSessionId();
104127
if (codeSessionId) {
105128
return <CodeViewer sessionId={codeSessionId} />;
106129
}
107130

108-
if (path === '/terminal') {
131+
if (isTerminalScreen) {
109132
return <TerminalApp />;
110133
}
111134

src/frontend/src/components/SessionsHub/SessionsHub.module.css

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@
197197
display: flex;
198198
flex-direction: column;
199199
padding: 16px;
200-
padding-bottom: calc(80px + env(safe-area-inset-bottom, 0px));
200+
padding-bottom: calc(80px + min(env(safe-area-inset-bottom, 0px), 2px));
201201
gap: 12px;
202202
}
203203

@@ -242,9 +242,13 @@
242242

243243
.hubFooter {
244244
position: fixed;
245-
bottom: calc(16px + env(safe-area-inset-bottom, 0px));
246-
left: calc(16px + env(safe-area-inset-left, 0px));
247-
right: calc(16px + env(safe-area-inset-right, 0px));
245+
bottom: 0;
246+
left: 0;
247+
right: 0;
248+
padding: 8px calc(16px + env(safe-area-inset-left, 0px))
249+
calc(8px + min(env(safe-area-inset-bottom, 0px), 2px))
250+
calc(16px + env(safe-area-inset-right, 0px));
251+
background: var(--bg);
248252
display: flex;
249253
gap: 10px;
250254
z-index: 50;

src/frontend/src/components/SidePanel/SidePanel.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
overflow: hidden;
2525
padding-top: env(safe-area-inset-top, 0px);
2626
padding-left: env(safe-area-inset-left, 0px);
27-
padding-bottom: env(safe-area-inset-bottom, 0px);
27+
padding-bottom: min(env(safe-area-inset-bottom, 0px), 2px);
2828
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
2929
animation: slide-in-left 0.25s ease-out;
3030
}

src/frontend/src/components/TerminalApp/TerminalApp.module.css

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
padding-top: env(safe-area-inset-top, 0px);
1818
display: flex;
1919
align-items: center;
20-
background: var(--surface);
21-
border-bottom: 1px solid var(--border);
20+
background: linear-gradient(to bottom, var(--surface) 0%, var(--surface) 35%, var(--bg) 100%);
2221
gap: 2px;
2322
position: fixed;
2423
top: 0;
@@ -181,17 +180,29 @@
181180
.terminalArea {
182181
position: absolute;
183182
top: calc(40px + env(safe-area-inset-top, 0px));
184-
bottom: calc(80px + env(safe-area-inset-bottom, 0px) + var(--keyboard-height, 0px));
183+
/* TouchBar height (71px) + capped home-indicator gap + keyboard height.
184+
iOS Safari does not shrink the layout viewport when the keyboard opens,
185+
so we must explicitly offset by --keyboard-height (set by
186+
useMobileKeyboard via visualViewport). */
187+
bottom: calc(71px + min(env(safe-area-inset-bottom, 0px), 2px) + var(--keyboard-height, 0px));
185188
left: 0;
186189
right: 0;
187190
display: flex;
188191
min-height: 0;
189192
background: var(--bg);
190193
}
191194

192-
/* Copilot chat hides the TouchBar — reclaim its space */
195+
/* When the keyboard is open, the touchbar's home-indicator padding is
196+
covered by the keyboard, so the terminal can sit directly on top of the
197+
71px touchbar core (above the keyboard). */
198+
.layout[data-keyboard-open] .terminalArea {
199+
bottom: calc(71px + var(--keyboard-height, 0px));
200+
}
201+
202+
/* Copilot chat hides the TouchBar — reclaim its space (no keyboard offset
203+
needed because TerminalPane handles its own resize/scroll-into-view). */
193204
.layout[data-hide-touchbar] .terminalArea {
194-
bottom: calc(env(safe-area-inset-bottom, 0px) + var(--keyboard-height, 0px));
205+
bottom: calc(min(env(safe-area-inset-bottom, 0px), 2px) + var(--keyboard-height, 0px));
195206
}
196207

197208
.split {
@@ -258,15 +269,16 @@
258269
}
259270
.terminalArea {
260271
top: calc(32px + env(safe-area-inset-top, 0px));
261-
bottom: calc(44px + env(safe-area-inset-bottom, 0px) + var(--keyboard-height, 0px));
272+
bottom: calc(44px + min(env(safe-area-inset-bottom, 0px), 2px) + var(--keyboard-height, 0px));
262273
}
263-
/* TouchBar hides when keyboard is open — remove its offset */
274+
/* TouchBar hides when keyboard is open in landscape — sit directly on
275+
top of the keyboard, no touchbar offset. */
264276
.layout[data-keyboard-open] .terminalArea {
265-
bottom: calc(env(safe-area-inset-bottom, 0px) + var(--keyboard-height, 0px));
277+
bottom: var(--keyboard-height, 0px);
266278
}
267279
/* Copilot chat hides TouchBar — reclaim its space */
268280
.layout[data-hide-touchbar] .terminalArea {
269-
bottom: calc(env(safe-area-inset-bottom, 0px) + var(--keyboard-height, 0px));
281+
bottom: var(--keyboard-height, 0px);
270282
}
271283
}
272284

src/frontend/src/components/TerminalApp/TerminalApp.tsx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,6 @@ export function TerminalApp() {
7171

7272
const { keyboardOpen, keyboardHeight } = useMobileKeyboard();
7373

74-
// Reset page scroll when keyboard opens (iOS can scroll body)
75-
useEffect(() => {
76-
if (keyboardOpen) {
77-
window.scrollTo(0, 0);
78-
document.documentElement.scrollTop = 0;
79-
}
80-
}, [keyboardOpen]);
81-
8274
async function handleNewSessionCreated(id: string, type?: 'terminal' | 'copilot', ptySessionId?: string | null, model?: string) {
8375
// Add a placeholder immediately so the pane mounts right away
8476
const store = useSessionStore.getState();

0 commit comments

Comments
 (0)