New board: theme picker shows Dark selected but the board renders Light #121

Open
opened 2026-04-30 11:54:55 +00:00 by AhmedHanafy725 · 3 comments
Member

Summary

Open a brand new board. The board content area renders in the Light theme (white canvas, light navbar) but the theme picker in the navbar marks Dark as the currently selected theme. The picker label and the actual rendered state disagree until the user picks any theme manually.

Steps to reproduce

  1. From the home page, create a new board.
  2. The board renders Light (white canvas + chrome).
  3. Click the theme icon — Dark is highlighted as the current selection.

Expected

The picker reflects what is actually rendered on screen, with no manual click required.

Actual

Picker says Dark, board says Light.

Root cause

crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js initializes:

var currentThemeName = 'Dark';

init() builds the picker UI but never calls applyTheme(currentThemeName). The picker reads currentThemeName to decide which row is highlighted, so it shows Dark. Meanwhile the chrome/canvas have nothing applied — they fall back to the CSS rule :root:not([data-bs-theme="dark"]) (Light) and to the unset canvas container background.

app.js::loadBoard only calls WhiteboardThemes.loadTheme(...) if boardData.theme is set (i.e. the board has a previously-saved theme). For a brand-new board there is no saved theme, so applyTheme is never called, and the default-theme name and the rendered state diverge.

Fix

Call applyTheme(currentThemeName, { _fromSync: true }) at the end of init() so the chrome/canvas always reflect the named default on first paint. The _fromSync flag suppresses the save-to-server and broadcast so this initial application doesn't spam the network. loadBoard's subsequent loadTheme call still overrides correctly when a saved theme exists.

## Summary Open a brand new board. The board content area renders in the Light theme (white canvas, light navbar) but the theme picker in the navbar marks **Dark** as the currently selected theme. The picker label and the actual rendered state disagree until the user picks any theme manually. ## Steps to reproduce 1. From the home page, create a new board. 2. The board renders Light (white canvas + chrome). 3. Click the theme icon — Dark is highlighted as the current selection. ## Expected The picker reflects what is actually rendered on screen, with no manual click required. ## Actual Picker says Dark, board says Light. ## Root cause `crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js` initializes: ```js var currentThemeName = 'Dark'; ``` `init()` builds the picker UI but never calls `applyTheme(currentThemeName)`. The picker reads `currentThemeName` to decide which row is highlighted, so it shows Dark. Meanwhile the chrome/canvas have nothing applied — they fall back to the CSS rule `:root:not([data-bs-theme="dark"])` (Light) and to the unset canvas container background. `app.js::loadBoard` only calls `WhiteboardThemes.loadTheme(...)` if `boardData.theme` is set (i.e. the board has a previously-saved theme). For a brand-new board there is no saved theme, so `applyTheme` is never called, and the default-theme name and the rendered state diverge. ## Fix Call `applyTheme(currentThemeName, { _fromSync: true })` at the end of `init()` so the chrome/canvas always reflect the named default on first paint. The `_fromSync` flag suppresses the save-to-server and broadcast so this initial application doesn't spam the network. `loadBoard`'s subsequent `loadTheme` call still overrides correctly when a saved theme exists.
Author
Member

Implementation Spec for Issue #121

Objective

Make the theme picker's "selected" indicator and the actually-rendered state agree on a brand-new board with no saved theme.

Root cause

themes.js::init() builds the picker UI but never applies the default theme. The picker reads currentThemeName (initialized to 'Dark') to decide which row to highlight, while the chrome/canvas have no data-bs-theme attribute set and no canvas-bg styled, so they fall back to the Light CSS rule.

Files to Modify

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js — apply currentThemeName at the end of init() with _fromSync: true so the initial paint matches the picker without saving or broadcasting.

Implementation Plan

Step 1: Apply default theme at init

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

At the end of init() (after the picker UI is built), add:

// Apply the default theme on startup so the chrome and canvas agree
// with the picker's "selected" indicator. _fromSync skips the
// save-to-server and the WS broadcast, which would be wrong on a
// passive page load. loadBoard runs after init and will override
// with a saved theme if the board has one.
applyTheme(currentThemeName, { _fromSync: true });

Dependencies: none. WhiteboardCanvas.init runs before WhiteboardThemes.init in app.js::init, so the canvas exists by the time applyTheme runs.

Acceptance Criteria

  • Create a brand-new board → chrome and canvas render with the default Dark palette on first paint, picker highlights Dark — they agree.
  • Open a board with a saved Light/Ocean/Warm theme → loadBoard's subsequent loadTheme overrides the default correctly; picker and render still agree.
  • No extra board.theme.updated broadcast appears in the WebSocket logs on initial page load.
  • No regression on theme switching (the picker click path still saves and broadcasts as before).
  • cargo fmt, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace --lib clean.

Notes

  • Why _fromSync: true: a passive page load should not write back to the server or broadcast a "theme changed" event over WebSocket. Only an explicit user pick (or a remote pick mirrored via the WS) should do that. Existing receiver paths (sync.js's board.theme.updated handler) already use _fromSync: true; init() should match.
  • loadBoard's loadTheme(...) call without _fromSync is a related minor inefficiency (it re-saves and re-broadcasts the same theme on every page load), but it does not affect the user-visible bug here. Out of scope.
## Implementation Spec for Issue #121 ### Objective Make the theme picker's "selected" indicator and the actually-rendered state agree on a brand-new board with no saved theme. ### Root cause `themes.js::init()` builds the picker UI but never applies the default theme. The picker reads `currentThemeName` (initialized to `'Dark'`) to decide which row to highlight, while the chrome/canvas have no `data-bs-theme` attribute set and no canvas-bg styled, so they fall back to the Light CSS rule. ### Files to Modify - `crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js` — apply `currentThemeName` at the end of `init()` with `_fromSync: true` so the initial paint matches the picker without saving or broadcasting. ### Implementation Plan #### Step 1: Apply default theme at init File: `crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js` At the end of `init()` (after the picker UI is built), add: ```js // Apply the default theme on startup so the chrome and canvas agree // with the picker's "selected" indicator. _fromSync skips the // save-to-server and the WS broadcast, which would be wrong on a // passive page load. loadBoard runs after init and will override // with a saved theme if the board has one. applyTheme(currentThemeName, { _fromSync: true }); ``` Dependencies: none. `WhiteboardCanvas.init` runs before `WhiteboardThemes.init` in `app.js::init`, so the canvas exists by the time `applyTheme` runs. ### Acceptance Criteria - [ ] Create a brand-new board → chrome and canvas render with the default Dark palette on first paint, picker highlights Dark — they agree. - [ ] Open a board with a saved Light/Ocean/Warm theme → loadBoard's subsequent loadTheme overrides the default correctly; picker and render still agree. - [ ] No extra `board.theme.updated` broadcast appears in the WebSocket logs on initial page load. - [ ] No regression on theme switching (the picker click path still saves and broadcasts as before). - [ ] `cargo fmt`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace --lib` clean. ### Notes - **Why `_fromSync: true`**: a passive page load should not write back to the server or broadcast a "theme changed" event over WebSocket. Only an explicit user pick (or a remote pick mirrored via the WS) should do that. Existing receiver paths (sync.js's `board.theme.updated` handler) already use `_fromSync: true`; init() should match. - **`loadBoard`'s `loadTheme(...)` call without `_fromSync`** is a related minor inefficiency (it re-saves and re-broadcasts the same theme on every page load), but it does not affect the user-visible bug here. Out of scope.
Author
Member

Implementation Summary

One-line addition in themes.js::init() so the rendered state matches the picker label on a brand-new board.

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

function init() {
    buildThemeSelector();
    // Apply the default theme on startup so the chrome and canvas agree
    // with the picker's "selected" indicator on a brand-new board.
    applyTheme(currentThemeName, { _fromSync: true });
}

_fromSync: true suppresses the save-to-server and WS broadcast that applyTheme would otherwise do, since this is a passive page load, not a user pick. loadBoard runs after init and still overrides with a saved theme via loadTheme(...) when the board has one.

Files Changed

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js+7 / 0

Test Results

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

Manual smoke

  1. Create a brand-new board → chrome and canvas render with the Dark palette on first paint, picker highlights Dark — they agree.
  2. Open a board with a saved Light/Ocean/Warm theme → loadBoard's subsequent loadTheme overrides the default; picker and render still agree.
  3. No extra board.theme.updated broadcast in WS logs on a passive page load.

Notes

  • A separate minor inefficiency exists where loadBoard's loadTheme(...) call is missing _fromSync: true, so loading a board with a saved theme re-saves and re-broadcasts the same theme. Out of scope here; it doesn't affect the user-visible bug.
## Implementation Summary One-line addition in `themes.js::init()` so the rendered state matches the picker label on a brand-new board. ### `crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js` ```js function init() { buildThemeSelector(); // Apply the default theme on startup so the chrome and canvas agree // with the picker's "selected" indicator on a brand-new board. applyTheme(currentThemeName, { _fromSync: true }); } ``` `_fromSync: true` suppresses the save-to-server and WS broadcast that `applyTheme` would otherwise do, since this is a passive page load, not a user pick. `loadBoard` runs after init and still overrides with a saved theme via `loadTheme(...)` when the board has one. ### Files Changed - `crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js` — `+7 / 0` ### Test Results - `cargo fmt --all -- --check` — clean - `cargo clippy --workspace --all-targets -- -D warnings` — clean - `cargo test --workspace --lib` — 0 failed - `node --check themes.js` — clean ### Manual smoke 1. Create a brand-new board → chrome and canvas render with the Dark palette on first paint, picker highlights Dark — they agree. 2. Open a board with a saved Light/Ocean/Warm theme → loadBoard's subsequent loadTheme overrides the default; picker and render still agree. 3. No extra `board.theme.updated` broadcast in WS logs on a passive page load. ### Notes - A separate minor inefficiency exists where `loadBoard`'s `loadTheme(...)` call is missing `_fromSync: true`, so loading a board with a saved theme re-saves and re-broadcasts the same theme. Out of scope here; it doesn't affect the user-visible bug.
Author
Member

Iteration: keep Light as the default

Changed the init() call to apply Light explicitly:

applyTheme('Light', { _fromSync: true });

Reasoning: the chrome and canvas were already rendering Light by default (CSS fallback when no data-bs-theme="dark" is set), so the picker should agree with what is actually on screen. New boards continue to look light; only the picker indicator now shows Light highlighted instead of Dark.

## Iteration: keep Light as the default Changed the init() call to apply Light explicitly: ```js applyTheme('Light', { _fromSync: true }); ``` Reasoning: the chrome and canvas were already rendering Light by default (CSS fallback when no `data-bs-theme="dark"` is set), so the picker should agree with what is actually on screen. New boards continue to look light; only the picker indicator now shows Light highlighted instead of Dark.
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#121
No description provided.