@tpitre/story-ui 4.5.2 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"StoryUIPanel.d.ts","sourceRoot":"","sources":["../../../templates/StoryUI/StoryUIPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,oBAAoB,CAAC;AAwwB5B,UAAU,iBAAiB;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B;AAED,iBAAS,YAAY,CAAC,EAAE,OAAO,EAAE,EAAE,iBAAiB,2CA0sCnD;AAED,eAAe,YAAY,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"StoryUIPanel.d.ts","sourceRoot":"","sources":["../../../templates/StoryUI/StoryUIPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,oBAAoB,CAAC;AAwwB5B,UAAU,iBAAiB;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B;AAED,iBAAS,YAAY,CAAC,EAAE,OAAO,EAAE,EAAE,iBAAiB,2CAkrCnD;AAED,eAAe,YAAY,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -525,92 +525,69 @@ function StoryUIPanel({ mcpPort }) {
525
525
  pollForExternalStories();
526
526
  return () => clearInterval(intervalId);
527
527
  }, []);
528
- // Detect Storybook theme
528
+ // Detect Storybook MANAGER theme (not preview background)
529
+ // This ensures Story UI follows Storybook's overall theme, not the story preview background toggle
529
530
  useEffect(() => {
530
- const detectTheme = () => {
531
- const body = document.body;
532
- const html = document.documentElement;
533
- // Check URL parameters for Storybook background setting
534
- const urlParams = new URLSearchParams(window.location.search);
535
- const globals = urlParams.get('globals') || '';
536
- const hasStorybookLightBg = globals.includes('backgrounds.value:light');
537
- const hasStorybookDarkBg = globals.includes('backgrounds.value:dark') ||
538
- globals.includes('backgrounds.value:%23') || // Hex colors starting with #
539
- globals.includes('backgrounds.value:!hex');
540
- // Check parent frame URL if we're in an iframe (Storybook 8+)
541
- let parentHasDarkBg = false;
542
- let parentHasLightBg = false;
543
- let parentHasDarkClass = false;
531
+ const detectManagerTheme = () => {
532
+ let managerIsDark = false;
533
+ // Strategy 1: Check parent frame for Storybook manager theme (Storybook 8+)
534
+ // The manager theme is set in .storybook/manager.tsx via addons.setConfig({ theme: themes.dark })
544
535
  try {
545
536
  if (window.parent !== window) {
546
- const parentUrl = new URL(window.parent.location.href);
547
- const parentGlobals = parentUrl.searchParams.get('globals') || '';
548
- parentHasLightBg = parentGlobals.includes('backgrounds.value:light');
549
- parentHasDarkBg = parentGlobals.includes('backgrounds.value:dark') ||
550
- parentGlobals.includes('backgrounds.value:%23');
551
- // Check parent document for Storybook dark theme classes
552
537
  const parentBody = window.parent.document.body;
553
538
  const parentHtml = window.parent.document.documentElement;
554
- parentHasDarkClass = parentBody.classList.contains('sb-dark') ||
555
- parentHtml.classList.contains('dark') ||
539
+ // Check for Storybook's dark theme class (most reliable)
540
+ if (parentBody.classList.contains('sb-dark') ||
541
+ parentHtml.classList.contains('sb-dark') ||
556
542
  parentHtml.getAttribute('data-theme') === 'dark' ||
557
- parentBody.getAttribute('data-theme') === 'dark';
558
- // Check Storybook 8+ manager theme
559
- const sbMainEl = window.parent.document.querySelector('.sb-main-padded, .sb-show-main');
560
- if (sbMainEl) {
561
- const sbBgColor = window.getComputedStyle(sbMainEl).backgroundColor;
562
- const sbRgb = sbBgColor.match(/\d+/g);
563
- if (sbRgb && sbRgb.length >= 3) {
564
- const sbLuminance = (0.299 * parseInt(sbRgb[0]) + 0.587 * parseInt(sbRgb[1]) + 0.114 * parseInt(sbRgb[2])) / 255;
565
- if (sbLuminance < 0.5)
566
- parentHasDarkClass = true;
543
+ parentBody.getAttribute('data-theme') === 'dark') {
544
+ managerIsDark = true;
545
+ }
546
+ // Check Storybook manager sidebar/header background color as fallback
547
+ // The manager UI elements use the theme colors, not the preview background
548
+ const managerEl = window.parent.document.querySelector('.sb-sidebar, [class*="sidebar"], .sb-bar');
549
+ if (managerEl && !managerIsDark) {
550
+ const bgColor = window.getComputedStyle(managerEl).backgroundColor;
551
+ const rgb = bgColor.match(/\d+/g);
552
+ if (rgb && rgb.length >= 3) {
553
+ const luminance = (0.299 * parseInt(rgb[0]) + 0.587 * parseInt(rgb[1]) + 0.114 * parseInt(rgb[2])) / 255;
554
+ managerIsDark = luminance < 0.5;
567
555
  }
568
556
  }
569
557
  }
570
558
  }
571
559
  catch {
572
- // Cross-origin access not allowed, ignore
560
+ // Cross-origin access not allowed, fall back to system preference
573
561
  }
574
- // Check the actual background color of the body
575
- const bgColor = window.getComputedStyle(body).backgroundColor;
576
- const rgb = bgColor.match(/\d+/g);
577
- let isBackgroundDark = false;
578
- if (rgb && rgb.length >= 3) {
579
- const luminance = (0.299 * parseInt(rgb[0]) + 0.587 * parseInt(rgb[1]) + 0.114 * parseInt(rgb[2])) / 255;
580
- isBackgroundDark = luminance < 0.5;
562
+ // Strategy 2: If not in iframe or can't detect, use system preference
563
+ if (!managerIsDark) {
564
+ const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
565
+ managerIsDark = systemPrefersDark;
581
566
  }
582
- // Check system preference as fallback
583
- const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
584
- // Explicit light mode takes precedence - if user selected "light" in Storybook, respect that
585
- const hasExplicitLightMode = hasStorybookLightBg || parentHasLightBg;
586
- // Explicit dark mode indicators
587
- const hasExplicitDarkMode = body.classList.contains('sb-dark') ||
588
- html.classList.contains('dark') ||
589
- html.getAttribute('data-theme') === 'dark' ||
590
- body.getAttribute('data-theme') === 'dark' ||
591
- hasStorybookDarkBg ||
592
- parentHasDarkBg;
593
- // Determine dark mode: explicit light mode forces light, otherwise check dark indicators
594
- const isDark = hasExplicitLightMode
595
- ? false
596
- : (hasExplicitDarkMode || parentHasDarkClass || isBackgroundDark || systemPrefersDark);
597
- dispatch({ type: 'SET_DARK_MODE', payload: isDark });
567
+ dispatch({ type: 'SET_DARK_MODE', payload: managerIsDark });
598
568
  };
599
- detectTheme();
600
- // Listen for URL changes (Storybook uses popstate for navigation)
601
- window.addEventListener('popstate', detectTheme);
602
- // Poll for changes since Storybook might change background without popstate
603
- const intervalId = setInterval(detectTheme, 500);
604
- const observer = new MutationObserver(detectTheme);
605
- observer.observe(document.body, { attributes: true, attributeFilter: ['class', 'data-theme', 'style'] });
606
- observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'data-theme', 'style'] });
569
+ detectManagerTheme();
570
+ // Poll for changes (manager theme changes are rare but possible)
571
+ const intervalId = setInterval(detectManagerTheme, 1000);
572
+ // Listen for system preference changes
607
573
  const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
608
- mediaQuery.addEventListener('change', detectTheme);
574
+ mediaQuery.addEventListener('change', detectManagerTheme);
575
+ // Observe parent document for theme changes if accessible
576
+ let parentObserver = null;
577
+ try {
578
+ if (window.parent !== window) {
579
+ parentObserver = new MutationObserver(detectManagerTheme);
580
+ parentObserver.observe(window.parent.document.body, { attributes: true, attributeFilter: ['class', 'data-theme'] });
581
+ parentObserver.observe(window.parent.document.documentElement, { attributes: true, attributeFilter: ['class', 'data-theme'] });
582
+ }
583
+ }
584
+ catch {
585
+ // Cross-origin, ignore
586
+ }
609
587
  return () => {
610
- window.removeEventListener('popstate', detectTheme);
611
588
  clearInterval(intervalId);
612
- observer.disconnect();
613
- mediaQuery.removeEventListener('change', detectTheme);
589
+ mediaQuery.removeEventListener('change', detectManagerTheme);
590
+ parentObserver?.disconnect();
614
591
  };
615
592
  }, []);
616
593
  // Close context menu when clicking outside
@@ -0,0 +1,84 @@
1
+ {/*
2
+ Story UI Panel - MDX Documentation Page
3
+
4
+ This MDX file renders the Story UI Panel as a standalone documentation page.
5
+ Using MDX instead of a .stories.tsx file ensures the React component renders
6
+ correctly across ALL framework Storybooks (React, Vue, Svelte, Angular, Web Components).
7
+
8
+ Why this works:
9
+ - MDX is processed by @storybook/addon-docs which always uses React
10
+ - Regular .stories.tsx files render in the Preview iframe using the project's framework
11
+ - Non-React frameworks cannot render React components in their Preview
12
+ - MDX pages bypass this limitation by being compiled with React directly
13
+
14
+ Port Configuration:
15
+ - The port is automatically read from VITE_STORY_UI_PORT environment variable
16
+ - This is set during `npx story-ui init` and stored in .env
17
+ - URL parameter override: ?mcp-port=XXXX
18
+
19
+ Reference: Steve Dodier-Lazaro (Storybook team) recommendation
20
+ */}
21
+
22
+ import { Meta } from '@storybook/addon-docs/blocks';
23
+ import { StoryUIPanel } from './StoryUIPanel';
24
+ import { useEffect, useState, useRef } from 'react';
25
+
26
+ <Meta title="Story UI/Story Generator" />
27
+
28
+ export const StoryUIPanelWrapper = () => {
29
+ const [isReady, setIsReady] = useState(false);
30
+ const hasInitialized = useRef(false);
31
+
32
+ useEffect(() => {
33
+ // Only run once on mount
34
+ if (hasInitialized.current) return;
35
+ hasInitialized.current = true;
36
+
37
+ if (typeof window !== 'undefined') {
38
+ // Check for URL parameter override for MCP port
39
+ const urlParams = new URLSearchParams(window.location.search);
40
+ const mcpPortParam = urlParams.get('mcp-port');
41
+
42
+ if (mcpPortParam) {
43
+ // URL parameter takes highest priority
44
+ window.STORY_UI_MCP_PORT = mcpPortParam;
45
+ }
46
+ // Otherwise, let StoryUIPanel.tsx's getApiBaseUrl() handle port detection
47
+ // from VITE_STORY_UI_PORT environment variable (set during story-ui init)
48
+ }
49
+
50
+ setIsReady(true);
51
+ }, []);
52
+
53
+ // Don't render until initialized to prevent hydration issues
54
+ if (!isReady) {
55
+ return (
56
+ <div style={{
57
+ display: 'flex',
58
+ alignItems: 'center',
59
+ justifyContent: 'center',
60
+ height: '100vh',
61
+ color: '#666'
62
+ }}>
63
+ Loading Story UI...
64
+ </div>
65
+ );
66
+ }
67
+
68
+ return <StoryUIPanel />;
69
+ };
70
+
71
+ {/*
72
+ Full-screen container to match the original story layout.
73
+ The StoryUIPanel component handles its own internal styling.
74
+ */}
75
+ <div style={{
76
+ position: 'fixed',
77
+ top: 0,
78
+ left: 0,
79
+ right: 0,
80
+ bottom: 0,
81
+ overflow: 'hidden'
82
+ }}>
83
+ <StoryUIPanelWrapper />
84
+ </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpitre/story-ui",
3
- "version": "4.5.2",
3
+ "version": "4.6.0",
4
4
  "description": "AI-powered Storybook story generator with dynamic component discovery",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,7 +26,8 @@
26
26
  "LICENSE"
27
27
  ],
28
28
  "scripts": {
29
- "build": "tsc",
29
+ "build": "tsc && npm run copy-templates",
30
+ "copy-templates": "node -e \"const fs=require('fs');const path=require('path');const src='templates/StoryUI';const dst='dist/templates/StoryUI';['.css','.mdx'].forEach(ext=>{fs.readdirSync(src).filter(f=>f.endsWith(ext)).forEach(f=>fs.copyFileSync(path.join(src,f),path.join(dst,f)))});console.log('Static template files copied to dist/')\"",
30
31
  "start": "yarn build && node dist/mcp-server/index.js",
31
32
  "mcp": "yarn build && node dist/mcp-server/mcp-stdio-server.js",
32
33
  "dev": "tsc --watch",
@@ -26,7 +26,7 @@
26
26
  --secondary: 240 4.8% 95.9%;
27
27
  --secondary-foreground: 240 5.9% 10%;
28
28
  --muted: 240 4.8% 95.9%;
29
- --muted-foreground: 240 3.8% 46.1%;
29
+ --muted-foreground: 240 5% 35%; /* Darker for WCAG AA compliance - was 46.1% */
30
30
  --accent: 240 4.8% 95.9%;
31
31
  --accent-foreground: 240 5.9% 10%;
32
32
  --destructive: 0 84.2% 60.2%;
@@ -113,15 +113,15 @@
113
113
  --input: 217 33% 22%;
114
114
  --ring: 213 94% 68%;
115
115
 
116
- /* User Bubble - Light bluish-gray tint (same in dark mode for chat consistency) */
117
- --user-bubble-bg: 210 35% 95%;
118
- --user-bubble-fg: 240 10% 3.9%;
119
- --user-bubble-border: 210 25% 88%;
116
+ /* User Bubble - Dark bluish tint in dark mode */
117
+ --user-bubble-bg: 217 33% 22%;
118
+ --user-bubble-fg: 210 40% 98%;
119
+ --user-bubble-border: 217 33% 28%;
120
120
 
121
- /* AI Bubble - Pure white (same in dark mode for chat consistency) */
122
- --ai-bubble-bg: 0 0% 100%;
123
- --ai-bubble-fg: 240 10% 3.9%;
124
- --ai-bubble-border: 0 0% 90%;
121
+ /* AI Bubble - Dark zinc in dark mode */
122
+ --ai-bubble-bg: 240 4% 16%;
123
+ --ai-bubble-fg: 0 0% 98%;
124
+ --ai-bubble-border: 240 4% 26%;
125
125
 
126
126
  --success: 142 69% 58%;
127
127
  --warning: 48 96% 53%;
@@ -151,15 +151,15 @@
151
151
  --input: 217 33% 22%;
152
152
  --ring: 213 94% 68%;
153
153
 
154
- /* User Bubble - Light bluish-gray tint (same in dark mode for chat consistency) */
155
- --user-bubble-bg: 210 35% 95%;
156
- --user-bubble-fg: 240 10% 3.9%;
157
- --user-bubble-border: 210 25% 88%;
154
+ /* User Bubble - Dark bluish tint in dark mode */
155
+ --user-bubble-bg: 217 33% 22%;
156
+ --user-bubble-fg: 210 40% 98%;
157
+ --user-bubble-border: 217 33% 28%;
158
158
 
159
- /* AI Bubble - Pure white (same in dark mode for chat consistency) */
160
- --ai-bubble-bg: 0 0% 100%;
161
- --ai-bubble-fg: 240 10% 3.9%;
162
- --ai-bubble-border: 0 0% 90%;
159
+ /* AI Bubble - Dark zinc in dark mode */
160
+ --ai-bubble-bg: 240 4% 16%;
161
+ --ai-bubble-fg: 0 0% 98%;
162
+ --ai-bubble-border: 240 4% 26%;
163
163
 
164
164
  --success: 142 69% 58%;
165
165
  --warning: 48 96% 53%;
@@ -168,18 +168,20 @@
168
168
  }
169
169
 
170
170
  /* ============================================
171
- Base Reset
171
+ Base Reset & CSS Isolation
172
172
  ============================================ */
173
- .sui-root,
174
- .sui-root *,
175
- .sui-root *::before,
176
- .sui-root *::after {
173
+
174
+ /* Isolate StoryUI from Storybook's theme CSS variables */
175
+ .sui-root {
176
+ /* Reset inherited properties to prevent Storybook theme bleeding */
177
+ all: revert;
178
+
179
+ /* Re-apply necessary layout */
177
180
  box-sizing: border-box;
178
181
  margin: 0;
179
182
  padding: 0;
180
- }
181
183
 
182
- .sui-root {
184
+ /* Typography */
183
185
  font-family: var(--font-sans);
184
186
  font-size: 14px;
185
187
  line-height: 1.5;
@@ -187,9 +189,28 @@
187
189
  background-color: hsl(var(--background));
188
190
  -webkit-font-smoothing: antialiased;
189
191
  -moz-osx-font-smoothing: grayscale;
192
+
193
+ /* Layout */
190
194
  height: 100%;
191
195
  width: 100%;
192
196
  display: flex;
197
+
198
+ /* Prevent inheriting Storybook's color scheme */
199
+ color-scheme: light;
200
+ }
201
+
202
+ .sui-root.dark {
203
+ color-scheme: dark;
204
+ }
205
+
206
+ .sui-root *,
207
+ .sui-root *::before,
208
+ .sui-root *::after {
209
+ box-sizing: border-box;
210
+ margin: 0;
211
+ padding: 0;
212
+ /* Prevent Storybook CSS variable inheritance */
213
+ color: inherit;
193
214
  }
194
215
 
195
216
  /* ============================================
@@ -405,58 +426,100 @@
405
426
  opacity: 0.5;
406
427
  }
407
428
 
408
- /* Button Variants - Storybook-style */
429
+ /* Button Variants - Storybook-style with explicit fallback colors */
409
430
  .sui-button-default {
410
- background: hsl(var(--secondary));
411
- color: hsl(var(--secondary-foreground));
412
- border: 1px solid hsl(var(--border));
431
+ /* Explicit fallback colors to prevent Storybook bleeding */
432
+ background: #f4f4f5; /* hsl(240 4.8% 95.9%) */
433
+ color: #18181b; /* hsl(240 5.9% 10%) */
434
+ border: 1px solid #e4e4e7; /* hsl(240 5.9% 90%) */
413
435
  border-radius: var(--radius-md);
414
436
  height: 2rem;
415
437
  padding: 0 var(--space-3);
416
438
  }
417
439
 
418
440
  .sui-button-default:hover {
419
- background: hsl(var(--accent));
420
- border-color: hsl(var(--muted-foreground) / 0.3);
441
+ background: #e4e4e7;
442
+ border-color: #a1a1aa;
443
+ }
444
+
445
+ /* Dark mode button defaults */
446
+ .sui-root.dark .sui-button-default {
447
+ background: #1e293b; /* hsl(217 33% 17%) */
448
+ color: #f8fafc; /* hsl(210 40% 98%) */
449
+ border-color: #334155; /* hsl(217 33% 22%) */
450
+ }
451
+
452
+ .sui-root.dark .sui-button-default:hover {
453
+ background: #334155;
454
+ border-color: #94a3b8;
421
455
  }
422
456
 
423
457
  .sui-button-secondary {
424
- background: hsl(var(--secondary));
425
- color: hsl(var(--secondary-foreground));
426
- border: 1px solid hsl(var(--border));
458
+ background: #f4f4f5;
459
+ color: #18181b;
460
+ border: 1px solid #e4e4e7;
427
461
  border-radius: var(--radius-md);
428
462
  height: 2rem;
429
463
  padding: 0 var(--space-3);
430
464
  }
431
465
 
432
466
  .sui-button-secondary:hover {
433
- background: hsl(var(--accent));
467
+ background: #e4e4e7;
468
+ }
469
+
470
+ .sui-root.dark .sui-button-secondary {
471
+ background: #1e293b;
472
+ color: #f8fafc;
473
+ border-color: #334155;
474
+ }
475
+
476
+ .sui-root.dark .sui-button-secondary:hover {
477
+ background: #334155;
434
478
  }
435
479
 
436
480
  .sui-button-ghost {
437
- color: hsl(var(--foreground));
481
+ color: #0a0a0b;
438
482
  border-radius: var(--radius-md);
439
483
  height: 2rem;
440
484
  padding: 0 var(--space-3);
441
485
  }
442
486
 
443
487
  .sui-button-ghost:hover {
444
- background: hsl(var(--accent));
445
- color: hsl(var(--accent-foreground));
488
+ background: #f4f4f5;
489
+ color: #18181b;
490
+ }
491
+
492
+ .sui-root.dark .sui-button-ghost {
493
+ color: #f8fafc;
494
+ }
495
+
496
+ .sui-root.dark .sui-button-ghost:hover {
497
+ background: #1e293b;
498
+ color: #f8fafc;
446
499
  }
447
500
 
448
501
  .sui-button-outline {
449
- border: 1px solid hsl(var(--border));
502
+ border: 1px solid #e4e4e7;
450
503
  background: transparent;
451
- color: hsl(var(--foreground));
504
+ color: #0a0a0b;
452
505
  border-radius: var(--radius-md);
453
506
  height: 2rem;
454
507
  padding: 0 var(--space-3);
455
508
  }
456
509
 
457
510
  .sui-button-outline:hover {
458
- background: hsl(var(--accent));
459
- color: hsl(var(--accent-foreground));
511
+ background: #f4f4f5;
512
+ color: #18181b;
513
+ }
514
+
515
+ .sui-root.dark .sui-button-outline {
516
+ border-color: #334155;
517
+ color: #f8fafc;
518
+ }
519
+
520
+ .sui-root.dark .sui-button-outline:hover {
521
+ background: #1e293b;
522
+ color: #f8fafc;
460
523
  }
461
524
 
462
525
  .sui-button-destructive {
@@ -747,27 +810,29 @@
747
810
  .sui-welcome-greeting {
748
811
  font-size: 1.75rem;
749
812
  font-weight: 600;
750
- color: hsl(var(--foreground));
813
+ /* Explicit high-contrast color - forces override of any inherited styles */
814
+ color: #18181b !important; /* hsl(240 6% 10%) - near black for maximum contrast */
751
815
  margin-bottom: var(--space-3);
752
816
  line-height: 1.3;
753
817
  }
754
818
 
755
819
  /* Dark mode: ensure greeting is clearly visible - WCAG AA compliant */
756
820
  .sui-root.dark .sui-welcome-greeting {
757
- color: #ffffff;
821
+ color: #ffffff !important;
758
822
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
759
823
  }
760
824
 
761
825
  .sui-welcome-subtitle {
762
826
  font-size: 1rem;
763
- color: hsl(var(--muted-foreground));
827
+ /* Explicit high-contrast color - WCAG AA compliant */
828
+ color: #52525b !important; /* hsl(240 5% 35%) - ~8:1 contrast on white */
764
829
  margin-bottom: var(--space-8);
765
830
  max-width: 400px;
766
831
  }
767
832
 
768
833
  /* Dark mode: make subtitle more visible */
769
834
  .sui-root.dark .sui-welcome-subtitle {
770
- color: hsl(210 25% 80%);
835
+ color: #cbd5e1 !important; /* hsl(213 27% 84%) - high contrast on dark bg */
771
836
  }
772
837
 
773
838
  .sui-welcome-chips {
@@ -786,26 +851,39 @@
786
851
  align-items: center;
787
852
  justify-content: center;
788
853
  padding: 0.5rem 1rem;
789
- background: hsl(var(--secondary));
790
- border: 1px solid hsl(var(--border));
854
+ /* Explicit high-contrast colors - forces override of inherited styles */
855
+ background: #f4f4f5 !important; /* hsl(240 4.8% 95.9%) */
856
+ border: 1px solid #d4d4d8 !important; /* hsl(240 5% 84%) - slightly darker border */
791
857
  border-radius: var(--radius-full);
792
858
  font-size: 0.875rem;
793
859
  font-weight: 500;
794
860
  line-height: 1;
795
- color: hsl(var(--foreground));
861
+ color: #18181b !important; /* hsl(240 6% 10%) - near black for high contrast */
796
862
  cursor: pointer;
797
863
  transition: all var(--transition-fast);
798
864
  }
799
865
 
800
866
  .sui-chip:hover {
801
- background: hsl(var(--accent));
802
- border-color: hsl(var(--muted-foreground) / 0.3);
867
+ background: #e4e4e7; /* hsl(240 4.8% 95.9%) */
868
+ border-color: #a1a1aa; /* hsl(240 3.8% 46.1% / 0.3) */
803
869
  }
804
870
 
805
871
  .sui-chip:active {
806
872
  transform: scale(0.98);
807
873
  }
808
874
 
875
+ /* Dark mode chips */
876
+ .sui-root.dark .sui-chip {
877
+ background: #1e293b !important; /* hsl(217 33% 17%) */
878
+ border-color: #475569 !important; /* hsl(215 19% 35%) - more visible border */
879
+ color: #f8fafc !important; /* hsl(210 40% 98%) - near white */
880
+ }
881
+
882
+ .sui-root.dark .sui-chip:hover {
883
+ background: #334155;
884
+ border-color: #94a3b8;
885
+ }
886
+
809
887
  /* ============================================
810
888
  Messages
811
889
  ============================================ */
@@ -852,14 +930,34 @@
852
930
  border-bottom-right-radius: var(--radius-sm);
853
931
  }
854
932
 
855
- /* AI bubble - pure white */
933
+ /* User bubble - dark mode: dark bluish tint with light text */
934
+ .sui-root.dark .sui-message-user .sui-message-bubble {
935
+ background: #2d3748 !important; /* Dark bluish - hsl(217 33% 22%) */
936
+ color: #f7fafc !important; /* Near white - hsl(210 40% 98%) */
937
+ border: 1px solid #4a5568 !important; /* Dark border - hsl(217 33% 28%) */
938
+ }
939
+
940
+ /* AI bubble - pure white with explicit high-contrast colors */
856
941
  .sui-message-ai .sui-message-bubble {
857
- background: hsl(var(--ai-bubble-bg));
858
- color: hsl(var(--ai-bubble-fg));
859
- border-color: hsl(var(--ai-bubble-border));
942
+ background: #ffffff !important; /* Pure white */
943
+ color: #18181b !important; /* Near black - high contrast */
944
+ border: 1px solid #e4e4e7 !important; /* Light gray border */
860
945
  border-bottom-left-radius: var(--radius-sm);
861
946
  }
862
947
 
948
+ /* AI bubble - dark mode: dark background with light text */
949
+ .sui-root.dark .sui-message-ai .sui-message-bubble {
950
+ background: #27272a !important; /* Dark zinc - hsl(240 4% 16%) */
951
+ color: #fafafa !important; /* Near white - hsl(0 0% 98%) */
952
+ border: 1px solid #3f3f46 !important; /* Dark border - hsl(240 4% 26%) */
953
+ }
954
+
955
+ /* AI bubble code in dark mode */
956
+ .sui-root.dark .sui-message-ai .sui-message-bubble code {
957
+ background: rgba(63, 63, 70, 0.5) !important; /* Semi-transparent dark */
958
+ color: #e4e4e7 !important; /* Light gray text */
959
+ }
960
+
863
961
  /*
864
962
  * Markdown content styling
865
963
  * Using .sui-message-bubble prefix for higher specificity (0,2,x)