Changelog
Version history of flexlayout-dash. The timeline on this page is rendered from
CHANGELOG.md, reproduced below.
All notable changes to flexlayout-dash will be documented in this file. ("DashDock" was this project's original name; the dash-dock-* CSS classes it left behind are still public API and are deliberately not renamed.)
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[2.0.0] - 2026-08-02
First PyPI upload since 1.0.0, renumbered from a prepared-but-never-published 1.2.0. Neither 1.1.0 (finalized 2026-06-22) nor 1.2.0 was ever uploaded, so this release carries 1.1.0's breaking dash_flex_layout → flexlayout_dash import rename to PyPI for the first time — which under semver is a major bump, not a minor one. Everything in this section and the 1.1.0 section below is new to anyone upgrading from 1.0.0.
Added
- The project's own brand mark.
scripts/make_brand_assets.py(the
dash-emoji-mart pattern) draws the dock glyph — indigo panels with a tab notch on a dark tile, the layout the component actually produces — and derives every icon surface from it: the manifest icons (favicon-192/512.png), the opaque apple-touch-icon.png, the multi-size favicon.ico, and site.webmanifest itself from lib/constants.py. Replaces the generic pip-install-python package-box icon set that arrived with the favicon folder. templates/index.html now declares the icon links explicitly, and make_social_card.py --artwork defaults to the new 512 mark.
- The 2plot network standard, in full (the satellite pass proven on
2plot.ai, 2plot.dev, boilerplate.2plot.dev and leaflet.2plot.dev), landed together with this site's FIRST deploy to https://flexlayout.2plot.dev:
.github/workflows/ci.ymlreshaped to the network baseline (CI on
pull_request/workflow_call only — main is owned by the new cd.yml, which deploys via a Render hook, waits for SUSTAINED health, then runs the live battery) plus an actionlint gate, a zero-secret pytest job, and a docker build→fingerprint→boot→battery job. release.yml (tag-driven PyPI publish) is unchanged and independent.
scripts/network_smoke.py+scripts/smoke_live.py— the same named
checks against the CI container and production, including the social card's REAL pixels on the CDN (IHDR vs the declared width/height).
tests/— 54-test secretless suite (site identity, social card +
installable-app surfaces, internal-traffic contract, the in-process battery run, and package-level checks replacing the stale test_app.py that still imported the deleted root app.py).
- Internal-traffic contract: the tracker drops
2plot-internalUAs at
WRITE time (before bot classification) and never stores /healthz; the ad fetch and the signed rollup POST send internal_ua(...) so the hub stops counting this satellite's machinery as its own bots.
- Ad-network app id converged to the short key
flexlayout(hub
2.2.0 rule) — lib/ad_client.py defaulted to the legacy flexlayout-dash spelling.
scripts/make_social_card.py+ the rendered 1200x630 card
(build/social-cards/flexlayout.2plot.dev.png), served from cdn.2plot.ai — never from the app, whose cold starts would poison scraper caches.
- Network bulletin wired (
lib/bulletin.py, the Mode-B fix from
network-bulletin-rollout.md): the hub's announcement feed now renders in the llms.txt viewer header. The package never reads NETWORK_BULLETIN_URL itself — run.py feeds it to configure_bulletin() with app_id=flexlayout (derived from satellite_reporter.app_key, never typed), prints a boot line saying which state it is in, and tests/test_bulletin.py fails if the call is ever commented out. scripts/smoke_live.py warns (never fails) when the banner shows the unwired fallback. Verified in the container: both hub tips render and "No announcements." is gone.
- Ported dash-email's
tests/test_smoke_live.py(the smoke script run
in-process with fetch stubbed, including the reshaped-card and dead-peer cases) and its test_no_dash_placeholder_is_named_inside_a_comment — a {%…%} named inside an HTML comment is a second, hidden copy of that block, the source of dash-email's two empty og:image tags. Suite is now 73 tests.
modelActionprop — imperative FlexLayout actions on the LIVE model. Dash callbacks can now add/remove/select/rename tabs and adjust weights at runtime without replacing themodelprop (which is ignored underuseStateForModel=Trueand re-mounts every tab otherwise). Shape:{type, nonce, ...args}with a changednonceper action. Types:addNode({json, toNodeId, location?, index?, select?}; re-adding an existing tab id selects it instead of throwing),deleteTab,selectTab,renameTab,updateNodeAttributes,adjustWeights. Actions apply viamodel.doAction(Actions.*), so sibling tabs keep their DOM — no re-mounts. Adfl.Tabchild whose tab isn't in the model is (as always) silently skipped, so keep dynamic-tab children inchildrenpermanently and letaddNode/deleteTabcontrol visibility. Motivated by a broadcast-monitoring app that opens an editor tab on demand.- Documentation site:
.env.exampledocumenting every environment key the
site reads (canonical base URL, ad network, satellite traffic reporting, analytics). run.py now calls load_dotenv() before the lib/ imports — lib/ad_client.py reads its env at import time, so a later load was silently ignored — and python-dotenv joined the site's requirements.
Changed
- Packaging metadata moved from
setup.pytopyproject.toml, which is now
the authoritative version. MANIFEST.in became a strict allowlist, so the documentation site, scripts/, render.yaml and the R/Julia backends can no longer leak into the PyPI artifact. Declared the real dependencies for the first time: dash>=4.1,<5 and typing_extensions>=4.0 — the latter is imported unconditionally by the generated wrappers and until now resolved only as a transitive dependency of Dash. Added requires-python = ">=3.9", classifiers and project URLs, and the 1 MB react-docgen metadata.json is no longer shipped.
LICENSEnow contains the MIT text. It had been a 0-byte file while the
package metadata claimed MIT.
- Repository reorganised to the standard satellite layout. The documentation
site moved out of documentation/ to the repo root (run.py, docs/, pages/, lib/, components/, assets/, requirements.txt), matching dash-email, dash-emoji-mart, dash-leaflet2 and the documentation boilerplate. Doc pages are now one folder per page — docs/<slug>/<slug>.md plus docs/<slug>/example.py — and the home page is docs/home/ with endpoint: "/" rather than a hand-rolled pages/home.py. Nothing about the published package changed; MANIFEST.in prunes every one of those directories by name and scripts/check_release.py now asserts it does.
- Documentation site: upgraded
dash-improve-my-llmsto 2.3.4 (the
network standard — resolve_site_title is what gives the /llms.txt H1 and the llms viewer chip one identity source), and replaced the 17 KB hand-rolled templates/index.html with a minimal one that declares ONLY what Dash omits: og:site_name/og:url, the og:image:* auxiliaries, the manifest + apple-touch-icon + theme-color. The template was briefly deleted outright on the theory that the prerender covers OG — it does, but only on the crawler path, and social scrapers (facebookexternalhit etc.) take the browser path; tests/test_social_card.py now pins the template in place. The favicon set moved under assets/favicon/, whose site.webmanifest arrived naming the HUB and now names this site.
- Documentation site: gunicorn floor raised to >=23 (21.x carried the
request-smuggling CVEs CVE-2024-6827 / CVE-2024-1135). markdown2dash 0.1.2 declares gunicorn<22, so it left requirements.txt and installs as a second pip install --no-deps command (Dockerfile, CI and scripts/compat_matrix.py all updated); CI fingerprints assert the dodge keeps working inside the shipped image.
- README: added the 2plot.ai logo above the header, and switched the image
references to absolute raw.githubusercontent.com URLs — relative paths render on GitHub but appear broken on the PyPI project page.
- Stale
documentation/references cleaned out ofpyproject.toml's
comments and the README's development section (cd documentation && python run.py) — the site has lived at the repo root since the reorganisation above, and the comments still described the old layout.
Fixed
- Documentation site: the table of contents rendered empty on every page
(and with it the aside's ad slot never showed). The .. toc:: directive — network convention — links ###-and-deeper headings only, and this site's docs used ## for sections, so the TOC found nothing and the aside rendered blank. All six documented pages now use ### sections, matching dash-email and the boilerplate. (The ad slot itself was wired correctly: the hub returns 204 for app=flexlayout because no campaign targets this app yet — that is /admin/ad-board data on the hub, not satellite code.)
- "Found on the email pass" items (satellite-rollout-kickoff):
lib/constants.BASE_URL now accepts the network-shared APP_BASE_URL first and the legacy FLEXLAYOUT_BASE_URL second, with render.yaml setting BOTH (removing one of two names from a live service is how a satellite deindexes itself); render.yaml's AD_APP_ID still carried the legacy flexlayout-dash and was overriding the code default — now the short key flexlayout; and the two-command markdown2dash install now appears in both README quickstarts and CLAUDE.md, not just the Dockerfile/CI.
- **Documentation site: invalid
<p>nesting on every page with inline
emphasis.** markdown2dash 0.1.2 renders **bold**, *italic* and ~~struck~~ as dmc.Text, which is a <p>, inside the paragraph's own <p> — React warned validateDOMNesting(...): <p> cannot appear as a descendant of <p> on /, /basic, /callbacks and /getting-started. lib/renderer.py subclasses the renderer to emit <span> (dmc.Text's span prop) instead. Upstream bug, shared by every boilerplate-derived site.
- Documentation site: the "Copy llms.txt URL" button was missing from every
page except the home page — the .. llms_copy:: directive was registered but never used in the markdown.
- Documentation site: the site title read "Dash" / "Home".
Dash(title=...)
was never set, so the browser tab, og:title/twitter:title and the llms viewer's brand chip all showed Dash's default; and the root /llms.txt H1 rendered # Home — the home page's navbar name — instead of the site name. The fix is the network identity standard: one SITE_BRAND = "flexlayout-dash — resizable panel layouts for Dash" in lib/constants.py, carried verbatim by Dash(title=), register_page_metadata(path="/", name=SITE_BRAND) (2.2+ merges, so the home page's frontmatter llms_doc is untouched), the home markdown H1 and the webmanifest — resolved on every surface by 2.3.4's resolve_site_title, and pinned by tests/test_site_identity.py.
release.ymlwas missing the two-command markdown2dash install in its
smoke step — caught on the workflow's first-ever execution (the v2.0.0 tag; no tag had ever been pushed before, so the step predating the gunicorn>=23 dodge was never exercised). The Dockerfile, ci.yml and compat_matrix.py all carried the dance; the release path now matches, and the v2.0.0 tag was moved to the fixed commit before anything was published.
- Documentation site: robots.txt now actually blocks AI-training crawlers.
block_ai_training was False while the comment above it claimed the opposite. Enabling it only became safe with dash-improve-my-llms 2.3.3, which separates training crawlers (GPTBot, ClaudeBot, CCBot — disallowed) from the user-triggered and search fetchers (Claude-User, Claude-SearchBot, ChatGPT-User, OAI-SearchBot — still allowed).
Removed
- No free/premium split. There is no tab limit, licence-key check or paid
edition anywhere in the package or the compiled bundle — verified for 1.2.0 across flexlayout_dash/, src/ and flexlayout_dash.min.js. Releases up to 1.0.0 were described on PyPI as a free build "limited to 3 tabs" alongside a premium one; this release's description supersedes that.
- The generated R and Julia bindings (
R/,man/,inst/,
deps/, DESCRIPTION, NAMESPACE, Project.toml, src/jl/), and --r-prefix/--jl-prefix dropped from npm run build:backends, which is what regenerated them. No other component in the network ships them.
- The unused webpack dev harness (
webpack.serve.config.js,
src/demo/, npm start, the stale root index.html), a stray root __init__.py still wired to the pre-1.1.0 dash_flex_layout.min.js, and the boilerplate _validate_init.py / review_checklist.md.
[1.1.0] - 2026-06-22
Fixed
- Eliminated unnecessary tab-content re-mounts on internal FlexLayout model changes. With
useStateForModel=False, everyonModelChangeround-trip recreated theModelviaModel.fromJson, re-running the factory and re-mounting all portaled tab content. Now model-prop echoes of FlexLayout's own internal changes are skipped; only a genuinely Dash-originated model recreates the Model. Fixes content flashing/resetting and re-mount storms for stateful tab children (canvases, editors, drag state, etc.).setPropsstill fires on every change, so downstreamInput(component, 'model')callbacks are unaffected. - Dock no longer escapes its container.
.dash-dock-containeris nowposition: relative, so FlexLayout's absolutely-positioned layout stays inside the component's own box instead of overflowing to the nearest positioned ancestor (e.g. rendering behind a fixedAppShellnavbar/sidebar). Consumers only need to give the component an explicit height via itsstyleprop.
Changed
- Unified the Python import name under
flexlayout_dash. Previously the package installed/bundled asflexlayout-dash(flexlayout_dash.min.js,window.flexlayout_dash) but its Python import package and backend were stilldash_flex_layout. The import name is nowflexlayout_dasheverywhere — consistent with the bundle and the npm/webpack library name. - BREAKING: code that did
import dash_flex_layoutmust change toimport flexlayout_dash. The PyPI distribution name is unchanged — you stillpip install flexlayout-dash. - Regenerated the Python/R/Julia backends and updated
setup.py,MANIFEST.in, the build (build:backends), and webpack config to theflexlayout_dashnamespace; removed the staledash_flex_layout/package directory.
[0.0.2] - 2026-01-04
Fixed
- Tab content vanishing bug: Resolved critical issue where tab content would disappear after Dash callbacks updated component props. The root cause was FlexLayout's internal caching of React elements with original props, which conflicted with Dash's callback-based prop updates.
Changed
- Rendering architecture: Implemented portal-based rendering to decouple Dash's component lifecycle from FlexLayout's mounting/unmounting behavior. The factory function now creates empty container elements, and actual content is rendered via React portals into these containers.
- Removed API key validation: DashDock is now completely free with all features unlocked. Removed
apiKey,apiUrl, andfreeTabLimitprops. - Removed premium/free tier system: Deleted
apiClient.tsandtabAnalyzer.tsutility files. All FlexLayout features are now available without restrictions.
Added
TabPortalContainercomponent for managing portal target registration- Technical documentation in
.claude/flexlayout-rendering.mdexplaining the rendering architecture and callback patterns - Apple Liquid Glass Theme: WWDC 2025-inspired glassmorphism theme with light and dark variants
dash-dock-liquid-glass-lightanddash-dock-liquid-glass-darkCSS classes- Backdrop blur with
blur(16px) saturate(180%)effect - Transparent rgba backgrounds (never opacity)
- Layered box shadows (outer depth + inner glow)
- Specular highlight gradients (135deg)
- Accessibility: high contrast mode, reduced motion, focus states
- Interactive Theme Control Panel (
pages/theming.py): - Live theme selector (Light, Dark, Glass Light, Glass Dark)
- Component props controls (useStateForModel, supportsPopout, realtimeResize, debugMode)
- Model options controls (tabEnableClose, tabEnableFloat, splitterSize, etc.)
- Splitter color customization with live preview
- CSS documentation and props reference tables
Technical Details
Portal-Based Rendering Solution
The previous direct rendering approach failed because:
- FlexLayout calls
factory()once and caches the returned React element - When Dash callbacks update props, React updates the rendered DOM
- When FlexLayout remounts a tab, it uses the cached element with original props
The new portal-based approach:
- Factory returns empty
TabPortalContainercomponents - Containers register themselves in a ref map when mounted
- Dash children are rendered via
ReactDOM.createPortal()into these containers - Portal content is managed by React's reconciliation, not FlexLayout
Recommended Model Configuration
For reliable callback behavior, set tabEnableRenderOnDemand: false in your model:
model = {
"global": {
"tabEnableRenderOnDemand": False, # Keep all tabs mounted
"tabEnableClose": False,
"tabEnableFloat": True,
},
# ...
}
Callback Best Practices
- Static structure: Define all possible components in layout, control visibility via style props
- Prop updates: Update component data/style props instead of replacing children
- State stores: Use
dcc.Storeto manage shared state across tabs
See .claude/flexlayout-rendering.md for detailed documentation.
[0.0.1] - 2026-01-04
Added
- Initial release of DashDock component
- FlexLayout-React integration for dockable, resizable, and floatable panels
- Drag-and-drop tab management
- Maximize, close, and pop-out tab capabilities
- Mantine theme integration (light/dark mode)
- Dash 2 and Dash 3 compatibility
DashDockmain component with model-based configurationTabwrapper component for content- Multi-page example application with:
- Basic usage examples
- Advanced layout configurations
- Theme integration demos
- Callback interactivity examples
- Comprehensive documentation in
.claude/folder
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