mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 15:09:42 +02:00
fix(mobile): hide attachments-history button on phones + regression guards
The COD-39 attachment-history header button was visible on the cramped phone header. Hide it on phones alongside the settings gear and lifecycle log (the mobile header is intentionally minimal — those controls live in the toolbar). One-line addition to the existing @media (max-width: 430px) display:none block in mobile.css. This is the second time a header control leaked onto mobile (the plan-usage chip was the first), so add two regression guards: - test/mobile-header-buttons-policy.test.ts — a pure static analysis of index.html + mobile.css (no browser), so it runs in the normal CI sweep (the test/mobile/** Playwright suite is EXCLUDED from CI and never gated this). It enumerates every default-visible header button and fails when one has no phone-visibility decision — either a mobile.css hide rule or an explicit MOBILE_VISIBLE_ALLOWLIST entry. A new header button now forces that decision. Verified it fails on the pre-fix state and passes after. - test/mobile/header-buttons.test.ts — real-browser E2E in the mobile suite: asserts the attachments/settings/lifecycle buttons are hidden on an emulated iPhone 14 Pro and the attachments button is visible on a desktop-class tablet. tsc + lint + prettier + both new tests green. Only CSS + tests changed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
// Port 3208 - Mobile header button visibility (real-browser E2E).
|
||||
//
|
||||
// Companion to the CI static guard (test/mobile-header-buttons-policy.test.ts).
|
||||
// This one renders the real app in an emulated phone vs. a desktop-class tablet
|
||||
// and asserts the ACTUAL computed visibility — catching CSS/layout regressions
|
||||
// the static parser can't see. Regression history: the COD-39 attachments button
|
||||
// (and earlier the plan-usage chip) shipped visible on the cramped phone header.
|
||||
import { describe, it, beforeAll, afterAll } from 'vitest';
|
||||
import type { WebServer } from '../../src/web/server.js';
|
||||
import { createTestServer, stopTestServer } from './helpers/server.js';
|
||||
import { createDevicePage, closeAllBrowsers } from './helpers/browser.js';
|
||||
import { assertHidden, assertVisible } from './helpers/assertions.js';
|
||||
import { REPRESENTATIVE_DEVICES } from './devices.js';
|
||||
import { PORTS, WAIT } from './helpers/constants.js';
|
||||
|
||||
const PORT = PORTS.HEADER_BUTTONS;
|
||||
const BASE_URL = `http://localhost:${PORT}`;
|
||||
|
||||
// Secondary header buttons that must NOT clutter the minimal phone header
|
||||
// (settings/case controls live in the mobile toolbar instead).
|
||||
const PHONE_HIDDEN = [
|
||||
'#attachmentsHistoryBtn', // COD-39 — the reported regression
|
||||
'.btn-icon-header.btn-settings',
|
||||
'.btn-icon-header.btn-lifecycle-log',
|
||||
];
|
||||
|
||||
describe('Mobile header button visibility (E2E)', () => {
|
||||
let server: WebServer;
|
||||
|
||||
beforeAll(async () => {
|
||||
server = await createTestServer(PORT);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await closeAllBrowsers();
|
||||
await stopTestServer(server);
|
||||
});
|
||||
|
||||
it('hides secondary header buttons on a standard phone (iPhone 14 Pro, 393px)', async () => {
|
||||
const { page } = await createDevicePage(REPRESENTATIVE_DEVICES['standard-phone'], BASE_URL);
|
||||
await page.waitForTimeout(WAIT.PAGE_SETTLE);
|
||||
for (const sel of PHONE_HIDDEN) {
|
||||
await assertHidden(page, sel);
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps the attachments button visible on a desktop-class tablet', async () => {
|
||||
// assertHidden also passes when an element is ABSENT, so prove the selector is
|
||||
// real by asserting the same button IS visible where the phone rule doesn't apply.
|
||||
const { page } = await createDevicePage(REPRESENTATIVE_DEVICES['large-tablet'], BASE_URL);
|
||||
await page.waitForTimeout(WAIT.PAGE_SETTLE);
|
||||
await assertVisible(page, '#attachmentsHistoryBtn');
|
||||
});
|
||||
});
|
||||
@@ -8,6 +8,7 @@ export const PORTS = {
|
||||
DEVICE_MATRIX: 3205,
|
||||
VISUAL_REGRESSION: 3206,
|
||||
ACCESSIBILITY: 3207,
|
||||
HEADER_BUTTONS: 3208,
|
||||
} as const;
|
||||
|
||||
// CSS Selectors
|
||||
@@ -64,8 +65,8 @@ export const BREAKPOINTS = {
|
||||
|
||||
// Keyboard constants (match app.js KeyboardHandler)
|
||||
export const KEYBOARD = {
|
||||
SHOW_THRESHOLD: 150, // heightDiff > 150px triggers show
|
||||
HIDE_THRESHOLD: 100, // heightDiff < 100px triggers hide
|
||||
SHOW_THRESHOLD: 150, // heightDiff > 150px triggers show
|
||||
HIDE_THRESHOLD: 100, // heightDiff < 100px triggers hide
|
||||
TYPICAL_IOS_HEIGHT: 336,
|
||||
FOCUSIN_DELAY: 400,
|
||||
ANIMATION_DELAY: 150,
|
||||
@@ -87,7 +88,7 @@ export const SWIPE = {
|
||||
export const SUBAGENT = {
|
||||
MOBILE_CARD_HEIGHT: 110,
|
||||
MOBILE_CARD_GAP: 4,
|
||||
MOBILE_CARD_STRIDE: 114, // height + gap
|
||||
MOBILE_CARD_STRIDE: 114, // height + gap
|
||||
TOOLBAR_OFFSET: 40,
|
||||
DEFAULT_HEADER_HEIGHT: 36,
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user