Changelog
Version history of dash-mui-charts. The timeline on this page is rendered from
CHANGELOG.md, reproduced below.
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Internal traffic stops counting itself in the read ledger
Sync 1.6.43, ported into this fork's shape.
Fixed
- **The network's own probes were the busiest "vendor" on this host's
ledger.** AnalyticsTracker.record_read — the hook that keeps a row for every corpus document served — never applied the internal-traffic drop that track_visit has always applied. So the hub's hourly health sweep, every link audit and every post-deploy battery landed in the reads table and rode the daily rollup to the network board. "Counted nowhere" now includes the read table. The drop keys on the event's ua field, which is what the package calls it — a drop keyed on user_agent would be a silent no-op, so a test asserts the field name against the resolved wheel rather than trusting the spec.
Recorded
vendor_classjoined the package's read-event fields between 2.9.0 and
2.9.4. lib/traffic_rollup.py already read it, so the rollup's vendor class column starts carrying real values rather than nulls, with no code change here.
The 1.6.41 remainder: short sidebar labels, a skip link, dated pages
Sync item 18, ported into this fork's shape.
Added
- Short sidebar labels. Pages declare
nav:in frontmatter, so the
sidebar reads "Basic / Dataset Mode / Stacking" under BARCHART while each page keeps its full name in <title>, og:title and its llms.txt heading. This restores exactly what the retired FAMILIES map showed — the 35 labels were taken from it — and closes the redundancy the previous round introduced by rendering name in the sidebar.
- A skip link. The first tab stop jumps past the sidebar's ~45 stops
straight to the content, visible only on keyboard focus.
/apiand/changelogare dated. Both entered the sitemap with no
lastmod: /api now declares one in frontmatter, /changelog derives it from the newest dated release heading in CHANGELOG.md.
- Header identity moved to
lib/constants.py(LOGO_ASSET,
LOGO_STYLE, WORDMARK, WORDMARK_COLOR, WORDMARK_VISIBLE_FROM), so a fork edits the identity block rather than the component.
Fixed
- A prose section counted as a release. The upstream changelog parser
was widened to accept bare ## 2.0.0 — date headings, and the widening swallowed free text: this file ends with ## Component License Requirements, which rendered a timeline card badged with that whole sentence and made the page claim 15 releases where there are 14. A release label is now bracketed, a version, a date, or Unreleased.
- The deploy battery's hidden-path list had drifted. It probed
/admin/control-board plus two paths this app has never had, and never learned about /admin/traffic — a 404 for a page that does not exist is not evidence that a page is hidden. Pinned against the page registry.
Recorded
- The corpus sweep for admin paths found six pre-existing mentions in
/llms-full.txt, all from this changelog's own prose in code spans and none of them links. The pin distinguishes a linked, reachable URL from a changelog naming what it changed.
The wall comes down, and navigation comes from one registry
Sync items 15 and 16 from the template's SYNC-1.6.22-1.6.38, ported into this fork's shape alongside items 12 and 13 below.
Changed
- Training crawlers are allowed.
block_ai_training=False: GPTBot,
ClaudeBot, CCBot and the rest now get the browser document, /healthz and no Disallow in robots.txt, the same as every other bot. The wall decided by vendor CLASS what nobody could account for; since the ledger round every corpus read is a row and the hub reconciles it against the wire, so a read is recorded and priceable. The tool from here is per-vendor — vendor_policy={"<key>": "block"|"meter"} for one vendor whose rows justify it — never the class. Measured in-process before and after: ClaudeBot and GPTBot on /, /llms.txt, /healthz went 403/200/403 → 200/200/200. The wire before the flip was that same 403/200/403, so every 403 this host ever served was the app's own middleware; there is no edge rule in front of it.
- The sidebar is built from the page registry, not a hand-written map.
components/navbar.py's FAMILIES list is gone; each page declares category: and order: in its own frontmatter and lib/constants.CATEGORY_ORDER names the section order. The rendered sidebar is unchanged — same families, same sequence — but a new page now arrives in the right section by describing itself, instead of by remembering to edit a second file. components/navbar.py, components/footer.py, pages/changelog.py and lib/aside.py are byte-identical to the template's now.
- Admin pages are hidden from navigation, not merely blocked. The
Admin section used to ship in every anonymous page's DOM with display: none and a callback to reveal it. It is now an empty placeholder that a server-side callback FILLS for an admin — so the admin URLs are not in the anonymous document at all. /admin/traffic gains its first nav link in the same change.
/changelogis a Timeline. Parsed fromCHANGELOG.mdat render
time, one card per section, and the file itself (minus its duplicate H1) is the page's LLMS_DOC — which also retires the "1 page have no LLMS_DOC source" warning this app printed at every boot.
- **The top bar's GitHub icon, JSON-LD
sameAsand the Resources block
read one constant** (GITHUB_URL). The "More Dash components" link to pip-install-python.com is retired — the domain has been out of the network directory since the retire sweep and this header was still sending readers there.
- Resources is third-party only:
dmcand MUI X Charts. The Dash
Community forum, the 2plot.dev link and the "Pip Components" section are gone; the network is listed once, in the new Other Apps menu in the top bar, built from lib/network_directory.PRIMARY so it cannot be listed twice or drift from the registry.
Added
- A footer — © year, the GitHub profile, Discord and YouTube, every
icon with an accessible name.
/admin/trafficgains a People section with the day's human hits,
visitors, sessions and median session, above the crawler tables and labelled: humans never enter the read ledger, so "(unidentified)" there is the UA-less crawler lane and never a person. Its day picker is a dmc.DatePickerInput bounded by the ledger's own first and last day.
- Accessibility: the code blocks' copy button and every icon-only
control in components/ carry names; tests/test_nav_contract.py greps for the next one that does not.
Fixed
- **
/apiserved thirteen headings and zero property rows to everything
that does not run JavaScript.** .. kwargs:: is a markdown2dash directive, and a directive renders Dash components — so its tables existed only in the browser's React tree. The machine lane (/api/llms.txt, the crawler document) and the non-JS prerender are all built from the page's markdown source, where the directive line is stripped. Measured on the wire: /api/llms.txt 2681 bytes with no table rows, the prerender with no <table>, while a real browser's rendered DOM showed 13 tables and 371 rows. Verified on the wire after the fix: /api/llms.txt 59967 bytes carrying all 371 properties, and 13 tables with 384 <tr> (371 data rows plus one header each) in the crawler document and the prerender alike. On the one page whose whole purpose is the prop list, every agent and every crawler got nothing. pages/markdown.py now expands .. kwargs:: into a markdown table exactly as it already did for .. source::, and both the directive and that expansion read ONE parse in the new lib/api_reference.py — all 371 properties in every lane, with a test that fails if the two ever disagree.
- Both live batteries were reading the wrong document.
scripts/network_smoke.py's default User-Agent was the bare internal token, which at dash-improve-my-llms 2.8 has no browser engine token and is therefore crawler-lane — so every default-UA check read the prerendered crawler document rather than the page a visitor gets. It now leads with a real Chrome/AppleWebKit token and keeps the internal token after it (a substring match, so this host's traffic is still counted nowhere on the far side); CRAWLER_UA is the other lane and is unchanged. scripts/smoke_live.py already had that shape and now has a test holding it there. The same rule reaches scripts/route_parity.py, whose in-process sweep feeds a CI gate.
- Code blocks inside a list item, blockquote or timeline row could widen
the whole document at phone width. One stylesheet rule for every container a code block can sit in, never a per-page override.
- The mobile drawer is
keepMounted— the hamburger no longer depends on
a mount-on-open transition, and the Admin callback's mobile target exists on every load.
- The right-hand aside is collapsed on pages that render no
.. toc::
(/changelog, home, the admin pages), which were rendering in the docs column beside an empty gutter.
- The Other Apps dropdown gets a solid themed background; it was
near-transparent in dark mode.
The ledger row, and a branch only CI can write
Sync items 12 and 13 from the template's SYNC-1.6.22-1.6.35, ported into this fork's shape (see DIVERGENCES.md for where the shapes differ).
Changed
- **
human_hitsDROPS andbot_hitsRISES from the first deploy after
this.** lib/analytics_tracker.py no longer carries its own User-Agent list; it delegates to dash_improve_my_llms.classify(), the same vendor registry robots.txt is already rendered from — so what this site SAYS about a vendor and what it COUNTS finally agree. The old list filed ClaudeBot (Anthropic's training crawler) under "search", still named the retired anthropic-ai / claude-web tokens, and counted every UA-less or library client (httpx, Go-http-client, node-fetch, an empty User-Agent) as a person. Those clients move from human to crawler. The hub's day-over-day view will show a step: that is the number becoming true, not a regression.
- An absent User-Agent is a crawler now. The same flip bit this repo's
own tooling before it bit the numbers: scripts/route_parity.py probed every route with no UA, so from the moment dash-improve-my-llms 2.8.0 could resolve, scripts/smoke_test.py — a CI gate — reported /admin/control-board: 404, the package's hidden-path answer to a crawler, with nothing about the app changed. The sweep now sends the same BROWSER_UA tests/conftest.py does. (The template hit this in tests/test_proxy_scheme.py, a file this fork does not carry.)
mainis no longer a deploy. Render deploys thereleasebranch,
and only cd.yml's deploy job writes it — a fast-forward push of the run's own sha, after the CI matrix is green. The Render deploy-hook step is gone; its repository secret is inert. A push to main whose CD run is red or still running leaves release where it was, and production with it. verify now runs only on needs.deploy.result == 'success' and asserts /healthz build == github.sha itself, which retires this fork's own weaker != 'skipped' divergence.
Added
- The read ledger. dash-improve-my-llms 2.8.0's
on_document_read
hands this app one row per corpus document it serves — tier, verdict, bytes, verified vendor — and AnalyticsTracker.record_read keeps it as a reads table in the same analytics file, same buffer, lock, flush cadence and retention as visits. It is a second table JOINED by the rollup, never summed into human_hits / bot_hits / pages, and the client address is dropped unless ANALYTICS_KEEP_CLIENT_IP=1.
- Rollup v4.
daily_rollupgainsvendors[](one row per vendor ×
verified × policy, with per-tier counts and bytes, capped at 40) and reads, present only on a day that had reads. Every v3 key is byte-identical; the reporter is unchanged.
/admin/traffic— this host's own ledger: vendor × day, vendor →
tier, top paths per vendor, and the v3 headline numbers for the same day. Behind the control board's exact gate, fails closed without Clerk, hidden from every machine surface, plain tables and no charts. The page says on its face that verified: n/a is a property of the vendor (Anthropic publishes no IP ranges, so ClaudeBot is always n/a) and not a defect on this host.
DIVERGENCES.mdnow carries a posture fence declaring what this
host serves, measured with a real vendor UA rather than assumed.
Floors
dash-improve-my-llms>=2.8.0inrequirements.txt(the Docker cache
bust), run.py's boot floor, and both version assertions in ci.yml.
The card X can read, and one fleet Python
twitter:cardis declared where a scraper can see it again. Dash
emits the card type with property= (hardcoded in dash/_pages.py); X's parser predates the Open Graph convention and reads name= only. templates/index.html had been trimmed of the template's name= line as a duplicate, so from 2026-08-24 to 2026-08-26 every page on this site offered X no card type at all — the image, title and description were all present and the card still could not render as a large image. The same wrong rule was encoded in THREE places and all three had to move: two assertions in tests/test_social_card.py (which is what required the deletion), and scripts/check_release.py's restated-tags gate (which then blocked the fix from shipping). twitter:title and twitter:description stay listed on purpose — X falls back to the og:* set for those, and does read it. Only the card TYPE has no fallback.
tests/test_smoke_live.pyexists. The post-deploy battery has run
in CD for months with nothing exercising it locally, which is how a new crawler/browser identity-parity check landed byte-perfect and found the card defect on the wire instead of in the suite. The script now runs against the in-process app on every test run; deleting the meta tag reproduces all four production failures locally.
- Production moves off Python 3.11.
render.yamldeclares
runtime: python, so Render's native runtime builds this service from requirements.txt and never reads the Dockerfile — which meant the 3.14-slim image bump certified CI while visitors kept getting PYTHON_VERSION: "3.11.12", with a third opinion (3.12) in the CI site lane. All four encodings now agree on 3.14: render.yaml 3.14.7, the site lane in ci.yml/cd.yml, and the image. /healthz gained a python field so the serving interpreter is visible from outside for the first time, and tests/test_python_version.py holds the encodings to the Dockerfile's FROM tag. The wheel's own requires-python window (3.9-3.13, in package-python-range) is a separate question and is deliberately untouched.
Floor round — dimll >=2.7.1, healthz tells the truth, one h1 per page
- dash-improve-my-llms floor 2.6.1 → 2.7.1, moved in every encoding:
requirements.txt (with its three commented backend extras), run.py's LLMS_PKG_FLOOR tuple and the boot-floor message ladder, and both CI asserts including the one inside the built image. The requirements line changing is the Docker cache bust — the dependency layer re-runs only when those bytes change, so a code-only commit can never pull a new release. What it buys: 2.7.0 dedups the prerender's H1 and the home footer's doubled /llms.txt link and hardens the idempotency probe; 2.7.1 adds the llms.txt v2 discovery relations + Link headers, the text/plain Accept ramp, and the representation digest.
/healthzis built per request and says who answered (template
1.6.10): app (SATELLITE_APP_KEY, else "unknown"), build (the running commit), and — on dimll >=2.7.0 — geo {configured, denied, resolved}, counts and flags only, never the denylist's country codes. The key is omitted on older packages rather than error-flagged, which makes its ABSENCE in production the fleet's stale-image alarm. The payload used to be a snapshot closed over at registration: harmless while every field was static, and wrong the moment one is not.
buildreplaces this repo'scommit. The CD build-match wait
(2026-08-21) shipped here first under the name commit; the template adopted the idea under the fleet's name, so cd.yml moves with it. The wait's one-time fallback covers the deploy where the running build still answers the old key.
- Every page serves exactly one
<h1>, pinned across all 41 routes on
the generic lane. The pin found three: /tree-basic, /tree-pro and /tree-simple each carried a body-level # TreeView-style heading duplicating the page title, preceded by a stray \ that rendered as a literal backslash paragraph. Demoted to ### — matching every other heading on those pages, and now carrying a TOC anchor like the rest.
- The source expansion is fence-aware (template 1.6.11): a
.. source:: inside a fenced block is documentation, not a directive. Preventive here — no page teaches the directive today, measured — but the first one that did would have closed the fence early and rendered an inlined Python file as markdown on the machine lane.
- Dependabot config added (the template's, verbatim): pip
version-updates restricted to dash*/plotly*/markdown2dash, the network drift alarm, without the floor-raise noise on every other pin.
- The Dockerfile gains the cache-semantics comment block. It has never had
a Node layer to drop — the component bundle is committed.
Round 3 — hashed selectors extinct, wide tables contained
- The three hashed
m_*fossils are gone fromassets/main.css, each
audited against the INSTALLED DMC 2.8 bundle first rather than on the template's say-so: m_46b77525 is Input.Wrapper's root (live in 2.8, where Mantine gives it only line-height) and this rule added an !important top margin to all 77 Input-family components across 7 routes; m_5caae85b has zero occurrences in 2.8 — dead, matching nothing; m_9cdde9a is AppShell's aside (confirmed from the bundle's own class map) and four of its five declarations restated Mantine's own rule verbatim. The fifth — a 15px breathing gap above the Table of Contents — was real intent and moved to the static aside.mantine-AppShell-aside rule beside the z-index override. A tombstone names all three hashes without the leading dot, so grep '\.m_' over assets/ finds live selectors only.
tests/test_css_hygiene.pyported byte-identical from the template:
it strips comments (tombstones may name hashes) and fails on any hashed selector in assets/*.css. Verified to have teeth against a planted .m_deadbeef.
- Wide tables scroll in their own box (`display: block; width:
max-content; max-width: 100%; overflow-x: auto — GitHub's recipe, a no-op for tables that already fit). The template's table.m2d-table selector would have been a **no-op on this site's widest content**: markdown2dash derives each class from the renderer that emitted it, so the .. kwargs:: directive's output is m2d-block-kwargs, and /api is 13 generated prop tables of which not one is an m2d-table. The rule covers both classes here; measured against the real layout trees, 14 of the site's 25 tables now match. The other 11 are hand-authored demo tables inside example modules with no class at all — deliberately left alone, since display: block` would restyle demo content.
Round 2 — the prerender becomes readable, and stops repeating itself
- dash-improve-my-llms floor 2.6.0 → 2.6.1, and the floor is
load-bearing: below it the injected prerender div carries a literal hidden attribute, so every visibility-respecting consumer — html-to-text extractors, text browsers, arguably crawler content weighting — read "Loading..." and nothing else. 2.6.1 serves the block visible and hides it with a synchronous inline script only JS browsers run (React's mount wipes the pair, so nothing changes for a human). The floor moved in all four places it lives: requirements.txt with its commented backend extras, run.py's LLMS_PKG_FLOOR and its boot message, and both CI assertions including the one inside the built image.
- The hand-written
<noscript>block is retired — 175 words, and the
same 175 on all 42 routes. /pie told a non-JS reader about SparklineChart and never said what a Pie Chart is; an outside SEO audit read that duplication as this host being the only one in the fleet that prerendered. It goes in the SAME deploy that brings 2.6.1, deliberately: no window exists where a non-JS reader loses the noscript without gaining the visible prerender. Its two tests moved with it rather than being deleted — the component count is now asserted inside the home page's visible prose, and tests/test_prerender.py pins the div's shape (present for a PLAIN client, no hidden, marked hide script, <main> prose) plus the thing the block was mistaken for: two different routes must not prerender the same paragraph.
lib/network_directory.pyre-copied verbatim from the template
(1.6.6): modelviewer.2plot.dev and excalidraw.2plot.dev joined the canonical directory when they went live in the gate wave. 18 peers.
- The apple-touch icon is opaque. The gate wave generated the favicon
set with the pre-fix make_favicons.py, so this site shipped an RGBA apple-touch icon for a day — iOS composites alpha onto its own background, black on some surfaces and white on others, so it rendered differently everywhere it appeared. The template fixed the script at the source; it is re-copied, the icon regenerated (it was the only one of the eight that changed), and tests/test_seo_icons.py now reads the PNG colour type out of the header to pin it.
- Discord/WidgetBot: nothing to remove. The crate was deleted in the
1.4.0-era pass (run.py, requirements, render.yaml); this sweep confirms zero references in any code, config, template or asset. The README's Discord links are community links, not the retired integration.
Gate wave — the sign-in gate, the control board, one stable icon
The docs site's half of the network's gate wave (batch 1: template sync + the Clerk env block). Ships DARK: PAGE_DEFAULT_TIER=public, so every page reads exactly as before and the flip to auth is one environment variable, no code change, no redeploy. The component library is untouched — no wheel change, no version bump.
- The interactive gate (
lib/gate_layouts.py,lib/access.py,
lib/page_tiers.py): every markdown page's layout is wrapped in a per-render verdict. With all tiers public the verdict is a dict lookup that always allows, so nothing changes until the env says so. scripts/route_parity.py measures the wrap as invisible: 195 chart mounts and 41 page trees identical, byte for byte.
- The control board (
/admin/control-board,lib/page_visibility.py):
flip any page between public/auth/admin/hidden and toggle its llms.txt, live, no redeploy. Fails CLOSED without Clerk, is excluded from the sidebar and both search fields, and its own Admin nav link is hidden until the server recognises an allowlisted account.
/api/agent-key(lib/agent_key.py): the person→agent handoff — a
signed-in reader's "copy llms.txt link" now carries a key that works when pasted into an assistant with no cookie. 204 for everyone until Clerk and the hub are configured.
- Presence beacon:
lib/satellite_reporter.pygains the ~60s
{app, active} ping the hub board's "live now" column reads. Display-only and ephemeral; the daily rollup stays the source of the numbers.
- ONE stable identity for crawlers. The per-load area/bar favicon
randomizer stays for browser tabs — but configure_seo(icons=[...]), the head links and assets/favicon/site.webmanifest now all declare the same generated area-chart set, and tests/test_seo_icons.py pins that dimll 2.6's autodiscovery finds exactly that set. The duplicate favicon_areachart.ico is gone; apple-touch-icon_areachart.png is the master everything regenerates from.
- Floors: dash-improve-my-llms >= 2.6.0 (sitemap
<lastmod>is emitted
verbatim or omitted — never invented), dash-mantine-components >= 2.8.0 (below it the mobile drawer renders as a floating card), vendored dash-clerk-auth 1.0.5, sha-verified in scripts/check_release.py.
- pip-audit now GATES (
continue-on-errorremoved): with
clerk-backend-api >= 7 and cryptography >= 50 the baseline is quiet, so a red audit means something new instead of the same four capped advisories.
- Accessibility: every icon-only control carries an
aria-label
(create_link now requires one). aria-label, never title= — DMC 2.8 raises on title during app construction.
[1.4.0] - 2026-08-03
Boilerplate migration — M5 (API reference)
- NEW
/apipage: every prop of all 13 components as tables generated
from the components' own metadata via the .. kwargs:: directive — they can never drift from the installed version. Enabling fix in lib/directives/kwargs.py: the numpy-style override (built for dash-mantine-components docstrings) shadowed markdown2dash's own parser for dash-generate-components docstrings, so dash-built components rendered EMPTY tables; the hook now falls back to the base parser when it sees "Keyword arguments:". Navbar gains a Reference section; route count 40 → 41 (both parity baselines re-recorded deliberately).
- Home stays the bespoke Python page it already was — it mounts no
charts, carries the brand/install/catalog and a full LLMS_DOC, and per-satellite custom homes are the network norm. Changelog likewise stays a Python page (its disk read is exercised by the suite on every run).
Fixed — tree rendering under the boilerplate shell (owner review)
- TreeView and SimpleTreeView now follow the theme toggle — both
components gained the data-mantine-color-scheme watcher + MUI ThemeProvider that TreeViewPro and TimeClock already had, so checkboxes, icons and edit fields switch with light/dark mode instead of rendering in the light palette everywhere (component fix in src/lib/components, bundle + wrappers rebuilt; wrappers regenerated on the current dash-generate-components, gaining its dash≤4.1 compat shim).
- Trees no longer overflow their fixed-height boxes — the
boilerplate's global markdown list styling (ul/ol and li margins in assets/main.css) leaked into MUI's nested ul/li tree DOM, inflating every tree's height (a height=200 tree bled into the next section on /tree-icons). Neutralized inside tree roots via assets/dark-mode.css; upstream, the boilerplate rule should arguably be prose-scoped.
- The /tree-icons SX demo's hardcoded light
#f8f9ffbackground became
var(--mantine-color-default) — it was unreadable in dark mode.
- The
heightprop now actually contains the tree (TreeView and
SimpleTreeView): a fixed height previously sized the wrapper only — block children grow past a fixed-height parent and divs don't clip, so the tree rendered full-length over the next section. height now implies overflow: auto on the wrapper, making the /tree-icons "Fixed Height with Scroll" demo do what its title says.
Boilerplate migration — M4 (TreeView + Date & Time Pickers)
- Ten more pages markdown-driven — the eight TreeView pages (basic,
simple, selection, expansion, editing, icons, disabled, and the Pro page with its live license-posture badge kept) and both pickers (/time-clock, and /time-clock-lab with its liquid-glass styling intact). Whole-page transforms, ids and callbacks verbatim; the four existing LLMS_DOC bodies folded into their md pages. Only home and changelog remain in pages/.
- The dogfooding story returns as content:
/tree-simplegained a
sidebar demo built FROM components/navbar.py's real family map — a SimpleTreeView that genuinely navigates these docs, exactly as the pre-migration shell's sidebar did (it can never drift from the actual nav because it is generated from it). Chart baseline deliberately re-recorded: 195 mounts, 79 chart-touching callbacks.
Boilerplate migration — M3 (the LineChart batch)
- Twelve more pages markdown-driven (38 of 40) — the seven LineChart
pages (basics, Pro, brush, reference lines, highlighting, ticks & hover, zoom preview), /highlighting-sync, /crosshair, and the three CompositeChart pages. The batch's 7,550 lines ported as whole-page transforms — one demo.py exec module per page, every id and all ~50 callbacks verbatim (the sync-tooltip overlays, the crosshair alert system and the render-BP LoadingOverlay dashboard included) — with md wrappers carrying frontmatter, overview prose, and the two existing LLMS_DOC bodies folded in. Per-section md granularity for this family is deliberately deferred to a content pass; the format, metadata, TOC/ad asides and real llms.txt documents are all in place now.
- Every Pro route now explains the key requirement in-page
(BANNER_ROUTES == PRO_ROUTES): the twelve md files carry the Pro admonition naming MUI_PRO_API_KEY, so silent degradation to a watermarked chart with no explanation is no longer an allowed posture — the test asserts it stays that way.
- Transform bugs the gates caught before runtime: the module cut point
matched from dash_mui_charts import inside LLMS_DOC code fences (syntax error), prose lines starting with "from " were harvested as imports, the banner-block strip went one line too deep on the two banner pages, and two pages lost the import dash their callbacks use (F821). All fixed; flake8 and chart parity green.
Boilerplate migration — M2 (BarChart + Candlestick + LiveTrading)
- Eight more pages markdown-driven (26 of 40): the six BarChart pages
(/barchart-basic 6 examples, /barchart-dataset 3, /barchart-stacking 5, /barchart-interaction 5 incl. the click/axis-click/highlight callbacks, /barchart-reference 6, /barchart-pro 3 Pro zoom demos with the license-posture line in the first), /candlestick (7 examples over a shared OHLC generator; the never-rendered 60-candle dataset dropped as dead code), and /live-trading (the whole simulator — 11 callbacks — transformed as one exec module; the redundant View Code block replaced by .. source::). Chart parity GREEN throughout.
- Directory naming rule learned: families whose examples import shared
_data.py need UNDERSCORE directories (docs/barchart_basic/) — a hyphenated package can be exec'd via importlib but cannot be sibling-imported (from docs.barchart-basic._data import x is a syntax error). Endpoints are unaffected (frontmatter owns them).
/live-tradingjoined BANNER_ROUTES: its markdown now documents the
MUI_PRO_API_KEY requirement permanently (Pro admonition + functions-as-props note) rather than only when the key is missing.
Boilerplate migration — M1 (Pie + Heatmap + Scatter)
- Five more pages markdown-driven (18 of 40 routes' chart content now
under docs/): /pie (6 exec examples), /pie-props (Titanic nested-pie playground, transformed whole), /scatter (7 examples sharing a seeded _data.py so every point cloud renders exactly as before), /heatmap (7 Pro examples + the color-scale reference as a real markdown table), /heatmap-props (Pro playground, transformed whole). Same endpoints, chart ids and callbacks throughout — chart parity GREEN against the pre-migration baseline.
- Pro degradation tests pinned by ROUTE, not source file — the
migration moves key-reading code from pages/<x>.py into docs/ exec modules, but the route's obligation to degrade (banner or unlicensed chart, never a traceback) doesn't move. 17 Pro routes, 6 banner routes, set-equality both ways.
- Transform fix caught by lint: the heatmap-props port had dropped its
json import (cell-click display would have crashed); restored, plus inherited f-string debt cleaned rather than carried into docs/.
- The stale "0.0.8" release banner on the Scatter page retired; its
llms.txt placeholder stubs (both props pages) became real documents.
Boilerplate migration — M0 (shell + chart-parity gate + Sparkline pilot)
- The docs app now runs on the dash-documentation-boilerplate structure
(model: dash_pannellum): run.py entry point (gunicorn run:server), components/ appshell/header/navbar, pluggable-backend plumbing (Flask enforced until the analytics hooks grow async twins), dependency floors at boot, optional Clerk (vendored 0.9.1, dormant without keys), proxy scheme fix, optional Dash MCP, and pages/markdown.py serving docs/**/*.md with frontmatter, TOC asides, .. exec:: live examples and per-page llms.txt built from the markdown body.
- Chart parity replaces exact-tree parity as the migration gate
(scripts/route_parity.py --charts-only): route set, per-route dash_mui_charts census, chart component ids, and the 78 chart-touching callbacks — recorded from the pre-migration app and GREEN after M0: all 40 routes, 194 chart mounts identical.
- SparklineChart family ported to markdown (
docs/sparkline*/): 3 md
pages + 11 exec example modules, callbacks verbatim, same endpoints. The three placeholder llms.txt stubs became real documents for free.
- The docs stack moves to Dash 4.4 / dmc 2.7 (requirements.txt;
markdown2dash installs --no-deps against its gunicorn<22 pin, mistune listed explicitly — LESSONS §8). The dash_mui_charts PACKAGE floor is unchanged (dash>=3.3).
- Ad client returns to the canonical boilerplate copy — per-page aside
slots served by the mount-fired MATCH callback, AD_APP_ID=muicharts. The floating-slot fork (and the double-impression bug it dodged) retired with the static shell that caused it.
- Old shell retired: SimpleTreeView sidebar (nav is now the grouped
boilerplate navbar; the dogfooding story moves into the TreeView docs in M4), 00-loading-theme.js + 01-nav-restore.js (they fought the new color-scheme system / targeted the removed tree), the dead license-key-store. dcc.Location(id="url") and the SPA analytics counting rule carry over unchanged; the header keeps the header-avatar favicon-swapper contract and the package-version badge.
- Tests updated for the new shell (80 passing, zero secrets): navbar
family map ↔ registry parity, per-page aside ad slots, old-shell assets stay deleted; identity/card/version/traffic suites unchanged.
- M0 owner-review fixes: the boilerplate's
main.css/m2d.css/
llms_copy.js now ship in assets/ (their absence left the theme toggle showing sun AND moon, and nav links unstyled); the navbar renders the old sidebar's SHORT labels with per-item icons ("BARCHART / Basic", not "BARCHART / Bar Chart - Basic") from a (path, label, icon) map; the stale "(0.0.8)" dropped from the Scatter page's display name; the Discord WidgetBot crate removed entirely (run.py, requirements, render.yaml); .claude/ is untracked and gitignored (owner request — session plans and screenshots live outside version control now).
Network-standard pass — Phase 4 (deploy cutover, repo side)
- render.yaml declares the cutover:
domains: [muicharts.2plot.dev]
plus every env var the app reads (APP_BASE_URL and AD_APP_ID with their canonical values, NETWORK_BULLETIN_URL pointing at the hub feed, WIDGETBOT_* optional, the existing secrets). LESSONS §10 caveat is in the file itself: blueprint envVars apply on Blueprint SYNC, not git-push autodeploys — every value must also be set on the SERVICE in the Render dashboard.
- Not applicable here: the Clerk satellite env from STANDARD §9 — this
app ships no auth surface (no dash-clerk-auth anywhere); nothing to configure until it ever gains one.
- Owner actions remain before go-live (deliberately not automatable):
upload build/social-cards/muicharts.2plot.dev.png to cdn.2plot.ai/github_assets/ and verify 200 + IHDR 1200×630 (the HARD GATE — smoke_live fails the deploy while it 404s, by design), set the service env, attach the subdomain + DNS, merge to main, then hub-side STANDARD §9 in pip-docs+ (promote shipping→live, VERIFIED_APP_IDS).
Network-standard pass — Phase 3 (tests + CI/CD)
- tests/ populated (80 tests, ZERO secrets by design) — the suite runs
exactly as CI's secretless container does, proving the degraded postures: the 17 MUI Pro pages fall back to license banners instead of dying, the traffic reporter stays dormant, the bulletin stays off. Files: site identity (one brand, every surface), social card (per-page image_url + description, template division rules, the placeholder-in-comment trap), version parity (the fix for the five-way version drift: package.json ↔ package-info.json ↔ header badge ↔ JSON-LD ↔ noscript, and no surface may claim "9 components" again), route smoke + preservation invariants (the url Location contract, the SimpleTreeView nav with every leaf routed, the ad-slot fork, asset contracts), internal-traffic contract (both halves), and the SPA/doc counting rule as executable arithmetic.
- CI (
ci.yml) — lint (flake8 with a budgeted, documented pages/ debt
ledger in .flake8, plus actionlint first), secretless pytest + a real gunicorn boot probed by the network battery, a Docker job that builds the production image, asserts version fingerprints INSIDE it (dash ≥4.1, dimll ≥2.3.4, gunicorn ≥23) and boots it against the same battery CD runs (LESSONS §19), a Dash matrix (4.1.0/4.2.0/4.3.0/4.4.1 × py3.12 + 4.4.1 × 3.10/3.13) that rebuilds the components with Node 20 (npm ci + build + validate-init) before smoke-testing, wheel build + clean-venv verification (13 components, top_level == dash_mui_charts, version == package.json, and a measured dash==3.3.0 floor install), a package × Python 3.9–3.13 range, JS parse checks on the committed bundle and every asset script, and advisory pip-audit.
- CD (
cd.yml) — main → full CI → Render deploy hook → 120s settle +
5 consecutive healthz 200s (Render swaps instances; one 200 proves nothing) → network_smoke.py + smoke_live.py against the live domain, including the social card's real pixels. Peer checks warn; own-host checks fail.
- Release (
release.yml) — tag-gated PyPI publish via OIDC trusted
publishing (no stored token), gated on tag == package.json version and scripts/check_release.py; GitHub Release cut from the CHANGELOG section. Publishing 1.3.0/1.4.0 remains a decision, not a side effect.
- Scripts —
network_smoke.py(boilerplate battery, per-site block:
this brand's H1, /sparkline/llms.txt, hidden-page canaries), smoke_live.py (canonical copy with the LESSONS §21 wake loop), check_release.py (versions, bundle freshness via git timestamps, a Python class per React component, packaging/SEO/network invariants), smoke_test.py (the matrix's structural gate — 40 routes, 200s, healthz, ≥150 chart mounts, node parse of every JS artifact).
- Two Dash floors made explicit and measured — the DOCS SITE needs
dash ≥4.1 (dash-improve-my-llms pins dash<5,>=4.1; production already resolves 4.4.x), while the PACKAGE needs only ≥3.3: setup.py now claims dash>=3.3.0 (raised from an unmeasured >=3.0.0) and python_requires>=3.9 (3.13 classifier added, untested 3.8 dropped) — both now measured by CI rather than asserted. Verified locally: the full route-parity gate is green under Dash 4.4.1, byte-identical to the 3.3.0 baseline.
- gunicorn floor raised to ≥23 in requirements.txt — closes
CVE-2024-6827 / CVE-2024-1135 (request smuggling); asserted inside the Docker image by CI so it cannot silently regress.
- Housekeeping:
import dash(unused) dropped from app.py; flake8 config
added with per-file ignores documenting why app.py's late imports and the pages idiom are deliberate.
Network-standard pass — Phase 2 (card + traffic + app id + bulletin)
- Social card generated —
scripts/make_social_card.py(boilerplate
template, this site's MUI-blue palette and area-chart mark) renders build/social-cards/muicharts.2plot.dev.png, 1200×630. HARD GATE outstanding: hand-upload to cdn.2plot.ai/github_assets/ and verify 200 + IHDR before the og:image deploy.
- Internal-traffic contract, both halves —
lib/analytics.record
drops 2plot-internal UAs at write time, before bot classification; the hourly rollup POST and every ad-server fetch now send internal_ua(...) so the hub stops counting this app as a python-requests bot.
- One short app id:
muicharts—traffic_report.APP_KEY,
ad_client.AD_APP_ID default, bulletin app_id and /healthz all converge on the directory key (legacy "dash-mui-charts" folds in at the hub). NOTE: the 2plot.ai traffic sink still keys this app "charts" and must gain a muicharts fold before deploy or its series forks.
- Bulletin wired opt-in —
lib/bulletin.py; boot log states wired/off.
NETWORK_BULLETIN_URL must be set on the Render SERVICE (blueprint envVars only apply on Blueprint sync).
Network-standard pass — Phase 1 (identity + llms surfaces)
- One brand, every surface —
lib/constants.py(SITE_BRAND
"dash-mui-charts — MUI X charts for Dash", SITE_DESCRIPTION, BASE_URL defaulting to https://muicharts.2plot.dev, OG card block, INTERNAL_UA). The header version badge, the JSON-LD version and the template origin are now substituted from single sources of truth at boot — this repo carried five conflicting version strings and three "9 components" claims (it has 13; README and .claude/CLAUDE.md corrected).
- dash-improve-my-llms ≥2.3.4 wired — /llms.txt (site prose from
pages/home.py's LLMS_DOC), per-page /<page>/llms.txt, /robots.txt with per-vendor bot policy, /sitemap.xml (40 URLs on the canonical origin), per-route canonical/og prerender, cross-host network directory (lib/network_directory.py). The analytics before_request stays registered ahead of the bot middleware so crawler hits keep being counted.
- Every register_page carries title/description/image_url — one
missing and Dash emits an empty tag that wins with scrapers; all 40 routes verified to serve zero empty meta tags. 14 pages (home + one per component family) carry LLMS_DOC prose sourced from SKILLS.md.
- templates/index.html rebuilt on the dedup rule — declares only what
Dash does not emit (og:site_name/locale/url, og:image auxiliaries, twitter:image:alt); the duplicate hardcoded title, static og/twitter block, stale JSON-LD and hardcoded canonical are gone. GA4 kept; favicon randomizer kept but its header-avatar sync now retries bounded instead of forever; SPA canonical/og:url sync script added.
Network-standard pass — Phase 0 (stabilize)
- Satellite analytics committed —
lib/analytics.py(SPA-aware hit
recorder), lib/traffic_report.py (hourly signed rollup + /healthz), verify_traffic.py (headless pipeline verification against the hub's own ingest verifier). All checks green.
- Route-parity gate —
scripts/route_parity.pyfingerprints all 40
routes (component tree, ids, chart-mount counts, callback census, HTTP sweep) against a committed baseline; every migration phase must keep it green.
- Requirements drift fixed —
requirements-deploy.txtdeleted (it had
lost requests, so the Docker image could not import app.py); requirements.txt is now the single dependency file for Render and the Dockerfile alike. Dockerfile gains PYTHONUNBUFFERED=1.
- Stray build artifact removed —
dash_mui_charts/dash_mui_chartswas a
full copy of package.json, created by build:backends passing the package name to -p/--package-info-filename; the flag now correctly says package-info.json and the artifact is deleted.
Components — TreeViewPro (2026-07-19)
- TreeViewPro kebab submenus + dividers.
kebabMenuItemsentries may now be
a leaf {label, value, icon?}, a {divider: true} rule, or a submenu {label, icon?, children: [entries]} that opens on hover/click (recursive nesting; a leaf anywhere in the chain closes the whole menu and fires kebabAction).
kebabMenuItemsById— per-node kebab menus:{itemId: [entries]}
overrides the global kebabMenuItems for that node (same entry shape, submenus/dividers included). Lets one tree carry different action sets for different node types (channel nodes vs media nodes vs viewport nodes — the 2plot.media /360-broadcast use case that motivated this).
[1.3.0] - 2026-06-05
Added
TimeClock — new component (13th)
TimeClockwraps the MUI X Date PickersTimeClock(@mui/x-date-pickers, Community) — an inline clock-face time selector with no input, popper, or modal. First component in a new Date & Time Pickers family.- String ↔ dayjs boundary: dayjs objects can't cross the Dash boundary, so
value/defaultValue/minTime/maxTimeare exchanged as plain strings — full wall-time ISO ("2022-04-17T15:30:00") or time-only ("15:30"/"15:30:45"). On change the component pushes backvalue(wall-time ISO), the currentview, and a parsedtimeDataconvenience object{hours, minutes, seconds, formatted, event_timestamp}. - Props:
views(hours/minutes/seconds),view(controlled, in/out),openTo,ampm(force 12h/24h),disabled,readOnly,autoFocus,minutesStep,minTime/maxTime,disableFuture/disablePast,disableIgnoringDatePartForTimeValidation,showViewSwitcher,sx,className. - Dark mode: observes
<html data-mantine-color-scheme>via aMutationObserverand wraps the clock in an MUIThemeProvider, re-skinning automatically — same pattern asTreeViewPro. - Dependencies: adds
@mui/x-date-pickerspinned to8.24.0(matches@mui/x-charts@8.24.0so they share a single@mui/x-internals) and thedayjsadapter (1.11.13). Compatible with the existing Material v6 stack — no migration.
Demo page /time-clock
- Mirrors the official MUI demo: basic usage, controlled vs. uncontrolled (with live readout + preset buttons), form props (disabled / read-only), views (hours-minutes-seconds, hours-only, minutes-seconds), and 12h/24h format. Theme-aware Mantine layout.
Demo page /time-clock-lab (TimeClock Lab)
- A deep-dive page showing TimeClock working hand-in-hand with dash-mantine-components:
- Dynamic colours —
dmc.ColorPicker(rgba, with an opacity slider) for the face plusdmc.ColorInputs for the hand and digits, fed live into the TimeClocksx(targets.MuiClock-clock,.MuiClockPointer-root/-thumb,.MuiClock-pin,.MuiClockNumber-root). - Liquid glass clock — a glassmorphic theme (
assets/liquid_glass_clock.css): frosted face with an animated halo, a pointer thumb restyled into a magnifying lens recentred on the digit, and the selected digit bolded/enlarged as the hand passes. Fully light/dark-mode aware. - Stopwatch —
dcc.Intervaldriven start/stop/restart; the clock view climbs with magnitude (seconds → minutes → hours). - Two-way pairings — fully synced with
dmc.TimeInput(+ reset/preset buttons),dmc.TimePicker,dmc.TimeGrid(preset half-hour slots), anddmc.DateTimePicker(the clock drives the time portion). One@callbackper pairing, branching onctx.triggered_idand returningno_updatefor the source side to avoid loops. - Each example carries a syntax-highlighted "View code" panel.
- New Date & Time Pickers group added to the navigation tree in
app.py.
Changed
Professional "View code" across demo pages
- Upgraded the legacy
html.Details+html.Pre"View Code" blocks todmc.CodeHighlight/dmc.CodeHighlightTabs— syntax highlighting, a copy button, file tabs, and theme awareness. 60 code blocks across 14 pages converted; dynamic outputhtml.Pretargets (e.g. click-data readouts) were correctly left untouched.
[1.2.3] - 2026-05-15
Fixed
TreeViewPro — Cell editing
[object Object]on edit:CustomTreeItempreviously passed the slider/kebab wrapper as the TreeItemlabelprop. MUI'sgetLabelProps()setschildren: labeland the edit input initialized from that same value, so entering edit mode stringified a React element to[object Object]. The custom UI is now injected through theslots.labelslot while the real stringlabelprop is left intact, so the edit input,onItemLabelChange, and double-click-to-edit all receive the correct text.itemIdis forwarded to the slot viaslotProps.label; MUI-internal props (editable,ownerState) are destructured out so they never reach the DOM. Group rows and the no-controls fallback also forward the slot props, keeping every row editable.- Edit mode collapsing mid-gesture: With
itemsReordering=True, MUI's reorder plugin setsdraggable="true"on the TreeItem root with no editing guard. While the label input was focused, drag-selecting a word started a native HTML5 element drag of the row, which blurred the input and committed/exited edit mode on mouse-up; clicking inside the cell also bubbled to the row's selection/focus handlers. A new customlabelInputslot (EditableLabelInput) now: (1) flips the nearestdraggable="true"ancestor tofalsewhile the input is mounted and restores it on cleanup; (2)stopPropagation()smousedown/pointerdown/click(withoutpreventDefault, so native caret placement and text selection still work) so the row no longer reacts; (3) cancels straydragstart.onBlur/onKeyDown/value/autoFocus/data-elementpass through untouched, so click-outside and Enter/Escape still commit/cancel. Net: double-click to edit → click to position the caret, drag to highlight, edit freely → click outside or Enter to commit.
Demo page /tree-pro
- Readouts use the renamed label:
editedItemLabelis now captured into adcc.Storeof{itemId: newLabel}overrides. "Last slider", "Last menu", and "Selected" resolve labels throughlabel_for(id, overrides)(override → static tree label → id). The two summary tiles render from stored last-slider / last-kebab state plus the overrides store, so renaming a cell immediately refreshes them with the new label instead of showing the stale one. Selected:shows labels, not IDs: selected items (groups or leaves, single or multi) now display their human labels via a fullALL_LABELSmap instead of raw slugs.
[1.2.2] - 2026-05-13
Added
TreeViewPro — Per-Item Slider + Kebab Menu
showItemControls(bool) — render a 0–100Sliderand a kebabIconButtondirectly after each tree item's label. Designed for "tree paired with a map / canvas" patterns where each leaf is a layer with an opacity / progress value and a row-level actions menu.controlsItems(list of IDs) — optional subset filter; the slider + kebab only appear on listed items (leaves only, by convention). Omit to show controls on every row.sliderValues(dict, bidirectional) —{itemId: value}. Pre-seed initial values from Python; the prop also updates live as the user drags so callbacks can mirror the state.sliderMin/sliderMax/sliderStep— bounds and granularity (defaults 0 / 100 / 1).sliderColor(string) — accepts Mantine palette names ("teal","blue.5"→var(--mantine-color-...-6)), CSS literals ("#ff6b6b","rgb(...)","oklch(...)"), or CSS expressions ("var(--mantine-...),"light-dark(...)). Applied viasxto the slider track, thumb, hover ring, value label, and rail.kebabMenuItems(list) —[{label, value, icon?}]defines the actions menu.iconresolves throughiconResolver.sliderChange(output) —{itemId, value, event_timestamp}, fired on slider commit (mouse-up / touch-end).kebabAction(output) —{itemId, action, event_timestamp}fired when a menu item is selected.orderedItems(output) — the full live tree after any drag-reorder. Emitted on everyonItemPositionChangevia an internalapplyReorderwalk so Dash callbacks can render the nested current order. Falls back toitemsuntil the first reorder.- Dark / light mode detection — TreeViewPro now wraps its subtree in an MUI
ThemeProviderwhosemodeis driven by aMutationObserveron<html data-mantine-color-scheme>. Checkbox, slider, IconButton, Menu paper and MenuItems re-skin automatically when the Mantine color scheme toggles. - Slider responsiveness on desktop —
ItemLabelWithControlsholds a local React state during drag, so the thumb tracks instantly even when Dash callback round-trips are slow. Nativedragstartis cancelled on the slider + kebab area to keepitemsReorderingfrom swallowing mouse drags; passive pointer/touch events no longer callpreventDefault(silences the "Unable to preventDefault inside passive event listener invocation" console message). MoreVert,ContentCopy,PersonAdd,CheckCircle,Archiveicons registered iniconResolver.jsfor kebab menu usage.
Demo page /tree-pro rebuilt
- Two-column grid (
dmc.Grid): the Pro tree on the left, a "map companion" panel on the right that mirrors the tree's state viadmc.Texttiles (Last slider, Last menu), a JSON readout titled "Slider values and nested order:", and a rolling action log. - Slug-derived IDs:
slugify_label+assign_idsturn label strings into stable IDs at startup. Spaces and non-alphanumerics →-, duplicate slugs auto-disambiguate with-1,-2, … soSite survey overlaybecomessite-survey-overlayand the JSON output reads like a layer manifest instead oftask-1,task-2. - Nested order JSON —
build_nested_viewwalksorderedItems(or the initialitemson first render) and injects slider values at the leaves, so the readout always reflects the current reordered tree. - Layer-themed labels (Planning / Active / Reference) replace the placeholder "Backlog / In Progress / Done" copy to match the map-companion use case.
- Section 3 wires
sliderColor="teal";ThemeIconand action-log icons reuse the same constant.
Notes
- Two
Each child in a list should have a unique "key" propwarnings remain whencheckboxSelection={true}and when the kebab menu opens. Both originate in MUI v6 internals (ButtonBaseripple array,FocusTrapchildren) and are tracked upstream — they are not caused by our code. They clear on upgrade to MUI v7.
[1.2.1] - 2026-04-11
Fixed
- TreeView
editableItemsprop not working:isItemEditabledefaulted tofalse(a boolean), causingtypeof isItemEditable === 'boolean'to short-circuit before checking theeditableItemsarray. Changed toisItemEditable === trueso per-item editing viaeditableItemsworks correctly. - SimpleTreeView React 18
defaultPropswarning: ReplacedComponent.defaultPropswith JavaScript default parameters in the function signature. - TreeView edit input dark mode: Added CSS overrides for
.MuiTreeItem-labelInputinput fields — dark background, white text, and blue focus underline in dark mode.
Added
- Per-item icons in SimpleTreeView: Items now support an
iconfield (string) resolved viaiconResolver. Renders inline MUI icon (18px, 0.7 opacity) next to the label. - 30+ new MUI icons in
iconResolver.js:ShowChart,BarChart,PieChart,ScatterPlot,GridOn,CandlestickChart,Timeline,TrendingUp,Layers,AccountTree,Brush,Highlight,Sync,ZoomIn,TouchApp,TableChart,StackedBarChart,Palette,Rule,Mouse,CheckBox,UnfoldMore,Block,Diamond,AutoGraph,ViewList,GpsFixed,Speed,Star,PlayArrow,Tune,History. - Changelog page:
/changelogrendersCHANGELOG.mdviadcc.Markdownwith styled tables and dark mode support.
Changed
- Navigation tree: Reordered (Home, Changelog, SparklineChart, PieChart, BarChart, Heatmap, ScatterChart, LineChart, CandlestickChart, LiveTradingChart, CompositeChart, TreeView). All items have unique MUI icons. Groups start collapsed.
itemChildrenIndentationreduced to 8px. - Tree state persistence: Expanded groups saved to
localStoragevia clientside callback, restored on page load viadash_clientside.set_props. No circular dependency. - SPA navigation: Tree selection uses
dcc.Location(refresh="callback-nav")— page content swaps without full reload, preserving tree state.
[1.2.0] - 2026-04-10
Added
BarChart Component (New)
- Vertical & Horizontal Bars:
layout='vertical'(default) orlayout='horizontal' - Multi-Series: Multiple data series with individual colors side by side
- Stacking:
stackgroup ID on series, withstackOffset('none', 'expand', 'diverging') andstackOrder('none', 'appearance', 'ascending', 'descending', 'reverse') - Bar Labels:
barLabel='value'withbarLabelPlacement('center' or 'outside') - Border Radius: Rounded bar corners via
borderRadiusprop - Dataset Mode:
dataset+dataKeypattern for table-format data (no duplication) - Bar Spacing:
categoryGapRatioandbarGapRatioon band axis for gap control - Reference Lines: Horizontal (
y) and vertical (x) markers for targets, thresholds - Axis Highlight: Configurable
axisHighlightwith 'band', 'line', or 'none' - Tooltip Modes:
tooltip={'trigger': 'axis'}ortooltip={'trigger': 'item'} - Highlighting:
highlightedItemprop (controlled, bidirectional) with per-serieshighlightScope - Custom Colors:
colorspalette prop for series color override - Batch Renderer:
renderer='svg-batch'for large dataset performance - Click Events:
clickData(bar click) andaxisClickData(axis area click) output props - Pro Features (require
licenseKey): initialZoom+showSliderfor zoom with range slidershowToolbarfor zoom/export toolbarbrushConfigfor brush selectionzoomInteractionConfigfor pan/wheel/pinch behaviorszoomDataoutput prop for zoom state callbacks
CandlestickChart Component (New)
- OHLC Candlestick Rendering: Custom SVG candles (body + wicks) built on MUI X Charts Pro composition API
- Array Format:
series=[{data: [[open,high,low,close], ...], upColor, downColor}] - Dataset Format:
dataset+series=[{datasetKeys: {open, high, low, close}}] - Volume Overlay: Optional volume bars below candles via
showVolume=TruewithvolumeKeyorvolumearray - Candle Styling:
bodyWidthRatio(0-1) andwickWidth(px) for custom candle appearance - OHLC Tooltip: Built-in hover tooltip showing Open, High, Low, Close values
- Reference Lines: Support/resistance lines, moving averages markers
- Click Events:
clickDatawith{dataIndex, label, open, high, low, close, timestamp} - Auto Y-Axis Domain: Automatically computes min/max from OHLC data with 5% padding
- Grid Support:
grid={'horizontal': True, 'vertical': True} - Pro Features: Zoom, slider, toolbar (via composition API)
- No Premium Dependency: Works with existing
@mui/x-charts-prov8.24.0 (no@mui/x-charts-premiumrequired)
Demo Pages (7 New)
/barchart-basic— Multi-series, stacked, horizontal, bar labels, rounded corners, negative values/barchart-dataset— Dataset mode with dataKey, bar/category gap control/barchart-stacking— Stack offsets (none, expand, diverging), multiple stack groups, horizontal stacked/barchart-interaction— Click events, axis click, highlighting, axis highlight modes, tooltip triggers/barchart-reference— Target lines, min/avg/max refs, vertical refs, animation, legend, color palette/barchart-pro— Zoom+slider, toolbar, stacked zoom (Pro features)/candlestick— Array format, dataset mode, volume overlay, styling, support/resistance lines, click events
Application UI/UX Redesign
- DMC AppShell Layout: Replaced flat blue top-bar with
dmc.AppShell(header + sidebar navbar + main content area) - Sidebar Tree Navigation:
SimpleTreeVieworganizes all 37 demo pages into 11 component groups with expand/collapse - Dark/Light Mode:
dmc.ColorSchemeToggle(DMC 2.6.1) withdmc.pre_render_color_scheme()to prevent theme flash on load - MUI Charts Dark Mode: Global CSS overrides for axis labels, tick marks, grid lines, legend labels, tooltips, bar labels, and reference lines — all auto-switch via
[data-mantine-color-scheme="dark"] - Theme-Aware Loading Screen: Animated liquid-blob splash screen with 2plot logo, swaps between light/dark assets based on saved preference (
localStorage) - Page Loading Overlay:
/composite-render-bpuses deferred content loading via callback withdmc.LoadingOverlay— shows logo + blur overlay while heavy chart sections build server-side - Home Page Redesign: Responsive 3-column component card grid with
dmc.Paper,dmc.Badge,dmc.CodeHighlightfor installation and usage examples
Changed
- CompositeChart: Custom tooltip components (
MuiAxisTooltip,ExternalAxisTooltip) now use Mantine CSS variables (--mantine-color-body,--mantine-color-text,--mantine-color-default-border) instead of hardcodedwhite/#e0e0e0— tooltips auto-adapt to dark mode - SimpleTreeView: Replaced
Component.defaultPropswith JavaScript default parameters to eliminate React 18 deprecation warning - Composite Render BP page: Deferred chart section building to a callback (from static layout) for instant page load with loading overlay
- Updated component count from 7 to 9
src/lib/index.jsexports BarChart and CandlestickChart- Auto-generated Python wrappers for both components via
dash-generate-components - Upgraded
dash-mantine-componentsfrom>=1.0.0to>=2.6.0inrequirements.txt - All demo pages updated with Mantine CSS variables for dark mode support (card backgrounds, text colors, code blocks, tooltips, borders)
- Pages with
dmc.MantineProviderwrappers (8 files) unwrapped — single root provider inapp.py
Fixed
- Legend labels not switching in dark mode: Added CSS selectors for
.MuiChartsLegend-labeland.MuiChartsLabel-root(HTML span elements used by MUI X Charts v8) - Custom tooltip dark mode in crosshair, highlighting sync, composite, and composite render pages — replaced hardcoded
white/#333/#e0e0e0with theme-adaptive CSS variables - Highlighting demo: Per-Series Highlight Scope example now uses 3 series with distinct
highlightScopeconfigs to clearly demonstrateseriesvsitemhighlight andglobalvsnonefade behaviors - Circular dependency: Replaced server-side nav callbacks with clientside
window.locationnavigation to breakurl.pathname ↔ nav-tree.selectedItemscycle
Documentation
- Updated CLAUDE.md with BarChart and CandlestickChart features
- Updated SKILLS.md with implementation details, prop references, and usage patterns
- Updated README.md with new component listings and examples
[1.1.0] - 2026-04-08
Added
Functions-as-Props Pattern (LineChart, CompositeChart)
resolveFunctionProputility — Mirrors Dash Mantine Components'dashMantineFunctionspattern- Python passes
{'function': 'name', 'options': {...}}as a prop value - React component resolves from
window.dashMuiChartsFunctionsregistry - Users define custom JS functions in
assets/*.jsfiles valueFormattersupport on xAxis/yAxis — Control how axis values are displayed- Accepts DMC-style function reference objects or native JS functions
- Enables custom date formatting, number formatting, and label rendering
- Works with all scale types (time, linear, point, band, etc.)
Built-in Date Formatting (LineChart, CompositeChart)
dateFormatprop on xAxis/yAxis — Format string for tooltip date labels (e.g.'M/d HH:mm')dateTickFormatprop on xAxis/yAxis — Separate shorter format for tick labels (e.g.'M/d')- Prevents label truncation by using compact tick labels while keeping full format in tooltips
- Supported tokens:
YYYY,MMM,MM,M,dd,d,HH,mm - Automatically creates a
valueFormatterinternally — no external JS file required
LiveTradingChart Component (New)
- Real-time streaming chart component for live data visualization
Demo Pages
- New
/linechart-tick-hoverpage — Comprehensive tick, hover, and axis configuration guide - Section 1-4: Date range best practices (week, quarter, year) with point and linear scales
- Section 5a: Angled labels with
dateFormat/dateTickFormaton time scale - Section 5b: Zoom with slider — tick behavior during zoom/pan interactions (Pro)
- Section 5c: Pro zoom with slider preview, brush select, toolbar, and
zoomInteractionConfig - Section 6: Interactive hover events with click data callbacks
- Section 7: Best practices summary reference table
- New
/live-tradingpage — LiveTradingChart demo
Assets
- New
assets/muiChartsFunctions.js— Ships a reusableformatDatefunction for the functions-as-props pattern - Supports format tokens:
YYYY,MMM,MM,M,dd,d,HH,mm - Context-aware: uses
tickFormatoption for tick labels,formatfor tooltips
Changed
- LineChart —
processedXAxisno longer short-circuits whenshowSlideris false - Previously skipped all axis processing (including valueFormatter resolution) when slider was disabled
- Now always processes axes for dateFormat and valueFormatter resolution
- CompositeChart — Same
processedXAxisimprovement as LineChart
Documentation
- Updated CLAUDE.md with new component features and axis formatting documentation
- Key Tick & Zoom Props reference table in the tick-hover demo page
[1.0.0] - 2026-03-19
Added
- Stable release — 6 chart components ready for production use
Changed
- Version bump to 1.0.0 to reflect API stability
- Fixed
package-info.jsonversion tracking (was stuck at0.0.1, causing stale JS bundle caching)
[0.0.8] - 2026-03-12
Added
ScatterChart (New Component)
- Multi-Series: Multiple scatter series with individual colors and marker sizes
- Z-Axis Color Mapping: Color points by a third variable (continuous, piecewise, ordinal)
- Voronoi Interaction:
voronoiMaxRadiusfor proximity-based hover/click - Dataset-Driven:
dataset+datasetKeyspattern for table-format data - Batch Renderer:
renderer='svg-batch'for large datasets - Click Events:
clickDatawith seriesId, dataIndex, x, y coordinates - Log/Sqrt Scales: Full axis scaleType support
CompositeChart (New Component)
- Chart Layering: Mix
type: 'scatter'andtype: 'line'series on a single surface - Custom Composite Tooltip: Axis tooltip shows both line and scatter data via proximity search
- Handles MUI's poor scatter tooltip formatting in composite charts
- Auto-computes proximity threshold from x-axis data spacing
- Reference Lines: Horizontal and vertical markers
- Multi-Axis: Scatter on left axis, line on right axis
- Zoom/Pan (Pro):
initialZoom,showSlider,zoomInteractionConfig - Toolbar (Pro):
showToolbar=Truefor zoom/export controls - Zoom Slider Preview:
preview: {markerSize}on scatter series for slider preview markers - Time Scale Support: Automatic epoch ms to Date object conversion for
scaleType: 'time' - Controlled Highlighting:
highlightedItemprop for programmatic control - Click Events: Separate click handlers for scatter and line series
Demo Pages
- New
/scatterpage with multi-series, z-axis color mapping, voronoi, and dataset-driven examples - New
/compositepage with scatter+trend, reference lines, multi-axis, and zoom-enabled examples - DMC sliders for interactive marker size control (chart and preview)
- Multi-color anomaly scatter (red critical / yellow warning)
Changed
- Updated component count from 4 to 6
src/lib/index.jsexports ScatterChart and CompositeChart
Documentation
- Updated CLAUDE.md with ScatterChart and CompositeChart features
- Updated SKILLS.md with new component documentation and patterns
- Updated README.md with new component listings
[0.0.7] - 2025-01-30
Added
Synchronized Tooltips with Custom Overlays
- Custom tooltip overlay system for true synchronized tooltips across multiple charts
- MUI X Charts limitation: native tooltips only appear on the hovered chart
- Solution: Custom Dash HTML divs positioned absolutely over each chart
- Both charts show tooltips simultaneously when hovering on either one
- New demo implementation in
/highlighting-syncpage showing: - Revenue + Expenses dual LineChart with synchronized custom tooltips
- Tooltips positioned using CSS
calc()for responsive layouts - Visual highlighting (marks, axis bands) continues to sync via
highlightedItem
LineChart Highlighting Improvements
- Improved callback patterns for highlight synchronization
- Better handling of highlight state when mouse leaves chart area
- Custom tooltip content generation for each chart in sync scenarios
Changed
- Highlighting Sync Demo: Updated to use custom tooltip overlays instead of
tooltipItem - Disabled MUI's built-in tooltips:
tooltip={'trigger': 'none'} - Added absolutely-positioned custom tooltip divs
- Tooltip x-position calculated from data index and chart margins
- Callback now listens to
highlightedItem(more reliable thantooltipItemfor sync)
Documentation
- Updated SKILLS.md with custom synchronized tooltip pattern
- Documented MUI X Charts tooltip limitation (GitHub issues #14455, #17555)
- Added code examples for custom tooltip positioning
[0.0.6] - 2025-01-29
Added
LineChart - Controlled Highlighting
- New
highlightedItemprop for controlled item highlight state - Programmatically highlight specific data points
- Bidirectional: updates on hover and accepts external values
- Object format:
{'seriesId': 'series-id', 'dataIndex': 0} - New
highlightedAxisprop for controlled axis highlight state - Programmatically highlight specific axis positions
- Array format:
[{'axisId': 'x-axis', 'dataIndex': 2}] - New
onHighlightChangeandonHighlightedAxisChangecallbacks (internal)
LineChart - Per-Series Highlight Scope
- Series-level
highlightScopeconfiguration highlight:'none'|'item'|'series'fade:'none'|'series'|'global'- Different highlight/fade behaviors per series
LineChart - Toolbar (Pro)
- New
showToolbarprop to display chart toolbar - Provides zoom/export controls
- Requires MUI X Pro license
LineChart - Synchronized Tooltips
- New
tooltipItemprop for controlled tooltip state - Enables synchronized tooltips across multiple charts
- Object format:
{'type': 'line', 'seriesId': 'series-id', 'dataIndex': 0} - Bidirectional: updates on hover and accepts external values
- Combined with
highlightedItemfor full visual synchronization
PieChart - Controlled Highlighting
highlightedItemprop now works as both input and output- Enables synchronized highlighting across multiple charts
- Object format:
{'seriesId': 'auto-generated-id-0', 'dataIndex': 0}
Demo Pages
- New
/linechart-highlightingpage demonstrating: - Controlled item highlights with buttons
- Controlled axis highlights
- Per-series highlightScope configuration
- New
/highlighting-syncpage demonstrating: - LineChart + PieChart synchronization
- Dual LineChart axis synchronization
- Cross-chart highlight coordination
Changed
- LineChart now uses controlled mode for highlighting by default
- Always passes
highlightedItem(asnullwhen not set) - Ensures proper MUI controlled mode initialization
- Callback patterns improved to avoid echo issues
- Separated button-triggered and display callbacks
- Use
dash.no_updateto prevent callback loops in sync scenarios
Fixed
- Item highlighting not triggering callbacks in composition API
- Controlled highlight state not syncing with MUI internal state
- Echo issues in highlight synchronization callbacks
Documentation
- Updated CLAUDE.md with v0.0.6 features
- Updated SKILLS.md with highlighting patterns and examples
[0.0.5] - 2025-01-26
Added
LineChart - Reference Lines
- Full support for
ChartsReferenceLineAPI - Horizontal reference lines via
yprop (targets, thresholds, averages) - Vertical reference lines via
xprop (dates, events, milestones) - Props:
x,y,axisId,label,labelAlign,lineStyle,labelStyle,spacing - Support for string/number/Date values on both axes
- Multi-axis support with
axisIdfor biaxial charts - New demo page:
/linechart-referencelines
LineChart - Brush Selection (Pro)
- New brush interaction for range selection on charts
brushConfigprop:{enabled, preventTooltip, preventHighlight}brushOverlayprop:'none'|'default'|'values''default': Standard MUI selection rectangle'values': Custom overlay showing start/end values with difference and percentage changebrushSeriesIdprop: Specify which series to use for value calculationsbrushDataoutput prop: Selection coordinates for callbacks- New demo page:
/linechart-brush
LineChart - Axis Highlight Configuration
- New
axisHighlightprop to configure hover highlighting - Options:
{x: 'none'|'line'|'band', y: 'none'|'line'} - Default:
{x: 'line', y: 'none'}
Changed
referenceLines.ynow acceptsstring | number(previously onlynumber)- Enables Date string values for time-based reference lines
Documentation
- New demo page:
pages/linechart_brush.py - New demo page:
pages/linechart_referencelines.py - Updated SKILLS.md with brush and reference line documentation
- Updated CLAUDE.md with new LineChart features
[0.0.1] - 2025-01-10
Added
Components
- LineChart - Full-featured line and area charts
- Multiple series support with independent Y-axes (biaxial charts)
- Interactive zoom & pan (requires MUI X Pro license)
- Line, area, and stacked area visualizations
- 10+ curve interpolation options (linear, monotone, natural, step, catmull-rom, bump, etc.)
- Configurable grid, legend, and margins
- Click events for axis, mark, line, and area interactions
- Zoom slider for range selection
- Loading state overlay
- Animation controls with prefers-reduced-motion support
- PieChart - Pie, donut, and nested pie charts
- Single series (simple pie/donut) or multiple series (nested/concentric pies)
- Customizable arc geometry (inner/outer radius, corners, padding)
- Arc labels with value, label, or formattedValue display
- Configurable start/end angles for half-pie and gauge charts
- Highlight interactions with global fade effects
- Click and hover event handling
- Full animation support
- Heatmap - Matrix/grid visualization (requires MUI X Pro license)
- Color-coded cell visualization
- Continuous or piecewise color scales
- Custom cell styling (rounded corners, gaps, value display)
- Band-scale axes for categorical data
- Cell click detection with coordinates
- Highlight interactions
- SparklineChart - Compact inline charts
- Ultra-compact design (36px default height)
- Line or bar plot types
- Area fill support
- Tooltip and highlight interactions
- Controlled highlight index for component synchronization
- Curve interpolation options
Features
- Full TypeScript-like type safety with Python TypedDict
- Auto-generated Python wrappers from React PropTypes
- Comprehensive prop documentation
- Click event data with timestamps
- Dash 3.x compatibility
- React 18+ support
Documentation
- Basic usage examples in
usage.py - Multi-page demo application with interactive examples
- Property explorer pages for Pie, Heatmap, and Sparkline components
Technical
- Webpack 5 build configuration
- MUI X Charts v8.24.0 integration
- MUI X Charts Pro v8.24.0 for advanced features
- Emotion styling support
Component License Requirements
| Component | License Required |
|---|---|
| LineChart (zoom/pan) | MUI X Pro |
| BarChart (zoom/brush) | MUI X Pro |
| CandlestickChart (zoom) | MUI X Pro |
| PieChart | Community (Free) |
| ScatterChart | Community (Free) |
| CompositeChart (zoom/pan) | MUI X Pro |
| Heatmap | MUI X Pro |
| SparklineChart | Community (Free) |
| LiveTradingChart | Community (Free) |
[Unreleased]: https://github.com/pip-install-python/dash-mui-charts/compare/v1.2.1...HEAD [1.2.1]: https://github.com/pip-install-python/dash-mui-charts/compare/v1.2.0...v1.2.1 [1.2.0]: https://github.com/pip-install-python/dash-mui-charts/compare/v1.1.0...v1.2.0 [1.1.0]: https://github.com/pip-install-python/dash-mui-charts/compare/v1.0.0...v1.1.0 [1.0.0]: https://github.com/pip-install-python/dash-mui-charts/compare/v0.0.8...v1.0.0 [0.0.8]: https://github.com/pip-install-python/dash-mui-charts/compare/v0.0.7...v0.0.8 [0.0.7]: https://github.com/pip-install-python/dash-mui-charts/compare/v0.0.6...v0.0.7 [0.0.6]: https://github.com/pip-install-python/dash-mui-charts/compare/v0.0.5...v0.0.6 [0.0.5]: https://github.com/pip-install-python/dash-mui-charts/compare/v0.0.1...v0.0.5 [0.0.1]: https://github.com/pip-install-python/dash-mui-charts/releases/tag/v0.0.1
Note for AI agents: This is the static, prerendered view of an interactive Dash application served because we detected a non-JS user agent. Full prose docs:
- /changelog/llms.txt — LLM-friendly documentation
- /sitemap.xml
- /robots.txt