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

Changelog

Version history of dash-pannellum. The timeline on this page is rendered from CHANGELOG.md, reproduced below.


Unreleased

The documentation site. The dash-pannellum package is unchanged; everything here is pannellum.2plot.dev. (Site-only work has not been changelogged since 2.0.0; this section starts doing so, because the first entry below changes numbers the hub already publishes.)

Changed

The floor is now dash-improve-my-llms>=2.8.0 and lib/analytics_tracker.py no longer carries its own User-Agent list — it delegates to the package's classify(), the same vendor registry /robots.txt is rendered from. Consequence on adoption day: human_hits DROPS and bot_hits RISES. UA-less and library clients (httpx, Go-http-client, node-fetch, an empty User-Agent) move from the human lane to the crawler lane, and ClaudeBot is filed as training rather than search — it is Anthropic's training crawler, which this repo's own run.py comment had said for months six lines from where the list ignored it. The hub's day-over-day view will show a step. That is the number becoming true, not a regression.

no longer Disallows GPTBot, ClaudeBot or CCBot, and the package's middleware stops answering 403 on the browser document and /healthz for them. The wall decided by vendor CLASS what nobody could account for; now every corpus read is a ledger row and the tool is per-vendor policy — block or meter ONE vendor whose rows justify it, never the class. Measured on this host before the flip: both UAs got 403 on / and /healthz, 200 on /llms.txt. Every one of those 403s was this app's own middleware; there is no edge rule.

header both spelled the repo dash-pannellum; it is dash_pannellum. The dead spelling was also in the JSON-LD codeRepository, the llms-github-repo meta tag and the home page. All fixed. package.json still carries it — that is the package lane and needs a rebuild.

the one surface the 2.0.0 identity pass missed.

Added

hands the app one row per corpus document it serves — tier, verdict, bytes, vendor, verification — and AnalyticsTracker.record_read keeps it as a reads list in the same ledger file as visits, with the same buffer, lock, flush cadence and retention. reads is joined by the rollup, never summed into human_hits / bot_hits / pages. client_ip is dropped unless ANALYTICS_KEEP_CLIENT_IP=1.

vendors[] (one row per vendor × verification × policy, with a per-tier breakdown) and reads. Every v3 key is byte-identical.

vendor for the picked day, behind the control board's exact gate and failing closed the same way. This is NOT the /analytics/traffic dashboard removed on 2026-08-02: that page was a second, staler view of the visits the hub already folds. This one shows the read table, which did not exist then, and answers a question the hub cannot — "does ClaudeBot actually get the corpus from THIS host?" Plain tables, no charts, no interval callback.

a hand-edited list: Home · Changelog → the app's own sections from each page's frontmatter category: + order: → API → Resources → Admin (owner-only, absent from the tree for everyone else). page_order and excluded_links are gone from components/navbar.py, which no longer holds anything this fork edits. The sections are Getting started · Panoramas · Interaction.

retired), community.plotly.com, and the pre-network "Other Apps I've built" block (Plotly.pro, GeoMapIndex), which was hand-typed and had drifted. Resources is now third-party only — dmc and Pannellum, the upstream project this component wraps. The network is listed once, in the top bar's Other Apps menu, generated from lib/network_directory.

YouTube. Every icon labelled.

menu, and search fed from the same registry the sidebar uses (so it can never list an admin or hidden page).

and the code-block copy button all have accessible names. The audit found each of them nameless.

code scroll or wrap inside their own box instead of dragging the whole document sideways at phone width.

ledger's own days, with presets) in place of a dcc.Dropdown, and a People section carrying the day's human numbers above the crawler tables — humans never enter the read ledger, and the page now says so.

and only CD's deploy job writes it: a fast-forward push of the run's own sha after the CI matrix is green. A push to main is a candidate, not a deploy, and main ahead of release means an uncertified push is pending. The Render deploy-hook step is gone from cd.yml; verify now runs only on a successful deploy and asserts /healthz build == github.sha itself, so a verify can no longer pass green against the previous build.

2.0.0 — 2026-08-02

The revival ships to PyPI — the first published release since 0.0.6. Everything from 0.1.0 through 0.4.0 below lands in this version; the number leaps to 2.0.0 to mark the break: Dash 4.2+ / React 18 required, and the 0.x line belongs to the original Dash 1/2-era component.

