Audience joined via presentation link can exit and see whole board #107

Open
opened 2026-04-29 13:10:09 +00:00 by AhmedHanafy725 · 3 comments
Member

Summary

When someone joins via a presentation share link (/s/<token>?present=1), they currently can press Esc or click the Exit button in the control bar to drop out of presentation mode and see the entire board. That defeats the purpose of the presentation link — the recipient was meant to watch slides, not roam the board.

Steps to reproduce

  1. As a presenter, start presenting and share the presentation link from the share modal.
  2. Open that link in another browser/tab.
  3. Press Esc or click the X Exit button in the control bar.

Expected

Audience joining via ?present=1 cannot exit presentation mode. The Exit button is hidden and Esc has no effect for them. The presenter (who did not arrive via ?present=1) can still exit normally.

Actual

The audience drops out of presentation and can pan / zoom around the entire board.

Notes

  • Applies to both viewer- and editor-role share links when joined with ?present=1.
  • Should also hide the navbar back-link on the viewer template when ?present=1, since that's another exit path.
  • Client-side URL manipulation (manually deleting ?present=1) is out of scope — we only need to remove the casual Exit / Esc / back-link affordances.
## Summary When someone joins via a **presentation share link** (`/s/<token>?present=1`), they currently can press **Esc** or click the **Exit** button in the control bar to drop out of presentation mode and see the entire board. That defeats the purpose of the presentation link — the recipient was meant to watch slides, not roam the board. ## Steps to reproduce 1. As a presenter, start presenting and share the presentation link from the share modal. 2. Open that link in another browser/tab. 3. Press **Esc** or click the **X** Exit button in the control bar. ## Expected Audience joining via `?present=1` cannot exit presentation mode. The Exit button is hidden and Esc has no effect for them. The presenter (who did not arrive via `?present=1`) can still exit normally. ## Actual The audience drops out of presentation and can pan / zoom around the entire board. ## Notes - Applies to both viewer- and editor-role share links when joined with `?present=1`. - Should also hide the navbar back-link on the viewer template when `?present=1`, since that's another exit path. - Client-side URL manipulation (manually deleting `?present=1`) is out of scope — we only need to remove the casual Exit / Esc / back-link affordances.
Author
Member

Implementation Spec for Issue #107

Objective

When someone joins via a presentation share link (?present=1), they should be locked into presentation mode. The Exit button is hidden, the Esc keydown handler is suppressed, and the navbar back-link in the viewer template doesn't show. The original presenter (no ?present=1) keeps the full Exit/Esc affordances.

Requirements

  • Audience joining via ?present=1 cannot stop presentation through any in-page UI (Exit button, Esc, viewer navbar back-link).
  • The presenter's normal flow is unchanged.
  • Applies to both board.html (editor share with ?present=1) and board_view.html (viewer share with ?present=1).
  • No changes needed in frames.js or sync.js — purely a template/UI gate.

Files to Modify

  • crates/hero_whiteboard_ui/templates/web/board_view.html — gate Exit button, Esc handler, and navbar back-link on ?present=1.
  • crates/hero_whiteboard_ui/templates/web/board.html — same gating for Exit button + Esc handler. (No navbar back-link change here — the editor template's navbar is already hidden via body.wb-presenting .wb-navbar { display:none }, so the back-link is unreachable while presenting. For the viewer template, the navbar is hidden via the same rule, so we may not even need the navbar tweak — verify during implementation.)

Implementation Plan

Step 1: Detect ?present=1 once and gate audience UI

Files: crates/hero_whiteboard_ui/templates/web/board_view.html, crates/hero_whiteboard_ui/templates/web/board.html

In each template's existing bindPresentation IIFE (or near the top of the inline <script> block):

var _isAudience = false;
try {
    _isAudience = new URLSearchParams(location.search).get('present') === '1';
} catch (e) {}

Then:

  1. Hide the Exit button — if _isAudience, set exitBtn.style.display = 'none' and skip the addEventListener('click', ...) wiring. Cheaper than removing the node from the DOM, and survives any code that re-queries the button by ID.

  2. Suppress the Esc handler — in the existing document.addEventListener('keydown', ...) block, add if (_isAudience) return; before if (e.key === 'Escape').

  3. (Viewer only) — the navbar (<div class="wb-navbar">) is already hidden by body.wb-presenting .wb-navbar { display:none !important; }, so there's no extra step needed unless smoke-testing reveals an edge case where the audience leaves presentation (which they shouldn't be able to). Skip this unless reproducible.

Dependencies: none.

Acceptance Criteria

  • Audience opens /s/<token>?present=1 — no Exit button visible in the control bar.
  • Audience presses Esc — nothing happens; presentation continues.
  • Presenter (own tab, /board/<id>) — Exit button visible; Esc still exits.
  • Editor share with ?present=1 — same audience lockdown as viewer share with ?present=1.
  • cargo fmt, clippy --all-targets -D warnings, cargo test --workspace --lib clean (no Rust changes; templates only re-compile via Askama).

Notes

  • Why not server-side enforcement? The ?present=1 flag is purely a UX hint — anyone can edit the URL bar. We're closing the casual exit affordances, not the URL-hacking path. That tradeoff was explicitly accepted in the issue.
  • Why hide instead of remove? Hiding via style.display = 'none' keeps the DOM stable so any other code that grabs the node by ID (e.g. shortcut bindings, future features) doesn't crash.
  • Reactions, share button, prev/next — these all stay accessible for the audience. Only the exit affordances are gated.
## Implementation Spec for Issue #107 ### Objective When someone joins via a presentation share link (`?present=1`), they should be locked into presentation mode. The Exit button is hidden, the Esc keydown handler is suppressed, and the navbar back-link in the viewer template doesn't show. The original presenter (no `?present=1`) keeps the full Exit/Esc affordances. ### Requirements - Audience joining via `?present=1` cannot stop presentation through any in-page UI (Exit button, Esc, viewer navbar back-link). - The presenter's normal flow is unchanged. - Applies to both `board.html` (editor share with `?present=1`) and `board_view.html` (viewer share with `?present=1`). - No changes needed in `frames.js` or `sync.js` — purely a template/UI gate. ### Files to Modify - `crates/hero_whiteboard_ui/templates/web/board_view.html` — gate Exit button, Esc handler, and navbar back-link on `?present=1`. - `crates/hero_whiteboard_ui/templates/web/board.html` — same gating for Exit button + Esc handler. (No navbar back-link change here — the editor template's navbar is already hidden via `body.wb-presenting .wb-navbar { display:none }`, so the back-link is unreachable while presenting. For the viewer template, the navbar is hidden via the same rule, so we may not even need the navbar tweak — verify during implementation.) ### Implementation Plan #### Step 1: Detect `?present=1` once and gate audience UI Files: `crates/hero_whiteboard_ui/templates/web/board_view.html`, `crates/hero_whiteboard_ui/templates/web/board.html` In each template's existing `bindPresentation` IIFE (or near the top of the inline `<script>` block): ```js var _isAudience = false; try { _isAudience = new URLSearchParams(location.search).get('present') === '1'; } catch (e) {} ``` Then: 1. **Hide the Exit button** — if `_isAudience`, set `exitBtn.style.display = 'none'` and skip the `addEventListener('click', ...)` wiring. Cheaper than removing the node from the DOM, and survives any code that re-queries the button by ID. 2. **Suppress the Esc handler** — in the existing `document.addEventListener('keydown', ...)` block, add `if (_isAudience) return;` before `if (e.key === 'Escape')`. 3. **(Viewer only)** — the navbar (`<div class="wb-navbar">`) is already hidden by `body.wb-presenting .wb-navbar { display:none !important; }`, so there's no extra step needed unless smoke-testing reveals an edge case where the audience leaves presentation (which they shouldn't be able to). Skip this unless reproducible. Dependencies: none. ### Acceptance Criteria - [ ] Audience opens `/s/<token>?present=1` — no Exit button visible in the control bar. - [ ] Audience presses Esc — nothing happens; presentation continues. - [ ] Presenter (own tab, `/board/<id>`) — Exit button visible; Esc still exits. - [ ] Editor share with `?present=1` — same audience lockdown as viewer share with `?present=1`. - [ ] `cargo fmt`, `clippy --all-targets -D warnings`, `cargo test --workspace --lib` clean (no Rust changes; templates only re-compile via Askama). ### Notes - **Why not server-side enforcement?** The `?present=1` flag is purely a UX hint — anyone can edit the URL bar. We're closing the casual exit affordances, not the URL-hacking path. That tradeoff was explicitly accepted in the issue. - **Why hide instead of remove?** Hiding via `style.display = 'none'` keeps the DOM stable so any other code that grabs the node by ID (e.g. shortcut bindings, future features) doesn't crash. - **Reactions, share button, prev/next** — these all stay accessible for the audience. Only the exit affordances are gated.
Author
Member

