Public: anyone can read this; the crew can change it.

The bets page's phone look is checked on a preview: linked in the pull, no sideways scroll, no proposed bet card taller than one screen #9337

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/9321-the-bets-page-s-phone-look-is-checked-on → main 5 commits fork 2 threads · 2 unresolved

  • Merged. mason/9321-the-bets-page-s-phone-look-is-checked-on was merged into main as c449ef3.

Review

Sign in to review this change.
3 files changed +73 −0
docs/design/areas/bets.md +7
@@ -26,3 +26,10 @@ scale in DESIGN §4; there are no spacing custom properties to use). It is an
2626 attribute because the area adds no component class. The preview seed holds a
2727 third proposed bet with a long name and an unbroken address as a journey, so
2828 the worst case is on `/preview-admin/sample/bets`.
29
30 ### Bounded body (#9321)
31
32 `[data-bet-card]` also carries `max-height: 80vh` with `overflow-x: hidden; overflow-y: auto`, so
33 no proposed bet card is taller than one phone screen (844 px) even with every
34 disclosure open; the overflow scrolls inside the card, never the page. No new
35 class. `test/bets-card-fit.test.ts` fails if the bound is removed.
src/web/style/content.ts +1
@@ -148,6 +148,7 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
148148 [data-apps], [data-bet-card] { overflow-wrap: anywhere; }
149149 [data-bet-card] h2, [data-bet-card] ul, [data-bet-card] p { margin-block: 8px; }
150150 [data-bet-card] ul { padding-inline-start: 16px; }
151 [data-bet-card] { max-height: 80vh; overflow-x: hidden; overflow-y: auto; }
outdated

overflow-y:auto forces overflow-x to auto too, so the card can scroll sideways inside itself and clip focus outlines. Add overflow-x:hidden (overflow-wrap:anywhere already stops long words), and check the phone look.

Bound and overflow-x:hidden are fine. Note that hidden can clip focus rings on the card edge; give the card a little inner padding if the measured phone look shows a clipped ring.

151152 [data-bet-actions] { align-items: flex-start; }
152153
153154 .bar > .device-code { flex: 1 1 auto; }
test/bets-card-fit.test.ts +65
@@ -16,6 +16,10 @@ import { utilities as base } from "../src/web/style/base.ts";
1616 import { components } from "../src/web/style/components.ts";
1717 import { content } from "../src/web/style/content.ts";
1818 import { SHOWN_JOURNEYS } from "../src/web/pages/bets.ts";
19 import { createSession } from "../src/core/sessions.ts";
20 import { findChromium } from "../src/looks/capture.ts";
21 import { previewAdmin } from "../src/spawn/preview-seed.ts";
22 import { chromiumRuns, evaluateLoaded } from "./helpers/chrome.ts";
1923
2024 const KEY = "bets-card-fit-key";
2125 let dir: string;
@@ -86,6 +90,14 @@ describe("the owner's bets page, a proposed bet card at rest", () => {
8690 for (const r of rules) expect(r).not.toMatch(/(?<![-\w])width\s*:\s*\d/);
8791 });
8892
93 test("a proposed bet's body is bounded: a max-height under one 844 px screen, scrolling inside (#9321)", () => {
94 const rule = content.match(/\[data-bet-card\]\s*\{[^}]*max-height:\s*(\d+)(vh|px)[^}]*\}/);
95 expect(rule).not.toBeNull();
96 const px = rule![2] === "vh" ? (Number(rule![1]) / 100) * 844 : Number(rule![1]);
97 expect(px).toBeLessThanOrEqual(844);
98 expect(rule![0]).toMatch(/overflow-y:\s*auto/);
99 });
100
89101 test("long words wrap", () => {
90102 expect(content).toMatch(/\[data-bet-card\][^{]*\{[^}]*overflow-wrap:\s*anywhere/);
91103 });
@@ -112,3 +124,56 @@ test("a forge preview takes the owner's bets page at desk and phone (#9275)", ()
112124 expect(PREVIEW_BETS_PAGE).toBe("/preview-admin/sample/bets");
113125 for (const kind of ["desktop", "phone"] as const) expect(looks).toContainEqual({ page: PREVIEW_BETS_PAGE, kind });
114126 });
127
128 /**
129 * The phone look measured, not read from the CSS (#9321): the owner's bets
130 * page in a real Chromium at 390x844 does not scroll sideways, and every
131 * proposed card is shorter than the screen with its actions in view at rest.
132 * Skipped where no Chromium starts; the forge's check runs it and logs the
133 * numbers.
134 */
135 const found = findChromium();
136 const chromium = found !== undefined && chromiumRuns(found) ? found : undefined;
137 if (chromium === undefined) console.log("SKIPPING: no chromium that starts on this machine, so the bets phone look is not measured");
138
139 describe.skipIf(chromium === undefined)("the owner's bets page, measured at phone and desk", () => {
140 const measure = `(() => {
141 const vw = document.documentElement.clientWidth;
142 const cards = [...document.querySelectorAll("[data-bet-card]")].map((c) => {
143 const box = c.parentElement.getBoundingClientRect();
144 const body = c.getBoundingClientRect();
145 const actions = c.querySelector("[data-bet-actions]").getBoundingClientRect();
146 return { height: Math.round(box.height), innerScroll: c.scrollHeight - c.clientHeight, sideways: c.scrollWidth - c.clientWidth, actionsInView: actions.bottom <= body.bottom + 1 };
147 });
148 // Within the cards: the layout's own chrome (off-canvas, clipped) is web-phone.test.ts's to hold.
149 const overflowing = [...document.querySelectorAll("[data-bet-card], [data-bet-card] *")].filter((el) => el.getBoundingClientRect().right > vw + 1).slice(0, 8).map((el) => el.tagName.toLowerCase());
150 return JSON.stringify({ scrollWidth: document.documentElement.scrollWidth, viewportWidth: vw, cards, overflowing });
151 })()`;
152 type Measured = { scrollWidth: number; viewportWidth: number; overflowing: string[]; cards: { height: number; innerScroll: number; sideways: number; actionsInView: boolean }[] };
153 const look = async (width: number, height: number): Promise<Measured> => {
154 const admin = previewAdmin(app)!;
155 const cookies = [{ name: "forge_session", value: createSession(app.db, admin.id).token }];
156 const m = JSON.parse(await evaluateLoaded(chromium!, `${servers.url}${PREVIEW_BETS_PAGE}`, { width, height }, cookies, measure)) as Measured;
157 console.log(`bets look ${width}x${height}: ${JSON.stringify(m)}`);
158 return m;
159 };
160
161 test("phone, 390x844: no sideways scroll, every proposed card under one screen with its actions in view", async () => {
162 const m = await look(390, 844);
163 expect(m.cards.length).toBe(2);
164 expect(m.overflowing).toEqual([]);
165 expect(m.scrollWidth).toBeLessThanOrEqual(m.viewportWidth);
166 for (const c of m.cards) {
167 expect(c.height).toBeLessThanOrEqual(844);
168 expect(c.sideways).toBe(0);
169 expect(c.innerScroll).toBe(0);
170 expect(c.actionsInView).toBe(true);
171 }
172 }, 30_000);
173
174 test("desk, 1280x800: no sideways scroll", async () => {
175 const m = await look(1280, 800);
176 expect(m.scrollWidth).toBeLessThanOrEqual(m.viewportWidth);
177 for (const c of m.cards) expect(c.sideways).toBe(0);
178 }, 30_000);
179 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs