Appearance
Theming System Implementation Plan
For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Replace the dual SCSS color system with a unified CSS custom property architecture supporting runtime light/dark theme switching, consolidating ~58 style files into ~14.
Architecture: Three-layer token system (primitives in OKLCH with hex fallbacks → semantic tokens per theme → optional component tokens). Theme switching via [data-theme] attribute on <html>. Vuetify stays in permanent light mode with a single reset file neutralizing its defaults. theme.css imported once in main.ts; _theme-helpers.scss injected via Vite additionalData.
Tech Stack: Vue 2 + Vuetify 2.3.6, Vite, SCSS, CSS Custom Properties, OKLCH color space
Spec: docs/superpowers/specs/2026-03-17-theming-system-design.md
File Structure
New Files
| File | Responsibility |
|---|---|
src/styles/theme.css | All CSS custom properties: primitives, semantic tokens (dark + light), shadows, motion tokens. Imported once in main.ts. |
src/styles/_theme-helpers.scss | SCSS-only helpers/mixins for working with tokens. Injected via additionalData. No :root blocks. |
src/styles/_vuetify-reset.scss | Neutralizes all Vuetify .theme--light defaults with token values. |
src/styles/_components-navigation.scss | Consolidation of TopBar, Breadcrumbs, ContentTop. |
src/styles/_components-tables.scss | Consolidation of MainTable, MainTableHeader, MainTableFooter, MainTableNumeric, MainTableSearch, MainTableStatus. |
src/styles/_components-forms.scss | Consolidation of Input, DropdownsAndSelects, DatePicker, ImageUploader. |
src/styles/_components-dialogs.scss | Consolidation of Dialog, FormShell, Wizards. |
src/styles/_components-buttons.scss | Consolidation of Buttons. |
src/styles/_components-builder.scss | Consolidation of BuilderPanels, BuilderTop, OptionRowShared, OperatorStyles, Preview, MiddlewareOp. |
src/styles/_components-misc.scss | Consolidation of Toast, Chip, CTooltip, LoadingCircle, ScrollBar, ActionMenu, List, Stepper, TreeView. |
src/styles/_pages.scss | Consolidation of MainPage, MainPageTiles, SignIn, EditProfileForm, user_profile, ReportsManagementView, ReportsTable. |
src/styles/_reports.scss | Consolidation of all 11 src/pages/Reports/styles/*.scss files. |
src/composables/useTheme.ts | Theme toggle composable: get/set theme, persist to localStorage, respect system preference. |
Modified Files
| File | Change |
|---|---|
vite.config.ts | Update additionalData — remove _colors.scss, _newcolors.scss, _shadows.scss, ScrollBar.scss; add _theme-helpers.scss |
src/main.ts | Add imports for theme.css, _vuetify-reset.scss, all consolidated SCSS files; add theme initialization |
src/App.vue | Replace $background-body, $primary-font-color with tokens; remove .theme--light override |
index.html | Add data-theme attribute; add inline script for theme flash prevention |
src/styles/_variables.scss | Absorb sizing vars from vertical_tabbed_layout_variables.scss |
src/styles/_animations.scss | Modernize — remove ad-hoc transitions now handled by motion tokens. Note: existing $transition-* SCSS vars in _variables.scss are kept as-is (they are sizing/timing, not colors) and can be migrated to CSS motion tokens gradually. |
| ~95 Vue component files | Replace hardcoded hex + old SCSS vars with var(--token) |
Deleted Files
| File | Reason |
|---|---|
src/styles/_colors.scss | Replaced by theme.css |
src/styles/_newcolors.scss | Replaced by theme.css (including all *-invert variables) |
src/styles/_shadows.scss | Shadow tokens moved into theme.css |
src/styles/GlobalTypography.scss | Base text styles handled by Vuetify reset + tokens |
src/styles/vertical_tabbed_layout_variables.scss | Sizing absorbed into _variables.scss, colors into tokens |
34 individual component SCSS files in src/styles/ | Replaced by 9 consolidated files |
11 Reports SCSS files in src/pages/Reports/styles/ | Replaced by _reports.scss |
Chunk 1: Foundation
Phase 1 from the design spec. Creates the new token system, Vuetify reset, and wiring. After this chunk the app still compiles and looks identical — old SCSS vars still exist in parallel.
Task 1: Create theme.css — Primitive Tokens
Files:
Create:
src/styles/theme.css[ ] Step 1: Create
theme.csswith primitive tokens
css
/* ==========================================================================
Cavai Theme — CSS Custom Properties
Imported ONCE in main.ts. Do NOT put in Vite additionalData.
========================================================================== */
/* --------------------------------------------------------------------------
Layer 1: Primitive Tokens (OKLCH with hex fallbacks)
These are the raw color values. Never use directly in components.
-------------------------------------------------------------------------- */
:root {
/* Grey scale — neutral with subtle blue undertone */
--primitive-grey-50: #1f2024;
--primitive-grey-50: oklch(15% 0.01 260);
--primitive-grey-100: #333338;
--primitive-grey-100: oklch(22% 0.01 260);
--primitive-grey-150: #3e3e44;
--primitive-grey-150: oklch(26% 0.01 260);
--primitive-grey-200: #494950;
--primitive-grey-200: oklch(30% 0.01 260);
--primitive-grey-250: #54545c;
--primitive-grey-250: oklch(34% 0.01 260);
--primitive-grey-300: #606068;
--primitive-grey-300: oklch(38% 0.01 260);
--primitive-grey-400: #73737c;
--primitive-grey-400: oklch(46% 0.01 260);
--primitive-grey-500: #878790;
--primitive-grey-500: oklch(54% 0.01 260);
--primitive-grey-600: #9c9ca6;
--primitive-grey-600: oklch(62% 0.01 260);
--primitive-grey-700: #b5b5bf;
--primitive-grey-700: oklch(72% 0.01 260);
--primitive-grey-800: #cfcfd8;
--primitive-grey-800: oklch(82% 0.01 260);
--primitive-grey-850: #dddde6;
--primitive-grey-850: oklch(87% 0.01 260);
--primitive-grey-900: #ebebf2;
--primitive-grey-900: oklch(92% 0.01 260);
--primitive-grey-950: #f5f5fa;
--primitive-grey-950: oklch(96% 0.01 260);
--primitive-white: #ffffff;
--primitive-white: oklch(100% 0 0);
/* Brand colors */
--primitive-blue-500: #0b84c2;
--primitive-blue-500: oklch(58% 0.18 240);
--primitive-red-500: #d15e5e;
--primitive-red-500: oklch(58% 0.20 18);
--primitive-green-500: #18a558;
--primitive-green-500: oklch(62% 0.17 155);
--primitive-yellow-500: #f5d565;
--primitive-yellow-500: oklch(82% 0.14 85);
/* Status colors (categorical — same in both themes) */
--primitive-status-blue: #0693c7;
--primitive-status-green: #09d270;
--primitive-status-purple: #7b76f5;
--primitive-status-yellow: #fcc11f;
--primitive-status-red: #f50023;
--primitive-status-pink: #ea5391;
}- [ ] Step 2: Verify file is valid CSS
Run: npx css-validator src/styles/theme.css 2>&1 || echo "Manual check: open in browser DevTools" Expected: No syntax errors (OKLCH duplicate-property pattern is valid CSS)
- [ ] Step 3: Commit
bash
git add src/styles/theme.css
git commit -m "feat(theme): create theme.css with primitive OKLCH tokens"Task 2: Add Semantic Tokens + Motion Tokens to theme.css
Files:
Modify:
src/styles/theme.css[ ] Step 1: Append dark theme semantic tokens
Add to end of theme.css:
css
/* --------------------------------------------------------------------------
Layer 2: Semantic Tokens — what colors MEAN
Components use ONLY these (or Layer 3 component tokens).
-------------------------------------------------------------------------- */
/* Dark theme (default) */
[data-theme="dark"] {
/* Surfaces */
--surface-base: var(--primitive-grey-100);
--surface-raised: var(--primitive-grey-150);
--surface-overlay: var(--primitive-grey-50);
--surface-sunken: var(--primitive-grey-50);
--surface-hover: var(--primitive-grey-250);
--surface-active: var(--primitive-grey-300);
/* Text */
--text-primary: var(--primitive-grey-850);
--text-secondary: var(--primitive-grey-600);
--text-muted: var(--primitive-grey-400);
/* Borders */
--border-default: var(--primitive-grey-250);
--border-strong: var(--primitive-grey-400);
--border-subtle: var(--primitive-grey-200);
/* Accents */
--accent-primary: var(--primitive-blue-500);
--accent-danger: var(--primitive-red-500);
--accent-success: var(--primitive-green-500);
--accent-warning: var(--primitive-yellow-500);
/* Status (categorical — same in both themes) */
--status-blue: var(--primitive-status-blue);
--status-green: var(--primitive-status-green);
--status-purple: var(--primitive-status-purple);
--status-yellow: var(--primitive-status-yellow);
--status-red: var(--primitive-status-red);
--status-pink: var(--primitive-status-pink);
/* Focus */
--focus-ring: var(--primitive-blue-500);
/* Shadows — more prominent on dark backgrounds */
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 4px 24px rgba(0, 0, 0, 0.35);
}- [ ] Step 2: Append light theme semantic tokens
css
/* Light theme */
[data-theme="light"] {
--surface-base: var(--primitive-grey-950);
--surface-raised: var(--primitive-white);
--surface-overlay: var(--primitive-white);
--surface-sunken: var(--primitive-grey-900);
--surface-hover: var(--primitive-grey-900);
--surface-active: var(--primitive-grey-850);
--text-primary: var(--primitive-grey-100);
--text-secondary: var(--primitive-grey-500);
--text-muted: var(--primitive-grey-600);
--border-default: var(--primitive-grey-850);
--border-strong: var(--primitive-grey-700);
--border-subtle: var(--primitive-grey-900);
--accent-primary: var(--primitive-blue-500);
--accent-danger: var(--primitive-red-500);
--accent-success: var(--primitive-green-500);
--accent-warning: var(--primitive-yellow-500);
--status-blue: var(--primitive-status-blue);
--status-green: var(--primitive-status-green);
--status-purple: var(--primitive-status-purple);
--status-yellow: var(--primitive-status-yellow);
--status-red: var(--primitive-status-red);
--status-pink: var(--primitive-status-pink);
--focus-ring: var(--primitive-blue-500);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06);
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.1);
}- [ ] Step 3: Append motion tokens
css
/* --------------------------------------------------------------------------
Motion Tokens — durations and easing
-------------------------------------------------------------------------- */
:root {
--duration-fast: 100ms;
--duration-normal: 200ms;
--duration-slow: 350ms;
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in-out: cubic-bezier(0.45, 0, 0.55, 1);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--transition-colors: var(--duration-fast) var(--ease-out);
--transition-transform: var(--duration-normal) var(--ease-spring);
--transition-layout: var(--duration-slow) var(--ease-in-out);
}- [ ] Step 4: Commit
bash
git add src/styles/theme.css
git commit -m "feat(theme): add semantic tokens (dark + light) and motion tokens"Task 3: Create _theme-helpers.scss
Files:
Create:
src/styles/_theme-helpers.scss[ ] Step 1: Create the SCSS helpers file
scss
// ==========================================================================
// Theme Helpers — SCSS-only utilities
// Injected via Vite additionalData. Contains NO :root blocks.
// ==========================================================================
// Shorthand mixin for transitioning color properties
@mixin color-transition($properties...) {
$transitions: ();
@each $prop in $properties {
$transitions: append($transitions, #{$prop} var(--transition-colors), comma);
}
transition: $transitions;
}
// Apply styles only in a specific theme
@mixin theme($name) {
[data-theme="#{$name}"] & {
@content;
}
}
// Scrollbar mixin — used by 14+ components via @include light-scrollbar
// (Moved from ScrollBar.scss which was in additionalData)
@mixin light-scrollbar {
scrollbar-color: rgba(0, 0, 0, .17) transparent;
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 15px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, .17);
border-radius: $size-15;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
background-clip: padding-box;
}
}- [ ] Step 2: Commit
bash
git add src/styles/_theme-helpers.scss
git commit -m "feat(theme): create _theme-helpers.scss with SCSS utility mixins and scrollbar mixin"Task 4: Create _vuetify-reset.scss
Files:
Create:
src/styles/_vuetify-reset.scss[ ] Step 1: Create the Vuetify reset file
scss
// ==========================================================================
// Vuetify Reset — neutralize .theme--light defaults
// Imported once in main.ts. Vuetify stays in "light" mode permanently.
// Our data-theme attribute handles actual theme switching.
// ==========================================================================
// Surface resets — these Vuetify components all default to white
.theme--light.v-application {
background-color: var(--surface-base);
color: var(--text-primary);
}
.theme--light.v-sheet,
.theme--light.v-card,
.theme--light.v-list,
.theme--light.v-data-table,
.theme--light.v-expansion-panels,
.theme--light.v-expansion-panel {
background-color: var(--surface-raised);
color: var(--text-primary);
}
.theme--light.v-menu__content,
.theme--light.v-dialog,
.theme--light.v-autocomplete__content {
background-color: var(--surface-overlay);
color: var(--text-primary);
}
// Text input resets
.theme--light .v-input input,
.theme--light .v-input textarea,
.theme--light .v-select__selection {
color: var(--text-primary);
}
.theme--light .v-label {
color: var(--text-secondary);
}
// Icon resets
.theme--light .v-icon {
color: var(--text-secondary);
}
// Divider resets
.theme--light .v-divider {
border-color: var(--border-subtle);
}
// Table-specific resets
.theme--light.v-data-table .v-data-table-header th {
color: var(--text-secondary);
}
.theme--light.v-data-table tbody tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) {
background: var(--surface-hover);
}
.theme--light.v-data-table tbody td {
border-bottom-color: var(--border-subtle);
}
// Toolbar resets
.theme--light.v-toolbar {
background-color: var(--surface-overlay);
color: var(--text-primary);
}
// Tabs
.theme--light.v-tabs > .v-tabs-bar {
background-color: transparent;
}- [ ] Step 2: Commit
bash
git add src/styles/_vuetify-reset.scss
git commit -m "feat(theme): create _vuetify-reset.scss to neutralize Vuetify defaults"Task 5: Update vite.config.ts — Change additionalData
Files:
Modify:
vite.config.ts:45-58[ ] Step 1: Update the SCSS additionalData
Replace the css block (lines 45-58) with:
typescript
css: {
preprocessorOptions: {
scss: {
additionalData: `
@import "@/styles/_variables.scss";
@import "@/styles/_fonts.scss";
@import "@/styles/_theme-helpers.scss";
@import "@/styles/_animations.scss";
`,
},
},
},What changed:
- Removed:
_colors.scss,_newcolors.scss,_shadows.scss,ScrollBar.scss - Added:
_theme-helpers.scss(which includes thelight-scrollbarmixin fromScrollBar.scss— 14 components@includeit, so it must remain inadditionalData) - Kept:
_variables.scss,_fonts.scss,_animations.scss
The .light-scrollbar class definition (the non-mixin part of ScrollBar.scss) moves to _components-misc.scss in Task 16.
Important: This will break the build because components still reference $grey-20, $background-body, etc. We handle this in two ways:
- Short-term (this task): Add backward-compatibility SCSS vars to
_theme-helpers.scssthat map old names → CSS custom properties - Long-term (Phase 2-3): Remove the compatibility vars as each file is migrated
- [ ] Step 2: Add backward-compat vars to
_theme-helpers.scss
Append to src/styles/_theme-helpers.scss:
scss
// ==========================================================================
// TEMPORARY backward-compatibility shim
// Maps old SCSS variable names to CSS custom properties.
// DELETE this entire section once all files are migrated (end of Phase 3).
// ==========================================================================
// --- Backgrounds ---
$background-body: var(--surface-base);
$background-content: var(--surface-raised);
$background-panel: var(--surface-raised);
$background-header: var(--surface-overlay);
$background-light: var(--surface-hover);
// --- Grey scale (from _newcolors.scss) ---
$grey-05: var(--primitive-grey-50);
$grey-10: var(--surface-sunken);
$grey-15: var(--surface-base);
$grey-20: var(--surface-raised);
$grey-25: var(--surface-hover);
$grey-30: var(--border-default);
$grey-35: var(--border-strong);
$grey-40: var(--border-strong);
$grey-45: var(--border-strong);
$grey-50: var(--text-muted);
$grey-55: var(--text-muted);
$grey-60: var(--text-secondary);
$grey-65: var(--text-secondary);
$grey-70: var(--text-secondary);
$grey-75: var(--primitive-grey-700);
$grey-80: var(--text-primary);
$grey-85: var(--border-subtle);
$grey-90: var(--border-subtle);
$grey-95: var(--border-subtle);
// --- Typography scale ---
$typography-default: var(--text-secondary);
$typography-plus1: var(--text-secondary);
$typography-plus2: var(--text-secondary);
$typography-plus3: var(--text-muted);
$typography-plus4: var(--primitive-grey-700);
$typography-plus5: var(--primitive-grey-800);
$typography-plus6: var(--border-subtle);
$typography-plus7: var(--primitive-grey-900);
$typography-minus1: var(--text-secondary);
$typography-minus2: var(--text-secondary);
$typography-minus3: var(--text-primary);
$typography-minus4: var(--text-primary);
$typography-minus5: var(--text-primary);
$typography-minus6: var(--text-primary);
$typography-minus7: var(--text-primary);
// --- Illustration scale ---
$illustration-default: var(--text-muted);
$illustration-plus1: var(--text-secondary);
$illustration-plus2: var(--text-secondary);
$illustration-plus3: var(--primitive-grey-700);
$illustration-plus4: var(--primitive-grey-700);
$illustration-plus5: var(--primitive-grey-800);
$illustration-plus6: var(--border-subtle);
$illustration-plus7: var(--primitive-grey-900);
$illustration-minus1: var(--text-muted);
$illustration-minus2: var(--text-muted);
$illustration-minus3: var(--border-strong);
$illustration-minus4: var(--border-default);
$illustration-minus5: var(--surface-raised);
$illustration-minus6: var(--surface-base);
$illustration-minus7: var(--primitive-grey-50);
// --- Borders ---
$border-default: var(--border-default);
$border-minus1: var(--border-strong);
$border-minus2: var(--border-strong);
$border-grey-light-default: var(--surface-hover);
$border-grey-light-minus1: var(--border-default);
$border-grey-light-minus2: var(--border-strong);
$border-grey-light-minus3: var(--border-strong);
$border-grey-light-minus6: var(--text-muted);
$text-input-bottom-border: var(--border-strong);
// --- Colors ---
$primary-default: var(--accent-primary);
$secondary-default: var(--accent-danger);
$tertiary-default: var(--accent-success);
$error-color: var(--accent-danger);
$warning-color: var(--accent-warning);
$info-color: var(--text-secondary);
$global-black: var(--text-primary);
$global-white: var(--surface-raised);
$white-color: var(--surface-raised);
$black-color: var(--text-primary);
$global-disabled: var(--text-muted);
$primary-font-color: var(--text-primary);
$secondary-font-color: var(--text-secondary);
// --- Table status ---
$table-status-color-blue: var(--status-blue);
$table-status-color-green: var(--status-green);
$table-status-color-purple: var(--status-purple);
$table-status-color-yellow: var(--status-yellow);
$table-status-color-red: var(--status-red);
$table-status-color-pink: var(--status-pink);
// --- Shadows ---
$background-drop-shadow: var(--shadow-md);
$shadow-milli: var(--shadow-md);
$shadow-milli-strong: var(--shadow-lg);
$shadow-micro: var(--shadow-sm);
$shadow-micro-strong: var(--shadow-md);Critical notes on SCSS function incompatibility:
Some SCSS functions (lighten(), darken(), mix(), adjust-hue(), invert(), saturate()) cannot operate on CSS custom properties because they need compile-time color values. These require hardcoded hex fallbacks in the shim:
scss
// SCSS-function-dependent vars — must use hardcoded hex, not CSS vars
$typography-plus7-hover: #f3f3f5; // was lighten($typography-plus7, 4)
$typography-plus7-active: #f0f0f2; // was lighten($typography-plus7, 2.5)The *-invert and *-invert-alt variables (65+ variables in _newcolors.scss) are NOT shimmed. Files using them will be fixed directly during the flow editor migration (Task 23). If any other file references an invert variable, it must be migrated before the shim is removed.
The calendar-svg-background() SCSS function in Input.scss (line 110) embeds color into a data URI SVG at compile time — CSS vars cannot work here. During migration (Task 12), replace with hardcoded hex values matching the token equivalents:
calendar-svg-background($border-grey-light-minus4)→calendar-svg-background(#6b6b6b)(≈--border-strong)calendar-svg-background($border-grey-light-minus6)→calendar-svg-background(#838383)(≈--text-muted)calendar-svg-background($primary-default)→calendar-svg-background(#0b84c2)(≈--accent-primary)calendar-svg-background(#ff5252)→ keep as-is (already hardcoded hex for error)
The mix() calls in table styling (e.g., mix($illustration-plus6, $global-white, 77%)) will be replaced with token values during consolidation.
Additional shim entries for completeness — append these to the shim:
scss
// --- Error variants ---
$error-default: #D15E5E;
$error-plus1: #DB7272;
$error-plus2: #E38786;
$error-plus3: #EB9C9B;
$error-plus4: #F0B1AF;
$error-plus5: #F5C5C4;
$error-plus6: #FAD9D7;
$error-plus7: #FFE9E8;
$error-minus1: #C44B4D;
$error-minus2: #B53A40;
$error-minus3: #A12B33;
$error-minus4: #8A1E29;
$error-minus5: #70141F;
$error-minus6: #520B15;
$error-minus7: #30040B;
// --- Primary variants (keep hex — used in gradients, opacity calcs) ---
$primary-plus1: #2E97CE;
$primary-plus2: #52AADA;
$primary-plus3: #76BDE5;
$primary-plus4: #9AD0EE;
$primary-plus5: #BDE2F5;
$primary-plus6: #DFF1FA;
$primary-plus7: #F0F8FD;
$primary-pale-default: #86C6DB;
// --- Secondary variants ---
$secondary-plus1: #E8687A;
$secondary-plus2: #ED7E8E;
$secondary-plus3: #F294A1;
$secondary-plus4: #F5AAB4;
$secondary-plus5: #F8C0C8;
$secondary-plus6: #FBD8DD;
$secondary-plus7: #FDECEF;
$secondary-pale-default: #EDB1B7;
// --- Tertiary variants ---
$tertiary-plus1: #3DB872;
$tertiary-plus2: #66CB8E;
$tertiary-plus3: #92DDAC;
$tertiary-plus4: #C2EECE;
// --- Builder colors ---
$builder-master-one: #88CB87;
$builder-master-two: #DEAE52;
$chatbotbuilder-font-color: var(--text-primary);
$chatbotbuilder-font-hover-color: var(--text-secondary);
$chatbotbuilder-zoomicons-border-color: var(--border-default);
// --- Brand illustrations (hardcoded — only used in specific illustrations) ---
$brand-light-illustration1: #FEF8E7;
$brand-main-illustration1: #FEF8E7;
$brand-main-illustration2: #FF4068;
$brand-light-illustration3: #F7EDF6;
$brand-main-illustration3: #990099;
$brand-dark-illustration3: #721C6E;
// --- Remaining border variants ---
$border-minus3: #6b6b6b;
$border-minus4: #777777;
$border-minus5: #838383;
$border-minus6: #919191;
$border-grey-light-minus4: #6b6b6b;
$border-grey-light-minus5: #777777;
// --- Color families (keep hex — rarely used outside flow editor) ---
// If build fails on alpha/bravo/charlie/delta/echo vars, add them here
// with their original hex values from _newcolors.scss. Most are only
// used in flow editor and Reports, which get migrated directly.- [ ] Step 3: Verify the app builds
Run: npm run build 2>&1 | tail -30 Expected: Build may fail on files using mix(), lighten(), darken(), invert(), or adjust-hue() with the shimmed vars. Note which files fail — they need direct migration in Phase 2-3.
If there are SCSS compilation errors, fix them by finding the specific files and replacing the SCSS function calls with token values directly. Common fixes:
mix($foo, $bar, 77%)→ replace with a token likevar(--border-subtle)lighten($typography-plus7, 4)→ replace withvar(--surface-hover)[ ] Step 4: Commit
bash
git add vite.config.ts src/styles/_theme-helpers.scss
git commit -m "feat(theme): update vite additionalData and add backward-compat SCSS shim"Task 6: Update main.ts — Import Theme Files
Files:
Modify:
src/main.ts[ ] Step 1: Add theme imports after the vuetify import
After line 10 (import '@/styles/vuetify.min.custom.css'), add:
typescript
import '@/styles/theme.css'
import '@/styles/_vuetify-reset.scss'- [ ] Step 2: Verify build
Run: npm run build 2>&1 | tail -20 Expected: Build succeeds (or same errors as Task 5 step 3 — no new errors)
- [ ] Step 3: Commit
bash
git add src/main.ts
git commit -m "feat(theme): import theme.css and vuetify reset in main.ts"Task 7: Update index.html — Theme Flash Prevention
Files:
Modify:
index.html[ ] Step 1: Add data-theme attribute and flash prevention script
Replace the <html> tag and add inline script in <head>:
html
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<script>
// Prevent theme flash — runs before any CSS loads
(function() {
var theme = localStorage.getItem('cavai-theme');
if (!theme) {
theme = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
}
document.documentElement.setAttribute('data-theme', theme);
// Set body bg to match theme to prevent white flash
document.documentElement.style.backgroundColor = theme === 'dark' ? '#333338' : '#f5f5fa';
})();
</script>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link id="favicon" rel="icon">
<title>Loading..</title>
</head>
<body>What changed:
Added
data-theme="dark"to<html>(fallback default)Added inline script for localStorage/system-preference detection
Removed hardcoded
style="background-color: #252525"from<body>(now set dynamically by script)[ ] Step 2: Commit
bash
git add index.html
git commit -m "feat(theme): add data-theme attribute and flash prevention to index.html"Task 8: Update App.vue — Replace Legacy Vars
Files:
Modify:
src/App.vue:7-27[ ] Step 1: Replace the style block
vue
<style lang="scss">
html {
overflow-y: auto;
}
body {
background-color: var(--surface-base);
}
.v-application {
color: var(--text-primary);
font-family: $noto, sans-serif;
font-weight: 300;
background-color: var(--surface-base);
}
</style>What changed:
$background-body→var(--surface-base)$primary-font-color→var(--text-primary)Removed
.theme--lightoverride (handled by_vuetify-reset.scssnow)Added explicit
background-color: var(--surface-base)on.v-application[ ] Step 2: Verify dev server renders correctly
Run: npm run dev and open in browser. Expected: App renders with dark theme. No visual changes from current state. DevTools should show CSS variables resolving on :root and [data-theme="dark"].
- [ ] Step 3: Commit
bash
git add src/App.vue
git commit -m "feat(theme): migrate App.vue to use semantic tokens"Task 9: Absorb vertical_tabbed_layout_variables.scss into _variables.scss
Files:
Modify:
src/styles/_variables.scssDelete:
src/styles/vertical_tabbed_layout_variables.scss[ ] Step 1: Append sizing vars to
_variables.scss
Add to end of _variables.scss:
scss
// --- Vertical Tabbed Layout (absorbed from vertical_tabbed_layout_variables.scss) ---
$max-profile-width: 1300px;
$top-margin: 40px;
$separator-border-width: 1px;
$separator: $separator-border-width solid var(--border-subtle);
$link-height: 3rem;
$update-indicator-size: 5px;
$top-bar-height: 40px;
$avatar-upload-overlay-size: 25px;
$empty-avatar-overlay-size: 32px;Note: $separator-color: #f1f1f1 and $separator: ... solid $grey-85 are replaced by the token-based $separator using var(--border-subtle).
- [ ] Step 2: Find all imports of
vertical_tabbed_layout_variablesand remove them
Search for imports:
bash
grep -r "vertical_tabbed_layout_variables" src/ --include="*.vue" --include="*.scss" -lIn each file found, remove the @import line (the vars now come through additionalData since _variables.scss is in additionalData).
- [ ] Step 3: Delete the old file
bash
git rm src/styles/vertical_tabbed_layout_variables.scss- [ ] Step 4: Verify build
Run: npm run build 2>&1 | tail -20 Expected: Build succeeds, no references to deleted file
- [ ] Step 5: Commit
bash
git add src/styles/_variables.scss
git add -u # stages the deletion
git commit -m "refactor(theme): absorb vertical_tabbed_layout_variables into _variables.scss"Chunk 2: Global Style Migration — Navigation, Tables, Forms
Phase 2 from the design spec (part 1). Consolidate the global SCSS files for navigation, tables, and forms into their target files, replacing all old color references with tokens and removing unnecessary
!importantoverrides.
Task 10: Create _components-navigation.scss
Files:
Create:
src/styles/_components-navigation.scssSource files to consolidate:
src/styles/TopBar.scss(263 lines)src/styles/Breadcrumbs.scss(44 lines)src/styles/ContentTop.scss(13 lines)
[ ] Step 1: Read all three source files
Read TopBar.scss, Breadcrumbs.scss, and ContentTop.scss in full.
- [ ] Step 2: Create
_components-navigation.scss
Combine all three files into one, replacing every color variable and hardcoded hex with semantic tokens:
- All
$grey-XX→ appropriatevar(--surface-*),var(--text-*), orvar(--border-*)based on context - All
$background-*→var(--surface-*) - All
$typography-*→var(--text-*) - All hardcoded hex → semantic token
- Remove
!importantwhere it was only fighting Vuetify defaults - Add section headers with comments:
// === TopBar ===,// === Breadcrumbs ===,// === ContentTop ===
Migration reference (from spec Section 7):
| Context | Old | New |
|---|---|---|
| TopBar background | $background-header / $grey-10 | var(--surface-overlay) |
| Breadcrumb text | $typography-minus4 | var(--text-secondary) |
| Breadcrumb links | $typography-minus2 | var(--text-muted) |
| Breadcrumb hover | $typography-minus6 | var(--text-primary) |
| Breadcrumb divider | $typography-plus2 | var(--border-default) |
| Content header text | inherited black | var(--text-primary) |
- [ ] Step 3: Add import to
main.ts
Add after the vuetify reset import:
typescript
import '@/styles/_components-navigation.scss'- [ ] Step 4: Delete old files
bash
git rm src/styles/TopBar.scss src/styles/Breadcrumbs.scss src/styles/ContentTop.scss- [ ] Step 5: Remove old imports from any Vue components
Search: grep -r "TopBar.scss\|Breadcrumbs.scss\|ContentTop.scss" src/ --include="*.vue" --include="*.scss" --include="*.ts" -l
Remove the @import lines from any files found (the styles are now global via main.ts).
- [ ] Step 6: Verify build and visual check
Run: npm run dev Check: TopBar, breadcrumbs, and content header render correctly.
- [ ] Step 7: Commit
bash
git add src/styles/_components-navigation.scss src/main.ts
git add -u
git commit -m "refactor(theme): consolidate navigation styles into _components-navigation.scss"Task 11: Create _components-tables.scss
Files:
Create:
src/styles/_components-tables.scssSource files to consolidate:
src/styles/MainTable.scss(156 lines)src/styles/MainTableHeader.scss(79 lines)src/styles/MainTableFooter.scss(35 lines)src/styles/MainTableNumeric.scss(64 lines)src/styles/MainTableSearch.scss(40 lines)src/styles/MainTableStatus.scss(36 lines)
[ ] Step 1: Read all six source files
[ ] Step 2: Create
_components-tables.scss
Combine all six files, replacing every color reference with tokens:
Key mappings:
| Context | Old | New |
|---|---|---|
| Table card bg | $grey-20 | var(--surface-raised) |
| Table card text | $grey-80 | var(--text-primary) |
| Table card border | $grey-30 | var(--border-default) |
| Table card shadow | 0 2px 8px rgba(0,0,0,0.15) | var(--shadow-md) |
| Data table bg | $grey-20 | var(--surface-raised) |
| Row border | $grey-30 / mix(...) | var(--border-subtle) |
| Row hover | $grey-25 / mix(...) | var(--surface-hover) |
| Zebra stripe | rgba(255,255,255,0.02) | var(--surface-hover) with low opacity, or keep as-is |
| Cell text | $grey-70 | var(--text-secondary) |
| Link text | $grey-80 | var(--text-primary) |
| Header bg | $grey-20 | var(--surface-raised) |
| Header border | $grey-35 | var(--border-default) |
| Sort arrow | $grey-60 | var(--text-muted) |
| Footer text | $grey-50 | var(--text-muted) |
| Numeric text | $grey-70 | var(--text-secondary) |
| Search input text | #252525 | var(--text-primary) |
| Status colors | $table-status-color-* | var(--status-*) |
| Dropdown bg | $grey-20 | var(--surface-overlay) |
| Dropdown hover | $grey-25 | var(--surface-hover) |
| Dropdown border | $grey-30 | var(--border-default) |
| Select text | $grey-80 | var(--text-primary) |
- [ ] Step 3: Add import to
main.ts
typescript
import '@/styles/_components-tables.scss'- [ ] Step 4: Delete old files
bash
git rm src/styles/MainTable.scss src/styles/MainTableHeader.scss src/styles/MainTableFooter.scss src/styles/MainTableNumeric.scss src/styles/MainTableSearch.scss src/styles/MainTableStatus.scss- [ ] Step 5: Remove old imports from Vue components
Search and remove any @import lines referencing these files.
- [ ] Step 6: Visual check — browse a table page (e.g., Brands list)
Expected: Table renders correctly with dark theme, hover works, search bar text visible, status dots correct colors.
- [ ] Step 7: Commit
bash
git add src/styles/_components-tables.scss src/main.ts
git add -u
git commit -m "refactor(theme): consolidate table styles into _components-tables.scss"Task 12: Create _components-forms.scss
Files:
Create:
src/styles/_components-forms.scssSource files to consolidate:
src/styles/Input.scss(353 lines)src/styles/DropdownsAndSelects.scss(90 lines)src/styles/DatePicker.scss(81 lines)src/styles/ImageUploader.scss(50 lines)
[ ] Step 1: Read all four source files
[ ] Step 2: Create
_components-forms.scss
Replace all color references with tokens:
Key mappings:
| Context | Old | New |
|---|---|---|
| Input bg | $grey-10 / $background-light | var(--surface-sunken) |
| Input text | $grey-80 / hardcoded | var(--text-primary) |
| Input border | $text-input-bottom-border / $grey-35 | var(--border-strong) |
| Input placeholder | $grey-50 | var(--text-muted) |
| Dropdown bg | $grey-20 / $global-white | var(--surface-overlay) |
| Dropdown hover | #EFEFEF / $grey-25 | var(--surface-hover) |
| Dropdown border | $grey-35 | var(--border-default) |
| Dropdown text | $grey-80 | var(--text-primary) |
| Dropdown caret bg | $global-white | var(--surface-overlay) |
| Dropdown shadow | hardcoded | var(--shadow-lg) |
| DatePicker bg | white | var(--surface-overlay) |
| ImageUploader border | various | var(--border-default) |
- [ ] Step 3: Add import to
main.ts
typescript
import '@/styles/_components-forms.scss'- [ ] Step 4: Delete old files and remove old imports
bash
git rm src/styles/Input.scss src/styles/DropdownsAndSelects.scss src/styles/DatePicker.scss src/styles/ImageUploader.scss- [ ] Step 5: Visual check — open a form (e.g., Create Campaign modal)
Expected: Inputs, dropdowns, date pickers render with dark theme colors.
- [ ] Step 6: Commit
bash
git add src/styles/_components-forms.scss src/main.ts
git add -u
git commit -m "refactor(theme): consolidate form styles into _components-forms.scss"Chunk 3: Global Style Migration — Dialogs, Buttons, Builder, Misc, Pages
Task 13: Create _components-dialogs.scss
Files:
Create:
src/styles/_components-dialogs.scssSource files:
src/styles/Dialog.scss(61 lines)src/styles/FormShell.scss(50 lines)src/styles/Wizards.scss(258 lines)
[ ] Step 1: Read all three source files
[ ] Step 2: Create
_components-dialogs.scss
Key mappings:
| Context | Old | New |
|---|---|---|
| Modal bg | $global-white / white | var(--surface-raised) |
| Modal text | $typography-default | var(--text-primary) |
| Modal subtitle | $typography-minus1 | var(--text-secondary) |
| Modal border | none | 1px solid var(--border-default) |
| Modal shadow | hardcoded | var(--shadow-lg) |
| Close button bg | $global-white | transparent |
| Close button icon | $global-black | var(--text-secondary) |
| Wizard step colors | various | var(--accent-primary), var(--text-muted) |
[ ] Step 3: Add import to
main.ts, delete old files[ ] Step 4: Visual check — open a dialog (Create Brand)
[ ] Step 5: Commit
bash
git add src/styles/_components-dialogs.scss src/main.ts
git add -u
git commit -m "refactor(theme): consolidate dialog styles into _components-dialogs.scss"Task 14: Create _components-buttons.scss
Files:
Create:
src/styles/_components-buttons.scssSource:
src/styles/Buttons.scss(72 lines)[ ] Step 1: Read
Buttons.scss[ ] Step 2: Create
_components-buttons.scss
Key mappings:
| Context | Old | New |
|---|---|---|
| Outlined bg | $global-white / $grey-25 | var(--surface-raised) |
| Outlined text | $typography-default / $grey-70 | var(--text-secondary) |
| Outlined border | $border-grey-light-minus6 / $grey-40 | var(--border-default) |
| Primary button bg | $primary-default | var(--accent-primary) |
| Danger button bg | $error-color | var(--accent-danger) |
[ ] Step 3: Add import to
main.ts, delete old file[ ] Step 4: Commit
bash
git add src/styles/_components-buttons.scss src/main.ts
git add -u
git commit -m "refactor(theme): consolidate button styles into _components-buttons.scss"Task 15: Create _components-builder.scss
Files:
Create:
src/styles/_components-builder.scssSource files:
src/styles/BuilderPanels.scss(268 lines)src/styles/BuilderTop.scss(417 lines)src/styles/OptionRowShared.scss(15 lines)src/styles/OperatorStyles.scss(83 lines)src/styles/Preview.scss(55 lines)src/styles/MiddlewareOp.scss(77 lines)
[ ] Step 1: Read all six source files
[ ] Step 2: Create
_components-builder.scss
The builder is the most complex area. Replace all colors with tokens. Pay special attention to:
Panel backgrounds →
var(--surface-raised)Builder header →
var(--surface-overlay)Operator/middleware styles →
var(--surface-raised),var(--border-default)Preview frame border →
var(--border-default)Focus states →
var(--focus-ring)[ ] Step 3: Add import to
main.ts, delete old files[ ] Step 4: Visual check — open the creative builder
Expected: Builder panels, top bar, operators, preview all themed correctly.
- [ ] Step 5: Commit
bash
git add src/styles/_components-builder.scss src/main.ts
git add -u
git commit -m "refactor(theme): consolidate builder styles into _components-builder.scss"Task 16: Create _components-misc.scss
Files:
Create:
src/styles/_components-misc.scssSource files:
src/styles/Toast.scss(105 lines)src/styles/Chip.scss(25 lines)src/styles/CTooltip.scss(73 lines)src/styles/LoadingCircle.scss(91 lines)src/styles/ScrollBar.scss(30 lines)src/styles/ActionMenu.scss(6 lines)src/styles/List.scss(16 lines)src/styles/Stepper.scss(58 lines)src/styles/TreeView.scss(29 lines)
[ ] Step 1: Read all source files
[ ] Step 2: Create
_components-misc.scss
Replace all color references with tokens. Group by component with comment headers.
[ ] Step 3: Add import to
main.ts, delete old files[ ] Step 4: Commit
bash
git add src/styles/_components-misc.scss src/main.ts
git add -u
git commit -m "refactor(theme): consolidate misc component styles into _components-misc.scss"Task 17: Create _pages.scss
Files:
Create:
src/styles/_pages.scssSource files:
src/styles/MainPage.scss(482 lines)src/styles/MainPageTiles.scss(144 lines)src/styles/SignIn.scss(447 lines)src/styles/EditProfileForm.scss(66 lines)src/styles/user_profile.scss(159 lines)src/styles/ReportsManagementView.scss(89 lines)src/styles/ReportsTable.scss(60 lines)
[ ] Step 1: Read all source files
[ ] Step 2: Create
_pages.scss
Key mappings for MainPage/Tiles:
| Context | Old | New |
|---|---|---|
| Card title | $typography-minus6 | var(--text-primary) |
| Card divider | $typography-plus6 / $grey-85 | var(--border-subtle) |
| Card bg | $global-white | var(--surface-raised) |
| Tile divider | $grey-40 | var(--border-default) |
SignIn page:
Background →
var(--surface-base)Input styling →
var(--surface-sunken),var(--text-primary),var(--border-strong)Button →
var(--accent-primary)[ ] Step 3: Add import to
main.ts, delete old files[ ] Step 4: Visual check — MainPage (dashboard), sign-in page
[ ] Step 5: Commit
bash
git add src/styles/_pages.scss src/main.ts
git add -u
git commit -m "refactor(theme): consolidate page styles into _pages.scss"Task 18: Create _reports.scss
Files:
Create:
src/styles/_reports.scssSource files (11 files, 5,140 lines total):
src/pages/Reports/styles/reports.scss(283 lines)src/pages/Reports/styles/reports-charts.scss(118 lines)src/pages/Reports/styles/reports-charts-header.scss(490 lines)src/pages/Reports/styles/reports-data-sources.scss(377 lines)src/pages/Reports/styles/reports-dialogs.scss(1,482 lines)src/pages/Reports/styles/reports-heading.scss(262 lines)src/pages/Reports/styles/reports-table-container.scss(30 lines)src/pages/Reports/styles/reports-table-wrapper.scss(1,043 lines)src/pages/Reports/styles/reports-tabs.scss(469 lines)src/pages/Reports/styles/reports-templates.scss(531 lines)src/pages/Reports/styles/reports-vuetify-overwrite.scss(55 lines)
[ ] Step 1: Read all 11 source files
This is the largest consolidation — 5,140 lines with 215 color declarations. Take care to understand the structure before migrating.
- [ ] Step 2: Create
_reports.scss
Replace all color references. The reports-vuetify-overwrite.scss file should become unnecessary after the Vuetify reset, but review its contents — if it has report-specific Vuetify overrides, keep those.
- [ ] Step 3: Add import to
main.ts
typescript
import '@/styles/_reports.scss'- [ ] Step 4: Remove old imports from Reports Vue components
Search: grep -r "reports-.*\.scss\|reports\.scss" src/pages/Reports/ --include="*.vue" -l
Remove @import lines in each file. The styles are now global.
- [ ] Step 5: Delete old files
bash
git rm -r src/pages/Reports/styles/- [ ] Step 6: Visual check — Reports page
Expected: Charts, tables, dialogs, tabs all render with dark theme.
- [ ] Step 7: Commit
bash
git add src/styles/_reports.scss src/main.ts
git add -u
git commit -m "refactor(theme): consolidate Reports styles into _reports.scss"Task 19: Delete Legacy Color Files
Files:
Delete:
src/styles/_colors.scssDelete:
src/styles/_newcolors.scssDelete:
src/styles/_shadows.scssDelete:
src/styles/GlobalTypography.scss[ ] Step 1: Verify no direct imports remain
bash
grep -r "_colors.scss\|_newcolors.scss\|_shadows.scss\|GlobalTypography.scss" src/ --include="*.vue" --include="*.scss" --include="*.ts" -lExpected: Only vite.config.ts (already updated) and possibly some Vue components that @import these directly. Fix any remaining direct imports.
- [ ] Step 2: Delete the files
bash
git rm src/styles/_colors.scss src/styles/_newcolors.scss src/styles/_shadows.scss src/styles/GlobalTypography.scss- [ ] Step 3: Verify build
Run: npm run build 2>&1 | tail -30 Expected: Build succeeds. If any file still references an old variable not covered by the shim, fix it.
- [ ] Step 4: Commit
bash
git add -u
git commit -m "refactor(theme): delete legacy color files (_colors, _newcolors, _shadows, GlobalTypography)"Chunk 4: Component Migration
Phase 3 from the design spec. Migrate all ~95 Vue components with
<style>blocks that reference old SCSS variables or hardcoded hex values.
Task 20: Migrate Commonly-Used Components (Batch 1 — Layout & Common)
Files to migrate (replace old vars/hex with tokens in <style> blocks):
These are the most visible components. Migrate and verify each:
src/components/layout/ContentHeader.vue—color: $grey-80→var(--text-primary)src/components/common/Dropdown.vue— multiple$grey-*and$global-whiterefssrc/components/common/InputField.vue— hardcoded hex +$grey-*src/components/common/TextArea.vue—$grey-*refssrc/components/common/ColorPicker.vue—$grey-*refssrc/components/common/TooltipWrapper.vue— hardcoded hex
[ ] Step 1: Read each file's
<style>block[ ] Step 2: Replace all color references in each file
For each file, replace:
$grey-XX→ appropriate semantic token (use migration reference from spec Section 7)$background-*→var(--surface-*)$typography-*→var(--text-*)$global-white/$white-color/#FFFFFF→var(--surface-raised)orvar(--surface-overlay)depending on context$global-black/$black-color/#000000→var(--text-primary)#252525→var(--surface-base)orvar(--text-primary)depending on contextHardcoded hex for backgrounds → appropriate
var(--surface-*)Hardcoded hex for text → appropriate
var(--text-*)Hardcoded hex for borders → appropriate
var(--border-*)Remove
!importantwhere it only existed to fight Vuetify[ ] Step 3: Verify build
[ ] Step 4: Commit
bash
git add src/components/layout/ContentHeader.vue src/components/common/Dropdown.vue src/components/common/InputField.vue src/components/common/TextArea.vue src/components/common/ColorPicker.vue src/components/common/TooltipWrapper.vue
git commit -m "refactor(theme): migrate layout and common components to semantic tokens"Task 21: Migrate Builder Components (Batch 2)
Files to migrate:
The builder has the largest surface area. Key files:
src/pages/Chatbots/components/BuilderVisuals/Configuration/components/SlideSection.vuesrc/pages/Chatbots/components/OptionRow/OptionInputField.vuesrc/pages/Chatbots/components/CavaiFlow/BrandingToolbar.vuesrc/pages/Chatbots/components/CavaiFlow/LabelToolbar.vuesrc/pages/Chatbots/components/CavaiFlow/CavaiFlow.vuesrc/pages/Chatbots/components/CavaiFlow/flowactors/OperatorBase.vue
[ ] Step 1: Read each file's
<style>block[ ] Step 2: Replace all color references
Same mapping rules as Task 20. Builder-specific:
$chatbotbuilder-font-color(#363636) →var(--text-primary)$chatbotbuilder-font-hover-color(#828384) →var(--text-secondary)$chatbotbuilder-zoomicons-border-color(#2D2D2D) →var(--border-default)[ ] Step 3: Verify build and visual check — open creative builder
[ ] Step 4: Commit
bash
git add src/pages/Chatbots/
git commit -m "refactor(theme): migrate builder components to semantic tokens"Task 22: Migrate Remaining Builder Components (Batch 3)
Files: All remaining Vue files under src/pages/Chatbots/ that reference old vars or hardcoded hex.
- [ ] Step 1: Find all remaining files
bash
grep -rn '\$grey-\|\$typography-\|\$illustration-\|\$background-\|\$primary-\|\$secondary-\|\$tertiary-\|\$error-\|\$border-\|\$global-\|\$white-color\|\$black-color\|#FFFFFF\|#ffffff\|#000000\|#252525\|#303030\|#1E1E1E\|#1e1e1e' src/pages/Chatbots/ --include="*.vue" -l[ ] Step 2: Migrate each file
[ ] Step 3: Commit
bash
git add src/pages/Chatbots/
git commit -m "refactor(theme): migrate remaining builder components to semantic tokens"Task 23: Migrate Flow Editor — Replace invert.scss Pattern
Files:
Modify: Flow editor Vue components that use
.invertclassDelete:
src/pages/Chatbots/components/CavaiFlow/flowactors/invert.scss(if it exists) or remove invert variable usage from_newcolors.scssreferences[ ] Step 1: Find all flow editor components using invert variables or
.invertclass
bash
grep -rn 'invert\|\.invert' src/pages/Chatbots/components/CavaiFlow/ --include="*.vue" --include="*.scss" -l- [ ] Step 2: Replace the
.invertpattern
The flow editor uses .invert class to create a localized light mode inside the dark app. With our new system, this is replaced by:
scss
// Instead of .invert { color: $typography-minus6-invert; }
// Use:
[data-theme="dark"] .flow-editor-canvas {
// Flow editor is always light-on-dark in dark mode (default)
}
[data-theme="light"] .flow-editor-canvas {
// Flow editor adapts to light mode automatically via tokens
}Or, if the flow editor should always be light regardless of app theme, scope it with a data attribute:
html
<div class="flow-editor-canvas" data-theme="light">This makes all token references inside resolve to light-theme values.
[ ] Step 3: Remove all
*-invertand*-invert-altvariable references[ ] Step 4: Verify flow editor works in both themes
[ ] Step 5: Commit
bash
git add src/pages/Chatbots/components/CavaiFlow/
git commit -m "refactor(theme): replace flow editor invert pattern with data-theme scoping"Task 24: Migrate CreativeGroups, Brands, Campaigns, Workspaces Pages
Files: All Vue components under:
src/pages/CreativeGroups/src/pages/Brands/src/pages/Campaigns/src/pages/Workspaces/[ ] Step 1: Find all files with old color references
bash
for dir in CreativeGroups Brands Campaigns Workspaces; do
echo "=== $dir ==="
grep -rn '\$grey-\|\$typography-\|\$background-\|\$primary-default\|\$error-color\|#FFFFFF\|#ffffff\|#000000\|#252525' "src/pages/$dir/" --include="*.vue" -l 2>/dev/null
done- [ ] Step 2: Migrate each file
Pay attention to:
Format badges/tags →
var(--surface-active)bg +var(--text-secondary)textExport tag badges →
var(--surface-active)bg +var(--border-default)border"+12 more" / "show less" links →
var(--text-secondary)Preview panel borders →
var(--border-default)[ ] Step 3: Verify build and visual check
[ ] Step 4: Commit
bash
git add src/pages/CreativeGroups/ src/pages/Brands/ src/pages/Campaigns/ src/pages/Workspaces/
git commit -m "refactor(theme): migrate page components to semantic tokens"Task 25: Migrate Reports Components
Files: All Vue components under src/pages/Reports/ (excluding already-deleted styles/ directory)
- [ ] Step 1: Find files with old color references
bash
grep -rn '\$grey-\|\$typography-\|\$background-\|\$primary-\|#FFFFFF\|#ffffff\|#000000\|#252525' src/pages/Reports/ --include="*.vue" -l- [ ] Step 2: Migrate each file
Reports-specific attention:
Chart colors may need to stay as-is (data visualization colors are intentional)
Table styling → tokens
Dialog backgrounds →
var(--surface-raised)[ ] Step 3: Commit
bash
git add src/pages/Reports/
git commit -m "refactor(theme): migrate Reports components to semantic tokens"Task 26: Migrate All Remaining Components
Files: Any Vue components not yet migrated.
- [ ] Step 1: Find ALL remaining old color references across the entire codebase
bash
grep -rn '\$grey-\|\$typography-\|\$illustration-\|\$background-panel\|\$background-body\|\$background-content\|\$background-header\|\$global-white\|\$global-black\|\$white-color\|\$black-color\|\$primary-default\|\$secondary-default\|\$tertiary-default\|\$error-color\|\$border-default\|\$border-grey\|\$text-input-bottom-border' src/ --include="*.vue" -l | sortAlso find remaining hardcoded hex:
bash
grep -rn '#FFFFFF\|#ffffff\|#000000\|#252525\|#303030\|#1E1E1E\|#1e1e1e\|#1b1b1b\|#3c3c3c\|#464646\|#525252\|#5e5e5e\|#6b6b6b\|#777777\|#838383\|#919191\|#CBCBCB\|#cbcbcb\|#EFEFEF\|#efefef' src/ --include="*.vue" -l | sort[ ] Step 2: Migrate each remaining file
[ ] Step 3: Verify build
Run: npm run build Expected: Clean build, no SCSS errors.
- [ ] Step 4: Commit
bash
git add src/
git commit -m "refactor(theme): migrate all remaining components to semantic tokens"Task 27: Remove Backward-Compatibility Shim
Files:
Modify:
src/styles/_theme-helpers.scss— remove the entire "TEMPORARY backward-compatibility shim" section[ ] Step 1: Verify no SCSS files reference old variable names
bash
grep -rn '\$grey-\|\$typography-\|\$illustration-\|\$background-panel\|\$background-body\|\$background-content\|\$background-header\|\$background-light\|\$global-white\|\$global-black\|\$white-color\|\$black-color\|\$primary-font-color\|\$secondary-font-color\|\$primary-default\|\$secondary-default\|\$tertiary-default\|\$error-color\|\$warning-color\|\$info-color\|\$border-default\b\|\$border-minus\|\$border-grey\|\$text-input-bottom-border\|\$shadow-milli\|\$shadow-micro\|\$background-drop-shadow\|\$table-status-color' src/ --include="*.vue" --include="*.scss" -l | grep -v '_theme-helpers.scss' | grep -v 'node_modules'Expected: No results. If any files still reference old vars, migrate them first.
- [ ] Step 2: Remove the shim section from
_theme-helpers.scss
Delete everything from // TEMPORARY backward-compatibility shim to the end of the file, leaving only the helper mixins.
- [ ] Step 3: Verify build
Run: npm run build Expected: Clean build.
- [ ] Step 4: Commit
bash
git add src/styles/_theme-helpers.scss
git commit -m "refactor(theme): remove backward-compatibility SCSS shim — migration complete"Chunk 5: Theme Switching, Verification & Polish
Phases 4-5 from the design spec. Add the toggle mechanism, visual QA, and animation polish.
Task 28: Create Theme Composable
Files:
- Create:
src/composables/useTheme.ts
Note: Create src/composables/ directory if it does not already exist.
- [ ] Step 1: Create the composable
typescript
import Vue from 'vue'
const STORAGE_KEY = 'cavai-theme'
type Theme = 'light' | 'dark'
// Reactive state (Vue 2 compatible)
const state = Vue.observable({
theme: (document.documentElement.getAttribute('data-theme') as Theme) || 'dark',
})
function setTheme(theme: Theme): void {
state.theme = theme
document.documentElement.setAttribute('data-theme', theme)
localStorage.setItem(STORAGE_KEY, theme)
// Update the background color for any unthemed areas
document.documentElement.style.backgroundColor =
theme === 'dark' ? '#333338' : '#f5f5fa'
}
function toggleTheme(): void {
setTheme(state.theme === 'dark' ? 'light' : 'dark')
}
function initTheme(): void {
const stored = localStorage.getItem(STORAGE_KEY) as Theme | null
if (stored) {
setTheme(stored)
} else {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
setTheme(prefersDark ? 'dark' : 'light')
}
// Listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem(STORAGE_KEY)) {
setTheme(e.matches ? 'dark' : 'light')
}
})
}
export function useTheme() {
return {
/** Reactive state — access theme via state.theme */
state,
/** Whether the current theme is dark */
get isDark(): boolean { return state.theme === 'dark' },
/** Set a specific theme */
setTheme,
/** Toggle between light and dark */
toggleTheme,
/** Initialize theme from localStorage / system preference. Call once at app startup. */
initTheme,
}
}- [ ] Step 2: Initialize theme in
main.ts
Add before the new Vue(...) block:
typescript
import { useTheme } from '@/composables/useTheme'
const { initTheme } = useTheme()
initTheme()- [ ] Step 3: Commit
bash
git add src/composables/useTheme.ts src/main.ts
git commit -m "feat(theme): add useTheme composable with localStorage persistence"Task 29: Add Theme Toggle UI
Files:
Modify: TopBar component (or user menu) — add a toggle button
[ ] Step 1: Find the TopBar or user menu component
bash
grep -rn "TopBar\|user-menu\|UserMenu\|account-menu" src/components/ --include="*.vue" -l- [ ] Step 2: Add a theme toggle button
Add a simple icon button that calls toggleTheme(). Use a sun/moon icon (available in MDI: mdi-weather-sunny / mdi-weather-night).
vue
<v-btn icon @click="onToggleTheme" :title="themeIsDark ? 'Switch to light mode' : 'Switch to dark mode'">
<v-icon small>{{ themeIsDark ? 'mdi-weather-sunny' : 'mdi-weather-night' }}</v-icon>
</v-btn>javascript
import { useTheme } from '@/composables/useTheme'
const theme = useTheme()
// In component options:
computed: {
themeIsDark() {
return theme.state.theme === 'dark'
}
},
methods: {
onToggleTheme() {
theme.toggleTheme()
}
}- [ ] Step 3: Visual check — click the toggle
Expected: Entire app switches between light and dark instantly. Colors update everywhere. No flash or partial renders.
- [ ] Step 4: Commit
bash
git add src/components/
git commit -m "feat(theme): add theme toggle button to TopBar"Task 30: Visual QA — Dark Theme
Files: None (verification only)
- [ ] Step 1: Set theme to dark and verify every page
Go through each page/view in the app:
- Dashboard (MainPage with tiles)
- Brands list
- Brand detail → Campaigns list
- Campaign detail → Creative Groups list
- Creative Group detail → Creatives list
- Creative Builder (panels, top bar, flow editor, preview)
- Reports dashboard
- Reports detail (charts, tables, tabs)
- User profile / Edit profile
- Sign-in page
- All modals (Create Brand, Create Campaign, Create Creative Group, Bulk Tag Export)
- All dropdowns (action menus, select inputs)
- Breadcrumbs at every level
- Table search, sort, pagination
- Table status indicators
- [ ] Step 2: Note any remaining issues
Look for:
White backgrounds that should be dark
Black/invisible text
Borders that are too light or invisible
Hover states that don't work
Focus rings not visible
Icons invisible against background
[ ] Step 3: Fix issues found
[ ] Step 4: Commit fixes
bash
git add -A
git commit -m "fix(theme): visual QA fixes for dark theme"Task 31: Visual QA — Light Theme
Files: None (verification only)
- [ ] Step 1: Set theme to light and verify every page
Same checklist as Task 30 but for light theme. Pay attention to:
Backgrounds should be light (whites and near-whites)
Text should be dark
Borders should be subtle but visible
Shadows should be lighter
Status colors still distinct
[ ] Step 2: Fix issues found
[ ] Step 3: Commit fixes
bash
git add -A
git commit -m "fix(theme): visual QA fixes for light theme"Task 32: Animation & Transition Polish
Files:
Modify:
src/styles/_animations.scssModify: Various consolidated SCSS files
[ ] Step 1: Update
_animations.scss
Remove any ad-hoc transition definitions that are now handled by motion tokens. Keep keyframe definitions.
- [ ] Step 2: Add smooth color transitions to key interactive elements
In the consolidated SCSS files, add transitions where hover/active states change:
scss
// In _components-tables.scss — table rows
.main-table tr {
transition: background-color var(--transition-colors);
}
// In _components-buttons.scss — buttons
.v-btn {
transition: background-color var(--transition-colors),
border-color var(--transition-colors),
box-shadow var(--transition-colors);
}
// In _components-navigation.scss — breadcrumb links
.breadcrumbs a {
transition: color var(--transition-colors);
}- [ ] Step 3: Add subtle card hover effects
scss
// In _pages.scss — dashboard tiles
.dashboard-tile {
transition: transform var(--transition-transform),
box-shadow var(--transition-colors);
&:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-lg);
}
}[ ] Step 4: Visual check — hover/click interactions feel smooth
[ ] Step 5: Commit
bash
git add src/styles/
git commit -m "feat(theme): modernize transitions with motion tokens"Task 33: Final Cleanup & Verification
- [ ] Step 1: Verify zero old color references remain
bash
# Check for any remaining old SCSS vars
grep -rn '\$grey-\|\$typography-\|\$illustration-\|\$background-panel\|\$background-body\|\$primary-default\|\$secondary-default\|\$error-color\|\$global-white\|\$global-black\|\$white-color\|\$black-color' src/ --include="*.vue" --include="*.scss" --include="*.ts" | grep -v node_modules | grep -v '_theme-helpers.scss' | grep -v '.md'
# Check for hardcoded hex color values (common ones)
grep -rn '#FFFFFF\|#ffffff\|#000000\|#252525\|#303030\|#1E1E1E\|#3c3c3c\|#CBCBCB\|#EFEFEF' src/ --include="*.vue" --include="*.scss" | grep -v node_modules | grep -v theme.css | grep -v vuetify.min.custom.css | grep -v '.md'Expected: No results (or only intentional exceptions like SVG data URIs).
- [ ] Step 2: Verify build passes clean
bash
npm run buildExpected: Build succeeds with no warnings about missing SCSS variables.
- [ ] Step 3: Count files — verify consolidation
bash
ls -1 src/styles/*.scss src/styles/*.css | wc -lExpected: ~14 files (down from ~47 + vuetify.min.custom.css)
- [ ] Step 4: Verify no
!importantoverrides for color properties (except necessary Vuetify specificity)
bash
grep -rn '!important' src/styles/ --include="*.scss" | grep -i 'color\|background\|border-color' | grep -v vuetify.min.customReview any results — they should be minimal and justified.
- [ ] Step 5: Final commit
bash
git add -A
git commit -m "refactor(theme): final cleanup — theming system migration complete"Success Criteria Checklist
From the design spec:
- [ ] Zero hardcoded color values in any SCSS or Vue component
<style>block - [ ] Zero
!importantoverrides for color/background properties (except where Vuetify specificity genuinely requires it) - [ ] Light and dark mode both visually correct across all pages
- [ ] Runtime theme switching works via
data-themeattribute - [ ] ~14 style files instead of ~58
- [ ] One color system instead of two
- [ ] Build passes with no SCSS compilation errors
- [ ] All existing functionality preserved — no behavioral changes
- [ ] Hex fallbacks for all OKLCH values
Preview Deploy (Staging)
The theming branch can be deployed as a live preview on Cloudflare Pages so creators can test it in parallel with production.
How it works
Cloudflare Pages gives every non-main deploy its own URL. Production (main) stays untouched at the normal app URL. The preview uses the same backend, API, and data -- only the frontend code differs.
Deploy command
bash
npm run build
npx wrangler pages deploy dist --project-name=application-frontend --branch=theming-system-redesignPreview URL: theming-system-redesign.application-frontend.pages.dev
Notes
- Uses the same
VITE_APP_*env variables as local dev (points to prod backend) - Creators log in as normal and work with real creatives
- Re-run the deploy command after new changes to update the preview
- The Cloudflare project name is
application-frontend(account ID99bd06f9ae384616f2f61b4dabca32d7)