Skip to main content
Version: v0.0.82

v0.0.79 — AI-modify polish, undo, per-tenant palette, dbt catalog

Released: 2026-05-18. Thirteen commits.

Big bug-fix-and-polish release. Closes the v0.0.78 Slack bug list (Ask-AI-Modify reliability + version history + donut readability + one-click Undo), lands the real dbt manifest adapter + Migration Cockpit page on the Catalog track, and ships the async runner for agent reviews.

Ask AI Modify, History, Search, Pie — Slack bug list

Five buckets of fixes around AI dashboard editing.

Bucket A — Ask-AI-Modify reliability

  • Pre-mutation snapshot in every AI action (create / modify / clear_all / set_layout / set_period / filter ops) so the first-ever AI turn on a fresh dashboard is undoable. Root cause for "AI wiped my dashboard and there's nothing to revert to".
  • SQL window 200 → 800 chars (4000 for selected card). The 200-char truncation dropped FROM/JOIN context, which is why MODIFY on a real card fell back to sample/mock data.
  • Recent revisions block injected into the AI context — last 10 rows with revision_number + age + change_note so the LLM can answer "what changed?" / "go back".
  • New chat action restore_revision — natural-language "undo" / "revert" routes to the same atomic snapshot+replay primitive the History drawer uses, with a pre-restore snapshot so even AI picking the wrong revision is recoverable.

Bucket B — Version history revert

Frontend silent error swallowing fixed on restoreRevision / loadRevisions / previewRevision / takeSnapshot. The "version history not working when we try to claim it" was a 403/500 silently eaten by catch { /* ignore */ }. The AI chat result handler reloads the dashboard on every mutation action, not just create / modify.

  • Shared datasets now visible: _DATASETS_QUERY LEFT JOINs dataset_shares on target_project_id = caller_pid.
  • Cross-project for org admins: is_org_admin flag threaded through router → discover_datasets; admin scope lifts the project boundary within the org. Cobuild + chat-agent tool path stay strict by default.
  • Phrase-match boost (+4.0) when the full intent appears verbatim in name / friendly_name / description. Catches long NL queries that pure token overlap was burying.
  • Result rows carry project_id / project_name / is_shared so the FE renders a "from Project X" pill for shared / foreign results.

Bucket D — Donut / Pie readability

Unified pie / donut renderer. Labels now show for every slice ≥ label_min_percent (default 4%) instead of being gated on data.length <= 6 (which left every realistic chart with no labels). New card_config.label_mode: percent | value | name_percent | none. White 2px stroke between slices for visible separation without hover.

Bucket E — One-click Undo

POST /dashboards/{id}/undo. Server resolves target: prefers the most recent "Before: …" snapshot, falls back to (latest - 1) for manual edits. 409 when there's nothing to undo. Toolbar Undo button (next to History) surfaces the undone change_note in the AI response panel.

Revisions — diff drawer + selected-card Undo + AI-search rerank

Three follow-ons to the bug-fix batch.

Revision diff endpoint + drawer

GET /dashboards/{id}/revisions/{a}/diff/{b} returns card-level added / removed / changed plus dashboard-level title / description / config diffs. Cards matched by card_id (fallback: lowercased title) so legacy snapshots still align. Field-level diffs limited to a curated whitelist to avoid noisy updated_at churn.

The History drawer's preview pane gets a Diff vs vN button that fetches the comparison against the revision immediately before the selected one. Inline +/−/~ summary with per-card field hints, capped to 5 fields per card for legibility.

Selected-card aware Undo (partial restore)

POST /dashboards/{id}/undo-card { card_id, revision_number? }. Resolves target revision the same way /undo does. Three branches: card added → DELETE, card removed → INSERT (preserving card_id), card changed → UPDATE every restorable field. Pre/post-snapshot bracket the operation so the partial undo is itself undoable.

The toolbar Undo button now reads Undo card when a card is selected and routes to /undo-card; otherwise it stays on /undo for full-dashboard revert.

AI-search LLM rerank

Optional stage-3 rerank using gpt-4o-mini. Token + phrase ranking stays as the deterministic base; when HONEYFRAME_AI_SEARCH_RERANK is enabled (default on) and intent has ≥3 tokens, the top _RERANK_TOP_K candidates go through one LLM call that re-orders by semantic relevance. Closes the "synonyms / NL intent gets buried" gap that phrase-match couldn't cover. Best-effort: any LLM / network failure leaves the token ranking intact. Model overridable via HONEYFRAME_AI_SEARCH_RERANK_MODEL (default openai:gpt-4o-mini).

