Published

Dashboards Fase 2 — UX / Performance / A11y Smoke Test

Connect any source, model it as an ontology, transform it, and operationalize it, analytics, automation and machine learning, under one governed, self-hostable roof. --- Most teams stitch the...

Dashboards Fase 2 — UX / Performance / A11y Smoke Test

Manual verification that the Fase 2 production-grade surface (2.A–2.F) holds end to end. Run in dev with at least one dashboard that has 10+ widgets and one widget bound to a table with >1 000 rows.

Keep Chrome DevTools open: Network, Console, React DevTools Profiler (extension), and the device emulation toolbar. Axe DevTools extension is recommended for a11y.

All nine cases must pass before closing Fase 2.


1 — Design tokens: dark/light swap clean (2.A)

Setup: open /dashboards. Toggle dark mode.

Expected:

  • No flash of unthemed content — all surfaces swap within one frame.
  • Accent blue (--ws-accent-primary) consistent across: hero search focus ring, dashboard card hover border, save button, widget slot selected border, filter pill active, loading spinner.
  • Success green (--ws-accent-success): "published" badge, Saved state, Publish button.
  • Error red (--ws-accent-error): save error banner, query error placeholder, WidgetErrorBoundary fallback.
  • Chart widget with 5+ series: each series picks from --ws-chart-1..15. In dark mode, series colours stay legible (no blown-out reds / too-dim blues).
  • Inspect any widget via DevTools → style attribute references var(--ws-*), not #hex.

2 — Skeletons + empty/error primitives (2.A)

Setup: throttle network to "Slow 3G" in DevTools. Open a dashboard.

Expected:

  • Widget bodies show chart-shaped skeletons (animated shimmer) while loading, not blank boxes.
  • KPI widgets show KPI-shaped skeletons (big number + label + delta shimmer).
  • Table widget shows header + rows skeleton.
  • Open data sidebar on a dashboard with no sources attached → EmptyState with icon + "No sources yet" + primary CTA "+ Add data source".
  • Click a dataset that fails to query → widget renders the ErrorState (or Placeholder) with coherent error copy, not a crash.

3 — Re-render isolation (2.B)

Setup: open a dashboard with 15+ widgets. Open React DevTools Profiler. Click "Record" → single-click on one widget → stop recording.

Expected:

  • Profiler shows ≤ 3 components with commit:
    • The touched WidgetSlot
    • Its parent (re-renders one level up are normal)
    • WidgetConfigPanel if open
  • The other 14+ WidgetSlots show as "Did not render" or skipped.
  • Hover a WidgetSlot that didn't render → tooltip: "This component was not affected by this update".

4 — Virtual scroll in TableViz (2.B)

Setup: table widget bound to a dataset with ≥ 1 000 rows (or mock a large dataset). Widget in view.

Expected:

  • Inspect DOM inside the widget: only ~15–25 div[role="row"] elements present at any moment, not 1 000+.
  • Scroll rapidly through the widget → stays at 60 fps (DevTools Performance tab: no dropped frames, no yellow/red bars on main thread).
  • Header (role="row" with role="columnheader" cells) stays pinned at top during scroll.
  • Row numbers in the # column are correct at any scroll position (not capped at 200).

5 — Focus trap + ESC in modals (2.C)

Setup: click + New dashboard to open the create modal.

Expected:

  • Focus lands on the first interactive element (name input) automatically.
  • Tab cycles through: name → description → location → Cancel → Save → back to name.
  • Shift+Tab cycles backward symmetrically.
  • ESC closes the modal when isSaving is false; focus returns to the + New dashboard button.
  • Start a save that fails (offline) → ESC is ignored while saving, resumes working after the failure.
  • Repeat for Data Source modal (from the builder) and Widget Picker modal — same behaviour.
  • Axe DevTools → "Run full-page scan" on the open modal → zero serious/critical violations.

6 — Canvas keyboard navigation (2.C)

