Skip to content

Workspace theme independent from document themes#59

Open
sushilbang wants to merge 2 commits intosohzm:masterfrom
sushilbang:theme-fixes
Open

Workspace theme independent from document themes#59
sushilbang wants to merge 2 commits intosohzm:masterfrom
sushilbang:theme-fixes

Conversation

@sushilbang
Copy link
Contributor

@sushilbang sushilbang commented Jan 7, 2026

Summary

  • Each workspace now stores its own theme preference
  • Switching workspaces automatically applies that workspace's theme
  • Documents use workspace theme as fallback when their theme is "default" or empty
  • New workspaces inherit the current theme

Summary by CodeRabbit

  • New Features
    • Workspace-level theme preferences now persist across sessions
    • Document theme application falls back to workspace theme when not explicitly set
    • Theme selector updates reflect global theme changes in real-time

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Jan 7, 2026

@sushilbang is attempting to deploy a commit to the sz47's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

📝 Walkthrough

Walkthrough

This PR implements workspace-level theme persistence by introducing methods to store and retrieve workspace-specific themes in localStorage, updating the editor to use workspace theme as a fallback when document theme is not explicitly set, and extending the sidebar to sync theme changes across the workspace.

Changes

Cohort / File(s) Summary
Workspace Theme Persistence
js/theme/theme.js
Added two new public methods: getWorkspaceTheme() reads workspace theme from localStorage for the current workspace; setWorkspaceTheme(themeName) persists the theme to localStorage for the current workspace.
Theme Fallback Logic & Sidebar Integration
js/editor.js, js/scripts/home-sidebar.js
Editor now applies workspace theme as fallback when document theme is falsy or 'default'. Sidebar theme selector now calls setWorkspaceTheme() and listens for wisk-theme-changed events to update the selector. New workspace creation payload includes a theme property, and workspace loading applies the stored theme via setTheme().

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Sidebar as Home Sidebar
    participant Theme as Theme Manager
    participant Storage as localStorage
    participant Editor as Editor

    User->>Sidebar: Change theme selector
    Sidebar->>Theme: setWorkspaceTheme(themeName)
    Theme->>Storage: Read/update workspaces + persist
    Theme->>Sidebar: Emit wisk-theme-changed event
    Sidebar->>Sidebar: Update selector
    
    rect rgb(230, 245, 250)
    Note over Editor,Storage: Editor Initialization
    Editor->>Theme: getWorkspaceTheme()
    Theme->>Storage: Read current workspace theme
    Theme-->>Editor: Return workspace theme
    Editor->>Editor: Compute themeToApply<br/>(doc theme or workspace fallback)
    Editor->>Theme: setTheme(themeToApply)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 With whiskers twitched and ears held high,
I code the themes that catch the eye!
From workspace deep to localStorage wide,
Now every theme gets a proper ride.
Light and dark in harmony stay,
As fallback themes save the day! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Workspace theme independent from document themes' directly and clearly summarizes the main objective of the changeset: enabling workspaces to maintain their own theme preferences separate from document themes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @js/theme/theme.js:
- Around line 121-133: The function wisk.theme.setWorkspaceTheme should guard
against corrupted localStorage and bad inputs: wrap
JSON.parse(localStorage.getItem('workspaces')) in a try/catch and handle parse
errors (log/return) instead of throwing; validate that parsed &&
Array.isArray(parsed.workspaces) before accessing .findIndex; validate themeName
(e.g., ensure typeof themeName === 'string' and not empty, or check against an
allowed themes list if available) and return/log on invalid input; also handle
missing currentWorkspace or workspaceIndex === -1 by logging/returning rather
than attempting to set parsed.workspaces[workspaceIndex].theme; finally only
call localStorage.setItem when all checks pass.
- Around line 110-119: The getWorkspaceTheme function lacks JSON parse error
handling and structure validation; wrap JSON.parse of
localStorage.getItem('workspaces') in a try/catch, return null on parse errors
(and optionally log the error), then ensure the parsed value has a workspaces
array before calling parsed.workspaces.find; also validate currentWorkspaceId
exists before searching and safely handle the case where find returns undefined
so the function returns null rather than throwing (refer to
wisk.theme.getWorkspaceTheme and variables workspacesData, parsed,
currentWorkspaceId, workspace).
🧹 Nitpick comments (1)
js/editor.js (1)

547-551: Consider simplifying the theme fallback logic for clarity.

The current nested ternary works correctly but is somewhat difficult to parse. The logic chain is: document theme → workspace theme → 'default'. Consider refactoring for readability.

♻️ Proposed refactor for clarity
-    // Use workspace theme as fallback if document theme is 'default' or empty
-    const docTheme = doc.data.config.theme;
-    const workspaceTheme = wisk.theme.getWorkspaceTheme();
-    const themeToApply = (!docTheme || docTheme === 'default') ? (workspaceTheme || docTheme) : docTheme;
-    wisk.theme.setTheme(themeToApply);
+    // Apply theme with fallback: document theme → workspace theme → 'default'
+    const docTheme = doc.data.config.theme;
+    let themeToApply = docTheme;
+    
+    // If document has no explicit theme, fall back to workspace theme
+    if (!docTheme || docTheme === 'default') {
+        const workspaceTheme = wisk.theme.getWorkspaceTheme();
+        themeToApply = workspaceTheme || docTheme || 'default';
+    }
+    
+    wisk.theme.setTheme(themeToApply);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1825148 and 327c686.

📒 Files selected for processing (3)
  • js/editor.js
  • js/scripts/home-sidebar.js
  • js/theme/theme.js
🧰 Additional context used
🧬 Code graph analysis (2)
js/scripts/home-sidebar.js (3)
js/wisk.js (1)
  • wisk (3-3)
js/theme/theme.js (1)
  • workspace (116-116)
js/storage/db.js (1)
  • workspace (247-247)
js/editor.js (1)
js/wisk.js (1)
  • wisk (3-3)
🔇 Additional comments (4)
js/scripts/home-sidebar.js (4)

79-80: LGTM - Workspace theme persistence.

The call to setWorkspaceTheme correctly persists the theme change to the workspace after applying it. The order is appropriate: first apply the theme with setTheme, then persist it with setWorkspaceTheme.


82-85: LGTM - Theme change synchronization.

The event listener keeps the theme selector UI in sync when the theme changes from other sources. This ensures the dropdown always reflects the current theme.


310-310: LGTM - New workspace inherits current theme.

New workspaces correctly inherit the current theme with a sensible 'Light' fallback. This ensures a consistent user experience.


123-126: No conflict exists between home sidebar and editor theme application—they operate in separate execution contexts.

The home sidebar theme applies only on the home page view (when no document ID is present), while the editor theme applies when loading a specific document. These are distinct code paths that never execute together.

Likely an incorrect or invalid review comment.

Comment on lines +110 to +119
wisk.theme.getWorkspaceTheme = function () {
const workspacesData = localStorage.getItem('workspaces');
if (!workspacesData) return null;

const parsed = JSON.parse(workspacesData);
const currentWorkspaceId = localStorage.getItem('currentWorkspace');
const workspace = parsed.workspaces.find(w => w.id === currentWorkspaceId);

return workspace?.theme || null;
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add error handling for JSON parsing and data structure validation.

The function lacks error handling for JSON.parse() and doesn't validate that parsed.workspaces exists before accessing it. If localStorage data is corrupted or has an unexpected structure, this will throw a runtime error.

🛡️ Proposed fix with error handling
 wisk.theme.getWorkspaceTheme = function () {
     const workspacesData = localStorage.getItem('workspaces');
     if (!workspacesData) return null;
 
+    try {
         const parsed = JSON.parse(workspacesData);
+        if (!parsed.workspaces || !Array.isArray(parsed.workspaces)) return null;
+        
         const currentWorkspaceId = localStorage.getItem('currentWorkspace');
         const workspace = parsed.workspaces.find(w => w.id === currentWorkspaceId);
 
         return workspace?.theme || null;
+    } catch (error) {
+        console.error('Failed to parse workspace data:', error);
+        return null;
+    }
 };
🤖 Prompt for AI Agents
In @js/theme/theme.js around lines 110 - 119, The getWorkspaceTheme function
lacks JSON parse error handling and structure validation; wrap JSON.parse of
localStorage.getItem('workspaces') in a try/catch, return null on parse errors
(and optionally log the error), then ensure the parsed value has a workspaces
array before calling parsed.workspaces.find; also validate currentWorkspaceId
exists before searching and safely handle the case where find returns undefined
so the function returns null rather than throwing (refer to
wisk.theme.getWorkspaceTheme and variables workspacesData, parsed,
currentWorkspaceId, workspace).

Comment on lines +121 to +133
wisk.theme.setWorkspaceTheme = function (themeName) {
const workspacesData = localStorage.getItem('workspaces');
if (!workspacesData) return;

const parsed = JSON.parse(workspacesData);
const currentWorkspaceId = localStorage.getItem('currentWorkspace');
const workspaceIndex = parsed.workspaces.findIndex(w => w.id === currentWorkspaceId);

if (workspaceIndex !== -1) {
parsed.workspaces[workspaceIndex].theme = themeName;
localStorage.setItem('workspaces', JSON.stringify(parsed));
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add error handling and input validation.

The function needs:

  1. Error handling for JSON.parse() to prevent runtime errors from corrupted data
  2. Validation that parsed.workspaces exists and is an array
  3. Optional: validation of the themeName parameter to ensure it's a valid theme
🛡️ Proposed fix with error handling and validation
 wisk.theme.setWorkspaceTheme = function (themeName) {
+    if (!themeName || typeof themeName !== 'string') {
+        console.error('Invalid theme name provided');
+        return;
+    }
+    
     const workspacesData = localStorage.getItem('workspaces');
     if (!workspacesData) return;
 
+    try {
         const parsed = JSON.parse(workspacesData);
+        if (!parsed.workspaces || !Array.isArray(parsed.workspaces)) {
+            console.error('Invalid workspaces data structure');
+            return;
+        }
+        
         const currentWorkspaceId = localStorage.getItem('currentWorkspace');
         const workspaceIndex = parsed.workspaces.findIndex(w => w.id === currentWorkspaceId);
 
         if (workspaceIndex !== -1) {
             parsed.workspaces[workspaceIndex].theme = themeName;
             localStorage.setItem('workspaces', JSON.stringify(parsed));
         }
+    } catch (error) {
+        console.error('Failed to update workspace theme:', error);
+    }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
wisk.theme.setWorkspaceTheme = function (themeName) {
const workspacesData = localStorage.getItem('workspaces');
if (!workspacesData) return;
const parsed = JSON.parse(workspacesData);
const currentWorkspaceId = localStorage.getItem('currentWorkspace');
const workspaceIndex = parsed.workspaces.findIndex(w => w.id === currentWorkspaceId);
if (workspaceIndex !== -1) {
parsed.workspaces[workspaceIndex].theme = themeName;
localStorage.setItem('workspaces', JSON.stringify(parsed));
}
};
wisk.theme.setWorkspaceTheme = function (themeName) {
if (!themeName || typeof themeName !== 'string') {
console.error('Invalid theme name provided');
return;
}
const workspacesData = localStorage.getItem('workspaces');
if (!workspacesData) return;
try {
const parsed = JSON.parse(workspacesData);
if (!parsed.workspaces || !Array.isArray(parsed.workspaces)) {
console.error('Invalid workspaces data structure');
return;
}
const currentWorkspaceId = localStorage.getItem('currentWorkspace');
const workspaceIndex = parsed.workspaces.findIndex(w => w.id === currentWorkspaceId);
if (workspaceIndex !== -1) {
parsed.workspaces[workspaceIndex].theme = themeName;
localStorage.setItem('workspaces', JSON.stringify(parsed));
}
} catch (error) {
console.error('Failed to update workspace theme:', error);
}
};
🤖 Prompt for AI Agents
In @js/theme/theme.js around lines 121 - 133, The function
wisk.theme.setWorkspaceTheme should guard against corrupted localStorage and bad
inputs: wrap JSON.parse(localStorage.getItem('workspaces')) in a try/catch and
handle parse errors (log/return) instead of throwing; validate that parsed &&
Array.isArray(parsed.workspaces) before accessing .findIndex; validate themeName
(e.g., ensure typeof themeName === 'string' and not empty, or check against an
allowed themes list if available) and return/log on invalid input; also handle
missing currentWorkspace or workspaceIndex === -1 by logging/returning rather
than attempting to set parsed.workspaces[workspaceIndex].theme; finally only
call localStorage.setItem when all checks pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant