@lightdash/cli 2.53.0 → 2.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,6 +27,7 @@ fetch anything yourself; the only host interaction is through the helpers the ho
27
27
  returns.
28
28
 
29
29
  ```tsx
30
+ const context = useVizContext();
30
31
  const {
31
32
  fieldMapping,
32
33
  rows,
@@ -36,7 +37,7 @@ const {
36
37
  ready,
37
38
  underlyingData,
38
39
  drillDown,
39
- } = useVizContext();
40
+ } = context;
40
41
  ```
41
42
 
42
43
  - `fieldMapping` — `Record<string, string>`: the field name you declared → the query field
@@ -47,6 +48,11 @@ const {
47
48
  option you declared (the viewer's choice, else your declared `default`).
48
49
  - `colorPalette` — `string[]`: the Lightdash palette resolved for this chart. Always pushed,
49
50
  whether or not you declared `colorPalette`. Empty only when the host resolved none.
51
+ - `seriesColors` — `Record<string, string>`: final host-resolved colours keyed by backend
52
+ pivot column name. Read them through `resolveSeriesColor(context, column, index)`.
53
+ - `valueColors` — `Record<string, Record<string, string>>`: final host-resolved colours keyed
54
+ by query field id then raw value. Read them through
55
+ `resolveValueColor(context, fieldId, rawValue, index)`.
50
56
  - `pivotDetails` — the complete backend-pivot layout, or `null` for ordinary rows. See
51
57
  "Backend-pivoted results" below.
52
58
  - `ready` — false until the first context arrives.
@@ -55,8 +61,8 @@ const {
55
61
  - `drillDown` — host-mediated drill on a clicked data point. See "Data-point
56
62
  actions" below.
57
63
 
58
- Read all of them. Ignoring `options` and `colorPalette` is the most common way to build a
59
- viz nobody can configure.
64
+ Read all of them. Pass the complete `context` to the colour helpers; they preserve model
65
+ colours and shared dashboard assignments before falling back to `colorPalette`.
60
66
 
61
67
  ### These app-level APIs do not apply to a viz
62
68
 
@@ -82,10 +88,15 @@ shadcn, charting, theming, floating surfaces, screenshots) still applies.
82
88
 
83
89
  ## Series colours
84
90
 
85
- Never hardcode series hex values and never hand-pick a palette of your own. Colour series
86
- with `colorPalette[i % colorPalette.length]`, and declare `colorPalette` in your output so
87
- the viewer gets the palette picker. Keep a small fallback array in your own code for the
88
- case where `colorPalette` is empty.
91
+ Use the query SDK's host-resolved colour helpers for every series or group:
92
+
93
+ - Backend-pivoted series: `resolveSeriesColor(context, column, index)`.
94
+ - Client-side groups: `resolveValueColor(context, fieldId, rawValue, index)`.
95
+
96
+ The helpers first honor model-defined fixed colours and Lightdash's shared dashboard colour
97
+ assignment, then fall back to `colorPalette[index % colorPalette.length]`. Keep a small
98
+ fallback array only for the case where the helper returns `undefined`, and declare
99
+ `colorPalette` in your output so the viewer gets the palette picker.
89
100
 
90
101
  This is the same rule as the sandbox skill's "chart series colors must come from
91
102
  `CHART_COLORS`" and `references/d3.md`'s "never hand-pick palettes" — the same Lightdash
@@ -132,11 +143,12 @@ const pivotedMetrics =
132
143
  (column) => column.referenceField === metricId,
133
144
  ) ?? [];
134
145
 
135
- const series = pivotedMetrics.map((column) => ({
146
+ const series = pivotedMetrics.map((column, index) => ({
136
147
  columnId: column.pivotColumnName,
137
148
  label:
138
149
  column.pivotValues.find((value) => value.referenceField === seriesId)
139
150
  ?.formatted ?? 'Unknown',
151
+ color: resolveSeriesColor(context, column, index) ?? FALLBACK_COLORS[index % FALLBACK_COLORS.length],
140
152
  }));
141
153
 
142
154
  const categoryId = fieldMapping['category'];
@@ -151,7 +163,7 @@ const data = rows.map((row) => ({
151
163
  }));
152
164
  ```
153
165
 
154
- Use the order of `valuesColumns` for stable colour assignment. A chart with multiple
166
+ Use the order of `valuesColumns` for the helper's fallback index. A chart with multiple
155
167
  declared series fields builds its label from each matching `pivotValues` entry in declared
156
168
  field order. Backend-pivoted rows do not have safe one-source-row provenance, so the host
157
169
  sets `underlyingData.enabled` to false for them.
@@ -275,8 +287,8 @@ One entry per data column the component reads:
275
287
  ### `configOptions`
276
288
 
277
289
  One entry per setting the viewer can change without regenerating the viz. Every option is a
278
- whole-viz value applying to the entire chart — there is no per-series option. To colour
279
- series individually, index into `colorPalette` by series position.
290
+ whole-viz value applying to the entire chart — there is no per-series option. Colour series
291
+ and groups with the SDK's resolved-colour helpers.
280
292
 
281
293
  Every option has:
282
294
 
@@ -301,9 +313,10 @@ Series colours are not in this list. They are declared separately, on `colorPale
301
313
  ### `colorPalette`
302
314
 
303
315
  Whether the viewer gets the standard Lightdash palette picker. Declare
304
- `{ "group": "..." }` when your component colours anything from `colorPalette`, or `null`
305
- when it colours nothing. `group` is optional and works like an option's: the picker joins
306
- that tab, and gets a tab of its own when no option shares the name.
316
+ `{ "group": "..." }` when your component colours anything with the resolved-colour helpers
317
+ or `colorPalette`, or `null` when it colours nothing. `group` is optional and works like an
318
+ option's: the picker joins that tab, and gets a tab of its own when no option shares the
319
+ name.
307
320
 
308
321
  This is not a config option. There is one palette per chart, it has no `name` and no
309
322
  `default`, and its colours arrive on `colorPalette` — never on `options`.
@@ -313,19 +326,21 @@ This is not a config option. There is one palette per chart, it has no `name` an
313
326
  Component and declaration lining up. Your chart will differ; the correspondence must not.
314
327
 
315
328
  ```tsx
316
- import { useVizContext, getFormatted, getRaw } from '@lightdash/query-sdk';
329
+ import { useVizContext, getFormatted, getRaw, resolveValueColor } from '@lightdash/query-sdk';
317
330
  import { Bar, BarChart, Cell, LabelList, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
318
331
 
319
332
  function Chart() {
320
- const { fieldMapping, rows, options, colorPalette, ready } = useVizContext();
333
+ const context = useVizContext();
334
+ const { fieldMapping, rows, options, ready } = context;
321
335
  if (!ready) return <div style={{ height: '100vh' }}>Loading…</div>;
322
336
  const catField = fieldMapping['category']; // your field name -> column id
323
337
  const valField = fieldMapping['value'];
324
338
  const showLabels = options['showLabels'] as boolean; // your option name -> current value
325
339
  const maxBars = options['maxBars'] as number;
326
- const colors = colorPalette.length ? colorPalette : ['#7162FF', '#1A1B1E'];
340
+ const fallbackColors = ['#7162FF', '#1A1B1E'];
327
341
  const data = rows.slice(0, maxBars).map((row) => ({
328
342
  label: getFormatted(row, catField), // display text, e.g. "Completed"
343
+ category: getRaw(row, catField), // raw value for host-resolved colour
329
344
  value: Number(getRaw(row, valField) ?? 0), // raw number
330
345
  }));
331
346
  return (
@@ -340,7 +355,12 @@ function Chart() {
340
355
  <YAxis tickFormatter={(v) => v.toLocaleString()} />
341
356
  <Tooltip />
342
357
  <Bar dataKey="value">
343
- {data.map((d, i) => <Cell key={d.label} fill={colors[i % colors.length]} />)}
358
+ {data.map((d, i) => (
359
+ <Cell
360
+ key={d.label}
361
+ fill={resolveValueColor(context, catField, d.category, i) ?? fallbackColors[i % fallbackColors.length]}
362
+ />
363
+ ))}
344
364
  {showLabels && <LabelList dataKey="value" position="top" />}
345
365
  </Bar>
346
366
  </BarChart>
@@ -372,15 +392,16 @@ minimum, not a menu:
372
392
  - every number you chose (bar width, max rows, decimal places, a threshold) → `number`
373
393
  - every string you wrote into the chart (title, axis label, empty-state text) → `text`
374
394
  - every accent colour that is not a series colour (a target line, a highlight) → `color`
375
- - the series colours, whenever the chart draws more than one series → `colorPalette`
395
+ - the series colours, whenever the chart draws more than one series → resolved-colour
396
+ helper plus `colorPalette`
376
397
 
377
398
  Where the answer is yes, make that literal the option's `default`, declare the option, and
378
399
  read the option in its place. Leave it hardcoded only where changing it would break the
379
400
  chart.
380
401
 
381
402
  Then check both directions: every key you read from `options` is declared, and every option
382
- you declared is read somewhere. `colorPalette` is declared when you colour from
383
- `colorPalette` — it is never read from `options`.
403
+ you declared is read somewhere. `colorPalette` is declared when you use either resolved-
404
+ colour helper or colour from `colorPalette` — it is never read from `options`.
384
405
 
385
406
  Finally, if any mark maps to exactly one source row, the data-point action
386
407
  menu is wired: each interactive datum carries `sourceRow`, the underlying-data
@@ -1218,6 +1218,15 @@ function tableToCsv(columns: Column[], data: Row[], format: FormatFn): string {
1218
1218
  </ScrollArea>
1219
1219
  ```
1220
1220
 
1221
+ ### Table and query performance
1222
+
1223
+ Treat Safari/WebKit as the constrained target for layout-heavy apps. Test dense tables and filter interactions in Safari: an app that is smooth there will generally be smooth in Chrome, but the reverse is not guaranteed.
1224
+
1225
+ - **Bound data-derived columns.** Pivot-style tables that derive columns from dates, categories, or other result values must cap the rendered window. Paginate the column range or virtualize it horizontally; never render an unbounded `dates.map(...)` or equivalent column set.
1226
+ - **Keep sticky positioning to headers.** Never apply `position: sticky` to body cells. If a pinned first column is essential, render it as a separate fixed pane synchronized with the scrollable data pane.
1227
+ - **Virtualize individual rows, not multi-row groups.** For large tables, avoid mounting virtualized rows inside one shared `<table>` layout where scroll-driven row changes can relayout the whole table. Prefer block or grid rows with fixed column widths, and make each virtual item one row rather than a group containing many rows and cells.
1228
+ - **Batch filter-driven queries.** When a control change would refetch multiple queries, debounce the committed filter value or require an Apply action. Do not fire the full query fan-out on every date, granularity, or filter input change.
1229
+
1221
1230
  ### Resizable panels
1222
1231
 
1223
1232
  Only when the user asks for adjustable panel sizing (or two sibling areas genuinely benefit from rebalancing), read `/app/references/resizable-panels.md` and use the pre-installed shadcn `Resizable`. Otherwise use fixed flex/grid proportions — don't reach for it by default.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/cli",
3
- "version": "2.53.0",
3
+ "version": "2.54.0",
4
4
  "description": "Lightdash CLI tool",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,8 +45,8 @@
45
45
  "unique-names-generator": "4.7.1",
46
46
  "uuid": "11.1.1",
47
47
  "yaml": "2.8.3",
48
- "@lightdash/common": "2.53.0",
49
- "@lightdash/warehouses": "2.53.0"
48
+ "@lightdash/warehouses": "2.54.0",
49
+ "@lightdash/common": "2.54.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/inquirer": "8.2.4",