Public: anyone can read this; the crew can change it.
"Report a problem (no account needed)" lives in one place, read by the app proxy, the API and the standards pages #5643
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/5624-report-a-problem-no-account-needed-lives → main 4 commits fork 1 thread · 1 unresolved
5 files
changed +40
−6
src/actions/report_words.ts +8 −3
| @@ -1,9 +1,14 @@ | ||
| 1 | 1 | /** |
| 2 | * The words an outsider reads back after filing a report. Every door — the | |
| 3 | * API's POST …/reports and the MCP `report` tool — answers from here, so | |
| 4 | * the sentence reads the same wherever the report came in. | |
| 2 | * The words a stranger is offered for reporting a problem and reads back | |
| 3 | * after filing one, said once here. The app proxy's Link header, the API's | |
| 4 | * refusal and POST …/reports, the MCP `report` tool and the standards pages' | |
| 5 | * help link all read from here, so the words are the same wherever they | |
| 6 | * appear. | |
| 5 | 7 | */ |
| 6 | 8 | |
| 9 | /** The title of the link to report a problem without an account. */ | |
| 10 | export const REPORT_LINK_TITLE = "Report a problem (no account needed)"; | |
| 11 | ||
| 7 | 12 | /** The answer to a report that was filed and now waits for a person. */ |
| 8 | 13 | export const REPORT_RECEIVED = |
| 9 | 14 | "Received. A person who maintains this repository will read it; nothing acts on it before they do."; |
src/apps/proxy.ts +2 −1
| @@ -20,6 +20,7 @@ import { getIssue, type Issue } from "../core/issues.ts"; | ||
| 20 | 20 | import { mayView } from "../core/sealed.ts"; |
| 21 | 21 | import { holdOpen } from "../http/caller.ts"; |
| 22 | 22 | import { appHostSuffix } from "./host.ts"; |
| 23 | import { REPORT_LINK_TITLE } from "../actions/report_words.ts"; | |
| 23 | 24 | import { createHmac, timingSafeEqual } from "node:crypto"; |
| 24 | 25 | import type { App } from "../app.ts"; |
| 25 | 26 | import { getAppById, getAppBySlug, type AppRecord } from "../core/apps.ts"; |
| @@ -632,7 +633,7 @@ function reportUrl(runtime: AppsRuntime, target: AppTarget): URL | undefined { | ||
| 632 | 633 | |
| 633 | 634 | /** The link relation a public app's pages carry to `/__forge/report`. */ |
| 634 | 635 | const REPORT_LINK = |
| 635 | '</__forge/report>; rel="help"; title="Report a problem (no account needed)"'; | |
| 636 | `</__forge/report>; rel="help"; title="${REPORT_LINK_TITLE}"`; | |
| 636 | 637 | |
| 637 | 638 | const isHtml = (headers: Headers): boolean => |
| 638 | 639 | (headers.get("content-type") ?? "").toLowerCase().startsWith("text/html"); |
src/http/api.ts +2 −1
| @@ -81,6 +81,7 @@ import { | ||
| 81 | 81 | import { resendVerification } from "../actions/verify.ts"; |
| 82 | 82 | import { PROVE_FIRST } from "../actions/verify_words.ts"; |
| 83 | 83 | import { CAPPED_WORDS } from "../actions/mail.ts"; |
| 84 | import { REPORT_LINK_TITLE } from "../actions/report_words.ts"; | |
| 84 | 85 | import { VERIFY_WORDS } from "../actions/verify_words.ts"; |
| 85 | 86 | import { editMessage } from "../actions/edit_words.ts"; |
| 86 | 87 | import { emailVerified } from "../core/verify.ts"; |
| @@ -516,7 +517,7 @@ function reportInstead(ctx: Ctx, repo: Repo): Response { | ||
| 516 | 517 | report: { page, api, method: "POST", fields: ["title", "body", "page"] }, |
| 517 | 518 | }, |
| 518 | 519 | 401, |
| 519 | { link: `<${page}>; rel="help"; title="Report a problem (no account needed)"` }, | |
| 520 | { link: `<${page}>; rel="help"; title="${REPORT_LINK_TITLE}"` }, | |
| 520 | 521 | ); |
| 521 | 522 | } |
| 522 | 523 |
src/web/standards.ts +2 −1
| @@ -27,6 +27,7 @@ import releases from "../core/releases.json" with { type: "json" }; | ||
| 27 | 27 | import { markPng, type MarkShape } from "../design/mark.ts"; |
| 28 | 28 | import { metaThemeColor } from "../design/themes.ts"; |
| 29 | 29 | import { BODY_MAX, TITLE_MAX } from "../core/reports.ts"; |
| 30 | import { REPORT_LINK_TITLE } from "../actions/report_words.ts"; | |
| 30 | 31 | import { sshTellCommand } from "../ssh/command.ts"; |
| 31 | 32 | import { llmsRepos, sitemapRepos } from "../core/listings.ts"; |
| 32 | 33 | import { canRead, getRepo } from "../core/repos.ts"; |
| @@ -404,7 +405,7 @@ export function pageLinks(ctx: Ctx): PageLink[] { | ||
| 404 | 405 | links.push({ |
| 405 | 406 | rel: "help", |
| 406 | 407 | href: `/${repo.ownerName}/${repo.name}/report`, |
| 407 | title: "Report a problem (no account needed)", | |
| 408 | title: REPORT_LINK_TITLE, | |
| 408 | 409 | }); |
| 409 | 410 | } |
| 410 | 411 | return links; |
test/report-link-words.test.ts added +26
| @@ -0,0 +1,26 @@ | ||
| 1 | /** | |
| 2 | * "Report a problem (no account needed)" is said in one place (#5624): | |
| 3 | * src/actions/report_words.ts. The proxy, the API and the standards pages | |
| 4 | * read it from there; a copy typed back into one of them fails here. | |
| 5 | */ | |
| 6 | ||
| 7 | import { expect, test } from "bun:test"; | |
| 8 | import { readFileSync } from "node:fs"; | |
| 9 | import { join } from "node:path"; | |
| 10 | import { REPORT_LINK_TITLE } from "../src/actions/report_words.ts"; | |
| 11 | ||
| 12 | const SENTENCES = ["Report a problem (no account needed)"]; | |
| 13 | const READERS = ["src/apps/proxy.ts", "src/http/api.ts", "src/web/standards.ts"]; | |
| 14 | const source = (path: string) => | |
| 15 | readFileSync(join(import.meta.dir, "..", path), "utf8"); | |
| 16 | ||
| 17 | test("the module says the sentence", () => { | |
| 18 | expect(REPORT_LINK_TITLE).toBe("Report a problem (no account needed)"); | |
| 19 | }); | |
| 20 | ||
| 21 | for (const path of READERS) | |
| 22 | test(`${path} imports the words and types none of them`, () => { | |
| 23 | const text = source(path); | |
| 24 | expect(text).toContain("../actions/report_words.ts"); | |
| 25 | for (const sentence of SENTENCES) expect(text).not.toContain(sentence); | |
| 26 | }); |
new above
offline
Nit: api.ts already imports
REPORT_RECEIVEDfrom this module further down; one import of both names reads cleaner.