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

A link you send looks like an invitation: share tags on a table, and a robots policy for the views that are nobody else's #71

open not picked up yet opened this
opened this

Intent. The app's own advice for filling a table is to copy its URL and send it to four people; make that link arrive as an invitation rather than as a bare address, and keep the private views out of search engines.

Measured on main today (e933965): /tables/1 renders the Invite field with the table's whole URL in it, and the document it points at carries <title>, a viewport and a stylesheet — no <meta name="description">, no og: or twitter: tags, no canonical link. Pasted into any chat window, the unfurl is the bare host and nothing else: not the table's name, not how many seats are free, not what the game is. There is no robots.txt and no X-Robots-Tag anywhere; private, no-store is a cache instruction and crawlers do not read it that way.

Done looks like

  • [ ] The layout takes an optional description and share block from the page, so this lives in one place and every page can fill it: <meta name="description">, og:title, og:description, og:url, og:type, og:site_name, twitter:card. Escaped like everything else, and built from the row's own values — never from anything a caller sent.
  • [ ] A table's tags say what the reader would want to know before clicking: the title, the state, how many of how many seats are taken, and the turn window — from tableState and nowhere else, so an unlisted fact cannot leak into an unfurl. Never the charter, never a charge, never a name that is not already on the public page.
  • [ ] og:url and the canonical link are built from publicOrigin and the row's id, so they are right in a preview and in production and cannot be aimed by a request (#61 is the boot check for FORGE_APP_URL being unset; this issue only consumes it).
  • [ ] GET /robots.txt (same-for-everyone, its own cache-control, not PRIVATE) allows the lobby and the rules page and disallows nothing that does not exist yet; every answer that is private, no-store also carries X-Robots-Tag: noindex from the one place the floor is set, so a view redacted for one viewer is never indexed for everyone.
  • [ ] A copy button on the Invite field for a thumb — navigator.clipboard where it exists, the field still selectable and readable where it does not, and 44px on a side. No dependency, and the page works with the script missing.
  • [ ] Tests that fail without it: the table page contains og:title with the table's title escaped and no charge or charter anywhere in the head; /robots.txt answers text/plain with the floor; a private answer carries X-Robots-Tag: noindex and /style does not.

Files I expect to change. src/pages/layout.ts, src/pages/tableview.ts, src/page.ts, src/routes.ts, src/http.ts, static/live.js or a small script for the copy button, src/server.test.ts.

Risks. An og:image is out of scope: the CSP is img-src 'self' data: and a generated card is a second feature — say in the pull that it was left out. Do not let the description drift into a second, hand-written copy of what the page already says; derive it. And keep it honest: an unfurl is public by definition, so nothing goes in it that the page does not already show a guest.

Who. @mason, reviewed by @sentinel (what an unfurl may carry) and @juniper (the words). Ready now.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs