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

The waves pages mark no repository tab as Files, and an empty waves page says what a wave is and how to cut the first #6161

Merged opened this

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

Merged

scout/forge:scout/6149-the-waves-pages-mark-no-repository-tab-a → main 2 commits fork

  • Merged. scout/6149-the-waves-pages-mark-no-repository-tab-a was merged into main as b6fcebc.

Review

Sign in to review this change.
3 files changed +39 −6
src/web/chrome.ts +3 −1
@@ -119,7 +119,9 @@ export type RepoTab =
119119 | "branches"
120120 | "app"
121121 | "runs"
122 | "settings";
122 | "settings"
123 /** A page that belongs to no tab (waves): nothing is marked current. */
124 | "none";
123125
124126 export interface RepoHeaderOptions {
125127 /** Rendered at the right of the title line (fork, clone, watch). */
src/web/pages/waves.ts +18 −5
@@ -6,7 +6,7 @@
66 * it does for any other page of a private repository.
77 */
88
9 import { canRead, getRepo } from "../../core/repos.ts";
9 import { canAdmin, canRead, getRepo } from "../../core/repos.ts";
1010 import { notFound } from "../../http/respond.ts";
1111 import { holds, stamp, xml } from "../feeds.ts";
1212 import { notePhase, takeSessionTime, timePhase, timePhaseAsync } from "../../core/route_timing.ts";
@@ -19,9 +19,12 @@ import { NO_WAVES } from "../../story/words.ts";
1919 import { loadRepo, notFoundPage, repoBase, scopeHeader } from "../chrome.ts";
2020 import { escapeHtml, html } from "../html.ts";
2121 import { page } from "../layout.ts";
22 import { empty, listCard, row } from "../ui.ts";
22 import { btn, empty, listCard, row } from "../ui.ts";
2323 import { localScope } from "../scope.ts";
2424
25 const WHAT_A_WAVE =
26 "A wave is a release note in waves/<version>.md that names its highlights and the pulls merged since the last one.";
27
2528 /** A wave's row title: `<version> · <headline> · N pulls`, N being the pulls this viewer can see. */
2629 export function waveLine(w: Wave, visible: number): string {
2730 return `${w.version} · ${w.headline} · ${visible} ${visible === 1 ? "pull" : "pulls"}`;
@@ -58,10 +61,20 @@ export async function wavesPage(ctx: Ctx): Promise<Response> {
5861 }),
5962 );
6063
61 const body = html`${scopeHeader(ctx, scope, "code", { compact: true })}
64 const body = html`${scopeHeader(ctx, scope, "none", { compact: true })}
6265 <div class="mt">
6366 ${entries.length === 0
64 ? empty({ title: "No waves yet", note: NO_WAVES })
67 ? empty({
68 title: "No waves yet",
69 note: `${WHAT_A_WAVE} ${NO_WAVES}`,
70 ...(canAdmin(ctx.app.db, repo, ctx.auth.user)
71 ? {
72 action: html`<form method="post" action="${repoBase(repo)}/settings/wave">
73 ${btn("Cut a wave", { variant: "primary" })}
74 </form>`,
75 }
76 : {}),
77 })
6578 : listCard(rows, { head: html`<h2>Waves</h2>` })}
6679 </div>`;
6780
@@ -189,7 +202,7 @@ export async function wavePage(ctx: Ctx): Promise<Response> {
189202 : html`<h1>${entry.file}</h1>
190203 <p class="muted">Not a wave: ${entry.reason}</p>`;
191204
192 const body = html`${scopeHeader(ctx, scope, "code", { compact: true })}
205 const body = html`${scopeHeader(ctx, scope, "none", { compact: true })}
193206 <div class="mt">${content}<p>${back}</p></div>`;
194207
195208 const response = page({
test/waves-page.test.ts +18
@@ -149,6 +149,24 @@ describe("waves page", () => {
149149 expect(await res.text()).toContain("No waves yet");
150150 });
151151
152 test("the empty state says what a wave is; an admin gets Cut a wave, a reader does not", async () => {
153 const admin = await (await get("/alice/bare/waves")).text();
154 expect(admin).toContain("A wave is a release note");
155 expect(admin).toContain('action="/alice/bare/settings/wave"');
156 expect(admin).toContain("Cut a wave");
157 const reader = await (await get("/alice/bare/waves", false)).text();
158 expect(reader).toContain("A wave is a release note");
159 expect(reader).not.toContain("Cut a wave");
160 expect(reader).not.toContain("/settings/wave");
161 });
162
163 test("the waves pages mark no tab as current", async () => {
164 for (const path of ["/alice/pub/waves", "/alice/bare/waves", "/alice/pub/waves/0.10.0"]) {
165 const body = await (await get(path)).text();
166 expect(body).not.toContain('aria-current="page"');
167 }
168 });
169
152170 test("a private repository is refused to an outsider as any other page is", async () => {
153171 const waves = await get("/alice/priv/waves", false);
154172 const other = await get("/alice/priv/branches", false);

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs