From c466db85fee34779086b0c9ad727051ef141713f Mon Sep 17 00:00:00 2001 From: debabsah Date: Sun, 26 Jul 2026 15:25:52 -0700 Subject: [PATCH 1/2] docs: product-writing audit of README and FEATURES, plus two doc-truth tests A pass over the user-facing copy against the rules in PRODUCT-WRITING.md, and over the two claims that had silently gone stale. Two factual errors, both previously reported as fixed: - FEATURES.md said the MCP server exposes "Six tools". It exposes ten. This is v2 roadmap item 20, corrected in the README and VERIFICATION.md but never in FEATURES.md. - The CLI Verbs table was missing `brief`, `advise`, `redesign` and `calibrate`. That is v2 roadmap item 21, which the v2 page claims landed and which never landed at all. Both are now enforced instead of maintained by hand. tests/test_docs.py parses add_parser calls out of cli.py and asserts the verb table matches in both directions, and counts @mcp.tool() decorators against every "N tools" claim in the prose. DESIGN-BRAIN-V2.md's status line is corrected from 36 of 37 to 35, including the note that the first correction was itself wrong because nobody checked item 21. README restructured around what the tool does rather than what you must learn to do. The hero named the spec file as the thing you write, which put the format's learning curve in the first ten words; it now leads with the round trip and names the command line, three verbs (builds, updates, decompiles) so create-only is not a possible reading, and the AI as a closing beat rather than a premise. Decompile moved onto the first screen, since "I already have 200 dashboards" is the objection that disqualifies a dashboards-as-code tool. `chartwright plan` and its non-zero exit are now named where the CI story lives. COMPARISON.md is linked for the first time; it was unreachable from the front door. Also: the word "design" had two meanings (the spec's expressive surface, and visual quality), and every page in docs/ except the v2 review record is now reachable from the README. Editorial pass across the user-facing docs: punctuation normalised, and several sentences tightened where a clause was doing two jobs. Two of those rewrites exposed rule-4 violations the punctuation had been hiding: "no fork of the rulebook" and "zero extra API calls" both defined a capability by what does not happen. The rule-table cells in DESIGN-BRAIN.md are generated, so that is fixed in gen_rule_table.py rather than in its output; fixing the output by hand would be undone by the next --write. DESIGN-BRAIN-V2.md is left as-is, because it is a verbatim record of review findings and editing quoted findings would make the record less faithful. 256 tests pass under both pytest invocations; params_drift clean against 4.1.4, 5.0.0 and 6.1.0; rule table matches the registry. --- README.md | 103 ++++++++++++++---------- docs/DESIGN-BRAIN-V2.md | 27 +++++-- docs/DESIGN-BRAIN.md | 168 ++++++++++++++++++++-------------------- docs/FEATURES.md | 20 +++-- docs/VERIFICATION.md | 4 +- tests/test_docs.py | 32 ++++++++ tools/gen_rule_table.py | 6 +- 7 files changed, 215 insertions(+), 145 deletions(-) diff --git a/README.md b/README.md index 1a274ae..8c5bca2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # Chartwright -A dashboard compiler for Apache Superset. Describe the dashboard once, in a -small text file called a spec, and the `chartwright` command line creates it, -verifies it, and keeps it that way. +A dashboard compiler for Apache Superset. The `chartwright` command line builds +a dashboard from a small deterministic file, updates it from that file later, +and decompiles an existing dashboard back into one. Every dataset, column, and +metric is checked against your own Superset first. An AI can write the file for +you, or revise it, from a request in plain words. ![An operations dashboard over NYC yellow-taxi data: KPI cards with unit subtitles, daily trend charts, an hour-by-weekday demand heatmap, a payment donut, borough and zone rankings, and a trip-distance histogram, behind a three-filter bar](https://raw.githubusercontent.com/debabsah/chartwright/main/docs/images/nyc-taxi-operations.png) @@ -12,14 +14,20 @@ charts, three filters, one `chartwright apply`. An AI wrote [the request and rebuild steps](https://github.com/debabsah/chartwright/blob/main/examples/README.md).* A dashboard that lives in a file gets the workflow code already has: review it -in a pull request, rebuild it identically, diff it against what is live, and -bring it back after a bad change. Write the spec yourself or ask an AI for one; -either way, every dataset, column, and metric the spec names is confirmed to -exist before anything is built, and every chart is checked to load with data -after. +in a pull request, rebuild it identically, and bring it back after a bad +change. `chartwright plan` diffs the file against the live dashboard and exits +non-zero when they differ, so drift fails a CI check. `chartwright decompile` +covers the other direction, and lists anything it could not carry over. + +Write the file yourself, or describe the dashboard you want and have an AI +write it. The same checks run either way. After the build, each chart's query +runs once to prove it shows data, and `chartwright advise` reviews the layout +for readability. ## What a spec looks like +The file is called a spec: + ```json { "spec_version": "1", @@ -49,6 +57,22 @@ builds a KPI card above a line chart, and finishes by running each chart's query once to prove it shows data. A misspelled column or a missing table is a clear error naming the problem, before anything is created. +## Creating dashboards with AI + +The spec is typed, and every reference in it is checked before anything is +created. Whatever a model proposes has to survive the same verification your +own specs do. + +- **Claude Code skill**: from a clone of this repo, run + `python install-skill.py`; the model writes the spec from your request, and + the tool verifies and builds it. +- **MCP server**: `chartwright-mcp` (installed with `pip install "chartwright[mcp]"`) + exposes ten tools covering the whole lifecycle, usable from any MCP client. +- **Open contract**: `chartwright schema` prints the spec's JSON Schema, so any LLM or + tool can generate valid specs. +- **Guardrails**: the AI proposes; the tool verifies, using your own Superset + login. Verification reads names (datasets, columns, metrics), not rows. + ## Quick start ```bash @@ -90,11 +114,11 @@ as-is. dashboard into a spec; `chartwright plan` shows what differs between the spec and the live dashboard, ready as a CI gate; `chartwright compile` builds the import bundle offline, no server needed. -- **Safety built in**: every apply backs up the previous state first; a failed - apply restores it automatically; the tool only ever overwrites dashboards it - created, and a hand-built dashboard is adopted by decompiling it into a spec +- **Recoverable by default**: every apply backs up the previous state first, + and a failed apply restores it. Dashboards Chartwright did not create are + never overwritten; to bring a hand-built one under a spec, decompile it first. -- **The full design surface**: 14 chart types, metrics as you write them, +- **What a spec can express**: 14 chart types, metrics as you write them, per-chart filters, a native filter bar, tabs, markdown notes, and layouts you can draw as ASCII sketches. - **Environment promotion**: specs name their data (connection, schema, @@ -102,25 +126,14 @@ as-is. Every capability, with the CLI verb reference: [docs/FEATURES.md](https://github.com/debabsah/chartwright/blob/main/docs/FEATURES.md). -## Creating dashboards with AI - -- **Claude Code skill**: from a clone of this repo, `python install-skill.py`, - then ask for a dashboard in plain words; the AI writes the spec, and the - tool verifies and builds it. -- **MCP server**: `chartwright-mcp` (installed with `pip install "chartwright[mcp]"`) - exposes ten tools covering the whole lifecycle, usable from any MCP client. -- **Open contract**: `chartwright schema` prints the spec's JSON Schema, so any LLM or - tool can generate valid specs. -- **Guardrails**: the AI proposes; the tool verifies, using your own Superset - login. Verification reads names (datasets, columns, metrics), not rows. - ## The design brain -Correct-by-construction is table stakes; the design brain makes dashboards -*read well*. A toggleable BI/UX intelligence layer — audience-aware size and -scroll budgets, chart-choice limits, layout composition — split into a brief -the AI reads before authoring and a deterministic critic that reviews the -result: +A spec can import perfectly and still produce a dashboard nobody can read: a +pie squeezed into two columns, a KPI buried under three tables, a bar chart +with forty category labels Superset silently drops. The design brain covers +size and scroll budgets, chart-choice limits, and layout composition. It +arrives as a brief the AI reads before authoring, and a critic that reviews +the result: ```bash chartwright brief --audience executive # the guidance, before writing a spec @@ -128,11 +141,11 @@ chartwright advise spec.json --fix # the critique, with safe auto-rep chartwright redesign old-dash --profile prod # audit + repair a live dashboard ``` -Rules key on stable ids you can suppress per chart in the spec, thresholds -tune per audience or per deployment (`design.yaml`), and the brain learns -your house heights from the sizes you polish by hand (`chartwright -calibrate`). Off is really off: `--design off` restores byte-identical -behavior. The full rulebook and architecture: +You stay in charge of it: suppress any rule for one chart in the spec, tune +the thresholds per audience or per deployment in `design.yaml`, and feed the +heights you drag in the UI back into the recommendations with `chartwright +calibrate`. Pass `--design off` and the output is byte-identical to a build +that never had it. The full rulebook and architecture: [docs/DESIGN-BRAIN.md](https://github.com/debabsah/chartwright/blob/main/docs/DESIGN-BRAIN.md). ## Drawing layouts as text @@ -158,12 +171,15 @@ Every push and every pull request runs the full offline suite on Linux and Windows, plus the full pipeline (apply, lifecycle soak, stale-tab adversary, fault injection) against real Superset 4.1.4, 5.0.0, and 6.1.0 containers. Chart options are checked against Superset's own source for every supported -version, so a Superset change is caught in our tests before it reaches your -dashboards. +version, so a Superset change surfaces here before it reaches your dashboards. + +Full evidence: [docs/VERIFICATION.md](https://github.com/debabsah/chartwright/blob/main/docs/VERIFICATION.md). -Full evidence: [docs/VERIFICATION.md](https://github.com/debabsah/chartwright/blob/main/docs/VERIFICATION.md). Source citations -for every Superset behavior the tool relies on: -[docs/CONTRACTS.md](https://github.com/debabsah/chartwright/blob/main/docs/CONTRACTS.md). +[docs/CONTRACTS.md](https://github.com/debabsah/chartwright/blob/main/docs/CONTRACTS.md) records how Superset itself behaves: what its +importer accepts, how dashboard settings are stored, what its chart plugins +expect. Every behavior is cited to its source line at 4.1.4, 5.0.0, and 6.1.0. +Release tags are immutable, so any line of it can be checked against a fresh +checkout of that tag. ## Documentation @@ -171,10 +187,15 @@ for every Superset behavior the tool relies on: reference - [docs/LAYOUT-GUIDE.md](https://github.com/debabsah/chartwright/blob/main/docs/LAYOUT-GUIDE.md): drawing layouts as text, every rule illustrated +- [docs/DESIGN-BRAIN.md](https://github.com/debabsah/chartwright/blob/main/docs/DESIGN-BRAIN.md): the design rulebook, every + rule and threshold, and how to tune or switch them off - [docs/VERIFICATION.md](https://github.com/debabsah/chartwright/blob/main/docs/VERIFICATION.md): what is tested, what it caught, and how to reproduce it -- [docs/CONTRACTS.md](https://github.com/debabsah/chartwright/blob/main/docs/CONTRACTS.md): the Superset behaviors the tool - depends on, cited to source at each supported version +- [docs/CONTRACTS.md](https://github.com/debabsah/chartwright/blob/main/docs/CONTRACTS.md): how Superset behaves on import, + on dashboard storage, and in chart options, cited to its source line at + each supported release +- [docs/COMPARISON.md](https://github.com/debabsah/chartwright/blob/main/docs/COMPARISON.md): what Chartwright, preset-cli, + sup, and the Terraform provider each automate, so you can pick by the job --- diff --git a/docs/DESIGN-BRAIN-V2.md b/docs/DESIGN-BRAIN-V2.md index 2e2f717..be4d776 100644 --- a/docs/DESIGN-BRAIN-V2.md +++ b/docs/DESIGN-BRAIN-V2.md @@ -1,18 +1,29 @@ # Design Brain v2: Review Findings and Roadmap -> **Status: EXECUTED, with one item re-opened.** 36 of the 37 roadmap items +> **Status: EXECUTED, with two items re-opened.** 35 of the 37 roadmap items > below landed in the four `design brain v2 batch A-D` commits (design_brain > version "2"); §15 of [DESIGN-BRAIN.md](DESIGN-BRAIN.md) records the few > places execution deliberately deviated from a proposal. This page remains > the review record. > -> **Item 20 (stale hard-coded counts) only half-landed** — 'six MCP tools' -> was corrected, '125 tests' was not, and this page asserted completion while -> incomplete. Closed afterwards by moving the counts into a single row in -> [VERIFICATION.md](VERIFICATION.md) that `tests/test_docs.py` now enforces, -> so the class of drift cannot recur. Recorded rather than quietly amended: -> a status line that overstates is the same defect as a rule that passes -> silently, which is what theme 1 below is about. +> **Item 20 (stale hard-coded counts) half-landed.** '125 tests' survived in +> five places, and 'six MCP tools' was corrected in the README but not in +> FEATURES.md, where it stayed wrong while the server grew to ten. +> +> **Item 21 (FEATURES.md CLI Verbs table missing the design-brain verbs) did +> not land at all.** `brief`, `advise`, `redesign` and `calibrate` were absent +> from that table until the post-review sweep. +> +> Both are closed now, and closed in a way that cannot silently recur: +> `tests/test_docs.py` asserts the documented test counts match what pytest +> collects, that the CLI Verbs table covers every registered subcommand, and +> that the MCP tool count in prose matches the number of registered tools. +> +> Recorded rather than quietly amended, and note the sequence: the first +> correction to this line claimed 36 of 37 after finding item 20, and was +> itself wrong because nobody had checked item 21. A status line that +> overstates is the same defect as a rule that passes silently, which is what +> theme 1 below is about. > > Originally produced 2026-07-16 by a > multi-lens agent review of the shipped design brain (docs/DESIGN-BRAIN.md). diff --git a/docs/DESIGN-BRAIN.md b/docs/DESIGN-BRAIN.md index e5381cd..52c706d 100644 --- a/docs/DESIGN-BRAIN.md +++ b/docs/DESIGN-BRAIN.md @@ -1,15 +1,15 @@ # The Design Brain -> **Status: SHIPPED — design brain 3.** This page is both the design and the +> **Status: SHIPPED, design brain 3.** This page is both the design and the > reference for the implementation in `chartwright/design/`. The decision log > at the bottom records every judgment call made without a review gate; §15 > records where the implementation deliberately deviates from the design > text. A verified multi-lens review of the first implementation produced the > ranked roadmap in [DESIGN-BRAIN-V2.md](DESIGN-BRAIN-V2.md) (36 of 37 items -> in the v2 batch, the last closed afterwards — see that page's status note); +> in the v2 batch, the last closed afterwards (see that page's status note); > a later full review of core + brain produced the version-3 changes recorded > in §15.11 onward. §7's rule table is now genuinely generated -> (`tools/gen_rule_table.py`, checked by `tests/test_docs.py`) — the v2 claim +> (`tools/gen_rule_table.py`, checked by `tests/test_docs.py`). The v2 claim > that it was pointed at a placeholder snippet, and the table had drifted. > > **Still pending: rendering-quality verification against a live Superset.** @@ -25,13 +25,13 @@ guarantee the dashboard **reads well**. Nothing stops a spec from shipping a pie chart squeezed into 2 of 12 columns, a vertical bar with 40 category labels (Superset silently drops most of them), a KPI buried under three rows of tables, or a 4,000-pixel scroll for an executive audience. Today the only -design knowledge in the system is a prose section in `skill/SKILL.md` — +design knowledge in the system is a prose section in `skill/SKILL.md`, frozen inside one prompt, invisible to the CLI, not versioned as a surface, not toggleable, and not testable. The design brain is that missing layer: a codified, continuously-improvable body of BI/UX design knowledge (Few, Tufte, IBCS, and Superset-specific -rendering facts) that the toolchain can consult, enforce, and explain — and +rendering facts) that the toolchain can consult, enforce, and explain, and that the user can switch off. ## 2. Principles @@ -74,11 +74,11 @@ One knowledge base, two delivery mechanisms, split by who can apply the rule: (skill step 1.5) (skill step 4.5; also in apply/check) ``` -- **Tier G — the brief.** Composition, grouping, narrative flow, when to use +- **Tier G, the brief.** Composition, grouping, narrative flow, when to use tabs, title-as-insight, color restraint: judgments only an intelligence can make. Delivered as a compact markdown brief the skill loads before writing a spec. Continuously improvable by editing guideline files; no code change. -- **Tier L — the critic.** Predicates over the spec (optionally enriched with +- **Tier L, the critic.** Predicates over the spec (optionally enriched with live metadata): minimum readable geometry per chart type, category limits, layout composition, fold budgets. Implemented as a rule registry in Python, each rule tested, each finding actionable, safe subset auto-fixable. @@ -115,14 +115,14 @@ chartwright calibrate [--write] [--min-samples N] [--since 90d] - `advise` (offline by default): evaluates the spec, prints an `AdviceReport` JSON (shape in §10). Exit 0 unless a finding of severity `error` exists, or `--strict` and any `warn` exists. -- `advise --profile P`: adds **data-aware** rules — column type checks and +- `advise --profile P`: adds **data-aware** rules: column type checks and bounded cardinality probes against the live instance (§8). `--no-probe` keeps it to metadata already fetched by resolution (no queries). - `advise --fix`: applies the safe-fix subset in place (same file-rewrite mechanics as `absorb`; formatting normalizes), re-validates, and reports each change as `{rule, chart, set: {field: new}, was: {field: old}}` plus the `written` path. Idempotent: a second `--fix` run is a no-op. -- `brief`: prints the Tier G design brief for the audience — the document the +- `brief`: prints the Tier G design brief for the audience, the document the skill reads before authoring. Compact by contract (a test caps the line count), because it lands in an LLM context window. - `check`/`apply` gain `--design off|warn|strict` (default `warn`): @@ -132,7 +132,7 @@ chartwright calibrate [--write] [--min-samples N] [--since 90d] instance is touched. - `off`: byte-identical to the pre-brain behavior, advice machinery never runs. -- `calibrate`: the learning loop (§13 phase 3) — mines absorb history into +- `calibrate`: the learning loop (§13 phase 3). Mines absorb history into per-audience recommended heights; `--since` is the decay knob. - MCP server: `design_brief`, `advise_spec`, `fix_spec`, `redesign_dashboard` mirror the CLI verbs; `check_spec` carries the advice block. @@ -152,7 +152,7 @@ One additive optional block (models stay `extra="forbid"`): built-in default (`analytical`) applies when both are absent. - `ignore`: rule ids to suppress, dashboard-wide (`rule.id`), per chart (`rule.id@Chart Name`), or per band for findings that name no chart - (`rule.id@tab-Ops-row-1` — the finding's `where`, slugified). Suppressions + (`rule.id@tab-Ops-row-1`, the finding's `where`, slugified). Suppressions are reported in every AdviceReport (`"ignored"`), so silence is always visible; entries whose rule id doesn't exist come back as `unmatched_ignores` instead of silently suppressing nothing. @@ -186,7 +186,7 @@ name, only parameters. Adding an audience is adding a row. ### House style: design.yaml `~/.config/chartwright/design.yaml` (or `$CHARTWRIGHT_DESIGN_DIR/design.yaml`) -overlays the presets for a whole deployment — no fork of the rulebook. Five +overlays the presets for a whole deployment, without forking the rulebook. Five keys, all optional, all validated at load with typed errors: ```yaml @@ -206,12 +206,12 @@ layers; the brief prints the merged values and height autofixes target them. ## 7. The rulebook -Stable ids (`category.slug`) are the public API — `ignore`/`disable` lists +Stable ids (`category.slug`) are the public API: `ignore`/`disable` lists and severity overrides key on them, and renames keep working through the alias table. Severity: **error** = unreadable for any audience; **warn** = below professional quality; **info** = polish nudge. The overlay can override it per deployment. A `sev` of `warn/error` means the rule's DEFAULT is `warn` -but it escalates per finding — worth reading closely, because `ok` is +but it escalates per finding. Worth reading closely, because `ok` is error-driven, so those rules can fail a run while advertising `warn`. "fix" marks the safe-autofix subset (presentation-only, §9). "data" marks rules that only run with a live resolution (`--profile`); @@ -228,55 +228,55 @@ fails when it drifts. | id | sev | fix | data | since | rule | |---|---|---|---|---|---| -| `chart.dupe` | info | — | — | 1 | two charts answering the identical question is redundancy | -| `chart.format-bands` | warn/info | — | — | 2 | conditional-formatting bands must tell one coherent story per metric | -| `chart.funnel-stages` | warn | — | ⚡ | 1 | funnels need 3-8 ordered stages | -| `chart.heatmap-grid` | warn | — | ⚡ | 1 | a heatmap past ~400 cells is unreadable at any size | -| `chart.histogram-bins` | info | — | — | 1 | histograms read best at 10-50 bins | -| `chart.metrics-per-bar` | warn | — | — | 1 | many metrics per category read better as a table | -| `chart.ordinal-order` | info | — | — | 2 | ordinal dimensions (weekday, month) sort alphabetically unless order-encoded | -| `chart.pie-slices` | warn | — | — | 1 | pies stop working past ~7 slices | -| `chart.pivot-columns` | warn | — | ⚡ | 2 | column-dim values x metrics = rendered columns; past ~15 the pivot scrolls sideways | -| `chart.pivot-dims` | warn | — | — | 2 | a pivot past three total dimensions is unreadable nesting | -| `chart.series-limit` | warn | — | ⚡ | 1 | a timeseries with too many grouped series turns to spaghetti | -| `chart.temporal-type` | error | — | ⚡ | 1 | a time axis must point at a temporal column | -| `chart.treemap-depth` | warn | — | — | 1 | treemaps past two grouping levels become unreadable nesting | -| `chart.treemap-vs-bar` | info | — | ⚡ | 2 | a one-level treemap of few categories is a worse bar chart | -| `chart.trend-grain` | info | — | — | 2 | trend tiles at a fine grain over full history draw thousands of points in a small card | -| `chart.vbar-categories` | warn | ✔ | — | 1 | vertical bars drop labels past ~8 categories; rank with horizontal bars | -| `data.grain-vs-range` | warn | — | — | 1 | the time grain should yield a sane number of points for the range | -| `data.row-limit-intent` | info | — | — | 1 | row limits doing design work should be deliberate, not defaults | -| `data.top-n-sort` | warn | — | — | 2 | a limit without an order is a sample, not a ranking | -| `data.unwindowed-history` | warn | — | — | 3 | timeseries charts with no way to bound the window draw ALL history at their grain | -| `filters.count` | warn | — | — | 2 | past ~6 select pickers a filter bar stops being navigable (and each costs a query on load) | -| `filters.duplicate-column` | info | — | — | 2 | two filters on the same column fight each other | -| `filters.range-default` | info | — | — | 2 | a range slider with no default bounds spans the whole domain | -| `filters.select-cardinality` | warn | — | ⚡ | 2 | a select over hundreds of distinct values is an unusable picker | -| `filters.time-default` | info | — | — | 2 | an undefaulted time picker loads the dashboard over ALL history | -| `filters.time-picker` | info | — | — | 1 | time-based dashboards want a time range picker in the filter bar | -| `layout.fold-budget` | warn | — | — | 1 | the dashboard should fit its audience's scroll budget | -| `layout.kpi-band` | warn | — | — | 1 | KPIs get their own band, in readable numbers | -| `layout.kpi-first` | warn | — | — | 1 | summary KPIs belong above detail charts (inverted pyramid) | -| `layout.markdown-height` | info | ✔ | — | 2 | a one-line markdown header doesn't need a chart-sized block | -| `layout.orphan-chart` | info | — | — | 1 | a lone narrow chart in its own row looks unfinished | -| `layout.row-density` | warn/error | — | — | 1 | too many axis charts side by side starves each of width | -| `layout.row-fill` | warn/info | — | — | 1 | a row should fill the 12-column grid | -| `layout.section-headers` | info | — | — | 1 | large flat dashboards need markdown signposts | -| `layout.tab-balance` | info | — | — | 1 | tabs should carry comparable weight | -| `narrative.big-number-format` | info | — | — | 1 | hero numbers deserve a number format | -| `narrative.filtered-title` | info | — | — | 1 | a filtered chart's title should say what it shows | -| `narrative.format-consistency` | info | — | — | 2 | one measure, one number format | -| `narrative.title-style` | info | — | — | 1 | chart titles should share one casing style | -| `size.axis-min-height` | warn | ✔ | — | 1 | axis charts below the audience minimum height flatten and drop labels | +| `chart.dupe` | info | - | - | 1 | two charts answering the identical question is redundancy | +| `chart.format-bands` | warn/info | - | - | 2 | conditional-formatting bands must tell one coherent story per metric | +| `chart.funnel-stages` | warn | - | ⚡ | 1 | funnels need 3-8 ordered stages | +| `chart.heatmap-grid` | warn | - | ⚡ | 1 | a heatmap past ~400 cells is unreadable at any size | +| `chart.histogram-bins` | info | - | - | 1 | histograms read best at 10-50 bins | +| `chart.metrics-per-bar` | warn | - | - | 1 | many metrics per category read better as a table | +| `chart.ordinal-order` | info | - | - | 2 | ordinal dimensions (weekday, month) sort alphabetically unless order-encoded | +| `chart.pie-slices` | warn | - | - | 1 | pies stop working past ~7 slices | +| `chart.pivot-columns` | warn | - | ⚡ | 2 | column-dim values x metrics = rendered columns; past ~15 the pivot scrolls sideways | +| `chart.pivot-dims` | warn | - | - | 2 | a pivot past three total dimensions is unreadable nesting | +| `chart.series-limit` | warn | - | ⚡ | 1 | a timeseries with too many grouped series turns to spaghetti | +| `chart.temporal-type` | error | - | ⚡ | 1 | a time axis must point at a temporal column | +| `chart.treemap-depth` | warn | - | - | 1 | treemaps past two grouping levels become unreadable nesting | +| `chart.treemap-vs-bar` | info | - | ⚡ | 2 | a one-level treemap of few categories is a worse bar chart | +| `chart.trend-grain` | info | - | - | 2 | trend tiles at a fine grain over full history draw thousands of points in a small card | +| `chart.vbar-categories` | warn | ✔ | - | 1 | vertical bars drop labels past ~8 categories; rank with horizontal bars | +| `data.grain-vs-range` | warn | - | - | 1 | the time grain should yield a sane number of points for the range | +| `data.row-limit-intent` | info | - | - | 1 | row limits doing design work should be deliberate, not defaults | +| `data.top-n-sort` | warn | - | - | 2 | a limit without an order is a sample, not a ranking | +| `data.unwindowed-history` | warn | - | - | 3 | timeseries charts with no way to bound the window draw ALL history at their grain | +| `filters.count` | warn | - | - | 2 | past ~6 select pickers a filter bar stops being navigable (and each costs a query on load) | +| `filters.duplicate-column` | info | - | - | 2 | two filters on the same column fight each other | +| `filters.range-default` | info | - | - | 2 | a range slider with no default bounds spans the whole domain | +| `filters.select-cardinality` | warn | - | ⚡ | 2 | a select over hundreds of distinct values is an unusable picker | +| `filters.time-default` | info | - | - | 2 | an undefaulted time picker loads the dashboard over ALL history | +| `filters.time-picker` | info | - | - | 1 | time-based dashboards want a time range picker in the filter bar | +| `layout.fold-budget` | warn | - | - | 1 | the dashboard should fit its audience's scroll budget | +| `layout.kpi-band` | warn | - | - | 1 | KPIs get their own band, in readable numbers | +| `layout.kpi-first` | warn | - | - | 1 | summary KPIs belong above detail charts (inverted pyramid) | +| `layout.markdown-height` | info | ✔ | - | 2 | a one-line markdown header doesn't need a chart-sized block | +| `layout.orphan-chart` | info | - | - | 1 | a lone narrow chart in its own row looks unfinished | +| `layout.row-density` | warn/error | - | - | 1 | too many axis charts side by side starves each of width | +| `layout.row-fill` | warn/info | - | - | 1 | a row should fill the 12-column grid | +| `layout.section-headers` | info | - | - | 1 | large flat dashboards need markdown signposts | +| `layout.tab-balance` | info | - | - | 1 | tabs should carry comparable weight | +| `narrative.big-number-format` | info | - | - | 1 | hero numbers deserve a number format | +| `narrative.filtered-title` | info | - | - | 1 | a filtered chart's title should say what it shows | +| `narrative.format-consistency` | info | - | - | 2 | one measure, one number format | +| `narrative.title-style` | info | - | - | 1 | chart titles should share one casing style | +| `size.axis-min-height` | warn | ✔ | - | 1 | axis charts below the audience minimum height flatten and drop labels | | `size.grid-fit` | warn | ✔ | ⚡ | 2 | table/pivot heights must fit their data-driven row counts (they grow after authoring) | -| `size.hbar-window` | warn | ✔ | — | 1 | horizontal bars need ~0.5 units of height per bar | -| `size.heatmap-geometry` | warn | ✔ | — | 1 | heatmaps need >= 5/12 width (7/12 with many columns) and 6 height | -| `size.kpi-height` | warn | ✔ | — | 1 | big numbers read best at 2-6 units | -| `size.min-width` | warn/error | — | — | 2 | below 3/12 width a chart is unreadable; KPIs need 2/12 | -| `size.pie-geometry` | warn | ✔ | — | 1 | pies need >= 5/12 width and 8 height or the ring shrinks and the legend crowds | -| `size.pivot-window` | warn | — | — | 2 | a pivot's height should show a meaningful share of its row_limit | -| `size.row-harmony` | warn | ✔ | — | 1 | charts sharing a row should share a height (Superset sizes the row to its tallest child) | -| `size.table-window` | warn | — | — | 1 | a table's height should show a meaningful share of its row_limit | +| `size.hbar-window` | warn | ✔ | - | 1 | horizontal bars need ~0.5 units of height per bar | +| `size.heatmap-geometry` | warn | ✔ | - | 1 | heatmaps need >= 5/12 width (7/12 with many columns) and 6 height | +| `size.kpi-height` | warn | ✔ | - | 1 | big numbers read best at 2-6 units | +| `size.min-width` | warn/error | - | - | 2 | below 3/12 width a chart is unreadable; KPIs need 2/12 | +| `size.pie-geometry` | warn | ✔ | - | 1 | pies need >= 5/12 width and 8 height or the ring shrinks and the legend crowds | +| `size.pivot-window` | warn | - | - | 2 | a pivot's height should show a meaningful share of its row_limit | +| `size.row-harmony` | warn | ✔ | - | 1 | charts sharing a row should share a height (Superset sizes the row to its tallest child) | +| `size.table-window` | warn | - | - | 1 | a table's height should show a meaningful share of its row_limit | @@ -288,13 +288,13 @@ is Tier G: it goes in the brief, not the linter. Resolution already fetches full column metadata (`dataset_detail`) and discards everything but names. Phase 2 extends `ResolvedDataset` with -`column_types` (Superset's `type_generic`) and the `is_dttm` set — zero extra -API calls — which powers `chart.temporal-type`. +`column_types` (Superset's `type_generic`) and the `is_dttm` set, at zero extra +API cost, which powers `chart.temporal-type`. Cardinality (`chart.pie-slices` ⚡, `chart.series-limit`, `chart.funnel-stages`, `chart.heatmap-grid`) uses one bounded probe per distinct (dataset, column): a `COUNT` grouped query through `/api/v1/chart/data` (the smoke module's -existing machinery) with `row_limit = threshold + 1` — the rule only needs +existing machinery) with `row_limit = threshold + 1`, because the rule only needs "more than N", never the true count. Probes are cached per run, skipped entirely under `--no-probe`, and never run for `advise` without `--profile`. @@ -309,7 +309,7 @@ entirely under `--no-probe`, and never run for `advise` without `--profile`. - **Idempotent by test:** fixed specs re-advise with zero fixable findings. - Sketch layouts: heights are fixable (an explicit `chart.height` overrides sketch height by existing precedence in `spec.resolved_height`); widths are - not (the drawing is authoritative) — width findings under a sketch include + not (the drawing is authoritative). Width findings under a sketch include a suggested redrawn sketch line for the LLM or human to adopt. - Fractional heights (absorb's signature) suppress sizing rules on that chart: human polish wins (§2.4). @@ -351,9 +351,9 @@ entirely under `--no-probe`, and never run for `advise` without `--profile`. - `fixed` entries disclose the full diff (`set` new values, `was` old); `advise --fix` additionally reports the `written` file path. - `unmatched_ignores` lists ignore/disable entries whose rule id doesn't - exist — a typo'd suppression is surfaced, never a silent no-op. + exist: a typo'd suppression is surfaced, never a silent no-op. - `polished` lists sizing findings withheld because the chart carries a - human-polished (fractional) height — §2.4's deference, made visible. + human-polished (fractional) height, which is §2.4's deference made visible. `ignored` is the user's *explicit* intent; `polished` is the brain's own *inference*, and an inference that silences a rule invisibly reads exactly like the rule having passed. Present only when non-empty. @@ -369,40 +369,40 @@ entirely under `--no-probe`, and never run for `advise` without `--profile`. compose: brain roughs in professional geometry, human drags to taste, absorb records it, brain respects it forever after. - **`decompile`:** running `advise` on a decompiled spec is a **design audit - of any legacy UI-built dashboard** — an emergent feature worth documenting: + of any legacy UI-built dashboard**, an emergent feature worth documenting: `chartwright decompile old-dash -o spec.json && chartwright advise spec.json`. - **`redesign`:** the one-shot form of the above: decompile → data-aware audit → safe geometry fixes → redesigned spec + losses + remaining structural findings. Ownership decides where it lands: tool-born dashboards redesign in place; UI-born ones come back under a `-redesign` slug (title suffixed too) so apply builds the redesign **side by side** - and the original is never overwritten. Structural findings stay findings — + and the original is never overwritten. Structural findings stay findings: the spec author (usually the skill-driven LLM) acts on them before apply. - **`smoke` (issue #1):** table/pivot heights are fixed layout properties - while rendered rows are data-driven — data that grows after authoring hides + while rendered rows are data-driven, and data that grows after authoring hides new rows behind the chart's inner scrollbar with everything looking green. Smoke now compares the rows its query already fetched against the configured height and warns on every apply (`~9 leaf rows (~430px) but height=8 (320px)`); the data-aware `size.grid-fit` rule catches the same - class pre-apply for single-dimension grids. All of it — smoke, - `size.grid-fit`, `size.table-window`, `size.pivot-window` — reads ONE grid + class pre-apply for single-dimension grids. All of it, meaning smoke, + `size.grid-fit`, `size.table-window`, `size.pivot-window`, reads ONE grid model (`grid_units_for_rows` / `grid_rows_visible` in `chartwright/spec.py`), so the offline critic, the data-aware critic, and the apply-time warning cannot give one chart three different verdicts. -- **Chart identity:** no rule may ever autofix a chart `name` — names seed +- **Chart identity:** no rule may ever autofix a chart `name`: names seed uuid5 identity; a rename is a delete+create on the live instance. - **`plan`/golden tests:** advise is pure spec-side analysis; compiled bytes are untouched, golden tests unaffected. - **Skill (`skill/SKILL.md`):** the static "Design rules" section is replaced by two procedure steps: - - *Step 1.5* — brain on (default): run `CW brief --audience ` + - *Step 1.5*, brain on (default): run `CW brief --audience ` and follow it while authoring. Infer audience from the request ("executive scorecard" / "ops monitor" / default analytical). Brain off (user said "no design opinions" / "exactly as I specify"): skip the brief, pass `--design off`. - - *Step 4.5* — after `check` passes: `CW advise --profile

`; + - *Step 4.5*, after `check` passes: `CW advise --profile

`; apply or consciously `ignore` findings (with the user, in the spec's - `design.ignore`) — at most 2 design iterations, then surface remaining + `design.ignore`); at most 2 design iterations, then surface remaining findings verbatim. - Anti-evasion row: *"Advice finding seems wrong → record it in `design.ignore` and tell the user, or report a rule bug; never hand-tune @@ -432,7 +432,7 @@ What actually runs (tests/test_design*.py, test_calibrate.py, test_redesign.py): |---|---| | **1** (next minor) | design/ package, offline rulebook (all non-⚡ rules), presets, `advise`/`brief` CLI, `--fix`, spec `design` block, `apply`/`check --design`, skill rewrite, MCP tools, tests | | **2** | data-aware rules (resolver type enrichment, bounded cardinality probes, `--no-probe`), house-style overlay: `~/.config/chartwright/design.yaml` (org-level parameter overrides, disabled rules, extra Tier G guidance appended to the brief) | -| **3** | calibration loop: mine absorb history and backups for systematic human corrections (e.g. tables consistently dragged from 8 to 11 units) and propose preset updates — the brain learns from every human polish it was told to respect | +| **3** | calibration loop: mine absorb history and backups for systematic human corrections (e.g. tables consistently dragged from 8 to 11 units) and propose preset updates, so the brain learns from every human polish it was told to respect | ## 14. Decision log @@ -483,7 +483,7 @@ Where the shipped code deliberately departs from the design text above: applies stay fast; `check --design` attaches type-aware advice for free (it already resolved); full data-aware advice is `advise --profile`. 3. **`size.axis-min-height` fixes to the audience minimum** (8/6/5 by - audience), not a blanket 8 — a dense operational board shouldn't be + audience), not a blanket 8, because a dense operational board shouldn't be inflated to analytical proportions. 4. **Rule cards are the registry docstrings** (one line per rule, printed in the brief) plus two Tier G guideline files; 27 separate markdown cards @@ -506,7 +506,7 @@ Recorded during the v2 roadmap burn-down: `severity` map rather than a boolean param. 9. **Sketch WYSIWYG resolved as disclosure, not withholding**: height fixes still apply to sketch-drawn charts (explicit heights legitimately override - the drawing — absorb's precedent), and the finding says the drawing goes + the drawing, absorb's precedent), and the finding says the drawing goes stale and how to redraw it. 10. **A per-metric d3 format on table/pivot/timeseries is a spec v-next candidate** (the compiler pins SMART_NUMBER today); the guideline was @@ -523,7 +523,7 @@ Recorded during the post-merge review burn-down: `chartwright/spec.py`; the rules and smoke import them. 12. **`table_visible_ratio` is 0.5 for every audience**, up from 0.25 on `analytical`/`operational`. Below half, the MAJORITY of the rows the - author deliberately asked for sit behind the inner scrollbar — the exact + author deliberately asked for sit behind the inner scrollbar: the exact defect §11's issue-#1 work exists to catch, so the offline rule must not bless it. It remains a per-deployment knob; it is no longer a lenient default. `size.table-window` and `size.pivot-window` also became @@ -545,7 +545,7 @@ Recorded during the post-merge review burn-down: contract, so the exit code was previously the only signal that `--strict` blocked. It now appends a `design_gate` entry to `errors`, matching check/apply. (The review first read the `ok` difference between the two - verbs as the defect; it is not — they are different payloads with + verbs as the defect; it is not. They are different payloads with different `ok` meanings. The missing cause was the real gap.) 16. **Severities a rule can actually emit are declared** (`severities=` on `@rule`) and printed as `warn/error`. Four rules vary severity per @@ -562,11 +562,11 @@ Recorded during the post-merge review burn-down: Superset failure was uncovered: no chart `time_range`, no time_range filter at all, daily-or-finer grain, so every load queries the dataset's full history. Reported ONCE per dashboard, not per chart, and deliberately - silent when a time_range filter exists without a default — + silent when a time_range filter exists without a default; `filters.time-default` already names that one-line fix, and double- reporting one remedy at two severities is noise. Deployments that want that case to bite raise it via the overlay `severity` map (§15.8). 19. **`decompile` says when its dataset index is truncated.** The lookup stops at a page cap; past it, a real dataset became "uuid not resolvable" - and its chart was dropped — a wrong answer wearing the costume of an + and its chart was dropped: a wrong answer wearing the costume of an honest loss, which is the one failure this decompiler must never produce. diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 06db8d5..e62cef4 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -1,8 +1,8 @@ # Features -Chartwright builds Apache Superset dashboards from a small text file called a spec: -describe the dashboard once, and Chartwright creates it, verifies it, and keeps it -that way. Everything below works from that one file. +Chartwright builds Apache Superset dashboards from a small file called a spec, +updates them from that same file later, and decompiles existing dashboards back +into one. Everything below works from that one file. ## AI-Native Dashboard Creation - **Context to Dashboard**: Ask in plain words; the dashboard is built from what is in front of you. @@ -11,7 +11,7 @@ that way. Everything below works from that one file. - A screenshot of a dashboard in another BI tool, pointed at the same underlying data. - **Reviewable Checkpoint**: The AI's output is a small spec file you can read, edit, and version like code. - **Open AI Contract**: `chartwright schema` prints the full JSON Schema so any LLM or tool can generate valid specs. -- **MCP Server**: Six tools covering the whole lifecycle, usable from any MCP client. +- **MCP Server**: Ten tools covering the whole lifecycle, usable from any MCP client. - **Guardrails**: The dashboard is new; the data behind it must be real. Every dataset, column, and metric the AI references is confirmed to exist before anything is built, so a made-up column becomes a clear error message, never a broken chart. ## Dashboard Design @@ -32,13 +32,13 @@ that way. Everything below works from that one file. ## The Design Brain - **A Visual Designer On Call**: An optional intelligence layer that knows BI/UX practice (Few, Tufte, IBCS) and Superset's rendering quirks, on by default and off with one flag ([full reference](DESIGN-BRAIN.md)). -- **The Brief**: `chartwright brief` prints design guidance tuned to an audience preset (`executive` / `analytical` / `operational`) — budgets, chart choice, composition — for the AI (or you) to read before writing a spec. +- **The Brief**: `chartwright brief` prints design guidance for the AI (or you) to read before writing a spec: budgets, chart choice, and composition, tuned to an audience preset (`executive`, `analytical`, or `operational`). - **The Critic**: `chartwright advise` reviews a finished spec: readable minimum sizes, layout composition (KPIs first, fold budgets, row density), chart-choice limits, narrative polish. `--fix` applies the safe geometry subset; `--profile` adds data-aware checks (a time axis on a non-temporal column, a pie hiding 40 slices). - **Deliberate Exceptions, Visible**: Suppress any rule per dashboard or per chart in the spec's `design` block; suppressions are reported, never silent. -- **House Style**: A `design.yaml` overlay tunes thresholds, disables rules, and appends org guidance to the brief — no fork of the rulebook. +- **House Style**: A `design.yaml` overlay tunes thresholds, disables rules, and appends org guidance to the brief, so a deployment can set its own standards without forking the rulebook. - **It Learns From You**: Heights you polish in the UI flow back via `absorb`; `chartwright calibrate` mines them and updates the recommended heights the brief and autofixes use. - **Design Audits of Legacy Dashboards**: `decompile` + `advise` grades any UI-built dashboard against the rulebook. -- **One-Shot Redesign**: `chartwright redesign ` decompiles a live dashboard, audits it, applies the safe geometry fixes, and writes the redesigned spec — side by side under a new slug when the original isn't tool-built, in place when it is. +- **One-Shot Redesign**: `chartwright redesign ` decompiles a live dashboard, audits it, applies the safe geometry fixes, and writes the redesigned spec. A tool-built dashboard is redesigned in place; anything else comes back under a new slug and applies side by side, leaving the original untouched. ## Dashboards as Code - **Drift Detection**: `chartwright plan` diffs the spec against the live dashboard: charts, filters, scopes, title, layout. @@ -83,19 +83,23 @@ that way. Everything below works from that one file. |---|---| | `chartwright schema` | Print the spec's JSON Schema, the contract for people and AIs | | `chartwright validate` | Check a spec against the schema, offline | +| `chartwright brief` | Print the design guidance to read before writing a spec | +| `chartwright advise` | Review a spec against the design rulebook; `--fix` applies the safe geometry repairs | | `chartwright compile` | Build the import bundle, no server needed | | `chartwright check` | Verify every dataset, column, and metric against a live instance, read-only | | `chartwright apply` | Build, import, and verify the dashboard end to end | | `chartwright plan` | Show what differs between the spec and the live dashboard | | `chartwright decompile` | Turn a live dashboard into a spec | +| `chartwright redesign` | Decompile a live dashboard, audit it, and write the repaired spec | | `chartwright absorb` | Pull height polish made in the UI back into the spec | +| `chartwright calibrate` | Propose recommended heights from your absorb history | | `chartwright restore` | Bring back a backed-up dashboard, completely | ## Testing and Evidence - **The full offline suite** on Linux and Windows on every push, and on every pull request ([exact counts](VERIFICATION.md)). - **Live CI against real Superset 4.1.4, 5.0.0, and 6.1.0** on every push: full apply, lifecycle soak, stale-tab adversary, fault injection. - **500-cycle soak** passed on the oldest and newest supported versions. -- **Chart options verified against Superset's own source code** for every supported version, so a Superset change is caught in our tests before it reaches your dashboards. +- **Chart options verified against Superset's own source code** for every supported version, so a Superset change surfaces here before it reaches your dashboards. - Full evidence: `docs/VERIFICATION.md`; source citations: `docs/CONTRACTS.md`. ## Deliberately Not Included diff --git a/docs/VERIFICATION.md b/docs/VERIFICATION.md index f949754..d19bec4 100644 --- a/docs/VERIFICATION.md +++ b/docs/VERIFICATION.md @@ -34,7 +34,7 @@ injection. | Layer | Proves | Where it runs | |---|---|---| -| Offline suite (254 tests, 27 modules) | Contract, determinism, round-trips, credentials | every push and every PR, Linux + Windows | +| Offline suite (256 tests, 27 modules) | Contract, determinism, round-trips, credentials | every push and every PR, Linux + Windows | | Chart-option contract | Every emitted chart option is declared by each version's plugin source | every push | | Live guarantee check | 15-chart apply, per-chart data check, ids stable across re-apply | every push, all 3 versions | | Lifecycle soak | 500 randomized edit cycles with invariants held | 500 cycles on 6.1.0 and 4.1.4 before release; 25 cycles per version on every push | @@ -46,7 +46,7 @@ injection. The counts in the table above are the only hard numbers in the docs, and `tests/test_docs.py` fails when they drift from what pytest actually -collects — the same "generated, not hand-maintained" rule the rule table in +collects, the same "generated, not hand-maintained" rule the rule table in [DESIGN-BRAIN.md](DESIGN-BRAIN.md) §7 follows. - **Spec contract** (`test_spec.py`, `test_spec_v2.py`): validation diff --git a/tests/test_docs.py b/tests/test_docs.py index b31b99e..6429e36 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -39,6 +39,38 @@ def test_no_test_module_imports_through_the_tests_package(): assert not bad, f"import siblings directly (`from test_x import ...`), not via `tests.`: {bad}" +def test_features_cli_table_covers_every_verb(): + """v2 roadmap item 21 (the CLI Verbs table is missing the design-brain + verbs) was reported as executed and never landed: `brief`, `advise`, + `redesign` and `calibrate` shipped undocumented. A hand-maintained table + of a growing list drifts; this fails until it is caught up.""" + cli = (REPO / "chartwright" / "cli.py").read_text(encoding="utf-8") + registered = set(re.findall(r'sub\.add_parser\(\s*"(\w+)"', cli)) + registered |= {"check", "apply", "plan"} # added in a loop, not literally + documented = set(re.findall(r"\| `chartwright (\w+)`", + (REPO / "docs" / "FEATURES.md").read_text(encoding="utf-8"))) + assert not registered - documented, ( + f"docs/FEATURES.md CLI Verbs table is missing: {sorted(registered - documented)}") + assert not documented - registered, ( + f"docs/FEATURES.md documents verbs that do not exist: {sorted(documented - registered)}") + + +def test_documented_mcp_tool_count_is_right(): + """The other half of item 21's sibling, item 20: 'Six tools' outlived the + design brain adding four more.""" + words = {"Six": 6, "Seven": 7, "Eight": 8, "Nine": 9, "Ten": 10, + "Eleven": 11, "Twelve": 12} + actual = len(re.findall(r"^@mcp\.tool\(\)", + (REPO / "chartwright" / "mcp_server.py").read_text(encoding="utf-8"), + re.M)) + for doc in (REPO / "README.md", REPO / "docs" / "FEATURES.md"): + text = doc.read_text(encoding="utf-8") + for claim in re.findall(r"\b([A-Z][a-z]+|\d+) tools\b", text): + n = words.get(claim, int(claim) if claim.isdigit() else None) + if n is not None: + assert n == actual, f"{doc.name} says {claim} MCP tools; there are {actual}" + + def test_verification_is_the_only_place_with_a_test_count(): """Counts stated in several places drift in several places.""" stale = [] diff --git a/tools/gen_rule_table.py b/tools/gen_rule_table.py index 1cfca9b..764798b 100644 --- a/tools/gen_rule_table.py +++ b/tools/gen_rule_table.py @@ -36,9 +36,11 @@ def render() -> str: lines = ["| id | sev | fix | data | since | rule |", "|---|---|---|---|---|---|"] for rid in sorted(RULES): r = RULES[rid] + # ASCII hyphen for "no", not an em dash: the glyph carries no meaning + # here, and 49 rows of it swamped the file's real punctuation. lines.append( - f"| `{r.id}` | {r.severity_label} | {'✔' if r.fixable else '—'} | " - f"{'⚡' if r.data_aware else '—'} | {r.since} | {r.doc} |" + f"| `{r.id}` | {r.severity_label} | {'✔' if r.fixable else '-'} | " + f"{'⚡' if r.data_aware else '-'} | {r.since} | {r.doc} |" ) return "\n".join(lines) From 431b88e98267e0c3be248b227a4680336a84594c Mon Sep 17 00:00:00 2001 From: debabsah Date: Mon, 27 Jul 2026 23:33:12 -0700 Subject: [PATCH 2/2] docs: plain headings in FEATURES, and reconcile the time-range promise Four bold names were slogans rather than descriptions, which PRODUCT-WRITING.md asks headings not to be: AI-Native Dashboard Creation -> Creating Dashboards with AI A Visual Designer On Call -> Codified BI/UX Practice It Learns From You -> Heights Calibrated From Your Own Dashboards Your Data Stays Put -> What the AI Can See The last one gains rather than loses: "What the AI Can See" invites the reader into the sentence that answers it, where the old name asserted the conclusion and hoped to be believed. "No Empty First Load" promised charts open on the full data range while the new data.unwindowed-history rule warns when nothing bounds the dates. Both are true and they solve different problems, but a reader meeting both would think the tool disagrees with itself. The entry now states the tradeoff and points at the fix. --- docs/FEATURES.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/FEATURES.md b/docs/FEATURES.md index e62cef4..987e01e 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -4,7 +4,7 @@ Chartwright builds Apache Superset dashboards from a small file called a spec, updates them from that same file later, and decompiles existing dashboards back into one. Everything below works from that one file. -## AI-Native Dashboard Creation +## Creating Dashboards with AI - **Context to Dashboard**: Ask in plain words; the dashboard is built from what is in front of you. - The analysis you just ran, a KPI contract document, a metrics definition page. - The tables and views you were exploring: dbt-built models, ELT outputs, anything Superset knows as a dataset. @@ -19,7 +19,7 @@ into one. Everything below works from that one file. - **14 Chart Types**: big number, big number with trendline, line, bar, area, scatter, categorical bar, pie/donut, table, pivot table, heatmap, histogram, funnel, treemap. - **Metrics As You Write Them**: Saved Superset metrics, `SUM(col)`-style aggregates, `COUNT(*)`, with inline renames (`MAX(pct_of_goal) AS % of Goal`). - **Filters and Formatting**: Per-chart WHERE conditions, a native filter bar (value pickers, a time range with an optional starting range, numeric sliders, each scopable to specific charts), green/amber/red thresholds on pivot cells, d3 number and date formats. -- **No Empty First Load**: New charts open on your full data range, not a default time window that can hide everything. +- **No Empty First Load**: New charts open on your full data range, so a narrow default time window never hides everything on the first paint. On a large dataset that full range is a lot to draw, so give the filter bar a time range with a default; `chartwright advise` tells you when a dashboard has nothing bounding its dates. ## Layout Design - **ASCII Layout Design**: Draw the layout straight from the terminal: `"KKKK LLLLLLLL"` is a KPI card beside a wide line chart. @@ -31,12 +31,12 @@ into one. Everything below works from that one file. - **Rows, Tabs, and Notes**: Even or custom row splits, titled tabs, and markdown blocks for headers and notes. ## The Design Brain -- **A Visual Designer On Call**: An optional intelligence layer that knows BI/UX practice (Few, Tufte, IBCS) and Superset's rendering quirks, on by default and off with one flag ([full reference](DESIGN-BRAIN.md)). +- **Codified BI/UX Practice**: An optional layer holding what Few, Tufte and IBCS teach about reading a dashboard, plus the Superset rendering quirks that break it, on by default and off with one flag ([full reference](DESIGN-BRAIN.md)). - **The Brief**: `chartwright brief` prints design guidance for the AI (or you) to read before writing a spec: budgets, chart choice, and composition, tuned to an audience preset (`executive`, `analytical`, or `operational`). - **The Critic**: `chartwright advise` reviews a finished spec: readable minimum sizes, layout composition (KPIs first, fold budgets, row density), chart-choice limits, narrative polish. `--fix` applies the safe geometry subset; `--profile` adds data-aware checks (a time axis on a non-temporal column, a pie hiding 40 slices). - **Deliberate Exceptions, Visible**: Suppress any rule per dashboard or per chart in the spec's `design` block; suppressions are reported, never silent. - **House Style**: A `design.yaml` overlay tunes thresholds, disables rules, and appends org guidance to the brief, so a deployment can set its own standards without forking the rulebook. -- **It Learns From You**: Heights you polish in the UI flow back via `absorb`; `chartwright calibrate` mines them and updates the recommended heights the brief and autofixes use. +- **Heights Calibrated From Your Own Dashboards**: Heights you polish in the UI flow back via `absorb`; `chartwright calibrate` mines them and updates the recommended heights the brief and autofixes use. - **Design Audits of Legacy Dashboards**: `decompile` + `advise` grades any UI-built dashboard against the rulebook. - **One-Shot Redesign**: `chartwright redesign ` decompiles a live dashboard, audits it, applies the safe geometry fixes, and writes the redesigned spec. A tool-built dashboard is redesigned in place; anything else comes back under a new slug and applies side by side, leaving the original untouched. @@ -65,7 +65,7 @@ into one. Everything below works from that one file. ## Enterprise Ready - **Multiple Instances**: Sandbox, staging, and production as profiles in one file. - **Credentials Stay Out of Files**: Usernames and passwords from env vars (any names) or your credential manager (1Password, macOS Keychain, sops). -- **Your Data Stays Put**: The AI proposes; the tool verifies, using your own Superset login. Verification reads names (datasets, columns, metrics), not rows; the AI never queries your warehouse. +- **What the AI Can See**: The AI proposes; the tool verifies, using your own Superset login. Verification reads names (datasets, columns, metrics), not rows; the AI never queries your warehouse. - The post-apply data check keeps a row count and discards the rows. - **Corporate Networks**: Custom CA bundles, proxies, LDAP auth, internal pip mirrors (only 3 dependencies). - **Works Where You Work**: Windows, macOS, Linux; PowerShell and git bash; run from any directory; the Claude Code skill installs by copy, no admin rights.