pannellum.2plot.dev joins the 2plot network. The component itself is unchanged since 0.4.0 (packaging metadata modernized: SPDX license expression, setuptools>=77); the rest of this entry is the documentation site reaching the network standard proven on 2plot.ai, 2plot.dev, boilerplate, leaflet, email, flexlayout and llms — deployed live at https://pannellum.2plot.dev on 2026-08-02.

The site now states one identity everywhere

dash-pannellum — 360° panoramas for Dash is the brand on every surface — <title>, the /llms.txt H1, the llms viewer's chip, og:site_name, the webmanifest, the home page H1 and the README. The package name leads because it is the string that finds the thing on PyPI; "Pip Install Python" is the byline, in the description. Previously every surface said "Dash Pannellum", a name that matches no installable package.

AI/LLM + SEO surfaces rebuilt on dash-improve-my-llms ≥ 2.3.4

The vendored 2.0.0 tarball is gone; the package installs from PyPI and run.py refuses to boot below the 2.3.4 floor. Every register_page now passes image_url= and description= — one page missing either makes Dash emit an empty og:image, which scrapers prefer over every correct tag. The social card lives on the CDN (cdn.2plot.ai/github_assets/pannellum.2plot.dev.png, 1200×630), not in assets/, so a cold container can never poison an unfurl cache. templates/index.html declares only what Dash does not emit. The cross-host network directory, the hub bulletin (NETWORK_BULLETIN_URL), and a real installable-app surface (favicon set + webmanifest) are wired.

The network's analytics contract

Requests carrying the 2plot-internal UA token are dropped at write time, before bot classification; every outbound call to another network host sends that token. The hourly signed traffic rollup POSTs to 2plot.ai when CROSS_APP_WEBHOOK_SECRET is set, under this app's one short id: pannellum (AD_APP_ID, SATELLITE_APP_KEY and the bulletin app_id all agree). /healthz answers ok: true on every backend — the exact field the fleet's batteries assert.

CI/CD is now a deploy gate

ci.yml (pull requests only; cd.yml owns main): actionlint first, a secretless pytest suite across flask/fastapi × py3.11–3.13 × dash 4.4.0 and pinned, then the real Docker image built, fingerprint-asserted from inside (dash ≥ 4.4, dimll ≥ 2.3.4, gunicorn ≥ 23), booted, and battered by the same script CD runs against production. cd.yml deploys via Render hook, waits for sustained health (120s settle + 5 consecutive 200s), and runs the battery plus smoke_live against the live domain. gunicorn moved past the request-smuggling CVEs (21.x → ≥ 23) by installing markdown2dash --no-deps; the Dockerfile gained PYTHONUNBUFFERED=1 and an explicit COPY list. render.yaml is authored for the first deploy (docker runtime, disk-backed analytics ledger, Clerk satellite env).

Housekeeping

The README was rebuilt to the network standard (badges, docs links) and made truthful again: it now lists all ten documentation pages — the old table missed scenes, arena, emotes and gyro — and no longer claims a vendored dash-improve-my-llms 2.0 (vendor/ holds only the dash-clerk-auth tarball). [project.urls] pointed at a hyphenated repository name that does not exist; it now points at dash_pannellum, with Documentation at pannellum.2plot.dev.

The local /analytics/traffic dashboard is gone: traffic accounting lives on the hub (the tracker and the hourly signed rollup to 2plot.ai are unchanged), so a per-satellite dashboard was a second, staler view of the same ledger.

tests/conftest.py removes its per-run temp state dir at exit. mkdtemp falls back to the current directory when no system temp dir is writable (sandboxed runs with TMPDIR unset), which had leaked eleven pannellum-tests-* dirs into the repo root; the pattern is gitignored as a backstop. The local legacy/ archive — an untracked 457 MB second clone of the original repo, whose history is already this repository's own history — was removed from the working tree, along with stale 0.4.0 build artifacts and a 0.4.0-era forum-post draft.

0.4.0 — 2026-06-12

Gyro look-around — the device is the camera.

Added

steering. Pannellum's native sensor pipeline does the quaternion work and runs the iOS 13+ permission prompt inside startOrientation; on iOS, set the prop from a clientside callback on a direct tap so the prompt lands inside Safari's gesture window.

request vs truth, kept separate on purpose: permission can be denied, desktops have no sensors, and Pannellum pauses gyro steering while the user grabs the panorama.

phones, plus a drag-pad tilt simulator on desktop that feeds the camera the same continuous lookAt stream a gyroscope would.

Notes

user agent + literal https:** — plain-HTTP localhost reports unsupported even though browsers would allow the sensor there. Use a TLS dev cert or a tunnel to test on a phone.

switch engages steering, synthetic device rotation pans the camera (yaw 5° → −25°), disengage restores drag-only.

0.3.1 — 2026-06-12

Upstream feedback from the 360 Dish Lab: "the live callbackHotspots diff is a wholesale remove-and-re-add … an in-place positional update would let DOM hotspots animate too." Done.

Changed

wholesale replace: unchanged hotspots are untouched; position-only changes move the existing DOM node in place (Pannellum re-reads pitch/yaw from the live spec object every render); other changes (text/type/cssClass) recreate just that hotspot; vanished names are removed. Contacts can now drift every tick as real DOM hotspots — staying hoverable and clickable mid-flight — instead of being redrawn into the canvas as a workaround.

re-assertion (a same-value setPitch short-circuits inside Pannellum and renders nothing — found the hard way).

Fixed

the panorama was still loading: a pre-load addHotSpot got a second div from Pannellum's own load pass, leaving an orphan marker. Hotspot sync now waits for isLoaded(); the on-load sync applies the latest prop value.

Added

contact orbiting on a 400 ms Interval via plain prop updates, verified: same DOM node across the whole flight, clickable mid-drift.

0.3.0 — 2026-06-11

The movement-mechanics release. 0.2.0 made the camera imperative; 0.3.0 makes the world updatable in place — and settles the design: LOOK with lookAt (free), MOVE by redrawing a canvas texture (no rebuild), rebuild only when the WORLD changes (and treat that as a real transition).

Added

DOM id of a <canvas>) instead of a panorama URL. The canvas becomes the sphere's texture via Pannellum's dynamic mode (the same machinery as 360° video): redraw the canvas and the view updates in place — no rebuild, no camera reset, no flash.

while a dynamic canvas scene is live.

playground (initial-view presets, autoRotate, zoom limits) plus the fly-to pattern: named targets + lookAt with a duration, triggered from buttons, from clicked hotspots, and from a simulated external event.

Fixed

#f4f4f4; the component now forces it transparent, so any rebuild dips to the host page's background instead of flashing white.

tour per glide step (the 0.2.0 data-URI approach, which rebuilt the viewer every move). It binds the scene once to a live canvas and glides by redrawing pixels. Verified: the viewer DOM node survives whole glide sessions and arena switches; frame brightness stays flat (no white frames). Arena switches get a deliberate 140 ms dip-to-dark fade.

stick went silent after one step; the arena now runs its own glide cadence while the stick is held at the rim.

0.2.0 — 2026-06-11

The real-time release — shaped by the 360 Dish Lab prototype's wishlist (its turn-based design existed purely to work around 0.1.0's lack of an imperative API).

Added

pans/zooms the live viewer with no rebuild. Smooth enough for joystick-driven steering via window.dash_clientside.set_props.

pitch/yaw, so it can be baked across rebuilds.

IDs and the active scene are ignored.

addHotSpot/removeHotSpot instead of requiring a tour re-init, so markers can move in real time (360 Dish Lab wishlist #1).

prefetched once the viewer is up: tour jumps no longer show a "Loading..." flash.

viewer, for preloaded tours and data-URI panoramas where the spinner is the only visible part of an instant transition.

(dash-gauge) drives a 360° petri-dish arena composed client-side from vendored {z}/{x}/{y} floor tilesets (3×3 @ z15, 5×5 @ z9, 7×7 @ z3) into data-URI panoramas: tilt to look (lookAt), pin the stick to glide (re-compose + bake gaze). The server is never in the locomotion loop.

Changed

than baked into the scene config (no behavioral change to clicks; verified against the hotspots docs page).

(preloadScenes + hideLoadingSpinner).

Still on the wishlist

Hotspots + viewer-config passthrough in multiRes mode, and a bundled (non-CDN) Pannellum runtime.

0.1.0 — 2026-06-11

The revival release. The component was rebuilt for the modern Dash stack and the repository was restructured around a live documentation site.

Breaking

Added

Changed

0.0.6 — 2024

Last release of the original component (Dash 1/2 era).

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: