Version history of dash-mui-charts, rendered from CHANGELOG.md.

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

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

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

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.

straight to the content, visible only on keyboard focus.

lastmod: /api now declares one in frontmatter, /changelog derives it from the newest dated release heading in CHANGELOG.md.

LOGO_STYLE, WORDMARK, WORDMARK_COLOR, WORDMARK_VISIBLE_FROM), so a fork edits the identity block rather than the component.

Fixed

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.

/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

/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

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.

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 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.

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.

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.

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

icon with an accessible name.

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.

control in components/ carry names; tests/test_nav_contract.py greps for the next one that does not.

Fixed

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.

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.

the whole document at phone width. One stylesheet rule for every container a code block can sit in, never a per-page override.

a mount-on-open transition, and the Admin callback's mobile target exists on every load.

(/changelog, home, the admin pages), which were rendering in the docs column beside an empty gutter.

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

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.

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.)

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

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.

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.

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.

host serves, measured with a real vendor UA rather than assumed.

Floors

bust), run.py's boot floor, and both version assertions in ci.yml.

The card X can read, and one fleet Python

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.

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.

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

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.

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.

(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.

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.

.. 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.

version-updates restricted to dash*/plotly*/markdown2dash, the network drift alarm, without the floor-raise noise on every other pin.

a Node layer to drop — the component bundle is committed.

Round 3 — hashed selectors extinct, wide tables contained

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.

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.

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

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.

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.

(1.6.6): modelviewer.2plot.dev and excalidraw.2plot.dev joined the canonical directory when they went live in the gate wave. 18 peers.

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.

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.

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.

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.

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.

{app, active} ping the hub board's "live now" column reads. Display-only and ephemeral; the daily rollup stays the source of the numbers.

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.

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.

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.

(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)

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).

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)

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).

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.

var(--mantine-color-default) — it was unreadable in dark mode.

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)

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/.

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)

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.

(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.

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)

(/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.

_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).

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)

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.

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.

json import (cell-click display would have crashed); restored, plus inherited f-string debt cleaned rather than carried into docs/.

llms.txt placeholder stubs (both props pages) became real documents.

Boilerplate migration — M0 (shell + chart-parity gate + Sparkline pilot)

(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.

(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.

pages + 11 exec example modules, callbacks verbatim, same endpoints. The three placeholder llms.txt stubs became real documents for free.

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).

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.

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.

family map ↔ registry parity, per-page aside ad slots, old-shell assets stay deleted; identity/card/version/traffic suites unchanged.

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)

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.

app ships no auth surface (no dash-clerk-auth anywhere); nothing to configure until it ever gains one.

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)

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.

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.

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.

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.

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).

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.

CVE-2024-6827 / CVE-2024-1135 (request smuggling); asserted inside the Docker image by CI so it cannot silently regress.

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)

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.

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.

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.

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)

"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).

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.

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.

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)

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.

routes (component tree, ids, chart-mount counts, callback census, HTTP sweep) against a committed baseline; every migration phase must keep it green.

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.

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)

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).

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)

Demo page /time-clock

Demo page /time-clock-lab (TimeClock Lab)

Changed

Professional "View code" across demo pages


[1.2.3] - 2026-05-15

Fixed

TreeViewPro — Cell editing

Demo page /tree-pro


[1.2.2] - 2026-05-13

Added

TreeViewPro — Per-Item Slider + Kebab Menu

Demo page /tree-pro rebuilt

Notes


[1.2.1] - 2026-04-11

Fixed

Added

Changed


[1.2.0] - 2026-04-10

Added

BarChart Component (New)

CandlestickChart Component (New)

Demo Pages (7 New)

Application UI/UX Redesign

Changed

Fixed

Documentation


[1.1.0] - 2026-04-08

Added

Functions-as-Props Pattern (LineChart, CompositeChart)

Built-in Date Formatting (LineChart, CompositeChart)

LiveTradingChart Component (New)

Demo Pages

Assets

Changed

Documentation


[1.0.0] - 2026-03-19

Added

Changed


[0.0.8] - 2026-03-12

Added

ScatterChart (New Component)

CompositeChart (New Component)

Demo Pages

Changed

Documentation


[0.0.7] - 2025-01-30

Added

Synchronized Tooltips with Custom Overlays

LineChart Highlighting Improvements

Changed

Documentation


[0.0.6] - 2025-01-29

Added

LineChart - Controlled Highlighting

LineChart - Per-Series Highlight Scope

LineChart - Toolbar (Pro)

LineChart - Synchronized Tooltips

PieChart - Controlled Highlighting

Demo Pages

Changed

Fixed

Documentation


[0.0.5] - 2025-01-26

Added

LineChart - Reference Lines

LineChart - Brush Selection (Pro)

LineChart - Axis Highlight Configuration

Changed

Documentation


[0.0.1] - 2025-01-10

Added

Components

Features

Documentation

Technical


Component License Requirements

ComponentLicense Required
LineChart (zoom/pan)MUI X Pro
BarChart (zoom/brush)MUI X Pro
CandlestickChart (zoom)MUI X Pro
PieChartCommunity (Free)
ScatterChartCommunity (Free)
CompositeChart (zoom/pan)MUI X Pro
HeatmapMUI X Pro
SparklineChartCommunity (Free)
LiveTradingChartCommunity (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: