v0.0.65 — Filter coverage + license escape hatch
Released: 2026-05-12.
Surface silent filter skip + one-click wire-into-card
The toolbar date filter has always silently skipped cards whose SQL didn't reference the filter column — correct cross-dialect behavior, but invisible to users. Operators would set a date range, see some cards update and others not, and reasonably conclude "the filter is broken". v0.0.65 closes that gap with a two-layer fix.
Surface layer
The dashboard toolbar and per-card chrome now make filter coverage explicit:
| Element | Surfaces when | What it shows |
|---|---|---|
| ⚠ Pick a date column pill | Range is set, no date column configured | Reminder to wire a column to the toolbar before the filter has anywhere to apply. |
| covers N/M cards pill | Range + column both set | Click to flash uncovered cards on the canvas. |
| ⊘ filter skipped badge (per card) | This card's SQL doesn't reference the filter column and has no bound param | Tells you which cards are sitting out. |
| Date-column suggestions | Per dashboard | Pulled from card SQL — the toolbar suggests columns it sees referenced. |
Fix layer
In edit mode, the ⊘ filter skipped badge becomes a button. Clicking it opens a modal with a diff preview:
- Apply rewrites the card's SQL to include the filter column and adds the corresponding dashboard parameter binding.
- The modal previews via
POST /api/dashboards/{id}/cards/{card_id}/wire-filterwithdry_run=true, so you see the exact rewrite before committing.
Pattern-based AND-injection respects GROUP BY / ORDER BY / HAVING / LIMIT — the filter clause lands inside the WHERE, not after LIMIT. A _filter_column_in_sql guard skips wrapping when the column is already bound via parameter nearby, so wired cards don't double-filter.
Coverage rules are centralized in pages/dashboards/filterCoverage.ts — the toolbar pill, per-card badge, and backend mirror all read from one source of truth.
License page — org-switch escape hatch
Bug observed 2026-05-12 on test ECS: a user whose localStorage X-Org-Id pointed at an unlicensed org (test-fixture pollution — "Pytest Smile Org") was hard-stuck on /license with no way to recover beyond pasting a fresh license key. The org switcher didn't render on the License Required screen, and every navigation away got redirected back by the axios interceptor.
Fixed:
- Backend —
/api/organizationsis now on the license-gate allowlist. The endpoint returns onlyorg_id/slug/display_name(no business data), so opening it past the gate doesn't expand the unlicensed surface area. - Frontend —
LicenseRequiredfetches/api/organizationsand, when the user belongs to other orgs, renders a switch to another organization block with one-click switch buttons. Picking one stampsselected_org_idin localStorage and hard-reloads to/.
This doesn't address the underlying test-pollution (orgs 2–13 on test ECS DB are leftover pytest/e2e fixtures) — that's a separate cleanup. It does make the dead-end recoverable on any tenant if it happens again.