Whiteboard: theme-controlled chrome (grid, frame, document scrollbar) ignores the active theme #187

Open
opened 2026-05-14 10:33:03 +00:00 by AhmedHanafy725 · 2 comments
Member

Problem

Three pieces of whiteboard chrome are hard-coded to dark-theme colors and never follow the active theme:

Grid dots

static/web/js/whiteboard/canvas.js:138 — the grid sceneFunc paints with ctx.fillStyle = "#495057". The theme system already defines canvas-grid-color for every theme (Dark, Light, Ocean, Warm, Minimal), but the grid never reads it. On Light / Minimal the dark dots clash with the white background.

Frame chrome

static/web/js/whiteboard/objects.js:1110, 1120 — frames are created with stroke: "#495057" (dashed border) and fill: "#adb5bd" (title label). The theme system already defines frame-stroke and frame-label-color for every theme. These hex literals are never themed and applyTheme doesnt re-paint existing frames.

Document scrollbar + placeholder

static/web/js/whiteboard/objects.js:1803, 1839, 1851, 1870, 1871 — scrollbar track / thumb / hover and the empty-state placeholder text are hard-coded (#1e293b, #64748b, #94a3b8). No theme tokens exist for these yet.

Scope (deliberately narrow)

Fix ONLY chrome that has no user-facing color picker. User-picked colors on sticky / text / shape / drawing / document bg / document border / frame title text / kanban / mindmap stay as the user chose them — those are content, not chrome.

Approach

  1. Add doc-scrollbar-track, doc-scrollbar-thumb, doc-scrollbar-thumb-hover, doc-placeholder-text tokens to each theme in themes.js.
  2. Make the grid sceneFunc read WhiteboardThemes.get("canvas-grid-color") instead of the hex literal.
  3. Replace the hard-coded frame stroke / fill with the existing frame-stroke / frame-label-color tokens at creation.
  4. Replace the hard-coded document scrollbar + placeholder fills with the new tokens at creation.
  5. Extend applyTheme to walk every existing frame and document, re-applying the new chrome colors. The grid is already redrawn via WhiteboardCanvas.drawGrid() and just needs the sceneFunc fix to take effect.

Acceptance

  • Switching to Light / Minimal: grid dots are visible against the new background.
  • Switching themes: existing frame borders + title labels repaint to the new themes tokens.
  • Switching themes: existing document scrollbar track / thumb / hover and the empty-state placeholder repaint.
  • Sticky / text / shape / drawing / document bg+border / kanban / mindmap colors are NOT touched on theme switch (user-picked values preserved).
  • Multi-user broadcast unchanged — theme switch is a per-client preference, no per-object server update for the chrome repaint.
## Problem Three pieces of whiteboard chrome are hard-coded to dark-theme colors and never follow the active theme: ### Grid dots `static/web/js/whiteboard/canvas.js:138` — the grid sceneFunc paints with `ctx.fillStyle = "#495057"`. The theme system already defines `canvas-grid-color` for every theme (Dark, Light, Ocean, Warm, Minimal), but the grid never reads it. On Light / Minimal the dark dots clash with the white background. ### Frame chrome `static/web/js/whiteboard/objects.js:1110, 1120` — frames are created with `stroke: "#495057"` (dashed border) and `fill: "#adb5bd"` (title label). The theme system already defines `frame-stroke` and `frame-label-color` for every theme. These hex literals are never themed and `applyTheme` doesnt re-paint existing frames. ### Document scrollbar + placeholder `static/web/js/whiteboard/objects.js:1803, 1839, 1851, 1870, 1871` — scrollbar track / thumb / hover and the empty-state placeholder text are hard-coded (`#1e293b`, `#64748b`, `#94a3b8`). No theme tokens exist for these yet. ## Scope (deliberately narrow) Fix ONLY chrome that has no user-facing color picker. User-picked colors on sticky / text / shape / drawing / document bg / document border / frame title text / kanban / mindmap stay as the user chose them — those are content, not chrome. ## Approach 1. Add `doc-scrollbar-track`, `doc-scrollbar-thumb`, `doc-scrollbar-thumb-hover`, `doc-placeholder-text` tokens to each theme in `themes.js`. 2. Make the grid `sceneFunc` read `WhiteboardThemes.get("canvas-grid-color")` instead of the hex literal. 3. Replace the hard-coded frame `stroke` / `fill` with the existing `frame-stroke` / `frame-label-color` tokens at creation. 4. Replace the hard-coded document scrollbar + placeholder fills with the new tokens at creation. 5. Extend `applyTheme` to walk every existing frame and document, re-applying the new chrome colors. The grid is already redrawn via `WhiteboardCanvas.drawGrid()` and just needs the sceneFunc fix to take effect. ## Acceptance - [ ] Switching to Light / Minimal: grid dots are visible against the new background. - [ ] Switching themes: existing frame borders + title labels repaint to the new themes tokens. - [ ] Switching themes: existing document scrollbar track / thumb / hover and the empty-state placeholder repaint. - [ ] Sticky / text / shape / drawing / document bg+border / kanban / mindmap colors are NOT touched on theme switch (user-picked values preserved). - [ ] Multi-user broadcast unchanged — theme switch is a per-client preference, no per-object server update for the chrome repaint.
Author
Member

Implementation Spec for Issue #187

Files to Modify

  • static/web/js/whiteboard/themes.js
  • static/web/js/whiteboard/canvas.js
  • static/web/js/whiteboard/objects.js

Implementation Plan

Step 1: Add document-chrome tokens to every theme

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

Add the four new tokens to defaultTheme (Dark) and override per-theme in Light, Ocean, Warm, Minimal. The existing frame-stroke / frame-label-color / canvas-grid-color tokens are already present in every theme — reuse them.

Per-theme values:

  • Dark (defaultTheme): doc-scrollbar-track: '#1e293b', doc-scrollbar-thumb: '#64748b', doc-scrollbar-thumb-hover: '#94a3b8', doc-placeholder-text: '#64748b' (preserves today's look).
  • Light: doc-scrollbar-track: '#e5e7eb', doc-scrollbar-thumb: '#9ca3af', doc-scrollbar-thumb-hover: '#6b7280', doc-placeholder-text: '#9ca3af'.
  • Ocean: doc-scrollbar-track: '#1d3461', doc-scrollbar-thumb: '#8892b0', doc-scrollbar-thumb-hover: '#ccd6f6', doc-placeholder-text: '#8892b0'.
  • Warm: doc-scrollbar-track: '#33271d', doc-scrollbar-thumb: '#a08060', doc-scrollbar-thumb-hover: '#e8d5c4', doc-placeholder-text: '#a08060'.
  • Minimal: doc-scrollbar-track: '#f3f4f6', doc-scrollbar-thumb: '#9ca3af', doc-scrollbar-thumb-hover: '#6b7280', doc-placeholder-text: '#9ca3af'.

Step 2: Make the grid sceneFunc read the active token

File: static/web/js/whiteboard/canvas.js (~line 138)

Replace ctx.fillStyle = '#495057'; with:

ctx.fillStyle = (typeof WhiteboardThemes !== 'undefined' && WhiteboardThemes.get)
    ? (WhiteboardThemes.get('canvas-grid-color') || '#495057')
    : '#495057';

drawGrid() is already invoked by applyTheme (themes.js:469), so theme switch will re-paint the grid automatically.

Step 3: Use theme tokens at frame creation

File: static/web/js/whiteboard/objects.js (~lines 1108-1124)

Replace the hard-coded stroke: '#495057' and fill: '#adb5bd' with:

stroke: WhiteboardThemes.get('frame-stroke') || '#495057',
fill: WhiteboardThemes.get('frame-label-color') || '#adb5bd',

Step 4: Use theme tokens at document scrollbar + placeholder creation

File: static/web/js/whiteboard/objects.js

  • :1803 placeholder fill: WhiteboardThemes.get('doc-placeholder-text') || '#64748b'.
  • :1839 track fill: WhiteboardThemes.get('doc-scrollbar-track') || '#1e293b'.
  • :1851 thumb fill: WhiteboardThemes.get('doc-scrollbar-thumb') || '#64748b'.
  • :1870-1871 thumb hover / leave fills: use the hover token / thumb token respectively.

Step 5: Extend applyTheme to repaint existing frames + documents

File: static/web/js/whiteboard/themes.js — extend the object-walk in applyTheme (~lines 474-487).

Add branches:

} else if (entry.type === 'frame') {
    var bg = entry.group.findOne('.bg');
    var lbl = entry.group.findOne('.label');
    if (bg) bg.stroke(currentTheme['frame-stroke']);
    if (lbl) lbl.fill(currentTheme['frame-label-color']);
} else if (entry.type === 'document' && typeof WhiteboardObjects !== 'undefined' && WhiteboardObjects.rerenderDocument) {
    WhiteboardObjects.rerenderDocument(entry.group);
}

rerenderDocument (objects.js:1882) rebuilds the scrollbar + placeholder from scratch with the new tokens. It also calls WhiteboardSync.onUpdate(group) — which is undesirable for a per-client theme switch (we'd broadcast a same-content object.updated to other users). Guard the call with the _applyingSync flag, OR call renderDocumentContent directly instead (it's the underlying renderer without the broadcast). The latter is cleaner:

} else if (entry.type === 'document' && typeof WhiteboardObjects !== 'undefined' && WhiteboardObjects.renderDocumentContent) {
    WhiteboardObjects.renderDocumentContent(entry.group);
    WhiteboardCanvas.getObjectLayer().batchDraw();
}

If renderDocumentContent isn't exported, fall back to rerenderDocument and accept the extra broadcast (cheap, idempotent on receivers).

After all branches, call WhiteboardCanvas.getObjectLayer().batchDraw() once.

Acceptance Criteria

  • Switching to Light / Minimal: grid dots visible against the light background.
  • Switching themes: existing frame borders + title labels repaint to the new theme's tokens.
  • Switching themes: existing document scrollbar + placeholder repaint to the new theme's tokens.
  • Sticky / text / shape / drawing / document bg+border / kanban / mindmap user-picked colors are NOT touched.
  • No per-object server object.updated for the chrome repaint (theme switch stays a client-only preference for chrome).

Notes

  • Tokens added are dark-theme-equivalent for the Dark theme so the default look doesn't change.
  • For the document scrollbar, we re-render rather than mutating individual Konva node fills because the scrollbar nodes may or may not exist depending on overflow.
  • The Konva default-fallback hex literals are kept inline (|| '#495057') so WhiteboardThemes.get returning undefined at boot doesn't break.
## Implementation Spec for Issue #187 ### Files to Modify - `static/web/js/whiteboard/themes.js` - `static/web/js/whiteboard/canvas.js` - `static/web/js/whiteboard/objects.js` ### Implementation Plan #### Step 1: Add document-chrome tokens to every theme File: `static/web/js/whiteboard/themes.js` Add the four new tokens to `defaultTheme` (Dark) and override per-theme in `Light`, `Ocean`, `Warm`, `Minimal`. The existing `frame-stroke` / `frame-label-color` / `canvas-grid-color` tokens are already present in every theme — reuse them. Per-theme values: - **Dark** (`defaultTheme`): `doc-scrollbar-track: '#1e293b'`, `doc-scrollbar-thumb: '#64748b'`, `doc-scrollbar-thumb-hover: '#94a3b8'`, `doc-placeholder-text: '#64748b'` (preserves today's look). - **Light**: `doc-scrollbar-track: '#e5e7eb'`, `doc-scrollbar-thumb: '#9ca3af'`, `doc-scrollbar-thumb-hover: '#6b7280'`, `doc-placeholder-text: '#9ca3af'`. - **Ocean**: `doc-scrollbar-track: '#1d3461'`, `doc-scrollbar-thumb: '#8892b0'`, `doc-scrollbar-thumb-hover: '#ccd6f6'`, `doc-placeholder-text: '#8892b0'`. - **Warm**: `doc-scrollbar-track: '#33271d'`, `doc-scrollbar-thumb: '#a08060'`, `doc-scrollbar-thumb-hover: '#e8d5c4'`, `doc-placeholder-text: '#a08060'`. - **Minimal**: `doc-scrollbar-track: '#f3f4f6'`, `doc-scrollbar-thumb: '#9ca3af'`, `doc-scrollbar-thumb-hover: '#6b7280'`, `doc-placeholder-text: '#9ca3af'`. #### Step 2: Make the grid sceneFunc read the active token File: `static/web/js/whiteboard/canvas.js` (~line 138) Replace `ctx.fillStyle = '#495057';` with: ```js ctx.fillStyle = (typeof WhiteboardThemes !== 'undefined' && WhiteboardThemes.get) ? (WhiteboardThemes.get('canvas-grid-color') || '#495057') : '#495057'; ``` `drawGrid()` is already invoked by `applyTheme` (`themes.js:469`), so theme switch will re-paint the grid automatically. #### Step 3: Use theme tokens at frame creation File: `static/web/js/whiteboard/objects.js` (~lines 1108-1124) Replace the hard-coded `stroke: '#495057'` and `fill: '#adb5bd'` with: ```js stroke: WhiteboardThemes.get('frame-stroke') || '#495057', fill: WhiteboardThemes.get('frame-label-color') || '#adb5bd', ``` #### Step 4: Use theme tokens at document scrollbar + placeholder creation File: `static/web/js/whiteboard/objects.js` - `:1803` placeholder `fill`: `WhiteboardThemes.get('doc-placeholder-text') || '#64748b'`. - `:1839` track `fill`: `WhiteboardThemes.get('doc-scrollbar-track') || '#1e293b'`. - `:1851` thumb `fill`: `WhiteboardThemes.get('doc-scrollbar-thumb') || '#64748b'`. - `:1870-1871` thumb hover / leave fills: use the hover token / thumb token respectively. #### Step 5: Extend `applyTheme` to repaint existing frames + documents File: `static/web/js/whiteboard/themes.js` — extend the object-walk in `applyTheme` (~lines 474-487). Add branches: ```js } else if (entry.type === 'frame') { var bg = entry.group.findOne('.bg'); var lbl = entry.group.findOne('.label'); if (bg) bg.stroke(currentTheme['frame-stroke']); if (lbl) lbl.fill(currentTheme['frame-label-color']); } else if (entry.type === 'document' && typeof WhiteboardObjects !== 'undefined' && WhiteboardObjects.rerenderDocument) { WhiteboardObjects.rerenderDocument(entry.group); } ``` `rerenderDocument` (`objects.js:1882`) rebuilds the scrollbar + placeholder from scratch with the new tokens. It also calls `WhiteboardSync.onUpdate(group)` — which is undesirable for a per-client theme switch (we'd broadcast a same-content `object.updated` to other users). Guard the call with the `_applyingSync` flag, OR call `renderDocumentContent` directly instead (it's the underlying renderer without the broadcast). The latter is cleaner: ```js } else if (entry.type === 'document' && typeof WhiteboardObjects !== 'undefined' && WhiteboardObjects.renderDocumentContent) { WhiteboardObjects.renderDocumentContent(entry.group); WhiteboardCanvas.getObjectLayer().batchDraw(); } ``` If `renderDocumentContent` isn't exported, fall back to `rerenderDocument` and accept the extra broadcast (cheap, idempotent on receivers). After all branches, call `WhiteboardCanvas.getObjectLayer().batchDraw()` once. ### Acceptance Criteria - [ ] Switching to Light / Minimal: grid dots visible against the light background. - [ ] Switching themes: existing frame borders + title labels repaint to the new theme's tokens. - [ ] Switching themes: existing document scrollbar + placeholder repaint to the new theme's tokens. - [ ] Sticky / text / shape / drawing / document bg+border / kanban / mindmap user-picked colors are NOT touched. - [ ] No per-object server `object.updated` for the chrome repaint (theme switch stays a client-only preference for chrome). ### Notes - Tokens added are dark-theme-equivalent for the Dark theme so the default look doesn't change. - For the document scrollbar, we re-render rather than mutating individual Konva node fills because the scrollbar nodes may or may not exist depending on overflow. - The Konva default-fallback hex literals are kept inline (`|| '#495057'`) so `WhiteboardThemes.get` returning undefined at boot doesn't break.
Author
Member

