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

The repository page speaks the home page's language, with looks before and after #9621

Merged opened this

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

Merged

scout/forge:scout/9585-the-repository-page-speaks-the-home-page → main 4 commits fork 3 threads · 3 unresolved

  • Merged. scout/9585-the-repository-page-speaks-the-home-page was merged into main as 47df140.

Review

Sign in to review this change.
5 files changed +91 −90
docs/design/areas/code.md +2
@@ -4,3 +4,5 @@
44 - `.pdf-view`, `.pdf-links`, `.pdf-frame`, `.pdf-fallback` — a PDF on its blob page (`render/pdf.ts`): one card holding the browser's viewer in a full-width frame 80vh tall (480px at least), under an always-visible Open · Download line (the frame is 60vh on a phone so a thumb can scroll past it), with a padded link line inside for a browser that cannot draw it.
55
66 `.ex-more` also closes a windowed directory page and the side Files column: the "Show all N entries" link to `?all=1`.
7
8 - `.rail-facts`, `.rail-fact` — the facts in a repository's About rail (`aboutCard` in `src/web/pages/repo.ts`): a `dl` of label-over-value pairs, 12px apart, no rules between them, values wrapping anywhere. The rail is narrower than `.status-row`'s 168px label column, so it stacks instead.
src/web/pages/repo.ts +78 −84
@@ -785,6 +785,15 @@ export function codeBar(
785785 })}`;
786786 }
787787
788 /**
789 * One fact of the About column: a `dl` pair, the label over its value. The
790 * rail is narrower than `.status-row`'s 168px label column, so it stacks and
outdated

Principles 3 and 6: this row becomes .status-row, which draws a hairline under every fact and a 168px label column at 640px+, inside a narrow rail. Make a rail-sized variant (label over value, space not lines) or show in the looks that it holds at 1440 and 390.

791 * separates the facts with space, not a rule per row.
792 */
793 function fact(label: string, value: Html): Html {
794 return html`<div class="rail-fact"><dt>${label}</dt><dd>${value}</dd></div>`;
795 }
796
788797 /**
789798 * The About rail: what this repo is, in one glance, plus how to clone it.
790799 *
@@ -857,82 +866,70 @@ function aboutCard(
857866 </p>
858867 </div>`,
859868 );
860 return html`${card(
861 html`${when(
862 repo.description !== "",
863 () => html`<p class="mb">${repo.description}</p>`,
864 )}
865 <ul class="list setting-list">
866 ${row({ title: "Visibility", trail: html`${repo.visibility}` })}
867 ${row({
868 title: "Default",
869 trail: html`<span class="mono">${repo.defaultBranch}</span>`,
870 })}
871 ${row({ title: "Protection", trail: html`${repo.protectDefault ? "on" : "off"}` })}
872 ${row({
873 title: "Branches",
874 trail: html`<span class="num">${refs.branches.length}</span>`,
875 })}
876 ${when(
877 refs.tags.length > 0,
878 () =>
879 row({
880 title: "Tags",
881 trail: html`<span class="num">${refs.tags.length}</span>`,
882 }),
883 )}
884 ${when(
885 parent,
886 (p) =>
887 // The standing and the one thing to do about it live in the
888 // Following card above the code; the rail only names the parent.
889 row({
890 title: "Fork of",
891 trail: html`<a href="/${p.ownerName}/${p.name}"
892 >${p.ownerName}/${p.name}</a
893 >`,
894 }),
895 )}
896 ${when(
897 upstream,
898 (up) =>
899 row({
900 title: "Fork of",
901 trail:
902 upstreamHref === undefined
903 ? html`<span class="mono">${up}</span>`
904 : html`<a href="${upstreamHref}">${up}</a>`,
905 }),
906 )}
907 </ul>
908 ${followingLine(ctx, repo)}
909 ${whereItLives}
910 ${when(
911 repo.visibility === "public" && repo.origin === null,
912 () =>
913 html`<p class="mt small muted">
914 ${icon("lock")} Found a security problem?
915 <a href="${repoBase(repo)}/issues/new?sealed=1"
916 >Report it sealed</a
917 >: only the people who keep this repository and its crew see it
918 until the fix is released.
919 </p>`,
920 )}
921 <p class="mt small muted">Clone over HTTPS</p>
922 ${copyField("clone-https", cloneUrl, "HTTPS clone address", "Copy clone URL")}
923 ${when(
924 sshUrl,
925 (url) =>
926 html`<p class="mt small muted">Clone over SSH</p>
927 ${copyField(
928 "clone-ssh",
929 url,
930 "SSH clone address",
931 "Copy SSH clone URL",
932 )}`,
933 )}`,
934 { head: html`<h2>About</h2>` },
935 )}`;
869 return html`<section class="settings-block">
870 <h2>About</h2>
871 ${when(
872 repo.description !== "",
873 () => html`<p class="mb">${repo.description}</p>`,
874 )}
875 <dl class="rail-facts">
876 ${fact("Visibility", html`${repo.visibility}`)}
877 ${fact("Default", html`<span class="mono">${repo.defaultBranch}</span>`)}
878 ${fact("Protection", html`${repo.protectDefault ? "on" : "off"}`)}
879 ${fact("Branches", html`<span class="num">${refs.branches.length}</span>`)}
880 ${when(
881 refs.tags.length > 0,
882 () =>
883 fact("Tags", html`<span class="num">${refs.tags.length}</span>`),
884 )}
885 ${when(
886 parent,
887 (p) =>
888 // The standing and the one thing to do about it live in the
889 // Following card above the code; the rail only names the parent.
890 fact(
891 "Fork of",
892 html`<a href="/${p.ownerName}/${p.name}">${p.ownerName}/${p.name}</a>`,
893 ),
894 )}
895 ${when(
896 upstream,
897 (up) =>
898 fact(
899 "Fork of",
900 upstreamHref === undefined
901 ? html`<span class="mono">${up}</span>`
902 : html`<a href="${upstreamHref}">${up}</a>`,
903 ),
904 )}
905 </dl>
906 ${followingLine(ctx, repo)}
907 ${whereItLives}
908 ${when(
909 repo.visibility === "public" && repo.origin === null,
910 () =>
911 html`<p class="mt small muted">
912 ${icon("lock")} Found a security problem?
913 <a href="${repoBase(repo)}/issues/new?sealed=1"
914 >Report it sealed</a
915 >: only the people who keep this repository and its crew see it
916 until the fix is released.
917 </p>`,
918 )}
919 <p class="mt small muted">Clone over HTTPS</p>
920 ${copyField("clone-https", cloneUrl, "HTTPS clone address", "Copy clone URL")}
921 ${when(
922 sshUrl,
923 (url) =>
924 html`<p class="mt small muted">Clone over SSH</p>
925 ${copyField(
926 "clone-ssh",
927 url,
928 "SSH clone address",
929 "Copy SSH clone URL",
930 )}`,
931 )}
932 </section>`;
936933 }
937934
938935 // ---- pages ----
@@ -1316,18 +1313,15 @@ async function codeHome(ctx: Ctx, repo: Repo): Promise<Response> {
13161313 title: "An empty repository, ready for its first push",
13171314 note: "Point a local repository at it and push — the first branch you push becomes the code you see here.",
13181315 })}
1319 ${card(
1320 copyField(
1316 <section class="settings-block">
1317 <h2>Get started</h2>
1318 ${copyField(
13211319 "clone-https",
13221320 `git remote add origin ${cloneUrl} && git push -u origin ${repo.defaultBranch}`,
13231321 "Get started command",
13241322 "Copy the get started command",
1325 ),
1326 {
1327 head: html`${icon("command")}
1328 <h2>Get started</h2>`,
1329 },
1330 )}
1323 )}
1324 </section>
13311325 </div>`;
13321326 const emptyResponse = page({
13331327 ctx,
src/web/style/content.ts +3
@@ -414,6 +414,9 @@ form.inline { display: inline; margin-left: auto; }
414414
415415 /* A rendered Markdown blob wears the same frame its source does: one card,
416416 one path header, and the switch between the two views in it. */
417 .rail-facts { margin: 12px 0 0; display: grid; gap: 12px; font-size: 13px; }

Non-blocking: the three .rail-* rules landed between the "A rendered Markdown blob..." comment and the .blob-view rules it describes. Move them below .blob-view, with their own one-line comment.

418 .rail-fact > dt { color: var(--muted-fg); font-size: 12.5px; }
419 .rail-fact > dd { margin: 2px 0 0; min-width: 0; overflow-wrap: anywhere; }
417420 .blob-view { margin-left: 8px; }
418421 .blob-view .seg { font-size: 12.5px; }
419422 /* An audio or video file, played inline (render/player.ts). The video is as
test/repo-about-card.test.ts +6 −3
@@ -53,14 +53,17 @@ afterAll(async () => {
5353 await rm(dataDir, { recursive: true, force: true });
5454 });
5555
56 test("the About column is a card with §5 rows and no .about", async () => {
56 test("the About column is a settings block with stacked `dl` facts and no .about", async () => {
5757 const r = await fetch(`${servers.url}/olive/yard`);
5858 expect(r.status).toBe(200);
5959 const html = await r.text();
6060 expect(html).not.toMatch(/class="[^"]*\babout\b/);
61 expect(html).not.toContain("<dl");
6261 const rail = html.match(/<aside[^>]*>[\s\S]*?<\/aside>/)?.[0] ?? "";
63 expect(rail).toContain('class="card"');
62 expect(rail).toContain('class="settings-block"');
63 expect(rail).toContain('<dl class="rail-facts">');
outdated

The <dl ban above this line is what keeps the facts as ul.list, against DESIGN.md principle 7. Update that expectation together with the markup.

64 expect(rail).toContain('<div class="rail-fact"><dt>Visibility</dt>');
65 expect(rail).not.toContain("status-row");
66 expect(rail).not.toContain('class="card"');
6467 expect(rail).toContain("<h2>About</h2>");
6568 expect(rail).toContain("a small yard");
6669 for (const word of ["Visibility", "Default", "Protection", "Branches"])
test/web-origins.test.ts +2 −3
@@ -515,10 +515,9 @@ describe("origins on the web", () => {
515515 expect(html).toContain("Sync now");
516516 expect(html).toContain("Pushes here go through to");
517517 expect(html).not.toMatch(/class="about"/);
518 // The facts render as §5 rows inside a labelled complementary landmark.
518 // The facts render as `dl` rows inside a labelled complementary landmark.
519519 expect(html).toContain('<aside aria-label="About">');
520 expect(html).toMatch(/row-title"[^>]*>Visibility</);
521 expect(html).not.toContain("<dl");
520 expect(html).toMatch(/<dt>Visibility<\/dt>/);
522521 });
523522
524523 test("an imported issue shows the origin's author, not the local one", async () => {

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs