Skip to content

The design brain: toggleable BI/UX intelligence for dashboard generation (v1 + verified v2 iteration) - #2

Merged
debabsah merged 11 commits into
mainfrom
feature/design-brain
Jul 26, 2026
Merged

The design brain: toggleable BI/UX intelligence for dashboard generation (v1 + verified v2 iteration)#2
debabsah merged 11 commits into
mainfrom
feature/design-brain

Conversation

@debabsah

@debabsah debabsah commented Jul 16, 2026

Copy link
Copy Markdown
Owner

What

Chartwright guaranteed a dashboard imports correctly; this adds the layer that makes it read well — a codified, continuously-improvable body of BI/UX design knowledge (Few, Tufte, IBCS, plus Superset rendering facts), on by default and off with one flag. Reference: docs/DESIGN-BRAIN.md. The branch lands the feature twice over: the initial three-phase implementation, then a full second iteration (design brain "2") executing every item of an adversarially-verified review of the first (docs/DESIGN-BRAIN-V2.md).

How it works

One rulebook, two delivery mechanisms, split by who can apply the rule:

  • Tier G — the brief (chartwright brief): audience-tuned design guidance (executive / analytical / operational presets) read before authoring a spec: a canonical sketch to start from, compiler-defaults table, budgets, chart choice, composition. Improvable by editing markdown.
  • Tier L — the critic (chartwright advise): a 47-rule deterministic linter over the finished spec — readable minimum geometry, KPI-first composition, fold budgets, chart/filter/pivot/color limits, narrative polish — with a presentation-only autofix subset (--fix, convergent, idempotent, and disclosing every diff as {set, was}). check/apply carry advice by default (--design off|warn|strict).

The surface

chartwright brief [--audience A]
chartwright advise <spec> [--profile P] [--fix] [--strict] [--no-probe]
chartwright redesign <slug-or-id> --profile P     # decompile -> audit -> fix, one shot
chartwright calibrate [--write] [--since 90d]     # learn heights from human polish
  • Data-aware mode: the resolver keeps column types (temporal-axis check = error) and bounded cardinality probes catch spaghetti series, 40-slice pies, 400+-cell heatmaps, unusable filter pickers.
  • House style: design.yaml overlay — parameter overrides, per-audience calibrated heights, rule disables, per-deployment severity overrides, extra brief guidance — every value validated at load with typed errors.
  • The learning loop: absorb logs the heights humans drag charts to (with audience); calibrate mines them (one vote per chart, --since decay) into recommendations the brief prints and autofixes target.
  • Redesign: tool-born dashboards redesign in place; UI-born ones land side by side under a -redesign slug — the original is never overwritten.
  • MCP server mirrors it all (design_brief, advise_spec, fix_spec, redesign_dashboard, advice on check_spec).

The v2 iteration

A seven-lens review of the initial implementation (each lens executing the code against scratch specs; every bug/edge-case/doc-drift claim independently re-verified with a refute-by-default stance) produced 80 confirmed findings, 0 refuted, ranked into 37 roadmap items — all executed in the four batch A–D commits:

  • Batch A — 15 verified bugs & false positives: an absorb crash that mutated spec files before dying; the autofix echo chamber (tool-written fractional heights forged the human-polish signature and blinded the size rules); overlay trust-boundary validation; sketch stacks / KPI sidebars falsely condemned (rules now count horizontal slots); the pie rule that taught truncated-pie lies.
  • Batch B — 9 knowledge packs: pivot_table (was a total blind spot), the filter bar, per-type point budgets, top-N-without-sort, ordinal ordering, format consistency, first color rules, brief upgrade.
  • Batch C — architecture & provenance: validated ignores (unmatched_ignores, scoped band keys), chart-type taxonomy contract test (a 15th type fails CI until classified), rule since/alias metadata, severity overrides, calibration v2, memoized sketch geometry (was ~cubic), fix diffs, WYSIWYG disclosure on sketch fixes.
  • Batch D — docs made true and rot-proof: the rule catalog in DESIGN-BRAIN.md §7 is generated from the live registry; deviations from the review's proposals recorded in §15.

Guarantees kept

  • The LLM stays the untrusted parser at the edge; everything enforceable is tested code.
  • Autofixes never change what data a chart shows, never rename a chart (uuid identity), never write fractional heights.
  • Human polish outranks the brain: absorbed heights silence height opinions (and only those).
  • Suppression is always visible: ignored and unmatched_ignores in every report.
  • --design off is byte-identical to pre-brain behavior; spec_version unchanged; compiled bundles untouched.

Testing

213 tests pass (95 added across the branch): per-rule tables, fix-loop convergence/idempotence/no-fractional invariants, seeded advise-never-raises fuzz, taxonomy and cache contracts, design.yaml trust-boundary rejections, calibration grouping/decay/round-trips, the golden dogfood (the shipped example advises clean, its two deliberate exceptions recorded in its design block), and MCP parity.

Deferred, tracked in the doc: live-Superset UI verification (side-by-side advised-vs-unadvised screenshots); thresholds come from field notes and BI literature. Version bumped to 0.3.0.

debabsah added 3 commits July 16, 2026 13:09
Two-tier architecture: a generative design brief for the LLM (Tier G)
and a deterministic, auto-fixing spec critic (Tier L), sharing one
versioned rulebook with audience presets. Full CLI/spec/skill surface,
27-rule initial catalog, phased delivery, and a decision log in lieu
of review gates.
Tier G: `chartwright brief` renders audience-tuned design guidance
(executive/analytical/operational presets) from packaged guidelines and
the rule registry, for the LLM to read before authoring a spec.

Tier L: `chartwright advise` runs a 27-rule deterministic critic over
the spec (sizes, composition, chart choice, narrative), with a
presentation-only autofix subset (--fix, convergent and idempotent)
and per-rule/per-chart suppression via the spec design block. check and
apply carry advice by default (--design off|warn|strict).

Phase 2: advise --profile adds data-aware rules — the resolver now keeps
column types (temporal-axis check) and bounded cardinality probes catch
spaghetti series, 40-slice pies, and oversized heatmap grids.
design.yaml overlay for house style (params, disabled rules, extra
brief guidance).

Phase 3: absorb logs human height polish; `chartwright calibrate`
mines it (one vote per chart, latest wins) and proposes/records
recommended heights that the brief and autofixes then honor.

Fractional (absorbed) heights silence sizing rules: human polish
outranks the brain. Off is really off: --design off is byte-identical
to 0.1.0 behavior. Example spec now advises clean (the two warnings it
fired were real). 157 tests pass.
…oard

decompile -> data-aware audit -> safe geometry fixes -> redesigned spec,
plus the decompile losses and the remaining structural findings for the
spec author to act on. Ownership picks the landing zone: tool-born
dashboards redesign in place; UI-born ones come back under a -redesign
slug (title suffixed) so apply builds side by side and never touches
the original. MCP tool + skill step 8 wire the same flow.
@debabsah
debabsah force-pushed the feature/design-brain branch from 88b407d to 4c809ff Compare July 16, 2026 21:07
Multi-lens review of the shipped design brain: seven independent lenses
(core machinery, per-rule logic, adversarial spec fuzzing, BI/UX domain
expertise, tool ergonomics, architecture/learning-loop, doc truth), each
executing the code against scratch specs; every bug/edge-case/doc-drift
claim independently re-verified by an adversarial reviewer. 80 findings
survived, 0 refuted; deduplicated into 37 ranked roadmap items under six
themes, headlined by a real absorb crash, an autofix echo chamber that
forges the human-polish signature, overlay trust-boundary validation,
sketch-stack false positives, and a pivot_table rule pack.
@debabsah
debabsah force-pushed the feature/design-brain branch from 1f39e9e to 8a4b850 Compare July 16, 2026 23:04
debabsah added 4 commits July 16, 2026 16:18
…sitives

Roadmap items 1-15 of docs/DESIGN-BRAIN-V2.md:
- absorb: missing json import crashed AFTER mutating the spec; serialize
  the report before any file side effect (1)
- echo chamber closed: autofix targets are ceiled integers so tool output
  can never mint absorb's fractional human-polish signature; suppression
  narrowed to height-driven findings (width complaints survive polish);
  explicit design.ignore intent always lands in `ignored` (2)
- design.yaml overlay: values validated (types, 1..100 height range,
  audience keys) with typed errors; recommended_heights merges per key
  across all four layers; advice payload degrades on any exception (3)
- grain-vs-range applies the compiler's P1D default when grain omitted (4)
- new size.min-width rule (3/12 hard floor, KPI 2/12); presets made
  arithmetically self-consistent (operational max_row_charts 4, fold 22;
  executive kpi_row_min 2) (5, 9)
- pie-slices no longer teaches truncated-pie lies; guideline added (6)
- row-density and kpi-band reason over horizontal slots, so sketch stacks
  and KPI sidebars stop being condemned (7)
- MCP: stale tool pin fixed, fix_spec tool added, advice block on
  check_spec, resolution_errors on advise_spec, audience validation (8)
- calibrate reports non-actionable candidates instead of dropping them (10)
- heatmap-grid re-probes a saturated side ("at least NxM") (11)
- acronyms don't vote in title-style; boolean/numeric filter values don't
  trigger filtered-title (12)
- axis-min-height defers to heatmap-geometry and hbar-window (13)
- duplicate tab titles rejected at the spec boundary (14)
- fix loop: convergence invariant documented + no-progress guard (15)

184 tests pass (27 new).
Roadmap items 16-24 of docs/DESIGN-BRAIN-V2.md:
- pivot_table pack, ending the rulebook's worst blind spot: size.pivot-window,
  chart.pivot-dims (<= 3 total), chart.pivot-columns (data-aware rendered-
  column budget), row-limit-intent coverage, guideline limits (16)
- filter-bar pack: filters.count (per-audience max_filter_selects param),
  duplicate-column, select-cardinality probe (> 500 distinct), time-default,
  range-default; spec docstring corrected (17)
- grain-vs-range v2: per-type point budgets (bars ~40, lines ~300), PT1M/PT1S
  and week-anchor grains, zero-span message; new chart.trend-grain for
  full-history sparklines with no defaulted window (18)
- data.top-n-sort: a limit without an order is a sample, not a ranking (19)
- factually wrong sort guidance rewritten per chart family; new
  chart.ordinal-order flags weekday/month dims that will sort Apr-Aug-Dec (20)
- brief upgrade: canonical exemplar sketch per audience, compiler-defaults
  table, d3 format and ISO grain vocabulary (21)
- narrative.format-consistency across KPI cards; guideline softened to what
  the spec can express (22)
- first color knowledge: chart.format-bands (overlapping bands warn, lone
  band info) + RAG-polarity and heatmap-scale guidelines (23)
- chart.treemap-vs-bar and the fixable layout.markdown-height (markdown
  blocks addressed by layout position; fix.py learned md targets) (24)

The example gained a design.ignore block for its two deliberate exceptions
-- demonstrating the mechanism the review asked users to reach for.
198 tests pass (14 new).
…est learning loop

Roadmap items 25-34 of docs/DESIGN-BRAIN-V2.md:
- ignore/disable entries validated up front (typos surface as
  unmatched_ignores, never silent no-ops); band findings get scoped
  positional keys (rule@tab-Ops-row-1) accepted alongside rule@Chart (25)
- STANDALONE_TYPES declared; contract test makes the chart-type taxonomy
  exhaustive, so a 15th type fails CI until classified (26)
- promises made true: design_brain version constant (bumped to "2"),
  Rule.since metadata + alias table for renames, golden dogfood test on
  the example, seeded advise-never-raises fuzz (27)
- per-deployment severity overrides in the overlay, applied at the single
  collection choke point; Rule.severity documented as the default (28)
- calibration v2: absorb events log the audience; proposals group by
  (audience, type) and --write lands under audiences.<aud>; --since 90d
  is the decay knob (29)
- geometry caching: parse_sketch memoized, RuleContext heights memoized —
  advise was measured ~cubic on sketch dashboards; a test pins one parse
  per sketch holder (30)
- autofixes disclose their diff: fixed entries carry {set, was}, the CLI
  payload names the rewritten file (31)
- check --design strict emits the same design_gate error entry as apply (32)
- implicit row widths use largest-remainder so 5-chart rows sum to 12:
  the phantom right-edge hole disappears from renders AND row-fill stops
  blaming users for the tool's arithmetic (33)
- sketch WYSIWYG: height fixes on sketch-drawn charts disclose that the
  drawing goes stale and how to redraw instead (34)

213 tests pass (15 new).
…-proof

Roadmap items 35-37 of docs/DESIGN-BRAIN-V2.md, closing the burn-down:
- README gains a design-brain section with the three-command quick start;
  MCP tool count corrected; VERIFICATION covers the design test suites;
  a worked design.yaml house-style reference lands in DESIGN-BRAIN §6 (35)
- precision sweep: §7's rule table is now GENERATED from the live registry
  (47 rules, cannot rot), §10 shows the real v2 payload (fixed diffs,
  unmatched_ignores, design_brain "2"), §12 lists the tests that actually
  exist, audience table matches the shipped presets, prose de-numbered (36)
- CLI polish: help strings on every verb, degraded advice payloads carry
  the audience, redesign refuses to clobber its default output path (37)
- deviations 7-10 recorded (0-indexed rows kept; time-default severity via
  overlay; sketch WYSIWYG as disclosure; per-metric formats = spec v-next)

Version 0.3.0. 213 tests pass. All 37 review items executed.
@debabsah debabsah changed the title The design brain: toggleable BI/UX intelligence for dashboard generation The design brain: toggleable BI/UX intelligence for dashboard generation (v1 + verified v2 iteration) Jul 17, 2026
debabsah and others added 3 commits July 16, 2026 20:51
… inner scroll)

Closes #1.

Grid heights are fixed layout properties while rendered rows are
data-driven: when a row dimension gains members after authoring, the new
rows render below the chart's inner scrollbar and every check stays
green. Two layers, per the issue:

- smoke now compares the rows its query already fetched against the
  configured height (pivots: distinct row-dimension tuples; tables:
  result rows) and warns on every apply -- the silent regression becomes
  an apply-time warning at zero extra query cost
- new data-aware rule size.grid-fit probes single-dimension pivots and
  aggregate tables pre-apply and autofixes the height when sane
- the sizing formula and the "table height is a data-dependent property"
  guidance land in the guidelines the brief ships

217 tests pass (4 new: the issue's exact 5-to-9-leaf-rows scenario among
them).
Review of the design-brain branch found seven things worth fixing before it
lands. Each is reproduced by a test that fails without the fix.

1. CI runs on pull requests. The offline matrix (Linux + Windows) and the
   live matrix (three Superset versions) only ran on push to main, so this
   4,900-line branch had never been through either.

2. `sort_by` actually sorts. It compiled to `order_desc: true` and nothing
   else - a direction with no column - so a "top 10" table returned ten
   arbitrary rows. Three symptoms, one field: the compiler dropped it, the
   resolver never checked it (a typo passed `check`), and `plan` reported
   the chart changed forever because decompile could not produce a field the
   compiler never wrote. Aggregate mode now emits timeseries_limit_metric,
   raw mode order_by_cols; both are declared by the Table plugin on 4.1.4,
   5.0.0 and 6.1.0, and params_drift stays clean. The kitchen-sink fixture
   carries a sorted table so live CI applies one against all three versions.
   Made worse by the branch: data.top-n-sort told users to set the field.

3. `--design strict` fails closed. Advice degrades to an error note rather
   than crashing check/apply - right for `warn`, but under `strict` it meant
   counts of zero, so one typo in an org-wide design.yaml silently disarmed
   the gate everywhere it was used.

4. Rows of equal markdown blocks no longer overflow the grid. Blocks compare
   by value, so every identical block resolved to the first one's index, took
   the largest-remainder +1, and pushed the row past 12 columns. Regression
   from this branch's own width fix; positions now match by identity.

5. One grid model, three consumers. size.table-window (0.8 units/row + 1
   header), size.grid-fit (0.75 + 3) and smoke (30px/row + 3x40px) each
   measured a rendered row differently, so a 20-row table at height 8 passed
   offline while the data-aware rule and the apply warning both said it hid
   ten rows - the exact defect the branch's newest commit exists to catch.
   Constants live once in chartwright/spec.py. table_visible_ratio is 0.5 for
   every audience (was 0.25 on analytical/operational): below half, most of
   the rows the author asked for sit behind the inner scrollbar.

6. The polish skip is disclosed (`polished` in the advice payload). Deferring
   to a human-dragged height is an INFERRED signal, and v2 theme 1 named it
   alongside unmatched_ignores and the strict-gate ok - only the other two
   landed. Known limits of the inference are recorded in DESIGN-BRAIN 15.13.

7. Doc counts are true and enforced. "125 tests" outlived four batches across
   five locations; v2 roadmap item 20 flagged it and only half-landed while
   the page asserted all 37 items were done. Counts now live in one row of
   VERIFICATION.md that tests/test_docs.py checks against what pytest
   collects, and the v2 status line records the miss instead of hiding it.

Not covered here (from the same review, still open): the design brain has no
live-CI or rendered-output verification, probes ignore chart filters and are
unbounded in warehouse work, `advise --strict` still prints ok:true while
exiting 1, and the MCP fallback hardcodes design_brain "1".

235 tests pass; params_drift clean against 4.1.4, 5.0.0 and 6.1.0.
Caught by the pull_request trigger the previous commit added, on its first
run: the offline job failed on Linux and Windows before this branch could
merge.

`tests` has no __init__.py, so pytest puts tests/ on sys.path, not the repo
root. `from tests.test_design_data import ...` resolved only under
`python -m pytest` (which adds the CWD) and never under the bare `pytest`
console script ci.yml runs, so the module was uncollectable in CI while
passing locally. Import the sibling directly, like every other test module.

A test now asserts no module imports through the `tests.` package, so the
class cannot come back for contributors who run it the other way. Local
verification of the previous commit used `python -m pytest`, which is exactly
why this survived to CI.

236 tests pass under BOTH invocations; params_drift clean.
@debabsah

Copy link
Copy Markdown
Owner Author

Pre-merge review pass: two commits pushed

A full review of this branch (core + design brain) turned up seven items
worth fixing before merge. Each is reproduced by a test that fails without
the fix.

1. CI runs on pull requests. Both matrices only ran on push to main, so
this 4,900-line branch had never been through either. This is its first run.

2. sort_by actually sorts. It compiled to order_desc: true and
nothing else, a direction with no column, so a "top 10" table returned ten
arbitrary rows. Three symptoms from one field: the compiler dropped it, the
resolver never checked it (a typo passed check), and plan reported the
chart changed forever because decompile could not produce a field the
compiler never wrote, which broke the CI drift gate for any sorted table.
Aggregate mode now emits timeseries_limit_metric, raw mode
order_by_cols; both are declared by the Table plugin on 4.1.4, 5.0.0 and
6.1.0, and params_drift stays clean. Made worse by this branch: the new
data.top-n-sort rule told users to set the field.

3. --design strict fails closed. Advice degrades to an error note
rather than crashing check/apply, which is right for warn. Under strict
it meant counts of zero, so one typo in an org-wide design.yaml silently
disarmed the gate everywhere it was used.

4. Rows of equal markdown blocks no longer overflow the grid. Blocks
compare by value, so every identical block resolved to the first one's index,
took the largest-remainder +1, and pushed the row past 12 columns. A
regression from this branch's own width fix; positions now match by identity.

5. One grid model, three consumers. size.table-window (0.8 units/row +
1 header), size.grid-fit (0.75 + 3) and smoke (30px/row + 3x40px) each
measured a rendered row differently, so a 20-row table at height 8 passed
offline while the data-aware rule and the apply warning both said it hid ten
rows: the exact defect this branch's newest commit exists to catch. The
constants live once in chartwright/spec.py. table_visible_ratio is now
0.5 for every audience (was 0.25 on analytical/operational), because below
half the majority of the rows the author asked for sit behind the inner
scrollbar.

6. The polish skip is disclosed as polished in the advice payload.
Deferring to a human-dragged height is an INFERRED signal, and v2 theme 1
named it alongside unmatched_ignores and the strict-gate ok; only the
other two landed. Silence there reads exactly like the rule having passed.

7. Doc counts are true and enforced. "125 tests" outlived four batches
across five locations. v2 roadmap item 20 flagged it and only half-landed
while DESIGN-BRAIN-V2.md asserted all 37 items were done. Counts now live in
one row of VERIFICATION.md that tests/test_docs.py checks against what
pytest actually collects, and the v2 status line records the miss.

The PR trigger caught a real failure on its first run

The offline job failed on Linux and Windows: tests/test_design_rules_v2.py
imported from tests.test_design_data import ..., which resolves only under
python -m pytest (which adds the CWD) and never under the bare pytest
console script ci.yml runs. tests is not a package, so that module was
uncollectable in CI while passing locally. Fixed in the second commit, with a
test asserting no module imports through the tests. package. This would
have broken main on merge.

Verification

236 offline tests pass under both pytest invocations, Linux and Windows.
params_drift clean against 4.1.4, 5.0.0 and 6.1.0. Live apply, soak,
adversary and fault injection green on all three versions, with the
kitchen-sink fixture now carrying a sorted table.

Scope note on item 2: live CI proves Superset accepts timeseries_limit_metric
on import and that the chart still returns rows on all three versions. It does
not assert the rows come back in sorted order, since smoke checks non-empty
rather than ordering. That rests on the plugin contract.

Known gaps, deliberately not closed here

  • The design brain has no rendered-output verification. Every Tier L rule
    reads the spec, not the rendered result; the thresholds come from field
    notes and BI literature, not measured pixels, and docs/CONTRACTS.md has
    no design entries, unlike every other Superset behaviour this repo relies
    on. A visual harness over the existing sandbox is the natural next branch.
  • Cardinality probes ignore each chart's own filters and time range (false
    positives on well-scoped charts) and are bounded in result, not in
    warehouse work.
  • advise --strict prints ok: true while exiting 1, where
    check --design strict sets ok: false.
  • The MCP error fallback hardcodes design_brain: "1".
  • No rule covers the commonest real failure: a timeseries with no
    time_range, default P1D grain, and no defaulted dashboard filter, drawing
    full history daily. data.grain-vs-range only fires when time_range is
    set.

@debabsah
debabsah merged commit 7c00f3a into main Jul 26, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant