Skip to content

feat: dashboard charts settings - #581

Merged
dmitriy-kostianetskiy merged 19 commits into
mainfrom
dashboard-charts-settings-1
May 5, 2026
Merged

dmitriy-kostianetskiy merged 19 commits into
mainfrom
dashboard-charts-settings-1

Conversation

@dmitriy-kostianetskiy

@dmitriy-kostianetskiy dmitriy-kostianetskiy commented May 4, 2026 •

Copy link
Copy Markdown
Collaborator
Screen.Recording.2026-05-04.at.14.56.51.mov

Summary by CodeRabbit

  • New Features

    • Added error boundary to prevent dashboard crashes when panels fail to render.
    • Introduced "Add Default Chart" functionality for faster chart creation.
    • Added chart settings panel for configuring chart types and field selections.
  • Bug Fixes

    • Improved error handling and user feedback when chart rendering fails.
  • Improvements

    • Refined chart builder integration with dashboard workflows.
    • Enhanced chart type selector with better filtering and availability detection.

dmitriy-kostianetskiy and others added 6 commits April 29, 2026 15:06
Co-authored-by: Copilot <[email protected]>
- Added `addDefaultChart` method to `MosaicDashboardContext` for default chart creation.
- Wrapped `RendererComponent` in `DashboardPanelErrorBoundary` in `MosaicDashboardPanel` for error handling.
- Updated `MosaicDashboardProfilerPanelRenderer` to use `ProfilerPanelRendererProps`.
- Refactored `createMosaicDashboardVgPlotPanelConfig` to accept `VgPlotChartConfig` directly.
- Modified `MosaicDashboardToolbar` to trigger `addDefaultChart` instead of `openBuilder`.
- Improved `ChartSettings` component structure with a new context for better state management.
- Removed deprecated chart settings components (e.g., `BoxPlotSettings`, `BubbleSettings`, etc.) and replaced them with a dynamic settings approach.
- Introduced `DynamicChartSettings` for handling chart-specific settings dynamically based on selected chart type.
- Updated `generateMosaicChartSpec` to include error handling and improved logging.
- Cleaned up imports and exports across various files for better organization and clarity.

Co-authored-by: Copilot <[email protected]>
@coderabbitai

coderabbitai Bot commented May 4, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR reorganizes the Mosaic chart type system by moving chart definitions to a centralized chart-types/ module with Zod schema validation, updates chart creation callbacks to pass typed configuration objects instead of raw specs, and introduces dashboard UI components for chart type selection, settings management, error boundaries, and resizable layouts.

Changes

Chart Type System Reorganization

Layer / File(s) Summary
Base Type Definitions
packages/mosaic/src/chart-types/base-types.ts
Introduces generic ChartTypeDefinition<TSettings>, VgPlotChartType, and supporting interfaces (ChartBuilderField, ChartBuilderColumn) as the foundation for all chart types.
Module Structure & Constants
packages/mosaic/src/chart-builders/constants.ts, packages/mosaic/src/chart-types/*/schema.ts
Consolidates column-type constants into chart-builders/constants.ts, and adds Zod schema modules for each chart type (histogram, line-chart, count-plot, ecdf, heatmap, box-plot, bubble-chart, custom-spec) defining settings and config validation.
Chart Definitions
packages/mosaic/src/chart-types/*/definition.ts
Implements chart-specific ChartTypeDefinition objects (e.g., histogramChartType, lineChartChartType) with metadata, field requirements, title builders, and spec generators; removes legacy packages/mosaic/src/chart-builders/chartTypes.ts.
Registry & Central Export
packages/mosaic/src/chart-types/registry.ts, packages/mosaic/src/chart-types/index.ts
Establishes a runtime registry with registerChartType, getChartTypeDefinition, and getAllChartTypes APIs; exports discriminated union VgPlotChartConfig via Zod, legacy mosaicChartTypes mapping, and createDefaultChartTypes() factory for backward compatibility.
Chart Builder Refactoring
packages/mosaic/src/chart-builders/types.ts, packages/mosaic/src/chart-builders/chartTypeUtils.ts, packages/mosaic/src/chart-builders/builders.ts
Re-exports types from chart-types/base-types, moves constants to chart-builders/constants.ts, adds titleFromDescription() helper, and updates import paths to use the new chart-types module.

Chart Creation Callback Signature Migration

Layer / File(s) Summary
Context & Store Types
packages/mosaic/src/chart-builders/ChartBuilderContext.tsx, packages/mosaic/src/chart-builders/createChartBuilderStore.ts
Changes onCreateChart callback from (spec: Spec, title: string) to (title: string, config: VgPlotChartConfig) and updates selectedTemplateId type from string to VgPlotChartType.
Component Props
packages/mosaic/src/chart-builders/ChartBuilderRoot.tsx, packages/mosaic/src/chart-builders/ChartBuilderContent.tsx, packages/mosaic/src/chart-builders/ChartBuilderDialog.tsx
Propagates the new callback signature through root, content, and dialog component props.
Builder Actions & Field Handling
packages/mosaic/src/chart-builders/ChartBuilderActions.tsx, packages/mosaic/src/chart-builders/ChartBuilderFields.tsx, packages/mosaic/src/chart-builders/hooks/useChartFieldForm.ts
Updates button handlers to invoke callbacks with the new shape; introduces useChartFieldForm hook to manage field change tracking and pass chartType, settings, and vgplot to onCreateChart.
Chart Creation in Dashboard
apps/sqlrooms-cli-ui/src/createDashboardChartTemplateTool.ts, apps/sqlrooms-cli-ui/src/store.ts, examples/deckgl-mosaic/src/components/filters/FiltersPanel.tsx
Consumer code (CLI dashboard tool, store, example filters) updates chart creation calls to use the new (title, config) signature and extracts spec from config.vgplot.

Dashboard VgPlot Panel Type System

Layer / File(s) Summary
Panel Config Schemas & Discrimination
packages/mosaic/src/dashboard/MosaicDashboardSlice.ts
Replaces permissive MosaicDashboardPanelConfig with Zod-based discriminated union on type field (VgPlotPanelConfig with config: VgPlotChartConfig and ProfilerPanel); adds LegacyPanelConfig fallback and type guard isVgPlotPanelConfig.
Panel Factory & Renderer Typing
packages/mosaic/src/dashboard/MosaicDashboardSlice.ts, packages/mosaic/src/dashboard/defaultPanelRenderers.ts, packages/mosaic/src/dashboard/MosaicDashboardProfilerPanelRenderer.tsx
Updates createMosaicDashboardVgPlotPanelConfig(title, config, source?) to embed VgPlotChartConfig directly; introduces generic MosaicDashboardPanelRenderer<TPanel> and type-erased AnyPanelRenderer for type-safe panel rendering.
Main Dashboard Integration
packages/mosaic/src/dashboard/MosaicDashboard.tsx, packages/mosaic/src/dashboard/MosaicDashboardContext.tsx
Updates dashboard's handleCreateChart callback to new signature and adds addDefaultChart() API; exposes via MosaicDashboardContextValue.

Chart Settings UI System

Layer / File(s) Summary
Settings Context & Provider
packages/mosaic/src/dashboard/chart-settings/ChartSettingsContext.tsx
Introduces context carrying tableName, config, columns, and onChange for coordinating chart settings state across child components.
Chart Type & Field Selectors
packages/mosaic/src/dashboard/chart-settings/ChartTypeSelector.tsx, packages/mosaic/src/dashboard/chart-settings/DynamicChartSettings.tsx
Implements dropdown to select chart types (filtered by available columns), and renders field input controls for the active chart's configuration; reads from store-provided chartTypes and registry.
Compound Settings Components
packages/mosaic/src/dashboard/chart-settings/ChartSettings.tsx, packages/mosaic/src/dashboard/chart-settings/ChartSettingsPanel.tsx
Provides ChartSettings.Root/Header/Content/TypeSelector/Fields compound API and ChartSettingsPanel wrapper that auto-resolves columns via useTableColumns; validates field completeness and generates/updates vgplot spec.
Supporting Hooks & Types
packages/mosaic/src/dashboard/chart-settings/useTableColumns.ts, packages/mosaic/src/dashboard/chart-settings/index.ts
Adds useTableColumns hook to fetch column metadata; consolidates exports via barrel module.
Spec Generation
packages/mosaic/src/dashboard/generateMosaicChartSpec.ts
Implements spec generator that looks up chart type in registry, applies user settings, and returns Mosaic spec (or null on error).

Dashboard Panel Rendering Improvements

Layer / File(s) Summary
Error Boundary
packages/mosaic/src/dashboard/DashboardPanelErrorBoundary.tsx
Introduces React error boundary to catch and gracefully display panel render errors.
Resizable Layout
packages/mosaic/src/dashboard/MosaicDashboardPanelLayout.tsx, packages/ui/src/components/resizable.tsx, packages/ui/src/index.ts
Implements resizable two-panel layout component with collapsible left settings panel; wraps ResizablePanel with forwardRef to expose handle for external control.
VgPlot Rendering
packages/mosaic/src/dashboard/MosaicDashboardVgPlotPanelRenderer.tsx, packages/mosaic/src/dashboard/MosaicDashboardVgPlotHeaderActions.tsx
Refactors VgPlot panel renderer to use ChartSettingsPanel and MosaicDashboardPanelLayout; adds header actions for settings toggle and spec editing via MosaicDashboardVgPlotHeaderActions.
Toolbar & Panel Integration
packages/mosaic/src/dashboard/MosaicDashboardPanel.tsx, packages/mosaic/src/dashboard/MosaicDashboardToolbar.tsx
Wraps panels in error boundary; updates toolbar "Add Chart" action to call addDefaultChart() instead of openBuilder.
Public API & Tests
packages/mosaic/src/index.ts, packages/mosaic/__tests__/*, packages/mosaic/test/setup.ts, packages/mosaic/package.json
Exports new types and components; adds comprehensive Jest tests for chart settings, chart spec generation, and dashboard slice; includes testing library dependencies.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • ilyabo
  • bdjulbic
  • mihajlo-stojkovic

🐰 Charts now bloom in fields of type,
Settings gathered, specs are kept,
From builders' hands to dashboards bright,
Our refactored types unite! ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dashboard-charts-settings-1

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/mosaic/src/dashboard/MosaicDashboardSlice.ts (1)

403-408: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Evict retained VGPlot charts on any config change, not just config.vgplot.

After this refactor, most VgPlotChartConfig updates no longer carry a top-level vgplot field. With the current check, changing histogram/line/etc. settings won't evict the retained chart, so the dashboard can keep rendering stale chart instances after a settings edit.

Suggested fix
   if (panel.type === MOSAIC_DASHBOARD_VGPLOT_PANEL_TYPE) {
-    return Boolean(
-      patch.config &&
-      Object.prototype.hasOwnProperty.call(patch.config, 'vgplot'),
-    );
+    return patch.config !== undefined;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/dashboard/MosaicDashboardSlice.ts` around lines 403 -
408, The current eviction check for VgPlot panels only returns true when
patch.config has a top-level 'vgplot' property, which misses most
VgPlotChartConfig updates; change the condition in the
MOSAIC_DASHBOARD_VGPLOT_PANEL_TYPE branch to evict on any config change by
checking presence of patch.config (e.g., return Boolean(patch.config)) instead
of using Object.prototype.hasOwnProperty.call(patch.config, 'vgplot'), and
ensure references to VgPlotChartConfig and the patch handling logic in
MosaicDashboardSlice (the code that inspects panel.type ===
MOSAIC_DASHBOARD_VGPLOT_PANEL_TYPE) are updated accordingly.
🧹 Nitpick comments (13)
packages/mosaic/src/dashboard/chart-settings/ChartSettingsContext.tsx (1)

41-47: ⚡ Quick win

Inline value object creates a new reference on every render, causing all context consumers to re-render unnecessarily.

Stabilize the context value with useMemo so consumers only re-render when the inputs actually change.

♻️ Proposed refactor
-import {createContext, type ReactNode, useContext} from 'react';
+import {createContext, type ReactNode, useContext, useMemo} from 'react';
 export function ChartSettingsProvider({
   tableName,
   config,
   columns,
   onChange,
   children,
 }: ChartSettingsProviderProps) {
+  const value = useMemo(
+    () => ({tableName, config, columns, onChange}),
+    [tableName, config, columns, onChange],
+  );
   return (
-    <ChartSettingsContext.Provider
-      value={{tableName, config, columns, onChange}}
-    >
+    <ChartSettingsContext.Provider value={value}>
       {children}
     </ChartSettingsContext.Provider>
   );
 }

As per coding guidelines, "Keep the compound context/provider layer thin and stable."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/dashboard/chart-settings/ChartSettingsContext.tsx` around
lines 41 - 47, The Provider is passing an inline object to
ChartSettingsContext.Provider causing a new reference each render; wrap the
value in React.useMemo and return {tableName, config, columns, onChange} inside
it so the object identity is stable, and use [tableName, config, columns,
onChange] (or the minimal real dependencies) as the dependency array; update the
ChartSettingsContext.Provider to pass this memoizedValue instead of the inline
object so consumers only re-render when those inputs actually change.
packages/mosaic/src/dashboard/MosaicDashboardPanelLayout.tsx (1)

31-41: 💤 Low value

onResize should be wrapped in useCallback for stable reference.

A new function is allocated every render and passed as the onResize prop, triggering unnecessary reconciliation inside ResizablePanel.

♻️ Proposed refactor
-  const onResize = () => {
+  const onResize = React.useCallback(() => {
     const isCollapsed = panelRef.current?.isCollapsed();
 
     if (isCollapsed && isOpen) {
       onIsOpenChange?.(false);
     }
 
     if (!isCollapsed && !isOpen) {
       onIsOpenChange?.(true);
     }
-  };
+  }, [isOpen, onIsOpenChange]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/dashboard/MosaicDashboardPanelLayout.tsx` around lines 31
- 41, The onResize function is recreated each render causing unstable props to
ResizablePanel; wrap the handler in React.useCallback to memoize it (referencing
panelRef.current, isOpen, and onIsOpenChange) so its identity remains stable
across renders. Update the onResize declaration (used as prop to ResizablePanel)
to use useCallback and include only the necessary dependencies (panelRef,
isOpen, onIsOpenChange) to avoid stale closures while preventing unnecessary
re-renders.
packages/mosaic/src/chart-types/ecdf/definition.ts (1)

9-14: ⚡ Quick win

Extract FG_COLOR and titleFromDescription to chartTypeUtils.ts — duplicated across every chart-type definition.

titleFromDescription (lines 11-14) and FG_COLOR (line 9) are copy-pasted verbatim into ecdf/definition.ts, box-plot/definition.ts, heatmap/definition.ts, and (based on context) histogram/definition.ts. Moving them to chartTypeUtils.ts eliminates the risk of diverging quietly.

♻️ Proposed refactor

In chartTypeUtils.ts:

+export const FG_COLOR = 'var(--color-chart-1)';
+
+export function titleFromDescription(description: string) {
+  return (fieldValues: Record<string, string>) =>
+    buildDefaultChartTitle(description, fieldValues);
+}

Then in each definition file:

 import {
   buildDefaultChartTitle,
+  FG_COLOR,
+  titleFromDescription,
   QUANTITATIVE_COLUMN_TYPES,
 } from '../../chart-builders/chartTypeUtils';
-
-const FG_COLOR = 'var(--color-chart-1)';
-
-function titleFromDescription(description: string) {
-  return (fieldValues: Record<string, string>) =>
-    buildDefaultChartTitle(description, fieldValues);
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/chart-types/ecdf/definition.ts` around lines 9 - 14, The
FG_COLOR constant and titleFromDescription function are duplicated across
chart-type definitions; extract them into a new exported helpers module
(chartTypeUtils.ts), export FG_COLOR and titleFromDescription (which should call
buildDefaultChartTitle as before), then replace the local declarations in each
chart-type definition (ecdf, box-plot, heatmap, histogram) with imports from
chartTypeUtils.ts so all files use the single shared FG_COLOR and
titleFromDescription.
packages/mosaic/src/chart-types/custom-spec/definition.ts (1)

6-9: ⚡ Quick win

titleFromDescription is the same function defined in line-chart/definition.ts — consolidate into chartTypeUtils.

See the extraction suggestion on line-chart/definition.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/chart-types/custom-spec/definition.ts` around lines 6 -
9, The titleFromDescription function is duplicated; extract it into the shared
utility module chartTypeUtils (create and export a function
titleFromDescription(description: string): (fieldValues: Record<string,string>)
=> string that delegates to buildDefaultChartTitle), update both usages in
custom-spec/definition.ts and line-chart/definition.ts to import the shared
titleFromDescription from chartTypeUtils, and remove the duplicate definition
from each file so only the exported utility remains.
packages/mosaic/src/chart-builders/hooks/useChartFieldForm.ts (1)

15-20: ⚡ Quick win

useCallback([onChange]) provides no stability when callers pass an inline onChange.

Both current call sites create onChange as an inline arrow on every render (e.g., (key, value) => setFieldValue(key, value as string) in ChartBuilderFields.tsx). A single value that is "always new" is enough to break memoization for an entire component. Since onChange is unstable at both call sites, handleFieldChange will be a new reference on every render regardless of the useCallback.

Either require callers to stabilize onChange before passing it in (document this contract), or drop the useCallback wrapper since it adds overhead without benefit in the current usage pattern.

♻️ Option A — drop the wrapper (honest, no false stability promise)
 export function useChartFieldForm({
   fields,
   values,
   onChange,
 }: UseChartFieldFormProps) {
-  const handleFieldChange = useCallback(
-    (fieldKey: string, value: unknown) => {
-      onChange(fieldKey, value);
-    },
-    [onChange],
-  );
-
   return {
     fields,
     values,
-    handleFieldChange,
+    handleFieldChange: onChange,
   };
 }
♻️ Option B — callers stabilize onChange (keeps the hook's contract)

In ChartBuilderFields.tsx, wrap with useCallback:

+const handleChange = useCallback(
+  (key: string, value: unknown) => setFieldValue(key, value as string),
+  [setFieldValue],
+);
 const {fields, handleFieldChange} = useChartFieldForm({
   fields: selectedTemplate?.fields || [],
   values: fieldValues,
-  onChange: (key: string, value: unknown) =>
-    setFieldValue(key, value as string),
+  onChange: handleChange,
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/chart-builders/hooks/useChartFieldForm.ts` around lines
15 - 20, The useCallback around handleFieldChange in useChartFieldForm is
ineffective because callers pass an inline unstable onChange; remove the
misleading memoization by replacing the useCallback wrapper with a plain
function export (i.e., change handleFieldChange in useChartFieldForm to a direct
function that calls onChange), or alternatively stabilize the caller-side
onChange (wrap the inline (key, value) => setFieldValue(...) in
ChartBuilderFields.tsx with useCallback) if you want to preserve the hook's
memoization contract—pick one approach and apply consistently to
handleFieldChange and its caller(s).
packages/mosaic/src/chart-types/line-chart/definition.ts (1)

12-15: ⚡ Quick win

titleFromDescription is duplicated across chart-type definitions — extract it to chartTypeUtils.ts.

The same helper is defined verbatim in custom-spec/definition.ts (and possibly other chart-type definitions). Since both files already import buildDefaultChartTitle from chartTypeUtils, the natural home is to export titleFromDescription from there.

♻️ Suggested extraction

In packages/mosaic/src/chart-builders/chartTypeUtils.ts, add:

+export function titleFromDescription(description: string) {
+  return (fieldValues: Record<string, string>) =>
+    buildDefaultChartTitle(description, fieldValues);
+}

Then in each definition file:

 import {
   buildDefaultChartTitle,
+  titleFromDescription,
   QUANTITATIVE_COLUMN_TYPES,
   NUMERIC_COLUMN_TYPES,
 } from '../../chart-builders/chartTypeUtils';

-function titleFromDescription(description: string) {
-  return (fieldValues: Record<string, string>) =>
-    buildDefaultChartTitle(description, fieldValues);
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/chart-types/line-chart/definition.ts` around lines 12 -
15, The helper titleFromDescription is duplicated across chart-type definitions;
extract and export it from the shared chartTypeUtils module where
buildDefaultChartTitle already lives, then remove the duplicate definitions.
Specifically, add an exported function titleFromDescription(description: string)
that returns (fieldValues) => buildDefaultChartTitle(description, fieldValues)
in the chartTypeUtils module, and in each chart definition (e.g., the line-chart
definition that currently declares titleFromDescription) replace the local
function with an import of the shared titleFromDescription and remove the
redundant code.
packages/mosaic/src/chart-types/histogram/definition.ts (1)

9-15: ⚡ Quick win

Extract shared chart-definition helpers to chartTypeUtils.ts.

BG_COLOR, FG_COLOR, and titleFromDescription are identically defined in both histogram/definition.ts (lines 9–15) and count-plot/definition.ts (lines 9–15), and presumably in all 8 chart-type definition files. With buildDefaultChartTitle already living in chartTypeUtils.ts, the curried wrapper belongs there too.

♻️ Suggested extraction

In packages/mosaic/src/chart-builders/chartTypeUtils.ts, add:

+export const CHART_BG_COLOR = 'var(--color-chart-overlay)';
+export const CHART_FG_COLOR = 'var(--color-chart-1)';
+
+export function titleFromDescription(description: string) {
+  return (fieldValues: Record<string, string>) =>
+    buildDefaultChartTitle(description, fieldValues);
+}

Then in each definition file:

-const BG_COLOR = 'var(--color-chart-overlay)';
-const FG_COLOR = 'var(--color-chart-1)';
-
-function titleFromDescription(description: string) {
-  return (fieldValues: Record<string, string>) =>
-    buildDefaultChartTitle(description, fieldValues);
-}
 import {
   buildDefaultChartTitle,
+  CHART_BG_COLOR as BG_COLOR,
+  CHART_FG_COLOR as FG_COLOR,
+  titleFromDescription,
   QUANTITATIVE_COLUMN_TYPES,
 } from '../../chart-builders/chartTypeUtils';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/chart-types/histogram/definition.ts` around lines 9 - 15,
BG_COLOR, FG_COLOR, and titleFromDescription are duplicated across multiple
chart definition files; move these shared helpers into chartTypeUtils.ts and
import them from there. Specifically, add BG_COLOR and FG_COLOR constants and a
titleFromDescription(description: string) curried wrapper that calls
buildDefaultChartTitle in packages/mosaic/src/chart-builders/chartTypeUtils.ts,
then remove the duplicated definitions from histogram/definition.ts (and other
chart definition files) and replace them with imports of BG_COLOR, FG_COLOR, and
titleFromDescription from chartTypeUtils.ts.
packages/mosaic/src/chart-types/count-plot/definition.ts (1)

9-15: ⚡ Quick win

Same duplication as histogram/definition.ts.

BG_COLOR, FG_COLOR, and titleFromDescription are byte-for-byte identical to lines 9–15 in histogram/definition.ts. See the suggested extraction comment on that file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/chart-types/count-plot/definition.ts` around lines 9 -
15, BG_COLOR, FG_COLOR and titleFromDescription are duplicated; extract them
into a shared module (e.g., export constants BG_COLOR, FG_COLOR and a function
titleFromDescription from a new/shared helper like chartConstants or
chartTitleUtils) and replace the local definitions in both count-plot and
histogram definitions with imports of those symbols; update usages to reference
the imported BG_COLOR, FG_COLOR and titleFromDescription so both files reuse the
single implementation.
packages/mosaic/src/dashboard/MosaicDashboard.tsx (1)

106-124: ⚡ Quick win

Narrow handleAddDefaultChart deps to dashboard?.selectedTable to avoid context churn.

The callback only reads dashboard?.selectedTable, but dashboard (the full object) is in the deps array. Since addPanel mutates dashboard, every panel addition recreates handleAddDefaultChart, which then re-evaluates contextValue (line 148), re-rendering all context consumers. Compare with handleCreateChart at line 103, which correctly scopes to dashboard?.selectedTable.

🔧 Proposed fix
-  const handleAddDefaultChart = useCallback(() => {
-    if (!dashboard?.selectedTable) {
-      return;
-    }
-
-    // Create empty chart panel - user will select type and fields
-    const panel = createMosaicDashboardVgPlotPanelConfig(
-      'New Chart',
-      {
-        chartType: 'histogram', // Default type, but with empty settings
-        settings: {},
-        vgplot: null,
-        settingsOpen: true, // Open settings by default
-      },
-      {tableName: dashboard.selectedTable},
-    );
-
-    addPanel(dashboardId, panel);
-  }, [addPanel, dashboard, dashboardId]);
+  const handleAddDefaultChart = useCallback(() => {
+    if (!dashboard?.selectedTable) {
+      return;
+    }
+
+    // Create empty chart panel - user will select type and fields
+    const panel = createMosaicDashboardVgPlotPanelConfig(
+      'New Chart',
+      {
+        chartType: 'histogram',
+        settings: {},
+        vgplot: null,
+        settingsOpen: true,
+      },
+      {tableName: dashboard?.selectedTable},
+    );
+
+    addPanel(dashboardId, panel);
+  }, [addPanel, dashboard?.selectedTable, dashboardId]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/dashboard/MosaicDashboard.tsx` around lines 106 - 124,
The handleAddDefaultChart callback closes over the whole dashboard object
causing unnecessary re-creations; change its dependency array to only include
the specific values it reads (dashboard?.selectedTable, addPanel, dashboardId)
by replacing "dashboard" with "dashboard?.selectedTable" so that
handleAddDefaultChart only re-runs when the selected table changes; keep the
rest of the implementation the same and ensure references to
createMosaicDashboardVgPlotPanelConfig and addPanel remain unchanged.
packages/mosaic/__tests__/generateMosaicChartSpec.test.ts (1)

101-126: ⚡ Quick win

Consider using jest.spyOn instead of direct registry mutation.

Direct mutation of mosaicChartTypes.histogram is fragile—if an assertion fails before line 124, the restore never runs, potentially breaking subsequent tests. Using jest.spyOn with mockRestore() in a finally block or afterEach is safer.

♻️ Proposed safer pattern
   it('handles createSpec throwing an error', () => {
     const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
+    const createSpecSpy = jest.spyOn(mosaicChartTypes.histogram, 'createSpec')
+      .mockImplementation(() => {
+        throw new Error('Test error');
+      });

-    // Mock a chart type that throws
-    const originalHistogram = mosaicChartTypes.histogram;
-    mosaicChartTypes.histogram = {
-      ...originalHistogram,
-      createSpec: () => {
-        throw new Error('Test error');
-      },
-    };
-
     const result = generateMosaicChartSpec('sales', 'histogram', {
       field: 'amount',
     });

     expect(result).toBeNull();
     expect(consoleSpy).toHaveBeenCalledWith(
       expect.stringContaining('Failed to generate spec'),
       expect.any(Error),
     );

-    // Restore
-    mosaicChartTypes.histogram = originalHistogram;
+    createSpecSpy.mockRestore();
     consoleSpy.mockRestore();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/__tests__/generateMosaicChartSpec.test.ts` around lines 101 -
126, The test directly mutates mosaicChartTypes.histogram which is fragile;
instead spy on the registry entry and restore it reliably. Replace the direct
assignment to mosaicChartTypes.histogram with jest.spyOn(mosaicChartTypes,
'histogram') (or spy on the createSpec method) to mock createSpec to throw, call
generateMosaicChartSpec('sales','histogram', {...}) as before, and ensure you
call mockRestore() in a finally block or in afterEach so the original
mosaicChartTypes.histogram is always restored; reference the
mosaicChartTypes.histogram symbol, the createSpec method,
generateMosaicChartSpec, jest.spyOn, and mockRestore when making the change.
packages/mosaic/src/dashboard/chart-settings/DynamicChartSettings.tsx (1)

16-48: ⚡ Quick win

Inline callback inside memo causes unnecessary re-renders.

The onChange callback on line 21-23 creates a new function reference on every render, which propagates through useChartFieldForm and triggers child re-renders, partially defeating memo(). Wrap it with useCallback.

♻️ Proposed fix
-export const DynamicChartSettings: FC<DynamicChartSettingsProps> = memo(
-  ({chartTypeDefinition, columns, values, onChange}) => {
+export const DynamicChartSettings: FC<DynamicChartSettingsProps> = memo(
+  ({chartTypeDefinition, columns, values, onChange}) => {
+    const handleChange = useCallback(
+      (key: string, value: unknown) => {
+        onChange({...values, [key]: value});
+      },
+      [onChange, values],
+    );
+
     const {fields, handleFieldChange} = useChartFieldForm({
       fields: chartTypeDefinition.fields,
       values,
-      onChange: (key, value) => {
-        onChange({...values, [key]: value});
-      },
+      onChange: handleChange,
     });

Note: You'll need to add useCallback to the import on line 1.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/dashboard/chart-settings/DynamicChartSettings.tsx` around
lines 16 - 48, The inline onChange passed into useChartFieldForm inside
DynamicChartSettings creates a new function each render and defeats memoization;
wrap that callback in useCallback (and add useCallback to the imports) so the
function reference is stable before calling useChartFieldForm — e.g. create a
memoized const onFieldChange = useCallback((key, value) => onChange({...values,
[key]: value}), [onChange, values]) and pass onFieldChange to useChartFieldForm;
keep the rest (fields, handleFieldChange, FieldSelectorInput usage) unchanged.
packages/mosaic/src/dashboard/chart-settings/ChartSettings.tsx (1)

55-76: 💤 Low value

Consider wrapping handleChartTypeChange in useCallback.

The handler creates a new function reference on every render. If ChartTypeSelector is memoized or passes this to memoized children, this could cause unnecessary re-renders.

♻️ Proposed fix
 const ChartSettingsTypeSelector: FC = () => {
   const {config, columns, onChange} = useChartSettingsContext();

-  const handleChartTypeChange = (newChartType: VgPlotChartType) => {
+  const handleChartTypeChange = useCallback((newChartType: VgPlotChartType) => {
     // When changing chart type, clear settings and don't show chart
     // until user selects all required fields
     onChange({
       chartType: newChartType,
       settings: {},
       vgplot: null,
       settingsOpen: config.settingsOpen,
     });
-  };
+  }, [config.settingsOpen, onChange]);

   return (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/dashboard/chart-settings/ChartSettings.tsx` around lines
55 - 76, The handler handleChartTypeChange in ChartSettingsTypeSelector should
be memoized with React's useCallback to avoid creating a new function reference
each render; wrap the existing function in useCallback and include the correct
dependency array (at minimum onChange and config.settingsOpen) so it only
changes when those values change, ensure useCallback is imported from React, and
keep the function body unchanged (still calling onChange to reset chartType,
settings, vgplot, and settingsOpen) so ChartTypeSelector receives a stable
onChange prop.
packages/mosaic/src/dashboard/MosaicDashboardVgPlotPanelRenderer.tsx (1)

18-34: 💤 Low value

Simplify toRenderableMosaicSpec by removing unnecessary try-catch.

The only operations inside the try block (type checking, destructuring, returning) cannot throw. The defensive try-catch adds complexity without catching real errors.

♻️ Simplified version
 function toRenderableMosaicSpec(vgplot: unknown): Spec | null {
-  try {
-    if (!vgplot || typeof vgplot !== 'object' || Array.isArray(vgplot)) {
-      return null;
-    }
-
-    const vgplotRecord = vgplot as Spec;
-
-    // eslint-disable-next-line `@typescript-eslint/no-unused-vars`
-    const {$schema, ...mosaicSpec} = vgplotRecord;
-
-    return mosaicSpec;
-  } catch (error) {
-    console.error('[toRenderableMosaicSpec] Failed to parse spec:', error);
+  if (!vgplot || typeof vgplot !== 'object' || Array.isArray(vgplot)) {
     return null;
   }
+
+  // eslint-disable-next-line `@typescript-eslint/no-unused-vars`
+  const {$schema, ...mosaicSpec} = vgplot as Spec;
+
+  return mosaicSpec;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/dashboard/MosaicDashboardVgPlotPanelRenderer.tsx` around
lines 18 - 34, Remove the unnecessary try-catch in toRenderableMosaicSpec: keep
the input validation (check for falsy, non-object, or array) and the
destructuring that strips $schema from the vgplotRecord, then directly return
mosaicSpec (typed as Spec | null). Update the function body in
MosaicDashboardVgPlotPanelRenderer.tsx to eliminate the try/catch block and
associated catch logging while preserving the early return of null on invalid
input and the cast to Spec for vgplotRecord.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/sqlrooms-cli-ui/src/store.ts`:
- Line 84: There is a duplicate import for VgPlotPanelConfig from
'@sqlrooms/mosaic'; remove the standalone import and add VgPlotPanelConfig to
the existing import declaration that already imports from '@sqlrooms/mosaic' (so
the single import statement includes VgPlotPanelConfig alongside the other
symbols), ensuring no duplicate-imports lint error and preserving the original
exported names and usage in store.ts.

In `@packages/mosaic/__tests__/DashboardPanelErrorBoundary.test.tsx`:
- Around line 5-49: Tests only exercise the no-error path because
renderToStaticMarkup is SSR and error boundaries don't catch errors there; add a
client-side test that renders DashboardPanelErrorBoundary with a child component
that throws to verify the fallback UI ("Failed to load panel") and that errors
are suppressed. Fix by replacing or adding a test that mounts
DashboardPanelErrorBoundary in a real DOM (either by adding
`@testing-library/react` and using its render() to assert the fallback, or by
using ReactDOM.createRoot to render the boundary with a throwing component, then
assert the DOM contains "Failed to load panel" and unmount/cleanup), targeting
DashboardPanelErrorBoundary and its getDerivedStateFromError behavior.

In `@packages/mosaic/src/chart-types/ecdf/definition.ts`:
- Around line 33-59: The y encoding currently uses a cumulative sum of the field
which produces a running total instead of an eCDF; in createSpec change the y
encodings for both the areaY and lineY marks to use cumulative counts by
replacing y: {sum: field, cumulative: true} with y: {count: null, cumulative:
true} so the plot represents the empirical cumulative distribution (apply the
same change for the areaY and lineY entries referencing field).

In `@packages/mosaic/src/chart-types/index.ts`:
- Around line 61-63: The function createDefaultChartTypes currently references
ChartTypeDefinition from ../chart-builders/types, creating a circular
dependency; remove the explicit return type import from chart-types/index.ts and
let TypeScript infer the return type for createDefaultChartTypes (or
alternatively extract ChartTypeDefinition into a neutral module both packages
can import). Update the createDefaultChartTypes signature to omit the
import-based return type so chart-types no longer imports chart-builders/types
and rerun the dependency check.

In `@packages/mosaic/src/dashboard/generateMosaicChartSpec.ts`:
- Around line 1-3: Change the first import to a type-only import for Spec (use
"import type {Spec} from '@uwdata/mosaic-spec'") and merge the two imports from
'../chart-types' into a single import that exports mosaicChartTypes,
VgPlotChartSettings, and VgPlotChartType so you only have one module specifier
for '../chart-types' (update references to Spec, mosaicChartTypes,
VgPlotChartSettings, and VgPlotChartType accordingly).

In `@packages/mosaic/src/dashboard/MosaicDashboardPanelLayout.tsx`:
- Around line 55-66: MosaicDashboardPanelLayout currently hardcodes the header
text "Chart settings"; add an optional prop settingsTitle?: string to the
MosaicDashboardPanelLayout component (with a default value of "Chart settings")
and replace the hardcoded span content with this prop so callers can override
the panel title; update the component's props/type signature and any call sites
as needed, and ensure the collapse button behavior
(panelRef.current?.collapse()) remains unchanged.

In `@packages/mosaic/src/dashboard/MosaicDashboardSlice.ts`:
- Around line 77-89: LegacyPanelConfig currently allows any string for "type" so
modern types can fall through to the legacy branch; change LegacyPanelConfig's
type schema to explicitly exclude the known modern panel type strings (e.g.
'vgplot' and 'profiler') so only true legacy payloads match. Concretely, update
the "type" entry in LegacyPanelConfig (the z.object that defines
id/type/title/source/config) to use a refinement or predicate (for example
z.string().refine(t => t !== 'vgplot' && t !== 'profiler')) or an explicit union
of allowed legacy literals, leaving VgPlotPanelConfig, ProfilerPanel and the
discriminatedUnion('type', [VgPlotPanelConfig, ProfilerPanel]) unchanged.

---

Outside diff comments:
In `@packages/mosaic/src/dashboard/MosaicDashboardSlice.ts`:
- Around line 403-408: The current eviction check for VgPlot panels only returns
true when patch.config has a top-level 'vgplot' property, which misses most
VgPlotChartConfig updates; change the condition in the
MOSAIC_DASHBOARD_VGPLOT_PANEL_TYPE branch to evict on any config change by
checking presence of patch.config (e.g., return Boolean(patch.config)) instead
of using Object.prototype.hasOwnProperty.call(patch.config, 'vgplot'), and
ensure references to VgPlotChartConfig and the patch handling logic in
MosaicDashboardSlice (the code that inspects panel.type ===
MOSAIC_DASHBOARD_VGPLOT_PANEL_TYPE) are updated accordingly.

---

Nitpick comments:
In `@packages/mosaic/__tests__/generateMosaicChartSpec.test.ts`:
- Around line 101-126: The test directly mutates mosaicChartTypes.histogram
which is fragile; instead spy on the registry entry and restore it reliably.
Replace the direct assignment to mosaicChartTypes.histogram with
jest.spyOn(mosaicChartTypes, 'histogram') (or spy on the createSpec method) to
mock createSpec to throw, call generateMosaicChartSpec('sales','histogram',
{...}) as before, and ensure you call mockRestore() in a finally block or in
afterEach so the original mosaicChartTypes.histogram is always restored;
reference the mosaicChartTypes.histogram symbol, the createSpec method,
generateMosaicChartSpec, jest.spyOn, and mockRestore when making the change.

In `@packages/mosaic/src/chart-builders/hooks/useChartFieldForm.ts`:
- Around line 15-20: The useCallback around handleFieldChange in
useChartFieldForm is ineffective because callers pass an inline unstable
onChange; remove the misleading memoization by replacing the useCallback wrapper
with a plain function export (i.e., change handleFieldChange in
useChartFieldForm to a direct function that calls onChange), or alternatively
stabilize the caller-side onChange (wrap the inline (key, value) =>
setFieldValue(...) in ChartBuilderFields.tsx with useCallback) if you want to
preserve the hook's memoization contract—pick one approach and apply
consistently to handleFieldChange and its caller(s).

In `@packages/mosaic/src/chart-types/count-plot/definition.ts`:
- Around line 9-15: BG_COLOR, FG_COLOR and titleFromDescription are duplicated;
extract them into a shared module (e.g., export constants BG_COLOR, FG_COLOR and
a function titleFromDescription from a new/shared helper like chartConstants or
chartTitleUtils) and replace the local definitions in both count-plot and
histogram definitions with imports of those symbols; update usages to reference
the imported BG_COLOR, FG_COLOR and titleFromDescription so both files reuse the
single implementation.

In `@packages/mosaic/src/chart-types/custom-spec/definition.ts`:
- Around line 6-9: The titleFromDescription function is duplicated; extract it
into the shared utility module chartTypeUtils (create and export a function
titleFromDescription(description: string): (fieldValues: Record<string,string>)
=> string that delegates to buildDefaultChartTitle), update both usages in
custom-spec/definition.ts and line-chart/definition.ts to import the shared
titleFromDescription from chartTypeUtils, and remove the duplicate definition
from each file so only the exported utility remains.

In `@packages/mosaic/src/chart-types/ecdf/definition.ts`:
- Around line 9-14: The FG_COLOR constant and titleFromDescription function are
duplicated across chart-type definitions; extract them into a new exported
helpers module (chartTypeUtils.ts), export FG_COLOR and titleFromDescription
(which should call buildDefaultChartTitle as before), then replace the local
declarations in each chart-type definition (ecdf, box-plot, heatmap, histogram)
with imports from chartTypeUtils.ts so all files use the single shared FG_COLOR
and titleFromDescription.

In `@packages/mosaic/src/chart-types/histogram/definition.ts`:
- Around line 9-15: BG_COLOR, FG_COLOR, and titleFromDescription are duplicated
across multiple chart definition files; move these shared helpers into
chartTypeUtils.ts and import them from there. Specifically, add BG_COLOR and
FG_COLOR constants and a titleFromDescription(description: string) curried
wrapper that calls buildDefaultChartTitle in
packages/mosaic/src/chart-builders/chartTypeUtils.ts, then remove the duplicated
definitions from histogram/definition.ts (and other chart definition files) and
replace them with imports of BG_COLOR, FG_COLOR, and titleFromDescription from
chartTypeUtils.ts.

In `@packages/mosaic/src/chart-types/line-chart/definition.ts`:
- Around line 12-15: The helper titleFromDescription is duplicated across
chart-type definitions; extract and export it from the shared chartTypeUtils
module where buildDefaultChartTitle already lives, then remove the duplicate
definitions. Specifically, add an exported function
titleFromDescription(description: string) that returns (fieldValues) =>
buildDefaultChartTitle(description, fieldValues) in the chartTypeUtils module,
and in each chart definition (e.g., the line-chart definition that currently
declares titleFromDescription) replace the local function with an import of the
shared titleFromDescription and remove the redundant code.

In `@packages/mosaic/src/dashboard/chart-settings/ChartSettings.tsx`:
- Around line 55-76: The handler handleChartTypeChange in
ChartSettingsTypeSelector should be memoized with React's useCallback to avoid
creating a new function reference each render; wrap the existing function in
useCallback and include the correct dependency array (at minimum onChange and
config.settingsOpen) so it only changes when those values change, ensure
useCallback is imported from React, and keep the function body unchanged (still
calling onChange to reset chartType, settings, vgplot, and settingsOpen) so
ChartTypeSelector receives a stable onChange prop.

In `@packages/mosaic/src/dashboard/chart-settings/ChartSettingsContext.tsx`:
- Around line 41-47: The Provider is passing an inline object to
ChartSettingsContext.Provider causing a new reference each render; wrap the
value in React.useMemo and return {tableName, config, columns, onChange} inside
it so the object identity is stable, and use [tableName, config, columns,
onChange] (or the minimal real dependencies) as the dependency array; update the
ChartSettingsContext.Provider to pass this memoizedValue instead of the inline
object so consumers only re-render when those inputs actually change.

In `@packages/mosaic/src/dashboard/chart-settings/DynamicChartSettings.tsx`:
- Around line 16-48: The inline onChange passed into useChartFieldForm inside
DynamicChartSettings creates a new function each render and defeats memoization;
wrap that callback in useCallback (and add useCallback to the imports) so the
function reference is stable before calling useChartFieldForm — e.g. create a
memoized const onFieldChange = useCallback((key, value) => onChange({...values,
[key]: value}), [onChange, values]) and pass onFieldChange to useChartFieldForm;
keep the rest (fields, handleFieldChange, FieldSelectorInput usage) unchanged.

In `@packages/mosaic/src/dashboard/MosaicDashboard.tsx`:
- Around line 106-124: The handleAddDefaultChart callback closes over the whole
dashboard object causing unnecessary re-creations; change its dependency array
to only include the specific values it reads (dashboard?.selectedTable,
addPanel, dashboardId) by replacing "dashboard" with "dashboard?.selectedTable"
so that handleAddDefaultChart only re-runs when the selected table changes; keep
the rest of the implementation the same and ensure references to
createMosaicDashboardVgPlotPanelConfig and addPanel remain unchanged.

In `@packages/mosaic/src/dashboard/MosaicDashboardPanelLayout.tsx`:
- Around line 31-41: The onResize function is recreated each render causing
unstable props to ResizablePanel; wrap the handler in React.useCallback to
memoize it (referencing panelRef.current, isOpen, and onIsOpenChange) so its
identity remains stable across renders. Update the onResize declaration (used as
prop to ResizablePanel) to use useCallback and include only the necessary
dependencies (panelRef, isOpen, onIsOpenChange) to avoid stale closures while
preventing unnecessary re-renders.

In `@packages/mosaic/src/dashboard/MosaicDashboardVgPlotPanelRenderer.tsx`:
- Around line 18-34: Remove the unnecessary try-catch in toRenderableMosaicSpec:
keep the input validation (check for falsy, non-object, or array) and the
destructuring that strips $schema from the vgplotRecord, then directly return
mosaicSpec (typed as Spec | null). Update the function body in
MosaicDashboardVgPlotPanelRenderer.tsx to eliminate the try/catch block and
associated catch logging while preserving the early return of null on invalid
input and the cast to Spec for vgplotRecord.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cfb57297-cae8-41d5-8d87-a22ca10cd537

📥 Commits

Reviewing files that changed from the base of the PR and between 134461c and 723bd9b.

📒 Files selected for processing (68)
  • apps/sqlrooms-cli-ui/src/createDashboardChartTemplateTool.ts
  • apps/sqlrooms-cli-ui/src/store.ts
  • examples/deckgl-mosaic/src/components/filters/FiltersPanel.tsx
  • packages/mosaic/__tests__/ChartSettings.test.tsx
  • packages/mosaic/__tests__/DashboardPanelErrorBoundary.test.tsx
  • packages/mosaic/__tests__/MosaicDashboardSlice.test.ts
  • packages/mosaic/__tests__/generateMosaicChartSpec.test.ts
  • packages/mosaic/src/MosaicChartBuilder.ts
  • packages/mosaic/src/chart-builders/ChartBuilderActions.tsx
  • packages/mosaic/src/chart-builders/ChartBuilderContent.tsx
  • packages/mosaic/src/chart-builders/ChartBuilderContext.tsx
  • packages/mosaic/src/chart-builders/ChartBuilderDialog.tsx
  • packages/mosaic/src/chart-builders/ChartBuilderFields.tsx
  • packages/mosaic/src/chart-builders/ChartBuilderRoot.tsx
  • packages/mosaic/src/chart-builders/builders.ts
  • packages/mosaic/src/chart-builders/chartTypeUtils.ts
  • packages/mosaic/src/chart-builders/chartTypes.ts
  • packages/mosaic/src/chart-builders/createChartBuilderStore.ts
  • packages/mosaic/src/chart-builders/hooks/useChartFieldForm.ts
  • packages/mosaic/src/chart-builders/types.ts
  • packages/mosaic/src/chart-types/box-plot/definition.ts
  • packages/mosaic/src/chart-types/box-plot/index.ts
  • packages/mosaic/src/chart-types/box-plot/schema.ts
  • packages/mosaic/src/chart-types/bubble-chart/definition.ts
  • packages/mosaic/src/chart-types/bubble-chart/index.ts
  • packages/mosaic/src/chart-types/bubble-chart/schema.ts
  • packages/mosaic/src/chart-types/count-plot/definition.ts
  • packages/mosaic/src/chart-types/count-plot/index.ts
  • packages/mosaic/src/chart-types/count-plot/schema.ts
  • packages/mosaic/src/chart-types/custom-spec/definition.ts
  • packages/mosaic/src/chart-types/custom-spec/index.ts
  • packages/mosaic/src/chart-types/custom-spec/schema.ts
  • packages/mosaic/src/chart-types/ecdf/definition.ts
  • packages/mosaic/src/chart-types/ecdf/index.ts
  • packages/mosaic/src/chart-types/ecdf/schema.ts
  • packages/mosaic/src/chart-types/heatmap/definition.ts
  • packages/mosaic/src/chart-types/heatmap/index.ts
  • packages/mosaic/src/chart-types/heatmap/schema.ts
  • packages/mosaic/src/chart-types/histogram/definition.ts
  • packages/mosaic/src/chart-types/histogram/index.ts
  • packages/mosaic/src/chart-types/histogram/schema.ts
  • packages/mosaic/src/chart-types/index.ts
  • packages/mosaic/src/chart-types/line-chart/definition.ts
  • packages/mosaic/src/chart-types/line-chart/index.ts
  • packages/mosaic/src/chart-types/line-chart/schema.ts
  • packages/mosaic/src/chart-types/registry.ts
  • packages/mosaic/src/dashboard/DashboardPanelErrorBoundary.tsx
  • packages/mosaic/src/dashboard/MosaicDashboard.tsx
  • packages/mosaic/src/dashboard/MosaicDashboardContext.tsx
  • packages/mosaic/src/dashboard/MosaicDashboardPanel.tsx
  • packages/mosaic/src/dashboard/MosaicDashboardPanelLayout.tsx
  • packages/mosaic/src/dashboard/MosaicDashboardProfilerPanelRenderer.tsx
  • packages/mosaic/src/dashboard/MosaicDashboardSlice.ts
  • packages/mosaic/src/dashboard/MosaicDashboardToolbar.tsx
  • packages/mosaic/src/dashboard/MosaicDashboardVgPlotHeaderActions.tsx
  • packages/mosaic/src/dashboard/MosaicDashboardVgPlotPanelRenderer.tsx
  • packages/mosaic/src/dashboard/chart-settings/ChartSettings.tsx
  • packages/mosaic/src/dashboard/chart-settings/ChartSettingsContext.tsx
  • packages/mosaic/src/dashboard/chart-settings/ChartSettingsPanel.tsx
  • packages/mosaic/src/dashboard/chart-settings/ChartTypeSelector.tsx
  • packages/mosaic/src/dashboard/chart-settings/DynamicChartSettings.tsx
  • packages/mosaic/src/dashboard/chart-settings/index.ts
  • packages/mosaic/src/dashboard/chart-settings/useTableColumns.ts
  • packages/mosaic/src/dashboard/defaultPanelRenderers.ts
  • packages/mosaic/src/dashboard/generateMosaicChartSpec.ts
  • packages/mosaic/src/index.ts
  • packages/ui/src/components/resizable.tsx
  • packages/ui/src/index.ts
💤 Files with no reviewable changes (1)
  • packages/mosaic/src/chart-builders/chartTypes.ts

Comment thread apps/sqlrooms-cli-ui/src/store.ts Outdated
Comment thread packages/mosaic/__tests__/DashboardPanelErrorBoundary.test.tsx Outdated
Comment on lines +33 to +59
createSpec: (tableName, {field}): Spec =>
({
plot: [
{
mark: 'areaY',
data: {from: tableName, filterBy: '$brush'},
x: field,
y: {sum: field, cumulative: true},
fill: FG_COLOR,
fillOpacity: 0.3,
},
{
mark: 'lineY',
data: {from: tableName, filterBy: '$brush'},
x: field,
y: {sum: field, cumulative: true},
stroke: FG_COLOR,
},
{select: 'intervalX', as: '$brush'},
],
xLabel: field,
yLabel: 'Cumulative',
height: 250,
width: 380,
margins: {left: 50, right: 20, top: 20, bottom: 50},
params: {brush: {select: 'crossfilter'}},
}) as Spec,

@coderabbitai coderabbitai Bot May 4, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Mosaic vgplot spec y channel "count cumulative" eCDF example

💡 Result:

In Mosaic vgplot, the y channel supports expressions including aggregates like count and window functions for cumulative computations, enabling empirical Cumulative Distribution Function (eCDF) plots. No official built-in eCDF mark exists, but you can create one using a line or area mark with a custom SQL transform for cumulative proportions. To plot an eCDF: 1. Bin the data on x if needed (e.g., bin(column)). 2. Compute cumulative count using a window function: count() OVER (ORDER BY x_column ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW). 3. Normalize by total count for proportions: divide by count() OVER . 4. Use lineY or areaY mark with y set to this cumulative expression. Example JavaScript spec (assuming a table "data" with numeric column "value"): import * as vg from "@uwdata/vgplot"; vg.plot( vg.lineY( vg.from("data", { select: { x: "value", y_cum: "SUM(count) OVER (ORDER BY value ROWS UNBOUNDED PRECEDING) / SUM(count) OVER " }, groupBy: ["value"], aggregate: { count: "count(*)" } }), { x: "x", y: "y_cum" } ), vg.width(600), vg.height(400) ); This groups by binned/unique values, aggregates counts per bin, then computes the running cumulative proportion via window SUM (equivalent to cumulative count normalized). For YAML/JSON spec mode, use equivalent field expressions in the mark's data transform. Official docs confirm y channels accept aggregate/window expressions via Mosaic SQL (e.g., count, window functions like cumulative sums). DuckDB supports cume_dist directly for normalized eCDF values: cume_dist OVER (ORDER BY value). Use y: "cume_dist OVER (ORDER BY value)" for simpler proportion eCDF. Examples appear in Mosaic vgplot demos (e.g., histograms with y: count), extensible to cumulatives. Legacy Mosaic R has plotCumfreq for eCDFs, but vgplot is the modern JS grammar.

Citations:


🏁 Script executed:

# Read the ECDF definition file to confirm the current implementation
cat -n packages/mosaic/src/chart-types/ecdf/definition.ts

Repository: sqlrooms/sqlrooms

Length of output: 2267


🏁 Script executed:

# Search for other eCDF references or tests in the codebase
rg "eCDF|cumulative.*count|ecdf" --type ts --type tsx -i

Repository: sqlrooms/sqlrooms

Length of output: 89


🌐 Web query:

Observable Plot cumulative aggregation "sum" vs "count" semantics

💡 Result:

In Observable Plot (Plot.js), the “sum” vs “count” distinction is purely about which reducer you choose inside an aggregation transform. 1) group transform (aggregates categorical groups) - Reducer options include: - count: number of elements in the group - sum: sum of the specified input channel values - Semantics rule: if you specify reducer sum, but you do not bind that output channel to an input channel to sum, then Plot treats sum as equivalent to count. 2) bin transform (aggregates quantitative/temporal into bins) - Similar reducer options exist for binned data, including count and sum. 3) cumulative behavior - “cumulative” is not about sum vs count; it’s a separate option on the bin transform that makes bins accumulate across bin thresholds (cumulative distribution). Practical example for your “stacked bar totals at top” goal - To label the total at the top of each x-category’s stacked bar, you typically compute a per-x total using groupX with y: "sum" (summing the y input values for each x group), then use that aggregated total for the text mark. If you want, paste a small schema/sample of your tidy data (names of x, y, z and what columns contain), and I’ll show the exact Plot snippet tailored to your field names.

Citations:


🌐 Web query:

Observable Plot y-channel aggregation "sum" "count" cumulative bin transform semantics

💡 Result:

In Observable Plot, the bin transform (e.g., binX, binY) supports y-channel aggregations like "count" and "sum" via the outputs object, such as {y: "count"} or {y: "sum"}. The "count" reducer computes the number of elements (frequency) in each bin. The "sum" reducer computes the sum of values from the specified y input channel; if no y input is provided, "sum" behaves identically to "count". The cumulative option on the bin transform produces cumulative distributions by adjusting bin contents: if positive (e.g., true or 1), each bin includes all lesser bins (cumulative from low to high); if -1, each bin includes all greater bins (reverse cumulative). This affects aggregation: for {y: "count"}, y becomes the cumulative count up to that bin; for {y: "sum"}, y becomes the cumulative sum of the y values up to that bin (requiring a y input channel). Example for cumulative count histogram: Plot.rectY(data, Plot.binX({y: "count"}, {x: "weight", cumulative: true})) Example for per-bin sum (non-cumulative): Plot.binX({y: "sum"}, {x: "culmen_length_mm", y: "body_mass_g"}) For cumulative sum, combine with cumulative: true, ensuring y input is defined. Aggregations respect series (z, fill, stroke) subdivisions. These semantics apply similarly to hexbin and group transforms.

Citations:


🏁 Script executed:

# Search for histogram definition to see how it handles cumulative
rg -A 10 "cumulative" packages/mosaic/src/chart-types/ --type ts

Repository: sqlrooms/sqlrooms

Length of output: 3119


🏁 Script executed:

# Search for tests or examples related to eCDF
rg -i "ecdf\|empirical.*cumulative" --type ts

Repository: sqlrooms/sqlrooms

Length of output: 43


🏁 Script executed:

# Look for Observable Plot or vgplot spec documentation references
rg "@uwdata/mosaic\|vgplot" packages/mosaic/src -l | head -5

Repository: sqlrooms/sqlrooms

Length of output: 43


🏁 Script executed:

# Search for count aggregation usage in chart definitions
rg "count.*cumulative|y:.*count" packages/mosaic/src/chart-types/ --type ts -B 2 -A 2

Repository: sqlrooms/sqlrooms

Length of output: 2328


🏁 Script executed:

# Look for histogram definition to see cumulative count pattern
fd definition.ts packages/mosaic/src/chart-types/histogram/ -x cat -n {}

Repository: sqlrooms/sqlrooms

Length of output: 2349


🌐 Web query:

Mosaic vgplot "count" aggregation syntax y-channel specification

💡 Result:

In Mosaic vgplot, the "count" aggregation is specified in encoding channels like the y-channel using the syntax vg.count in JavaScript or {count: } (or {count: null}) in YAML/JSON specs. For a histogram with vertical bars (binned x, count on y), use rectY or barY marks: JavaScript example: plot( rectY(from("trips"), {x: bin("time"), y: count, fill: "steelblue"}), ... ) YAML/JSON equivalent: - mark: rectY data: {from: "trips"} x: {bin: "time"} y: {count: } fill: steelblue When aggregates like count are used, non-aggregate fields (e.g., the binned x) are automatically included in GROUP BY. This pushes aggregation to the database (e.g., DuckDB). For other channels (fill, r), count can similarly be used, e.g., in hexbin: {x: "i", y: "v", fill: count}. This follows Observable Plot semantics adapted for Mosaic SQL query generation.

Citations:


y: {sum: field, cumulative: true} computes cumulative sum of values, not an eCDF.

An eCDF (Empirical Cumulative Distribution Function) maps each value x to the proportion (or count) of observations ≤ x. This requires cumulative count, not cumulative sum of the field. For data [1, 2, 3]:

  • {sum: field, cumulative: true} → running total [1, 3, 6] (wrong shape)
  • {count: null, cumulative: true} → [1, 2, 3] — the correct eCDF count shape

The current implementation will plot the wrong curve entirely. Fix both marks (lines 40 and 48) to use {count: null, cumulative: true}.

Proposed fix
-        y: {sum: field, cumulative: true},
+        y: {count: null, cumulative: true},

Apply identically to both the areaY mark (line 40) and the lineY mark (line 48).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
createSpec: (tableName, {field}): Spec =>
({
plot: [
{
mark: 'areaY',
data: {from: tableName, filterBy: '$brush'},
x: field,
y: {sum: field, cumulative: true},
fill: FG_COLOR,
fillOpacity: 0.3,
},
{
mark: 'lineY',
data: {from: tableName, filterBy: '$brush'},
x: field,
y: {sum: field, cumulative: true},
stroke: FG_COLOR,
},
{select: 'intervalX', as: '$brush'},
],
xLabel: field,
yLabel: 'Cumulative',
height: 250,
width: 380,
margins: {left: 50, right: 20, top: 20, bottom: 50},
params: {brush: {select: 'crossfilter'}},
}) as Spec,
createSpec: (tableName, {field}): Spec =>
({
plot: [
{
mark: 'areaY',
data: {from: tableName, filterBy: '$brush'},
x: field,
y: {count: null, cumulative: true},
fill: FG_COLOR,
fillOpacity: 0.3,
},
{
mark: 'lineY',
data: {from: tableName, filterBy: '$brush'},
x: field,
y: {count: null, cumulative: true},
stroke: FG_COLOR,
},
{select: 'intervalX', as: '$brush'},
],
xLabel: field,
yLabel: 'Cumulative',
height: 250,
width: 380,
margins: {left: 50, right: 20, top: 20, bottom: 50},
params: {brush: {select: 'crossfilter'}},
}) as Spec,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mosaic/src/chart-types/ecdf/definition.ts` around lines 33 - 59, The
y encoding currently uses a cumulative sum of the field which produces a running
total instead of an eCDF; in createSpec change the y encodings for both the
areaY and lineY marks to use cumulative counts by replacing y: {sum: field,
cumulative: true} with y: {count: null, cumulative: true} so the plot represents
the empirical cumulative distribution (apply the same change for the areaY and
lineY entries referencing field).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmitriy-kostianetskiy to fix this in a separate PR

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmitriy-kostianetskiy, understood! Would you like me to open a GitHub issue to track this fix so it doesn't get lost? I can create one with a reference back to this PR and comment.

Comment thread packages/mosaic/src/chart-types/index.ts Outdated
Comment thread packages/mosaic/src/dashboard/generateMosaicChartSpec.ts Outdated
Comment thread packages/mosaic/src/dashboard/MosaicDashboardPanelLayout.tsx Outdated
Comment thread packages/mosaic/src/dashboard/MosaicDashboardSlice.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
packages/mosaic/src/dashboard/MosaicDashboardPanelLayout.tsx (2)

29-39: ⚡ Quick win

Memoize onResize with useCallback to avoid passing a new function reference on every render.

onResize is recreated on every render and passed as a prop to ResizablePanel. Wrap it in useCallback with [isOpen, onIsOpenChange] as dependencies.

♻️ Proposed fix
-const onResize = () => {
+const onResize = React.useCallback(() => {
   const isCollapsed = panelRef.current?.isCollapsed();

   if (isCollapsed && isOpen) {
     onIsOpenChange?.(false);
   }

   if (!isCollapsed && !isOpen) {
     onIsOpenChange?.(true);
   }
-};
+}, [isOpen, onIsOpenChange]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mosaic/src/dashboard/MosaicDashboardPanelLayout.tsx` around lines 29
- 39, The onResize handler is recreated each render and should be memoized: wrap
the onResize function in React's useCallback with dependencies [isOpen,
onIsOpenChange] so the same function reference is passed to ResizablePanel;
inside the memoized callback keep the existing logic that reads
panelRef.current?.isCollapsed() and calls onIsOpenChange?.(false) or
onIsOpenChange?.(true) based on isCollapsed and isOpen.

21-27: ⚡ Quick win

Guard expand()/collapse() with a current-state check to avoid redundant calls.

The effect unconditionally calls expand() or collapse() even when the panel is already in the desired state. This can fire onResize unnecessarily (e.g., when the user manually drags the panel, which already updates isOpen via onResize → onIsOpenChange, and then the effect re-runs and calls collapse() on an already-collapsed panel, triggering onResize a second time). The safe pattern used elsewhere in the codebase guards on current state first:

// Context: packages/layout/src/node-renderers/split-node-renderer/CollapsiblePanelWrapper.tsx
useEffect(() => {
  if (collapsed && !handle.isCollapsed()) {
    handle.collapse();
  } else if (!collapsed && handle.isCollapsed()) {
    handle.expand();
  }
}, [collapsed]);
♻️ Proposed fix
 useEffect(() => {
-  if (isOpen) {
-    panelRef.current?.expand();
-  } else {
-    panelRef.current?.collapse();
+  if (isOpen && panelRef.current?.isCollapsed()) {
+    panelRef.current.expand();
+  } else if (!isOpen && !panelRef.current?.isCollapsed()) {
+    panelRef.current?.collapse();
   }
 }, [isOpen]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mosaic/src/dashboard/MosaicDashboardPanelLayout.tsx` around lines 21
- 27, The effect in MosaicDashboardPanelLayout.tsx currently calls
panelRef.current?.expand() or ?.collapse() unconditionally; change it to first
read the panel's current state (e.g., panelRef.current?.isCollapsed()) and only
call expand() when isOpen is true and isCollapsed() is true, or call collapse()
when isOpen is false and isCollapsed() is false, to avoid redundant calls and
extra onResize triggers; update the useEffect that references isOpen and
panelRef to perform that guarded check before invoking expand()/collapse().
packages/mosaic/src/chart-builders/types.ts (1)

15-29: ⚡ Quick win

Preserve TSettings through exported aliases/helpers

The new generic ChartTypeDefinition<TSettings> is currently collapsed to default any in this adapter layer. Threading generics here keeps settings typing intact for downstream callers.

Suggested patch
-export interface ChartBuilderTemplate extends ChartTypeDefinition {
+export interface ChartBuilderTemplate<TSettings = any>
+  extends ChartTypeDefinition<TSettings> {
   /** Icon component */
   icon: ComponentType<{className?: string}>;
 }
@@
-export type ChartSpec = ChartTypeDefinition;
+export type ChartSpec<TSettings = any> = ChartTypeDefinition<TSettings>;
@@
 export function toChartTypeDefinition(
-  template: ChartBuilderTemplate,
-): ChartTypeDefinition {
+  template: ChartBuilderTemplate<TSettings>,
+): ChartTypeDefinition<TSettings> {
@@
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mosaic/src/chart-builders/types.ts` around lines 15 - 29, The
exported types and helper should preserve the ChartTypeDefinition generic
parameter so TSettings isn't lost: make ChartBuilderTemplate generic
(ChartBuilderTemplate<TSettings> extends ChartTypeDefinition<TSettings>) and
update ChartSpec to be a generic alias (ChartSpec<TSettings> =
ChartTypeDefinition<TSettings>), then change toChartTypeDefinition to accept
template: ChartBuilderTemplate<TSettings> and return
ChartTypeDefinition<TSettings>; update any internal references to use the
generic TSettings so downstream callers keep the settings type.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/mosaic/__tests__/ChartSettings.test.tsx`:
- Around line 45-68: The test leaves console.error mocked on failure because
consoleSpy.mockRestore() is only called on the success path; wrap the assertions
and render in a try/finally that always calls consoleSpy.mockRestore(), or add
global cleanup via afterEach(() => jest.restoreAllMocks()) so console.error is
always restored; update the test that uses consoleSpy and ChartSettings.Root /
ChartSettings.Fields to ensure consoleSpy.mockRestore() is executed in finally
(or use jest.restoreAllMocks() in beforeEach/afterEach).

In `@packages/mosaic/src/chart-types/box-plot/definition.ts`:
- Around line 11-14: Extract the duplicated helper into chart-builders/constants
by adding and exporting titleFromDescription, then in each chart definition
(box-plot/definition.ts, heatmap/definition.ts, ecdf/definition.ts,
line-chart/definition.ts, count-plot/definition.ts) remove the local
titleFromDescription function and import the shared titleFromDescription and
buildDefaultChartTitle; to eliminate the duplicated literal description string
in each file, assign the description to a const (e.g., const description = "…")
and use that const for both the object’s description field and for title:
titleFromDescription(description).

In `@packages/mosaic/src/dashboard/chart-settings/ChartSettings.tsx`:
- Around line 65-73: The header always renders the close Button even when
onClose is undefined; update ChartSettings.Header to only render the Button (and
XIcon) when the onClose prop is provided—i.e., guard the Button with a
conditional check on onClose so the close control is not shown if no handler
exists (refer to ChartSettings.Header, onClose, Button, and XIcon to locate the
JSX to change).
- Around line 117-122: The required-field check in ChartSettings.tsx (variable
allRequiredFieldsFilled iterating chartTypeDef.fields) treats undefined as not
required; change the predicate to treat a field as required unless
field.required === false (i.e., use field.required !== false) so it matches
canCreateChartFromType/isChartTypeAvailable and
ChartSettingsFields.handleSettingsChange behavior; ensure the value presence
check (value !== undefined && value !== null && value !== '') remains the same
for those fields.

---

Nitpick comments:
In `@packages/mosaic/src/chart-builders/types.ts`:
- Around line 15-29: The exported types and helper should preserve the
ChartTypeDefinition generic parameter so TSettings isn't lost: make
ChartBuilderTemplate generic (ChartBuilderTemplate<TSettings> extends
ChartTypeDefinition<TSettings>) and update ChartSpec to be a generic alias
(ChartSpec<TSettings> = ChartTypeDefinition<TSettings>), then change
toChartTypeDefinition to accept template: ChartBuilderTemplate<TSettings> and
return ChartTypeDefinition<TSettings>; update any internal references to use the
generic TSettings so downstream callers keep the settings type.

In `@packages/mosaic/src/dashboard/MosaicDashboardPanelLayout.tsx`:
- Around line 29-39: The onResize handler is recreated each render and should be
memoized: wrap the onResize function in React's useCallback with dependencies
[isOpen, onIsOpenChange] so the same function reference is passed to
ResizablePanel; inside the memoized callback keep the existing logic that reads
panelRef.current?.isCollapsed() and calls onIsOpenChange?.(false) or
onIsOpenChange?.(true) based on isCollapsed and isOpen.
- Around line 21-27: The effect in MosaicDashboardPanelLayout.tsx currently
calls panelRef.current?.expand() or ?.collapse() unconditionally; change it to
first read the panel's current state (e.g., panelRef.current?.isCollapsed()) and
only call expand() when isOpen is true and isCollapsed() is true, or call
collapse() when isOpen is false and isCollapsed() is false, to avoid redundant
calls and extra onResize triggers; update the useEffect that references isOpen
and panelRef to perform that guarded check before invoking expand()/collapse().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1b2fb603-6875-4610-afa8-e1bbea409ff9

📥 Commits

Reviewing files that changed from the base of the PR and between 723bd9b and 5675b4c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (27)
  • apps/sqlrooms-cli-ui/src/store.ts
  • packages/mosaic/__tests__/ChartSettings.test.tsx
  • packages/mosaic/__tests__/MosaicDashboardSlice.test.ts
  • packages/mosaic/__tests__/MosaicProfilerStatusBar.test.tsx
  • packages/mosaic/__tests__/generateMosaicChartSpec.test.ts
  • packages/mosaic/package.json
  • packages/mosaic/src/chart-builders/chartTypeUtils.ts
  • packages/mosaic/src/chart-builders/constants.ts
  • packages/mosaic/src/chart-builders/types.ts
  • packages/mosaic/src/chart-types/base-types.ts
  • packages/mosaic/src/chart-types/box-plot/definition.ts
  • packages/mosaic/src/chart-types/bubble-chart/definition.ts
  • packages/mosaic/src/chart-types/count-plot/definition.ts
  • packages/mosaic/src/chart-types/custom-spec/definition.ts
  • packages/mosaic/src/chart-types/ecdf/definition.ts
  • packages/mosaic/src/chart-types/heatmap/definition.ts
  • packages/mosaic/src/chart-types/histogram/definition.ts
  • packages/mosaic/src/chart-types/index.ts
  • packages/mosaic/src/chart-types/line-chart/definition.ts
  • packages/mosaic/src/chart-types/registry.ts
  • packages/mosaic/src/dashboard/MosaicDashboardPanelLayout.tsx
  • packages/mosaic/src/dashboard/chart-settings/ChartSettings.tsx
  • packages/mosaic/src/dashboard/chart-settings/ChartSettingsPanel.tsx
  • packages/mosaic/src/dashboard/chart-settings/ChartTypeSelector.tsx
  • packages/mosaic/src/dashboard/generateMosaicChartSpec.ts
  • packages/mosaic/test/setup.ts
  • packages/mosaic/tsconfig.json
✅ Files skipped from review due to trivial changes (5)
  • packages/mosaic/tsconfig.json
  • packages/mosaic/test/setup.ts
  • packages/mosaic/tests/generateMosaicChartSpec.test.ts
  • packages/mosaic/src/chart-builders/constants.ts
  • packages/mosaic/package.json
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/mosaic/src/chart-types/registry.ts
  • packages/mosaic/tests/MosaicDashboardSlice.test.ts
  • packages/mosaic/src/chart-types/histogram/definition.ts
  • packages/mosaic/src/chart-types/index.ts
  • packages/mosaic/src/dashboard/generateMosaicChartSpec.ts
  • packages/mosaic/src/dashboard/chart-settings/ChartSettingsPanel.tsx
  • packages/mosaic/src/chart-types/bubble-chart/definition.ts
  • packages/mosaic/src/dashboard/chart-settings/ChartTypeSelector.tsx
  • packages/mosaic/src/chart-types/custom-spec/definition.ts
  • apps/sqlrooms-cli-ui/src/store.ts

Comment thread packages/mosaic/__tests__/ChartSettings.test.tsx Outdated
Comment thread packages/mosaic/src/chart-types/box-plot/definition.ts Outdated
Comment thread packages/mosaic/src/dashboard/chart-settings/ChartSettings.tsx
Comment thread packages/mosaic/src/dashboard/chart-settings/ChartSettings.tsx
Comment thread apps/sqlrooms-cli-ui/src/store.ts Outdated
.mosaicDashboard.getDashboard(artifactId)
?.panels.find((panel) => panel.type === 'vgplot')?.config.vgplot;
?.panels.find(
(panel): panel is VgPlotPanelConfig => panel.type === 'vgplot',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe extract a type guard function?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/sqlrooms-cli-ui/src/store.ts (1)

339-357: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the same isVgPlotPanelConfig guard in the update path.

Line 340 narrows only by panel.type === 'vgplot', then mutates primaryPanel.config. That can break on legacy/stale persisted panel configs that still have type: 'vgplot' but no longer match current config shape. Reusing the guard here makes update behavior consistent with Line 364 and avoids parse-time failures.

🔧 Minimal fix
-          const primaryPanel = dashboard?.panels.find(
-            (panel) => panel.type === 'vgplot',
-          );
+          const primaryPanel = dashboard?.panels.find(isVgPlotPanelConfig);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/sqlrooms-cli-ui/src/store.ts` around lines 339 - 357, The update path
currently finds primaryPanel by panel.type === 'vgplot' and directly spreads
primaryPanel.config; change it to first check
isVgPlotPanelConfig(primaryPanel?.config) before calling
get().mosaicDashboard.updatePanel(artifactId, primaryPanel.id, ...). If the
guard fails, fall back to addPanel as the code already does; ensure you
reference primaryPanel, isVgPlotPanelConfig, get().mosaicDashboard.updatePanel,
artifactId and vgplot when making the conditional so the update only runs when
the config shape matches.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/mosaic/src/dashboard/MosaicDashboardVgPlotHeaderActions.tsx`:
- Around line 44-53: The icon-only settings Button lacks an explicit accessible
name and state; update the Button (the JSX using SettingsIcon and
onClick={handleToggleSettings}) to include an aria-label like "Chart settings"
and add aria-pressed={isSettingsOpen} so screen readers announce the control and
its toggle state (leave title if desired but ensure aria-label and aria-pressed
are present).

---

Outside diff comments:
In `@apps/sqlrooms-cli-ui/src/store.ts`:
- Around line 339-357: The update path currently finds primaryPanel by
panel.type === 'vgplot' and directly spreads primaryPanel.config; change it to
first check isVgPlotPanelConfig(primaryPanel?.config) before calling
get().mosaicDashboard.updatePanel(artifactId, primaryPanel.id, ...). If the
guard fails, fall back to addPanel as the code already does; ensure you
reference primaryPanel, isVgPlotPanelConfig, get().mosaicDashboard.updatePanel,
artifactId and vgplot when making the conditional so the update only runs when
the config shape matches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f37a8d0a-1c68-4781-94b1-c9494129b062

📥 Commits

Reviewing files that changed from the base of the PR and between 5675b4c and fa906d0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • apps/sqlrooms-cli-ui/src/createDashboardChartTemplateTool.ts
  • apps/sqlrooms-cli-ui/src/store.ts
  • examples/deckgl-mosaic/src/components/filters/FiltersPanel.tsx
  • packages/mosaic/__tests__/ChartSettings.test.tsx
  • packages/mosaic/__tests__/MosaicDashboardSlice.test.ts
  • packages/mosaic/src/chart-builders/FieldSelectorInput.tsx
  • packages/mosaic/src/chart-builders/chartTypeUtils.ts
  • packages/mosaic/src/chart-builders/constants.ts
  • packages/mosaic/src/chart-types/box-plot/definition.ts
  • packages/mosaic/src/chart-types/bubble-chart/definition.ts
  • packages/mosaic/src/chart-types/count-plot/definition.ts
  • packages/mosaic/src/chart-types/custom-spec/definition.ts
  • packages/mosaic/src/chart-types/ecdf/definition.ts
  • packages/mosaic/src/chart-types/heatmap/definition.ts
  • packages/mosaic/src/chart-types/histogram/definition.ts
  • packages/mosaic/src/chart-types/line-chart/definition.ts
  • packages/mosaic/src/dashboard/MosaicDashboardSlice.ts
  • packages/mosaic/src/dashboard/MosaicDashboardVgPlotHeaderActions.tsx
  • packages/mosaic/src/dashboard/MosaicDashboardVgPlotPanelRenderer.tsx
  • packages/mosaic/src/dashboard/chart-settings/ChartSettings.tsx
  • packages/mosaic/src/dashboard/chart-settings/ChartTypeSelector.tsx
  • packages/mosaic/src/index.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/mosaic/src/chart-builders/FieldSelectorInput.tsx
  • packages/mosaic/src/chart-builders/constants.ts
  • packages/mosaic/src/chart-types/ecdf/definition.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/mosaic/tests/MosaicDashboardSlice.test.ts
  • packages/mosaic/tests/ChartSettings.test.tsx
  • packages/mosaic/src/chart-types/heatmap/definition.ts
  • examples/deckgl-mosaic/src/components/filters/FiltersPanel.tsx
  • packages/mosaic/src/dashboard/chart-settings/ChartTypeSelector.tsx
  • packages/mosaic/src/dashboard/MosaicDashboardVgPlotPanelRenderer.tsx
  • packages/mosaic/src/chart-types/count-plot/definition.ts
  • packages/mosaic/src/dashboard/chart-settings/ChartSettings.tsx
  • packages/mosaic/src/dashboard/MosaicDashboardSlice.ts
  • packages/mosaic/src/index.ts

Comment on lines +44 to +53
<Button
variant="ghost"
size="icon"
className="data-[state=active]:bg-accent h-6 w-6"
title="Chart settings"
onClick={handleToggleSettings}
data-state={isSettingsOpen ? 'active' : 'inactive'}
>
<SettingsIcon className="h-3.5 w-3.5" />
</Button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add an explicit accessible name to the icon-only settings button.

The button is icon-only and currently relies on title. Add aria-label (and optionally aria-pressed since this is a toggle) so screen readers can reliably announce and track state.

Suggested change
       <Button
         variant="ghost"
         size="icon"
         className="data-[state=active]:bg-accent h-6 w-6"
         title="Chart settings"
+        aria-label="Chart settings"
+        aria-pressed={isSettingsOpen}
         onClick={handleToggleSettings}
         data-state={isSettingsOpen ? 'active' : 'inactive'}
       >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
variant="ghost"
size="icon"
className="data-[state=active]:bg-accent h-6 w-6"
title="Chart settings"
onClick={handleToggleSettings}
data-state={isSettingsOpen ? 'active' : 'inactive'}
>
<SettingsIcon className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
className="data-[state=active]:bg-accent h-6 w-6"
title="Chart settings"
aria-label="Chart settings"
aria-pressed={isSettingsOpen}
onClick={handleToggleSettings}
data-state={isSettingsOpen ? 'active' : 'inactive'}
>
<SettingsIcon className="h-3.5 w-3.5" />
</Button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mosaic/src/dashboard/MosaicDashboardVgPlotHeaderActions.tsx` around
lines 44 - 53, The icon-only settings Button lacks an explicit accessible name
and state; update the Button (the JSX using SettingsIcon and
onClick={handleToggleSettings}) to include an aria-label like "Chart settings"
and add aria-pressed={isSettingsOpen} so screen readers announce the control and
its toggle state (leave title if desired but ensure aria-label and aria-pressed
are present).

@dmitriy-kostianetskiy
dmitriy-kostianetskiy merged commit afc1f0e into main May 5, 2026
4 checks passed
@dmitriy-kostianetskiy
dmitriy-kostianetskiy deleted the dashboard-charts-settings-1 branch May 5, 2026 12:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants