QuantEcon / QuantEcon/quantecon-plugins.mystmd

datavis plugin family v1 — tracking

Open
#2 9 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
0
Forks
0
Avg merge
2h 52m
Merged PRs (30d)
7

Description

Repository: QuantEcon/quantecon-plugins.mystmd · Decisions: 2026-09-02 (D3, D7), 2026-09-04 (the contract audit), 2026-09-10 (gated containers) · Design record: report theme design bundle at 15f6672b · Sibling trackers: QuantEcon/quantecon-theme-report.mystmd#2 (first consumer), QuantEcon/compliance-lecture-style#28 (first content site)

This is a long-lived tracker for the first plugin family, revised in place against live state and never session-closed. Work items are the native sub-issues below.

Background

The report theme's design brief proposed eighteen compliance directives. Analysed one by one (REVIEW §9), they resolve to eight generic data-presentation structures that any mystmd project publishing reports or dashboards could use, three that existing MyST already covers, and a thin layer of compliance-specific wrappers. Nothing in mystmd core or the myst-ext-* family covers stat tiles, bar lists, stacked bars, heatmaps, sortable tables or chip grids; csv-table takes inline data only and tables render statically.

Decision D7 (2026-09-02) creates this repository now so the generic family is designed as a community-usable extension from the start, with a documented node contract that themes implement. Decision D3 fixes the contract's nature: portable AST, no custom node types, tone hints instead of domain thresholds. The compliance wrappers live beside the report theme and emit the same nodes without importing this repository's code: share the contract, not the code.

Where we stand (verified 2026-09-10 20:04 AEST)

Area State
Repository main @ eab2ffc, green. v0.0.1 tagged and released 2026-09-10, datavis.mjs attached (2,852 bytes). Labels: QEP-2 core set plus refactor; delete-branch-on-merge on
Toolchain (#4) Merged 2026-09-03 as c6ce310: CSV reader, project-root resolution, file cache, diagnostics, node builders, esbuild bundle, and a myst-driven test harness
Contract (#3) Merged 2026-09-03 as f922dd7: CONTRACT.md, eight Draft 2020-12 schemas, valid and invalid samples per primitive, and a validator wired into CI
Contract audit (#15, #17, #18, #20) Merged 2026-09-10 as 12f3a9f (#22): all four applied, together with the two-container-form specification. All four closed by hand, the PR carrying no closing keyword by repository convention. Suite on main: 366 contract checks, up from 252; 86 unit; 25 plugin; none skipped
Release pipeline (part of #7) Merged 3beef97 (#21) and exercised: v0.0.1 pushed 2026-09-10, all four guards passed, run green first time. Split out of #7 per QuantEcon/workspace-themes#12 so downstream has a URL to pin before there is anything worth pinning
Directives (#5, #6) Not started, and nothing gates them — #15's decisions are recorded and applied on main. These lead the next session
Conformance checker (#16) Not started. Worth landing with #5, since the directive tests want it
Schema gaps (#23) Filed 2026-09-10: nine defects found while auditing and deliberately left out of #22, so the change stayed reviewable. The largest is that the anchor-key ban goes unenforced on inline content in six of the eight schemas. Backlog; gates nothing
First consumer QuantEcon/quantecon-theme-report.mystmd#2. Its data contract is in review as QuantEcon/quantecon-theme-report.mystmd#15 and cites the gated form
Upstream No upstream proposal; the family is a QuantEcon plugin first (REVIEW §9 naming)

Findings that shape the work

Engine behaviours, each verified against mystmd 1.10.1 (qe-v10) by a real build rather than by reading source.

Finding Consequence Severity
Plugins load by local path or URL only; the documented remote form is a release asset Ship one bundled .mjs per family as a release asset. Landed in #21 structural
A loaded bundle cannot import npm packages or other bundles Node built-ins only; own CSV reader; esbuild single-file bundle structural
First directive registered under a name wins; core registers first; duplicates warn per page Register once per family; plain names with alias fallback; a test for core collisions medium
Unknown node types vanish in myst-to-react and error in myst-to-tex; div/span with class render everywhere Portable output is the contract structural
myst-to-tex has no handler for grid, card, header, footer or grid-item, and drops the subtree while exiting 0 Amends D3: the portable tree excludes core grid and card. The compliance card kit is classed div structures instead structural
myst build --md writes no export file at all for a page carrying any node of this family, logging Unsupported node type: div and exiting 0 under --strict Not ours: core {div} fails identically. Site and LaTeX builds are unaffected, so the compliance ledger is not blocked; a repo shipping downloadable .md or .ipynb is. An engine gap, recorded in CONTRACT.md and an upstream candidate structural
A directive's fileError is logged but not counted by myst build --strict, so a broken directive exits 0 Directives defer diagnostics onto the node they emit; a document-stage transform re-raises them. See QuantEcon/mystmd#95 structural
A container directive's run() executes before the directives nested in its body and sees raw mystDirective nodes No item directives among the eight primitives. Data is a bullet list, a pipe table, or a CSV structural
A container must re-parent the exact node objects it was handed. Dropping them deletes the content; cloning them publishes raw directive scaffolding into the page Object identity is the mechanism. Both failure modes exit 0. Stated as a rule in CONTRACT.md with the three ways to get it wrong structural
joinGatesTransform is entirely type-agnostic — it pairs any node carrying gate: "start" with the next sibling carrying gate: "end" and never looks at a directive name A plugin gets gated syntax by emitting the property. Both container forms execute a nested code-cell; the gated one additionally exits 1 when unclosed, because joinGates raises from a transform structural
markChildrenAsProcessed arrived in mystmd 1.7.0; before it a non-myst body's subtree was processed rather than skipped 1.7.0 is the contract's minimum engine version. A consumer on 1.6 meets the opposite behaviour, not a degraded one medium
mystmd emits a lone label on a div — 399 of 52,059 fuzzed labels, and an identifier without an html_id never once The anchor trio binds with two dependencies, not three: identifier and html_id each require the other two, and label requires nothing. Binding label as well would reject trees the engine really emits. Enforced behaviourally in scripts/validate-contract.mjs rather than structurally, since that check must stay idiom-blind and live outside schema/, which freezes at v1.0.0 medium
A plugin URL that 404s does not fail myst build --html --strict — it logs ⛔️ Unknown plugin "…", it must be an mjs file, a message that misdescribes the cause, and exits 0. The ⛔️ unknown directive that follows on every use of the missing directives also exits 0 A typo'd or stale version pin publishes a site with every data component silently absent, green. The same shape as QuantEcon/mystmd#95 and arguably its general case: config-stage and parse-stage errors do not reach --strict. Four repositories are about to pin plugin URLs, so consumers need a positive check that the plugin loaded rather than trusting the exit code. Filed as QuantEcon/mystmd#101, where the unknown-directive half is confirmed to share QuantEcon/mystmd#95's root cause — loadFile clears a file's warnings and then serves cached mdast without re-parsing — while the plugin-load half is separate, registered against the plugin's own path and so never in the set --strict reads structural
A plugin cannot declare a data file a dependency of a page A CSV edit under myst start shows stale output until the page is touched. See QuantEcon/mystmd#96 medium

Work plan

Phase Issue Work item State
1a #3 Node contract and JSON schema for the eight primitives merged
1a #4 Toolchain: CSV reader, root resolution, cache, bundle step, test harness merged
1a #15, #17, #18, #20 The contract audit, and the two container forms merged, #22
1a part of #7 release.yml merged, #21
1a part of #7 The v0.0.1 tag, and the D5 proof it exists for done
1a #5 Directives: stats, bar-list, stacked-bar, heatmap next
1a #6 Directives: data-table, chips, badges, delta-list next, parallel to #5
1a #16 The AST conformance checker the contract promises with #5
1a #7 Docs per directive, default-theme fallback proof, v0.1.0 release after #5 and #6
#23, #19 Schema gaps, and the 50 unverified audit findings backlog

Sequencing

The contract (#3) came first because both the directives here and the compliance wrappers in the report repo emit to it; the toolchain (#4) landed alongside. The release pipeline moved ahead of the directive groups because four downstream items were written against a URL that did not exist, and a v0.0.x tag is testable with directives = []. The two directive groups are independent of each other. The release (#7) marks the moment the report theme's renderers can pin a version. The contract freezes at the report theme's Phase 3 exit and v1.0.0 follows.

Two container forms, decided 2026-09-10

A card body may need to contain executable code — the ledger's charts page runs matplotlib. Every container in the card kit offers two forms, and both are supported: nested in one fence, or gated as {qe-…-start}{qe-…-end}. Either carries a code-cell, verified end to end with a real kernel at any nesting depth, so the choice is about editing rather than execution. The eight generic primitives take no gated pair, because a bullet list and a pipe table cannot hold executable content; the form is specified generally so a future primitive that could would take it on the same terms.

The tick-count cost is smaller than it first looked. Colon fences escalate with depth, but a backtick code fence is a different fence type and does not escalate with them: a code-cell two container levels down, at plain three backticks, executes and its output lands in the card's __body. So nesting costs one extra colon per level, on the container fences and not on the code.

The asymmetry that decides it is editing safety. Write the container and its item at the same colon depth and the parser pairs the first closing marker with the inner directive: the card renders, so the page looks half right, but the outer closing marker is left loose and every line after it is swallowed into a code node — verified, with no error and no warning under --strict, exit 0. An unclosed gate exits 1, named and located.

So: gated for executable content, and for any long or often-edited card set. Nested for a set of prose cards, where it reads better, keeps the set in one fence, and is the only form in which the container can reject a bad item with a line number.

What does not need to change

Nothing in the lecture theme or the compliance ledger depends on this repository until the report theme's first release. The git-metadata transform stays in the lecture theme; its move here is #11 and is not part of this tracker.

Next session — resume here

v0.0.1 is released and D5 is proven. The tag went up 2026-09-10; release.yml ran green first time, all four guards passing. The published datavis.mjs hashes to ca2896d0…, byte-identical to a local npm run bundle — so source, CI bundle, release asset and the engine's fetched copy are the same bytes end to end. A real project pinning the release URL logs 🔌 QuantEcon datavis (…) loaded: 0 directives, 0 roles, 1 transform. The estate-wide distribution model of QuantEcon/workspace-themes#12 now rests on an execution rather than on a reading of the source.

It also turned up a defect, which is the whole reason for tagging early — see the findings table: a plugin URL that 404s logs an error and exits 0 under --html --strict, and so does every unknown directive that follows. A stale or typo'd version pin therefore ships a green site with all the data components missing. Four repositories are about to pin plugin URLs.

In order:

Decided 2026-09-10: this project leads, ahead of the report theme's Phase 1b (QuantEcon/quantecon-theme-report.mystmd#5). Three reasons, all from that issue's own body. Its tests load datavis.mjs "to prove no duplicate registration", which is vacuous while src/index.mjs registers zero directives. Its plan to "assert the emitted AST against the contract schema" is what #16 exists to provide, so it would otherwise build a second conformance checker. And its stated constraint that "fileError plus myst build --strict exits non-zero" was false — QuantEcon/mystmd#95 — so it needed a spec correction before it could be worked at all; that correction is now filed there. Nothing in this repository waits on anything.

  1. #5 and #6 in parallel, with #16 alongside #5 because the directive tests want the conformance checker. Each directive is written against CONTRACT.md and its schema/<primitive>.json, and the fixtures in samples/ are what a new directive's test asserts against. Fill the empty registry in src/index.mjs; the bundle test asserts the registered count, so it changes with the first directive.
  2. Decide where the plugin-load guard lives. The engine gap belongs in the fork beside QuantEcon/mystmd#95, whose general case it may be. The consumer-side mitigation is estate-shaped: every repository that pins a plugin URL needs a positive assertion that the plugin loaded, because the exit code will not tell it. Worth settling before QuantEcon/compliance-lecture-style#34 writes its deploy workflow, one of whose acceptance criteria is that a broken build fails the deploy.
  3. #23 and #19 are backlog and gate nothing.

Four things to carry into the directive work, all learned the hard way and all documented in CONTRACT.md: there are no item directives among the eight, so a directive body is a bullet list or a pipe table; a directive reports a fatal problem with defer() and never with a bare fileError; a container that re-parents body content must return the exact node objects it was given, because a clone silently publishes raw directive scaffolding; and a primitive never writes label, identifier or html_id below the root, while the root may carry them — bound two ways, not three, because a lone label is a tree the engine really emits.

Re-verify the table above first.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

This is a long-lived tracker rather than a single change. Start with sub-issues #5 and #6 for the directive work, and #16 for the conformance checker; the contract is documented in CONTRACT.md and validation is wired through scripts/validate-contract.mjs. Done means the selected sub-issue's directives or checker are implemented, tested, and integrated with the released plugin bundle.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
data-visualization, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.