Test Results

  • cargo fmt --all -- --check — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo check -p hero_whiteboard_ui — clean
  • cargo test --workspace --lib — 0 failed (no unit tests in any crate; no regressions)

Templates re-compiled by Askama. JS-only logic gated on URLSearchParams.get('present').

## Test Results - `cargo fmt --all -- --check` — clean - `cargo clippy --workspace --all-targets -- -D warnings` — clean - `cargo check -p hero_whiteboard_ui` — clean - `cargo test --workspace --lib` — 0 failed (no unit tests in any crate; no regressions) Templates re-compiled by Askama. JS-only logic gated on `URLSearchParams.get('present')`.
Author
Member

Implementation Summary

Lock the audience into presentation when they arrive via ?present=1. The presenter is unaffected.

Detection

At the top of each template's inline <script> block:

var _isAudience = false;
try { _isAudience = new URLSearchParams(location.search).get('present') === '1'; } catch (e) {}

Gating

crates/hero_whiteboard_ui/templates/web/board.html

  • Esc keydown handler: skips stopPresentation when _isAudience is true (Prev/Next still work for the audience).
  • Exit button: style.display = 'none' for audience; click handler not wired.

crates/hero_whiteboard_ui/templates/web/board_view.html

  • Same Exit-button gate.
  • Esc keydown handler: early-return when _isAudience is true.

The viewer-template navbar is already hidden by body.wb-presenting .wb-navbar { display:none !important; }, so the back-link is unreachable while presenting — no extra work needed.

Files Changed

  • crates/hero_whiteboard_ui/templates/web/board.html+8/-2
  • crates/hero_whiteboard_ui/templates/web/board_view.html+12/-2

Test Results

  • cargo fmt --all -- --check — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo check -p hero_whiteboard_ui — clean
  • cargo test --workspace --lib — 0 failures

Manual smoke

  1. Presenter starts presenting on /board/<id> → Exit visible, Esc still exits.
  2. Audience opens /s/<token>?present=1 → no Exit button, Esc does nothing, Prev/Next/reactions still work.
  3. Editor share with ?present=1 → same audience lockdown.

Notes

  • This closes the casual exit affordances. URL-bar manipulation (deleting ?present=1) is out of scope, as documented in the issue.
  • Reactions, Share, Prev, Next, slide counter remain accessible to the audience.
## Implementation Summary Lock the audience into presentation when they arrive via `?present=1`. The presenter is unaffected. ### Detection At the top of each template's inline `<script>` block: ```js var _isAudience = false; try { _isAudience = new URLSearchParams(location.search).get('present') === '1'; } catch (e) {} ``` ### Gating **`crates/hero_whiteboard_ui/templates/web/board.html`** - Esc keydown handler: skips `stopPresentation` when `_isAudience` is true (Prev/Next still work for the audience). - Exit button: `style.display = 'none'` for audience; click handler not wired. **`crates/hero_whiteboard_ui/templates/web/board_view.html`** - Same Exit-button gate. - Esc keydown handler: early-return when `_isAudience` is true. The viewer-template navbar is already hidden by `body.wb-presenting .wb-navbar { display:none !important; }`, so the back-link is unreachable while presenting — no extra work needed. ### Files Changed - `crates/hero_whiteboard_ui/templates/web/board.html` — `+8/-2` - `crates/hero_whiteboard_ui/templates/web/board_view.html` — `+12/-2` ### Test Results - `cargo fmt --all -- --check` — clean - `cargo clippy --workspace --all-targets -- -D warnings` — clean - `cargo check -p hero_whiteboard_ui` — clean - `cargo test --workspace --lib` — 0 failures ### Manual smoke 1. Presenter starts presenting on `/board/<id>` → Exit visible, Esc still exits. 2. Audience opens `/s/<token>?present=1` → no Exit button, Esc does nothing, Prev/Next/reactions still work. 3. Editor share with `?present=1` → same audience lockdown. ### Notes - This closes the casual exit affordances. URL-bar manipulation (deleting `?present=1`) is out of scope, as documented in the issue. - Reactions, Share, Prev, Next, slide counter remain accessible to the audience.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_whiteboard#107
No description provided.