Viewer / presentation share opens with default theme instead of board theme #114

Open
opened 2026-04-30 08:19:08 +00:00 by AhmedHanafy725 · 3 comments
Member

Summary

When a viewer joins a board through a shared link (with or without ?present=1), the page loads with the default light theme regardless of what theme the owner has applied. If the owner changes the theme while the viewer is already on the page, the change syncs live and renders correctly — only the initial load is wrong.

Steps to reproduce

  1. As the owner, set a non-default theme on a board (e.g. Dark, Ocean, Warm).
  2. Share the board (viewer link) and open the link in another browser/tab. Optionally append ?present=1.
  3. Observe the viewer tab.

Expected

The viewer tab loads with the owner's chosen theme.

Actual

The viewer tab loads in the default light theme. If the owner then re-applies the same theme, the viewer tab updates instantly. So the live-sync path works; only the initial load is broken.

Root cause (already pinpointed)

crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js::loadBoard gates initial theme loading on !readonlyMode:

if (boardData && boardData.theme && !readonlyMode) {
    WhiteboardThemes.loadTheme(boardData.theme, boardData.customCSS);
}

The viewer template (board_view.html) initializes with { readonly: true }, so the theme is silently skipped. The live-update path in sync.js::handleWsMessage (board.theme.updated) does not check readonlyMode, which is why a change applies but the initial load doesn't.

Fix

Remove the !readonlyMode gate. Theme is a presentation-only concern — it makes no sense to skip it for read-only viewers.

## Summary When a viewer joins a board through a shared link (with or without `?present=1`), the page loads with the default light theme regardless of what theme the owner has applied. If the owner *changes* the theme while the viewer is already on the page, the change syncs live and renders correctly — only the initial load is wrong. ## Steps to reproduce 1. As the owner, set a non-default theme on a board (e.g. Dark, Ocean, Warm). 2. Share the board (viewer link) and open the link in another browser/tab. Optionally append `?present=1`. 3. Observe the viewer tab. ## Expected The viewer tab loads with the owner's chosen theme. ## Actual The viewer tab loads in the default light theme. If the owner then re-applies the same theme, the viewer tab updates instantly. So the live-sync path works; only the initial load is broken. ## Root cause (already pinpointed) `crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js::loadBoard` gates initial theme loading on `!readonlyMode`: ```js if (boardData && boardData.theme && !readonlyMode) { WhiteboardThemes.loadTheme(boardData.theme, boardData.customCSS); } ``` The viewer template (`board_view.html`) initializes with `{ readonly: true }`, so the theme is silently skipped. The live-update path in `sync.js::handleWsMessage` (`board.theme.updated`) does not check `readonlyMode`, which is why a *change* applies but the *initial* load doesn't. ## Fix Remove the `!readonlyMode` gate. Theme is a presentation-only concern — it makes no sense to skip it for read-only viewers.
Author
Member

Implementation Spec for Issue #114

Objective

Viewer / presentation-share tabs must load the board's theme on initial render, not just pick it up if the owner re-applies it after the page is already open.

Root cause (already located)

crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js::loadBoard gates the initial theme load on !readonlyMode:

if (boardData && boardData.theme && !readonlyMode) {
    WhiteboardThemes.loadTheme(boardData.theme, boardData.customCSS);
}

The viewer template (board_view.html) calls WhiteboardApp.init(_boardId, { readonly: true }), so the theme load is silently skipped on first render. The live-update path in sync.js::handleWsMessage for board.theme.updated doesn't check readonlyMode, which is why a subsequent owner-side change applies but the initial load doesn't.

Requirements

  • Viewer / presentation-share tabs render the owner's theme on initial load, not just on subsequent changes.
  • No regression for the editor (board.html) initial load — it already worked.
  • No regression for boards that have no boardData.theme — they still fall through to defaults.
  • No theme-edit UI is enabled in viewer mode (the readonly gating around the theme picker stays elsewhere; we only change the read-only load gate).

Files to Modify

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js — drop && !readonlyMode from the initial-load theme branch in loadBoard.

Implementation Plan

Step 1: Apply theme on initial load regardless of read/write mode

File: crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js

Change:

if (boardData && boardData.theme && !readonlyMode) {
    WhiteboardThemes.loadTheme(boardData.theme, boardData.customCSS);
}

to:

if (boardData && boardData.theme) {
    WhiteboardThemes.loadTheme(boardData.theme, boardData.customCSS);
}

That's the entire fix. WhiteboardThemes.loadTheme already handles _fromSync-style behavior cleanly via the same code path (parses customCSS, registers it under customThemes[name], and re-applies CSS variables to the DOM); calling it on initial load in readonly mode is exactly what we want.

Dependencies: none.

Acceptance Criteria

  • Owner sets a Dark/Ocean/Warm theme on a board.
  • Open the viewer share link in a fresh tab — the board renders with the owner's theme on first paint.
  • Open the viewer share link with ?present=1 — same: presentation tab loads with the correct theme.
  • Editor share / /board/<id> — unchanged.
  • No theme on board → viewer still loads with default theme (no JS error).
  • cargo fmt, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace --lib clean.

Notes

  • One-line surgical fix. The previous gating was the bug, not a guarded behavior — applying a theme has no side effects that depend on read/write mode.
  • The theme picker UI (in themes.js::init) is built into the navbar, which is hidden in board_view.html via existing CSS — so the viewer still has no way to change the theme. We're only fixing the display of the owner's chosen theme.
## Implementation Spec for Issue #114 ### Objective Viewer / presentation-share tabs must load the board's theme on initial render, not just pick it up if the owner re-applies it after the page is already open. ### Root cause (already located) `crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js::loadBoard` gates the initial theme load on `!readonlyMode`: ```js if (boardData && boardData.theme && !readonlyMode) { WhiteboardThemes.loadTheme(boardData.theme, boardData.customCSS); } ``` The viewer template (`board_view.html`) calls `WhiteboardApp.init(_boardId, { readonly: true })`, so the theme load is silently skipped on first render. The live-update path in `sync.js::handleWsMessage` for `board.theme.updated` doesn't check `readonlyMode`, which is why a subsequent owner-side change applies but the initial load doesn't. ### Requirements - Viewer / presentation-share tabs render the owner's theme on initial load, not just on subsequent changes. - No regression for the editor (`board.html`) initial load — it already worked. - No regression for boards that have no `boardData.theme` — they still fall through to defaults. - No theme-edit UI is enabled in viewer mode (the readonly gating around the theme picker stays elsewhere; we only change the read-only *load* gate). ### Files to Modify - `crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js` — drop `&& !readonlyMode` from the initial-load theme branch in `loadBoard`. ### Implementation Plan #### Step 1: Apply theme on initial load regardless of read/write mode File: `crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js` Change: ```js if (boardData && boardData.theme && !readonlyMode) { WhiteboardThemes.loadTheme(boardData.theme, boardData.customCSS); } ``` to: ```js if (boardData && boardData.theme) { WhiteboardThemes.loadTheme(boardData.theme, boardData.customCSS); } ``` That's the entire fix. `WhiteboardThemes.loadTheme` already handles `_fromSync`-style behavior cleanly via the same code path (parses `customCSS`, registers it under `customThemes[name]`, and re-applies CSS variables to the DOM); calling it on initial load in readonly mode is exactly what we want. Dependencies: none. ### Acceptance Criteria - [ ] Owner sets a Dark/Ocean/Warm theme on a board. - [ ] Open the viewer share link in a fresh tab — the board renders with the owner's theme on first paint. - [ ] Open the viewer share link with `?present=1` — same: presentation tab loads with the correct theme. - [ ] Editor share / `/board/<id>` — unchanged. - [ ] No theme on board → viewer still loads with default theme (no JS error). - [ ] `cargo fmt`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace --lib` clean. ### Notes - One-line surgical fix. The previous gating was the bug, not a guarded behavior — applying a theme has no side effects that depend on read/write mode. - The theme picker UI (in `themes.js::init`) is built into the navbar, which is hidden in `board_view.html` via existing CSS — so the viewer still has no way to *change* the theme. We're only fixing the *display* of the owner's chosen theme.
Author
Member

Test Results

  • cargo fmt --all -- --check — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo test --workspace --lib — 0 failed
  • node --check app.js — clean
## Test Results - `cargo fmt --all -- --check` — clean - `cargo clippy --workspace --all-targets -- -D warnings` — clean - `cargo test --workspace --lib` — 0 failed - `node --check app.js` — clean
Author
Member

Implementation Summary

One-line surgical fix: drop the !readonlyMode gate from initial theme loading in app.js.

Root cause

WhiteboardApp.loadBoard was skipping the theme load on initial render whenever the page was opened with { readonly: true }, which is exactly the case for board_view.html (viewer share + presentation share). The live-update path in sync.js::handleWsMessage for board.theme.updated was not gated, which is why a change applied but the initial load did not.

crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js

-                if (boardData && boardData.theme && !readonlyMode) {
+                if (boardData && boardData.theme) {
                     WhiteboardThemes.loadTheme(boardData.theme, boardData.customCSS);
                 }

Files Changed

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js+1/-1

Test Results

  • cargo fmt --all -- --check — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo test --workspace --lib — 0 failed
  • node --check app.js — clean

Manual smoke

  1. Owner sets a non-default theme on a board.
  2. Open the viewer share link in a new tab — board renders with the owner's theme on first paint.
  3. Same with ?present=1 — presentation tab loads with the correct theme.
  4. Editor /board/<id> — unchanged.
  5. Boards with no theme set → viewer falls through to default; no JS error.

Notes

  • The theme picker UI lives in the navbar, which is hidden on board_view.html via existing CSS. The viewer still has no way to change the theme — we only fixed displaying the owner's chosen theme.
## Implementation Summary One-line surgical fix: drop the `!readonlyMode` gate from initial theme loading in `app.js`. ### Root cause `WhiteboardApp.loadBoard` was skipping the theme load on initial render whenever the page was opened with `{ readonly: true }`, which is exactly the case for `board_view.html` (viewer share + presentation share). The live-update path in `sync.js::handleWsMessage` for `board.theme.updated` was not gated, which is why a *change* applied but the *initial* load did not. ### `crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js` ```diff - if (boardData && boardData.theme && !readonlyMode) { + if (boardData && boardData.theme) { WhiteboardThemes.loadTheme(boardData.theme, boardData.customCSS); } ``` ### Files Changed - `crates/hero_whiteboard_ui/static/web/js/whiteboard/app.js` — `+1/-1` ### Test Results - `cargo fmt --all -- --check` — clean - `cargo clippy --workspace --all-targets -- -D warnings` — clean - `cargo test --workspace --lib` — 0 failed - `node --check app.js` — clean ### Manual smoke 1. Owner sets a non-default theme on a board. 2. Open the viewer share link in a new tab — board renders with the owner's theme on first paint. 3. Same with `?present=1` — presentation tab loads with the correct theme. 4. Editor `/board/<id>` — unchanged. 5. Boards with no theme set → viewer falls through to default; no JS error. ### Notes - The theme picker UI lives in the navbar, which is hidden on `board_view.html` via existing CSS. The viewer still has no way to *change* the theme — we only fixed *displaying* the owner's chosen theme.
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#114
No description provided.