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

Each proposed bet card fits one screen at 390 px with no horizontal scroll #9191

Merged opened this

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

Merged

mason/forge:mason/8970-each-proposed-bet-card-fits-one-screen-a → main 4 commits fork 3 threads · 3 unresolved

  • Merged. mason/8970-each-proposed-bet-card-fits-one-screen-a was merged into main as 6565f39.

Review

Sign in to review this change.
6 files changed +66 −6
docs/design/areas/bets.md +17
@@ -9,3 +9,20 @@ proposed appetite, and a second `<details>` ("Change the share") holding the
99 share field. When `mayBack` refuses (5 running, or the week would be passed)
1010 the button is `disabled` and the reason is a `muted` note beside it. No new
1111 class; the card is stacked blocks, so at 390 px it needs no horizontal scroll.
12
13 ### One screen at 390 px (#8970)
14
15 At rest the card shows the name, at most three journeys (the rest behind a
16 "N more" disclosure), the issue count, the button and the cut button; the
17 share field stays folded. The card carries `data-bet-card` (an attribute, not
18 a class) which wraps long words anywhere and tightens block margins, so a long
19 name or journey neither scrolls sideways nor pushes the button off the screen.
20 Desk and phone looks are attached to the pull from its preview.
21
22 **Departure, named.** `data-bet-card` is not a variant of `card()`: the card
23 itself is the stock one. The attribute only scopes three rules inside it
24 (wrap anywhere, `8px` block margins, `16px` list indent, all from the 4px
25 scale in DESIGN §4; there are no spacing custom properties to use). It is an
26 attribute because the area adds no component class. The preview seed holds a
27 third proposed bet with a long name and an unbroken address as a journey, so
28 the worst case is on `/preview-admin/sample/bets`.
src/spawn/preview-seed.ts +8 −2
@@ -323,13 +323,13 @@ async function seedWave(app: App, admin: User, repo: Repo, pullTitle: string): P
323323
324324 /** One bet backed at 20% of the week and one proposed, over the open issues. */
325325 function seedBets(app: App, admin: User, repo: Repo, open: readonly Issue[]): Result<void, string> {
326 const bet = (name: string, from: number) =>
326 const bet = (name: string, from: number, journeys = "Someone can see it") =>
327327 insertBet(
328328 app.db,
329329 {
330330 repoId: repo.id,
331331 name,
332 journeys: "Someone can see it",
332 journeys,
333333 appetite: 20,
334334 leadId: null,
335335 issueIds: open.slice(from, from + 2).map((i) => i.id),
@@ -338,6 +338,12 @@ function seedBets(app: App, admin: User, repo: Repo, open: readonly Issue[]): Re
338338 );
339339 const backed = bet("Show the week", 2);
340340 bet("Price the list", 4);
341 // The worst case for the phone: a long name, an unbroken word, more journeys than fit at rest (#8970).
342 bet(
343 "Make every long and winding bet name wrap instead of pushing the page sideways",
344 0,
345 ["Someone opens https://example.com/a/very/long/unbroken/address/that/has/no/spaces/at/all/anywhere", "Second journey", "Third journey", "Fourth journey", "Fifth journey"].join("\n"),
346 );
341347 const result = backBet(app, admin, repo, backed.id, 20);
342348 return result.ok ? ok() : err(`backing a bet: ${result.error.message}`);
343349 }
src/web/pages/bets.ts +8 −3
@@ -71,6 +71,9 @@ function oneButton(repo: Repo, b: Bet, verb: string, label: string, variant: "da
7171 return html`<form method="post" action="${post(repo, b, verb)}">${btn(label, { variant })}</form>`;
7272 }
7373
74 /** Journeys shown at rest; the rest sit behind a disclosure so the card stays one screen tall. */
75 const SHOWN_JOURNEYS = 3;
76
7477 /**
7578 * A proposed bet as one card for the owner: name, journeys, its issues as a
7679 * count that opens the list, and one button that backs it at the proposed
@@ -87,8 +90,10 @@ function proposedCard(ctx: Ctx, repo: Repo, b: Bet, backed: readonly Bet[]): Htm
8790 <ul>${scopes.map((s) => html`<li><a href="${repoBase(repo)}/issues/${s.number}">#${s.number} ${s.title}</a></li>`)}</ul>
8891 </details>`;
8992 return card(
90 html`<h2><a href="${betHref(repo, b.id)}">${b.name}</a></h2>
91 ${journeys.length === 0 ? null : html`<ul>${journeys.map((j) => html`<li>${j}</li>`)}</ul>`}
93 html`<div data-bet-card><h2><a href="${betHref(repo, b.id)}">${b.name}</a></h2>
94 ${journeys.length === 0 ? null : html`<ul>${journeys.slice(0, SHOWN_JOURNEYS).map((j) => html`<li>${j}</li>`)}</ul>`}
95 ${journeys.length <= SHOWN_JOURNEYS ? null : html`<details><summary>${journeys.length - SHOWN_JOURNEYS} more ${journeys.length - SHOWN_JOURNEYS === 1 ? "journey" : "journeys"}</summary>
outdated

Summary reads "2 more" with no noun. Use "2 more journeys".

96 <ul>${journeys.slice(SHOWN_JOURNEYS).map((j) => html`<li>${j}</li>`)}</ul></details>`}
9297 ${issues}
9398 <form method="post" action="${post(repo, b, "back")}">
9499 <input type="hidden" name="share" value="${String(b.appetite)}">
@@ -96,7 +101,7 @@ function proposedCard(ctx: Ctx, repo: Repo, b: Bet, backed: readonly Bet[]): Htm
96101 ${why === null ? null : html`<p class="muted" role="note">${why[0]!.toUpperCase() + why.slice(1)}.</p>`}
97102 </form>
98103 <details class="mt"><summary>Change the share</summary>${backForm(repo, b, "Back it")}</details>
99 <div class="mt">${oneButton(repo, b, "cut", "Cut it", "danger")}</div>`,
104 <div class="mt">${oneButton(repo, b, "cut", "Cut it", "danger")}</div></div>`,
100105 );
101106 }
102107
src/web/style/content.ts +3 −1
@@ -145,7 +145,9 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
145145 font-variant-numeric: tabular-nums; letter-spacing: 0.14em;
146146 text-align: center; user-select: all; word-break: break-all;
147147 }
148 [data-apps] { overflow-wrap: anywhere; }
148 [data-apps], [data-bet-card] { overflow-wrap: anywhere; }
149 [data-bet-card] h2, [data-bet-card] ul, [data-bet-card] p { margin-block: 8px; }
outdated

Literal 6px and 18px: use the spacing tokens, or say why not.

150 [data-bet-card] ul { padding-inline-start: 16px; }
149151
150152 .bar > .device-code { flex: 1 1 auto; }
151153
test/bets.test.ts +29
@@ -579,3 +579,32 @@ describe("a proposed bet is one card backed with one tap", () => {
579579 expect(shown).toContain("5 bets already run");
580580 });
581581 });
582
583 describe("a proposed bet card stays one screen tall (#8970)", () => {
584 const card = async (journeys: string) => {
585 app.db.query("DELETE FROM bet WHERE repo_id = ?1").run(repo.id);
586 const cookie = await signIn("alice");
587 const b = fresh2("Many");
588 app.db.query("UPDATE bet SET journeys = ?1 WHERE id = ?2").run(journeys, b.id);
589 return await (await fetch(`${servers.url}/alice/notes/bets`, { headers: { cookie } })).text();
590 };
591 test("three journeys at rest; the rest sit inside a details that names them", async () => {
592 const out = await card("j1\nj2\nj3\nj4\nj5");
593 expect(out).toContain("data-bet-card");
outdated

Assert j4 and j5 sit inside the details and not at rest; add the 3-or-fewer case with no disclosure.

594 const [rest, folded] = out.split(/<details>\s*<summary>2 more journeys<\/summary>/);
595 expect(folded).toBeDefined();
596 for (const j of ["j1", "j2", "j3"]) expect(rest).toContain(`<li>${j}</li>`);
597 for (const j of ["j4", "j5"]) {
598 expect(rest).not.toContain(`<li>${j}</li>`);
599 expect(folded!.split("</details>")[0]).toContain(`<li>${j}</li>`);
600 }
601 });
602 test("three or fewer journeys have no disclosure", async () => {
603 const out = await card("j1\nj2\nj3");
604 expect(out).toContain("<li>j3</li>");
605 expect(out).not.toMatch(/more journeys?</);
606 });
607 test("one folded journey reads in the singular", async () => {
608 expect(await card("j1\nj2\nj3\nj4")).toContain("1 more journey</summary>");
609 });
610 });
test/looks-owner.test.ts +1
@@ -134,6 +134,7 @@ describe("the preview seed", () => {
134134 expect(bets).toEqual([
135135 ["Show the week", "backed", 20],
136136 ["Price the list", "proposed", 20],
137 ["Make every long and winding bet name wrap instead of pushing the page sideways", "proposed", 20],
137138 ]);
138139
139140 // The sample account that prices the week has no grant, so no run or

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs