@graphit/cli 0.1.16 → 0.1.19
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.
- package/dist/commands/dashboard.js +79 -3
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/kb.js +32 -6
- package/dist/commands/kb.js.map +1 -1
- package/dist/commands/query.js +67 -18
- package/dist/commands/query.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/output/format.d.ts +1 -1
- package/dist/output/format.js +2 -0
- package/dist/output/format.js.map +1 -1
- package/dist/output/styled.d.ts +19 -0
- package/dist/output/styled.js +70 -0
- package/dist/output/styled.js.map +1 -0
- package/package.json +3 -1
- package/skills/graphit/SKILL.md +51 -49
- package/skills/graphit/cursor/graphit-dashboard-planning.mdc +5 -0
- package/skills/graphit/cursor/graphit-kb-traversal.mdc +7 -0
- package/skills/graphit/cursor/graphit-presentations.mdc +56 -0
- package/skills/graphit/cursor/graphit-sql-reference.mdc +35 -0
- package/skills/graphit/cursor/graphit-style.mdc +16 -0
- package/skills/graphit/graphit.mdc +64 -11
- package/skills/graphit/references/dashboard-planning.md +24 -0
- package/skills/graphit/references/governance.md +32 -4
- package/skills/graphit/references/graphit-style.md +27 -0
- package/skills/graphit/references/kb-traversal.md +65 -0
- package/skills/graphit/references/presentations.md +143 -0
- package/skills/graphit/references/sql-reference.md +108 -0
package/skills/graphit/SKILL.md
CHANGED
|
@@ -43,7 +43,7 @@ Your HTML must NEVER contain:
|
|
|
43
43
|
- `<img src="https://...">` - no external images
|
|
44
44
|
|
|
45
45
|
If you use ANY `src=` or `href=` pointing to a URL, the dashboard will be blank.
|
|
46
|
-
All CSS in `<style>`, all JS in `<script>`, all fonts from system stack. The iframe has a built-in runtime (`graphit.chart`, `graphit.table`, `graphit.kpi`) for standard visualizations - use it instead of importing libraries.
|
|
46
|
+
All CSS in `<style>`, all JS in `<script>`, all fonts from system stack. The iframe has a built-in runtime (`graphit.chart`, `graphit.table`, `graphit.kpi`, `graphit.presentation`) for standard visualizations - use it instead of importing libraries.
|
|
47
47
|
|
|
48
48
|
### 2. ALWAYS query through data sources
|
|
49
49
|
NEVER query the warehouse directly when a cached data source covers the table. Data sources return in ~100ms. Warehouse queries take ~10s and cost Snowflake credits.
|
|
@@ -54,7 +54,9 @@ NEVER query the warehouse directly when a cached data source covers the table. D
|
|
|
54
54
|
3. Only use `graphit query "SQL" --warehouse --connection <id>` if NO data source exists and the user approves
|
|
55
55
|
|
|
56
56
|
**Wrong:** Jumping straight to `graphit query "SELECT SUM(cost) FROM marketing_ua" --warehouse` when a data source already caches that table.
|
|
57
|
-
**Right:** `graphit ds list` first, find the DS ID, then `graphit query "SELECT SUM(cost) FROM
|
|
57
|
+
**Right:** `graphit ds list` first, find the DS ID, then `graphit query "SELECT SUM(cost) FROM marketing_ua_ds" --ds ds_abc123`.
|
|
58
|
+
|
|
59
|
+
**Use the data source name in SQL** (e.g. `FROM MARKETING_UA_DS`), not the raw source table. The DS is a KB table - governance rules target it directly, and users recognize DS names from the platform.
|
|
58
60
|
|
|
59
61
|
### 3. EVERY element must have entity wrapping
|
|
60
62
|
Without `data-graphit-*` attributes, elements are invisible to the platform - no click info, no mentions, no KB provenance. Every chart, KPI card, table, and text section needs ALL FIVE attributes:
|
|
@@ -63,7 +65,7 @@ Without `data-graphit-*` attributes, elements are invisible to the platform - no
|
|
|
63
65
|
<div data-graphit-id="revenue-trend"
|
|
64
66
|
data-graphit-label="Revenue Trend"
|
|
65
67
|
data-graphit-kb="metric:REVENUE,dimension:REGION,table:ORDERS"
|
|
66
|
-
data-graphit-sql="SELECT region, SUM(revenue) FROM
|
|
68
|
+
data-graphit-sql="SELECT region, SUM(revenue) FROM orders GROUP BY region"
|
|
67
69
|
data-graphit-ds="ds_abc123">
|
|
68
70
|
<!-- chart/KPI/table content here -->
|
|
69
71
|
</div>
|
|
@@ -73,7 +75,7 @@ Without `data-graphit-*` attributes, elements are invisible to the platform - no
|
|
|
73
75
|
|-----------|--------|---------|
|
|
74
76
|
| `data-graphit-id` | Unique kebab-case | `"spend-by-source"` |
|
|
75
77
|
| `data-graphit-label` | Human-readable name | `"Ad Spend by Source"` |
|
|
76
|
-
| `data-graphit-kb` | `type:NAME` comma-separated | `"metric:CPI,dimension:MEDIA_SOURCE,table:
|
|
78
|
+
| `data-graphit-kb` | `type:NAME` comma-separated | `"metric:CPI,dimension:MEDIA_SOURCE,table:MARKETING_UA_DS"` |
|
|
77
79
|
| `data-graphit-sql` | Executable SQL (HTML-encode `<>&"`) | `"SELECT ..."` |
|
|
78
80
|
| `data-graphit-ds` | Data source ID from `graphit ds list` | `"ds_abc123"` |
|
|
79
81
|
|
|
@@ -222,42 +224,9 @@ For metrics with natural variant axes (D7/D30/D90, gross/net), propose parameter
|
|
|
222
224
|
|
|
223
225
|
## Presenting Results to the User
|
|
224
226
|
|
|
225
|
-
The user CANNOT see raw CLI output
|
|
226
|
-
|
|
227
|
-
**After every query**, show the data:
|
|
228
|
-
```
|
|
229
|
-
Queried **MARKETING_UA** (ds_abc123, 6 rows):
|
|
227
|
+
The user CANNOT see raw CLI JSON output. You are the rendering layer - format and present every result using markdown. Per-command templates are in the reference file for each domain: `sql-reference.md` (query + DS results), `kb-traversal.md` (KB list/get/search/explore), `governance.md` (governance status + errors), `dashboard-planning.md` (dashboard + connector lists).
|
|
230
228
|
|
|
231
|
-
|
|
232
|
-
|-------------|----------|----------|-------|
|
|
233
|
-
| Facebook | $42,100 | 12,400 | $3.40 |
|
|
234
|
-
| Google UAC | $38,500 | 9,800 | $3.93 |
|
|
235
|
-
| TikTok | $21,300 | 8,200 | $2.60 |
|
|
236
|
-
```
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
**After KB exploration**, summarize what you found:
|
|
240
|
-
```
|
|
241
|
-
Found **12 metrics** and **8 dimensions** on table MARKETING_UA:
|
|
242
|
-
- **Metrics:** TOTAL_SPEND, CPI, ROAS_D7, ROAS_D30, INSTALLS, ...
|
|
243
|
-
- **Dimensions:** MEDIA_SOURCE, CAMPAIGN_NAME, COUNTRY, PLATFORM, ...
|
|
244
|
-
- **Rules:** EXCLUDE_ORGANIC (filters organic installs from paid metrics)
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
**Formatting rules:**
|
|
248
|
-
- **Bold** metric names, table names, and key numbers
|
|
249
|
-
- Use **markdown tables** for any tabular data (query results, entity lists, comparisons)
|
|
250
|
-
- Format numbers: commas for thousands (`12,400`), `$` for currency, `%` for rates
|
|
251
|
-
- After KB or data source discovery, list what's available before asking what to build
|
|
252
|
-
- Show the SQL you ran (in a code block) so the user can validate the logic
|
|
253
|
-
- When a query returns nulls or zero rows, explain what you checked and what went wrong
|
|
254
|
-
- Narrate your progress between steps: "Found 3 data sources. Using **Marketing UA DS** (ds_abc123) which covers spend, installs, and ROAS columns."
|
|
255
|
-
|
|
256
|
-
**Never do these:**
|
|
257
|
-
- Run 3 queries silently then jump to building HTML
|
|
258
|
-
- Say "I found the data" without showing what the data looks like
|
|
259
|
-
- Present raw JSON output without formatting
|
|
260
|
-
- Skip showing KB exploration results before proposing a dashboard
|
|
229
|
+
**Core rules:** Ground every result in the KB - list referenced metrics/dimensions/tables, show both the `{{metric:X}}` query and resolved SQL, always use `--verbose`. Bold every KB asset/table/DS name. Markdown tables for tabular data. SQL in ```sql code blocks. Format numbers (commas, $, %). Governance provenance footer after every query. For ad-hoc queries, suggest the KB reference equivalent. Never silently consume output.
|
|
261
230
|
|
|
262
231
|
---
|
|
263
232
|
|
|
@@ -267,16 +236,22 @@ The platform enforces server-side query governance. Use KB reference syntax for
|
|
|
267
236
|
|
|
268
237
|
| Syntax | Expands To | Example |
|
|
269
238
|
|--------|-----------|---------|
|
|
270
|
-
| `{{metric:NAME}}` | Metric calculation (aggregation) | `{{metric:
|
|
239
|
+
| `{{metric:NAME}}` | Metric calculation (aggregation) | `{{metric:CPI}}` |
|
|
240
|
+
| `{{metric:NAME(K=V)}}` | Parameterized metric | `{{metric:ARPU(DAY=7)}}` |
|
|
271
241
|
| `{{metric_raw:NAME}}` | Raw expression (no aggregate) | `{{metric_raw:REVENUE}}` |
|
|
272
242
|
| `{{dim:NAME}}` | Dimension expression | `{{dim:INSTALL_MONTH}}` |
|
|
273
243
|
|
|
244
|
+
**Parameterized metrics:** Some metrics (ARPU, ROAS, RETENTION) contain `${PARAM:X}` tokens and require explicit parameter values. Run `graphit kb list metric` - the `params` column shows required parameter names. Use `{{metric:ARPU(DAY=7)}}` to resolve `${PARAM:DAY}` to `7`. Omitting required parameters returns a clear error with the exact syntax to use. Pre-baked variants (ARPU_D7, ROAS_D30) have the value hardcoded and need no parameters.
|
|
245
|
+
|
|
274
246
|
```bash
|
|
275
247
|
# Governed query using reference syntax
|
|
276
|
-
graphit query "SELECT {{dim:INSTALL_MONTH}}, {{metric:
|
|
248
|
+
graphit query "SELECT {{dim:INSTALL_MONTH}}, {{metric:CPI}} as cpi FROM MARKETING_UA_DS GROUP BY 1" --ds ds_abc123
|
|
249
|
+
|
|
250
|
+
# Parameterized metric
|
|
251
|
+
graphit query "SELECT {{dim:INSTALL_MONTH}}, {{metric:ARPU(DAY=7)}} as arpu FROM MARKETING_UA_DS GROUP BY 1" --ds ds_abc123
|
|
277
252
|
|
|
278
253
|
# Verbose shows expanded SQL and trust tier
|
|
279
|
-
graphit query "SELECT {{
|
|
254
|
+
graphit query "SELECT {{metric:CPI}} as cpi FROM MARKETING_UA_DS" --ds ds_abc123 --verbose
|
|
280
255
|
```
|
|
281
256
|
|
|
282
257
|
Queries using reference syntax produce **governed** trust tier. Inline formulas produce **ad-hoc** tier, which may be blocked in strict mode. Use `--override-rules RULE_NAME` to bypass specific enforceable rules when the user explicitly requests it.
|
|
@@ -287,7 +262,7 @@ Use reference syntax in `graphit.resolve()` calls too - the server expands them
|
|
|
287
262
|
|
|
288
263
|
```js
|
|
289
264
|
graphit.resolve({
|
|
290
|
-
sql: "SELECT {{dim:INSTALL_MONTH}}, {{metric:ARPU}} FROM
|
|
265
|
+
sql: "SELECT {{dim:INSTALL_MONTH}}, {{metric:ARPU(DAY=7)}} as arpu FROM MARKETING_UA_DS GROUP BY 1",
|
|
291
266
|
dataSourceId: "ds_abc123",
|
|
292
267
|
target: "#arpu-chart"
|
|
293
268
|
});
|
|
@@ -311,7 +286,30 @@ const result = await graphit.resolve({
|
|
|
311
286
|
|
|
312
287
|
The `dataSourceId` is the ID from `graphit ds list`. The `target` parameter (CSS selector or element) shows a blur + spinner overlay while loading and removes it on completion. `result.data` is an array of row objects you can render however you want.
|
|
313
288
|
|
|
314
|
-
**Error handling:** `graphit.resolve()` rejects on timeout (
|
|
289
|
+
**Error handling:** `graphit.resolve()` rejects on timeout (120s), bad SQL, or invalid data source ID. Wrap calls in try/catch and show a user-visible error message in the target element on failure. Verify SQL returns data via the CLI before embedding it in HTML.
|
|
290
|
+
|
|
291
|
+
### First-paint loading state
|
|
292
|
+
|
|
293
|
+
The dashboard HTML paints before the SDK connects (iframe load + handshake), so the SDK's own spinner cannot cover the first moments. Bake this pure-CSS overlay into the HTML so every chart shows a spinner from the first frame; the SDK adopts it and removes it when that element's `graphit.resolve()` settles (success or error).
|
|
294
|
+
|
|
295
|
+
Add once to the page `<style>`:
|
|
296
|
+
|
|
297
|
+
```css
|
|
298
|
+
@keyframes gh-spin{to{transform:rotate(360deg)}}
|
|
299
|
+
.gh-loading{position:relative;min-height:120px}
|
|
300
|
+
.gh-loading-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;z-index:9998;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);background:color-mix(in srgb,var(--graphit-surface-raised,#fff) 50%,transparent);border-radius:inherit}
|
|
301
|
+
.gh-loading-spin{animation:gh-spin .7s linear infinite}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Add inside EVERY element you pass as `target:` to `graphit.resolve()` - and ONLY those elements (a static text or title section with no resolve call would spin forever):
|
|
305
|
+
|
|
306
|
+
```html
|
|
307
|
+
<div id="spend-chart" class="gh-loading">
|
|
308
|
+
<div class="gh-loading-overlay"><svg class="gh-loading-spin" width="24" height="24" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" stroke="var(--graphit-border,#e5e5e5)" stroke-width="2.5"/><path d="M12 2a10 10 0 0 1 10 10" stroke="var(--graphit-accent,#4DB6AC)" stroke-width="2.5" stroke-linecap="round"/></svg></div>
|
|
309
|
+
</div>
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The class names are a contract with the SDK (`gh-loading`, `gh-loading-overlay`, `gh-loading-spin`, `gh-spin`) - keep them exactly as shown or the SDK cannot adopt and remove the overlay. NEVER write text placeholders ("Loading...", "Fetching data...") - they never animate and make slow loads look stuck.
|
|
315
313
|
|
|
316
314
|
### Rendering
|
|
317
315
|
|
|
@@ -322,8 +320,9 @@ The iframe also provides optional convenience helpers if you want quick standard
|
|
|
322
320
|
| Helper | Usage |
|
|
323
321
|
|---|---|
|
|
324
322
|
| `graphit.chart(el, {type, data, x, y, ...})` | Bar, line, area, donut, scatter, stacked-bar, heatmap, funnel, gauge, sparkline |
|
|
325
|
-
| `graphit.table(el, {data, columns?,
|
|
323
|
+
| `graphit.table(el, {data, columns?, maxRows?})` | Styled HTML table. `columns` is a `string[]` of data keys (NOT objects), defaults to `Object.keys(data[0])` |
|
|
326
324
|
| `graphit.kpi(el, {value, label?, format?})` | KPI card with optional delta |
|
|
325
|
+
| `graphit.presentation(el)` | Full-screen slide deck. Returns builder: `.slide({bg, layout, html})` then `.start()`. See `presentations.md` |
|
|
327
326
|
|
|
328
327
|
These are shortcuts, not requirements. Use them when a standard chart is all you need. Hand-roll when you want full control over the visualization.
|
|
329
328
|
|
|
@@ -336,15 +335,17 @@ These are shortcuts, not requirements. Use them when a standard chart is all you
|
|
|
336
335
|
```html
|
|
337
336
|
<div data-graphit-id="spend-by-source"
|
|
338
337
|
data-graphit-label="Ad Spend by Source"
|
|
339
|
-
data-graphit-kb="metric:CPI,dimension:MEDIA_SOURCE,table:
|
|
340
|
-
data-graphit-sql="SELECT MEDIA_SOURCE, SUM(APPSFLYER_COST) as spend FROM
|
|
338
|
+
data-graphit-kb="metric:CPI,dimension:MEDIA_SOURCE,table:MARKETING_UA_DS"
|
|
339
|
+
data-graphit-sql="SELECT MEDIA_SOURCE, SUM(APPSFLYER_COST) as spend FROM MARKETING_UA_DS GROUP BY MEDIA_SOURCE ORDER BY spend DESC"
|
|
341
340
|
data-graphit-ds="ds_abc123">
|
|
342
|
-
<div id="spend-chart"
|
|
341
|
+
<div id="spend-chart" class="gh-loading">
|
|
342
|
+
<div class="gh-loading-overlay"><svg class="gh-loading-spin" width="24" height="24" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" stroke="var(--graphit-border,#e5e5e5)" stroke-width="2.5"/><path d="M12 2a10 10 0 0 1 10 10" stroke="var(--graphit-accent,#4DB6AC)" stroke-width="2.5" stroke-linecap="round"/></svg></div>
|
|
343
|
+
</div>
|
|
343
344
|
</div>
|
|
344
345
|
<script>
|
|
345
346
|
(async function() {
|
|
346
347
|
var r = await graphit.resolve({
|
|
347
|
-
sql: "SELECT MEDIA_SOURCE, SUM(APPSFLYER_COST) as spend FROM
|
|
348
|
+
sql: "SELECT MEDIA_SOURCE, SUM(APPSFLYER_COST) as spend FROM MARKETING_UA_DS GROUP BY MEDIA_SOURCE ORDER BY spend DESC",
|
|
348
349
|
dataSourceId: "ds_abc123",
|
|
349
350
|
target: "#spend-chart"
|
|
350
351
|
});
|
|
@@ -378,3 +379,4 @@ Detailed knowledge lives in `references/`. Consult the relevant file when you ne
|
|
|
378
379
|
| `graphit-style.md` | Building the HTML. Design principles, typography scale, color system with usage rules, layout patterns (page structure, KPI cards, data tables). |
|
|
379
380
|
| `chart-patterns.md` | Custom chart implementations. Inline SVG/CSS code for: scatter/bubble, heatmap, funnel, gauge, sparkline, stacked bar, and the shared tooltip pattern. |
|
|
380
381
|
| `governance.md` | Query governance. Reference syntax, trust tiers, enforceable rules, override flow, governance commands. |
|
|
382
|
+
| `presentations.md` | Slide deck presentations. Builder API, layouts (center/split/full), backgrounds, navigation, live data inside slides. |
|
|
@@ -98,3 +98,8 @@ Before generating the HTML:
|
|
|
98
98
|
- [ ] 3+ chart types for 4+ graphs
|
|
99
99
|
- [ ] Every KPI has definition + baseline + direction
|
|
100
100
|
- [ ] No anti-patterns present
|
|
101
|
+
|
|
102
|
+
## Presenting Dashboard & Connector Results
|
|
103
|
+
|
|
104
|
+
**dashboard list**: table with **bold Name**, ID, URL.
|
|
105
|
+
**connector list**: table with **bold Name**, Account, Auth.
|
|
@@ -62,3 +62,10 @@ Use `kb_explore` with max_depth=2 and no edge_type filter. Returns the table's d
|
|
|
62
62
|
| 3 (max) | Full neighborhood. Use sparingly - can return many results for connected tables. |
|
|
63
63
|
|
|
64
64
|
Start at depth 1. Only increase if the user's question requires it or the initial results are insufficient.
|
|
65
|
+
|
|
66
|
+
## Presenting KB Results
|
|
67
|
+
|
|
68
|
+
**kb list**: `**12 metrics** across 3 tables:` then table with **bold name**, table, calculation. Adapt columns per type.
|
|
69
|
+
**kb get**: `### **CPI** (metric, verified)` + *description* + key-value table (Table, Calculation, Parameters, Topics, Dims).
|
|
70
|
+
**kb search**: `**5 results** for "query":` then table with Type, **bold Name**, Description.
|
|
71
|
+
**kb explore**: tree with bold names indented by level: Asset > Tables > Dimensions > Rules > Domain.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: false
|
|
3
|
+
description: "Graphit: Build full-screen slide deck presentations inside canvas dashboards using graphit.presentation(). Builder pattern with layouts, backgrounds, and keyboard navigation."
|
|
4
|
+
globs: []
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Presentations
|
|
8
|
+
|
|
9
|
+
Build slide deck presentations inside Graphit canvas dashboards using `graphit.presentation()`.
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
var deck = graphit.presentation('#my-deck');
|
|
15
|
+
deck.slide({ bg: 'dark', layout: 'center', html: '<h1>Title</h1>' });
|
|
16
|
+
deck.slide({ bg: 'white', layout: 'split', html: '<div>Left</div><div>Right</div>' });
|
|
17
|
+
deck.slide({ bg: 'paper', layout: 'full', html: '<div id="charts">...</div>' });
|
|
18
|
+
var ctrl = deck.start();
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Slide Config
|
|
22
|
+
|
|
23
|
+
| Field | Type | Default | Description |
|
|
24
|
+
|-------|------|---------|-------------|
|
|
25
|
+
| `layout` | `'center'` / `'split'` / `'full'` | `'center'` | Content arrangement |
|
|
26
|
+
| `bg` | `'paper'` / `'white'` / `'dark'` / `'teal'` / hex | `'paper'` | Fixed background |
|
|
27
|
+
| `html` | `string` | `''` | Raw HTML content |
|
|
28
|
+
|
|
29
|
+
## Layouts
|
|
30
|
+
|
|
31
|
+
- **center**: Centered horizontally/vertically. Title slides, CTAs.
|
|
32
|
+
- **split**: First two child elements become left/right panels. Text + image.
|
|
33
|
+
- **full**: Top-to-bottom flow. Tables, grids, live data.
|
|
34
|
+
|
|
35
|
+
## Backgrounds (fixed, not theme-aware)
|
|
36
|
+
|
|
37
|
+
| Name | Background | Text |
|
|
38
|
+
|------|-----------|------|
|
|
39
|
+
| `paper` | `#F7F6F2` | `#222224` |
|
|
40
|
+
| `white` | `#FFFFFF` | `#222224` |
|
|
41
|
+
| `dark` | `#222224` | `#F7F6F2` |
|
|
42
|
+
| `teal` | `#4DB6AC` | `#FFFFFF` |
|
|
43
|
+
|
|
44
|
+
## Navigation
|
|
45
|
+
|
|
46
|
+
Arrow keys, spacebar, Home/End. Prev/Next buttons bottom-right with slide counter.
|
|
47
|
+
|
|
48
|
+
## Live data works inside slides
|
|
49
|
+
|
|
50
|
+
`graphit.resolve()` + `graphit.chart/table/kpi` work inside slides. Entity wrapping (`data-graphit-*`) works normally.
|
|
51
|
+
|
|
52
|
+
## Anti-Patterns
|
|
53
|
+
|
|
54
|
+
- Don't use external fonts/images (CSP blocks). Embed as base64 or use system fonts.
|
|
55
|
+
- Don't put scrollable content on slides. Split into multiple slides.
|
|
56
|
+
- One `graphit.presentation()` per dashboard.
|
|
@@ -117,6 +117,26 @@ SELECT UNNEST(generate_series(
|
|
|
117
117
|
|
|
118
118
|
The outer SELECT can ONLY reference columns the subquery exposes (its aliases). Base-table columns aggregated away in the subquery do NOT exist at the outer level.
|
|
119
119
|
|
|
120
|
+
## Cache-Friendly SQL (Canvas Resolve)
|
|
121
|
+
|
|
122
|
+
Canvas `graphit.resolve()` queries that follow these shapes serve from a semantic cache in ~10ms on filter changes instead of a full DuckDB recompute (5-37s on wide data sources). Write resolve SQL in this style by default.
|
|
123
|
+
|
|
124
|
+
**Shape rules:**
|
|
125
|
+
- Single table (no JOIN/UNION)
|
|
126
|
+
- WHERE as flat AND of `column = literal`, `column IN (...)`, `column BETWEEN ... AND ...` conjuncts
|
|
127
|
+
- Bare aggregates only: `SUM(col)`, `COUNT(*)`, `MIN(col)`, `MAX(col)` - no wrapping functions (`ROUND(SUM(x))`), no aggregate arithmetic (`SUM(a)/NULLIF(SUM(b),0)`), no `AVG` (v2)
|
|
128
|
+
- Literal dates (`>= '2026-01-01'`), never `CURRENT_DATE` expressions
|
|
129
|
+
- GROUP BY column names or ordinals; ORDER BY / LIMIT allowed (outer only)
|
|
130
|
+
- CTEs are fine when the CTE body follows the same rules
|
|
131
|
+
- Top-N rank queries: project the sort metric in SELECT (`SELECT dim, SUM(metric) AS rv ... ORDER BY rv DESC LIMIT N`), not only in ORDER BY
|
|
132
|
+
|
|
133
|
+
**Shapes that skip the cache** (fall back to normal execution, still correct):
|
|
134
|
+
- `COUNT(DISTINCT x)`, window functions, HAVING, QUALIFY
|
|
135
|
+
- OR / NOT in WHERE
|
|
136
|
+
- Ratio metrics (`SUM(a)/NULLIF(SUM(b),0)`) - compute client-side or use two resolves
|
|
137
|
+
- `CURRENT_DATE`-relative predicates
|
|
138
|
+
- Top-N with aggregate only in ORDER BY (`SELECT dim FROM t GROUP BY dim ORDER BY SUM(metric) DESC LIMIT N` - no decomposable aggregate in SELECT)
|
|
139
|
+
|
|
120
140
|
## Data Source Routing
|
|
121
141
|
|
|
122
142
|
| Situation | Command | Speed |
|
|
@@ -125,3 +145,18 @@ The outer SELECT can ONLY reference columns the subquery exposes (its aliases).
|
|
|
125
145
|
| No data source | `graphit query "SQL" --warehouse --connection <id>` | ~10s, Snowflake |
|
|
126
146
|
|
|
127
147
|
Always prefer cached data sources. Check with `graphit ds list`. If no data source covers the table, suggest creating one for future speed.
|
|
148
|
+
|
|
149
|
+
## Presenting Query Results
|
|
150
|
+
|
|
151
|
+
Ground every query in the KB. When using `{{metric:X}}`/`{{dim:X}}` references, show five sections:
|
|
152
|
+
1. **KB Assets:** list all referenced metrics, dimensions, tables as bold names
|
|
153
|
+
2. **Query:** original SQL with `{{metric:X}}` references in ```sql block
|
|
154
|
+
3. **Resolved SQL:** expanded SQL (always use `--verbose`) in ```sql block
|
|
155
|
+
4. **Results:** markdown table with right-aligned numbers, row count, DS ID
|
|
156
|
+
5. **Governance:** tier, KB refs count, rules enforced (**bold names**), max rows
|
|
157
|
+
|
|
158
|
+
For inline SQL (no KB refs): show query + results + governance, and suggest the KB reference equivalent to nudge toward governed tier.
|
|
159
|
+
|
|
160
|
+
## Presenting Data Source Results
|
|
161
|
+
|
|
162
|
+
After `graphit ds list`: table with **bold Name**, ID, Rows (right-aligned), Status, Governed. End with DS recommendation for current task.
|
|
@@ -79,6 +79,12 @@ Use the system font stack everywhere: `-apple-system, BlinkMacSystemFont, 'Segoe
|
|
|
79
79
|
box-shadow:0 1px 3px rgba(0,0,0,0.08); }
|
|
80
80
|
.card h3 { font-size:12px; font-weight:600; color:var(--graphit-fg-subtle); text-transform:uppercase;
|
|
81
81
|
letter-spacing:0.05em; margin-bottom:16px; }
|
|
82
|
+
@keyframes gh-spin{to{transform:rotate(360deg)}}
|
|
83
|
+
.gh-loading { position:relative; min-height:120px; }
|
|
84
|
+
.gh-loading-overlay { position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
|
|
85
|
+
z-index:9998; backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
|
|
86
|
+
background:color-mix(in srgb,var(--graphit-surface-raised,#fff) 50%,transparent); border-radius:inherit; }
|
|
87
|
+
.gh-loading-spin { animation:gh-spin .7s linear infinite; }
|
|
82
88
|
@media(max-width:900px) {
|
|
83
89
|
.kpi-grid { grid-template-columns:repeat(2,1fr); }
|
|
84
90
|
.charts-grid { grid-template-columns:1fr; }
|
|
@@ -118,6 +124,16 @@ Use the system font stack everywhere: `-apple-system, BlinkMacSystemFont, 'Segoe
|
|
|
118
124
|
</style>
|
|
119
125
|
```
|
|
120
126
|
|
|
127
|
+
### Loading State
|
|
121
128
|
|
|
129
|
+
Bake this overlay inside every element passed as `target:` to `graphit.resolve()` - and only those (static text/title cards would spin forever). It shows from the first paint, before the SDK connects; the SDK removes it when the resolve settles. Class names are the SDK contract - keep them exactly:
|
|
130
|
+
|
|
131
|
+
```html
|
|
132
|
+
<div id="spend-chart" class="gh-loading">
|
|
133
|
+
<div class="gh-loading-overlay"><svg class="gh-loading-spin" width="24" height="24" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" stroke="var(--graphit-border,#e5e5e5)" stroke-width="2.5"/><path d="M12 2a10 10 0 0 1 10 10" stroke="var(--graphit-accent,#4DB6AC)" stroke-width="2.5" stroke-linecap="round"/></svg></div>
|
|
134
|
+
</div>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Never write "Loading..." text placeholders - they don't animate and make slow loads look stuck.
|
|
122
138
|
|
|
123
139
|
For inline chart implementations (bar, line, donut, heatmap, funnel, sparkline, gauge, stacked bar, multi-series), see `chart-patterns.md`.
|
|
@@ -25,6 +25,8 @@ NEVER query the warehouse directly when a cached data source covers the table. D
|
|
|
25
25
|
|
|
26
26
|
Run `graphit ds list` FIRST. If a DS covers your table, use `graphit query "SQL" --ds <id>`. Only use `--warehouse` if NO data source exists and the user approves.
|
|
27
27
|
|
|
28
|
+
**Use the DS name in SQL, not the source table name.** Write `FROM MARKETING_UA_DS`, not `FROM MARKETING_UA`. The DS name is what users see in the platform. DuckDB resolves both, but the DS name keeps SQL consistent with the UI.
|
|
29
|
+
|
|
28
30
|
### 3. EVERY element must have entity wrapping
|
|
29
31
|
Without `data-graphit-*` attributes, elements are invisible to the platform. Every chart, KPI card, table, and text section needs ALL FIVE attributes:
|
|
30
32
|
|
|
@@ -32,7 +34,7 @@ Without `data-graphit-*` attributes, elements are invisible to the platform. Eve
|
|
|
32
34
|
<div data-graphit-id="revenue-trend"
|
|
33
35
|
data-graphit-label="Revenue Trend"
|
|
34
36
|
data-graphit-kb="metric:REVENUE,dimension:REGION,table:ORDERS"
|
|
35
|
-
data-graphit-sql="SELECT region, SUM(revenue) FROM
|
|
37
|
+
data-graphit-sql="SELECT region, SUM(revenue) FROM orders GROUP BY region"
|
|
36
38
|
data-graphit-ds="ds_abc123">
|
|
37
39
|
<!-- chart/KPI/table content here -->
|
|
38
40
|
</div>
|
|
@@ -42,7 +44,7 @@ Without `data-graphit-*` attributes, elements are invisible to the platform. Eve
|
|
|
42
44
|
|-----------|--------|---------|
|
|
43
45
|
| `data-graphit-id` | Unique kebab-case | `"spend-by-source"` |
|
|
44
46
|
| `data-graphit-label` | Human-readable name | `"Ad Spend by Source"` |
|
|
45
|
-
| `data-graphit-kb` | `type:NAME` comma-separated | `"metric:CPI,dimension:MEDIA_SOURCE,table:
|
|
47
|
+
| `data-graphit-kb` | `type:NAME` comma-separated | `"metric:CPI,dimension:MEDIA_SOURCE,table:MARKETING_UA_DS"` |
|
|
46
48
|
| `data-graphit-sql` | Executable SQL (HTML-encode `<>&"`) | `"SELECT ..."` |
|
|
47
49
|
| `data-graphit-ds` | Data source ID from `graphit ds list` | `"ds_abc123"` |
|
|
48
50
|
|
|
@@ -63,6 +65,38 @@ NEVER embed query results as static JS variables. The dashboard iframe provides
|
|
|
63
65
|
|
|
64
66
|
---
|
|
65
67
|
|
|
68
|
+
## Presenting Results to the User
|
|
69
|
+
|
|
70
|
+
You are the rendering layer - format and present every CLI result using markdown. Never silently consume output.
|
|
71
|
+
|
|
72
|
+
**Formatting fundamentals:** Bold every KB asset/table/DS name (**CPI**, **MARKETING_UA_DS**). Markdown tables for tabular data. Code blocks (```sql) for every SQL query. Format numbers: commas (12,400), $ for currency, % for rates. Narrate between steps.
|
|
73
|
+
|
|
74
|
+
**After `graphit query`** - ground in KB, show five sections:
|
|
75
|
+
1. **KB Assets:** bold names of all referenced metrics/dimensions/tables
|
|
76
|
+
2. **Query:** original SQL with `{{metric:X}}` references in ```sql block
|
|
77
|
+
3. **Resolved SQL:** expanded SQL (always use `--verbose`) in ```sql block
|
|
78
|
+
4. **Results:** markdown table, row count, DS ID
|
|
79
|
+
5. **Governance:** tier, KB refs, rules enforced (**bold names**)
|
|
80
|
+
- For ad-hoc queries: show query + results + governance, suggest KB reference equivalent
|
|
81
|
+
|
|
82
|
+
**After `graphit kb list`** - count summary + table with bold names:
|
|
83
|
+
- `**12 metrics** across 3 tables:` then table with **bold name**, table, calculation, params
|
|
84
|
+
- Footer summarizing parameterized vs pre-baked
|
|
85
|
+
|
|
86
|
+
**After `graphit kb get`** - entity heading + key-value table:
|
|
87
|
+
- `### **CPI** (metric, verified)` with description in italics
|
|
88
|
+
- Key-value table: Table, Calculation, Parameters, Topics, Dimensions
|
|
89
|
+
|
|
90
|
+
**After `graphit kb search`** - result count + table with type column
|
|
91
|
+
**After `graphit kb explore`** - tree structure: Asset > Tables > Dimensions > Rules
|
|
92
|
+
**After `graphit ds list`** - table with Name, ID, Rows, Status, Governed + recommend which DS to use
|
|
93
|
+
**After `graphit governance status`** - mode + conformance table (Governed/Ad-hoc/Blocked with counts and %)
|
|
94
|
+
**After errors** - bold error message + actionable fix suggestion
|
|
95
|
+
|
|
96
|
+
**Never:** run queries silently, present raw JSON, skip governance footer, omit SQL code blocks, show tables without bold asset names.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
66
100
|
## Workflow: Dashboard Build
|
|
67
101
|
|
|
68
102
|
1. **Ask the user** what dashboard they want.
|
|
@@ -87,13 +121,17 @@ When the user wants to build or populate their KB from files, schemas, or scratc
|
|
|
87
121
|
## Query Governance and Reference Syntax
|
|
88
122
|
|
|
89
123
|
Use KB references for governed queries:
|
|
90
|
-
- `{{metric:NAME}}` - expands to metric calculation
|
|
124
|
+
- `{{metric:NAME}}` - expands to metric calculation, e.g. `{{metric:CPI}}`
|
|
125
|
+
- `{{metric:NAME(K=V)}}` - parameterized metric, e.g. `{{metric:ARPU(DAY=7)}}`
|
|
91
126
|
- `{{dim:NAME}}` - expands to dimension expression
|
|
92
127
|
- `{{metric_raw:NAME}}` - raw expression (no aggregate)
|
|
93
128
|
|
|
129
|
+
**Parameterized metrics:** Some metrics require parameters (check `graphit kb list metric` - `params` column). Use `{{metric:ARPU(DAY=7)}}` to resolve `${PARAM:DAY}`. Pre-baked variants (ARPU_D7, ROAS_D30) need no parameters.
|
|
130
|
+
|
|
94
131
|
```bash
|
|
95
|
-
graphit query "SELECT {{dim:INSTALL_MONTH}}, {{metric:
|
|
96
|
-
graphit query "SELECT
|
|
132
|
+
graphit query "SELECT {{dim:INSTALL_MONTH}}, {{metric:CPI}} as cpi FROM MARKETING_UA_DS GROUP BY 1" --ds ds_abc123
|
|
133
|
+
graphit query "SELECT {{metric:ARPU(DAY=7)}} as arpu FROM MARKETING_UA_DS" --ds ds_abc123 # parameterized
|
|
134
|
+
graphit query "SELECT * FROM events" --ds ds_123 --override-rules EXCLUDE_RETARGETING # bypass rule
|
|
97
135
|
graphit governance status # view mode and conformance
|
|
98
136
|
graphit governance set warn # change mode (admin)
|
|
99
137
|
graphit ds update <id> --governed-mode on # enable governance on DS
|
|
@@ -113,14 +151,27 @@ const result = await graphit.resolve({
|
|
|
113
151
|
// Returns: { columns: string[], data: object[], rowCount: number, truncated: boolean }
|
|
114
152
|
```
|
|
115
153
|
|
|
116
|
-
**Error handling:** Rejects on timeout (
|
|
154
|
+
**Error handling:** Rejects on timeout (120s), bad SQL, or invalid data source ID. Wrap in try/catch.
|
|
155
|
+
|
|
156
|
+
### First-paint loading state
|
|
157
|
+
|
|
158
|
+
The HTML paints before the SDK connects, so bake a pure-CSS spinner overlay into the page; the SDK adopts it and removes it when that element's `graphit.resolve()` settles (success or error). Add once to the page `<style>`:
|
|
159
|
+
|
|
160
|
+
```css
|
|
161
|
+
@keyframes gh-spin{to{transform:rotate(360deg)}}
|
|
162
|
+
.gh-loading{position:relative;min-height:120px}
|
|
163
|
+
.gh-loading-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;z-index:9998;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);background:color-mix(in srgb,var(--graphit-surface-raised,#fff) 50%,transparent);border-radius:inherit}
|
|
164
|
+
.gh-loading-spin{animation:gh-spin .7s linear infinite}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Add the overlay inside EVERY element you pass as `target:` - and ONLY those elements (a static text/title section with no resolve call would spin forever). Keep the class names exactly as shown; they are the contract the SDK uses to adopt and remove the overlay. NEVER write text placeholders ("Loading...", "Fetching data...") - they never animate and make slow loads look stuck. See the canonical pattern below for the overlay markup.
|
|
117
168
|
|
|
118
169
|
### Runtime chart helpers
|
|
119
170
|
|
|
120
171
|
| Helper | Usage |
|
|
121
172
|
|---|---|
|
|
122
173
|
| `graphit.chart(el, {type, data, x, y, ...})` | Bar, line, area, donut, scatter, stacked-bar, heatmap, funnel, gauge, sparkline |
|
|
123
|
-
| `graphit.table(el, {data, columns?,
|
|
174
|
+
| `graphit.table(el, {data, columns?, maxRows?})` | Styled HTML table. `columns` is a `string[]` of data keys (NOT objects), defaults to `Object.keys(data[0])` |
|
|
124
175
|
| `graphit.kpi(el, {value, label?, format?})` | KPI card with optional delta |
|
|
125
176
|
|
|
126
177
|
`graphit.chart` types: `"bar"`, `"line"`, `"area"`, `"donut"` (alias `"pie"`), `"scatter"` (alias `"bubble"`), `"stacked-bar"` (alias `"stacked"`), `"heatmap"`, `"funnel"`, `"gauge"`, `"sparkline"`. Config: `x` (category field), `y` (value field), `series` (group-by), `title`, `height` (140-900px), `valueFormat` (`"currency"` | `"percent"` | `"number"`), `colors` (array). Scatter adds: `size`, `label`. Gauge adds: `min`, `max`, `format`. Sparkline adds: `width`, `showValue`.
|
|
@@ -130,15 +181,17 @@ const result = await graphit.resolve({
|
|
|
130
181
|
```html
|
|
131
182
|
<div data-graphit-id="spend-by-source"
|
|
132
183
|
data-graphit-label="Ad Spend by Source"
|
|
133
|
-
data-graphit-kb="metric:CPI,dimension:MEDIA_SOURCE,table:
|
|
134
|
-
data-graphit-sql="SELECT MEDIA_SOURCE, SUM(APPSFLYER_COST) as spend FROM
|
|
184
|
+
data-graphit-kb="metric:CPI,dimension:MEDIA_SOURCE,table:MARKETING_UA_DS"
|
|
185
|
+
data-graphit-sql="SELECT MEDIA_SOURCE, SUM(APPSFLYER_COST) as spend FROM MARKETING_UA_DS GROUP BY MEDIA_SOURCE ORDER BY spend DESC"
|
|
135
186
|
data-graphit-ds="ds_abc123">
|
|
136
|
-
<div id="spend-chart"
|
|
187
|
+
<div id="spend-chart" class="gh-loading">
|
|
188
|
+
<div class="gh-loading-overlay"><svg class="gh-loading-spin" width="24" height="24" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" stroke="var(--graphit-border,#e5e5e5)" stroke-width="2.5"/><path d="M12 2a10 10 0 0 1 10 10" stroke="var(--graphit-accent,#4DB6AC)" stroke-width="2.5" stroke-linecap="round"/></svg></div>
|
|
189
|
+
</div>
|
|
137
190
|
</div>
|
|
138
191
|
<script>
|
|
139
192
|
(async function() {
|
|
140
193
|
var r = await graphit.resolve({
|
|
141
|
-
sql: "SELECT MEDIA_SOURCE, SUM(APPSFLYER_COST) as spend FROM
|
|
194
|
+
sql: "SELECT MEDIA_SOURCE, SUM(APPSFLYER_COST) as spend FROM MARKETING_UA_DS GROUP BY MEDIA_SOURCE ORDER BY spend DESC",
|
|
142
195
|
dataSourceId: "ds_abc123",
|
|
143
196
|
target: "#spend-chart"
|
|
144
197
|
});
|
|
@@ -92,3 +92,27 @@ Before generating the HTML:
|
|
|
92
92
|
- [ ] 3+ chart types for 4+ graphs
|
|
93
93
|
- [ ] Every KPI has definition + baseline + direction
|
|
94
94
|
- [ ] No anti-patterns present
|
|
95
|
+
|
|
96
|
+
## Presenting Dashboard & Connector Results
|
|
97
|
+
|
|
98
|
+
**After `graphit dashboard list`:**
|
|
99
|
+
|
|
100
|
+
~~~
|
|
101
|
+
**4 dashboards:**
|
|
102
|
+
|
|
103
|
+
| Name | ID | URL |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| **UA Command Center** | dash_abc | https://app.graphit-app.com/custom-dashboard/dash_abc |
|
|
106
|
+
| **Player Quality** | dash_def | https://app.graphit-app.com/custom-dashboard/dash_def |
|
|
107
|
+
~~~
|
|
108
|
+
|
|
109
|
+
**After `graphit connector list`:**
|
|
110
|
+
|
|
111
|
+
~~~
|
|
112
|
+
**2 connections:**
|
|
113
|
+
|
|
114
|
+
| Name | Account | Auth |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| **prod-snowflake** | xy12345.us-east-1 | OAuth |
|
|
117
|
+
| **staging-sf** | ab67890.us-east-1 | Keypair |
|
|
118
|
+
~~~
|
|
@@ -8,12 +8,12 @@ Use KB references instead of inline formulas for governed queries:
|
|
|
8
8
|
|
|
9
9
|
```sql
|
|
10
10
|
-- Governed: server expands to KB-defined formulas
|
|
11
|
-
SELECT {{dim:INSTALL_MONTH}}, {{metric:
|
|
12
|
-
FROM
|
|
11
|
+
SELECT {{dim:INSTALL_MONTH}}, {{metric:CPI}} as cpi
|
|
12
|
+
FROM MARKETING_UA_DS
|
|
13
13
|
GROUP BY 1
|
|
14
14
|
|
|
15
|
-
-- Parameterized metric
|
|
16
|
-
SELECT {{metric:ARPU(
|
|
15
|
+
-- Parameterized metric (ARPU requires DAY parameter)
|
|
16
|
+
SELECT {{metric:ARPU(DAY=7)}} as arpu FROM MARKETING_UA_DS
|
|
17
17
|
|
|
18
18
|
-- Raw expression (no outer aggregate)
|
|
19
19
|
SELECT {{metric_raw:REVENUE}} FROM orders
|
|
@@ -21,6 +21,8 @@ SELECT {{metric_raw:REVENUE}} FROM orders
|
|
|
21
21
|
|
|
22
22
|
The server compiles references to actual expressions, injects rule constraints, stamps trust tier, and caches results. Reference syntax works in both `graphit query` and `graphit.resolve()`.
|
|
23
23
|
|
|
24
|
+
**Parameterized metrics:** Some metrics (ARPU, ROAS, RETENTION) require parameters. Check `graphit kb list metric` - the `params` column shows required names. Pre-baked variants (ARPU_D7, ROAS_D30) have values hardcoded and need no parameters. Omitting required parameters returns a clear error with the exact syntax to use.
|
|
25
|
+
|
|
24
26
|
## Trust Tiers
|
|
25
27
|
|
|
26
28
|
| Tier | Meaning | Badge |
|
|
@@ -76,3 +78,29 @@ graphit ds update <id> --governed-mode on # Enable governed mode
|
|
|
76
78
|
graphit ds update <id> --governed-mode off # Disable
|
|
77
79
|
graphit ds update <id> --max-rows 10000 # Set row cap
|
|
78
80
|
```
|
|
81
|
+
|
|
82
|
+
## Presenting Governance Results
|
|
83
|
+
|
|
84
|
+
**After `graphit governance status`:**
|
|
85
|
+
|
|
86
|
+
~~~
|
|
87
|
+
**Governance mode:** strict
|
|
88
|
+
|
|
89
|
+
**7-day conformance:**
|
|
90
|
+
|
|
91
|
+
| Tier | Queries | Share |
|
|
92
|
+
|---|---:|---:|
|
|
93
|
+
| Governed | 847 | 72% |
|
|
94
|
+
| Ad-hoc | 328 | 28% |
|
|
95
|
+
| Blocked | 12 | 1% |
|
|
96
|
+
|
|
97
|
+
5 active rules across 3 governed data sources.
|
|
98
|
+
~~~
|
|
99
|
+
|
|
100
|
+
**After governance errors:**
|
|
101
|
+
|
|
102
|
+
~~~
|
|
103
|
+
**Error:** Query blocked by governance.
|
|
104
|
+
|
|
105
|
+
Rule **EXCLUDE_ORGANIC** (override policy: `never`) cannot be overridden. Contact your admin to change the policy, or rewrite the query to include the required filter.
|
|
106
|
+
~~~
|
|
@@ -37,6 +37,17 @@ NEVER use hardcoded hex colors. Use CSS custom properties that automatically ada
|
|
|
37
37
|
- **Warning:** `var(--graphit-warning)`
|
|
38
38
|
- **Danger / bad:** `var(--graphit-error)`
|
|
39
39
|
|
|
40
|
+
### Custom Colors
|
|
41
|
+
|
|
42
|
+
When the user explicitly requests specific colors or a custom color theme, ask for both light and dark mode variants. Write explicit CSS with `.dark` class overrides so the dashboard adapts to both modes:
|
|
43
|
+
|
|
44
|
+
```css
|
|
45
|
+
.revenue-card { background: #001f3f; color: #ffffff; }
|
|
46
|
+
.dark .revenue-card { background: #0a3d6b; color: #e0e0e0; }
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
NEVER hardcode a structural color (background, color, border-color) without a `.dark` counterpart. Data/chart series colors (bar fills, line strokes) are exempt - they work in both modes.
|
|
50
|
+
|
|
40
51
|
### Multi-Series
|
|
41
52
|
Use `var(--graphit-accent)` as the primary series. For additional series, choose muted complementary colors that work on both light and dark backgrounds.
|
|
42
53
|
|
|
@@ -73,6 +84,12 @@ Use the system font stack everywhere: `-apple-system, BlinkMacSystemFont, 'Segoe
|
|
|
73
84
|
box-shadow:0 1px 3px rgba(0,0,0,0.08); }
|
|
74
85
|
.card h3 { font-size:12px; font-weight:600; color:var(--graphit-fg-subtle); text-transform:uppercase;
|
|
75
86
|
letter-spacing:0.05em; margin-bottom:16px; }
|
|
87
|
+
@keyframes gh-spin{to{transform:rotate(360deg)}}
|
|
88
|
+
.gh-loading { position:relative; min-height:120px; }
|
|
89
|
+
.gh-loading-overlay { position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
|
|
90
|
+
z-index:9998; backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
|
|
91
|
+
background:color-mix(in srgb,var(--graphit-surface-raised,#fff) 50%,transparent); border-radius:inherit; }
|
|
92
|
+
.gh-loading-spin { animation:gh-spin .7s linear infinite; }
|
|
76
93
|
@media(max-width:900px) {
|
|
77
94
|
.kpi-grid { grid-template-columns:repeat(2,1fr); }
|
|
78
95
|
.charts-grid { grid-template-columns:1fr; }
|
|
@@ -112,6 +129,16 @@ Use the system font stack everywhere: `-apple-system, BlinkMacSystemFont, 'Segoe
|
|
|
112
129
|
</style>
|
|
113
130
|
```
|
|
114
131
|
|
|
132
|
+
### Loading State
|
|
133
|
+
|
|
134
|
+
Bake this overlay inside every element passed as `target:` to `graphit.resolve()` - and only those (static text/title cards would spin forever). It shows from the first paint, before the SDK connects; the SDK removes it when the resolve settles. Class names are the SDK contract - keep them exactly:
|
|
135
|
+
|
|
136
|
+
```html
|
|
137
|
+
<div id="spend-chart" class="gh-loading">
|
|
138
|
+
<div class="gh-loading-overlay"><svg class="gh-loading-spin" width="24" height="24" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" stroke="var(--graphit-border,#e5e5e5)" stroke-width="2.5"/><path d="M12 2a10 10 0 0 1 10 10" stroke="var(--graphit-accent,#4DB6AC)" stroke-width="2.5" stroke-linecap="round"/></svg></div>
|
|
139
|
+
</div>
|
|
140
|
+
```
|
|
115
141
|
|
|
142
|
+
Never write "Loading..." text placeholders - they don't animate and make slow loads look stuck.
|
|
116
143
|
|
|
117
144
|
For inline chart implementations (bar, line, donut, heatmap, funnel, sparkline, gauge, stacked bar, multi-series), see `chart-patterns.md`.
|