Skip to content

feat(dashboard): add light/dark theme toggle#616

Open
scotthamilton77 wants to merge 3 commits intogastownhall:mainfrom
scotthamilton77:feat/dashboard-light-theme
Open

feat(dashboard): add light/dark theme toggle#616
scotthamilton77 wants to merge 3 commits intogastownhall:mainfrom
scotthamilton77:feat/dashboard-light-theme

Conversation

@scotthamilton77
Copy link
Copy Markdown

Summary

  • Adds a ☀/🌙 toggle button to the dashboard header, next to the Commands button
  • Clicking switches between dark (default) and light mode
  • Preference is saved to localStorage under key gc-dashboard-theme and restored on page load
  • Light theme uses a clean neutral palette with darkened accent colours for readability at full luminance

Changes

  • dashboard.css — adds [data-theme="light"] CSS custom property overrides (19 lines); dark palette unchanged as default
  • convoy.html — adds toggle button with aria-label and title to the header flex row
  • dashboard.js — theme init on load (reads localStorage before first paint), delegated click handler, localStorage persistence with silent fallback

Testing

  • go test ./cmd/gc/dashboard/... — all 18 tests pass
  • go build ./cmd/gc/... — clean
  • Manual verification: toggle dark→light→dark round-trip, localStorage persistence across page reload

🤖 Generated with Claude Code

scotthamilton77 and others added 2 commits April 11, 2026 12:59
…tence

Adds a theme toggle button to the dashboard header. Clicking switches
between dark (default) and light mode; the preference is saved to
localStorage and restored on page load.

Changes:
- dashboard.css: add [data-theme="light"] CSS variable overrides
- convoy.html: add toggle button alongside Commands button in header
- dashboard.js: theme init on load (reads localStorage), click handler,
  localStorage persistence with graceful fallback

Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]>
Copilot AI review requested due to automatic review settings April 11, 2026 17:07
@github-actions github-actions bot added the status/needs-triage Inbox — we haven't looked at it yet label Apr 11, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a light/dark theme toggle to the GC dashboard UI, persisting the user’s preference in localStorage and applying a light color palette via CSS variable overrides.

Changes:

  • Add a header toggle button for switching between dark (default) and light themes.
  • Add JS to apply/persist theme selection and update the toggle icon.
  • Add CSS custom property overrides under [data-theme="light"] for the light palette.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 3 comments.

File Description
cmd/gc/dashboard/templates/convoy.html Adds the theme toggle button/icon to the dashboard header.
cmd/gc/dashboard/static/dashboard.js Implements theme apply/toggle logic and persistence.
cmd/gc/dashboard/static/dashboard.css Adds light-theme CSS variable overrides.
.gitignore Adds ignore rules for .sidekick/* and .worktrees/ (unrelated to theme toggle).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cmd/gc/dashboard/static/dashboard.js Outdated
Comment on lines +181 to +183
var saved = localStorage.getItem(THEME_STORAGE_KEY);
if (saved === 'light') {
applyTheme('light');
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localStorage.getItem(...) is called without a try/catch. In environments where storage access is blocked (e.g., privacy mode / disabled storage), this can throw and abort the rest of dashboard.js execution (breaking SSE + UI handlers). Wrap the read in the same try/catch pattern used by loadRecentCommands() and fall back to default theme when unavailable.

Suggested change
var saved = localStorage.getItem(THEME_STORAGE_KEY);
if (saved === 'light') {
applyTheme('light');
try {
var saved = localStorage.getItem(THEME_STORAGE_KEY);
if (saved === 'light') {
applyTheme('light');
}
} catch (_) {
// localStorage unavailable, fall back to default theme

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in fb7ec9c — the IIFE now wraps localStorage.getItem in a try/catch, matching the pattern used by loadRecentCommands. Falls back silently to default (dark) theme.

Comment thread cmd/gc/dashboard/static/dashboard.js Outdated
Comment on lines +179 to +185
// Apply saved theme immediately on load (before first paint if possible).
(function() {
var saved = localStorage.getItem(THEME_STORAGE_KEY);
if (saved === 'light') {
applyTheme('light');
}
})();
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This init runs from /static/dashboard.js which is loaded at the end of <body> in convoy.html, so it will generally execute after the first paint. If the intent is to avoid a dark→light flash for users with a saved light preference, consider moving a minimal theme bootstrap (read storage + set data-theme) into an inline <script> in the document <head> before rendering, and keep this block for icon wiring/click handling.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in fb7ec9c — added an inline <script> in <head> (before the stylesheet) that reads localStorage and sets data-theme before first paint. The JS block in dashboard.js is now responsible only for icon wiring and click handling, not initial theme application.

Comment on lines +171 to +176
function applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
var icon = document.getElementById('theme-toggle-icon');
if (icon) {
icon.textContent = theme === 'light' ? '🌙' : '☀';
}
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applyTheme() updates the toggle icon, but the header (including #theme-toggle-icon) lives inside #dashboard-main which is periodically replaced via HTMX morph. After a swap, the server-rendered icon will revert to the template default (☀) even if data-theme is still light. To keep UI consistent, re-apply the current theme (or at least re-sync the icon) in the existing htmx:afterSwap handler when target.id === 'dashboard-main'.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in fb7ec9c — added a call to applyTheme() at the top of the existing htmx:afterSwap handler that's already scoped to #dashboard-main. This re-syncs the icon (and data-theme) after each morph without touching partial-swap paths.

- Wrap localStorage.getItem in try/catch to handle privacy mode/blocked
  storage without aborting JS execution
- Add inline <script> in <head> to apply saved theme before first paint,
  preventing dark->light flash for returning light-mode users
- Re-sync theme icon in htmx:afterSwap handler for #dashboard-main so
  morph replacements don't revert the icon to the template default

Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]>
@julianknutsen julianknutsen added priority/p3 Backlog — good idea, reviewed when there's bandwidth kind/feature New capability and removed status/needs-triage Inbox — we haven't looked at it yet labels Apr 13, 2026
@julianknutsen julianknutsen added this to the 1.0+ milestone Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature New capability priority/p3 Backlog — good idea, reviewed when there's bandwidth

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants