Public: anyone can read this; the crew can change it.
The home figures share one formatting module, wrap on a phone, and say the live version once #2910
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/2881-the-home-figures-share-one-formatting-mo → main 1 commit fork 1 thread · 1 unresolved
2 files
changed +12
−3
src/web/studio/feed_style.ts +3 −3
| @@ -24,12 +24,12 @@ export const feedCss = /* css */ ` | ||
| 24 | 24 | .composer textarea { width: 100%; min-height: 64px; resize: vertical; } |
| 25 | 25 | .composer-bar { display: flex; align-items: center; gap: 8px; min-width: 0; } |
| 26 | 26 | .composer-bar select { min-width: 0; max-width: 60%; } |
| 27 | /* The figures row: three cards at a fixed height, so first paint is final. */ | |
| 27 | /* The figures row: three cards at a fixed height, so first paint is final; the note wraps into two reserved lines. */ | |
| 28 | 28 | .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin-bottom: 12px; } |
| 29 | .kpi { display: grid; grid-template-rows: 20px 32px 20px 24px; align-content: start; gap: 2px; padding: 12px 14px; color: inherit; text-decoration: none; } | |
| 29 | .kpi { display: grid; grid-template-rows: 20px 32px 40px 24px; align-content: start; gap: 2px; padding: 12px 14px; color: inherit; text-decoration: none; } | |
| 30 | 30 | .kpi:hover { border-color: var(--border-strong); } |
| 31 | 31 | .kpi-value { font-size: 16px; line-height: 32px; font-weight: 600; } |
| 32 | .kpi-note { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | |
| 32 | .kpi-note { line-height: 20px; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow-wrap: anywhere; } | |
| 33 | 33 | .kpi-spark { display: block; color: var(--muted-fg); max-width: 100%; } |
| 34 | 34 | /* Your apps: a narrow column on a desk, a tab away on a phone. */ |
| 35 | 35 | .home-tabs { display: none; margin-bottom: 12px; } |
test/home-kpi-row.test.ts +9
| @@ -2,6 +2,7 @@ | ||
| 2 | 2 | import { describe, expect, test } from "bun:test"; |
| 3 | 3 | import { checkTime, kpiLines, sparkText } from "../src/flow/kpi_words.ts"; |
| 4 | 4 | import { running } from "../src/kernel/version.ts"; |
| 5 | import { feedCss } from "../src/web/studio/feed_style.ts"; | |
| 5 | 6 | import { kpiRowFor, liveNow, sparkline } from "../src/web/studio/kpi_row.ts"; |
| 6 | 7 | |
| 7 | 8 | const hours = (n: number) => Array.from({ length: 24 }, (_, i) => (i === 3 ? n : 0)); |
| @@ -70,6 +71,14 @@ describe("home kpi row", () => { | ||
| 70 | 71 | expect(out).toContain('title="No check finished in 24 h"'); |
| 71 | 72 | }); |
| 72 | 73 | |
| 74 | test("the note wraps into two reserved lines instead of cutting off on a phone", () => { | |
| 75 | const rule = (sel: string) => new RegExp(`\\${sel} \\{([^}]*)\\}`).exec(feedCss)![1]!; | |
| 76 | expect(rule(".kpi-note")).not.toContain("nowrap"); | |
| 77 | expect(rule(".kpi-note")).toContain("line-height: 20px"); | |
| 78 | expect(rule(".kpi-note")).toContain("line-clamp: 2"); | |
| 79 | expect(rule(".kpi")).toContain("grid-template-rows: 20px 32px 40px 24px"); | |
| 80 | }); | |
| 81 | ||
| 73 | 82 | test("check time reads as a person would", () => { |
| 74 | 83 | expect(checkTime(null)).toBe("—"); |
| 75 | 84 | expect(checkTime(45_000)).toBe("45s"); |
new above
offline
This is a deliberate departure from DESIGN's "one line, ellipsis, title" rule. The two-line clamp still ends in an ellipsis and keeps the full
title, so it stays honest. Record the exception in docs/DESIGN.md.