@@ -670,6 +671,7 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) {
diff --git a/packages/app/src/components/DBDeltaChart.tsx b/packages/app/src/components/DBDeltaChart.tsx
index c4961f91..0777a06f 100644
--- a/packages/app/src/components/DBDeltaChart.tsx
+++ b/packages/app/src/components/DBDeltaChart.tsx
@@ -28,8 +28,8 @@ import { isAggregateFunction } from '@/ChartUtils';
import { useQueriedChartConfig } from '@/hooks/useChartConfig';
import { getFirstTimestampValueExpression } from '@/source';
import {
- CHART_COLOR_ERROR,
- CHART_COLOR_SUCCESS,
+ getChartColorError,
+ getChartColorSuccess,
truncateMiddle,
} from '@/utils';
@@ -250,13 +250,13 @@ function PropertyComparisonChart({
diff --git a/packages/app/src/components/DBTraceWaterfallChart.tsx b/packages/app/src/components/DBTraceWaterfallChart.tsx
index 4142b3c0..2334703a 100644
--- a/packages/app/src/components/DBTraceWaterfallChart.tsx
+++ b/packages/app/src/components/DBTraceWaterfallChart.tsx
@@ -40,10 +40,10 @@ import {
import TimelineChart from '@/TimelineChart';
import { useFormatTime } from '@/useFormatTime';
import {
- CHART_COLOR_ERROR,
- CHART_COLOR_ERROR_HIGHLIGHT,
- CHART_COLOR_WARNING,
- CHART_COLOR_WARNING_HIGHLIGHT,
+ getChartColorError,
+ getChartColorErrorHighlight,
+ getChartColorWarning,
+ getChartColorWarningHighlight,
} from '@/utils';
import {
getHighlightedAttributesFromData,
@@ -89,9 +89,11 @@ function barColor(condition: {
}) {
const { isError, isWarn, isHighlighted } = condition;
if (isError)
- return isHighlighted ? CHART_COLOR_ERROR_HIGHLIGHT : CHART_COLOR_ERROR;
+ return isHighlighted ? getChartColorErrorHighlight() : getChartColorError();
if (isWarn)
- return isHighlighted ? CHART_COLOR_WARNING_HIGHLIGHT : CHART_COLOR_WARNING;
+ return isHighlighted
+ ? getChartColorWarningHighlight()
+ : getChartColorWarning();
return isHighlighted ? '#A9AFB7' : '#6A7077';
}
diff --git a/packages/app/src/components/LogLevel.tsx b/packages/app/src/components/LogLevel.tsx
index 9cb1af3e..0bae0411 100644
--- a/packages/app/src/components/LogLevel.tsx
+++ b/packages/app/src/components/LogLevel.tsx
@@ -16,7 +16,7 @@ export default function LogLevel({
levelClass === 'error'
? 'red'
: levelClass === 'warn'
- ? 'yellow'
+ ? 'var(--color-chart-warning)'
: 'gray'
}
{...props}
diff --git a/packages/app/src/theme/ChartColors.stories.tsx b/packages/app/src/theme/ChartColors.stories.tsx
index 132e5596..56e17a25 100644
--- a/packages/app/src/theme/ChartColors.stories.tsx
+++ b/packages/app/src/theme/ChartColors.stories.tsx
@@ -1,10 +1,10 @@
import React from 'react';
import {
- CHART_COLOR_ERROR,
- CHART_COLOR_SUCCESS,
- CHART_COLOR_WARNING,
COLORS,
+ getChartColorError,
+ getChartColorSuccess,
+ getChartColorWarning,
} from '@/utils';
// Labels for chart colors - brand green first, then Observable palette
@@ -31,15 +31,19 @@ const CHART_COLORS = COLORS.map((hex, i) => ({
const SEMANTIC_CHART_COLORS = [
{
name: 'color-chart-success',
- hex: CHART_COLOR_SUCCESS,
+ hex: getChartColorSuccess(),
label: 'Success (Green)',
},
{
name: 'color-chart-warning',
- hex: CHART_COLOR_WARNING,
+ hex: getChartColorWarning(),
label: 'Warning (Orange)',
},
- { name: 'color-chart-error', hex: CHART_COLOR_ERROR, label: 'Error (Red)' },
+ {
+ name: 'color-chart-error',
+ hex: getChartColorError(),
+ label: 'Error (Red)',
+ },
];
const story = {
diff --git a/packages/app/src/theme/themes/clickstack/_tokens.scss b/packages/app/src/theme/themes/clickstack/_tokens.scss
index 22e35078..8bad5ba6 100644
--- a/packages/app/src/theme/themes/clickstack/_tokens.scss
+++ b/packages/app/src/theme/themes/clickstack/_tokens.scss
@@ -146,6 +146,8 @@
--color-state-hover: #2b2c3d;
--color-state-selected: #34354a;
--color-state-focus: #4d4f66;
+ --color-outline-focus: var(--mantine-primary-color-filled);
+ --input-bd-focus: var(--mantine-primary-color-filled);
/* Code / Misc UI */
--color-bg-code: #1d1e30;
@@ -161,8 +163,40 @@
--color-json-array: #ffd966;
--color-json-punctuation: #666980;
+ /*
+ * Chart Colors - Observable 10 categorical palette
+ * NOTE: These colors are intentionally duplicated in both dark and light mode sections.
+ * CSS specificity requires them to be defined within each [data-mantine-color-scheme] selector
+ * to ensure they're applied correctly. A shared .theme-clickstack section would have lower
+ * specificity and could be overridden by other styles.
+ */
+ --color-chart-1: #437eef; /* Blue - Primary */
+ --color-chart-2: #efb118; /* Orange */
+ --color-chart-3: #ff725c; /* Red */
+ --color-chart-4: #6cc5b0; /* Cyan */
+ --color-chart-5: #3ca951; /* Green */
+ --color-chart-6: #ff8ab7; /* Pink */
+ --color-chart-7: #a463f2; /* Purple */
+ --color-chart-8: #97bbf5; /* Light Blue */
+ --color-chart-9: #9c6b4e; /* Brown */
+ --color-chart-10: #9498a0; /* Gray */
+
+ /* Chart Semantic Colors */
+ --color-chart-success: #3ca951; /* Green */
+ --color-chart-warning: #efb118; /* Orange */
+ --color-chart-error: #ff725c; /* Red */
+
+ /* Chart Semantic Colors - Highlighted (for hover/selection states) */
+ --color-chart-error-highlight: #ffa090;
+ --color-chart-warning-highlight: #f5c94d;
+
/* Mantine Overrides */
--mantine-color-body: var(--color-bg-body) !important;
+
+ /* Tabler icons default stroke width - Set default stroke-width to 1.5 for Tabler icons (instead of default 2). This only affects SVGs that have a stroke attribute set. Can be overridden on individual icons by setting strokeWidth prop */
+ .tabler-icon {
+ stroke-width: 1.5;
+ }
}
/* Light Mode */
@@ -306,6 +340,7 @@
--color-state-hover: #e9ecef;
--color-state-selected: #dee2e6;
--color-state-focus: #ced4da;
+ --color-outline-focus: var(--mantine-primary-color-filled);
/* Code / Misc UI */
--color-bg-code: var(--click-global-color-background-muted);
@@ -321,6 +356,32 @@
--color-json-array: #997300;
--color-json-punctuation: #868e96;
+ /* Chart Colors - See dark mode section for explanation of why duplication is required */
+ --color-chart-1: #437eef; /* Blue - Primary */
+ --color-chart-2: #efb118; /* Orange */
+ --color-chart-3: #ff725c; /* Red */
+ --color-chart-4: #6cc5b0; /* Cyan */
+ --color-chart-5: #3ca951; /* Green */
+ --color-chart-6: #ff8ab7; /* Pink */
+ --color-chart-7: #a463f2; /* Purple */
+ --color-chart-8: #97bbf5; /* Light Blue */
+ --color-chart-9: #9c6b4e; /* Brown */
+ --color-chart-10: #9498a0; /* Gray */
+
+ /* Chart Semantic Colors */
+ --color-chart-success: #3ca951; /* Green */
+ --color-chart-warning: #efb118; /* Orange */
+ --color-chart-error: #ff725c; /* Red */
+
+ /* Chart Semantic Colors - Highlighted (for hover/selection states) */
+ --color-chart-error-highlight: #ffa090;
+ --color-chart-warning-highlight: #f5c94d;
+
/* Mantine Overrides */
--mantine-color-body: var(--color-bg-body);
+
+ /* Tabler icons default stroke width - Set default stroke-width to 1.5 for Tabler icons (instead of default 2). This only affects SVGs that have a stroke attribute set. Can be overridden on individual icons by setting strokeWidth prop */
+ .tabler-icon {
+ stroke-width: 1.5;
+ }
}
diff --git a/packages/app/src/theme/themes/clickstack/mantineTheme.ts b/packages/app/src/theme/themes/clickstack/mantineTheme.ts
index cfcaadff..c2d57b46 100644
--- a/packages/app/src/theme/themes/clickstack/mantineTheme.ts
+++ b/packages/app/src/theme/themes/clickstack/mantineTheme.ts
@@ -228,6 +228,7 @@ export const makeTheme = ({
baseVars['--button-bg'] = 'var(--color-primary-button-bg)';
baseVars['--button-hover'] = 'var(--color-primary-button-bg-hover)';
baseVars['--button-color'] = 'var(--color-primary-button-text)';
+ baseVars['--button-color-hover'] = 'var(--color-primary-button-text)';
}
if (props.variant === 'secondary') {
diff --git a/packages/app/src/utils.ts b/packages/app/src/utils.ts
index 0d472e24..40a3fc9f 100644
--- a/packages/app/src/utils.ts
+++ b/packages/app/src/utils.ts
@@ -424,10 +424,29 @@ export const CHART_PALETTE = {
orangeHighlight: '#f5c94d',
} as const;
-// Ordered array for chart series - green first for brand consistency
+// ClickStack theme chart color palette - Observable 10 categorical palette
+// https://observablehq.com/@d3/color-schemes
+export const CLICKSTACK_CHART_PALETTE = {
+ blue: '#437EEF', // Primary color for ClickStack
+ orange: '#efb118',
+ red: '#ff725c',
+ cyan: '#6cc5b0',
+ green: '#3ca951',
+ pink: '#ff8ab7',
+ purple: '#a463f2',
+ lightBlue: '#97bbf5',
+ brown: '#9c6b4e',
+ gray: '#9498a0',
+ // Highlighted variants (lighter shades for hover/selection states)
+ redHighlight: '#ffa090',
+ orangeHighlight: '#f5c94d',
+} as const;
+
+// Ordered array for chart series - green first for brand consistency (HyperDX default)
// Maps to CSS variables: COLORS[0] -> --color-chart-1, COLORS[1] -> --color-chart-2, etc.
+// NOTE: This is a fallback for SSR. In browser, getColorFromCSSVariable() reads from CSS variables
export const COLORS = [
- CHART_PALETTE.green, // 1 - Brand green (primary)
+ CHART_PALETTE.green, // 1 - Brand green (primary) - HyperDX default
CHART_PALETTE.blue, // 2
CHART_PALETTE.orange, // 3
CHART_PALETTE.red, // 4
@@ -439,6 +458,63 @@ export const COLORS = [
CHART_PALETTE.gray, // 10
];
+/**
+ * Detects the active theme by checking for theme classes on documentElement.
+ * Returns 'clickstack' if theme-clickstack class is present, 'hyperdx' otherwise.
+ * Note: classList.contains() is O(1) and fast - no caching needed.
+ */
+function detectActiveTheme(): 'clickstack' | 'hyperdx' {
+ if (typeof window === 'undefined') {
+ // SSR: default to hyperdx (can't detect theme without DOM)
+ return 'hyperdx';
+ }
+
+ try {
+ const isClickStack =
+ document.documentElement.classList.contains('theme-clickstack');
+ return isClickStack ? 'clickstack' : 'hyperdx';
+ } catch {
+ // Fallback if DOM access fails
+ return 'hyperdx';
+ }
+}
+
+/**
+ * Reads chart color from CSS variable based on index.
+ * CSS variables handle theme switching automatically via theme classes on documentElement.
+ * Falls back to COLORS array if CSS variable is not available (SSR or getComputedStyle fails).
+ *
+ * Note on SSR/Hydration: During SSR, this returns fallback colors (HyperDX green palette).
+ * On client hydration, it reads from CSS variables which may differ for ClickStack theme.
+ * This is expected behavior - charts typically render after data fetching (client-side),
+ * so hydration mismatches are rare. If needed, wrap chart components with suppressHydrationWarning.
+ */
+export function getColorFromCSSVariable(index: number): string {
+ const colorArrayLength = COLORS.length;
+
+ if (typeof window === 'undefined') {
+ // SSR: fallback to default colors (HyperDX palette)
+ return COLORS[index % colorArrayLength];
+ }
+
+ try {
+ const cssVarName = `--color-chart-${(index % colorArrayLength) + 1}`;
+ // Read from documentElement - CSS variables cascade from theme classes
+ const computedStyle = getComputedStyle(document.documentElement);
+ const color = computedStyle.getPropertyValue(cssVarName).trim();
+
+ // Only use CSS variable if it's actually set (non-empty)
+ if (color && color !== '') {
+ return color;
+ }
+ } catch {
+ // Fallback if getComputedStyle fails
+ }
+
+ // Fallback to default colors
+ return COLORS[index % colorArrayLength];
+}
+
export function hashCode(str: string) {
let hash = 0,
i,
@@ -452,14 +528,81 @@ export function hashCode(str: string) {
return hash;
}
-// Semantic colors for log levels (derived from palette)
-export const CHART_COLOR_SUCCESS = CHART_PALETTE.green;
-export const CHART_COLOR_WARNING = CHART_PALETTE.orange;
-export const CHART_COLOR_ERROR = CHART_PALETTE.red;
+/**
+ * Gets theme-aware chart color from CSS variable or falls back to palette.
+ * Reads from --color-chart-{type} CSS variable, falls back to theme-appropriate palette.
+ *
+ * Note on SSR/Hydration: During SSR, returns HyperDX colors as default.
+ * On client, reads from CSS variables for accurate theme colors.
+ * Charts typically render client-side after data fetching, minimizing hydration issues.
+ */
+function getSemanticChartColor(
+ cssVarName: string,
+ hyperdxColor: string,
+ clickstackColor: string,
+): string {
+ if (typeof window === 'undefined') {
+ // SSR: use HyperDX as default (can't detect theme without DOM)
+ return hyperdxColor;
+ }
-// Highlighted variants (derived from palette)
-export const CHART_COLOR_ERROR_HIGHLIGHT = CHART_PALETTE.redHighlight;
-export const CHART_COLOR_WARNING_HIGHLIGHT = CHART_PALETTE.orangeHighlight;
+ try {
+ const computedStyle = getComputedStyle(document.documentElement);
+ const color = computedStyle.getPropertyValue(cssVarName).trim();
+ if (color && color !== '') {
+ return color;
+ }
+ } catch {
+ // Fallback if getComputedStyle fails
+ }
+
+ // Fallback to theme-appropriate palette
+ const activeTheme = detectActiveTheme();
+ return activeTheme === 'clickstack' ? clickstackColor : hyperdxColor;
+}
+
+// Semantic colors for log levels (theme-aware)
+// These are functions that read from CSS variables with theme-appropriate fallbacks
+export function getChartColorSuccess(): string {
+ return getSemanticChartColor(
+ '--color-chart-success',
+ CHART_PALETTE.green,
+ CLICKSTACK_CHART_PALETTE.green,
+ );
+}
+
+export function getChartColorWarning(): string {
+ return getSemanticChartColor(
+ '--color-chart-warning',
+ CHART_PALETTE.orange,
+ CLICKSTACK_CHART_PALETTE.orange,
+ );
+}
+
+export function getChartColorError(): string {
+ return getSemanticChartColor(
+ '--color-chart-error',
+ CHART_PALETTE.red,
+ CLICKSTACK_CHART_PALETTE.red,
+ );
+}
+
+// Highlighted variants (theme-aware)
+export function getChartColorErrorHighlight(): string {
+ return getSemanticChartColor(
+ '--color-chart-error-highlight',
+ CHART_PALETTE.redHighlight,
+ CLICKSTACK_CHART_PALETTE.redHighlight,
+ );
+}
+
+export function getChartColorWarningHighlight(): string {
+ return getSemanticChartColor(
+ '--color-chart-warning-highlight',
+ CHART_PALETTE.orangeHighlight,
+ CLICKSTACK_CHART_PALETTE.orangeHighlight,
+ );
+}
// Try to match log levels to colors
export const semanticKeyedColor = (
@@ -469,47 +612,51 @@ export const semanticKeyedColor = (
const logLevel = getLogLevelClass(`${key}`);
if (logLevel != null) {
return logLevel === 'error'
- ? CHART_COLOR_ERROR
+ ? getChartColorError()
: logLevel === 'warn'
- ? CHART_COLOR_WARNING
- : CHART_COLOR_SUCCESS;
+ ? getChartColorWarning()
+ : // Info-level logs use primary chart color (blue for ClickStack, green for HyperDX)
+ getColorFromCSSVariable(0);
}
- return COLORS[index % COLORS.length];
+ // Use CSS variable for theme-aware colors, fallback to hardcoded array
+ return getColorFromCSSVariable(index);
};
export const logLevelColor = (key: string | number | undefined) => {
const logLevel = getLogLevelClass(`${key}`);
return logLevel === 'error'
- ? CHART_COLOR_ERROR
+ ? getChartColorError()
: logLevel === 'warn'
- ? CHART_COLOR_WARNING
- : CHART_COLOR_SUCCESS;
+ ? getChartColorWarning()
+ : // Info-level logs use primary chart color (blue for ClickStack, green for HyperDX)
+ getColorFromCSSVariable(0);
};
-// order of colors for sorting. green on bottom, then yellow, then red
-export const logLevelColorOrder = [
- logLevelColor('info'),
- logLevelColor('warn'),
- logLevelColor('error'),
-];
+// order of colors for sorting. primary color (blue/green) on bottom, then yellow, then red
+// Computed lazily to avoid DOM access at module initialization (SSR-safe)
+export function getLogLevelColorOrder(): string[] {
+ return [logLevelColor('info'), logLevelColor('warn'), logLevelColor('error')];
+}
const getLevelColor = (logLevel?: string) => {
if (logLevel == null) {
return;
}
return logLevel === 'error'
- ? CHART_COLOR_ERROR
+ ? getChartColorError()
: logLevel === 'warn'
- ? CHART_COLOR_WARNING
- : CHART_COLOR_SUCCESS;
+ ? getChartColorWarning()
+ : // Info-level logs use primary chart color (blue for ClickStack, green for HyperDX)
+ getColorFromCSSVariable(0);
};
export const getColorProps = (index: number, level: string): string => {
const logLevel = getLogLevelClass(level);
const colorOverride = getLevelColor(logLevel);
- return colorOverride ?? COLORS[index % COLORS.length];
+ // Use CSS variable for theme-aware colors, fallback to hardcoded array
+ return colorOverride ?? getColorFromCSSVariable(index);
};
export const truncateMiddle = (str: string, maxLen = 10) => {
diff --git a/packages/app/styles/app.scss b/packages/app/styles/app.scss
index 968c44ae..75ea2445 100644
--- a/packages/app/styles/app.scss
+++ b/packages/app/styles/app.scss
@@ -1,4 +1,4 @@
-/* stylelint-disable */
+/* stylelint-disable-file */
// stylelint adds '}}' to end of file.. not sure why. Disabled for now.
@use '../src/theme/themes/base-tokens';
@@ -279,17 +279,6 @@ button:focus-visible {
opacity: 0.8;
}
-.hover-color-white {
- &:hover {
- color: white;
- }
-}
-
-
-a.mantine-focus-auto:hover {
- color: inherit;
-}
-
.recharts-tooltip-wrapper {
&:focus,
&:focus-visible {