@lightdash/cli 1.74.1 → 1.75.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.
|
@@ -24,6 +24,7 @@ Users describe features by what they see in the Lightdash editor. Translate:
|
|
|
24
24
|
| Google Sheets export | `gsheet-export` | app code opt-in |
|
|
25
25
|
| external API data | `external-fetch` | app code opt-in |
|
|
26
26
|
| runs inside a dashboard tile | `viz-context` | required — see below |
|
|
27
|
+
| light/dark mode, "matches my Lightdash theme" | `follow-host-theme` | CSS tokens — see below |
|
|
27
28
|
|
|
28
29
|
## Automatic (zero wiring — active on any current-SDK bundle)
|
|
29
30
|
|
|
@@ -62,6 +63,24 @@ Wrap the app in `VizContextProvider` (see the template `main.jsx`) and read
|
|
|
62
63
|
the host-supplied query context with `useVizContext`. Only relevant for
|
|
63
64
|
visualization-style apps meant to run inside dashboards.
|
|
64
65
|
|
|
66
|
+
### `follow-host-theme` — light/dark mode
|
|
67
|
+
|
|
68
|
+
The SDK puts the `dark` class on `<html>` as the app boots and again
|
|
69
|
+
whenever the viewer toggles their Lightdash theme, so an app that styles
|
|
70
|
+
everything through the theme tokens follows along with no code at all. What an
|
|
71
|
+
older app usually needs is the opposite of wiring — *removing* what pins it to
|
|
72
|
+
one mode:
|
|
73
|
+
|
|
74
|
+
- drop any `className="dark …"` on the app shell and any
|
|
75
|
+
`document.documentElement.classList.add('dark')`;
|
|
76
|
+
- move dark values out of `:root` and into `.dark`, leaving a complete set of
|
|
77
|
+
light values on `:root`;
|
|
78
|
+
- keep both sets complete for every token the app defines.
|
|
79
|
+
|
|
80
|
+
For colours CSS can't reach (a chart library's theme object, a logo swap), read
|
|
81
|
+
the mode: `const colorScheme = useColorScheme();` — `'light' | 'dark'`,
|
|
82
|
+
re-rendering on every host toggle.
|
|
83
|
+
|
|
65
84
|
### App-code opt-ins (call the API where it fits the app)
|
|
66
85
|
|
|
67
86
|
- `drill-down`: `drillDown(...)` derives a more detailed query from a clicked
|
|
@@ -25,6 +25,7 @@ Hard rules when a theme is active:
|
|
|
25
25
|
- **Theme CSS overrides `frontend-design`'s color/typography direction.** The aesthetic distinctness `frontend-design` pushes for still applies to layout, density, and motion — but colors, font families, and any other tokens the theme CSS defines win over your own picks. If the theme sets `--accent: #6B5B95`, your headings use that purple; don't reach for a "more distinctive" alternative.
|
|
26
26
|
- **If the theme CSS defines a chart palette (CSS custom properties like `--chart-1`, `--chart-2`, …, or any `*-chart-*` variables), use it instead of `CHART_COLORS` from `@/lib/theme`.** Read the values via `getComputedStyle(document.documentElement).getPropertyValue('--chart-1')` once on mount and cycle them by index for multi-series. Falling back to `CHART_COLORS` when the theme doesn't define a chart palette is correct.
|
|
27
27
|
- **Instruction text in the appended system prompt is binding.** Any rules described under "Organization theme instructions" later in this prompt override conflicting defaults in this file. Treat them as customer-supplied product requirements, not suggestions.
|
|
28
|
+
- **Theme CSS may deliberately pin one light/dark mode, and that wins.** The default rule is to follow the host's mode (complete `:root` light values, complete `.dark` values). If the organization's CSS defines only one set of values, or its instructions say the app must always render on the brand's dark (or light) surface, honour that — brand consistency beats mode-following. Don't "fix" a single-mode theme by inventing the other mode's colours.
|
|
28
29
|
- **Do not modify files under `/app/src/design/`.** `Write(//app/src/**)` would technically allow it, but those files are the source of truth for the brand and may be reused across many apps. Treat the directory as read-only.
|
|
29
30
|
|
|
30
31
|
When `/app/src/design/` does not exist or is empty, behave as if these rules don't apply — no theme is active and `frontend-design`'s direction is the whole story.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
You are building a React data app that queries the Lightdash semantic layer. This file is your reference for the environment, SDK, and data model.
|
|
4
4
|
|
|
5
|
-
**Building a single reusable chart rather than an app?** Use the `reusable-visualization` skill before writing any code. A reusable visualization runs no query of its own — the host hands it rows, a field mapping, config option values and a colour palette — so the data and filter APIs below do not apply to it. That skill is the contract for those builds and overrides this guide wherever the two differ. Everything else here (environment, components, visual design) still applies.
|
|
5
|
+
**Building a single reusable chart rather than an app?** Use the `reusable-visualization` skill before writing any code. A reusable visualization runs no query of its own — the host hands it rows, a field mapping, config option values and a colour palette — so the data and filter APIs below do not apply to it. That skill is the contract for those builds and overrides this guide wherever the two differ. Everything else here (environment, components, visual design) still applies. It says nothing about light/dark mode, and silence is not an override: **following the host's colour scheme is a platform contract that applies to a reusable visualization exactly as it does to an app** — see "Visual Design".
|
|
6
6
|
|
|
7
7
|
## Iteration mindset
|
|
8
8
|
|
|
@@ -667,11 +667,14 @@ For any external HTTP API call, read `/app/references/external-apis.md` and use
|
|
|
667
667
|
|
|
668
668
|
**Use the `frontend-design` skill before writing any UI code.** It drives the aesthetic direction — pick a distinctive look for *this* app rather than defaulting to generic shadcn-on-dark-mode. This guide does not prescribe layout, typography, color, or composition; that's `frontend-design`'s job.
|
|
669
669
|
|
|
670
|
+
Express that direction **through the theme tokens, in both modes**. "Distinctive" is never a licence to pin one colour scheme: the viewer's Lightdash decides light or dark, and an app that ignores it reads as broken for half its audience. If a look only works on a dark background, it is not a look you can ship — rebuild it so the same design resolves correctly in both.
|
|
671
|
+
|
|
670
672
|
Lightdash-specific constraints that apply on top of `frontend-design`'s direction:
|
|
671
673
|
|
|
672
674
|
- **Chart series colors must come from `CHART_COLORS` in `@/lib/theme`** — the canonical Lightdash palette, so generated apps' charts visually match native Lightdash dashboards. Cycle by index for multi-series (`CHART_COLORS[i % CHART_COLORS.length]`). `frontend-design`'s chosen accent/background/typography colors are independent of this.
|
|
673
675
|
- **Use semantic shadcn tokens for UI chrome** — `bg-background`, `bg-card`, `text-foreground`, `text-muted-foreground`, `text-destructive`, `border`, etc. Don't hardcode hex values for surfaces, text, or borders. (`frontend-design` may direct you to redefine the underlying CSS variables for a chosen theme — that's fine; the rule is no inline hex, not "use only the default token values".)
|
|
674
|
-
- **
|
|
676
|
+
- **Follow the host's light/dark mode; never pin one.** Lightdash tells the app which mode the viewer is in — as the app boots, and again whenever they toggle — by putting the `dark` class on `<html>`. Your job is to make both modes correct: define **complete** light values on `:root` and **complete** dark values on `.dark` (the template already does for every default token; keep both in step for any token you add or redefine), then style everything through those tokens. **Never** hard-code `className="dark …"` on the app shell, add `dark` to `<html>` yourself, or put dark values in `:root` — each of those pins the app to one mode and it will read as broken for half the viewers. **A literal colour in an inline `style={{ background: '#0f0d17', color: '#f4f1ff' }}` pins it just as hard** — inline styles are the most common way this rule gets broken, because they bypass Tailwind and the tokens without looking like a theme decision. Write `var(--background)` / `var(--foreground)` there instead, or use the utility classes. This applies to a reusable visualization too: it has no page shell to carry `bg-background text-foreground`, so put the tokens on the chart's own root element. Nor should you branch surfaces, text or borders on the mode in JS (`scheme === 'dark' ? '#12101a' : '#faf9fc'`) — that is a token's job, and hand-rolling it is how apps end up half-following the host. Before declaring done, check in both modes: text stays legible, surfaces keep their contrast, and no colour is invisible against the other mode's background.
|
|
677
|
+
- **Redefine tokens in `src/index.css`, not a new stylesheet.** `:root` and `.dark` have equal specificity, so the one emitted *last* wins. `index.css` is imported before `./App` in `main.jsx`, which means a `styles/theme.css` you import from a component lands after it and overrides it — but only for the selectors you actually restate. Split your light values into that file and leave `.dark` in `index.css` (or vice versa) and the two sets end up in the wrong order, pinning the app to one mode with no error anywhere. Edit the `:root` and `.dark` blocks that are already in `index.css` and keep both complete. If you genuinely need a separate file, restate **both** blocks there and never reorder the imports in `main.jsx`.
|
|
675
678
|
- **Theme CSS variables hold complete raw colors, consumed as `var(--x)`.** The template's tokens are `oklch(…)` values, and everything — Tailwind utilities, the floating-surface chrome, your own CSS — reads them with plain `var(--x)`. When overriding on `:root`, write full CSS colors (`--background: oklch(0.14 0.005 286);` — any valid color works). **Never write bare `H S% L%` triplets and never wrap a variable in `hsl(var(--x))`** — both come from a different shadcn convention this template does not use; they produce invalid CSS that browsers silently drop, which breaks text colors or turns popovers/tooltips transparent.
|
|
676
679
|
- **The page background must reach the bottom of the iframe.** Viewers render the app in an iframe as tall as their browser window, and app content is routinely shorter than that. So structure every page as a **full-height themed shell** (theme class, background, `text-foreground`, `min-h-screen`) wrapping a **content-sized inner wrapper** that owns the padding — see the example in the next section. Putting the background on a content-sized element instead leaves everything below it painting the template default: a hard edge across the page where your theme stops, and the single most common way a generated app reads as broken. Never paint the page background on `#root` or `body` instead; they sit outside your theme.
|
|
677
680
|
- **Leave a gutter at the bottom of the content.** Don't let the last card, chart, or footer sit flush against the bottom edge — it reads as clipped. Put the bottom padding (`pb-8` or similar) on the inner wrapper, so the gutter sits inside both the theme and the screenshot bounds.
|
|
@@ -683,7 +686,7 @@ Scheduled deliveries (Slack/email) render the app inside a tall **1400×4000** i
|
|
|
683
686
|
**Mark the content extent with `data-screenshot-bounds`.** Put the attribute on the inner content wrapper from the rule above — **not** on the full-height shell. The delivery pipeline uses that element's bottom edge as the image height and crops everything below it, which is exactly why the shell may fill the iframe for free: the shell's `min-h-screen` never reaches the delivered image, and the crop still lands just under your last row. Without the attribute the pipeline falls back to a best-effort measurement that the patterns below easily inflate, so set it.
|
|
684
687
|
|
|
685
688
|
```tsx
|
|
686
|
-
<div className="
|
|
689
|
+
<div className="bg-background text-foreground min-h-screen"> {/* fills the viewport, not measured */}
|
|
687
690
|
<div data-screenshot-bounds className="p-8 pb-12"> {/* measured: crop lands here */}
|
|
688
691
|
<Header />
|
|
689
692
|
<ChartGrid />
|
|
@@ -692,7 +695,7 @@ Scheduled deliveries (Slack/email) render the app inside a tall **1400×4000** i
|
|
|
692
695
|
</div>
|
|
693
696
|
```
|
|
694
697
|
|
|
695
|
-
`text-foreground` on the shell is not optional:
|
|
698
|
+
`bg-background text-foreground` on the shell is not optional: switching modes re-points the theme variables but not the `color` already inherited from `<body>`, so without them every bare `<h1>`/`<p>`/`<div>` keeps the other mode's ink.
|
|
696
699
|
|
|
697
700
|
Then keep the content from inflating the canvas:
|
|
698
701
|
|
|
@@ -882,14 +885,33 @@ Action menus and dialogs use shadcn's components as-is — no className needed f
|
|
|
882
885
|
</DropdownMenuContent>
|
|
883
886
|
```
|
|
884
887
|
|
|
885
|
-
**One scope rule still matters:**
|
|
888
|
+
**One scope rule still matters:** the `dark` class belongs on `<html>`, and Lightdash is what puts it there. Radix portals into `document.body`, so `<div className="dark">` inside `<App />` doesn't contain portaled menus/dialogs/popovers — floating surfaces would leak out to the other mode. Do not write `<div className="dark">` anywhere, and do not set or remove the class yourself.
|
|
889
|
+
|
|
890
|
+
For the rare colour that can't be expressed in CSS — a charting library's theme object, a light/dark logo swap — read the current mode instead:
|
|
891
|
+
|
|
892
|
+
```jsx
|
|
893
|
+
import { useColorScheme } from '@lightdash/query-sdk';
|
|
894
|
+
|
|
895
|
+
const colorScheme = useColorScheme(); // 'light' | 'dark', re-renders on host toggle
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
`useColorScheme()` is the **only** supported way to read the mode in JS. Never
|
|
899
|
+
derive it from the DOM yourself:
|
|
886
900
|
|
|
887
|
-
```
|
|
888
|
-
//
|
|
889
|
-
document.documentElement.classList.
|
|
901
|
+
```jsx
|
|
902
|
+
// ✗ Broken. Correct on first paint, permanently stale afterwards.
|
|
903
|
+
const colorScheme = document.documentElement.classList.contains('dark')
|
|
904
|
+
? 'dark'
|
|
905
|
+
: 'light';
|
|
890
906
|
```
|
|
891
907
|
|
|
892
|
-
|
|
908
|
+
That reads the right value while the app is mounting, so the app looks correct
|
|
909
|
+
and the bug survives review — but it subscribes to nothing. When the viewer
|
|
910
|
+
toggles, Lightdash updates `<html>` and React never re-renders, so every colour
|
|
911
|
+
you derived stays on the old mode. `useColorScheme()` subscribes to the same
|
|
912
|
+
change and re-renders. The same applies to `matchMedia('(prefers-color-scheme)')`:
|
|
913
|
+
that reports the viewer's *operating system*, not the Lightdash mode, so it is
|
|
914
|
+
wrong from the first frame.
|
|
893
915
|
|
|
894
916
|
### Data interactions — action menu
|
|
895
917
|
|
|
@@ -10,9 +10,14 @@ import {
|
|
|
10
10
|
} from '@lightdash/query-sdk';
|
|
11
11
|
import { FilterProvider } from '@/lib/filters';
|
|
12
12
|
import { ErrorBoundary } from '@/lib/ErrorBoundary';
|
|
13
|
-
|
|
13
|
+
// Base tokens must be imported before ./App: any stylesheet the app imports
|
|
14
|
+
// from a component lands after these in the bundle, and `:root` and `.dark`
|
|
15
|
+
// have equal specificity — so whichever comes last wins. Import App first and
|
|
16
|
+
// the template's light `:root` overrides the app's own `.dark`, pinning it to
|
|
17
|
+
// light no matter what the host sends.
|
|
14
18
|
import './index.css';
|
|
15
19
|
import './chart-overrides.css';
|
|
20
|
+
import App from './App';
|
|
16
21
|
import initScreenshotHandler from './screenshotHandler';
|
|
17
22
|
|
|
18
23
|
const queryClient = new QueryClient({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.75.0",
|
|
4
4
|
"description": "Lightdash CLI tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"unique-names-generator": "4.7.1",
|
|
42
42
|
"uuid": "11.1.1",
|
|
43
43
|
"yaml": "2.8.3",
|
|
44
|
-
"@lightdash/
|
|
45
|
-
"@lightdash/
|
|
44
|
+
"@lightdash/warehouses": "1.75.0",
|
|
45
|
+
"@lightdash/common": "1.75.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/inquirer": "8.2.4",
|