Public: anyone can read this; the crew can change it.
A repository's two-column layout uses base.ts grid utilities, and .repo-layout leaves the stylesheet #4783
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/4592-a-repository-s-two-column-layout-uses-ba → main 7 commits fork 2 threads · 2 unresolved
4 files
changed +60
−9
docs/DESIGN.md −1
| @@ -441,7 +441,6 @@ the same line (#4888). | ||
| 441 | 441 | - `.policy-line` |
| 442 | 442 | - `.repo-actions` |
| 443 | 443 | - `.repo-desc` |
| 444 | - `.repo-layout` | |
| 445 | 444 | - `.repo-title` |
| 446 | 445 | - `.slash` |
| 447 | 446 | - `.sub` |
src/web/pages/repo.ts +1 −1
| @@ -734,7 +734,7 @@ export async function renderCode( | ||
| 734 | 734 | <div class="mt">${readme}</div>`; |
| 735 | 735 | return o.rail === undefined |
| 736 | 736 | ? main |
| 737 | : html`<div class="repo-layout"> | |
| 737 | : html`<div class="split"> | |
| 738 | 738 | <div>${main}</div> |
| 739 | 739 | <aside aria-label="About">${o.rail}</aside> |
| 740 | 740 | </div>`; |
src/web/style/content.ts +1 −7
| @@ -157,12 +157,6 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); } | ||
| 157 | 157 | |
| 158 | 158 | .repo-actions { display: flex; align-items: center; gap: 6px; margin-left: auto; } |
| 159 | 159 | |
| 160 | .repo-layout { display: grid; gap: 16px; margin-top: 16px; align-items: start; } | |
| 161 | @media (min-width: 960px) { | |
| 162 | .repo-layout { grid-template-columns: minmax(0, 1fr) 280px; gap: 24px; } | |
| 163 | .repo-layout > aside { position: sticky; top: calc(var(--topbar-h) + 16px); } | |
| 164 | } | |
| 165 | ||
| 166 | 160 | .tok { white-space: nowrap; } |
| 167 | 161 | |
| 168 | 162 | .filters { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; } |
| @@ -250,7 +244,7 @@ form.inline { display: inline; margin-left: auto; } | ||
| 250 | 244 | *content*, so a column with a long unbreakable line pushes the page wider |
| 251 | 245 | than the phone rather than letting its own ellipsis do the work. A zero |
| 252 | 246 | min-width is what makes truncation inside a grid actually truncate. */ |
| 253 | .split > *, .repo-layout > * { min-width: 0; } | |
| 247 | .split > * { min-width: 0; } | |
| 254 | 248 | |
| 255 | 249 | /* The generic two-column page: content, then a sidebar that sticks. */ |
| 256 | 250 | .split { display: grid; gap: 16px; margin-top: 16px; align-items: start; } |
test/repo-layout.test.ts added +58
| @@ -0,0 +1,58 @@ | ||
| 1 | /** | |
| 2 | * A repository's code page lays main and its About rail with `.split`, the | |
| 3 | * one two-column page; `.repo-layout` is gone (#4592). | |
| 4 | */ | |
| 5 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 6 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 7 | import { tmpdir } from "node:os"; | |
| 8 | import { join } from "node:path"; | |
| 9 | import { createRepo } from "../src/actions/repos.ts"; | |
| 10 | import { createApp, type App } from "../src/app.ts"; | |
| 11 | import { createUser } from "../src/core/users.ts"; | |
| 12 | import { repoDir } from "../src/git/repos.ts"; | |
| 13 | import { commitFiles } from "../src/git/write.ts"; | |
| 14 | import { serve, type Servers } from "../src/http/server.ts"; | |
| 15 | import { quietLogs } from "./helpers/globals.ts"; | |
| 16 | ||
| 17 | quietLogs(); | |
| 18 | ||
| 19 | let dataDir: string; | |
| 20 | let app: App; | |
| 21 | let servers: Servers; | |
| 22 | ||
| 23 | beforeAll(async () => { | |
| 24 | dataDir = await mkdtemp(join(tmpdir(), "forge-repo-layout-")); | |
| 25 | app = createApp({ dataDir, port: 0, host: "forge.test" }); | |
| 26 | servers = await serve(app); | |
| 27 | const made = await createUser(app.db, { | |
| 28 | origin: "seed", | |
| 29 | trusted: true, | |
| 30 | username: "olive", | |
| 31 | email: "[email protected]", | |
| 32 | password: "a-good-passphrase", | |
| 33 | adminIfFirst: true, | |
| 34 | }); | |
| 35 | if (!made.ok) throw new Error(made.error); | |
| 36 | const r = await createRepo(app, made.value, { name: "yard", visibility: "public" }); | |
| 37 | if (!r.ok) throw new Error(String(r.error)); | |
| 38 | const c = await commitFiles(repoDir(app.reposRoot, "olive", "yard"), { | |
| 39 | branch: "main", | |
| 40 | message: "first", | |
| 41 | files: [{ path: "README.md", content: "# yard\n" }], | |
| 42 | author: { name: "olive", email: "[email protected]" }, | |
| 43 | }); | |
| 44 | if (!c.ok) throw new Error("no commit"); | |
| 45 | }); | |
| 46 | ||
| 47 | afterAll(async () => { | |
| 48 | await servers.stop(); | |
| 49 | await rm(dataDir, { recursive: true, force: true }); | |
| 50 | }); | |
| 51 | ||
| 52 | test("the code page wraps main and the About aside in .split, not .repo-layout", async () => { | |
| 53 | const r = await fetch(`${servers.url}/olive/yard`); | |
| 54 | expect(r.status).toBe(200); | |
| 55 | const html = await r.text(); | |
| 56 | expect(html).not.toContain("repo-layout"); | |
| 57 | expect(html).toMatch(/<div class="split">\s*<div>[\s\S]*<\/div>\s*<aside aria-label="About">/); | |
| 58 | }); |
new above
offline
Fine.
.splitcollapses to one column below 960px, so a phone gets main first and then About, in DOM order, which suits a keyboard and a screen reader. Check the sticky aside once at 960–1100px.