diff --git a/.changeset/data-inspector-expanded-brackets.md b/.changeset/data-inspector-expanded-brackets.md new file mode 100644 index 0000000000..98128f03af --- /dev/null +++ b/.changeset/data-inspector-expanded-brackets.md @@ -0,0 +1,5 @@ +--- +'@workflow/web-shared': patch +--- + +Rework the data inspector's JSON rendering: bracket notation (`{ … }` / `[ … ]`), colored keys, typed value colors, `▸`/`▾` disclosure icons, trailing commas, and a `...` collapsed indicator. Replaces the `react-inspector` engine with an in-house tree renderer while keeping the workflow-specific value handling (StreamRef/RunRef badges, encrypted markers, decoded byte streams, dates, class instances). diff --git a/packages/web-shared/package.json b/packages/web-shared/package.json index 27879eacc8..df7f3bdb59 100644 --- a/packages/web-shared/package.json +++ b/packages/web-shared/package.json @@ -59,7 +59,6 @@ "lucide-react": "0.575.0", "react": "19.1.0", "react-dom": "19.1.0", - "react-inspector": "9.0.0", "react-use-measure": "2.1.1", "react-virtuoso": "4.18.1", "shiki": "4.0.0", diff --git a/packages/web-shared/src/components/ui/data-inspector.styles.ts b/packages/web-shared/src/components/ui/data-inspector.styles.ts new file mode 100644 index 0000000000..5af1224455 --- /dev/null +++ b/packages/web-shared/src/components/ui/data-inspector.styles.ts @@ -0,0 +1,96 @@ +/** + * Class names and styles for the data inspector tree. + * + * Kept out of the component module for readability. The CSS is injected via a + * React 19 hoistable ` + + + ); if (onStreamClick) { - wrapped = ( + content = ( - {wrapped} + {content} ); } if (onRunClick) { - wrapped = ( + content = ( - {wrapped} + {content} ); } if (onDecrypt) { - wrapped = ( + content = ( - {wrapped} + {content} ); } - return wrapped; + return content; } +// --------------------------------------------------------------------------- +// Render stabilization (avoid re-renders when data is deeply equal) +// --------------------------------------------------------------------------- + function useStableInspectorData(next: T): T { const previousRef = useRef(next); if (!isDeepEqual(previousRef.current, next)) { diff --git a/packages/web-shared/src/components/ui/inspector-theme.ts b/packages/web-shared/src/components/ui/inspector-theme.ts deleted file mode 100644 index 5d04188082..0000000000 --- a/packages/web-shared/src/components/ui/inspector-theme.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Shared theme configuration for react-inspector's ObjectInspector. - * - * Colors follow Geist's Shiki JSON palette so the inspector reads the same - * as highlighted code blocks across the product: - * - property names / punctuation: --ds-gray-1000 (default foreground) - * - strings / numbers / booleans: --ds-green-900 - * - null / undefined: --ds-gray-900 (muted) - * - regexp / function: --ds-purple-900 - * - date: --ds-pink-900 - * - * Because the `--ds-*` tokens adapt to theme automatically, the light and - * dark objects are intentionally identical. - */ - -// --------------------------------------------------------------------------- -// Extended color tokens not supported by react-inspector's built-in theme -// system, applied via our custom nodeRenderer in data-inspector.tsx. -// --------------------------------------------------------------------------- - -export interface InspectorThemeExtended { - /** Color for Date values (Node: 'magenta') */ - OBJECT_VALUE_DATE_COLOR: string; -} - -export const inspectorThemeExtendedLight: InspectorThemeExtended = { - OBJECT_VALUE_DATE_COLOR: 'var(--ds-pink-900)', -}; - -export const inspectorThemeExtendedDark: InspectorThemeExtended = { - OBJECT_VALUE_DATE_COLOR: 'var(--ds-pink-900)', -}; - -// --------------------------------------------------------------------------- -// Shared structural values (same in both themes) -// --------------------------------------------------------------------------- - -const shared = { - BASE_FONT_SIZE: '11px', - BASE_LINE_HEIGHT: 1.4, - BASE_BACKGROUND_COLOR: 'transparent', - OBJECT_PREVIEW_ARRAY_MAX_PROPERTIES: 10, - OBJECT_PREVIEW_OBJECT_MAX_PROPERTIES: 5, - HTML_TAGNAME_TEXT_TRANSFORM: 'lowercase' as const, - ARROW_MARGIN_RIGHT: 3, - ARROW_FONT_SIZE: 12, - TREENODE_FONT_FAMILY: 'var(--font-mono)', - TREENODE_FONT_SIZE: '11px', - TREENODE_LINE_HEIGHT: 1.4, - TREENODE_PADDING_LEFT: 12, - TABLE_DATA_BACKGROUND_IMAGE: 'none', - TABLE_DATA_BACKGROUND_SIZE: '0', -}; - -// --------------------------------------------------------------------------- -// Light theme -// --------------------------------------------------------------------------- - -const geistTheme = { - ...shared, - - // Base text - BASE_COLOR: 'var(--ds-gray-1000)', - - // Property names — default foreground (matches JSON key color in Geist Shiki) - OBJECT_NAME_COLOR: 'var(--ds-gray-1000)', - - // Strings & symbols — green - OBJECT_VALUE_STRING_COLOR: 'var(--ds-green-900)', - OBJECT_VALUE_SYMBOL_COLOR: 'var(--ds-green-900)', - - // Numbers & booleans — green (Geist JSON tokens) - OBJECT_VALUE_NUMBER_COLOR: 'var(--ds-green-900)', - OBJECT_VALUE_BOOLEAN_COLOR: 'var(--ds-green-900)', - - // null — muted foreground - OBJECT_VALUE_NULL_COLOR: 'var(--ds-gray-900)', - - // undefined — muted foreground - OBJECT_VALUE_UNDEFINED_COLOR: 'var(--ds-gray-900)', - - // RegExp — purple - OBJECT_VALUE_REGEXP_COLOR: 'var(--ds-purple-900)', - - // Functions — purple - OBJECT_VALUE_FUNCTION_PREFIX_COLOR: 'var(--ds-purple-900)', - - // HTML (rarely used here, kept consistent with the palette) - HTML_TAG_COLOR: 'var(--ds-gray-900)', - HTML_TAGNAME_COLOR: 'var(--ds-blue-900)', - HTML_ATTRIBUTE_NAME_COLOR: 'var(--ds-amber-900)', - HTML_ATTRIBUTE_VALUE_COLOR: 'var(--ds-green-900)', - HTML_COMMENT_COLOR: 'var(--ds-gray-700)', - HTML_DOCTYPE_COLOR: 'var(--ds-gray-700)', - - // Structural - ARROW_COLOR: 'var(--ds-gray-700)', - TABLE_BORDER_COLOR: 'var(--ds-gray-300)', - TABLE_TH_BACKGROUND_COLOR: 'var(--ds-gray-100)', - TABLE_TH_HOVER_COLOR: 'var(--ds-gray-200)', - TABLE_SORT_ICON_COLOR: 'var(--ds-gray-700)', -}; - -export const inspectorThemeLight = geistTheme; - -// --------------------------------------------------------------------------- -// Dark theme -// --------------------------------------------------------------------------- - -export const inspectorThemeDark = geistTheme; diff --git a/packages/web-shared/src/lib/hydration.ts b/packages/web-shared/src/lib/hydration.ts index 936c35c1fb..2caf2cf0c7 100644 --- a/packages/web-shared/src/lib/hydration.ts +++ b/packages/web-shared/src/lib/hydration.ts @@ -298,7 +298,7 @@ export function getWebRevivers(): Revivers { // Web-specific overrides for class instances. // Create objects with a dynamically-named constructor so that - // react-inspector shows the class name (it reads constructor.name). + // the data inspector shows the class name (it reads constructor.name). Class: (value) => ``, Instance: (value) => { // Run instances are rendered as clickable RunRef badges @@ -311,7 +311,7 @@ export function getWebRevivers(): Revivers { const props = data && typeof data === 'object' ? { ...data } : { value: data }; // Create a constructor with the right name using computed property - // so react-inspector's `object.constructor.name` shows the class name. + // so the data inspector's `object.constructor.name` shows the class name. // Must use `function` (not arrow) because arrow functions have no .prototype. // biome-ignore lint/complexity/useArrowFunction: arrow functions have no .prototype const ctor = { [className]: function () {} }[className]!; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94ed2b11a2..6bfef0f6c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1215,9 +1215,6 @@ importers: react-dom: specifier: 19.1.0 version: 19.1.0(react@19.1.0) - react-inspector: - specifier: 9.0.0 - version: 9.0.0(react@19.1.0) react-use-measure: specifier: 2.1.1 version: 2.1.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -15272,11 +15269,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - react-inspector@9.0.0: - resolution: {integrity: sha512-w/VJucSeHxlwRa2nfM2k7YhpT1r5EtlDOClSR+L7DyQP91QMdfFEDXDs9bPYN4kzP7umFtom7L0b2GGjph4Kow==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -26050,14 +26042,6 @@ snapshots: optionalDependencies: vite: 7.3.2(@types/node@22.19.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.9.0) - '@vitest/mocker@4.0.18(vite@7.3.2(@types/node@22.19.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.9.0))': - dependencies: - '@vitest/spy': 4.0.18 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.2(@types/node@22.19.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.9.0) - '@vitest/mocker@4.0.18(vite@7.3.2(@types/node@24.6.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.0.18 @@ -33817,10 +33801,6 @@ snapshots: dependencies: react: 19.2.3 - react-inspector@9.0.0(react@19.1.0): - dependencies: - react: 19.1.0 - react-is@16.13.1: {} react-is@17.0.2: {} @@ -36417,7 +36397,7 @@ snapshots: vitest@4.0.18(@opentelemetry/api@1.9.1)(@types/node@22.19.0)(jiti@2.7.0)(jsdom@26.1.0)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.9.0): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.2(@types/node@22.19.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.9.0)) + '@vitest/mocker': 4.0.18(vite@7.3.2(@types/node@24.6.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.9.0)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18