# Global Theme Fix — 2026-05-30

## Root Cause

Filament 3.3's `base.blade.php` layout contains its own dark mode script that runs **before** our HEAD_END render hook. This script reads `localStorage['theme']` (Filament's key). Our system uses `localStorage['crm-theme']` (different key). Two problems:

### Problem 1 — Different localStorage keys
- Our system: `localStorage['crm-theme'] = 'dark'|'light'`
- Filament's system: `localStorage['theme'] = 'dark'|'light'`
- When user toggled dark/light via our toggle, `localStorage.crm-theme` updated but `localStorage.theme` did NOT
- On next page load, Filament's `loadDarkMode()` read its own stale key and potentially re-added the `dark` class

### Problem 2 — HEAD inline script did not remove `dark` class
- Our HEAD_END inline script added `dark` class for dark mode but **never removed it** for light mode
- Filament's `loadDarkMode()` function runs before our inline script
- If `localStorage.theme = 'dark'` (stale), Filament added `dark` class
- Our inline script for light mode only omitted adding `dark` — it did NOT explicitly remove it
- Result: `dark` class remained → Tailwind `dark:` prefix classes activated → pages showed dark mode regardless

### Problem 3 — Livewire navigation re-ran Filament's loadDarkMode
- Filament registers: `document.addEventListener('livewire:navigated', loadDarkMode)`
- On Livewire SPA navigation, `loadDarkMode()` re-ran with stale `localStorage.theme`
- Our `crm-theme.js` had no `livewire:navigated` listener to correct this

## Fix Applied

### 1. AdminPanelProvider.php — HEAD_END inline script
```javascript
(function(){
  var t = localStorage.getItem('crm-theme') || 'dark';
  document.documentElement.classList.remove('crm-theme-dark', 'crm-theme-light');
  document.documentElement.classList.add('crm-theme-' + t);
  if (t === 'dark') {
    document.documentElement.classList.add('dark');
  } else {
    document.documentElement.classList.remove('dark');  // ← KEY FIX
  }
  localStorage.setItem('theme', t);  // ← Sync Filament's key
  var p = localStorage.getItem('crm-primary-color');
  if (p) document.documentElement.style.setProperty('--crm-primary', p);
})();
```

### 2. crm-theme.js — setTheme() function
Added `localStorage.setItem('theme', theme)` so Filament's key is always synced when user toggles.

### 3. crm-theme.js — livewire:navigated listener
```javascript
document.addEventListener('livewire:navigated', function () {
  setTheme(getTheme(), false);
  applyPrimaryToDOM(getPrimary());
  updateToggleButton();
});
```

### 4. crm-theme.css — hide Filament's theme switcher
```css
.fi-theme-switcher { display: none !important; }
```
Prevents two competing theme toggles in the sidebar.

## Theme Manager Location
- **JS**: `public/js/crm-theme.js`
- **Loaded**: via `PanelsRenderHook::BODY_END` in `AdminPanelProvider.php`
- **Inline script**: `PanelsRenderHook::HEAD_END` (runs before any content renders)

## Design System CSS Location
- **File**: `public/css/crm-theme.css`
- **Loaded**: via `PanelsRenderHook::HEAD_END` on every admin page
- **Coverage**: body, sidebar, topbar, tables, forms, inputs, modals, dropdowns, pagination, buttons, badges, filters

## Storage
- **Key**: `localStorage['crm-theme']` = `'dark'` or `'light'`
- **Synced key**: `localStorage['theme']` = same value (Filament's key, kept in sync)
- **Default**: `'dark'`

## How Theme Applies to All Pages
1. Every page: HEAD_END inline script reads `crm-theme`, removes old classes, adds correct ones, syncs `theme` key
2. Filament's `loadDarkMode()` runs, reads synced `theme` key → no conflict
3. BODY_END: `crm-theme.js` loads, calls `setTheme()` again as final correction + syncs keys
4. Livewire navigation: `livewire:navigated` listener re-applies theme
