Pasting an invite link now works on every popup tab, joins the room at once
and confirms with a toast. Other pasted content shows an "invalid invite
link" toast; pasting while already in a room asks to leave first. A paste
during a running connection attempt joins as soon as the button is released.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Try each #j2:/#join: fragment in text order, so a broken example
earlier in a message no longer hides the real invite link.
- Drop a closing delimiter only when the link itself was wrapped
("<link>", "(link)", "[text](link)"), so wrapped legacy links no
longer add ">" or ")" to the password while raw legacy passwords
keep their punctuation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qx2BQfvCSescFYEacvute
A "Create New Room" attempt that failed validation (custom server
without URL) left pendingRoomCreation set. Filling the form from an
invite does not fire an input event, so the next Join was treated as a
room creation and replaced the invite's chat key with a new one,
breaking chat with the other participants. Applying an invite now
clears the creation state.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qx2BQfvCSescFYEacvute
- Listen for paste on the whole join view, so Ctrl+V works without
first opening the collapsed Manual Connect section; other text
fields keep their normal paste.
- Open the Manual Connect section after an invite is applied, so the
filled-in room, password and relay are visible.
- Strip trailing brackets and punctuation from j2 links pasted from
messages ("(link)", "<link>", "link."), which previously corrupted
the chat key.
- Fix the e2e spec lint error and test real keyboard paste.
- Document the paste flow in the changelog, README and HOW_IT_WORKS.
Refs #47
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qx2BQfvCSescFYEacvute
Pasting an invite link into the room ID or password field now fills
room, password, chat key and relay, regardless of the link's domain.
Only the #j2:/#join: fragment is parsed, so self-hosted invite links
work without opening the page and without injecting into other sites.
The paste event is used directly, so no clipboard permission is needed.
Refs #47
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qx2BQfvCSescFYEacvute
Bump vitest/@vitest/coverage-v8 to 4.1.12, addons-linter to 10.14.0,
sharp to 0.35.5 and svgo to 4.1.0 (supersedes the Dependabot svgo PR).
Raise the brace-expansion, fast-uri, js-yaml and postcss overrides to
their patched releases and refresh undici, image-size and
source-map-js through the lockfile. npm audit now reports 0
vulnerabilities for the root and server packages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qx2BQfvCSescFYEacvute
The late-joiner E2E failed intermittently in CI for two reasons:
- The spec now joins Charlie with the chat secret but kept an empty room
password. With a chat key, the popup's invite link requires a password,
so init() threw before rendering the participant list, which then only
appeared if a later PEER_UPDATE happened to arrive. Created rooms always
carry a password, so the spec now uses one.
- A PEER_UPDATE that rebuilt the participant list right after a
confirmation was cancelled dropped keyboard focus. The popup now restores
focus to the same participant name after every rebuild.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGtFfKKJucjVZrph9x6AKa
- Mark the selected document as showing a video only after a heartbeat has
passed the session checks that follow getSettings(), so a heartbeat that
turned stale meanwhile cannot leave the document marked shareable.
- Correct the tabs permission disclosure: tab addresses are read locally for
tab selection and synchronization regardless of the setting; only sharing
them with room participants depends on Share video link.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGtFfKKJucjVZrph9x6AKa
Align the privacy policy, chat protocol notes, help and changelog with the
link-sharing behaviour: links are shared only for pages with a detected
video, secret holders exchange links only with each other and are protected
against an active relay, and every current client takes part in the
encrypted handshake even with sharing off, exposing a secret-derived tag to
the relay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGtFfKKJucjVZrph9x6AKa
- A chat-secret holder never answers an untagged or foreign-tagged hello
with an ECDH key frame. A relay could otherwise strip the tag from a
forged hello and read the holder's links. Secret holders now exchange
links only within their secret group; rooms without a secret keep the
pairwise exchange.
- Share the selected tab's URL only while its current document has shown a
video. A cross-document navigation withdraws the link until the new page
reports a video, so a selected tab taken to an unrelated site no longer
publishes that site.
- A queued withdrawal may use the chat budget reserved for user messages, so
active chatting cannot delay revoking a link.
- GET_STATUS returns peer links only to extension pages, and the chat
overlay no longer receives CONNECTION_STATUS twice for top-level players.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGtFfKKJucjVZrph9x6AKa
- Show the full-URL disclosure as visible text under the toggle, wired with
aria-describedby, instead of only in a tooltip.
- Cut long destination hosts from the left so the registrable domain stays
visible in the participant row.
- Return keyboard focus to the participant name after cancelling a link
confirmation.
- Insert i18n placeholder values literally; peer-controlled data containing
`$&` or similar patterns no longer rewrites the message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGtFfKKJucjVZrph9x6AKa
A busy browser could finish loading another tab while a completion was in
progress and overwrite the single queued tab id, so the job tab was only
activated by the next keep-alive resume. Queue every completed tab instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGtFfKKJucjVZrph9x6AKa
Video links use the relay's encrypted chat channel. With sharing enabled and a
connected relay that does not advertise chat, the Privacy setting now shows an
explanatory note (translated in all 15 locales) instead of silently doing nothing.
The note stays hidden on capable relays, when sharing is off, or while offline.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA13Ty4MaUSPjvfxN3DuYG
npm audit fix in server/ raises engine.io 6.6.9 to 6.6.11 (Engine.IO protocol
revision mismatch DoS, GHSA-2gc4-cqfq-p2gv) and proxy-addr 2.0.7 to 2.0.8
(IPv4-mapped IPv6 trust subnet spoofing, GHSA-jqcg-44mw-7w3h). Lockfile only; the
server production audit in `npm run verify` reports 0 vulnerabilities again.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA13Ty4MaUSPjvfxN3DuYG
An issued peer navigation is no longer failed after 45 seconds. A slow page that
finishes loading later is still activated, with a five-minute cap enforced by the
keep-alive resume. After the 45-second grace period the loaded page must still be
on the peer's site, so a tab the user took elsewhere meanwhile is reported instead
of being activated. A navigation that was stored but never issued still expires
after 45 seconds.
Adds an end-to-end lifecycle spec (default off, enable/disable, tab switch, clear,
navigation, tab close, relay restart, leave; with and without a chat secret) and
unit tests for the privacy gate and a relay without chat support.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA13Ty4MaUSPjvfxN3DuYG
Participant rows now show the link's destination host (insecure http:// and
punycode look-alikes stay visible). When a tab is already selected, a click shows
an inline confirmation with Cancel focused; Escape cancels. The popup pins the
displayed URL in NAVIGATE_TO_PEER and the worker rejects the request with
status 'changed' if the peer published a different link in the meantime, so an
earlier confirmation can never open a newer destination.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA13Ty4MaUSPjvfxN3DuYG
The share-video-link tooltip now says the full URL, including query string and
fragment, is shared with the room, and that the setting is off by default
(all 15 locales). Privacy policy (EN/DE), docs/PRIVACY.md, help page, the chat
protocol document and the changelog describe the opt-in link sharing and the
peer-link subprotocol carried inside the existing chat envelope.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA13Ty4MaUSPjvfxN3DuYG
Peers that hold the same chat secret now receive each sender key through one
broadcast frame wrapped under an HKDF-derived group key. A handshake costs a
constant number of frames per peer (hello, group key, URL) instead of one key
frame per participant; per-pair ECDH remains only for peers without the secret.
A 25-peer room converges in ~5 s with 4 frames per peer instead of ~32 s and 27.
Peer-link traffic can no longer starve user chat on the shared relay budget:
the limiter gains a reserve so link frames use at most 6 of 10 slots, and its
window carries a 500 ms jitter margin against the relay's fixed-window counter.
A URL withdrawal now overtakes queued handshake frames.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA13Ty4MaUSPjvfxN3DuYG