Per-tenant branding palette

Pre-migration only primary_color was per-org — hover / accent_bg / page_bg / border_color / secondary_color all fell back to a global default hard-coded to honeyframe amber. That made full-tenant rebrands impossible without code edits.

Migration 2026-05-18_branding_palette.sql adds 5 palette columns to honeyframe.organizations (all nullable so existing tenants are unchanged) and can seed a tenant's row with a palette extracted from that tenant's visual system. Idempotent — only fills NULLs, so a future override doesn't conflict.

branding.GET ?slug=… overlays the new columns onto DEFAULTS with the same null-falls-through-to-default rule as the existing fields. UndefinedColumn from pre-migration tenants is caught and the SELECT retries against the legacy schema — zero-impact rollout.

Catalog — real dbt manifest adapter + Migration Cockpit page

DbtAdapter.list_assets() now parses manifest.json and emits one CatalogAsset per model / seed / snapshot / source. Tests are skipped (not migratable feeds). get_lineage() walks parent_map + child_map to surface upstream + downstream edges. health() reports asset_count + ISO last_sync_at. Manifest cache invalidates on mtime so long-running processes don't need a restart after dbt run. FQN convention: dbt.<project>.<schema>.<asset>.

bootstrap.py auto-configures a honeyframe-dbt instance at startup if a honeyframe manifest is found at HONEYFRAME_DBT_MANIFEST, paas/dbt/target/manifest.json, or /opt/honeyframe/paas/dbt/target/.

New MigrationCockpitPage at /catalog (project + standalone routes) + Data nav entry. Surfaces the 5 backend endpoints already scaffolded in v0.0.78: GET /sources, GET /adapter-types, GET /feeds, POST /score, POST /migrate (drawer with dbt path + SQL preview + AI explanation). Backend stubs still return empty so the page renders empty states that describe Phase 1 vs Phase 2 — honest about what's wired and what's coming.

Agent reviews — async runner + cancel

POST /agent-reviews/{rid}/runs now returns {run_id, status: "queued"} with HTTP 201 immediately instead of blocking until the loop finishes. run_all_tests splits into _prepare_run + _execute_run_loop; start_run() schedules execution via asyncio.create_task on a fresh AsyncSession. Frontend handleRunAll polls GET /runs/{id} every 2s until status is terminal (30-min ceiling).

POST /agent-reviews/{rid}/runs/{run_id}/cancel + a Cancel button next to Run All appears while the poll loop is active. Queued runs flip to status='cancelled' directly on the endpoint; running runs set a process-local cancel flag that _execute_run_loop checks between executions; the loop writes status='cancelled' and finishes the partial counts.

Webapp — header chips for freshness + alert

Optional header_meta / header_alert / header_alert_severity on AppPageSpec — render next to the page title (right side) as pills (e.g. a data-freshness stamp + a critical-branches alert).

Polish + small fixes

  • KPI hero fills card vertically + bigger value. Used flex-col without justify-, so content clustered at the top of the row leaving dead space when the grid row was sized for chart cards. justify-between so caption pins top, value sits middle, meta row anchors bottom. Hero value bumped to text-5xl extrabold tracking-tight.
  • Free-form toggle label inverted. Button showed the NEXT mode rather than the CURRENT one. New label reads Layout: Compact / Layout: Free-form so current state is unambiguous, with a tooltip describing what clicking will do and an icon to make the affordance obvious.
  • status_label = 'N/A' dominated 'completed' in a unioned dbt intermediate model. One external source's rows hardcoded status_label = NULL, so the dashboard's COALESCE(status_label, 'N/A') ballooned every row from that source into the N/A bucket — outnumbering the real completed rows from the primary source. Replaced the NULL with a case-insensitive CASE WHEN against that source's raw status column covering completed / booked / pending / draft / no_show. Requires a downstream dbt run --select <model>+ to take effect.
  • Data Policies slice 6 audit now captures old_status (the pre-flip status) alongside new_value, so the audit drawer can reconstruct the transition without a side query.