Repository navigation
Releases: DataZooDE/anofox-visualization
Release list
v2026.10.28
80 pull requests since v2026.10.25.
New
- Viewer state and forms:
::SUBMITwith an append-only per-viewer store,POST /api/submit, read-back asviewer_state/viewer_state_latest, the browser UI, anddashboard state export|erase(#530, #538, #547, #550, #555, #575, #599, #611) - Guided steps and tours:
::GUIDE_STEPsequences with Back/Next and an optional::GATE(#537);-- @tourover existing dashboards (#542); progress remembered for a signed-in viewer (#578); an offline stepper in the ⤓ HTML export (#571) ::SHOW_IF: conditional panels and boxes (#532)- Images:
::IMAGE/::IMAGE_DARKpanels and Markdown images (#544); served fromassets/on the locked server (#554) and the authoring builder (--assets, #604); embedded in static Markdown output (#601) - Maps:
::POINTS: dense line/area series no longer turn into chains of dots. Their markers stay hover and click targets (#637)- Layout: opt-in
::VIEW_PLACEMENT 'header'switcher (#572);::MARKDOWN_STYLEplain or card (#567) -- @namepanel names withdata-dp-nameanchors (#529)- Serving: several nav pickers and a daterange type (#582); a Check button on a live authoring backend (#512);
dashboard --siteaccepts the locked server's options file (#507) anofox_plot_assignmentfacets by the producer's group column too (#509)
Fixes
- Spreadsheet formulas are neutralised in CSV/.xls exports (#526)
- Script-free dashboard controls work under their own CSP (#570)
- A locked embed variable's
::INPUTdefault no longer wins (#541) ::COLORSis honoured on density, histogram, QQ, radar and pie/donut charts (#558)- A VARCHAR x axis that looks like ISO dates stays discrete on violins (#561)
::HEIGHTbefore a::VIEWgroup applies to every alternative (#594)/d/<id>links and nav hrefs stay under a published site's sub-path (#502)::MARKDOWNtext wraps by pixel width and renders on the serve path (#583, #586)- HTML export: charts are honest images; ⤓ HTML and Share stay disabled until ready (#630, #633)
- Focus stays on a
::VIEWtab button across a header card swap (#629) - Map legend fixes: narrow colourbars go to the bottom (#623, #636); bivariate axis titles are readable in dark mode (#620); hexbin time maps are faster (#632)
- Windows build (#592, #650); flock retry (#585); NUL bytes in state seeding and CSV export (#625, #628)
Drawing on ggplot-rs
ggplot-rs 0.30.2 → 0.32.0 (#496, #527, #584, #612, #636, #637). Maps no longer use the old hand-written SVG writer (#543).
Build and CI
v2026.10.25
43 pull requests since v2026.10.24.
New
dashboard --site: publish a dashboard folder as a static DuckDB-Wasm site (GitHub Pages, S3, any web server) (#429)::SPARKBAR: in-cell bar histogram for tables (#453)::HEXBINon a planar::SCATTER: a 2D count density (#439, #450)- Histogram/QQ honour
::CATEGORY(overlaid series per group) and::FACET; new::HIST_BINS,::HIST_NORM(#442, #472) - Heatmap honours
::FACET; VARCHAR axes are always discrete (#444) - Compact KPI tiles (
'compact'::KPI_SIZE);::FITkeeps KPI rows at their natural height (#430) - Searchable
::DROPDOWNfor long option lists (#433) - Axes: round log/sqrt ticks,
::XBREAKS/::YBREAKS(#432) -- @shared <name>header marks a deliberately shared row-select/input variable for--check --strict(#483)- An installation default for "charts as tables" (#438)
Faster
- Static render runs a whole dashboard in one DuckDB process (about 15 s to 1.5 s on the reference dashboard), with a watchdog (#434, #447)
Fixes
- Input option queries see the other inputs' current values, including a fix for an SQL injection through a NUMBER URL seed (#436)
- Viewer values apply before the dashboard's
SET VARIABLEs (#431) - Every
::REFLINEcolumn draws, not just the first (#482) - A chart inside a
::VIEWin ann::COLslot lays out at the slot's width (#484) ::LINKworks on::TRACKlanes;{column}placeholders resolve by name (#466)- DECIMAL/DATE/TIMESTAMP nested in a LIST/STRUCT/MAP arrive correctly in the browser (#469)
::SPARKLINEtable cells draw in--svg/static HTML (#452)mediation_bykeeps its term order (#443); deterministic donut slice order (#456)- Explore data's comparison overlay keeps its intended size (#485)
Drawing moved onto ggplot-rs (#318)
ggplot-rs 0.26 to 0.30.1 (#440, #448, #451, #455, #474). Tick picking and KPI sparklines (#457), ::TRACK alignment and the GAM smoother (#461), ::DIAGRAM drawing, with bold node names and outlined edge labels (#487), browser table cells and Explore data mini charts (#476, #480). Maps now draw through geom_sf for plain, classed, graduated-symbol, flow and hexbin maps (#459, #467, #475, #478). Visible changes: legends sit at the top of maps; size legends are graduated; Top legends with a title sit in their own band.
CI
A fast lane for PRs (Linux on Blacksmith runners, about 10 min); full platform builds on master, nightly, full-ci and releases (#465, #486).
v2026.10.24
Charts and dashboards for DuckDB from SQL. This release builds for DuckDB 1.5.6 and 2.0. It makes served dashboards much faster to click through: setup is reused across panel requests. It also adds a fit-to-screen mode for wall displays (::FIT), a ::LEGEND role, a dashboard picker for the served nav bar, and a Content-Security-Policy header on every served page. It follows anofox_statistics v0.13.
Live demo: https://datazoode.github.io/anofox-visualization/
Upgrade notes
- anofox_statistics v0.13 changed
split :=(upstream #205). In*_prediction_by(…, split := …)a row whose split is NULL is no longer a training row. Only'train'/'training'(any case) trains.split := CASE WHEN id > 50 THEN 'test' ENDnow trains on nothing and returns an emptyyhat. AddELSE 'train'. Our examples, docs and skills are updated (#408). - "Charts as tables" is off by default (#404, #401). Viewers turn it on from the app menu, and the choice is remembered. Authors show it for a dashboard with
-- @astables on.
Serving
- Warm connections (#392, #388): the locked server reuses a dashboard's setup across panel requests instead of re-running it for each one. A setup-heavy dashboard went from 17.6 s to 0.82 s per click. Connections are keyed on the variables the setup reads, so a viewer never sees another viewer's setup state.
folder/setup-tablelint (#391, #389):dashboard --check <folder>warns about a setup statement that builds a table (CREATE [TEMP] TABLE … AS); a view is usually the better choice.- Content-Security-Policy header on every server's HTML pages (#396, #399, #252): the locked server's dashboards and login pages,
dashboard serve's pages and the authoring builders. - The served nav keeps path variables (#410, #407): with
"path_vars": ["model"], links to other dashboards carry the current?v.model=, as real links that also work in a new tab. nav_picker(#410, #407): a locked-server option{"var": "model", "sql": "SELECT model_id, title FROM …", "label": "Model"}puts one dropdown in the nav bar that sets a path variable for every page.- The query is server config: it gets the same read-only check as data-folder statements and runs once per snapshot.
- Its values must be plain names, like any
path_varsvalue. - A credential that locks the variable gets no picker.
- It can't be combined with an access policy.
- The details are in docs/secure-serving.md.
Dashboards
- Fit to screen,
'fit'::FIT(#402, #400): a tab (or sub-tab, or view) that fills the window with no page scroll, for a TV, a wall display or a one-screen overview.- Rows keep their authored height ratio, and charts are redrawn at their real size, not stretched.
- Tables scroll inside their panel. KPI tiles keep a minimum height.
- With too many panels to fit, the page scrolls as usual and a notice says why;
--checkwarns ahead of time (layout/fit-too-many,layout/fit-ambiguous). ?fit=1/?fit=0override the setting for kiosk screens. Phones ignore it.- The downloaded HTML fits itself too, and static pages fit a whole-dashboard
::FITwith CSS.
::LEGEND(#412, #407):'none'hides a chart's legend or colourbar (handy on a dense heatmap).'top','right','bottom'and'left'move it. It works on every dashboard chart and on maps.--checkwarns about an unknown value or a panel without a legend.
Statistics plots
- Nested cross-validation (#408):
ridge_nested_cv_by/elasticnet_nested_cv_byoutput (curve_type = 'nested_cv') draws each outer fold's test error with the mean ± SE band, one panel per model. - The new
ols_anova_by/ols_anova_agg(nested-model F test) andmediation_byrender throughanofox_plotand their schema's macro. Conformance: 162/162 producers, 345/345 plot calls.
Charts
- ggplot-rs 0.25 (#394, #393):
stat_alignforgeom_area, so stacked areas whose series have different x values stack cleanly.
Builder
- Assistant: the file-path check is stricter. A path whose query string holds a newline (
'x.csv?\nz') now counts as a path (#395, #239).
Tests
- New real-browser checks in CI: fit-to-screen, the "Charts as tables" opt-in, and the served nav picker with link carry-over.
v2026.10.23
Charts and dashboards for DuckDB from SQL. This release builds for DuckDB 1.5.6 and 2.0. It adds three new contract plots (schedule/Gantt, bin and wave assignment, categorical distribution), a ::TOOLBAR directive for small tables and a short -- @nav label for served dashboards. It also moves the plotting onto ggplot-rs 0.24, where bars and areas stack by default as in ggplot2. The charts in this repository keep their documented look.
Live demo: https://datazoode.github.io/anofox-visualization/
DuckDB 2.0
- One source, two DuckDB versions (#353): the C++ extension builds for DuckDB 1.5.6 and 2.0. A non-blocking CI job builds and tests on 2.0. The
dashboardCLI checks the DuckDB version it talks to. - Chart errors stay chart errors on 2.0 (#373, #372):
anofox_renderis marked as able to throw, so a bad spec or a missing column is a normal error rather than an INTERNAL error. Its parameter is listed asspecinduckdb_functions(). - Access policies work on 2.0 (#362, #357). The locked server classifies DuckDB 2.0's function list.
- macOS: links the
Securityframework that DuckDB 2.0's httplib needs (#359; shipped alone as v2026.10.22). - Prototype, opt-in: the locked server can snapshot from a DuckDB 2.0 Quack source (#356). A Quack token never reaches the browser.
New contract plots (provisional schemas)
anofox_plot_schedule(#378, #176): real Gantt bars, one lane per resource, coloured by status and faceted per model. A late task's due date is marked by a connector from the bar's end. Rows with no start/end, or that end before they start, are dropped with a warning. Lanes sort naturally (L1, L2, L10). The anofox-optimize production example uses it.anofox_plot_assignment(#375, #177): items stacked per bin or wave, with capacity as a line, or as a marker per bin when it varies.anofox_plot_distribution(#375, #181): a categorical distribution per id. The docs give the one-line recipe for anofox-decide'sMAPoutput.anofox_plot(t)picks all three from the columns.- Fixed:
anofox_plot_cv's fold bars had no hover text (#378).
Dashboards
::TOOLBAR(#379, #354): hide a table's toolbar ('none') or keep only some parts, e.g.'search,export'. A totals row the author asked for stays. Saved search, filter and column state can't hide rows behind a control that isn't shown. The hover ⤓ CSV export exports the rows as shown.-- @nav <short label>(#376, #355): a short label for the served nav bar, the start-page cards and the examples sidebar.@titlestays the page heading and becomes the tooltip.--checkwarns about an empty@nav, one over 24 characters, and two dashboards that look the same in the nav.- Downloaded HTML: sub-tab pages are no longer empty (#383, #381).
- Crosshair: a date reference line (
::XLINE) shows its date, not epoch seconds (#374, #365). - The MotherDuck connect button is gone from the builder's top bar (#382, #380). A token stored by an earlier version is cleared once.
::PAGEDover remote tables is unchanged. - Headless and static output: truncated table headers and TeX shown as text are fixed (#363, #351); narrow charts no longer overlap axis labels (#346). Escaping follow-ups (#340).
Charts on ggplot-rs 0.24 (#366, #371, #385)
- ggplot-rs 0.22.2 → 0.24.0. In 0.24
geom_colandgeom_areastack by default, as in ggplot2.::BARCHART,::AREA, waterfall, bullet and funnel set their position explicitly, so they look as before. A render of every example before and after shows no change from the bump. - Legends: per-panel dodge and jitter, positive/negative stacking, and wrapped and vertical legends that fit, from ggplot-rs 0.23.
Builder
- Assistant follow-ups (#343): an explicit run releases only the statements it ran.
- Check (lint) scratch session: the cleanup always detaches its scratch database, and a real-browser test checks that a cancelled query leaves nothing behind (#377, #258).
Tests
v2026.10.22
Build fix for DuckDB 2.0 on macOS: the extension now links the Security framework that DuckDB 2.0's bundled httplib needs on macOS (#359). Without it the osx_arm64 build on DuckDB 2.0.0 failed to link, which kept community-extensions from publishing a 2.0.0 binary. No functional changes.
v2026.10.21
Charts and dashboards for DuckDB from SQL. This release turns on the AI assistant panel in the builder, switches the anofox extensions' telemetry off by default, and moves the plotting onto ggplot-rs 0.22. More than 1,000 lines of local SVG and layout code are gone as a result. It also adds three new contract plots: cross-validation folds, data-quality checks and a correlation matrix.
Live demo: https://datazoode.github.io/anofox-visualization/
Privacy
- Anofox extension telemetry is off by default (#335, #333).
dashboard,dashboard serve, the extension when loaded into any DuckDB, and the locked server all setDATAZOO_DISABLE_TELEMETRY=1unless you set it yourself. An empty value counts as unset. To opt in, setDATAZOO_DISABLE_TELEMETRY=0before loading. This extension itself never sent any. - Windows: the default is now written to both the OS environment and the C runtime, so it holds for our own module and for any extension sharing the C runtime (#348). An anofox extension loaded before ours is only reached through the best-effort
SET anofox_telemetry_enabled = false, so load anofox_visualization first or set the variable yourself.
AI assistant (builder)
- The assistant panel and "new from question" (#272, M1-3 of #244). Describe a change or a new dashboard. The assistant proposes SQL, previews it in a locked, network-free DuckDB-Wasm engine on a capped copy of your tables, and lets you choose Apply and run, Apply, don't run or Cancel.
- Nothing runs on your page's database until you confirm.
- A held proposal stays held across undo/redo, edits, line endings and reloads of the preview.
- Links can't leave the page during a preview.
- The Apply dialog shows every statement in full, with its real target.
- The assistant uses your own API key, is off on static hosting unless enabled, and gets a CSP over the whole tab.
- Security: seven independent adversarial review rounds; every finding was fixed before merge.
Charts on ggplot-rs
Visualization logic now lives in ggplot-rs (0.20 → 0.22), not in this repository (#318).
- Maps: flow maps (
::ORIGIN/::DEST, arrows, width legend) and hexagonal density maps (::HEXBIN) are drawn by ggplot-rsgeom_curveandgeom_hexon a fixed lattice that never re-bins on zoom (#308, #310). - Axes and legends (#338):
- outer x labels stay inside the chart, and crowded axes thin their labels the way ggplot2 does;
- long legends wrap with "+N more";
- faceted stacks stack per panel;
- labels on grouped and stacked bars sit on their own bars;
- box plots draw precomputed five-number summaries natively.
- Rotated axis labels (ggplot-rs 0.22.1, #345): crowded axes rotate only when the drawn labels don't fit. Each label ends at its own tick and never overlaps the plot.
- Hover values and date reference lines now come from ggplot-rs's native formatting for every chart (#350, #326).
- SVG post-processing removed (#325): mark classes, formatted hover values, opacity and NaN handling now come from ggplot-rs. The wasm bundle got smaller.
Serving
- Authoring
serve: a per-query timeout (--query-timeout, default 120 s) ends any hung query, including one that reads stdin, and starts a fresh session. Killing the server cleans up its session directory (#342, #304). -- @order N: sets the order of served dashboards in the nav and on the start page (#342, #301).- Snapshots of databases with foreign keys read the column catalogue once per database, which is 25–36 % faster on 3,000 tables (#342, #299).
- Script-free page:
data-warningsis never emitted, and a broken contract panel is redacted (#342, #315). A<select>option value containing a control character round-trips (#342).
New contract plots (provisional schemas)
anofox_plot_cv: a cross-validation fold timeline forts_cv_folds_byandts_cv_split_by(#307, #189).anofox_plot_decomposition: shipped in v2026.10.20.anofox_plot_checks: data-quality checks as a band or a status strip (#280, #182).anofox_plot_matrix: a correlation or confusion heatmap, taking anofox-tabular'sprofile_correlationsas it comes (#324, #183).
Dashboards
::ROWSELECT: numeric-looking keys are clickable, and a new lint flags two tables that share one selection name (#341, #330).::HEATMAP: tiles fill their cells on date axes, a categorical (string) measure gets a categorical legend, and dates sit on day boundaries (#341, #332).- Dark theme maps: the sequential classes keep their order and reach a 3:1 contrast against the card (#341, #311).
UNION ALL: later branches drop their::ROLEcasts correctly (#322, #294).- Multi-series bar charts: the legend uses the column names (#322, #292).
- Docs and lint:
::ROWSELECT/::TOTALS/::EMPTYare documented as next-panel directives, with a lint (#322, #291). - Headless
dashboard --svg: fills::LINKcells and{{col}}markdown placeholders (#328, #327).
Robustness
- Control characters can no longer make an SVG invalid XML, and headless links refuse backslash and whitespace tricks (#339, #329).
- No
inf/NaNvalues in marks, labels or diagram attributes (#339, #320, #319). - Conformance SVGs are byte-stable across runs (#302, #264).
Known issue
- AI assistant (only where an installation has enabled it): after Apply, don't run → Undo → Run → Redo, a partial redo can bring back proposal statements without the hold, and a later implicit re-run (e.g. the dark-mode toggle) would then run them on the page's database (#349, Medium). The fix is in review and ships in the next release. Until then, run or discard a held proposal before undoing past it.
v2026.10.20
Charts and dashboards for DuckDB from SQL. This release adds alternative views of a panel (::VIEW), series focus on hover, graduated-symbol maps and a time-series decomposition plot. It hardens the locked server and makes the browser bundle about a third smaller.
Live demo: https://datazoode.github.io/anofox-visualization/
Security
Every serving change below had an independent adversarial review, and every finding was fixed before merge.
- The script-free page hides error text: DuckDB errors (#288, #279) and in-figure panel render errors (#314, #285) no longer reach the viewer.
@requiresapplies on the script-free page too (#278, #274).- The pinned DuckDB-Wasm extensions are the builds the assistant's function lists were generated against. The assistant's gate also reviews family macros that wrap other functions (#300, #235, #239).
Dashboards and charts
::VIEW: alternative views of one panel, behind a switcher (#273, #214).- Hover focus: hovering a mark emphasises its whole series and fades the others (#284, #213).
- Decomposition plot (provisional):
anofox_plot_decompositionshows observed, level, trend, seasonal and remainder panels. It reads anofox-forecast'sts_mstl_decomposition_by/ts_forecast_explain_byoutput as it comes (#306, #188). - Maps: graduated symbols with a size legend and a stacked legend, plus a map-lint fix (#303, M2a of #149).
- Formats: the
en-IElocale, English with the euro (#296, #276). - Freshness: a date-only
::ASOFreads as a calendar day (#293, #282). - Downsampling: rates aren't summed into "Other", and ranked queries keep their first rows (#289, #281).
Serving
- Authoring
servekeeps one long-lived DuckDB session (#290, #275). - Snapshots of databases with foreign keys are taken table by table, parents first (#298, #283).
- The
--app-logo-darkerror text is spelled correctly (#278, #267).
Build
v2026.10.19
Charts and dashboards for DuckDB from SQL. This release adds maps, status heatmaps, table totals and a faster locked server. It removes the DataZoo credit from every output, and lays the groundwork for the AI assistant. The assistant is still off and has no panel yet; that comes in the next release.
Live demo: https://datazoode.github.io/anofox-visualization/
Security
Every serving or AI change below had an independent adversarial review, and every finding was fixed before merge.
- Locked server: variable-scoped re-runs and caching (#222, #210). A panel re-runs and caches only by the variables it reads. A variable a panel doesn't read is dropped before any
SET, so one request can't fail another request that shares its single-flight. - Locked server: parquet footer cache (#215, #209). Metadata is cached across requests, which makes requests about 10× faster on large folders.
- Static pages draw
::TITLE(#250, #232). Titles are escaped, and capped at 200 characters on static pages (#262, #251). - AI assistant groundwork (#192, #238, #256, #270). Nothing below is user-visible yet: the assistant stays off unless an installation enables it, and there is no panel.
- The core: the user's own key, through the vendored SDK (SHA-256 checked). It uses read-only tools, a write gate and per-request budgets, and masks personal-looking values in what it sends.
- The SQL gate: each statement is parsed and refused if it uses dynamic queries, reads of computed, remote or GDAL paths, secrets or settings functions, or native
*_operatorfunctions. - A locked DuckDB-Wasm engine in its own Worker (#204). There is no network or file access. It works on a capped copy of the page's tables, and every query before Apply runs there.
- A tab-wide CSP goes on once the assistant opens, or at startup whenever a key is stored. Remember is opt-in per installation and never offered on shared hosting origins. Option objects are safe against prototype pollution.
Maps (#147, #168, #191, #206, #149)
- Our own map writer, with projections and classed choropleths.
- A boundary pack: Natural Earth plus BKG for Germany.
- Region codes (
::REGION),::LAT/::LON, labels and a scale bar. - Click-to-highlight, drill-through and synced zoom.
- A new showcase: "Where do our deliveries and customers cluster…".
Dashboards and charts
- Heatmap status colours:
::COLORSCALE,::THRESHOLDS(#193). - Tables: per-column totals (
::TOTALS), with no total for rates and percentages by default (#240, #207). Rate and mean names match whole words, so "generated" isn't a rate (#254, #227). Headless--svgtables honour::TOTALS(#226). The linter flags a::TOTALScolumn that doesn't exist (#228). - Stacked bars: they work under
::FACET(#217, #208), the hover shows correct shares, and labels are centred (#242, #218, #219). - Legends: long top or bottom legends wrap into rows, with "+N more" (#241, #175).
::OPTION_LABEL: a dropdown, buttons or multiselect option shows a label while the variable keeps the id (#265, #170).- Tabs: tabs and
::TRACKs with unmet@requiresare hidden (#167, #162, #163). - App bar: an optional dark-theme logo via
logo_dark/--app-logo-dark(#266, #198). - Credit removed: the "Powered by DataZoo GmbH" credit and the "© DataZoo GmbH" sidebar line are gone from every output (#245, #261).
- Delivery dashboard reworked (#205, #249): street grid, cost model, tab-scoped inputs, faceted cost per day.
Statistics and contract plots
anofox_plot_testsdraws one panel per method when a table mixes tests, such as ADF next to KPSS (#257, #190).anofox_plotno longer gives a misleading "closest" hint for a shared generic column or a one-letter near miss (#257, #262, #180, #255).anofox_renderhonours::TITLE(#233, #178). The headless card uses the first non-NULL title (#246).- Conformance suites for anofox-forecast, -optimize, -decide and -tabular, with one shared helper (#187, #171, #172, #173, #263).
Build and tooling
--versionshows the build:v2026.10.19on the tag, otherwisev…+N · sha(#234, #216).build-native.shaccepts any output name and exits non-zero on a failed smoke render (#229, #247, #201, #231).- The C-API build documents that its macros need
memory.qualification afterUSE, with a smoke check (#260, #179). - The Python
connect(extension=…)call hints at the required file name (#247, #230). - The tabular examples install the signed community build (#259, #185).
- No anofox extension telemetry from CI, tests or scripts (#174).
- The linter has no
requires/on-setupfalse positive forSET VARIABLEsubqueries (#236, #166). - The Explore view's starter KPIs use whole-word name rules (#262, #248).
For the forecast dashboards
This release ships #162/#163 (@requires hiding) and #209/#210 (the locked-server speed-ups), which the forecast dashboards were waiting for.
Pinned DuckDB-Wasm extensions (v1.5.6, 2026-10-19)
Pinned DuckDB-Wasm extension builds for scripts/fetch-wasm-extensions.sh — not a release of anofox-visualization; scripts/wasm-extensions.lock, #235
v2026.10.18 — a sidebar you can organise, served-folder start page, app branding, @requires with variables, box plots from statistics
Charts and dashboards for DuckDB from SQL. This release makes the builder's sidebar something you can organise: your own dashboards, the examples and served folders all group themselves from header comments in the SQL. It also adds a start page for served folders, branding for the app bar, @requires with variables and alternatives, box plots from pre-computed statistics, and a batch of fixes, three of them reviewed for security.
Live demo: https://datazoode.github.io/anofox-visualization/
Security
Every change to the serving code below had an independent review before merge, and all findings were fixed before merge.
- Token redirect of the authoring
serve(#159). With a valid token, a link like/\evil.example/?token=…redirected to an outside site; a backslash in the path, including%5C, now falls back to/. The redirect also drops every spelling of thetokenparameter and keeps the others (?theme,?v.*), which it used to lose (#155). - Locked server,
::PAGED+::INFO(#152, #159). The browser asks the server to leave out the definition column. The server accepts this only for the panel's own::INFOcolumn and answers anything else with 400, without echoing the value.docs/secure-serving.mdnow says that a column raisingerror()is not an access control. - Locked mode, setup variables (#151). The browser no longer sends a dashboard's own
SET VARIABLE x = <expression>as a viewer value. The server rules are unchanged; the docs now explain that setup statements run best effort, so restrict viewer variables with-- @varsand claims. -- @requireson the locked server (#156, #160). Variables expand only from server-side values. Glob characters count only in the author's own text. Every match must resolve to a path inside the data folder, so symlinks pointing out never count. Errors show only as "Not available" and the details go to the server log. Results are cached for 5 s per path, and only definite answers are cached.
Sidebar you can manage (#141, #143, #148, #150, #158)
-
Header comments place a dashboard:
-- @group Sales(one sub-level with/),-- @tags weekly, kpi,-- @owner,-- @certified YYYY-MM-DDand-- @description. One parser serves the builder, both servers anddashboard --check, which lints them (also a whole served folder:dashboard --check <folder>). -
My dashboards come first:
- rename, duplicate, move to a group, and delete with Undo;
- sort by manual order, name or last edited;
- Save asks before replacing another dashboard;
- groups live in the SQL as
-- @groupand can be reordered; - the toolbar name and
::DASHBOARD_TITLEare one name; - returning users land on the dashboard they had open last.
Saved dashboards move to the new storage automatically, with a backup.
-
Examples form one library grouped by business area: Start here, Demand & stock, Logistics & production, Sales & customers, Service & operations, Data quality, How it works. It is collapsed once you have your own dashboards, and you can hide it. Every example has a stable id; all old
?dashboard=names still work. -
Search covers names, groups, tags, owner and descriptions, also in the phone drawer. Tag chips, a "✓ certified" filter and Recently opened are new.
-
Import and export:
- Download one dashboard as
.sql. - Export all writes a
.zipthatanofox_serve_dashboardsserves as it is. - Import takes
.sqlfiles and uncompressed.zipfiles, through the menu or by dropping them on the sidebar, with Keep both / Replace / Skip on a name clash.
- Download one dashboard as
-
In-app dialogs replace the browser's
prompt()/confirm().
Served folders (#158)
- A start page at
/on the locked server and onserve --dashboards: dashboard cards grouped by@group, with the question, description, tags, owner, ✓ certified and the last update. From 9 dashboards on it has search, tag filters and "certified only". It is themed light and dark and works on a phone. options.home/--home <id>makes/open one dashboard instead, if the viewer may see it.- The navigation bar on served pages is grouped. From 9 dashboards on it becomes a searchable menu.
- Fixed: the navigation bar on a served page filled half the window.
App bar branding (#146)
- The builder's app bar takes an installation's name, tagline, logo, link and favicon:
app-config.jsonnext to the page (static hosting);serve --app-name/--app-tagline/--app-logo/--app-link/--app-favicon/--app-hide-name;anofox_serve(port, options);options.appon the locked server, for the start page.
- The default tagline is now "Dashboards from SQL". The "Powered by DataZoo GmbH" credit stays.
Charts and SQL
- Box plots from statistics (#157):
::Q1 ::MEDIAN ::Q3plus optional::WHISKER_LOW/HIGH,::MEANand::OUTLIERS, one row per box. They draw the same as a box plot from raw rows, and the tooltip lists all the numbers. -- @requireswith${var}and alternatives (#156):-- @requires ${rd}/_anofox_meta_forecasts.parquet | ${rd}/forecasts.parquet. Also new:dashboard --check --data <dir>and lints for unknown variables, empty alternatives and guarded setup statements.- Status colours for categories: a
::COLORSvalue like'Critical:bad; Watch:warn; OK:good'next to a::CATEGORYuses the theme's status colours, in dark mode and in the colour-blind theme too. A constant::SIZEsets the point size. {{name}}placeholders in::MARKDOWNread a value column namedc0,c1, … correctly (#138, #140).- Tables with up to 20 rows open on one page.
Examples
- Where will stock run short, and can we trust the plan? opens on a new Overview tab (#142): a risk map, a 28-day heatmap and a watch list ranked Critical / Watch / OK with plan trust.
Behaviour changes
- Relative
-- @requirespaths now resolve against the data folder (anofox_data) when it is set, not the working directory. Use${var}paths ordashboard --check --data <dir>. ::PERCENTand the'percent'format take values from 0 to 100; this is now documented (#154).- The
design/ungrouped-kpislint no longer counts tables as KPI tiles (#153).
Other
- The sidebar footer shows the real version, not
v0.1.0(#144).
Full list: v2026.10.17...v2026.10.18