docs: slice 2 strings
Copy review waived per suite #5; catalogued for the later pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CzvnuKhLzudvq7jZ7SvVt2
1 file changed,
+148
−0
docs/superpowers/copy/2026-09-10-slice-2-strings.md+148 −0
CI passed — run details
diff --git a/docs/superpowers/copy/2026-09-10-slice-2-strings.md b/docs/superpowers/copy/2026-09-10-slice-2-strings.md| new file mode 100644 |
| index 0000000..c9f7b34 |
| --- /dev/null |
| +++ b/docs/superpowers/copy/2026-09-10-slice-2-strings.md |
| @@ -0,0 +1,148 @@ |
| +# Slice 2 copy list |
| + |
| +Copy review waived for this round per suite discussion 5; catalogued for the later pass. |
| + |
| +This covers every user-facing string introduced on branch `slice/2-room` |
| +(`77227de..6354af6`): the holding screen's scripted half, the room and its |
| +client, the removal and takeover confirm pages, and the one `AGENTS.md` |
| +prose change a person could trace back to a screen. Sources: `git diff |
| +77227de..6354af6 -- internal/meet/templates internal/meet/static |
| +internal/meet/handlers_room.go internal/meet/handlers_join.go AGENTS.md`, |
| +task-6/7/8-report.md's own "new/changed user-facing strings" lists, and the |
| +reviewers' copy notes recorded in `.superpowers/sdd/2026-09-10-meet-slice-2-room/progress.md`. |
| +No string below has been through `copy-review`; none was changed to write |
| +this file. |
| + |
| +A note on the JSON refusal bodies: `stream.go`'s 410 body carries only a |
| +machine reason code (`buffer`, `replaced`, `standing`, `expired`, |
| +`removed`, `ended`) in `body["reason"]` — nothing in that JSON is prose a |
| +person reads. `room.js`'s `terminal(reason)` looks the code up in its own |
| +`TERMINAL` table to get the sentence a person actually sees, so the |
| +sentences in the Terminal sentences table below are the complete set of |
| +what those refusal bodies can cause to appear. |
| + |
| +## Holding page (`/m/{sqid}/join`) |
| + |
| +`templates/join.html` |
| + |
| +| String | Where | Note | |
| +| --- | --- | --- | |
| +| This meeting is already open in another tab. | `join.html`, `content` block, `OpenElsewhere` notice | Reviewer note (progress.md, Task 6 review): untrue during the resume window — a session whose stream has merely lapsed, not collided, still sees this. Suggested replacement, not applied to code: "You are already in this meeting from this browser." | |
| +| Use this tab instead | `join.html`, `content` block, link inside the `OpenElsewhere` notice, to `/m/{sqid}/takeover` | Same notice as above; this is the link text, not a separate sentence. | |
| +| The call needs JavaScript to reach your camera and the other participants; everything else on this page works without it. | `join.html`, `content` block, `<noscript>` | | |
| +| Your camera preview | `join.html`, `content` block, `aria-label` on `#preview` | Added in Task 7's fix round (task-7-report.md item 11). | |
| +| Camera | `join.html`, `content` block, `field-select` Label for `#pick-camera` | Same word is the fixed `aria-label`/button text below, on `#toggle-camera`. | |
| +| Microphone | `join.html`, `content` block, `field-select` Label for `#pick-mic` | Same word is the fixed `aria-label`/button text below, on `#toggle-mute`. | |
| +| Speaker | `join.html`, `content` block, `field-select` Label for `#pick-speaker` (row hidden unless `setSinkId` is supported and a speaker is found) | | |
| +| Microphone | `join.html`, `content` block, `#toggle-mute` — `aria-label` and visible button text | Task 7 fix: the accessible name is now fixed; only `aria-pressed` and the state word move. Replaces earlier "Mute"/"Unmute" wording (task-7-report.md item 10). | |
| +| Camera | `join.html`, `content` block, `#toggle-camera` — `aria-label` and visible button text | Same fix; replaces earlier "Camera off"/"Camera on". | |
| +| Join the call | `join.html`, `content` block, `#join` button | | |
| + |
| +`static/devices.js` |
| + |
| +| String | Where | Note | |
| +| --- | --- | --- | |
| +| This browser cannot reach a camera or microphone here. You can still join with neither. | `devices.js`, top level (no `navigator.mediaDevices.getUserMedia`) | "You can still join with neither" recurs in all four device-error/no-support sentences below — the room is built to allow joining with no local media, and every sentence says so the same way. | |
| +| Asking for your camera and microphone… | `devices.js`, `startPreview()` | Task 7 fix: now said before the `getUserMedia` call, not only after it fails (task-7-report.md item 9). | |
| +| Meet needs permission to use your camera and microphone. You can still join with neither. | `devices.js`, `errorSentence()` (`NotAllowedError`) | | |
| +| Your camera or microphone is already in use by another app. You can still join with neither. | `devices.js`, `errorSentence()` (`NotReadableError`) | New in Task 7's fix round; previously fell through to the generic sentence (task-7-report.md item 8). | |
| +| Meet could not reach a camera or microphone. You can still join with neither. | `devices.js`, `errorSentence()` (default — anything but `NotAllowedError`/`NotReadableError`) | Task 7 fix replaces an earlier "No camera or microphone was found…" (task-7-report.md item 8). | |
| +| Muted / On | `devices.js`, `applyToggles()`, `#toggle-mute-state` | State word, never colour alone; moves with `prefs.muted`. | |
| +| Off / On | `devices.js`, `applyToggles()`, `#toggle-camera-state` | State word; moves with `prefs.cameraOff`. | |
| +| Camera `<n>` | `devices.js`, `labelFor()` | Fallback option label in `#pick-camera` when the browser gives no `device.label` (before permission is granted). | |
| +| Microphone `<n>` | `devices.js`, `labelFor()` | Same fallback, for `#pick-mic`. | |
| + |
| +## Room page (`/m/{sqid}`) |
| + |
| +`templates/room.html` — an entirely new file this branch. Several of its |
| +lines are the holding screen's own wording, reused: noted per row. |
| + |
| +| String | Where | Note | |
| +| --- | --- | --- | |
| +| Starts {{.Starts}}. | `room.html`, `content` block (meeting has a start time) | Mirrors the holding screen's identical line (already catalogued for slice 1). | |
| +| You are {{.Name}}, joining as a {{.Standing}}. | `room.html`, `content` block | Task 6 changed this from an initial "in the call as a `<kind>`." to match the holding screen's wording exactly — true before any participation exists, since this renders server-side ahead of anything `room.js` does (task-6-report.md item 14). | |
| +| This team's Meet has no relay yet. Calls usually connect only between people on the same network. | `room.html`, `content` block, floor notice (no relay configured) | Mirrors the holding screen's identical notice. | |
| +| The call needs JavaScript to reach your camera and the other participants; everything else on this page works without it. | `room.html`, `content` block, `<noscript>` | Same sentence as the holding screen's `<noscript>`, with a link appended (next row) the holding screen's version doesn't have. | |
| +| Back to the holding screen | `room.html`, `content` block, link inside the `<noscript>`, to `/m/{sqid}/join` | Same wording `room.js`'s own `wayOut()` uses for its link of the same name (see Links, below) — the scriptless and scripted paths land on the same words. | |
| +| Microphone | `room.html`, `content` block, `#mute` — `aria-label` and visible button text | Same fixed-name pattern as the holding screen's toggle. | |
| +| Camera | `room.html`, `content` block, `#camera` — `aria-label` and visible button text | Same fixed-name pattern as the holding screen's toggle. | |
| +| Leave | `room.html`, `content` block, `#leave` button (`rst-btn="danger"`) | | |
| +| Waiting to join | `room.html`, `content` block, `#queue` heading (`h2`) | Shown only when the admission queue is non-empty. | |
| +| Meetings | `room.html`, `content` block, back-nav Label (members only) | Same established back-nav label used across the app (already catalogued for slice 1); reused here, not new wording. | |
| +| End the meeting | `room.html`, `content` block, member-only link to `/meetings/{sqid}/end` | Same established link text (already catalogued for slice 1); `target="_blank" rel="noopener"` is new here — the room holds the call in this tab, so ending opens in a new one. | |
| + |
| +`static/room.js` |
| + |
| +### Status sentences (`renderStatus()`, via `say()`) |
| + |
| +| String | Where | Note | |
| +| --- | --- | --- | |
| +| The room is full. | `room.js`, `renderStatus()` | Shown while `state.full` is set, cleared by the next roster. | |
| +| Waiting for someone to let you in. | `room.js`, `renderStatus()` | Shown to a guest that has knocked (joined, not yet admitted). A member in the same window is "connecting", never "waiting" — a member is admitted by its own join. | |
| +| Connecting to the call. | `room.js`, `renderStatus()` | Default state between `hello` and admission, for a member or a guest that hasn't joined yet. Task 8 review flagged the risk of this sentence showing forever behind an unnamed refusal; the stall counter (`MAX_STALLS`/`STALL_WINDOW`) and the terminal sentences below are the fix. | |
| +| You are the only one here. | `room.js`, `renderStatus()` | Shown once admitted with no other peers in the mesh. | |
| + |
| +### Terminal sentences (`TERMINAL`, said by `terminal()`) |
| + |
| +| String | Where | Note | |
| +| --- | --- | --- | |
| +| This meeting is open in another tab. | `room.js`, `TERMINAL.replaced` | The one reason with no "Back to the holding screen" link appended — it already has its own real next step (the takeover link, below), and the browser gate reads `#status a` as a single element (task-8-report.md, Deviations item 1). | |
| +| You no longer have access to this meeting. | `room.js`, `TERMINAL.standing` | | |
| +| Your invitation to this meeting has expired. | `room.js`, `TERMINAL.expired` | | |
| +| Someone removed you from this meeting. | `room.js`, `TERMINAL.removed` | | |
| +| This meeting has ended. | `room.js`, `TERMINAL.ended` | Also the fallback for any reason code `TERMINAL` doesn't recognise. | |
| +| You cannot join this meeting. | `room.js`, `TERMINAL.gone` | Task 8 review: "the fresh-404 sentence must be neutral" — replaces an earlier "This meeting is no longer open." (progress.md Task 8 note; task-8-report.md item 1 of "New user-facing strings"). | |
| +| You have been signed out. | `room.js`, `TERMINAL.signedout` | | |
| +| This browser could not open the call’s connection. | `room.js`, `TERMINAL.accept` (406) and `TERMINAL.badlast` (400) | Same sentence for both refusals; written as `’` in the source. | |
| +| Meet cannot reach the call. The room may be full, or you may have been signed out. | `room.js`, `TERMINAL.unreachable` | Reached after `MAX_STALLS` rounds with no `hello` inside `STALL_WINDOW` — a guess covering the two causes the client can't tell apart. | |
| +| This browser cannot make calls. | `room.js`, `TERMINAL.nowebrtc` | | |
| + |
| +### Links (`wayOut()`) |
| + |
| +| String | Where | Note | |
| +| --- | --- | --- | |
| +| Sign in again | `room.js`, `wayOut()`, link to `/signin` | Shown only for `signedout`, and it is the *only* link on that screen — no "Back to the holding screen" is appended, because that would just bounce the person straight back to sign-in (task-8-report.md, Deviations item 1). | |
| +| Use this tab instead | `room.js`, `wayOut()`, link to `/m/{sqid}/takeover` | Shown only for `replaced`. Same wording as the takeover confirm page's own submit button (see below). | |
| +| Try again | `room.js`, `wayOut()`, link to `/m/{sqid}` | Shown only for `unreachable`, alongside "Back to the holding screen". | |
| +| Back to the holding screen | `room.js`, `wayOut()`, link to `/m/{sqid}/join` | Shown for every terminal reason except `replaced` and `signedout` — the rule is that no terminal screen is a dead end. | |
| + |
| +### Tile captions |
| + |
| +| String | Where | Note | |
| +| --- | --- | --- | |
| +| `<name>` (you) | `room.js`, `tile()` | The self tile's caption; `<name>` is the server-rendered `data-self-name`, never typed by this browser. | |
| +| Someone | `room.js`, `renderTiles()` | Fallback caption for a peer whose name hasn't arrived from the server yet. | |
| +| Could not connect. | `room.js`, `tile()` | Shown on a tile whose pair has failed; never colour alone — this sentence plus `.meet-tile-note`'s weight are the two cues. | |
| +| Retry | `room.js`, `tile()` | Button shown beside "Could not connect.", restarts ICE (or requests a fresh generation if the browser has no `restartIce`). | |
| +| Play | `room.js`, `tile()` / `startPlaying()` | Shown only if the browser refuses autoplay on a remote track; new in Task 8's fix round (task-8-report.md item 7 / "New user-facing strings"). | |
| +| Remove | `room.js`, `removeLink()` | A member's removal control on another peer's tile; opens `/m/{sqid}/remove/{peer}` in a new tab (§3.5 destructive-action rule). | |
| + |
| +### The queue |
| + |
| +| String | Where | Note | |
| +| --- | --- | --- | |
| +| Someone | `room.js`, `renderQueue()` | Fallback name for a waiting guest whose name hasn't been sent. Same fallback word as the tile caption above, independent occurrence. | |
| +| Admit | `room.js`, `renderQueue()` | Button beside each waiting guest's name; posts `admit` for that peer. | |
| + |
| +## Removal and takeover confirm pages |
| + |
| +`handlers_room.go`, rendered through `templates/confirm.html` |
| + |
| +| String | Where | Note | |
| +| --- | --- | --- | |
| +| Remove `<name>` from the call? | `handlers_room.go`, `confirmRemove()` — `Title` | `<name>` comes from `hub.PeerNameFor`, the same authority `Remove` itself checks — this page can never name a peer it couldn't actually remove. Earlier draft named the meeting instead of the person (task-6-report.md, Concerns); fixed in Task 6's fix round. Applies equally to a waiting guest in the admission queue — there is no separate wording for that case (progress.md, Task 6 review note). | |
| +| They will be disconnected and can rejoin only if they are still allowed in. | `handlers_room.go`, `confirmRemove()` — `Body` | One sentence for both actor kinds rather than branching copy: true for a guest, who has only the link, and for a member, who can also come back from the meeting list (code comment, `handlers_room.go`; noted there for this catalogue). | |
| +| Remove | `handlers_room.go`, `confirmRemove()` — `Label` (submit button, via `confirm-form`) | | |
| +| Cancel | `templates/confirm.html`, `content` block, `confirm-form` `CancelLabel` | Pre-existing text (already catalogued for slice 1); `confirmRemove`'s and `confirmTakeover`'s `CancelHref` now both point at `/m/{sqid}/join`, not back into the room — a second `/m/{sqid}` would open a second stream and trip the takeover 409 for someone who only pressed Cancel (code comment, `handlers_room.go`; issue 11 half-closed per progress.md, Task 6 review). | |
| +| `<meeting title>` | `handlers_room.go`, `confirmRemove()` and `confirmTakeover()` — `BackLabel`, via `a.store.title(m)` | Back-nav label on both confirm pages, replacing the generic "Meetings" default; `confirm.html`'s `{{or .BackLabel "Meetings"}}` is what makes this possible (task-6-report.md item 9). | |
| +| This meeting is open in another tab | `handlers_room.go`, `confirmTakeover()` — `Title` | | |
| +| Use this tab instead? The other tab will leave the call. | `handlers_room.go`, `confirmTakeover()` — `Body` | | |
| +| Use this tab | `handlers_room.go`, `confirmTakeover()` — `Label` (submit button) | Distinct from `room.js`'s "Use this tab instead" link text (`wayOut()`) and the holding screen's identical link text — three slightly different wordings for the same move, not yet reconciled. | |
| + |
| +## The `AGENTS.md` exception line |
| + |
| +| String | Where | Note | |
| +| --- | --- | --- | |
| +| Screens work with JavaScript disabled, with one exception: browser media participation (the holding screen's camera preview and join button, and the call itself in the room) needs script; both pages render their title, standing, floor notice and navigation without it. Destructive actions get their own confirm-page URL. | `AGENTS.md`, "Rules the family holds hard" | Replaces the prior unqualified "Screens work with JavaScript disabled; destructive actions get their own confirm-page URL." — the exception is scoped to exactly the two pages above. | |
| +| `make browser` is the second gate, for any change to a page or to the client: a real call in real Chromium. It needs the Claude Code bash sandbox **off** — the sandbox blocks UDP, so Chromium gathers no ICE candidates and every call fails with `frames` stuck at 0, which reads exactly like a client bug and is not one. | `AGENTS.md`, "Rules the family holds hard", new bullet | Developer-facing (this doc, and whoever else reads `AGENTS.md`), not end-user copy, but a string introduced on this branch a person reads. | |
| +| Accessible by construction, progressively enhanced, and never dependent on JavaScript to be usable, with the one exception named above: browser media participation, on the holding screen and in the room. | `AGENTS.md`, "UX conventions" intro line | Same exception restated where the UX-conventions section makes the blanket claim; kept consistent with the Rules-section wording rather than contradicting it (task-6-report.md item 10). | |