Skip to content

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

FileResponsibility
src/styles/theme.cssAll CSS custom properties: primitives, semantic tokens (dark + light), shadows, motion tokens. Imported once in main.ts.
src/styles/_theme-helpers.scssSCSS-only helpers/mixins for working with tokens. Injected via additionalData. No :root blocks.
src/styles/_vuetify-reset.scssNeutralizes all Vuetify .theme--light defaults with token values.
src/styles/_components-navigation.scssConsolidation of TopBar, Breadcrumbs, ContentTop.
src/styles/_components-tables.scssConsolidation of MainTable, MainTableHeader, MainTableFooter, MainTableNumeric, MainTableSearch, MainTableStatus.
src/styles/_components-forms.scssConsolidation of Input, DropdownsAndSelects, DatePicker, ImageUploader.
src/styles/_components-dialogs.scssConsolidation of Dialog, FormShell, Wizards.
src/styles/_components-buttons.scssConsolidation of Buttons.
src/styles/_components-builder.scssConsolidation of BuilderPanels, BuilderTop, OptionRowShared, OperatorStyles, Preview, MiddlewareOp.
src/styles/_components-misc.scssConsolidation of Toast, Chip, CTooltip, LoadingCircle, ScrollBar, ActionMenu, List, Stepper, TreeView.
src/styles/_pages.scssConsolidation of MainPage, MainPageTiles, SignIn, EditProfileForm, user_profile, ReportsManagementView, ReportsTable.
src/styles/_reports.scssConsolidation of all 11 src/pages/Reports/styles/*.scss files.
src/composables/useTheme.tsTheme toggle composable: get/set theme, persist to localStorage, respect system preference.

Modified Files

FileChange
vite.config.tsUpdate additionalData — remove _colors.scss, _newcolors.scss, _shadows.scss, ScrollBar.scss; add _theme-helpers.scss
src/main.tsAdd imports for theme.css, _vuetify-reset.scss, all consolidated SCSS files; add theme initialization
src/App.vueReplace $background-body, $primary-font-color with tokens; remove .theme--light override
index.htmlAdd data-theme attribute; add inline script for theme flash prevention
src/styles/_variables.scssAbsorb sizing vars from vertical_tabbed_layout_variables.scss
src/styles/_animations.scssModernize — 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 filesReplace hardcoded hex + old SCSS vars with var(--token)

Deleted Files

FileReason
src/styles/_colors.scssReplaced by theme.css
src/styles/_newcolors.scssReplaced by theme.css (including all *-invert variables)
src/styles/_shadows.scssShadow tokens moved into theme.css
src/styles/GlobalTypography.scssBase text styles handled by Vuetify reset + tokens
src/styles/vertical_tabbed_layout_variables.scssSizing 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.css with 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 the light-scrollbar mixin from ScrollBar.scss — 14 components @include it, so it must remain in additionalData)
  • 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:

  1. Short-term (this task): Add backward-compatibility SCSS vars to _theme-helpers.scss that map old names → CSS custom properties
  2. 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 like var(--border-subtle)

  • lighten($typography-plus7, 4) → replace with var(--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-bodyvar(--surface-base)

  • $primary-font-colorvar(--text-primary)

  • Removed .theme--light override (handled by _vuetify-reset.scss now)

  • 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.scss

  • Delete: 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_variables and remove them

Search for imports:

bash
grep -r "vertical_tabbed_layout_variables" src/ --include="*.vue" --include="*.scss" -l

In 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 !important overrides.

Task 10: Create _components-navigation.scss

Files:

  • Create: src/styles/_components-navigation.scss

  • Source 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 → appropriate var(--surface-*), var(--text-*), or var(--border-*) based on context
  • All $background-*var(--surface-*)
  • All $typography-*var(--text-*)
  • All hardcoded hex → semantic token
  • Remove !important where it was only fighting Vuetify defaults
  • Add section headers with comments: // === TopBar ===, // === Breadcrumbs ===, // === ContentTop ===

Migration reference (from spec Section 7):

ContextOldNew
TopBar background$background-header / $grey-10var(--surface-overlay)
Breadcrumb text$typography-minus4var(--text-secondary)
Breadcrumb links$typography-minus2var(--text-muted)
Breadcrumb hover$typography-minus6var(--text-primary)
Breadcrumb divider$typography-plus2var(--border-default)
Content header textinherited blackvar(--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.scss

  • Source 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:

ContextOldNew
Table card bg$grey-20var(--surface-raised)
Table card text$grey-80var(--text-primary)
Table card border$grey-30var(--border-default)
Table card shadow0 2px 8px rgba(0,0,0,0.15)var(--shadow-md)
Data table bg$grey-20var(--surface-raised)
Row border$grey-30 / mix(...)var(--border-subtle)
Row hover$grey-25 / mix(...)var(--surface-hover)
Zebra stripergba(255,255,255,0.02)var(--surface-hover) with low opacity, or keep as-is
Cell text$grey-70var(--text-secondary)
Link text$grey-80var(--text-primary)
Header bg$grey-20var(--surface-raised)
Header border$grey-35var(--border-default)
Sort arrow$grey-60var(--text-muted)
Footer text$grey-50var(--text-muted)
Numeric text$grey-70var(--text-secondary)
Search input text#252525var(--text-primary)
Status colors$table-status-color-*var(--status-*)
Dropdown bg$grey-20var(--surface-overlay)
Dropdown hover$grey-25var(--surface-hover)
Dropdown border$grey-30var(--border-default)
Select text$grey-80var(--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.scss

  • Source 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:

ContextOldNew
Input bg$grey-10 / $background-lightvar(--surface-sunken)
Input text$grey-80 / hardcodedvar(--text-primary)
Input border$text-input-bottom-border / $grey-35var(--border-strong)
Input placeholder$grey-50var(--text-muted)
Dropdown bg$grey-20 / $global-whitevar(--surface-overlay)
Dropdown hover#EFEFEF / $grey-25var(--surface-hover)
Dropdown border$grey-35var(--border-default)
Dropdown text$grey-80var(--text-primary)
Dropdown caret bg$global-whitevar(--surface-overlay)
Dropdown shadowhardcodedvar(--shadow-lg)
DatePicker bgwhitevar(--surface-overlay)
ImageUploader bordervariousvar(--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.scss

  • Source 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:

ContextOldNew
Modal bg$global-white / whitevar(--surface-raised)
Modal text$typography-defaultvar(--text-primary)
Modal subtitle$typography-minus1var(--text-secondary)
Modal bordernone1px solid var(--border-default)
Modal shadowhardcodedvar(--shadow-lg)
Close button bg$global-whitetransparent
Close button icon$global-blackvar(--text-secondary)
Wizard step colorsvariousvar(--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.scss

  • Source: src/styles/Buttons.scss (72 lines)

  • [ ] Step 1: Read Buttons.scss

  • [ ] Step 2: Create _components-buttons.scss

Key mappings:

ContextOldNew
Outlined bg$global-white / $grey-25var(--surface-raised)
Outlined text$typography-default / $grey-70var(--text-secondary)
Outlined border$border-grey-light-minus6 / $grey-40var(--border-default)
Primary button bg$primary-defaultvar(--accent-primary)
Danger button bg$error-colorvar(--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.scss

  • Source 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.scss

  • Source 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.scss

  • Source 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:

ContextOldNew
Card title$typography-minus6var(--text-primary)
Card divider$typography-plus6 / $grey-85var(--border-subtle)
Card bg$global-whitevar(--surface-raised)
Tile divider$grey-40var(--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.scss

  • Source 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.scss

  • Delete: src/styles/_newcolors.scss

  • Delete: src/styles/_shadows.scss

  • Delete: 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" -l

Expected: 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:

  1. src/components/layout/ContentHeader.vuecolor: $grey-80var(--text-primary)
  2. src/components/common/Dropdown.vue — multiple $grey-* and $global-white refs
  3. src/components/common/InputField.vue — hardcoded hex + $grey-*
  4. src/components/common/TextArea.vue$grey-* refs
  5. src/components/common/ColorPicker.vue$grey-* refs
  6. src/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 / #FFFFFFvar(--surface-raised) or var(--surface-overlay) depending on context

  • $global-black / $black-color / #000000var(--text-primary)

  • #252525var(--surface-base) or var(--text-primary) depending on context

  • Hardcoded hex for backgrounds → appropriate var(--surface-*)

  • Hardcoded hex for text → appropriate var(--text-*)

  • Hardcoded hex for borders → appropriate var(--border-*)

  • Remove !important where 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:

  1. src/pages/Chatbots/components/BuilderVisuals/Configuration/components/SlideSection.vue
  2. src/pages/Chatbots/components/OptionRow/OptionInputField.vue
  3. src/pages/Chatbots/components/CavaiFlow/BrandingToolbar.vue
  4. src/pages/Chatbots/components/CavaiFlow/LabelToolbar.vue
  5. src/pages/Chatbots/components/CavaiFlow/CavaiFlow.vue
  6. src/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 .invert class

  • Delete: src/pages/Chatbots/components/CavaiFlow/flowactors/invert.scss (if it exists) or remove invert variable usage from _newcolors.scss references

  • [ ] Step 1: Find all flow editor components using invert variables or .invert class

bash
grep -rn 'invert\|\.invert' src/pages/Chatbots/components/CavaiFlow/ --include="*.vue" --include="*.scss" -l
  • [ ] Step 2: Replace the .invert pattern

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 *-invert and *-invert-alt variable 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) text

  • Export 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 | sort

Also 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:

  1. Dashboard (MainPage with tiles)
  2. Brands list
  3. Brand detail → Campaigns list
  4. Campaign detail → Creative Groups list
  5. Creative Group detail → Creatives list
  6. Creative Builder (panels, top bar, flow editor, preview)
  7. Reports dashboard
  8. Reports detail (charts, tables, tabs)
  9. User profile / Edit profile
  10. Sign-in page
  11. All modals (Create Brand, Create Campaign, Create Creative Group, Bulk Tag Export)
  12. All dropdowns (action menus, select inputs)
  13. Breadcrumbs at every level
  14. Table search, sort, pagination
  15. 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.scss

  • Modify: 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 build

Expected: 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 -l

Expected: ~14 files (down from ~47 + vuetify.min.custom.css)

  • [ ] Step 4: Verify no !important overrides 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.custom

Review 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 !important overrides 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-theme attribute
  • [ ] ~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-redesign

Preview 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 ID 99bd06f9ae384616f2f61b4dabca32d7)

Internal documentation