Remove the individual session journey page and make the live visitor
count a static indicator. Prepares for the new aggregated User Journeys
feature (v0.17).
Replaces 4 separate frustration API calls with single useBehavior hook.
Removes manual fetchData, loading/error state, and refresh interval—SWR
handles caching, revalidation, and error state automatically.
Frustration APIs and dashboard API are separate — when frustration
calls fail, Scroll Depth still rendered from cached SWR data,
creating a broken mixed state. Now tracks error state and hides
the bottom section entirely on failure.
- Remove column headers for cleaner look
- Show secondary info (avg, sessions, last seen) on hover
- Add orange percentage badge that slides in on hover
- Add empty row padding for consistent card height
- Add column headers to rage/dead click tables
- Rich empty states with icons matching dashboard pattern
- Add frustration trend comparison chart (current vs previous period)
- Show "New" badge instead of misleading "+100%" when previous period is 0
- Click-to-copy on CSS selectors with toast feedback
- Normalize min-height to 270px for consistent card sizing
- Fix page title to include site domain (Behavior · domain | Pulse)
- Add "last seen" column with relative timestamps
Add send hour, day of week/month selectors to report schedule modal.
Schedule cards now show descriptive delivery times like
"Every Monday at 9:00 AM (UTC)". Timezone picker moved into modal.
Add LRU cache provider (200 entries) to prevent unbounded SWR memory
growth. Clean up stale PKCE localStorage keys on app init. Cap chart
annotations to 20 visible reference lines with overflow indicator.
Replace 5-second setInterval polling with EventSource connection to the
new /realtime/stream SSE endpoint. The server pushes visitor updates
instead of each client independently polling. Auto-reconnects on
connection drops.
Replace useDashboardOverview, useDashboardPages, useDashboardLocations,
useDashboardDevices, useDashboardReferrers, useDashboardPerformance, and
useDashboardGoals with a single useDashboard hook that calls the existing
/dashboard batch endpoint. This endpoint runs all queries in parallel on
the backend and caches the result in Redis (30s TTL).
Reduces dashboard requests from 12 to 6 per refresh cycle (50% reduction).
At 1,000 concurrent users: ~6,000 req/min instead of 12,000.
The setInterval that drives the "Live · Xs ago" display was at the page level,
forcing all 10+ dashboard components to re-render every second. Now it lives
inside Chart — the only consumer — so the rest of the dashboard is unaffected.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When navigating to dashboard with cached SWR data, setLastUpdatedAt
triggered a second full render of the entire dashboard immediately
after the first. Using a ref instead avoids this — the value still
updates and Chart reads it on the next 1-second tick re-render.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DottedMap: move createMap, stagger helpers, and base dots path to module
scope so they compute once on module load and survive unmount/remount
cycles — eliminates all recomputation when switching tabs.
Dashboard: restore static imports for the 5 above-fold components
(Chart, ContentStats, TopReferrers, Locations, TechSpecs) now that
their heavy computations are memoized. Keeps below-fold components
(PerformanceStats, GoalStats, ScrollDepth, Campaigns, etc.) dynamic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Memoize createMap() in DottedMap (was regenerating 8000 SVG dots every
render) and convert 11 heavy dashboard components to next/dynamic imports
so the page shell renders instantly instead of blocking on one massive
render pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SiteNav now lives in the [id] layout instead of each page, so it stays
mounted during route transitions. Switching between Dashboard, Uptime,
Funnels, and Settings no longer flashes a full-page skeleton.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Drops in the exact 21st.dev FunnelChart component with motion/react,
curved bezier segments, layered rings, and spring hover animations.
Removes the previous custom SVG implementation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the recharts BarChart with a custom funnel component using clip-path
trapezoid segments, framer-motion animations, and hover interactions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ChartContainer, ChartConfig, ChartTooltip, ChartTooltipContent
primitives ported from ShadCN's chart pattern. Refactor all 3 chart
locations (dashboard, funnels, uptime) to use CSS variable-driven
theming instead of duplicated CHART_COLORS_LIGHT/DARK objects.
- Add --chart-1 through --chart-5, --chart-grid, --chart-axis CSS vars
- Remove duplicated color objects from 3 files (-223 lines)
- Add accessibilityLayer to all charts
- Rounded bar corners on funnel chart
- Tooltips use Tailwind dark classes instead of imperative style props
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Custom calendar (DatePicker) instead of native date input
- Custom dropdown (Select) instead of native select
- EU date format (DD/MM/YYYY) in tooltips and form
- Right-click context menu on chart to add annotations
- Optional time field (HH:MM) for precise timestamps
- Escape key to dismiss, loading state on save
- Bump @ciphera-net/ui to 0.0.95
Adds toggle in Data & Privacy > Filtering section to exclude entries
where geographic data could not be resolved from location stats.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The /features page mixed inline SVG JSX with Phosphor component
references in the capabilities array. The typeof check couldn't
distinguish them, causing a prerender crash:
"Objects are not valid as a React child (found: object with keys
{$$typeof, render, displayName})".
Pre-rendering Share2Icon and GlobeIcon as JSX makes all entries
consistent and eliminates the conditional entirely.
window.location.reload() causes infinite loops when the CDN keeps
serving cached assets. Instead, silently treat Server Action failures
as no-session — the OAuth flow uses full navigations (window.location.href)
which naturally fetch fresh content from the server on return.
Use sessionStorage guard so the hard reload only fires once. If the
reload doesn't fix it (CDN still serving stale JS), fall through
gracefully instead of looping forever.
Wrap all Server Action calls (getSessionAction, exchangeAuthCode,
logoutAction) in try-catch so a cached browser bundle with old action
IDs triggers a hard reload instead of an infinite loading spinner.
Dashboard, Uptime, Funnels, and Settings now use a consistent
underline tab bar with orange active indicator, matching the
existing panel tab design language.
Pair Campaigns with Goals & Events in a half-width grid to avoid empty
space. Show medium and campaign on a visible second line under source
name instead of subtle inline text.
Rebuild Campaigns from table layout to simple row-based design matching
Content, Referrers, Locations, and Technology panels. Add click-to-filter
by utm_source. Change filter button icon from plus to funnel.
Filter dropdowns previously only showed ~10 values from cached dashboard
data. Now lazy-loads up to 100 values per dimension when the dropdown
opens. Also removes duplicate "Direct" entry from referrer suggestions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>