Version history of flexlayout-dash, rendered from CHANGELOG.md.

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

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.

2plot.ai, 2plot.dev, boilerplate.2plot.dev and leaflet.2plot.dev), landed together with this site's FIRST deploy to https://flexlayout.2plot.dev:

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.

checks against the CI container and production, including the social card's REAL pixels on the CDN (IHDR vs the declared width/height).

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

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.

2.2.0 rule) — lib/ad_client.py defaulted to the legacy flexlayout-dash spelling.

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

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.

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

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.

package metadata claimed MIT.

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.

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.

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.

references to absolute raw.githubusercontent.com URLs — relative paths render on GitHub but appear broken on the PyPI project page.

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

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

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.

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.

page except the home page — the .. llms_copy:: directive was registered but never used in the markdown.

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.

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.

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

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.

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.

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

Changed

[0.0.2] - 2026-01-04

Fixed

Changed

Added

Technical Details

Portal-Based Rendering Solution

The previous direct rendering approach failed because:

  1. FlexLayout calls factory() once and caches the returned React element
  2. When Dash callbacks update props, React updates the rendered DOM
  3. When FlexLayout remounts a tab, it uses the cached element with original props

The new portal-based approach:

  1. Factory returns empty TabPortalContainer components
  2. Containers register themselves in a ref map when mounted
  3. Dash children are rendered via ReactDOM.createPortal() into these containers
  4. 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

  1. Static structure: Define all possible components in layout, control visibility via style props
  2. Prop updates: Update component data/style props instead of replacing children
  3. State stores: Use dcc.Store to manage shared state across tabs

See .claude/flexlayout-rendering.md for detailed documentation.

[0.0.1] - 2026-01-04

Added

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: