Public: anyone can read this; the crew can change it.
The search, map, flow and system pages each carry a first-view attention budget that test/attention.test.ts holds #10691
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/10687-the-search-map-flow-and-system-pages-eac → main 3 commits fork 1 thread · 1 unresolved
3 files
changed +100
−4
docs/design/attention-budget.json +64
| @@ -118,5 +118,69 @@ | ||
| 118 | 118 | "blocks": 32, |
| 119 | 119 | "colours": 1 |
| 120 | 120 | } |
| 121 | }, | |
| 122 | "search": { | |
| 123 | "390": { | |
| 124 | "words": 42, | |
| 125 | "blocks": 3, | |
| 126 | "colours": 1 | |
| 127 | }, | |
| 128 | "1280": { | |
| 129 | "words": 42, | |
| 130 | "blocks": 3, | |
| 131 | "colours": 1 | |
| 132 | }, | |
| 133 | "note": "Bare /search, signed in as an admin." | |
| 134 | }, | |
| 135 | "search results": { | |
| 136 | "390": { | |
| 137 | "words": 58, | |
| 138 | "blocks": 5, | |
| 139 | "colours": 1 | |
| 140 | }, | |
| 141 | "1280": { | |
| 142 | "words": 58, | |
| 143 | "blocks": 5, | |
| 144 | "colours": 1 | |
| 145 | }, | |
| 146 | "note": "/search?q=forge, signed in as an admin." | |
| 147 | }, | |
| 148 | "map": { | |
| 149 | "390": { | |
| 150 | "words": 68, | |
| 151 | "blocks": 6, | |
| 152 | "colours": 6 | |
| 153 | }, | |
| 154 | "1280": { | |
| 155 | "words": 68, | |
| 156 | "blocks": 6, | |
| 157 | "colours": 6 | |
| 158 | } | |
| 159 | }, | |
| 160 | "flow": { | |
| 161 | "390": { | |
| 162 | "words": 102, | |
| 163 | "blocks": 9, | |
| 164 | "colours": 1 | |
| 165 | }, | |
| 166 | "1280": { | |
| 167 | "words": 102, | |
| 168 | "blocks": 9, | |
| 169 | "colours": 1 | |
| 170 | }, | |
| 171 | "note": "Measured on the test forge's empty state (no samples seeded, nothing waited for)." | |
| 172 | }, | |
| 173 | "system": { | |
| 174 | "390": { | |
| 175 | "words": 212, | |
| 176 | "blocks": 65, | |
| 177 | "colours": 0 | |
| 178 | }, | |
| 179 | "1280": { | |
| 180 | "words": 212, | |
| 181 | "blocks": 65, | |
| 182 | "colours": 0 | |
| 183 | }, | |
| 184 | "note": "Measured on the test forge's empty state with the machine reading replaced by an empty one and the two cell-cap sentences fixed (the live page's counts depend on the host's cores, disks, processes and cgroups); nothing seeded or waited for." | |
| 121 | 185 | } |
| 122 | 186 | } |
docs/features/attention.md +8 −1
| @@ -30,7 +30,14 @@ reader sees on first view at 390 px and 1280 px: **words**, **text blocks** and | ||
| 30 | 30 | | pulls list | 24 / 6 / 1 | 24 / 6 / 1 | |
| 31 | 31 | | runs | 38 / 8 / 1 | 38 / 8 / 1 | |
| 32 | 32 | | run | 34 / 11 / 3 | 34 / 11 / 3 | |
| 33 | | settings | 137 / 32 / 1 | 137 / 32 / 1 | | |
| 33 | | settings | 140 / 32 / 1 | 140 / 32 / 1 | | |
| 34 | | search | 42 / 3 / 1 | 42 / 3 / 1 | | |
| 35 | | search results (`/search?q=forge`) | 58 / 5 / 1 | 58 / 5 / 1 | | |
| 36 | | map | 68 / 6 / 6 | 68 / 6 / 6 | | |
| 37 | | flow | 102 / 9 / 1 | 102 / 9 / 1 | | |
| 38 | | system | 212 / 65 / 0 | 212 / 65 / 0 | | |
| 39 | ||
| 40 | `/flow` and `/system` are sampled pages; their entries are the empty state the test forge has (nothing seeded, nothing waited for), and say so in the entry's `note`. `/system` draws the host it runs on, so the test hands it an empty machine reading (`useLive`) and fixed cap sentences; a live `/system` is larger. | |
| 34 | 41 | |
| 35 | 42 | ## App page, calmed (#10299) |
| 36 | 43 |
test/attention.test.ts +28 −3
| @@ -19,12 +19,16 @@ import { createRun } from "../src/core/runs.ts"; | ||
| 19 | 19 | import { serve, type Servers } from "../src/http/server.ts"; |
| 20 | 20 | import { firstView, WIDTHS, type Attention } from "../src/measure/attention.ts"; |
| 21 | 21 | import { proveAll } from "./helpers/verified.ts"; |
| 22 | import { useLive, type Live } from "../src/system/live.ts"; | |
| 23 | import type { SystemView } from "../src/system/view.ts"; | |
| 22 | 24 | import { quietLogs } from "./helpers/globals.ts"; |
| 23 | 25 | |
| 24 | 26 | quietLogs(); |
| 25 | 27 | |
| 26 | 28 | const BUDGET = "docs/design/attention-budget.json"; |
| 27 | 29 | type Budget = Record<string, Record<string, Attention>>; |
| 30 | // An entry may carry a "note" string beside its widths; it is read by people only. | |
| 31 | const widthsOf = (entry: object): string[] => Object.keys(entry).filter((k) => k !== "note"); | |
| 28 | 32 | |
| 29 | 33 | let root = ""; |
| 30 | 34 | let app: App; |
| @@ -33,6 +37,19 @@ let cookie = ""; | ||
| 33 | 37 | let pages: Record<string, string> = {}; |
| 34 | 38 | const seen: Budget = {}; |
| 35 | 39 | |
| 40 | // /system draws the machine it runs on (cores, disks, processes, whether the | |
| 41 | // kernel caps cells), so the same code counts differently on every host. The | |
| 42 | // test hands it an empty reading and one fixed sentence for each cap line. | |
| 43 | const emptyView: SystemView = { | |
| 44 | at: "", seconds: 0, cpu: { busyPct: null, cores: [], history: [] }, temps: [], hottestC: null, load: null, | |
| 45 | memory: null, disks: [], net: [], io: [], processes: 0, groups: [], duplicates: [], | |
| 46 | }; | |
| 47 | const fixedLive: Live = { | |
| 48 | read: () => emptyView, subscribe: () => () => {}, running: () => false, publish: () => {}, | |
| 49 | }; | |
| 50 | const fixedCaps = (html: string): string => | |
| 51 | html.replace(/<p class="small" id="(cell-caps|cell-pids)"[^>]*>[\s\S]*?<\/p>/g, '<p class="small" id="$1">Each cell is capped.</p>'); | |
| 52 | ||
| 36 | 53 | beforeAll(async () => { |
| 37 | 54 | root = mkdtempSync(join(tmpdir(), "forge-attention-")); |
| 38 | 55 | app = createApp({ dataDir: root, port: 0, forgeRepo: "alice/forge" }); |
| @@ -59,6 +76,7 @@ beforeAll(async () => { | ||
| 59 | 76 | const pull = await openPull(app, u.value, repo, { title: "Add x", baseBranch: "main", headBranch: "feature/x" }); |
| 60 | 77 | if (!pull.ok) throw new Error(String(pull.error)); |
| 61 | 78 | const run = createRun(app.db, { repoId: repo.id, kind: "check", env: "main", commitOid: "0".repeat(40), reason: "push" }); |
| 79 | useLive(fixedLive); | |
| 62 | 80 | servers = await serve(app); |
| 63 | 81 | const signin = await fetch(`${servers.url}/signin`, { |
| 64 | 82 | method: "POST", headers: { origin: servers.url }, |
| @@ -70,11 +88,13 @@ beforeAll(async () => { | ||
| 70 | 88 | home: "/", repository: r, app: `${r}/app`, pull: `${r}/pulls/${pull.value.number}`, |
| 71 | 89 | issue: `${r}/issues/${issue.value.number}`, "issues list": `${r}/issues`, "pulls list": `${r}/pulls`, |
| 72 | 90 | runs: `${r}/runs`, run: `${r}/runs/${run.id}`, settings: "/settings", |
| 91 | search: "/search", "search results": "/search?q=forge", map: "/map", flow: "/flow", system: "/system", | |
| 73 | 92 | }; |
| 74 | 93 | for (const [name, path] of Object.entries(paths)) { |
| 75 | 94 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie }, redirect: "follow" }); |
| 76 | 95 | if (res.status !== 200) throw new Error(`${name} ${path} answered ${res.status}`); |
| 77 | pages[name] = await res.text(); | |
| 96 | const text = await res.text(); | |
| 97 | pages[name] = name === "system" ? fixedCaps(text) : text; | |
| 78 | 98 | } |
| 79 | 99 | }); |
| 80 | 100 | afterAll(() => { |
| @@ -88,7 +108,12 @@ test("each page stays inside its first-view budget", () => { | ||
| 88 | 108 | seen[name] = {}; |
| 89 | 109 | for (const w of WIDTHS) seen[name]![String(w)] = firstView(html, w); |
| 90 | 110 | } |
| 91 | if (process.env.FORGE_ATTENTION_WRITE === "1") writeFileSync(BUDGET, `${JSON.stringify(seen, null, 2)}\n`); | |
| 111 | if (process.env.FORGE_ATTENTION_WRITE === "1") { | |
| 112 | const old = JSON.parse(readFileSync(BUDGET, "utf8")) as Record<string, { note?: string }>; | |
| 113 | const out: Record<string, unknown> = {}; | |
| 114 | for (const [n, v] of Object.entries(seen)) out[n] = old[n]?.note ? { ...v, note: old[n]!.note } : v; | |
| 115 | writeFileSync(BUDGET, `${JSON.stringify(out, null, 2)}\n`); | |
| 116 | } | |
| 92 | 117 | const budget = JSON.parse(readFileSync(BUDGET, "utf8")) as Budget; |
| 93 | 118 | const rows: string[] = ["page".padEnd(13) + "width words blocks colours"]; |
| 94 | 119 | const over: string[] = []; |
| @@ -110,5 +135,5 @@ test("the budget file names every page at both widths and no other", () => { | ||
| 110 | 135 | // A budget for every page at both widths, and no stale page in the file. |
| 111 | 136 | const budget = JSON.parse(readFileSync(BUDGET, "utf8")) as Budget; |
| 112 | 137 | expect(Object.keys(budget).sort()).toEqual(Object.keys(pages).sort()); |
| 113 | for (const name of Object.keys(budget)) expect(Object.keys(budget[name]!)).toEqual(WIDTHS.map(String)); | |
| 138 | for (const name of Object.keys(budget)) expect(widthsOf(budget[name]!)).toEqual(WIDTHS.map(String)); | |
| 114 | 139 | }); |
Add a blank line after this paragraph; without it the next
## App page, calmedheading is glued to it.