@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.
@@ -56,3 +56,68 @@ Use `kb_explore` with max_depth=2 and no edge_type filter. Returns the table's d
56
56
  | 3 (max) | Full neighborhood. Use sparingly - can return many results for connected tables. |
57
57
 
58
58
  Start at depth 1. Only increase if the user's question requires it or the initial results are insufficient.
59
+
60
+ ## Presenting KB Results
61
+
62
+ **After `graphit kb list <type>`** - count summary + table with bold names:
63
+
64
+ ~~~
65
+ **12 metrics** across 3 tables:
66
+
67
+ | Metric | Table | Calculation | Params |
68
+ |---|---|---|---|
69
+ | **CPI** | **MARKETING_UA** | `SUM(spend)/SUM(installs)` | - |
70
+ | **ARPU** | **MARKETING_UA** | `SUM(revenue)/COUNT(...)` | DAY |
71
+ | **RETENTION** | **PLAYER_QUALITY** | `COUNT(CASE WHEN ...)` | DAY |
72
+ | ... | | | |
73
+
74
+ 4 parameterized (need `(DAY=N)` syntax), 8 pre-baked.
75
+ ~~~
76
+
77
+ Adapt columns per type: dimensions include semantic type, rules include constraint count, domains include asset count.
78
+
79
+ **After `graphit kb get <type> <name>`** - entity heading + key-value table:
80
+
81
+ ~~~
82
+ ### **CPI** (metric, verified)
83
+
84
+ *Cost per install*
85
+
86
+ | | |
87
+ |---:|---|
88
+ | **Table** | **MARKETING_UA** |
89
+ | **Calculation** | `SUM(spend) / SUM(installs)` |
90
+ | **Parameters** | none |
91
+ | **Topics** | ACQUISITION, SPEND |
92
+ | **Default dims** | MEDIA_SOURCE, CAMPAIGN_NAME |
93
+ ~~~
94
+
95
+ Adapt fields per type. Rules: content, constraints, apply-on, override policy. Dimensions: expression, semantic type, output type.
96
+
97
+ **After `graphit kb search`** - result count + table with type column:
98
+
99
+ ~~~
100
+ **5 results** for "revenue":
101
+
102
+ | Type | Name | Description |
103
+ |---|---|---|
104
+ | metric | **TOTAL_REVENUE** | Total revenue across all channels |
105
+ | dimension | **REVENUE_BUCKET** | Revenue range segmentation |
106
+ | synonym | GMV | Maps to **TOTAL_REVENUE** (metric) |
107
+ ~~~
108
+
109
+ **After `graphit kb explore`** - tree with bold names, indented by level:
110
+
111
+ ~~~
112
+ **CPI** (metric) on **MARKETING_UA**:
113
+ - **Calculation:** `SUM(spend) / SUM(installs)`
114
+ - **Tables:** **MARKETING_UA**, **MARKETING_UA_6MO** (secondary)
115
+ - **Related dimensions (8):**
116
+ - **MEDIA_SOURCE** - `media_source` (categorical)
117
+ - **CAMPAIGN_NAME** - `campaign_name` (categorical)
118
+ - ...
119
+ - **Rules:** **EXCLUDE_ORGANIC** (filters organic installs)
120
+ - **Domain:** MARKETING
121
+ ~~~
122
+
123
+ For domain exploration, show Domain > Table > Asset hierarchy as a tree.
@@ -0,0 +1,143 @@
1
+ # Presentations
2
+
3
+ Build full-screen slide deck presentations inside Graphit canvas dashboards using `graphit.presentation()`.
4
+
5
+ ## API
6
+
7
+ ```js
8
+ var deck = graphit.presentation('#my-deck');
9
+
10
+ deck.slide({ bg: 'dark', layout: 'center', html: '<h1>Title</h1><p>Subtitle</p>' });
11
+ deck.slide({ bg: 'white', layout: 'split', html: '<div>Left panel</div><div>Right panel</div>' });
12
+ deck.slide({ bg: 'paper', layout: 'full', html: '<div id="charts">...</div>' });
13
+
14
+ var ctrl = deck.start();
15
+ // ctrl.go(3) - jump to slide 4
16
+ // ctrl.total - number of slides
17
+ ```
18
+
19
+ `graphit.presentation(el)` returns a **builder**. Call `.slide()` to add slides (chainable), then `.start()` to render and wire navigation. `start()` returns a controller with `go(n)` and `total`.
20
+
21
+ ## Slide Config
22
+
23
+ | Field | Type | Default | Description |
24
+ |-------|------|---------|-------------|
25
+ | `layout` | `'center'` / `'split'` / `'full'` | `'center'` | How content is arranged inside the slide |
26
+ | `bg` | `'paper'` / `'white'` / `'dark'` / `'teal'` / hex | `'paper'` | Fixed background color (not theme-aware) |
27
+ | `html` | `string` | `''` | Raw HTML content for the slide |
28
+
29
+ ## Layouts
30
+
31
+ ### `center` (default)
32
+ Content is centered horizontally and vertically. Use for title slides, quotes, big stats, CTAs.
33
+
34
+ ### `split`
35
+ The first two child elements in `html` become left and right panels (50/50 flex). Use for text + image, comparison, side-by-side content. On mobile (<700px), panels stack vertically.
36
+
37
+ ```js
38
+ deck.slide({
39
+ layout: 'split',
40
+ bg: 'white',
41
+ html: '<div><h2>Left Title</h2><p>Text content</p></div><div><img src="data:..."></div>'
42
+ });
43
+ ```
44
+
45
+ ### `full`
46
+ Content flows top-to-bottom with generous padding. Use for tables, grids, live data dashboards, complex layouts that need the full slide width.
47
+
48
+ ## Background Themes
49
+
50
+ Backgrounds are **fixed colors** - they do not change with the app's light/dark mode. A dark slide stays dark. Content inside CAN use `var(--graphit-*)` tokens.
51
+
52
+ | Name | Background | Text Color |
53
+ |------|-----------|------------|
54
+ | `paper` | `#F7F6F2` (warm cream) | `#222224` (charcoal) |
55
+ | `white` | `#FFFFFF` | `#222224` |
56
+ | `dark` | `#222224` (charcoal) | `#F7F6F2` (cream) |
57
+ | `teal` | `#4DB6AC` (brand teal) | `#FFFFFF` |
58
+ | hex string | Custom (e.g. `#1a1a2e`) | Inherited from content HTML |
59
+
60
+ ## Navigation
61
+
62
+ The slide deck includes built-in navigation:
63
+ - **Arrow keys**: Left/Right to navigate (guarded - skips when focus is on input/textarea)
64
+ - **Spacebar**: Next slide
65
+ - **Home/End**: First/last slide
66
+ - **Prev/Next buttons**: Bottom-right corner with slide counter
67
+
68
+ ## Live Data Inside Slides
69
+
70
+ `graphit.resolve()` and `graphit.chart/table/kpi` work inside slides. All resolve calls fire on page load (cached queries are fast). Charts render into their target elements regardless of which slide is visible.
71
+
72
+ ```js
73
+ // Slide with live entities
74
+ deck.slide({
75
+ layout: 'full',
76
+ bg: 'white',
77
+ html: `
78
+ <h2>Live Data</h2>
79
+ <div data-graphit-id="spend-chart" data-graphit-label="Ad Spend"
80
+ data-graphit-kb="metric:TOTAL_AD_SPEND,table:MARKETING_UA"
81
+ data-graphit-sql="SELECT MEDIA_SOURCE, SUM(APPSFLYER_COST) AS spend FROM MARKETING_UA_DS GROUP BY 1 ORDER BY spend DESC LIMIT 6"
82
+ data-graphit-ds="ds_abc123">
83
+ <div id="chart1" class="gh-loading">
84
+ <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="#e5e5e5" stroke-width="2.5"/><path d="M12 2a10 10 0 0 1 10 10" stroke="#4DB6AC" stroke-width="2.5" stroke-linecap="round"/></svg></div>
85
+ </div>
86
+ </div>
87
+ `
88
+ });
89
+
90
+ // After deck.start(), fire resolve calls
91
+ deck.start();
92
+
93
+ graphit.resolve({
94
+ sql: "SELECT MEDIA_SOURCE, SUM(APPSFLYER_COST) AS spend FROM MARKETING_UA_DS GROUP BY 1 ORDER BY spend DESC LIMIT 6",
95
+ dataSourceId: "ds_abc123",
96
+ target: "#chart1"
97
+ }).then(function(r) {
98
+ graphit.chart("#chart1", { type: "bar", data: r.data, x: "MEDIA_SOURCE", y: "spend", valueFormat: "currency" });
99
+ });
100
+ ```
101
+
102
+ ## Entity Wrapping
103
+
104
+ Every data element inside slides should have full `data-graphit-*` entity wrapping, same as regular dashboards. The entity provenance panel, @ mentions, and click-to-inspect all work inside slides.
105
+
106
+ ## Example: Complete Pitch Deck
107
+
108
+ ```js
109
+ var deck = graphit.presentation('#deck');
110
+
111
+ deck.slide({
112
+ bg: 'paper',
113
+ layout: 'center',
114
+ html: '<h1 style="font-size:6rem;font-weight:300">Company Update</h1><p style="font-size:1.5rem;color:#6B7280;margin-top:16px">Q2 2026</p>'
115
+ });
116
+
117
+ deck.slide({
118
+ bg: 'dark',
119
+ layout: 'split',
120
+ html: '<div style="color:#F7F6F2"><h2 style="font-size:3rem">The Problem</h2><p style="font-size:1.25rem;opacity:0.7;margin-top:16px">Analysts spend 80% of their time assembling dashboards instead of finding insights.</p></div><div style="display:flex;align-items:center;justify-content:center"><img src="data:image/webp;base64,..." style="max-height:60vh;border-radius:16px"></div>'
121
+ });
122
+
123
+ deck.slide({
124
+ bg: 'white',
125
+ layout: 'full',
126
+ html: '<h2 style="font-size:2.5rem;margin-bottom:24px">Key Metrics</h2><div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px">...</div>'
127
+ });
128
+
129
+ deck.slide({
130
+ bg: 'teal',
131
+ layout: 'center',
132
+ html: '<h2 style="font-size:4rem;color:#fff">Thank You</h2><p style="font-size:1.25rem;color:rgba(255,255,255,0.7);margin-top:16px">company.com</p>'
133
+ });
134
+
135
+ deck.start();
136
+ ```
137
+
138
+ ## Anti-Patterns
139
+
140
+ - **Don't put too much content on one slide.** If it scrolls, split into multiple slides.
141
+ - **Don't use external fonts via @import or link.** The iframe CSP blocks them. Embed as base64 @font-face or use system fonts.
142
+ - **Don't use external images.** Use data URIs (base64) or inline SVG.
143
+ - **Don't nest presentations.** One `graphit.presentation()` per dashboard.
@@ -111,6 +111,26 @@ SELECT UNNEST(generate_series(
111
111
 
112
112
  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.
113
113
 
114
+ ## Cache-Friendly SQL (Canvas Resolve)
115
+
116
+ 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.
117
+
118
+ **Shape rules:**
119
+ - Single table (no JOIN/UNION)
120
+ - WHERE as flat AND of `column = literal`, `column IN (...)`, `column BETWEEN ... AND ...` conjuncts
121
+ - 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)
122
+ - Literal dates (`>= '2026-01-01'`), never `CURRENT_DATE` expressions
123
+ - GROUP BY column names or ordinals; ORDER BY / LIMIT allowed (outer only)
124
+ - CTEs are fine when the CTE body follows the same rules
125
+ - 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
126
+
127
+ **Shapes that skip the cache** (fall back to normal execution, still correct):
128
+ - `COUNT(DISTINCT x)`, window functions, HAVING, QUALIFY
129
+ - OR / NOT in WHERE
130
+ - Ratio metrics (`SUM(a)/NULLIF(SUM(b),0)`) - compute client-side or use two resolves
131
+ - `CURRENT_DATE`-relative predicates
132
+ - 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)
133
+
114
134
  ## Data Source Routing
115
135
 
116
136
  | Situation | Command | Speed |
@@ -119,3 +139,91 @@ The outer SELECT can ONLY reference columns the subquery exposes (its aliases).
119
139
  | No data source | `graphit query "SQL" --warehouse --connection <id>` | ~10s, Snowflake |
120
140
 
121
141
  Always prefer cached data sources. Check with `graphit ds list`. If no data source covers the table, suggest creating one for future speed.
142
+
143
+ ## Presenting Query Results
144
+
145
+ After every `graphit query`, present results grounded in the KB. Always show which KB assets were used - this is what makes governed queries valuable.
146
+
147
+ **When using KB reference syntax** (`{{metric:X}}`, `{{dim:X}}`), show all five sections:
148
+
149
+ ~~~
150
+ **KB Assets:** dimension **CAMPAIGN_CATEGORY**, metric **TOTAL_INSTALLS**, metric **CPI**, table **MARKETING_UA_DS**
151
+
152
+ **Query:**
153
+ ```sql
154
+ SELECT
155
+ {{dim:CAMPAIGN_CATEGORY}} AS category,
156
+ {{metric:TOTAL_INSTALLS}} AS installs,
157
+ {{metric:CPI}} AS cpi
158
+ FROM MARKETING_UA_DS
159
+ WHERE ACTIVITY_TIME >= '2026-01-01'
160
+ GROUP BY {{dim:CAMPAIGN_CATEGORY}}
161
+ ORDER BY installs DESC
162
+ ```
163
+
164
+ **Resolved SQL:**
165
+ ```sql
166
+ SELECT
167
+ CASE WHEN IS_RETARGETING THEN 'Retargeting'
168
+ WHEN IS_CTV THEN 'Connected TV'
169
+ ELSE 'User Acquisition' END AS category,
170
+ SUM(INSTALLS) AS installs,
171
+ SUM(APPSFLYER_COST) / NULLIF(SUM(INSTALLS), 0) AS cpi
172
+ FROM MARKETING_UA_DS
173
+ WHERE ACTIVITY_TIME >= '2026-01-01'
174
+ GROUP BY 1
175
+ ORDER BY installs DESC
176
+ ```
177
+
178
+ **Results** (3 rows via **ds_abc123**):
179
+
180
+ | Category | Installs | CPI |
181
+ |---|---:|---:|
182
+ | User Acquisition | 80,605 | $0.79 |
183
+ | Retargeting | 12,300 | $1.24 |
184
+ | Connected TV | 3,100 | $2.80 |
185
+
186
+ **Governance:** governed - 3 KB refs, 2 rules enforced (**EXCLUDE_ORGANIC**, **MIN_SPEND**). Max rows: 1,000.
187
+ ~~~
188
+
189
+ **When using inline SQL** (no `{{metric:X}}`), show query + results + governance:
190
+
191
+ ~~~
192
+ **Query:**
193
+ ```sql
194
+ SELECT media_source, SUM(spend) AS spend FROM MARKETING_UA_DS GROUP BY 1
195
+ ```
196
+
197
+ **Results** (6 rows via **ds_abc123**):
198
+
199
+ | Media Source | Spend |
200
+ |---|---:|
201
+ | Facebook | $42,100 |
202
+ | Google UAC | $38,500 |
203
+
204
+ **Governance:** ad-hoc - 0 KB refs. Consider using `{{metric:TOTAL_SPEND}}` for governed tier.
205
+ ~~~
206
+
207
+ For ad-hoc queries, suggest the KB reference equivalent when one exists. This nudges users toward governed queries.
208
+
209
+ **Always use `--verbose`** to get the resolved SQL. If the user didn't pass it, re-run with `--verbose` so you can show both the reference query and the expanded SQL.
210
+
211
+ Zero rows: explain what you checked and hypothesize why (wrong date range, filter too strict, table empty).
212
+
213
+ ## Presenting Data Source Results
214
+
215
+ After `graphit ds list`:
216
+
217
+ ~~~
218
+ **3 data sources:**
219
+
220
+ | Name | ID | Rows | Status | Governed |
221
+ |---|---|---:|---|---|
222
+ | **MARKETING_UA_DS** | ds_abc123 | 1,247,832 | active | yes |
223
+ | **PLAYER_QUALITY** | ds_def456 | 892,104 | active | no |
224
+ | **REVENUE_EVENTS** | ds_ghi789 | 3,412,006 | stale | yes |
225
+
226
+ Using **MARKETING_UA_DS** (ds_abc123) which covers spend, installs, and ROAS columns.
227
+ ~~~
228
+
229
+ End with a recommendation of which DS to use, or note if none covers the needed table.