Public: anyone can read this; the crew can change it.
A pull's looks sit beside the same page's look from main: before and after #3104
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/2737-a-pull-s-looks-sit-beside-the-same-page → main 4 commits fork 2 threads · 2 unresolved
4 files
changed +81
−6
docs/DESIGN.md +1 −1
| @@ -385,7 +385,7 @@ gone, so this list only shrinks. | ||
| 385 | 385 | - **`style/content.ts`, prose (markdown)** — table cell alignment, built by `markdown.ts` as `ta-${align}`: `.ta-c` `.ta-r` |
| 386 | 386 | - **`style/content.ts`, diff viewer** — the diff table: its gutters, line kinds, file head and inline review comments: `.add` `.del` `.dh` `.diff` `.diff-body` `.diff-head` `.diff-note` `.diff-path` `.diff-stat` `.diff-stub` `.diff-summary` `.diff-table` `.dl` `.dl-review` `.dn` `.dn-comment` `.nonl` `.rc-body` `.rc-form` `.rc-head` `.rc-outdated` `.rc-resolved-line` `.wd` |
| 387 | 387 | - **`style/content.ts`, review verdicts** — the body under a verdict: `.review-body` |
| 388 | - **`style/content.ts`, apps, runs, and the crew** — run pages, their logs, checks and look screenshots: `.app-url` `.check-progress` `.check-steps` `.ln-forge` `.ln-pulse` `.ln-say` `.ln-tool` `.ln-you` `.log-tail-row` `.look` `.look-attached` `.look-attached-phone` `.look-attachments` `.look-card` `.look-desktop` `.look-now` `.look-of` `.look-pair` `.look-phone` `.look-thumb` `.pinned-failures` `.pinned-slot` `.run-actions` `.run-jump` `.run-live` `.run-steer` `.run-summary` `.step-state` `.tests-bar` `.tests-words` `.txt` `.who` | |
| 388 | - **`style/content.ts`, apps, runs, and the crew** — run pages, their logs, checks and look screenshots (a pull's look sets the before side, main's, sits first, then the pull's after side: a `.look-compare` of two `.look-side`): `.app-url` `.check-progress` `.check-steps` `.ln-forge` `.ln-pulse` `.ln-say` `.ln-tool` `.ln-you` `.log-tail-row` `.look` `.look-attached` `.look-attached-phone` `.look-attachments` `.look-card` `.look-compare` `.look-desktop` `.look-now` `.look-of` `.look-pair` `.look-phone` `.look-side` `.look-thumb` `.pinned-failures` `.pinned-slot` `.run-actions` `.run-jump` `.run-live` `.run-steer` `.run-summary` `.step-state` `.tests-bar` `.tests-words` `.txt` `.who` | |
| 389 | 389 | - **`style/content.ts`, the code browser: explorer beside content** — the explorer column's own parts: `.code-main` `.code-split` `.ex-empty` `.ex-find` `.ex-head` `.ex-list` `.ex-name` `.ex-ref` `.ex-twist` `.explorer-toggle` |
| 390 | 390 | - **`style/content.ts`, the finder** — the finder's footnote: `.finder-note` |
| 391 | 391 | - **`style/content.ts`, the editor** — the editor's parts beyond the §5 row: `.editor-mirror` `.editor-path` `.editor-preview` `.editor-stack` `.editor-tab` `.mirrored` `.ml` |
src/web/pages/looks.ts +41 −5
| @@ -170,7 +170,16 @@ export function lookCard( | ||
| 170 | 170 | a === DEFAULT_PAGE ? -1 : b === DEFAULT_PAGE ? 1 : a.localeCompare(b), |
| 171 | 171 | ); |
| 172 | 172 | const labelled = pages.length > 1; |
| 173 | const picture = (look: Look, kind: LookKind, page: string): Html => { | |
| 173 | // What main looks like, to set each page beside: only what this viewer may fetch. | |
| 174 | const mainLooks = looksForEnv(ctx.app.db, record.id, "production").filter( | |
| 175 | (l) => !isAttached(l) && mayFetchLook(ctx, repo, record, l), | |
| 176 | ); | |
| 177 | const picture = ( | |
| 178 | look: Look, | |
| 179 | kind: LookKind, | |
| 180 | page: string, | |
| 181 | of = `#${pull.number}`, | |
| 182 | ): Html => { | |
| 174 | 183 | const url = lookUrl(base, look); |
| 175 | 184 | const scheme = look.theme === "dark" ? " in the dark" : ""; |
| 176 | 185 | return html`<a class="look look-${kind}" href="${url}"> |
| @@ -180,7 +189,7 @@ export function lookCard( | ||
| 180 | 189 | height="${LOOK_SIZES[kind].height}" |
| 181 | 190 | alt="${repo.name}${page === DEFAULT_PAGE |
| 182 | 191 | ? "" |
| 183 | : ` ${page}`} #${pull.number} on a ${kind}${scheme}" | |
| 192 | : ` ${page}`} ${of} on a ${kind}${scheme}" | |
| 184 | 193 | loading="lazy" |
| 185 | 194 | /> |
| 186 | 195 | </a>`; |
| @@ -206,6 +215,14 @@ export function lookCard( | ||
| 206 | 215 | </p> |
| 207 | 216 | </div>`; |
| 208 | 217 | const moved = shown.commitOid !== headOid; |
| 218 | const mainOfPage = mainLooks.filter((l) => l.page === page); | |
| 219 | const beforePhone = pictured(mainOfPage, "phone"); | |
| 220 | const beforeDesktop = pictured(mainOfPage, "desktop"); | |
| 221 | // Beside the same page at the same width: a width main lacks is left out. | |
| 222 | const beforeShown = [ | |
| 223 | phone === undefined ? undefined : beforePhone, | |
| 224 | desktop === undefined ? undefined : beforeDesktop, | |
| 225 | ].filter((l): l is Look => l !== undefined); | |
| 209 | 226 | // Only the dark pictures of the commit the caption names: after a push |
| 210 | 227 | // the light pair is retaken first, and an old head's dark pair must not |
| 211 | 228 | // sit under the new head's words. |
| @@ -216,9 +233,28 @@ export function lookCard( | ||
| 216 | 233 | const darkDesktop = pictured(darkOfPage, "desktop"); |
| 217 | 234 | return html`<div class="look-group"> |
| 218 | 235 | ${label} |
| 219 | <div class="look-pair"> | |
| 220 | ${when(phone, (l) => picture(l, "phone", page))} | |
| 221 | ${when(desktop, (l) => picture(l, "desktop", page))} | |
| 236 | <div class="look-compare"> | |
| 237 | <div class="look-side look-before"> | |
| 238 | <p class="muted small">Before · main</p> | |
| 239 | ${beforeShown.length === 0 | |
| 240 | ? html`<p class="muted small look-nobefore"> | |
| 241 | Main has no look of ${page} yet. | |
| 242 | </p>` | |
| 243 | : html`<div class="look-pair"> | |
| 244 | ${beforeShown.map((l) => picture(l, l.kind, page, "main"))} | |
| 245 | </div> | |
| 246 | <span class="muted small look-of" | |
| 247 | >at ${sha(beforeShown[0]!.commitOid, `${base}/commit/${beforeShown[0]!.commitOid}`)} · | |
| 248 | ${time(beforeShown[0]!.createdAt)}</span | |
| 249 | >`} | |
| 250 | </div> | |
| 251 | <div class="look-side look-after"> | |
| 252 | <p class="muted small">After · #${pull.number}</p> | |
| 253 | <div class="look-pair"> | |
| 254 | ${when(phone, (l) => picture(l, "phone", page))} | |
| 255 | ${when(desktop, (l) => picture(l, "desktop", page))} | |
| 256 | </div> | |
| 257 | </div> | |
| 222 | 258 | </div> |
| 223 | 259 | ${when(darkPhone ?? darkDesktop, () => |
| 224 | 260 | html`<details class="quiet-details"> |
src/web/style/content.ts +4
| @@ -873,6 +873,10 @@ form.inline { display: inline; margin-left: auto; } | ||
| 873 | 873 | .look-pair { display: flex; gap: 14px; align-items: flex-start; } |
| 874 | 874 | .look { display: block; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; background: var(--muted); } |
| 875 | 875 | .look img { display: block; width: 100%; height: auto; } |
| 876 | .look-compare { display: flex; gap: 20px; align-items: flex-start; flex-wrap: wrap; } | |
Document these classes in docs/DESIGN.md. In .look-side, let .look-pair wrap so a phone and a desktop picture are not squeezed side by side at 360px. |
||
| 877 | .look-side { flex: 1 1 320px; min-width: 0; } | |
| 878 | .look-side > p { margin: 0 0 6px; } | |
| 879 | .look-side .look-pair { flex-wrap: wrap; } | |
| 876 | 880 | .look-attachments { flex-wrap: wrap; margin-top: 10px; } |
| 877 | 881 | .look-attached { margin: 0; display: flex; flex-direction: column; gap: 6px; flex: 1 1 200px; min-width: 0; max-width: 1280px; } |
| 878 | 882 | .look-attached-phone { flex: 0 0 200px; } |
test/looks.test.ts +35
| @@ -1100,6 +1100,41 @@ describe("the look", () => { | ||
| 1100 | 1100 | } |
| 1101 | 1101 | }); |
| 1102 | 1102 | |
| 1103 | test("a pull's look of a page sits beside main's newest look of it, or says main has none", async () => { | |
| 1104 | const env = previewEnv(pull.number); | |
| 1105 | const oid = "a".repeat(40); | |
| 1106 | const mainOid = "b".repeat(40); | |
| 1107 | const put = (e: string, commitOid: string, page: string) => | |
| 1108 | recordLook(app.db, { | |
| 1109 | appId: record.id, | |
| 1110 | env: e, | |
| 1111 | commitOid, | |
| 1112 | page, | |
| 1113 | kind: "phone", | |
| 1114 | path: `looks/${record.id}/${e}/${commitOid}-x-phone.png`, | |
| 1115 | }); | |
| 1116 | put(env, oid, "/map"); | |
| 1117 | put(env, oid, "/solo"); | |
| 1118 | put("production", mainOid, "/map"); | |
| 1119 | const text = await ( | |
| 1120 | await web(`/alice/hello/pulls/${pull.number}`, aliceCookie) | |
| 1121 | ).text(); | |
| 1122 | const group = (page: string) => | |
| 1123 | text.split(`<p class="muted small look-page">${page}</p>`)[1]!.split("look-group")[0]!; | |
| 1124 | const map = group("/map"); | |
| 1125 | expect(map).toContain(`/looks/production/${mainOid}/map-phone.png?v=`); | |
| 1126 | expect(map).toContain(`/looks/${env}/${oid}/map-phone.png?v=`); | |
| 1127 | expect(map.indexOf("Before")).toBeLessThan(map.indexOf("After")); | |
| 1128 | expect(map.indexOf(`/looks/production/`)).toBeLessThan( | |
| 1129 | map.indexOf(`/looks/${env}/`), | |
| 1130 | ); | |
| 1131 | expect(map).not.toContain("has no look"); | |
| 1132 | expect(map).toContain(`/commit/${mainOid}`); | |
| 1133 | const solo = group("/solo"); | |
| 1134 | expect(solo).toContain("Main has no look of /solo yet."); | |
| 1135 | expect(solo).not.toContain("/looks/production/"); | |
| 1136 | }); | |
| 1137 | ||
| 1103 | 1138 | test("memory pressure is what the container truly holds, never its page cache", async () => { |
| 1104 | 1139 | const { cgroupMemoryPressure, memoryPressure } = |
| 1105 | 1140 | await import("../src/looks/service.ts"); |
new above
offline
Add the commit and time of main's picture here, as look-of does for the after side.