The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
12 KiB
Managing existing dashboard widgets
SKILL route: SKILL.md §3 Update a shipped type — skip widget-intake.md.
Use when changing a shipped widget type — not when adding a new widget_type (checklist-new-widget-type.md).
Reference implementation for patterns: products/dashboards/frontend/widgets/error_tracking/ (error_tracking_list).
Before finishing, apply skill-maintenance.md for any doc updates required by your change.
What you cannot change in place
widget_typeis immutable on an existingDashboardWidgetrow. To switch visualization kind, add a new tile (new type) and remove the old one.- Stored tile
w/hare not rewritten when catalogdefaultLayoutchanges — only new adds and min/max enforcement on the next layout pass. See layout-and-ux.md.
What kind of change?
| Goal | Primary files | Also check |
|---|---|---|
Widget query / run_widgets payload |
backend/widgets/<widget_type>.py, widget_registry.py, widget_query_throttle.py |
test_run_widgets.py, test_widget_query_throttle.py; product listing throttles (replay); dashboard_widget_delivery SLO (automatic) |
| Config fields (filters, limits, sort) | widget_specs/configs.py (Pydantic SSOT), WidgetSpec.form_fields, Edit*WidgetModal.tsx, *WidgetConfigValidation.ts (API errors) |
generated/widget-configs.zod.ts, insightscli build:openapi (OpenAPI/Zod derive from WIDGET_SPECS), registry parseConfigApiError, MCP catalog config_schema snapshots |
run_* result shape / list footer |
backend/widgets/<widget_type>.py, widget Component footer |
list-widget-patterns.md § Pagination footer |
| Tile filter bar | *WidgetTileFilters.tsx, widgetTileFiltersHooks.ts, registry TileFilters; mount gated by hasProductAccess in DashboardWidgetItem |
list-widget-patterns.md; debounced refresh via scheduleRefreshDashboardWidgets in dashboardLogic.tsx listeners |
| ET issue row actions on tile | ErrorTrackingWidget.tsx, widgetProductAccess.ts |
Dashboard edit or Error tracking Editor; read-only status/assignee when neither |
| Header title → product scene | catalog.ts titleHref, WidgetCardHeader.tsx isDashboardEditMode |
list-widget-patterns.md § Header title navigation |
| Tile name / description UX | Edit*WidgetModal.tsx, EditWidgetModalTileDetailsSection.tsx |
Mirror EditErrorTrackingWidgetModal.tsx field layout; activity log tests if serializer fields change |
| Default size when adding a tile | catalog.ts defaultLayout.w / .h |
dashboardLogic.addWidgetTiles (reads catalog defaults) |
| Min/max resize on dashboard grid | catalog.ts defaultLayout.minW / .minH |
layout-and-ux.md § Tile min/max size — tileLayouts.ts, tileLayouts.test.ts |
| Card header / date range display | catalog.ts (headerLayout, headerMeta, headerTitle, titleHref) |
WidgetCardHeader, stories |
| Setup / availability gate | catalog.ts availability, widgetAvailability.ts |
WidgetRuntimeAvailabilityGuard; BE availability_requirements for MCP catalog |
| RBAC lock on tile | widget_registry.py required_product_access, FE catalog.ts productAccess |
types.ts, widgetProductAccess.ts (WIDGET_PRODUCT_ACCESS_CHECKS) |
| Add-modal preview | widgets/previews/ + DASHBOARD_WIDGET_PREVIEWS in widget_types/catalog.ts |
Reuse demo data from widgetOverviewStoryFixtures.ts when possible |
| Public/shared placeholder copy | catalog.ts sharedPlaceholder |
DashboardWidgetItem.test.tsx public placement; test_sharing.py |
| Agent-facing catalog copy | widget_specs/registry.py WidgetSpec (label, description, product_access_denied_message); config_schema auto from Pydantic |
dashboard-widget-catalog-list / test_dashboard_widgets.py catalog assertions |
| Storybook / overview fixtures | WidgetCard/*.stories.tsx, widgetOverviewStoryFixtures.ts, Edit*WidgetModal.stories.tsx |
getWidgetOverviewDemoState exhaustive switch per catalog key |
| Deprecating / removing a type | See § Deprecating a widget type below | Orphan tiles fall back to unknown-type UI; keep BE runner until tiles are gone |
Registry parity table (all layers that must stay aligned): CONTRIBUTING.md § Frontend / backend parity.
Config schema migration (existing tiles)
When changing config shape on tiles already stored in Postgres:
- Backward-compatible (preferred) — in the Pydantic model (
widget_specs/configs.py), usemodel_validator/ field aliases to accept legacy keys;extra="forbid"strips unknown keys on validate. Existing tiles keep working without a data migration. - Breaking — treat as a new
widget_type(or accept that old tiles may fail validation until users re-save from the edit modal). - Touch every layer for non-breaking adds/removals:
- BE: Pydantic model in
widget_specs/configs.py(orcommon.py) - FE:
*WidgetConfigValidation.ts(form.pick()+ validation); registryparseConfigApiError - Codegen:
insightscli build:openapi— OpenAPI +widget-configs.zod.ts+ MCP schemas regenerate from Pydantic (newwidget_typeneeds a*WidgetConfigPydantic model +form_fieldsonWidgetSpec) - Stories/fixtures: component stories, edit modal stories, preview,
widgetOverviewStoryFixtures.ts
- BE: Pydantic model in
- Relative date ranges — update
backend/constants.py(WIDGET_DATE_FROM_VALUES_ORDERED) and PydanticWidgetDateFrominwidget_specs/common.py, theninsightscli build:openapi(regenerateswidget-date-from-options.json;widgetConfigShared.tsre-exportsWIDGET_DATE_RANGE_SELECT_OPTIONSfrom that JSON).
Config update flow (runtime)
- User opens ⋯ → Edit →
Edit*WidgetModalvalidates with Zod →onSave(config, metadataPatch?)(metadata frombuildWidgetTileMetadataPatch) DashboardItemscallsuseAsyncActions(dashboardLogic).updateWidgetTileso the modalawait onSave(...)waits for the PATCHdashboardLogic.updateWidgetTile→updateDashboardWidgetTile→ onedashboardsPartialUpdatewith nestedwidget{ config, name, description }and optionalshow_description- When
configchanged,refreshDashboardWidgetsre-runsrun_widgetsfor that tile
Guard saves with loading / disabledReason on Save — never leave the button clickable mid-mutation.
Edit modal layout
Type-specific edit-modal sections use a 2-column CSS grid (grid grid-cols-1 sm:grid-cols-2 gap-4) — copy EditErrorTrackingWidgetModal.tsx or EditWidgetModalTileDetailsSection.tsx.
- Full-width fields (name, description, test-account filter): put
WIDGET_SETTINGS_FIELD_FULL_WIDTH_CLASS(sm:col-span-2) on the grid child —Field.Pureor a wrapper<div>— not on the innerInput/TextArea. - Half-width fields (date range, sort, limit): omit
col-span-2; usefullWidthonSelectwhere needed.
Copy EditErrorTrackingWidgetModal.tsx Tile details + Filters sections when wiring a new edit modal.
Changing min/max tile size
Follow layout-and-ux.md § Tile min/max size — catalog defaultLayout.minW / minH only; changing mins affects all tiles of that type on the next layout pass without rewriting Postgres h/w.
Deprecating a widget type
There is no soft-delete for widget_type strings. Typical approach:
- Stop listing the type in FE/BE catalogs and registries (or never ship removal until tiles are migrated).
- Keep
run_*+ registry entry until no production tiles reference the type, or accept unknown-type fallback (header + bodyErrorBoundary, no live data). - Update MCP tool schema snapshots and
services/mcp/tests/tools/dashboards.integration.test.tswhen removing from catalog. - Update skill docs per skill-maintenance.md if deprecate process changed.
Do not remove backend validation for a type that still has rows in insights_dashboardwidget.
Verify (updates)
Run SKILL.md §6 Verify for every layer you touched. Prefer targeted paths under products/dashboards/frontend/widgets/<product>/ and test_run_widgets.py when the change is narrow.