diff --git a/frontend/src/components/MonacoEditor.theme.test.ts b/frontend/src/components/MonacoEditor.theme.test.ts
new file mode 100644
index 00000000..4fc49d24
--- /dev/null
+++ b/frontend/src/components/MonacoEditor.theme.test.ts
@@ -0,0 +1,66 @@
+import { describe, expect, it, vi } from 'vitest';
+
+import { BUILTIN_CUSTOM_THEME_PRESETS } from '../utils/customThemePresets';
+import { registerGonaviMonacoThemes } from './MonacoEditor';
+
+const readHexProperty = (css: string, property: string): string => {
+ const escaped = property.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ const match = css.match(new RegExp(`${escaped}\\s*:\\s*(#[0-9a-f]{6})`, 'i'));
+ if (!match?.[1]) throw new Error(`Missing hexadecimal custom property: ${property}`);
+ return match[1];
+};
+
+const relativeLuminance = (hex: string): number => {
+ const channels = [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16) / 255);
+ const [red, green, blue] = channels.map((channel) => (
+ channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4
+ ));
+ return 0.2126 * red + 0.7152 * green + 0.0722 * blue;
+};
+
+const contrastRatio = (foreground: string, background: string): number => {
+ const light = Math.max(relativeLuminance(foreground), relativeLuminance(background));
+ const dark = Math.min(relativeLuminance(foreground), relativeLuminance(background));
+ return (light + 0.05) / (dark + 0.05);
+};
+
+describe('GoNavi Monaco themes', () => {
+ it('defines bold SQL keyword colors that remain AA-readable across every built-in preset', () => {
+ const defineTheme = vi.fn();
+ registerGonaviMonacoThemes({ editor: { defineTheme } } as never);
+
+ const definitions = new Map(
+ defineTheme.mock.calls.map(([name, definition]) => [name, definition]),
+ );
+ const keywordTokens = [
+ 'keyword.sql',
+ 'keyword.try.sql',
+ 'keyword.catch.sql',
+ 'keyword.block.sql',
+ 'keyword.choice.sql',
+ ];
+ const lightRules = definitions.get('transparent-light')?.rules ?? [];
+ const darkRules = definitions.get('transparent-dark')?.rules ?? [];
+
+ for (const token of keywordTokens) {
+ expect(lightRules.find((rule: any) => rule.token === token)).toMatchObject({
+ foreground: '6D28D9',
+ fontStyle: 'bold',
+ });
+ expect(darkRules.find((rule: any) => rule.token === token)).toMatchObject({
+ foreground: 'C792EA',
+ fontStyle: 'bold',
+ });
+ }
+
+ for (const preset of BUILTIN_CUSTOM_THEME_PRESETS) {
+ const keyword = (preset.baseMode === 'dark' ? darkRules : lightRules)
+ .find((rule: any) => rule.token === 'keyword.sql');
+ const background = readHexProperty(preset.css, '--gn-bg-input');
+ expect(
+ contrastRatio(`#${keyword.foreground}`, background),
+ `${preset.id} SQL keyword must contrast with its editor background`,
+ ).toBeGreaterThanOrEqual(4.5);
+ }
+ });
+});
diff --git a/frontend/src/components/MonacoEditor.tsx b/frontend/src/components/MonacoEditor.tsx
index 6d0fdf53..6383642a 100644
--- a/frontend/src/components/MonacoEditor.tsx
+++ b/frontend/src/components/MonacoEditor.tsx
@@ -495,7 +495,13 @@ export const registerGonaviMonacoThemes: BeforeMount = (monaco) => {
monaco.editor.defineTheme('transparent-dark', {
base: 'vs-dark',
inherit: true,
- rules: [],
+ rules: [
+ { token: 'keyword.sql', foreground: 'C792EA', fontStyle: 'bold' },
+ { token: 'keyword.try.sql', foreground: 'C792EA', fontStyle: 'bold' },
+ { token: 'keyword.catch.sql', foreground: 'C792EA', fontStyle: 'bold' },
+ { token: 'keyword.block.sql', foreground: 'C792EA', fontStyle: 'bold' },
+ { token: 'keyword.choice.sql', foreground: 'C792EA', fontStyle: 'bold' },
+ ],
colors: {
'editor.background': '#00000000',
'editor.lineHighlightBackground': '#ffffff10',
@@ -507,7 +513,13 @@ export const registerGonaviMonacoThemes: BeforeMount = (monaco) => {
monaco.editor.defineTheme('transparent-light', {
base: 'vs',
inherit: true,
- rules: [],
+ rules: [
+ { token: 'keyword.sql', foreground: '6D28D9', fontStyle: 'bold' },
+ { token: 'keyword.try.sql', foreground: '6D28D9', fontStyle: 'bold' },
+ { token: 'keyword.catch.sql', foreground: '6D28D9', fontStyle: 'bold' },
+ { token: 'keyword.block.sql', foreground: '6D28D9', fontStyle: 'bold' },
+ { token: 'keyword.choice.sql', foreground: '6D28D9', fontStyle: 'bold' },
+ ],
colors: {
'editor.background': '#00000000',
'editor.lineHighlightBackground': '#00000010',
diff --git a/frontend/src/components/Sidebar.locate-toolbar.test.tsx b/frontend/src/components/Sidebar.locate-toolbar.test.tsx
index 38f1b79a..dc82b158 100644
--- a/frontend/src/components/Sidebar.locate-toolbar.test.tsx
+++ b/frontend/src/components/Sidebar.locate-toolbar.test.tsx
@@ -1260,8 +1260,8 @@ describe('Sidebar locate toolbar', () => {
expect(v2TreeTitleCss).toContain('min-width: 100%;');
expect(v2TreeTitleCss).toContain('overflow: visible;');
expect(css).toMatch(/\.gn-v2-tree-status \{[^}]*width: 14px;[^}]*height: 14px;[^}]*flex: 0 0 14px;[^}]*overflow: visible;/s);
- expect(css).toMatch(/\.gn-v2-tree-status::before \{[^}]*width: 7px;[^}]*height: 7px;[^}]*border-radius: 50%;/s);
- expect(css).toMatch(/\.gn-v2-tree-status\.is-success::before \{[^}]*background: #22c55e;[^}]*box-shadow: 0 0 0 4px rgba\(34, 197, 94, 0\.18\);/s);
+ expect(css).toMatch(/\.gn-v2-tree-status::before \{[^}]*width: 9px;[^}]*height: 9px;[^}]*border: 1\.5px solid var\(--gn-fg-4\);[^}]*border-radius: 50%;/s);
+ expect(css).toMatch(/\.gn-v2-tree-status\.is-success::before \{[^}]*border: 0;[^}]*background: var\(--gn-status-connected\);[^}]*box-shadow: 0 0 0 3px color-mix\(in srgb, var\(--gn-status-connected\) 22%, transparent\);/s);
const treeLabelCss = readCssRuleBlock(css, 'body[data-ui-version="v2"] .gn-v2-tree-label');
expect(treeLabelCss).toContain('flex: 0 0 auto;');
expect(treeLabelCss).toContain('overflow: visible;');
diff --git a/frontend/src/components/TabManager.hover.test.tsx b/frontend/src/components/TabManager.hover.test.tsx
index b0968cb6..013b80cf 100644
--- a/frontend/src/components/TabManager.hover.test.tsx
+++ b/frontend/src/components/TabManager.hover.test.tsx
@@ -289,6 +289,9 @@ describe('TabManager hover info', () => {
expect(source).toContain('buildTabDisplayModel(tab, connection, appearance.tabDisplay, t)');
expect(source).toContain('displayModel={displayModel}');
expect(source).toContain('displayModel.primaryParts.map(renderV2TabDisplayPart)');
+ expect(source).toContain('renderV2TabSecondaryParts(displayModel.secondaryParts)');
+ expect(source).toContain('aria-label={displayModel.secondaryText}');
+ expect(source).toContain('className="gn-v2-tab-label-separator" aria-hidden="true">·');
expect(source).toContain("if (part.key === 'kind')");
expect(source).toContain('className="gn-v2-tab-kind"');
expect(source).toContain('hasDoubleLineTabLabel');
diff --git a/frontend/src/components/TabManager.tsx b/frontend/src/components/TabManager.tsx
index 3f4d6545..5593b5a6 100644
--- a/frontend/src/components/TabManager.tsx
+++ b/frontend/src/components/TabManager.tsx
@@ -260,6 +260,13 @@ const renderV2TabDisplayPart = (part: TabDisplayPart) => {
);
};
+const renderV2TabSecondaryParts = (parts: TabDisplayPart[]) => parts.map((part, index) => (
+
+ {index > 0 ? · : null}
+ {renderV2TabDisplayPart(part)}
+
+));
+
const SortableTabLabel: React.FC = ({
tab,
displayModel,
@@ -304,8 +311,12 @@ const SortableTabLabel: React.FC = ({
: displayModel.primaryText}
{showSecondaryLine ? (
-
- {displayModel.secondaryText}
+
+ {renderV2TabSecondaryParts(displayModel.secondaryParts)}
) : null}
diff --git a/frontend/src/utils/customThemePresets.test.ts b/frontend/src/utils/customThemePresets.test.ts
index 4ceca9cd..d07bcda4 100644
--- a/frontend/src/utils/customThemePresets.test.ts
+++ b/frontend/src/utils/customThemePresets.test.ts
@@ -50,6 +50,9 @@ describe('built-in custom theme presets', () => {
expect(preset.css).toContain('--gn-ant-on-primary:');
expect(preset.css).toContain('--gn-settings-card-bg:');
expect(preset.css).toContain('--gn-explain-critical:');
+ expect(preset.css).toContain('--gn-status-connected:');
+ expect(preset.css).toContain('.gn-v2-tab-label-part-host');
+ expect(preset.css).toContain('.gn-v2-tab-label-part-database');
}
expect(BUILTIN_CUSTOM_THEME_PRESETS.filter((preset) => preset.baseMode === 'dark')).toHaveLength(4);
expect(BUILTIN_CUSTOM_THEME_PRESETS.filter((preset) => preset.baseMode === 'light')).toHaveLength(2);
@@ -72,6 +75,7 @@ describe('built-in custom theme presets', () => {
it('keeps preset text and solid-button colors at WCAG AA contrast', () => {
for (const preset of BUILTIN_CUSTOM_THEME_PRESETS) {
const panel = readHexProperty(preset.css, '--gn-bg-panel');
+ const panel2 = readHexProperty(preset.css, '--gn-bg-panel-2');
for (const property of [
'--gn-fg-1',
'--gn-fg-2',
@@ -84,6 +88,7 @@ describe('built-in custom theme presets', () => {
'--gn-warn',
'--gn-danger',
'--gn-purple',
+ '--gn-status-connected',
]) {
const color = readHexProperty(preset.css, property);
expect(
@@ -91,6 +96,12 @@ describe('built-in custom theme presets', () => {
`${preset.id} ${property} must contrast with --gn-bg-panel`,
).toBeGreaterThanOrEqual(4.5);
}
+ for (const property of ['--gn-info', '--gn-accent', '--gn-status-connected']) {
+ expect(
+ contrastRatio(readHexProperty(preset.css, property), panel2),
+ `${preset.id} ${property} must contrast with --gn-bg-panel-2`,
+ ).toBeGreaterThanOrEqual(4.5);
+ }
const onAccent = readHexProperty(preset.css, '--gn-on-accent');
for (const property of ['--gn-accent', '--gn-accent-2']) {
diff --git a/frontend/src/utils/customThemePresets.ts b/frontend/src/utils/customThemePresets.ts
index 8714dd1e..67065def 100644
--- a/frontend/src/utils/customThemePresets.ts
+++ b/frontend/src/utils/customThemePresets.ts
@@ -62,7 +62,7 @@ export type BuiltinCustomThemePreset = CustomThemeDefinition & {
};
};
-const BUILTIN_THEME_REVISION = 2026071101;
+const BUILTIN_THEME_REVISION = 2026071301;
const createBuiltinThemeCss = (id: string, palette: BuiltinThemePalette): string => `/* GoNavi built-in theme: ${id} */
body[data-custom-theme],
@@ -107,6 +107,7 @@ body[data-custom-theme][data-ui-version="v2"] {
--gn-danger-strong: ${palette.dangerStrong};
--gn-danger-strong-hover: ${palette.dangerHover};
--gn-on-danger: ${palette.onDanger};
+ --gn-status-connected: ${palette.mode === 'dark' ? '#4ade80' : '#15803d'};
--gn-purple: ${palette.purple};
--gn-purple-soft: ${palette.purpleSoft};
@@ -217,8 +218,8 @@ body[data-custom-theme][data-ui-version="v2"] .gn-v2-ai-quick-card.tone-purple .
body[data-custom-theme][data-ui-version="v2"] .gn-v2-live-dot.is-live,
body[data-custom-theme][data-ui-version="v2"] .gn-v2-rail-status.is-live,
body[data-custom-theme][data-ui-version="v2"] .gn-v2-tree-status.is-success::before {
- background: var(--gn-accent-text, var(--gn-accent)) !important;
- box-shadow: 0 0 0 3px var(--gn-accent-soft) !important;
+ background: var(--gn-status-connected) !important;
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--gn-status-connected) 22%, transparent) !important;
}
body[data-custom-theme][data-ui-version="v2"] .gn-v2-live-dot.is-loading,
@@ -227,6 +228,14 @@ body[data-custom-theme][data-ui-version="v2"] .gn-v2-tree-status.is-loading::bef
border-top-color: var(--gn-info) !important;
}
+body[data-custom-theme][data-ui-version="v2"] .gn-v2-tab-label-part-host {
+ color: var(--gn-info) !important;
+}
+
+body[data-custom-theme][data-ui-version="v2"] .gn-v2-tab-label-part-database {
+ color: var(--gn-accent) !important;
+}
+
body[data-custom-theme][data-ui-version="v2"] .monaco-editor,
body[data-custom-theme][data-ui-version="v2"] .monaco-editor-background,
body[data-custom-theme][data-ui-version="v2"] .monaco-editor .margin,
@@ -309,7 +318,7 @@ export const BUILTIN_CUSTOM_THEME_PRESETS: readonly BuiltinCustomThemePreset[] =
fg1: '#eceff4', fg2: '#d8dee9', fg3: '#c1cad8', fg4: '#9aa7b8', fg5: '#929ead',
border1: 'rgba(216, 222, 233, 0.07)', border2: 'rgba(216, 222, 233, 0.12)', border3: 'rgba(216, 222, 233, 0.20)',
accent: '#88c0d0', accent2: '#6faabb', accentSoft: 'rgba(136, 192, 208, 0.16)', accentSoftHover: 'rgba(136, 192, 208, 0.24)', accentOutline: 'rgba(136, 192, 208, 0.42)', onAccent: '#17252a',
- info: '#81a1c1', infoSoft: 'rgba(129, 161, 193, 0.17)', onInfo: '#17212b', warn: '#ebcb8b', warnSoft: 'rgba(235, 203, 139, 0.16)', danger: '#df858d', dangerStrong: '#a94f59', dangerHover: '#943f49', onDanger: '#ffffff', purple: '#b993b2', purpleSoft: 'rgba(185, 147, 178, 0.17)',
+ info: '#89add0', infoSoft: 'rgba(137, 173, 208, 0.17)', onInfo: '#17212b', warn: '#ebcb8b', warnSoft: 'rgba(235, 203, 139, 0.16)', danger: '#df858d', dangerStrong: '#a94f59', dangerHover: '#943f49', onDanger: '#ffffff', purple: '#b993b2', purpleSoft: 'rgba(185, 147, 178, 0.17)',
shadowSm: '0 1px 2px rgba(20, 24, 31, 0.24)', shadowMd: '0 4px 14px rgba(20, 24, 31, 0.32)', shadowLg: '0 12px 38px rgba(20, 24, 31, 0.42)', shadowCard: '0 0 0 0.5px rgba(216, 222, 233, 0.08), 0 1px 3px rgba(20, 24, 31, 0.25)',
kbdBg: '#3b4351', kbdFg: '#d8dee9',
},
@@ -340,8 +349,8 @@ export const BUILTIN_CUSTOM_THEME_PRESETS: readonly BuiltinCustomThemePreset[] =
hover: 'rgba(67, 59, 49, 0.05)', active: 'rgba(67, 59, 49, 0.09)', selected: 'rgba(47, 125, 104, 0.14)',
fg1: '#292722', fg2: '#45413a', fg3: '#655f55', fg4: '#766e63', fg5: '#7a7267',
border1: 'rgba(67, 59, 49, 0.08)', border2: 'rgba(67, 59, 49, 0.13)', border3: 'rgba(67, 59, 49, 0.20)',
- accent: '#2f7d68', accent2: '#236553', accentSoft: '#dcede6', accentSoftHover: '#cce4da', accentOutline: 'rgba(47, 125, 104, 0.30)', onAccent: '#ffffff',
- info: '#3976a8', infoSoft: '#ddeaf4', onInfo: '#ffffff', warn: '#9f5f1d', warnSoft: '#f2e4cf', danger: '#b94a4a', dangerStrong: '#a83c3c', dangerHover: '#923333', onDanger: '#ffffff', purple: '#765b93', purpleSoft: '#eae1f2',
+ accent: '#2d7864', accent2: '#236553', accentSoft: '#dcede6', accentSoftHover: '#cce4da', accentOutline: 'rgba(45, 120, 100, 0.30)', onAccent: '#ffffff',
+ info: '#3572a4', infoSoft: '#ddeaf4', onInfo: '#ffffff', warn: '#9f5f1d', warnSoft: '#f2e4cf', danger: '#b94a4a', dangerStrong: '#a83c3c', dangerHover: '#923333', onDanger: '#ffffff', purple: '#765b93', purpleSoft: '#eae1f2',
shadowSm: '0 1px 2px rgba(67, 59, 49, 0.07)', shadowMd: '0 4px 14px rgba(67, 59, 49, 0.10)', shadowLg: '0 12px 36px rgba(67, 59, 49, 0.16)', shadowCard: '0 0 0 0.5px rgba(67, 59, 49, 0.10), 0 1px 3px rgba(67, 59, 49, 0.07)',
kbdBg: '#ece5d9', kbdFg: '#45413a',
},
diff --git a/frontend/src/v2-theme.contrast.test.ts b/frontend/src/v2-theme.contrast.test.ts
new file mode 100644
index 00000000..3312f1cd
--- /dev/null
+++ b/frontend/src/v2-theme.contrast.test.ts
@@ -0,0 +1,61 @@
+import { readFileSync } from 'node:fs';
+import { describe, expect, it } from 'vitest';
+
+const source = readFileSync(new URL('./v2-theme.css', import.meta.url), 'utf8');
+
+const readRuleBlock = (selector: string): string => {
+ const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ const match = source.match(new RegExp(`${escaped}\\s*\\{([^}]*)\\}`));
+ if (!match?.[1]) throw new Error(`Missing CSS rule: ${selector}`);
+ return match[1];
+};
+
+const readHexProperty = (block: string, property: string): string => {
+ const escaped = property.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ const match = block.match(new RegExp(`${escaped}\\s*:\\s*(#[0-9a-f]{6})`, 'i'));
+ if (!match?.[1]) throw new Error(`Missing hexadecimal custom property: ${property}`);
+ return match[1];
+};
+
+const relativeLuminance = (hex: string): number => {
+ const channels = [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16) / 255);
+ const [red, green, blue] = channels.map((channel) => (
+ channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4
+ ));
+ return 0.2126 * red + 0.7152 * green + 0.0722 * blue;
+};
+
+const contrastRatio = (foreground: string, background: string): number => {
+ const light = Math.max(relativeLuminance(foreground), relativeLuminance(background));
+ const dark = Math.min(relativeLuminance(foreground), relativeLuminance(background));
+ return (light + 0.05) / (dark + 0.05);
+};
+
+describe('v2 theme connection identity contrast', () => {
+ it('uses dedicated connected-state tokens and distinct Host/DB tab colors', () => {
+ const light = readRuleBlock('body[data-ui-version="v2"][data-theme="light"]');
+ const dark = readRuleBlock('body[data-ui-version="v2"][data-theme="dark"]');
+ const host = readRuleBlock('body[data-ui-version="v2"] .gn-v2-tab-label-part-host');
+ const database = readRuleBlock('body[data-ui-version="v2"] .gn-v2-tab-label-part-database');
+ const liveDot = readRuleBlock('body[data-ui-version="v2"] .gn-v2-live-dot');
+ const treeDot = readRuleBlock('body[data-ui-version="v2"] .gn-v2-tree-status::before');
+
+ expect(host).toContain('color: var(--gn-info);');
+ expect(database).toContain('color: var(--gn-accent);');
+ expect(liveDot).toContain('width: 10px;');
+ expect(liveDot).toContain('height: 10px;');
+ expect(treeDot).toContain('width: 9px;');
+ expect(treeDot).toContain('height: 9px;');
+ expect(source).toContain('background: var(--gn-status-connected);');
+
+ for (const [mode, block] of [['light', light], ['dark', dark]] as const) {
+ const panel2 = readHexProperty(block, '--gn-bg-panel-2');
+ for (const property of ['--gn-info', '--gn-accent', '--gn-status-connected']) {
+ expect(
+ contrastRatio(readHexProperty(block, property), panel2),
+ `${mode} ${property} must contrast with --gn-bg-panel-2`,
+ ).toBeGreaterThanOrEqual(4.5);
+ }
+ }
+ });
+});
diff --git a/frontend/src/v2-theme.css b/frontend/src/v2-theme.css
index 59dabdef..7930957a 100644
--- a/frontend/src/v2-theme.css
+++ b/frontend/src/v2-theme.css
@@ -43,16 +43,17 @@ body[data-ui-version="v2"][data-theme="light"] {
--gn-br-2: rgba(15, 23, 42, 0.12);
--gn-br-3: rgba(15, 23, 42, 0.18);
- --gn-accent: #16a34a;
- --gn-accent-2: #15803d;
+ --gn-accent: #15803d;
+ --gn-accent-2: #166534;
--gn-accent-soft: #dcfce7;
- --gn-info: #0284c7;
+ --gn-info: #0369a1;
--gn-info-soft: #e0f2fe;
--gn-warn: #d97706;
--gn-warn-soft: rgba(245, 158, 11, 0.14);
--gn-danger: #dc2626;
--gn-danger-strong: #dc2626;
--gn-danger-strong-hover: #b91c1c;
+ --gn-status-connected: #15803d;
--gn-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.05);
--gn-shadow-md: 0 4px 14px rgba(15, 23, 42, 0.08);
@@ -97,6 +98,7 @@ body[data-ui-version="v2"][data-theme="dark"] {
--gn-danger: #f87171;
--gn-danger-strong: #dc2626;
--gn-danger-strong-hover: #b91c1c;
+ --gn-status-connected: #4ade80;
--gn-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
--gn-shadow-md: 0 4px 14px rgba(0, 0, 0, 0.45);
@@ -2073,7 +2075,7 @@ body[data-ui-version="v2"] .gn-v2-rail-status {
}
body[data-ui-version="v2"] .gn-v2-rail-status.is-live {
- background: var(--gn-accent);
+ background: var(--gn-status-connected);
}
body[data-ui-version="v2"] .gn-v2-rail-status.is-error {
@@ -2150,16 +2152,19 @@ body[data-ui-version="v2"] .gn-v2-active-connection-trigger {
}
body[data-ui-version="v2"] .gn-v2-live-dot {
- width: 7px;
- height: 7px;
+ width: 10px;
+ height: 10px;
+ box-sizing: border-box;
+ border: 1.5px solid var(--gn-fg-4);
border-radius: 50%;
flex-shrink: 0;
- background: var(--gn-fg-5);
+ background: transparent;
}
body[data-ui-version="v2"] .gn-v2-live-dot.is-live {
- background: #22c55e;
- box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18);
+ border: 0;
+ background: var(--gn-status-connected);
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--gn-status-connected) 22%, transparent);
}
body[data-ui-version="v2"] .gn-v2-live-dot.is-loading {
@@ -2172,7 +2177,9 @@ body[data-ui-version="v2"] .gn-v2-live-dot.is-loading {
}
body[data-ui-version="v2"] .gn-v2-live-dot.is-error {
+ border: 0;
background: var(--gn-danger);
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--gn-danger) 20%, transparent);
}
body[data-ui-version="v2"] .gn-v2-active-connection-copy {
@@ -2856,16 +2863,19 @@ body[data-ui-version="v2"] .gn-v2-tree-status {
body[data-ui-version="v2"] .gn-v2-tree-status::before {
content: '';
- width: 7px;
- height: 7px;
+ width: 9px;
+ height: 9px;
+ box-sizing: border-box;
+ border: 1.5px solid var(--gn-fg-4);
border-radius: 50%;
- flex: 0 0 7px;
- background: #b8c0cc;
+ flex: 0 0 9px;
+ background: transparent;
}
body[data-ui-version="v2"] .gn-v2-tree-status.is-success::before {
- background: #22c55e;
- box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.18);
+ border: 0;
+ background: var(--gn-status-connected);
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--gn-status-connected) 22%, transparent);
}
body[data-ui-version="v2"] .gn-v2-tree-status.is-loading::before {
@@ -2880,8 +2890,9 @@ body[data-ui-version="v2"] .gn-v2-tree-status.is-loading::before {
}
body[data-ui-version="v2"] .gn-v2-tree-status.is-error::before {
+ border: 0;
background: var(--gn-danger);
- box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.16);
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--gn-danger) 20%, transparent);
}
@keyframes gn-v2-tree-status-spin {
@@ -3586,9 +3597,11 @@ body[data-ui-version="v2"] .gn-v2-tab-label-main {
body[data-ui-version="v2"] .gn-v2-tab-label-secondary {
min-width: 0;
- display: block;
+ display: flex;
+ align-items: center;
+ gap: 4px;
overflow: hidden;
- color: var(--gn-fg-5);
+ color: var(--gn-fg-4);
font-family: var(--gn-font-mono);
font-size: 10px;
font-weight: 650;
@@ -3626,6 +3639,19 @@ body[data-ui-version="v2"] .gn-v2-tab-label-part-schema {
max-width: 112px;
}
+body[data-ui-version="v2"] .gn-v2-tab-label-part-host {
+ color: var(--gn-info);
+}
+
+body[data-ui-version="v2"] .gn-v2-tab-label-part-database {
+ color: var(--gn-accent);
+}
+
+body[data-ui-version="v2"] .gn-v2-tab-label-separator {
+ flex: 0 0 auto;
+ color: var(--gn-fg-5);
+}
+
body[data-ui-version="v2"] .gn-v2-tab-kind-icon {
display: inline-flex;
color: var(--gn-accent);