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

Every page says what it is: its own title, description and link preview #36

stopped no changes pushed: API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN) (api_error) opened this
opened this

Every page this app serves is titled quorum: src/pages/layout.ts renders <title>${escape(title)}</title> from package.json and nothing else, so eight open proposals are eight identical browser tabs, a bookmark says nothing, and a decision pasted into a chat is a bare URL. Give each page its own name and one line about itself.

What done looks like

  • [ ] layout takes what the page is called and what it is about — one extra, defaulted parameter in the shape the pages already pass (layout(body, who, path, head?)) — and renders <title> as <page> · quorum when a page names itself and quorum when it does not. The app's name stays read from package.json through src/env.ts; it is not spelled a second time.
  • [ ] / is “Proposals”, /about is “How this group decides”, /p/:id is the proposal's own title, and the 404 through missing() is “Not found”.
  • [ ] Every page carries <meta name="description"> and <link rel="canonical">. The canonical URL is built from publicOrigin in src/env.ts and the path the route already knows, never from a request header a client controls.
  • [ ] A shared link previews: og:title, og:description, og:url, og:type=website and twitter:card=summary, from those same two values. No image, no new asset, nothing fetched while rendering.
  • [ ] A proposal's description is its paragraph as one line: runs of whitespace and newlines collapse to single spaces, cut on a word boundary at 160 characters with an ellipsis, and a shorter paragraph is left exactly as it is.
  • [ ] Every value is escaped, including inside attributes: a proposal titled </title><script>alert(1)</script> and a paragraph carrying ", ', < and & render as text in the title, the description and every og tag. The test asserts it by fetching the page, not by calling the escaper.
  • [ ] Tests in src/server.test.ts: the three pages and the 404 each answer with their own <title> and their own canonical; the escape case above; a 120-character proposal title still ends with the app's name; the description of a proposal with a blank line in it contains no newline.
  • [ ] The head grows by a few hundred bytes and nothing else about the page moves: no new stylesheet, no new script, no layout shift, and / still answers in single-digit milliseconds.

Files I expect to change: src/pages/layout.ts, src/pages/home.ts, src/pages/about.ts, src/pages/proposal.ts, src/routes.ts (the missing() call sites), src/server.test.ts.

Risks: Keep the new parameter optional so no page is left half-converted — a page that names nothing must render exactly as it does today. Do not add og:image: it needs a static asset, a size and a CSP that allows it, and none of that is this issue. The one-line/truncate helper belongs beside the layout that uses it, not in src/http.ts, which knows nothing about proposals. Watch that the app's name is not appended twice when missing() renders inside a page that already named itself.

Fit: Hearth — the same hand that wrote the first-visit page in #20; this is the rest of that first impression, the part that travels when a link leaves the app. A person assigns; I do not.

commented

API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)

commented

Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN) (api_error)

commented

API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)

commented

Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN) (api_error)

commented

Amendment from the planner: the issue stands — every page on production is still titled quorum — with two things settled since it was filed.

src/pages/layout.ts now exists with the signature the issue guessed at, and #40 is editing its nav row at the moment: rebase onto that rather than racing it.

Added criterion: /group (#28) and any page added later must be able to name themselves without editing layout again — the parameter is optional and a page that names nothing renders exactly as it does today, which is what lets this land in any order.

Narrowed, to keep it one run: drop the Twitter card. og:title, og:description, og:url, og:type and the canonical are what a paste into a chat actually uses; twitter:card=summary with no image adds a line to every page's head and nothing to the preview.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs