@graphit/cli 0.2.208 → 0.2.242
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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/bin/graphit +1 -1
- package/bin/graphit.ps1 +1 -1
- package/dist/api/client.d.ts +36 -0
- package/dist/api/client.js +42 -0
- package/dist/api/client.js.map +1 -1
- package/dist/auth/credentials.d.ts +0 -1
- package/dist/auth/credentials.js.map +1 -1
- package/dist/auth/login.js +0 -1
- package/dist/auth/login.js.map +1 -1
- package/dist/commands/auth.js +0 -3
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/ds.js +67 -9
- package/dist/commands/ds.js.map +1 -1
- package/dist/commands/status.d.ts +51 -0
- package/dist/commands/status.js +117 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/output/format.js +18 -1
- package/dist/output/format.js.map +1 -1
- package/package.json +1 -1
- package/scripts/generate-commands-doc.mjs +1 -1
- package/skills/graphit/SKILL.md +11 -5
- package/skills/graphit/VERSION.json +1 -1
- package/skills/graphit/references/chart-patterns.md +1 -1
- package/skills/graphit/references/data-sources.md +9 -3
- package/skills/graphit/references/governance-explained.md +1 -1
- package/skills/graphit/references/kb-actions.md +13 -2
- package/skills/graphit/references/migration.md +89 -0
- package/skills/graphit/references/operations.md +6 -10
- package/skills/graphit/references/runtime.md +39 -59
|
@@ -1,45 +1,43 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
SIZE EXEMPTION (reference file)
|
|
3
|
-
Hard limit: 7,168 chars | Exempted ceiling:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Reviewed: 2026-07-09
|
|
7
|
-
Next review: 2026-10-09
|
|
3
|
+
Hard limit: 7,168 chars | Exempted ceiling: set by the reference-file exemption paragraph in docs/knowledge/prompt-engineering/sizing/SIZING.md, which names this file; PE-DENY-005 mirrors it and cli/test/skill-size.test.mjs enforces it. A ceiling written only in this header is not an authority.
|
|
4
|
+
Rationale: one co-load unit the co-load test forbids splitting; loads only on HTML-deliverable turns.
|
|
5
|
+
Reviewed: 2026-07-28 | Next review: 2026-10-28
|
|
8
6
|
-->
|
|
9
7
|
# Canvas Runtime: Live Data and the Entity Contract
|
|
10
8
|
|
|
11
|
-
Consult when authoring
|
|
9
|
+
Consult when authoring dashboard HTML and wiring its data: fetching live data, making every visible element a platform entity, painting before data arrives, and shaping resolve SQL so filter changes stay instant. Design tokens and layout CSS live in `graphit-style.md`; this file owns the data wiring.
|
|
12
10
|
|
|
13
11
|
## The live-data API
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
`graphit.resolve()` fetches live data from cached data sources on every page load. NEVER embed query results as static JS variables (`const data = [...]`) - that freezes a snapshot that never refreshes and breaks provenance.
|
|
14
|
+
|
|
15
|
+
The entity owns the query. A resolve call passes no `sql` and no `dataSourceId`: both are read from the entity wrapper, authored once in the attributes and never repeated in the call.
|
|
16
16
|
|
|
17
17
|
```js
|
|
18
18
|
const result = await graphit.resolve({
|
|
19
|
-
sql: "SELECT region, SUM(revenue) AS rev FROM ORDERS_DS GROUP BY region",
|
|
20
|
-
dataSourceId: "ORDERS_DS",
|
|
21
19
|
target: "#chart-container",
|
|
22
20
|
maxRows: 10000
|
|
23
21
|
});
|
|
24
22
|
// Returns: { columns: string[], data: object[], rowCount: number, truncated: boolean }
|
|
25
23
|
```
|
|
26
24
|
|
|
27
|
-
- `
|
|
28
|
-
- `
|
|
25
|
+
- `target` (CSS selector or element) does two jobs: locates the entity whose `data-graphit-sql` / `data-graphit-ds` this call runs, and shows a blur/spinner overlay while loading, removed on completion.
|
|
26
|
+
- `params` (optional) supplies values for the `:name` placeholders in the entity's SQL.
|
|
29
27
|
- `targetEntityIds` (optional, `string[]`) - `data-graphit-id`s of OTHER graphs this result also renders into, so each one's details panel reflects filters (not just `target`); entity ids, never CSS selectors.
|
|
30
28
|
- `sourceEntityId` (optional) - the graph that owns a `target`-less resolve feeding several graphs (pair with `targetEntityIds`).
|
|
31
29
|
- `maxRows` (optional) defaults to **10,000**, capped at **50,000**. Aggregate to a chartable grain well under the default; raise it only for a genuine row-level export, never above the cap.
|
|
32
30
|
- `result.data` is an array of row objects you render however you want.
|
|
33
31
|
|
|
34
|
-
MUST: every resolve
|
|
32
|
+
MUST: every resolve feeding a rendered graph, KPI, or table carries attribution - `target` (an element inside the entity wrapper), or `sourceEntityId` plus `targetEntityIds` when one result feeds several graphs. Attribution records the live filtered query behind that entity's details panel; unattributed, the panel shows `data-graphit-sql` as an unrun example, so a user changing a filter sees the SQL never move. Saving a page with filters or params and zero attributed resolves returns an `unattributed_resolves` warning. Queries feeding no visual (option lists, freshness probes) stay unattributed.
|
|
35
33
|
|
|
36
|
-
CRITICAL: use KB reference syntax (`{{metric:NAME}}`, `{{dim:NAME}}`) inside the
|
|
34
|
+
CRITICAL: use KB reference syntax (`{{metric:NAME}}`, `{{dim:NAME}}`) inside the entity's `data-graphit-sql` whenever a KB asset exists - the server expands it at query time, producing the governed trust tier. Syntax and trust tiers: `governance.md`.
|
|
37
35
|
|
|
38
36
|
Error handling: `graphit.resolve()` rejects on timeout (120s), bad SQL, or an invalid data source ID. Wrap calls in try/catch and show a user-visible error in the target element on failure. Verify the SQL returns data via the CLI before embedding it.
|
|
39
37
|
|
|
40
38
|
## The entity contract
|
|
41
39
|
|
|
42
|
-
Every visible element - chart, KPI card, table, text section - must be wrapped so the platform can see it.
|
|
40
|
+
Every visible element - chart, KPI card, table, text section - must be wrapped so the platform can see it. Unwrapped it is invisible: no click info, no @ mentions, no KB provenance. Wrapping also gives it the 3-dot menu (hover, top-right) and details panel - data source, governed SQL, KB lineage, live results. A graph you draw is as first-class as a `graphit.graph()` chart once wrapped; never rebuild a custom dashboard as native graphs to gain the menu, just add the wrapper. Each needs ALL FOUR attributes:
|
|
43
41
|
|
|
44
42
|
```html
|
|
45
43
|
<div data-graphit-id="revenue-trend"
|
|
@@ -52,27 +50,35 @@ Every visible element - chart, KPI card, table, text section - must be wrapped s
|
|
|
52
50
|
|
|
53
51
|
| Attribute | Format | Example |
|
|
54
52
|
|-----------|--------|---------|
|
|
55
|
-
| `data-graphit-id` | Unique kebab-case | `"spend-by-source"` |
|
|
53
|
+
| `data-graphit-id` | Unique; lowercase letters, digits, `-` and `_`, max 80 (kebab-case preferred) | `"spend-by-source"` |
|
|
56
54
|
| `data-graphit-label` | Human-readable name | `"Ad Spend by Source"` |
|
|
57
|
-
| `data-graphit-sql` |
|
|
55
|
+
| `data-graphit-sql` | The query this entity runs - executable, parameterized (HTML-encode `<`, `>`, `&`, `"`) | `"SELECT ... WHERE d = :day"` |
|
|
58
56
|
| `data-graphit-ds` | Data source name (same as the FROM table) or id | `"ORDERS_DS"` |
|
|
59
57
|
|
|
60
|
-
KB asset references are derived automatically from `{{metric:X}}` / `{{dim:X}}`
|
|
58
|
+
KB asset references are derived automatically from `{{metric:X}}` / `{{dim:X}}` in the SQL; the governance compiler resolves these and shows KB asset chips in the details panel. Missing any one attribute breaks the entity; missing the wrapper makes the element invisible to the platform.
|
|
59
|
+
|
|
60
|
+
**JavaScript may populate an entity, never create one.** Every entity - including on hidden tabs, collapsed panels, and lazily-shown views - exists as static markup in the HTML you save; JavaScript fills its chart host, wires listeners, and toggles visibility. Nothing server-side runs your JavaScript, so a card built at render time (`host.innerHTML = charts.map(...)`) does not exist for governance, lineage, the KB graph, `list-entities`, or a later `get-entity`. A save whose `data-graphit-id`s are reachable only by running your JavaScript is REFUSED and the error names them. A dynamic **query** is supported - the attribute carries the canonical template; a dynamic **entity** is not.
|
|
61
|
+
|
|
62
|
+
- **Declare statically, execute lazily.** A hidden card must not resolve on load - resolve a tab's entities when it first becomes visible, or a 3-tab dashboard turns 8 concurrent queries into 22 on first paint.
|
|
63
|
+
- **One source of truth.** The entity element owns `data-graphit-sql` and `data-graphit-ds`: the resolve/bind call reads them from the entity rather than repeating them, and a JS config array may keep rendering behavior (type, colors, height) but must never restate the chart list.
|
|
64
|
+
- **Never write `data-graphit-id=` in script** (selectors included) - the gate reads it as a phantom entity and refuses the save; match via `el.dataset.graphitId`.
|
|
61
65
|
|
|
62
|
-
**SQL must be complete and executable.**
|
|
66
|
+
**SQL must be complete and executable.** This attribute is the query that runs, and the platform runs it again when a user opens the details panel. NEVER abbreviate, truncate, or leave an ellipsis (`FROM ...`, `SELECT ...`, three dots). Use the real DS table name and only columns that exist in the DS - never an invented summary column, a CTE alias, a JS variable name, or prose. If the query uses a CTE, store the full WITH query. Where the query is filtered, store the parameterized template with its `:name` placeholders - never a frozen variant with one date range baked in, which is the drift this contract removes. The panel's **Current query** is the server's record of what actually ran; never copy that resolved SQL back into this attribute.
|
|
63
67
|
|
|
64
68
|
- **Wrong:** `data-graphit-sql="SELECT INSTALL_TIME, ROIAP_D0 FROM UA_DS"` when the DS has no `ROIAP_D0` column (the chart computes it via CASE) - the details panel errors.
|
|
65
69
|
- **Right:** `data-graphit-sql="SELECT INSTALL_TIME, SUM(CASE WHEN SENIORITY=0 THEN TOTAL_IAP END)/NULLIF(SUM(COST),0) AS ROIAP_D0 FROM UA_DS GROUP BY 1"` - the same derivation the chart runs.
|
|
66
70
|
|
|
67
|
-
|
|
71
|
+
When the entity is filtered, `graphit.bind(el, { params, deps, render })` supplies the values - it derives SQL and data source from the bound entity the same way a resolve does. Older dashboards pass `sql` and `dataSourceId` in the call itself; that still executes and is not a defect to fix, so move a query onto its entity only when asked to.
|
|
68
72
|
|
|
69
|
-
**
|
|
73
|
+
**Label equals the visible title.** `data-graphit-label` MUST match the card's visible heading exactly - users find their chart by that label in @ mention dropdowns and entity panels, and a mismatch means they cannot find it.
|
|
74
|
+
|
|
75
|
+
**Editing one existing entity.** Edit surgically: `graphit dashboard list-entities <id>` lists every entity (id, label, KB refs, data source) to find the right `data-graphit-id`; `graphit dashboard get-entity <id> <entityId>` returns just that entity's inner HTML - the exact fragment `graphit dashboard update-entity <id> <entityId>` accepts - which you change and write back. Use full-page `get-html` / `update-html` only when restructuring the layout.
|
|
70
76
|
|
|
71
77
|
**Name every version.** Always pass `--label "<what changed>"` on every `update-html` / `update-entity` (e.g. `--label "Added revenue KPI row"`) - it names the version in the dashboard's history so edits stay traceable. Keep it short; no secrets or SQL dumps.
|
|
72
78
|
|
|
73
79
|
## First-paint loading state
|
|
74
80
|
|
|
75
|
-
The
|
|
81
|
+
The HTML paints before the SDK connects, so the SDK's own spinner cannot cover the first moments. Bake a pure-CSS overlay into the HTML so every chart spins from the first frame; the SDK adopts it and removes it when that element's `graphit.resolve()` settles (success or error).
|
|
76
82
|
|
|
77
83
|
Add once to the page `<style>`:
|
|
78
84
|
|
|
@@ -83,7 +89,7 @@ Add once to the page `<style>`:
|
|
|
83
89
|
.gh-loading-spin{animation:gh-spin .7s linear infinite}
|
|
84
90
|
```
|
|
85
91
|
|
|
86
|
-
Add the overlay inside EVERY element passed as `target:` to `graphit.resolve()` - and ONLY those
|
|
92
|
+
Add the overlay inside EVERY element passed as `target:` to `graphit.resolve()` - and ONLY those. A static text or title section with no resolve call would spin forever.
|
|
87
93
|
|
|
88
94
|
```html
|
|
89
95
|
<div id="spend-chart" class="gh-loading">
|
|
@@ -95,7 +101,7 @@ The class names are a contract with the SDK (`gh-loading`, `gh-loading-overlay`,
|
|
|
95
101
|
|
|
96
102
|
## Cache-friendly resolve SQL
|
|
97
103
|
|
|
98
|
-
A resolve query
|
|
104
|
+
A resolve query following these shapes serves from a semantic cache in roughly 10ms on filter changes instead of a full DuckDB recompute (5 to 37s on wide data sources). Write resolve SQL this way by default.
|
|
99
105
|
|
|
100
106
|
**Shapes that hit the cache:**
|
|
101
107
|
|
|
@@ -113,25 +119,25 @@ A resolve query that follows these shapes serves from a semantic cache in roughl
|
|
|
113
119
|
- `OR` or `NOT` in WHERE.
|
|
114
120
|
- Ratio metrics (`SUM(a)/NULLIF(SUM(b),0)`) - compute client-side or use two resolves. To display a ratio as a percent, multiply by 100 in SQL (`* 100.0 ... AS x_pct`): the `"percent"` format only appends `%`, it does not scale, so a 0-to-1 ratio would otherwise show as `0.42%`, not `42%`.
|
|
115
121
|
- `CURRENT_DATE`-relative predicates.
|
|
116
|
-
- Top-N with the aggregate only in ORDER BY
|
|
122
|
+
- Top-N with the aggregate only in ORDER BY.
|
|
117
123
|
|
|
118
|
-
**Fresh anchors.** When a query needs a data-driven anchor -
|
|
124
|
+
**Fresh anchors.** When a query needs a data-driven anchor - latest install date, a "days since" reference, a MAX(date) - never fetch it once at page load into a JS variable and reuse it across refreshes. A long-open tab silently goes stale and every maturity gate or rolling window computed against it drifts. Re-resolve the anchor inside each refresh cycle, or derive it in a subquery.
|
|
119
125
|
|
|
120
126
|
## Rate-limit budget
|
|
121
127
|
|
|
122
|
-
`graphit.resolve()` is rate-limited to 120 requests per minute per user per dashboard. Each call counts as one
|
|
128
|
+
`graphit.resolve()` is rate-limited to 120 requests per minute per user per dashboard. Each call counts as one. Design for that budget:
|
|
123
129
|
|
|
124
|
-
- **Single refresh function.** Put all queries in ONE `Promise.all` inside one `refresh()`
|
|
125
|
-
- **Count queries per interaction.** 6 charts is 6 requests per filter change, about 20 changes per minute of budget; 12 charts is about 10
|
|
126
|
-
- **Reuse trend data for KPIs.** If you already fetch a weekly time series, derive the KPI total and its sparkline from that result in JS instead of
|
|
127
|
-
- **Avoid redundant refreshes.** If a filter affects only some charts, split into targeted refresh functions (`refreshKPIs()`, `refreshCharts()`)
|
|
130
|
+
- **Single refresh function.** Put all queries in ONE `Promise.all` inside one `refresh()` so they share a time window. NEVER scatter `graphit.resolve()` across independent event handlers or timeouts - that turns one user action into several bursts.
|
|
131
|
+
- **Count queries per interaction.** 6 charts is 6 requests per filter change, about 20 changes per minute of budget; 12 charts is about 10. With 10 or more charts and 3 or more filters, debounce filter changes (300ms).
|
|
132
|
+
- **Reuse trend data for KPIs.** If you already fetch a weekly time series, derive the KPI total and its sparkline from that result in JS instead of a separate aggregate query. Anchor the extra graphs it feeds with `targetEntityIds` per the attribution rule above. Canonical KPI-row example: `kpi.md`.
|
|
133
|
+
- **Avoid redundant refreshes.** If a filter affects only some charts, split into targeted refresh functions (`refreshKPIs()`, `refreshCharts()`).
|
|
128
134
|
- **No polling.** NEVER use `setInterval(refresh, ...)`. Data sources update on their own schedule; a polling dashboard burns the entire budget.
|
|
129
135
|
|
|
130
136
|
If you hit the limit, the API returns a "Too many requests" error with a retry-after hint.
|
|
131
137
|
|
|
132
138
|
## Helper index
|
|
133
139
|
|
|
134
|
-
You have full creative freedom: draw with `type:'custom'` or inline SVG/CSS/HTML tables
|
|
140
|
+
You have full creative freedom: draw with `type:'custom'` or inline SVG/CSS/HTML tables. The helpers below are shortcuts, not requirements: when a documented type fits, use it; otherwise hand-roll immediately - do not deliberate.
|
|
135
141
|
|
|
136
142
|
| Helper | What it renders | Depth |
|
|
137
143
|
|---|---|---|
|
|
@@ -142,32 +148,6 @@ You have full creative freedom: draw with `type:'custom'` or inline SVG/CSS/HTML
|
|
|
142
148
|
| `graphit.presentation(el)` | A full-screen slide deck builder | `presentations.md` |
|
|
143
149
|
| `graphit.filter / param / dateRange / cascade / bind` | Headless interactivity (zero imposed markup) | `filters.md`, `filters-advanced.md` |
|
|
144
150
|
|
|
145
|
-
**Standard `graphit.graph` types (set `config.type`):** `"bar"`, `"horizontal-bar"` (alias `"hbar"`), `"line"`, `"area"`, `"donut"`, `"pie"` (alias of `donut`), `"scatter"` (alias `"bubble"`), `"stacked-bar"` (alias `"stacked"`), `"heatmap"`, `"funnel"`, `"gauge"`, `"sparkline"`, plus `"custom"
|
|
146
|
-
|
|
147
|
-
**Logic versus styling.** `filter`, `param`, `bind`, `dateRange`, `cascade` are headless logic with zero imposed styling - you own the markup. A standard `graphit.graph` type, `table`, `kpi`, `presentation` render a fixed house style. Two trade-offs to surface to the user: a control persists to saved views ONLY when registered with `graphit.filter` / `param` / `dateRange` (a hand-rolled `<select>` will not save), and a standard chart type cannot be deeply restyled - for a custom look use `graphit.graph(el, {type:'custom', draw})` (see `chart-patterns.md`) or hand-draw SVG/CSS, still fetching data via `graphit.resolve`.
|
|
151
|
+
**Standard `graphit.graph` types (set `config.type`):** `"bar"`, `"horizontal-bar"` (alias `"hbar"`), `"line"`, `"area"`, `"donut"`, `"pie"` (alias of `donut`), `"scatter"` (alias `"bubble"`), `"stacked-bar"` (alias `"stacked"`), `"heatmap"`, `"funnel"`, `"gauge"`, `"sparkline"`, plus `"custom"`. Full per-type config (axes, dual axis, `valueFormat`, the non-scaling percent rule, the custom `ctx` contract, hand-rolled shapes) lives in `chart-patterns.md`. Saved org templates register as types too.
|
|
148
152
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
```html
|
|
152
|
-
<div data-graphit-id="spend-by-source"
|
|
153
|
-
data-graphit-label="Ad Spend by Source"
|
|
154
|
-
data-graphit-sql="SELECT {{dim:MEDIA_SOURCE_DIMENSION}} AS source, {{metric:TOTAL_AD_SPEND}} AS spend FROM MARKETING_UA_DS GROUP BY source ORDER BY spend DESC"
|
|
155
|
-
data-graphit-ds="MARKETING_UA_DS">
|
|
156
|
-
<div id="spend-chart" class="gh-loading">
|
|
157
|
-
<!-- gh-loading-overlay spinner from the First-paint section -->
|
|
158
|
-
</div>
|
|
159
|
-
</div>
|
|
160
|
-
<script>
|
|
161
|
-
(async function() {
|
|
162
|
-
var r = await graphit.resolve({
|
|
163
|
-
sql: "SELECT MEDIA_SOURCE, SUM(APPSFLYER_COST) AS spend FROM MARKETING_UA_DS GROUP BY MEDIA_SOURCE ORDER BY spend DESC",
|
|
164
|
-
dataSourceId: "MARKETING_UA_DS",
|
|
165
|
-
target: "#spend-chart"
|
|
166
|
-
});
|
|
167
|
-
graphit.graph("#spend-chart", {
|
|
168
|
-
type: "bar", data: r.data, x: "MEDIA_SOURCE", y: "spend",
|
|
169
|
-
title: "Ad Spend by Source", valueFormat: "currency"
|
|
170
|
-
});
|
|
171
|
-
})();
|
|
172
|
-
</script>
|
|
173
|
-
```
|
|
153
|
+
**Logic versus styling.** `filter`, `param`, `bind`, `dateRange`, `cascade` are headless - you own the markup. `graphit.graph` types, `table`, `kpi`, `presentation` render a fixed house style. Surface two trade-offs to the user: a control persists to saved views ONLY when registered with `graphit.filter` / `param` / `dateRange` (a hand-rolled `<select>` will not save), and a standard chart type cannot be deeply restyled - for a custom look use `type:'custom'` or hand-draw SVG/CSS, still fetching via `graphit.resolve`.
|