Setup: dashboard with 5+ widgets. Click one widget to select it.

Expected:

  • Tab from outside the canvas → focus enters the selected widget (visible :focus-visible outline in primary colour).
  • ArrowRight / ArrowDown → selects and focuses the next widget; the previous one loses its aria-selected="true".
  • ArrowLeft / ArrowUp → reverse. Wraps around at the ends.
  • Delete or Backspace on a selected widget → removes that widget; focus ends up somewhere reasonable (next sibling or none).
  • Screen-reader announcement (VoiceOver / NVDA) reads the widget title + "widget" + "selected".

7 — Responsive: iPad + iPhone (2.D)

Setup: DevTools device toolbar → emulate iPhone 12 Pro, then iPad Pro. Open /dashboards and open one dashboard.

iPhone 12 Pro (portrait):

  • Listing: grid 1 column. Top nav tabs scroll horizontally without breaking.
  • Dashboard card row collapses — no horizontal overflow.
  • Create modal opens as a bottom sheet (aligned to bottom, full width, rounded top corners).
  • Open builder → data sidebar, filter sidebar, config panel all hidden; AI Assist and Publish buttons hidden.
  • Canvas visible with widgets. Touching a widget does not accidentally drag (touchMode=true disables RGL gestures).

iPad Pro (portrait):

  • Listing: 2–3 cards per row (dashGrid auto-fill).
  • Builder: data sidebar narrower (~200px), config panel narrower (~260px). Canvas runs 8-col grid.
  • Touch tap on a widget selects it (no drag). Rotating to landscape → canvas returns to 12-col.

8 — Sidebar polish + frescura (2.E)

Setup: open a builder with 5+ attached sources (mix of dataset and object_type).

Expected:

  • Data sidebar header is sticky: scroll the list down, header + search row stay pinned at top.
  • Header count badge is pill-shaped with primary accent background.
  • Search input appears (because ≥ 2 sources). Type "xyz" (no match) → sidebar shows EmptyState "No matches — Nothing matches 'xyz'", no error.
  • Clear button (×) next to the search input clears the query.
  • Each group header (DATASETS, OBJECT TYPES) has its own small count badge next to the label.
  • Empty case (detach all sources) → sidebar shows EmptyState with icon + "No sources yet" + primary CTA that opens the data browser.
  • On the Page tab, filter sidebar shows EmptyState with message about "Coming in the next release". Add button is disabled with tooltip "Coming soon".
  • Press any primary CTA → subtle scale-down animation (0.98) on active, returns smoothly.

9 — Templates removed cleanly (2.F)

Setup: in DevTools console, seed a stale session:

sessionStorage.setItem('dashboards_tab_nav', JSON.stringify({ topTab: 'templates', selectedDashboardId: null, viewMode: 'grid' }));

Reload /dashboards.

Expected:

  • Top nav shows only "All Dashboards" and "Shared with you" — no "Templates" tab.
  • The stale topTab: 'templates' session restores as 'all' (no error in console, no blank state).
  • No template cards rendered anywhere. Searching the source for TEMPLATES[ returns zero results in the codebase (sanity check).
  • grep -n 'templates' components/workspace/tabs/dashboards/DashboardTab.tsx returns zero operational lines (only the header doc comment + the migration guard).

Closing — cross-cutting

Run once at the end:

  • npx tsc --noEmit → clean.
  • Lighthouse → Accessibility score ≥ 95 on /dashboards (listing and with one dashboard open).
  • Fase 1 smoke test (docs/dashboards-reliability-smoketest.md) re-run, all 6 cases still pass. Zero regressions on reliability is non-negotiable.
  • Prometheus: dashboard_widget_renders_total{status=...} still increments with widget renders (no telemetry break).
  • Dashboard with one object_type-backed widget and one raw dataset widget — both emit metrics with the correct source_kind label. The ontology differentiator channel stays alive.

If any item fails, stop and file before moving to Fase 3.