mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-10 09:19:42 +02:00
fix(ui): #542 landing fixes
Follow the keyboard: the menu's cap now uses var(--app-height, 100dvh) instead of 100dvh. The viewport meta has no interactive-widget, so dvh does not shrink for an on-screen keyboard, while MobileDetection always sets --app-height to the visual viewport height and KeyboardHandler keeps it there while the keyboard is up (body and .app already size the same way). Subtract both safe areas from the cap: in the iPhone home-screen app the phone header grows by the top inset and the toolbar sits above the bottom inset, so without them the top of a full menu slid under the fixed header. Add overflow-x: hidden. overflow-y: auto computes overflow-x to auto, so a long nowrap custom-endpoint label showed a horizontal scrollbar that touch-action: pan-y cannot pan (the same trap the file documents for .run-mode-history). Raise the toolbar while the Run menu is open, by adding .toolbar:has(.run-mode-menu.active) to the existing popover raise rule. The menu is trapped in the toolbar's stacking context, so on a touch device the keyboard accessory bar (z 51) and the visible CJK input (z 52) covered its last rows even when scrolled to the end. This follows the rule the case settings popover and case combobox already use. Reword the rule's comment: it claimed dvh follows the keyboard and that the vh line is a fallback, and neither is true (a declaration carrying var() is never dropped at parse time). The new text has no braces and no max-height text, which the gate test's rule() slicer depends on. Pin the fixes in the gate test (the --app-height and safe-area terms, overflow-x: hidden, the toolbar raise) and retitle the cap test so it no longer names dvh as the mechanism. The test now strips CSS comments before reading the rule, since the rule's own comment names overflow-y: auto and touch-action: pan-y and would otherwise keep those assertions green after the declarations were deleted (checked by deleting them: the test now fails). Drop .changeset/run-menu-scroll.md: it repeated the false dvh claim, and the release writes one consolidated changeset at COM. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"aicodeman": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
The Run dropdown scrolls. It opens upward from the toolbar with no height limit, so with every CLI, the custom endpoint entries ("Claude Code (llama.cpp)" and so on), Terminal and the saved URLs it grew taller than the room above the toolbar: its top ran off-screen and the entries up there could not be reached, worst on a phone. It is now capped to the space between the header and the toolbar (`dvh`, with a `vh` fallback) and scrolls inside that, keeping the scroll from chaining to the page, and its sub-lists (history, saved URLs) are no longer squashed to nothing as the menu fills.
|
|
||||||
@@ -5355,7 +5355,8 @@ html[data-skin="og"] .welcome-primary:hover {
|
|||||||
z-index inside the toolbar. When the popover is open, raise the toolbar
|
z-index inside the toolbar. When the popover is open, raise the toolbar
|
||||||
above the CJK input (z-index 52) so the popover is interactable. */
|
above the CJK input (z-index 52) so the popover is interactable. */
|
||||||
.toolbar:has(.case-settings-popover:not(.hidden)),
|
.toolbar:has(.case-settings-popover:not(.hidden)),
|
||||||
.toolbar:has(.case-combobox-list:not(.hidden)) {
|
.toolbar:has(.case-combobox-list:not(.hidden)),
|
||||||
|
.toolbar:has(.run-mode-menu.active) {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5651,11 +5652,19 @@ html[data-skin="og"] .welcome-primary:hover {
|
|||||||
endpoint entries, Terminal and the saved URLs it can be taller than the room above the
|
endpoint entries, Terminal and the saved URLs it can be taller than the room above the
|
||||||
toolbar: its top then sits off-screen with nothing to scroll, and the entries up there
|
toolbar: its top then sits off-screen with nothing to scroll, and the entries up there
|
||||||
cannot be reached (worst on a phone). Cap it to the space between the header and the
|
cannot be reached (worst on a phone). Cap it to the space between the header and the
|
||||||
toolbar and scroll inside it. `dvh` so an on-screen keyboard or a collapsing browser bar
|
toolbar and scroll inside it. The cap follows --app-height, not `dvh`: the viewport meta
|
||||||
shrinks the cap with it; the `vh` line is the fallback for browsers without it. */
|
has no interactive-widget, so `dvh` does not shrink for an on-screen keyboard, while
|
||||||
max-height: calc(100vh - var(--header-height) - var(--toolbar-height) - 16px);
|
MobileDetection always sets --app-height to the visual viewport height and KeyboardHandler
|
||||||
max-height: calc(100dvh - var(--header-height) - var(--toolbar-height) - 16px);
|
keeps it there while the keyboard is up. Both safe areas come off too, because the phone
|
||||||
|
header and toolbar grow by them in the iPhone home-screen app. The second line is the one
|
||||||
|
that applies (a declaration carrying var() is never dropped at parse time, so the first
|
||||||
|
line is not a working fallback for it; it stays only as the plain-viewport form). */
|
||||||
|
max-height: calc(100vh - var(--header-height) - var(--safe-area-top) - var(--toolbar-height) - var(--safe-area-bottom) - 16px);
|
||||||
|
max-height: calc(var(--app-height, 100dvh) - var(--header-height) - var(--safe-area-top) - var(--toolbar-height) - var(--safe-area-bottom) - 16px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
/* overflow-y: auto computes overflow-x to auto as well, so a long nowrap custom-endpoint
|
||||||
|
label would show a horizontal scrollbar that touch-action: pan-y cannot pan. */
|
||||||
|
overflow-x: hidden;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
touch-action: pan-y;
|
touch-action: pan-y;
|
||||||
|
|||||||
@@ -8,7 +8,12 @@
|
|||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
const css = readFileSync(new URL('../src/web/public/styles.css', import.meta.url), 'utf8');
|
// Comments stripped: the rule's own comment names `overflow-y: auto` and `touch-action: pan-y`,
|
||||||
|
// which would otherwise satisfy the assertions below after the declarations were deleted.
|
||||||
|
const css = readFileSync(new URL('../src/web/public/styles.css', import.meta.url), 'utf8').replace(
|
||||||
|
/\/\*[\s\S]*?\*\//g,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
/** The declaration block of the FIRST rule whose selector is exactly `selector`. */
|
/** The declaration block of the FIRST rule whose selector is exactly `selector`. */
|
||||||
function rule(selector: string): string {
|
function rule(selector: string): string {
|
||||||
@@ -27,17 +32,32 @@ describe('Run dropdown scrolls when it is taller than the room above the toolbar
|
|||||||
expect(menu).toMatch(/touch-action:\s*pan-y/);
|
expect(menu).toMatch(/touch-action:\s*pan-y/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('is capped to the space between the header and the toolbar, in dvh with a vh fallback', () => {
|
it('is capped to the space between the header and the toolbar, following --app-height and the safe areas', () => {
|
||||||
const caps = [...menu.matchAll(/max-height:\s*([^;]+);/g)].map((m) => m[1]);
|
const caps = [...menu.matchAll(/max-height:\s*([^;]+);/g)].map((m) => m[1]);
|
||||||
expect(caps).toHaveLength(2);
|
expect(caps).toHaveLength(2);
|
||||||
expect(caps[0]).toContain('100vh');
|
expect(caps[0]).toContain('100vh');
|
||||||
expect(caps[1]).toContain('100dvh');
|
expect(caps[1]).toContain('100dvh');
|
||||||
|
// dvh does not shrink for the on-screen keyboard; --app-height (the visual viewport) does.
|
||||||
|
expect(caps[1]).toContain('var(--app-height');
|
||||||
for (const cap of caps) {
|
for (const cap of caps) {
|
||||||
expect(cap).toContain('var(--header-height)');
|
expect(cap).toContain('var(--header-height)');
|
||||||
expect(cap).toContain('var(--toolbar-height)');
|
expect(cap).toContain('var(--toolbar-height)');
|
||||||
|
// The phone header and toolbar grow by the safe areas in the iPhone home-screen app.
|
||||||
|
expect(cap).toContain('var(--safe-area-top)');
|
||||||
|
expect(cap).toContain('var(--safe-area-bottom)');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('never scrolls sideways (a long custom-endpoint label must not add a horizontal scrollbar)', () => {
|
||||||
|
expect(menu).toMatch(/overflow-x:\s*hidden/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('raises the toolbar while the menu is open, so its last rows clear the keyboard bar and the CJK input', () => {
|
||||||
|
// The menu is trapped in the toolbar's stacking context (backdrop-filter), so only the
|
||||||
|
// toolbar's own z-index can lift it over the accessory bar (z 51) and #cjkInput (z 52).
|
||||||
|
expect(css).toMatch(/\.toolbar:has\(\.run-mode-menu\.active\)[\s\S]*?\{\s*z-index:\s*100;/);
|
||||||
|
});
|
||||||
|
|
||||||
it('does not let scrolling children (history, saved URLs) be squashed to nothing', () => {
|
it('does not let scrolling children (history, saved URLs) be squashed to nothing', () => {
|
||||||
expect(rule('.run-mode-menu > *')).toMatch(/flex-shrink:\s*0/);
|
expect(rule('.run-mode-menu > *')).toMatch(/flex-shrink:\s*0/);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user