Test Results + Final Summary

Changes

  • static/web/js/whiteboard/themes.js:
    • Added doc-scrollbar-track, doc-scrollbar-thumb, doc-scrollbar-thumb-hover, doc-placeholder-text to defaultTheme (Dark) and to Light / Ocean / Warm / Minimal overrides.
    • Extended applyTheme's object walk with two new branches: frame (repaints .bg stroke and .label fill from the active theme) and document (calls renderDocumentContent to rebuild scrollbar + placeholder).
  • static/web/js/whiteboard/canvas.js:
    • Grid sceneFunc reads WhiteboardThemes.get('canvas-grid-color') with a hex fallback.
  • static/web/js/whiteboard/objects.js:
    • Frame creation uses frame-stroke and frame-label-color tokens.
    • Document placeholder, scrollbar track, scrollbar thumb, and hover fill use the new tokens.
    • Exported renderDocumentContent so applyTheme can rebuild document chrome without going through rerenderDocument (which would broadcast object.updated).

Behaviour after fix

  • Switching themes repaints: grid dots, frame dashed border, frame title label, document scrollbar track, scrollbar thumb (incl. hover), document placeholder text.
  • User-picked colors (sticky bg, text fill, shape stroke/fill, drawing stroke, document bg+border, frame title text, kanban column color, mindmap tree color) are untouched.
  • Theme switch fires no per-object object.updated broadcast — chrome repaint is a per-client preference.

Gates

  • node -c themes.js / canvas.js / objects.js — JS syntax OK
  • cargo fmt --check — pass
  • cargo clippy --workspace --all-targets -- -D warnings — pass

Manual verification still required

Rebuild + restart hero_whiteboard_admin, hard-reload a board with stickies, frames, and documents:

  1. Switch from Dark → Light → Minimal → Ocean → Warm. Confirm grid, frame border, frame label, document scrollbar, and placeholder all repaint each switch.
  2. Confirm sticky colors, text colors, shape stroke/fill, drawing color, kanban column colors, mindmap tree color all stay as the user originally picked them.
## Test Results + Final Summary ### Changes - `static/web/js/whiteboard/themes.js`: - Added `doc-scrollbar-track`, `doc-scrollbar-thumb`, `doc-scrollbar-thumb-hover`, `doc-placeholder-text` to `defaultTheme` (Dark) and to Light / Ocean / Warm / Minimal overrides. - Extended `applyTheme`'s object walk with two new branches: `frame` (repaints `.bg` stroke and `.label` fill from the active theme) and `document` (calls `renderDocumentContent` to rebuild scrollbar + placeholder). - `static/web/js/whiteboard/canvas.js`: - Grid `sceneFunc` reads `WhiteboardThemes.get('canvas-grid-color')` with a hex fallback. - `static/web/js/whiteboard/objects.js`: - Frame creation uses `frame-stroke` and `frame-label-color` tokens. - Document placeholder, scrollbar track, scrollbar thumb, and hover fill use the new tokens. - Exported `renderDocumentContent` so `applyTheme` can rebuild document chrome without going through `rerenderDocument` (which would broadcast `object.updated`). ### Behaviour after fix - Switching themes repaints: grid dots, frame dashed border, frame title label, document scrollbar track, scrollbar thumb (incl. hover), document placeholder text. - User-picked colors (sticky bg, text fill, shape stroke/fill, drawing stroke, document bg+border, frame title text, kanban column color, mindmap tree color) are untouched. - Theme switch fires no per-object `object.updated` broadcast — chrome repaint is a per-client preference. ### Gates - `node -c themes.js / canvas.js / objects.js` — JS syntax OK - `cargo fmt --check` — pass - `cargo clippy --workspace --all-targets -- -D warnings` — pass ### Manual verification still required Rebuild + restart `hero_whiteboard_admin`, hard-reload a board with stickies, frames, and documents: 1. Switch from Dark → Light → Minimal → Ocean → Warm. Confirm grid, frame border, frame label, document scrollbar, and placeholder all repaint each switch. 2. Confirm sticky colors, text colors, shape stroke/fill, drawing color, kanban column colors, mindmap tree color all stay as the user originally picked them.
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#187
No description provided.