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

A preview's /__forge/ door links the pull it previews, with its title #6052

Merged opened this

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

Merged

scout/forge:scout/6046-a-preview-s-forge-door-links-the-pull-it → main 3 commits fork 1 thread · 1 unresolved

  • Merged. scout/6046-a-preview-s-forge-door-links-the-pull-it was merged into main as 0977f9d.

Review

Sign in to review this change.
2 files changed +50 −2
src/apps/door.ts +11 −1
@@ -47,6 +47,16 @@ export function doorWhere(where: PillWhere): string {
4747 const link = (href: string, text: string): string =>
4848 `<li><a href="${escapeAttr(href)}">${escapeAttr(text)}</a></li>`;
4949
50 /** The where line; a reader of the pull gets its number linked and its title. */
51 function whereLine(where: PillWhere, forgeUrl: string): string {
52 const words = escapeAttr(doorWhere(where));
53 if (where.pull === undefined || where.repo === undefined || where.title === undefined)
54 return words;
55 const href = new URL(`/${where.repo}/pulls/${where.pull}`, forgeUrl).href;
56 const n = `pull #${where.pull}`;
57 return `${words.replace(n, `<a href="${escapeAttr(href)}">${n}</a>`)}: ${escapeAttr(where.title)}`;

words.replace(n, ...) relies on doorWhere containing the literal "pull #N". It is correct today, but it is coupled to the wording in doorWhere. Building the link from where.pull directly would stop a later wording change from silently dropping the link.

58 }
59
5060 /** `2026-10-25 14:03 UTC`, or the raw text when it is not a date. */
5161 function when(at: string): string {
5262 const t = Date.parse(at);
@@ -109,7 +119,7 @@ export function doorPage(facts: DoorFacts, head: boolean): Response {
109119 : `<p>Terminal:</p><pre><code>${escapeAttr(facts.ssh)}</code></pre>`;
110120 const html = `<!doctype html>
111121 <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex"><title>${escapeAttr(where.app)}, ${escapeAttr(doorWhere(where))}</title><style>${STYLE}</style></head>
112 <body><main><h1>${escapeAttr(where.app)}</h1><p>${escapeAttr(doorWhere(where))}</p>${servingHtml(facts)}${pullsHtml(facts.pulls)}<ul>${links.join("")}</ul>${terminal}</main></body></html>
122 <body><main><h1>${escapeAttr(where.app)}</h1><p>${whereLine(where, forgeUrl)}</p>${servingHtml(facts)}${pullsHtml(facts.pulls)}<ul>${links.join("")}</ul>${terminal}</main></body></html>
113123 `;
114124 return new Response(head ? null : html, {
115125 headers: {
test/apps-door.test.ts +39 −1
@@ -104,12 +104,50 @@ test("a preview says the pull, and a signed-in director gets the tell link", asy
104104 const html = await (
105105 await door(`notes--${pull}.${DOMAIN}`, `forge_grant_${record.id.slice(-8)}=${grant}`)
106106 ).text();
107 expect(html).toContain(`<p>preview of pull #${pull}</p>`);
107 expect(html).toContain(`preview of <a href="${servers.url}/alice/notes/pulls/${pull}">pull #${pull}</a>`);
108108 expect(html).toContain(`href="${servers.url}/alice/notes/tell"`);
109109 expect(html).toContain(">Tell the crew<");
110110 expect(html).not.toContain("<b>t</b>");
111111 });
112112
113 test("a preview links the pull and shows its escaped title to a reader; a private repo shows neither", async () => {
114 const html = await (await door(`notes--${pull}.${DOMAIN}`)).text();
115 expect(html).toContain(
116 `<a href="${servers.url}/alice/notes/pulls/${pull}">pull #${pull}</a>: &lt;b&gt;t&lt;/b&gt;`,
117 );
118 const owner = getUserById(app.db, alice)!;
119 const made = await newApp(app, owner, { name: "hush", description: "x", visibility: "private" });
120 if (!made.ok) throw new Error(String(made.error));
121 const hush = getAppByRepo(app.db, made.value.repo.id)!;
122 const p = createIssue(app.db, {
123 repoId: made.value.repo.id,
124 authorId: alice,
125 title: "secret plan",
126 kind: "pull",
127 });
128 if (!p.ok) throw new Error(p.error);
129 const n = p.value.number;
130 const target = { app: hush, env: `pull-${n}` } as AppTarget;
131 const grant = signGrant(dataDir, target, 60_000, Date.now(), {
132 id: alice,
133 name: "alice",
134 role: "admin",
135 });
136 const mine = await (
137 await door(`hush--${n}.${DOMAIN}`, `forge_grant_${hush.id.slice(-8)}=${grant}`)
138 ).text();
139 expect(mine).toContain("secret plan");
140 // Same facts without the reader's rights: number only.
141 const bare = doorPage(
142 { where: { app: "hush", env: `pull-${n}`, pull: n }, forgeUrl: servers.url },
143 false,
144 );
145 const text = await bare.text();
146 expect(text).toContain(`<p>preview of pull #${n}</p>`);
147 expect(text).not.toContain("<a href=\"" + servers.url + "/alice/hush/pulls");
148 expect(text).not.toContain("secret plan");
149 });
150
113151 test("a nested host is the guest's: not found", async () => {
114152 const res = await door(`other--notes.${DOMAIN}`);
115153 expect(res.status).toBe(404);

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs