diff --git a/src/app/elements.ts b/src/app/elements.ts index e7a4453..b34f015 100644 --- a/src/app/elements.ts +++ b/src/app/elements.ts @@ -125,6 +125,14 @@ export function getAppElements(): AppElements { export function syncAppHeight() { const height = window.visualViewport?.height || window.innerHeight; document.documentElement.style.setProperty("--app-height", `${height}px`); + // iOS Safari can scroll the document body when focusing an input near the + // bottom of the viewport (keyboard appearance). With our fixed-height, + // overflow:hidden layout that just leaves part of the UI — e.g. the + // composer footer or send button — visually clipped at the edges of the + // visual viewport. Snap any stray scroll back to the top. + if (window.scrollY !== 0 || window.scrollX !== 0) { + window.scrollTo(0, 0); + } } export function initAppHeightSync() { diff --git a/src/styles/base.css b/src/styles/base.css index 43cab97..83ca280 100644 --- a/src/styles/base.css +++ b/src/styles/base.css @@ -14,12 +14,19 @@ --app-height: 100vh; } +@supports (height: 100dvh) { + :root { --app-height: 100dvh; } +} + * { box-sizing: border-box; } html, body { width: 100%; max-width: 100%; height: var(--app-height); overflow: hidden; + /* iOS Safari sometimes scrolls the html element when an input is focused + even with body { overflow: hidden }. Lock scroll position explicitly. */ + overscroll-behavior: none; } body { margin: 0; diff --git a/src/styles/responsive.css b/src/styles/responsive.css index 338c555..ae5683c 100644 --- a/src/styles/responsive.css +++ b/src/styles/responsive.css @@ -29,14 +29,42 @@ height: var(--app-height); min-height: 0; padding: 10px 0; + padding-top: max(10px, env(safe-area-inset-top)); padding-bottom: max(10px, env(safe-area-inset-bottom)); } .composer { margin: 0 10px; margin-bottom: max(0px, env(safe-area-inset-bottom)); + /* Defensive: never let the composer (or its footer) overflow the + viewport horizontally on iOS, where keyboard / focus-scroll quirks + can otherwise clip the send button at the right edge. */ + max-width: calc(100vw - 20px); + min-width: 0; + } + .composerFooter { + min-width: 0; + max-width: 100%; + } + /* Expanded composer needs to respect safe-area insets on iOS too. */ + .composer.expanded { + inset: max(10px, env(safe-area-inset-top)) + max(10px, env(safe-area-inset-right)) + max(10px, env(safe-area-inset-bottom)) + max(10px, env(safe-area-inset-left)); } .message.user { margin: 12px 10px 12px 0; } + /* On narrow phone screens the cwd is too long and pushes the right-side + status-bar buttons (bucket, settings) off-screen. Hide it on mobile; + the working directory is still surfaced via the empty-state chooser + and the session list. */ + .statusPath { + display: none; + } + .statusTitle { + max-width: min(60vw, 240px); + } + .contextMeterLabel { top: -13px; right: 0; diff --git a/src/styles/statusBar.css b/src/styles/statusBar.css index 5b0092e..1a36f71 100644 --- a/src/styles/statusBar.css +++ b/src/styles/statusBar.css @@ -7,6 +7,9 @@ padding: 0 8px; height: 24px; flex-shrink: 0; + /* Prevent right-side buttons (settings, bucket, …) from being pushed + off-screen on narrow viewports if a child refuses to shrink. */ + overflow: hidden; } .statusBar .iconButton { flex: 0 0 auto;