From fff7c5afc5d274c048ecb1143cc4d6051d079799 Mon Sep 17 00:00:00 2001 From: Aleksandr Lesnenko Date: Fri, 3 Jul 2026 19:19:29 -0400 Subject: [PATCH] reinforce 24 col dashboard grid in skills --- skill-data/core/SKILL.md | 2 +- skill-data/dashboard/SKILL.md | 29 ++++++++++++++++++++++++++--- skill-data/data-workflow/SKILL.md | 28 ++++++++++++++-------------- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/skill-data/core/SKILL.md b/skill-data/core/SKILL.md index dbda329..44e32e4 100644 --- a/skill-data/core/SKILL.md +++ b/skill-data/core/SKILL.md @@ -123,7 +123,7 @@ Routine verb shapes (list / get / create / update), every flag, and output schem - **field has no `list`.** Fields are per-table — get them via `table get --include fields`. Never enumerate fields across a whole db (context blow-up). `field summary` is live cardinality `{field_id, count, distincts}`; `field values` is the cached distinct set (`has_more_values: true` ⇒ truncated cache). `field update` patches metadata only (`base_type` isn't editable) — this is where you set a column's `semantic_type` or foreign-key target. - **upload (CSV → tables).** `upload csv --file ` creates a new table + model (prints `{model_id, table_id}`); `upload append ` / `upload replace --file ` add to / overwrite a table **previously created by upload** (columns must match). The destination db+schema is admin-configured, not per-call — check with `mb setting get uploads-settings --json` (`db_id: null` ⇒ uploads off/unconfigured; needs admin to read). `--collection ` only sets the model's collection. Max 50 MB. Errors: **"The uploads database is not configured."** = no db has uploads enabled; **"Uploads are not enabled."** = the append/replace target isn't an uploaded table. - **card.** `dataset_query` is the **flat** `mbql/query` value, not a legacy `{type:"query",query:…}` envelope (→ `mbql`). `--export-format csv|xlsx` streams the raw export (pipe to a file), bypassing the JSON envelope. `archive` is the only delete; unarchive with `update --body '{"archived":false}'`. `visualization_settings` keys are scoped by `display` and aren't pre-flighted — see `visualization`. -- **dashboard.** Dashcards round-trip through `PUT /api/dashboard/:id` (no per-dashcard endpoint): `update-dashcard ` patches one safely; `update --body '{"dashcards":[…]}'` replaces the whole set (omitted ids are deleted server-side; negative ids for new cards). `create` accepts the **same** `dashcards` array in its initial body — lay out the whole dashboard in one call: negative ids for new cards, and `card_id:null` plus a `visualization_settings.virtual_card` block (`{display:"text"|"heading"|"link"|…}`) for non-question cards. `create`/`update` pre-flight every positive `card_id` and exit **2** with `{ok:false,errors:[…]}` on a bad ref (non-bypassable). `dashboard get ` (or `--full`) hydrates dashcards/tabs; `list` omits them. **The grid is 24 columns wide:** each dashcard's `{col, row, size_x, size_y}` is in grid units — **full-width is `size_x: 24`** (`size_x: 12` is half a row, the usual cause of a card filling only half the width). Keep `col + size_x ≤ 24`, start a full-width stack's `col` at 0, and don't overlap (the server stores collisions as sent — no auto-fix). +- **dashboard.** Dashcards round-trip through `PUT /api/dashboard/:id` (no per-dashcard endpoint): `update-dashcard ` patches one safely; `update --body '{"dashcards":[…]}'` replaces the whole set (omitted ids are deleted server-side; negative ids for new cards). `create` accepts the **same** `dashcards` array in its initial body — lay out the whole dashboard in one call: negative ids for new cards, and `card_id:null` plus a `visualization_settings.virtual_card` block (`{display:"text"|"heading"|"link"|…}`) for non-question cards. `create`/`update` pre-flight every positive `card_id` and exit **2** with `{ok:false,errors:[…]}` on a bad ref (non-bypassable). `dashboard get ` (or `--full`) hydrates dashcards/tabs; `list` omits them. **The grid is 24 columns wide:** each dashcard's `{col, row, size_x, size_y}` is in grid units — **full-width is `size_x: 24`** (`size_x: 12` is half a row, the usual cause of a card filling only half the width). Keep `col + size_x ≤ 24`, start a full-width stack's `col` at 0, and don't overlap (the server stores collisions as sent — no auto-fix). Layout patterns and per-chart default sizes → the `dashboard` skill; load it before composing any `dashcards` array. - **dashboard parameters (filters).** A dashboard's `parameters` array holds its filter widgets; they're part of the dashboard record, so read them with `dashboard get --fields parameters --json` (no separate verb). **Editing replaces the _whole_ array** (like dashcards), so it's a read-modify-write loop: pull the current set with `dashboard get --fields parameters --json`, add/change entries, and send the full array back via `dashboard update --body '{"parameters":[…]}'` (or supply it in `create`). Omitting a parameter deletes it. Each parameter is `{id, type, …}`. **`id` is a descriptive slug-like string you pick (e.g. `order_status`), unique within this dashboard — Metabase stores any non-blank string verbatim. Do NOT invent a random/opaque id by guessing; reuse the `slug`. If you genuinely need an opaque id, mint one with `mb uuid` — never fabricate one.** `type` is a **closed enum**; an unlisted value is a hard parse error that echoes the full allowed set back to you: string ops `string/=` `string/!=` `string/contains` `string/does-not-contain` `string/starts-with` `string/ends-with`; number ops `number/=` `number/!=` `number/between` `number/>=` `number/<=`; date `date/single` `date/range` `date/relative` `date/month-year` `date/quarter-year` `date/all-options`; plus `category`, `id`, `boolean/=`, `temporal-unit`, and bare `number`/`text`/`date`/`boolean`. A parameter only filters a card once it is **mapped**: each dashcard's `parameter_mappings` is `[{parameter_id, target}]` where `parameter_id` must match a parameter's `id` exactly, and `target` is `["dimension", ["field", , null]]` for an MBQL card column, `["dimension", ["template-tag", ""]]` for a native field-filter tag, or `["variable", ["template-tag", ""]]` for a native raw-value tag. Populate a dropdown with `values_source_type`: `"static-list"` + `values_source_config.values`, or `"card"` + `{card_id, value_field, label_field}`; omit it to pull live distinct values from the mapped field. `dashboard parameter-values [--query ]` fetches those selectable values (`{values, has_more_values}`); `--query` is a case-insensitive substring search. - **snippet `--archived` is a swap, not a union** — list returns _either_ active _or_ archived rows, never both. (Same for `--filter archived` on dashboard/collection.) - **segment / measure.** `update` and `archive` require a non-blank `revision_message` (audit-logged); the CLI does not synthesize it on `update`. `archive` defaults to `"Archived via mb CLI"` — override with `--revision-message`. `definition` is a flat MBQL clause (→ `mbql`): segment = a filter, measure = exactly one aggregation. diff --git a/skill-data/dashboard/SKILL.md b/skill-data/dashboard/SKILL.md index e0ecb6f..eb88e8e 100644 --- a/skill-data/dashboard/SKILL.md +++ b/skill-data/dashboard/SKILL.md @@ -1,17 +1,39 @@ --- name: dashboard -description: Build interactive Metabase dashboards via the `mb` CLI — turn cards into a filterable, cross-linked app. Covers wiring filters to cards (parameters + parameter_mappings), the field-filter vs. raw-variable target grammar, linked/cascading filters and their foreign-key requirement, cross-filtering (click a chart to filter the rest), click-through to a question/dashboard/URL, multi-series overlays, and tabs. Grid math, the parameter type enum, and whole-array replace semantics live in `core`. Triggers — "wire a filter to these cards", "make a filter cascade", "click a bar to filter the other charts", "add a dashboard tab", "add a second series", "make clicking a row open another dashboard", "why isn't my filter showing". +description: Build Metabase dashboards via the `mb` CLI — lay out dashcards on the 24-column grid (`{col,row,size_x,size_y}` math, per-chart default sizes) and turn cards into a filterable, cross-linked app. Covers wiring filters to cards (parameters + parameter_mappings), the field-filter vs. raw-variable target grammar, linked/cascading filters and their foreign-key requirement, cross-filtering, click-through, multi-series overlays, and tabs. The parameter type enum and whole-array replace semantics live in `core`. Triggers — "build a dashboard from these cards", "my dashboard is squished into half the width", "wire a filter to these cards", "make a filter cascade", "click a bar to filter the other charts", "add a dashboard tab", "add a second series", "why isn't my filter showing". allowed-tools: Read, Write, Edit, Bash, AskUserQuestion --- # Dashboard -A dashboard starts as cards on a grid; it becomes an **app** when filters drive the cards, charts cross-filter each other, and clicks navigate. This skill is that interactive layer. +A dashboard starts as cards on a grid; it becomes an **app** when filters drive the cards, charts cross-filter each other, and clicks navigate. This skill owns both: the grid layout and the interactive layer. -**`core` owns the mechanics** — the 24-column grid and `{col,row,size_x,size_y}` math, the whole-array replace semantics (editing `dashcards` or `parameters` replaces the entire set; omitted entries are deleted; new cards use negative ids), `update-dashcard` for a single safe patch vs. `update --body` for a full replace, the closed `parameter.type` enum, and the `parameter-values` verb. Read it first (`mb skills get core`). **`visualization`** owns each card's chart and the full `click_behavior` key catalog. This file assumes both and focuses on wiring. +**`core` owns the transport mechanics** — the whole-array replace semantics (editing `dashcards` or `parameters` replaces the entire set; omitted entries are deleted; new cards use negative ids), `update-dashcard` for a single safe patch vs. `update --body` for a full replace, the closed `parameter.type` enum, and the `parameter-values` verb. Read it first (`mb skills get core`). **`visualization`** owns each card's chart and the full `click_behavior` key catalog. Inspect before you wire: `mb dashboard get --json` hydrates `parameters`, `dashcards`, and `tabs`; `mb dashboard cards ` lists just the dashcards. +## Layout: the grid is 24 columns — not 12 + +Every dashcard carries `{col, row, size_x, size_y}` in grid units: `col` is 0-indexed from the left edge, `row` grows downward, and `col + size_x ≤ 24`. **Full-width is `size_x: 24` — Metabase's per-chart _default_ width of 12 is half a row.** A layout authored on the usual 12-column web-grid assumption crams the whole dashboard into the left half of the viewport. The server stores whatever geometry you send — overlaps and gaps included, no auto-fix. + +Default sizes (w×h): `scalar`/`smartscalar` 6×3, `pie` 12×8, `table`/`pivot`/`object` 12×9, `waterfall` 14×6, `sankey` 16×10, `heading` 24×1, `text` 12×3, every other chart 12×6. + +The standard shape — a KPI row of scalars across the full 24, charts in halves or thirds below, wide tables full-width: + +```jsonc +"dashcards": [ + { "id": -1, "card_id": 101, "col": 0, "row": 0, "size_x": 6, "size_y": 3 }, // 4 KPIs × 6 = 24 + { "id": -2, "card_id": 102, "col": 6, "row": 0, "size_x": 6, "size_y": 3 }, + { "id": -3, "card_id": 103, "col": 12, "row": 0, "size_x": 6, "size_y": 3 }, + { "id": -4, "card_id": 104, "col": 18, "row": 0, "size_x": 6, "size_y": 3 }, + { "id": -5, "card_id": 105, "col": 0, "row": 3, "size_x": 12, "size_y": 6 }, // two halves + { "id": -6, "card_id": 106, "col": 12, "row": 3, "size_x": 12, "size_y": 6 }, + { "id": -7, "card_id": 107, "col": 0, "row": 9, "size_x": 24, "size_y": 9 } // full-width table +] +``` + +**Sanity-check before sending:** rows should fill to 24 and at least one card must end at `col + size_x = 24`. If nothing in the array crosses column 12, you've authored a 12-column layout — double every width. + ## The wiring loop: a filter is a parameter + a mapping per card A dashboard filter is one entry in the dashboard's `parameters` array **plus** a `parameter_mappings` entry on every dashcard it should control. A parameter with no mapping is an inert widget — the most common "my filter does nothing" cause. @@ -101,6 +123,7 @@ Set the **driver** chart's whole-card click behavior to `crossfilter`, mapping t ## Don't +- Don't lay out on a 12-column assumption — full-width is `size_x: 24`; a layout where no card crosses column 12 renders in the left half of the viewport. - Don't declare a parameter and forget the per-card `parameter_mappings` — the widget won't filter anything. - Don't expect a linked filter to work off a model/question join, a custom column, or with a static/card value source — it needs a metadata FK and a live source. - Don't hand-author a complex `click_behavior` — copy a UI-built one. diff --git a/skill-data/data-workflow/SKILL.md b/skill-data/data-workflow/SKILL.md index f58e5d7..88ed003 100644 --- a/skill-data/data-workflow/SKILL.md +++ b/skill-data/data-workflow/SKILL.md @@ -10,14 +10,14 @@ The front door for turning a raw database into clean tables, reusable definition A data project moves through stages. A user can start at any of them — detect where their data already is, don't assume. -| Stage | What it does | Where the method lives | -| --------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| **Build clean tables** | Raw, normalized source DB → a small set of wide, clean, analysis-ready tables (built as transforms) | `references/building-clean-tables.md` | -| **Define reusable metrics** | Clean tables → shared segments (saved filters), measures (saved calculations), metrics (official numbers) | `references/reusable-definitions.md` | -| **Answer questions** | Clean tables → a trustworthy plain-language written answer | `references/answering-questions.md` | -| **Build dashboards** | Clean tables / definitions → charts and dashboards people look at | the `visualization` skill (charts) + `dashboard` skill (wiring filters, interactivity) | +| Stage | What it does | Where the method lives | +| --------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| **Build clean tables** | Raw, normalized source DB → a small set of wide, clean, analysis-ready tables (built as transforms) | `references/building-clean-tables.md` | +| **Define reusable metrics** | Clean tables → shared segments (saved filters), measures (saved calculations), metrics (official numbers) | `references/reusable-definitions.md` | +| **Answer questions** | Clean tables → a trustworthy plain-language written answer | `references/answering-questions.md` | +| **Build dashboards** | Clean tables / definitions → charts and dashboards people look at | the `visualization` skill (charts) + `dashboard` skill (grid layout, filters, interactivity) | -The first three methods are references in this skill — read **only the one the current stage needs**: run `mb skills path data-workflow` and Read `references/.md`. (`mb skills get data-workflow --full` appends all three at once — heavier; prefer the single Read.) "Build dashboards" lives in two standalone skills — `visualization` for authoring each chart, `dashboard` for wiring filters and interactivity across the dashboard — both CLI capabilities in their own right. +The first three methods are references in this skill — read **only the one the current stage needs**: run `mb skills path data-workflow` and Read `references/.md`. (`mb skills get data-workflow --full` appends all three at once — heavier; prefer the single Read.) "Build dashboards" lives in two standalone skills — `visualization` for authoring each chart, `dashboard` for laying cards out on the grid and wiring filters and interactivity — both CLI capabilities in their own right. Load `dashboard` before composing any dashboard, even a plain no-filter layout: the grid geometry lives there. CLI mechanics come from the reference skills, not from here: `mb skills get core` (auth, inspection, the `field`/`table`/`library`/`segment`/`measure` verbs), `mbql` (query and definition bodies), `transform` (creating/running transforms). This skill owns the _judgment_ — which tables, which definitions, what to keep — and the conventions below. @@ -96,13 +96,13 @@ Don't make the user name a _stage_ — but do find out _where their data lives_ **Read the shape to pick a stage.** Raw, normalized, SaaS-synced tables (many tables, coded columns, `*_field`/`*_choice` lookups)? → **build clean tables** first. Already wide, clean, human-readable ones? Then it depends on the goal: -| What the user wants / what's there | Stage | -| -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -| "Clean up / flatten / make sense of" raw, normalized data; no clean tables yet | Build clean tables → `references/building-clean-tables.md` | -| Clean tables exist; "make this reusable", "define active customers / revenue / MRR officially" | Define reusable metrics → `references/reusable-definitions.md` | -| Clean tables exist; "answer this question", "who registered", "analyze / report on X" (wants a written answer) | Answer questions → `references/answering-questions.md` | -| Clean tables (and maybe definitions) exist; "chart this", "build a dashboard", "show me X over time" | Build dashboards → `visualization` (charts) + `dashboard` (interactivity) | -| "Do the whole thing" / "set up analytics for X" from raw data | Start at build-clean-tables, then continue down the stages | +| What the user wants / what's there | Stage | +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | +| "Clean up / flatten / make sense of" raw, normalized data; no clean tables yet | Build clean tables → `references/building-clean-tables.md` | +| Clean tables exist; "make this reusable", "define active customers / revenue / MRR officially" | Define reusable metrics → `references/reusable-definitions.md` | +| Clean tables exist; "answer this question", "who registered", "analyze / report on X" (wants a written answer) | Answer questions → `references/answering-questions.md` | +| Clean tables (and maybe definitions) exist; "chart this", "build a dashboard", "show me X over time" | Build dashboards → `visualization` (charts) + `dashboard` (layout, interactivity) | +| "Do the whole thing" / "set up analytics for X" from raw data | Start at build-clean-tables, then continue down the stages | **Answer and dashboard are alternative endpoints, not a sequence.** Once tables are clean (and maybe defined), answering in prose and building a dashboard are two different things you can do with the data — route to whichever the goal calls for; neither has to precede the other.