Files
SnapOtter/tests/unit/web/app-shell-viewport.test.ts
T
SnapOtterandGitHub 3f7214bac2 fix(web): use h-dvh for app shells so mobile tool controls stay reachable (#559)
On mobile browsers 100vh is the tall viewport (URL bar retracted), so the
full-height shells (h-screen + overflow-hidden) rendered their bottom strip
below the visible area with no way to scroll to it. After an upload the tool
page's bottom control is the "Process" peek bar, so it landed off-screen on
every tool.

Switch the fixed-height shells from h-screen (100vh) to h-dvh (100dvh), which
tracks the visible viewport. AppLayout covers every tool page; the same sweep
covers the editor, login, change-password, 404, and the error/loading screens.
On desktop dvh equals vh, so desktop is unchanged. Adds a source-level guard
(tests/unit/web/app-shell-viewport.test.ts) since headless Chromium has no URL
bar and cannot reproduce the bug.
2026-07-17 23:53:26 +08:00

41 lines
1.6 KiB
TypeScript

import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
/**
* Regression guard for the mobile "can't reach the Process button" bug.
*
* The full-height, overflow-hidden app shells must size to the *dynamic* viewport
* (`h-dvh` / 100dvh), never `h-screen` (100vh). On mobile browsers 100vh is the
* tall viewport measured with the URL bar retracted, so a 100vh shell with
* `overflow: hidden` pushes its bottom strip (the fixed bottom nav and the in-flow
* tool "Process" peek bar) below the visible area with no way to scroll to it.
* `dvh` tracks the actual visible viewport, keeping those controls reachable.
*
* This can't be caught by a behavioral e2e test: headless Chromium has no dynamic
* URL bar, so `100vh === visible height` there and the bug never reproduces. A
* source-level guard is the only thing that stops a well-meaning "cleanup" from
* reverting `h-dvh` to `h-screen`.
*/
const SHELL = "flex flex-col h-dvh";
const BROKEN = "flex flex-col h-screen";
const SHELLS = {
"app-layout.tsx": "../../../apps/web/src/components/layout/app-layout.tsx",
"editor-page.tsx": "../../../apps/web/src/pages/editor-page.tsx",
} as const;
describe("full-height app shells use the dynamic viewport unit", () => {
for (const [name, rel] of Object.entries(SHELLS)) {
const src = readFileSync(fileURLToPath(new URL(rel, import.meta.url)), "utf8");
it(`${name} sizes the shell with h-dvh`, () => {
expect(src).toContain(SHELL);
});
it(`${name} does not use h-screen for the shell`, () => {
expect(src).not.toContain(BROKEN);
});
}
});