Skip to content

feat(insights): add BoxPlot chart primitive to hog-charts library#59880

Merged
sampennington merged 13 commits into
masterfrom
posthog-code/hog-charts-boxplot-primitive
May 27, 2026
Merged

feat(insights): add BoxPlot chart primitive to hog-charts library#59880
sampennington merged 13 commits into
masterfrom
posthog-code/hog-charts-boxplot-primitive

Conversation

@sampennington
Copy link
Copy Markdown
Contributor

@sampennington sampennington commented May 25, 2026

Problem

The hog-charts library is the new in-house chart toolkit gradually replacing Chart.js for trends visualizations. The legacy BoxPlot trend uses Chart.js via the chartjs-chart-boxplot plugin. To migrate it, we need a native hog-charts BoxPlot primitive that follows the library's layering conventions (pure geometry + canvas drawing primitives + hit-testing + React component) and reuses the BarChart band/group scale infrastructure.

This PR only adds the library primitive. Wiring it into the BoxPlot insight behind a feature flag is a separate follow-up — Trends.tsx, feature flags, and persons-modal integration are not touched here.

Changes

Adds a boxplot chart

How did you test this code?

Storybook snapshots. It's no used anywhere yet.

Publish to changelog?

do not publish to changelog

Docs update

No docs update needed — this is a library primitive that isn't user-visible until the BoxPlot insight migration follow-up.

🤖 Agent context

  • Agent: Claude Code (Opus 4.7) via PostHog Code.
  • Approach: Studied BarChart (closest template — band+group scales, grouped slot hit-testing) and PieChart (most recent new-chart-type onboarding — single-canvas, dedicated geometry module + tests). The hardest decision was how to reconcile Series.data: number[] with a per-point six-number summary. I considered (1) building a non-Chart-base component, (2) overloading series.data with flattened arrays, and (3) adapting BoxPlotSeries to Series<{ datums }> internally with medians on data and synthetic min/max series fed into createBarScales's existing stackedSeries option to drive the y-domain. Picked (3) — it reuses the entire Chart infrastructure (interaction, tooltip lifecycle, error boundary, axis labels, overlays) without forking, and the meta carries the original datums all the way to the tooltip / click callbacks.
  • Tooltip in tests: renderHogChart intercepts the component's tooltip prop to capture TooltipContext, so the component-level test asserts on the structured context (datums reachable through series.meta) and a separate BoxPlotTooltip.test.tsx renders the tooltip directly with a hand-crafted context to verify the six-stat row ordering — same split RTL would suggest for any context-dependent render.
  • onBoxClick semantics: matches BarChart's onPointClickseries is the primary (first-visible) series, with all visible series at the column in crossSeriesData. The product layer can narrow to the under-cursor series via crossSeriesData if needed.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

🎭 Playwright didn't run on this PR — your changes touch code that could affect E2E behavior, but Playwright is opt-in via label now to keep CI cost down.

Add the run-playwright label if you want an E2E sweep before merging — CI will pick it up automatically.

Most PRs don't need this. Real regressions still get caught on master and fix-forward.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 25, 2026

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
frontend/src/lib/hog-charts/charts/BoxPlot/BoxPlot.tsx:298-305
Redundant full-series recompute on every hover event. `datum` is already in hand at this point, so `computeBoxRect` can be called directly instead of iterating every label via `computeSeriesBoxes` and then searching for the matching index with `.find`.

```suggestion
                const box = computeBoxRect({
                    seriesKey: s.key,
                    label: drawLabels[hoverIndex],
                    dataIndex: hoverIndex,
                    datum,
                    scales: priv.scales,
                    grouped: priv.grouped,
                })
```

### Issue 2 of 2
frontend/src/lib/hog-charts/charts/BoxPlot/BoxPlot.tsx:404-405
The JSDoc says the function "Falls back to `rgba(0,0,0,alpha)`" for unrecognised colors, but the actual code returns the original `color` string unchanged. The comment is misleading — the canvas will just try to use the raw color string as-is.

```suggestion
/** Best-effort hex/rgb-to-rgba conversion. Returns the original color string unchanged when
 *  the input isn't a recognised hex/rgb/rgba color — the canvas will attempt to use it as-is. */
```

Reviews (1): Last reviewed commit: "feat(insights): add BoxPlot chart primit..." | Re-trigger Greptile

Comment thread frontend/src/lib/hog-charts/charts/BoxPlot/BoxPlot.tsx Outdated
Comment thread frontend/src/lib/hog-charts/charts/BoxPlot/BoxPlot.tsx Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

Size Change: 0 B

Total Size: 80.2 MB

ℹ️ View Unchanged
Filename Size Change
frontend/dist-report/decompression-worker/src/scenes/session-recordings/player/snapshot-processing/decompressionWorker 2.85 kB 0 B
frontend/dist-report/exporter/_chunks/chunk 8.39 MB +388 B (0%)
frontend/dist-report/exporter/_parent/products/actions/frontend/pages/Action 24.9 kB 0 B
frontend/dist-report/exporter/_parent/products/actions/frontend/pages/Actions 1.3 kB 0 B
frontend/dist-report/exporter/_parent/products/business_knowledge/frontend/scenes/BusinessKnowledgeScene 19 kB 0 B
frontend/dist-report/exporter/_parent/products/conversations/frontend/components/Assignee/CyclotronJobInputAssignee 1.64 kB 0 B
frontend/dist-report/exporter/_parent/products/conversations/frontend/components/SlaBusinessHours/CyclotronJobInputBusinessHours 3.02 kB 0 B
frontend/dist-report/exporter/_parent/products/conversations/frontend/components/TicketTags/CyclotronJobInputTicketTags 1.02 kB 0 B
frontend/dist-report/exporter/_parent/products/conversations/frontend/scenes/settings/SupportSettingsScene 1.78 kB 0 B
frontend/dist-report/exporter/_parent/products/conversations/frontend/scenes/ticket/SupportTicketScene 33.9 kB 0 B
frontend/dist-report/exporter/_parent/products/conversations/frontend/scenes/tickets/SupportTicketsScene 1.04 kB 0 B
frontend/dist-report/exporter/_parent/products/customer_analytics/frontend/CustomerAnalyticsScene 36.9 kB 0 B
frontend/dist-report/exporter/_parent/products/customer_analytics/frontend/scenes/CustomerAnalyticsConfigurationScene/CustomerAnalyticsConfigurationScene 2.61 kB 0 B
frontend/dist-report/exporter/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyBuilderScene/CustomerJourneyBuilderScene 2.15 kB 0 B
frontend/dist-report/exporter/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyTemplatesScene/CustomerJourneyTemplatesScene 7.83 kB 0 B
frontend/dist-report/exporter/_parent/products/data_warehouse/DataWarehouseScene 46.8 kB 0 B
frontend/dist-report/exporter/_parent/products/data_warehouse/frontend/scenes/NewSourceScene/NewSourceScene 1.08 kB 0 B
frontend/dist-report/exporter/_parent/products/data_warehouse/frontend/scenes/SchemaScene/SchemaScene 24 kB 0 B
frontend/dist-report/exporter/_parent/products/data_warehouse/frontend/scenes/SourceScene/SourceScene 1.03 kB 0 B
frontend/dist-report/exporter/_parent/products/data_warehouse/frontend/scenes/SourcesScene/SourcesScene 6.27 kB 0 B
frontend/dist-report/exporter/_parent/products/deployments/frontend/Deployment 4.02 kB 0 B
frontend/dist-report/exporter/_parent/products/deployments/frontend/DeploymentProject 5.54 kB 0 B
frontend/dist-report/exporter/_parent/products/deployments/frontend/Deployments 9.28 kB 0 B
frontend/dist-report/exporter/_parent/products/early_access_features/frontend/EarlyAccessFeature 991 B 0 B
frontend/dist-report/exporter/_parent/products/early_access_features/frontend/EarlyAccessFeatures 3.21 kB 0 B
frontend/dist-report/exporter/_parent/products/endpoints/frontend/EndpointScene 40.6 kB 0 B
frontend/dist-report/exporter/_parent/products/endpoints/frontend/EndpointsScene 24.5 kB 0 B
frontend/dist-report/exporter/_parent/products/error_tracking/frontend/scenes/ErrorTrackingFingerprintsScene/ErrorTrackingIssueFingerprintsScene 7.37 kB 0 B
frontend/dist-report/exporter/_parent/products/error_tracking/frontend/scenes/ErrorTrackingIssueScene/ErrorTrackingIssueScene 102 kB 0 B
frontend/dist-report/exporter/_parent/products/error_tracking/frontend/scenes/ErrorTrackingScene/ErrorTrackingScene 27.1 kB 0 B
frontend/dist-report/exporter/_parent/products/feature_flags/frontend/FeatureFlagTemplatesScene 7.35 kB 0 B
frontend/dist-report/exporter/_parent/products/games/368Hedgehogs/368Hedgehogs 5.58 kB 0 B
frontend/dist-report/exporter/_parent/products/games/FlappyHog/FlappyHog 6.09 kB 0 B
frontend/dist-report/exporter/_parent/products/legal_documents/frontend/scenes/LegalDocumentNewScene 59.7 kB 0 B
frontend/dist-report/exporter/_parent/products/legal_documents/frontend/scenes/LegalDocumentsScene 5.28 kB 0 B
frontend/dist-report/exporter/_parent/products/links/frontend/LinkScene 25.2 kB 0 B
frontend/dist-report/exporter/_parent/products/links/frontend/LinksScene 4.51 kB 0 B
frontend/dist-report/exporter/_parent/products/live_debugger/frontend/LiveDebugger 19.4 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/clusters/LLMAnalyticsClusterScene 21.6 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/clusters/LLMAnalyticsClustersScene 55 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/datasets/LLMAnalyticsDatasetScene 20.9 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/datasets/LLMAnalyticsDatasetsScene 3.6 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/evaluations/EvaluationTemplates 881 B 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/evaluations/LLMAnalyticsEvaluation 59.8 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/evaluations/LLMAnalyticsEvaluationsScene 28.1 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/LLMAnalyticsScene 118 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/LLMAnalyticsSessionScene 16.7 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/LLMAnalyticsTraceScene 130 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/LLMAnalyticsUsers 832 B 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/LLMASessionFeedbackDisplay 5.15 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/playground/LLMAnalyticsPlaygroundScene 37.7 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/prompts/LLMPromptScene 29.1 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/prompts/LLMPromptsScene 4.79 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/skills/LLMSkillScene 895 B 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/skills/LLMSkillsScene 912 B 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/tags/LLMAnalyticsTag 27.3 kB 0 B
frontend/dist-report/exporter/_parent/products/llm_analytics/frontend/tags/LLMAnalyticsTagsScene 7.26 kB 0 B
frontend/dist-report/exporter/_parent/products/logs/frontend/LogsScene 17.8 kB 0 B
frontend/dist-report/exporter/_parent/products/logs/frontend/scenes/LogsAlertDetailScene/LogsAlertDetailScene 17.3 kB 0 B
frontend/dist-report/exporter/_parent/products/logs/frontend/scenes/LogsSamplingDetailScene/LogsSamplingDetailScene 5.27 kB 0 B
frontend/dist-report/exporter/_parent/products/logs/frontend/scenes/LogsSamplingNewScene/LogsSamplingNewScene 2.22 kB 0 B
frontend/dist-report/exporter/_parent/products/managed_migrations/frontend/ManagedMigration 14.9 kB 0 B
frontend/dist-report/exporter/_parent/products/mcp_analytics/frontend/MCPAnalyticsScene 40.2 kB 0 B
frontend/dist-report/exporter/_parent/products/mcp_analytics/frontend/MCPAnalyticsToolDetail 18.5 kB 0 B
frontend/dist-report/exporter/_parent/products/metrics/frontend/MetricsScene 1.15 kB 0 B
frontend/dist-report/exporter/_parent/products/product_analytics/frontend/insights/trends/StickinessBarChart/StickinessBarChart 3.27 kB 0 B
frontend/dist-report/exporter/_parent/products/product_analytics/frontend/insights/trends/StickinessLineChart/StickinessLineChart 3.11 kB 0 B
frontend/dist-report/exporter/_parent/products/product_analytics/frontend/insights/trends/TrendsBarChart/TrendsBarChart 7.12 kB 0 B
frontend/dist-report/exporter/_parent/products/product_analytics/frontend/insights/trends/TrendsLifecycleChart/TrendsLifecycleChart 4.06 kB 0 B
frontend/dist-report/exporter/_parent/products/product_analytics/frontend/insights/trends/TrendsLineChart/TrendsLineChart 4.57 kB 0 B
frontend/dist-report/exporter/_parent/products/product_analytics/frontend/insights/trends/TrendsPieChart/TrendsPieChart 4.31 kB 0 B
frontend/dist-report/exporter/_parent/products/replay_vision/frontend/replay_scanners/ReplayScanner 20.5 kB 0 B
frontend/dist-report/exporter/_parent/products/replay_vision/frontend/replay_scanners/ReplayScannersScene 12.4 kB 0 B
frontend/dist-report/exporter/_parent/products/revenue_analytics/frontend/RevenueAnalyticsScene 26.5 kB 0 B
frontend/dist-report/exporter/_parent/products/session_summaries/frontend/SessionGroupSummariesTable 5.02 kB 0 B
frontend/dist-report/exporter/_parent/products/session_summaries/frontend/SessionGroupSummaryScene 19.2 kB 0 B
frontend/dist-report/exporter/_parent/products/tasks/frontend/TaskDetailScene 23.5 kB 0 B
frontend/dist-report/exporter/_parent/products/tasks/frontend/TaskTracker 14.6 kB 0 B
frontend/dist-report/exporter/_parent/products/tracing/frontend/TracingScene 54.1 kB 0 B
frontend/dist-report/exporter/_parent/products/user_interviews/frontend/UserInterview 9.28 kB 0 B
frontend/dist-report/exporter/_parent/products/user_interviews/frontend/UserInterviewResponse 5.64 kB 0 B
frontend/dist-report/exporter/_parent/products/user_interviews/frontend/UserInterviews 6.04 kB 0 B
frontend/dist-report/exporter/_parent/products/visual_review/frontend/scenes/VisualReviewIndexScene 2.52 kB 0 B
frontend/dist-report/exporter/_parent/products/visual_review/frontend/scenes/VisualReviewRunScene 44.6 kB 0 B
frontend/dist-report/exporter/_parent/products/visual_review/frontend/scenes/VisualReviewRunsScene 7.29 kB 0 B
frontend/dist-report/exporter/_parent/products/visual_review/frontend/scenes/VisualReviewSettingsScene 11.1 kB 0 B
frontend/dist-report/exporter/_parent/products/visual_review/frontend/scenes/VisualReviewSnapshotHistoryScene 13.9 kB 0 B
frontend/dist-report/exporter/_parent/products/visual_review/frontend/scenes/VisualReviewSnapshotOverviewScene 19.5 kB 0 B
frontend/dist-report/exporter/_parent/products/workflows/frontend/TemplateLibrary/MessageTemplate 16.6 kB 0 B
frontend/dist-report/exporter/_parent/products/workflows/frontend/Workflows/WorkflowScene 111 kB 0 B
frontend/dist-report/exporter/_parent/products/workflows/frontend/WorkflowsScene 60.1 kB 0 B
frontend/dist-report/exporter/src/exporter/exporter 19.1 kB 0 B
frontend/dist-report/exporter/src/exporter/scenes/ExporterDashboardScene 1.99 kB 0 B
frontend/dist-report/exporter/src/exporter/scenes/ExporterHeatmapScene 19.6 kB 0 B
frontend/dist-report/exporter/src/exporter/scenes/ExporterInsightScene 2.98 kB 0 B
frontend/dist-report/exporter/src/exporter/scenes/ExporterInterviewScene 310 kB 0 B
frontend/dist-report/exporter/src/exporter/scenes/ExporterNotebookScene 2.71 MB 0 B
frontend/dist-report/exporter/src/exporter/scenes/ExporterRecordingScene 1.1 kB 0 B
frontend/dist-report/exporter/src/exporterSharedChunkAnchors 1.19 kB 0 B
frontend/dist-report/exporter/src/lib/components/Cards/TextCard/TextCardMarkdownEditor 11.3 kB 0 B
frontend/dist-report/exporter/src/lib/components/MonacoDiffEditor 471 B 0 B
frontend/dist-report/exporter/src/lib/lemon-ui/LemonMarkdown/MermaidDiagram 2.22 kB 0 B
frontend/dist-report/exporter/src/lib/lemon-ui/LemonTextArea/LemonTextAreaMarkdown 808 B 0 B
frontend/dist-report/exporter/src/lib/lemon-ui/Link/Link 359 B 0 B
frontend/dist-report/exporter/src/lib/monaco/CodeEditorInline 798 B 0 B
frontend/dist-report/exporter/src/lib/monaco/vimMode 211 kB 0 B
frontend/dist-report/exporter/src/lib/ui/Button/ButtonPrimitives 422 B 0 B
frontend/dist-report/exporter/src/queries/nodes/WebVitals/WebVitals 7.48 kB 0 B
frontend/dist-report/exporter/src/queries/nodes/WebVitals/WebVitalsPathBreakdown 4.05 kB 0 B
frontend/dist-report/exporter/src/queries/schema 732 kB 0 B
frontend/dist-report/exporter/src/scenes/approvals/changeRequestsLogic 850 B 0 B
frontend/dist-report/exporter/src/scenes/authentication/passkeyLogic 790 B 0 B
frontend/dist-report/exporter/src/scenes/data-pipelines/event-filtering/EventFilterScene 22.2 kB 0 B
frontend/dist-report/exporter/src/scenes/data-pipelines/TransformationsScene 6.51 kB 0 B
frontend/dist-report/exporter/src/scenes/insights/views/BoxPlot/BoxPlot 5.35 kB 0 B
frontend/dist-report/exporter/src/scenes/insights/views/CalendarHeatMap/CalendarHeatMap 8.81 kB 0 B
frontend/dist-report/exporter/src/scenes/insights/views/RegionMap/RegionMap 29.7 kB 0 B
frontend/dist-report/exporter/src/scenes/insights/views/WorldMap/WorldMap 1.04 MB 0 B
frontend/dist-report/exporter/src/scenes/models/ModelsScene 19 kB 0 B
frontend/dist-report/exporter/src/scenes/models/NodeDetailScene 17 kB 0 B
frontend/dist-report/monaco-editor-worker/src/lib/monaco/workers/monacoEditorWorker 288 kB 0 B
frontend/dist-report/monaco-json-worker/src/lib/monaco/workers/monacoJsonWorker 419 kB 0 B
frontend/dist-report/monaco-typescript-worker/src/lib/monaco/workers/monacoTsWorker 7.02 MB 0 B
frontend/dist-report/posthog-app/_chunks/chunk 8.58 MB +387 B (0%)
frontend/dist-report/posthog-app/_parent/products/actions/frontend/pages/Action 25.1 kB 0 B
frontend/dist-report/posthog-app/_parent/products/actions/frontend/pages/Actions 1.36 kB 0 B
frontend/dist-report/posthog-app/_parent/products/business_knowledge/frontend/scenes/BusinessKnowledgeScene 19 kB 0 B
frontend/dist-report/posthog-app/_parent/products/conversations/frontend/components/Assignee/CyclotronJobInputAssignee 1.67 kB 0 B
frontend/dist-report/posthog-app/_parent/products/conversations/frontend/components/SlaBusinessHours/CyclotronJobInputBusinessHours 3.06 kB 0 B
frontend/dist-report/posthog-app/_parent/products/conversations/frontend/components/TicketTags/CyclotronJobInputTicketTags 1.06 kB 0 B
frontend/dist-report/posthog-app/_parent/products/conversations/frontend/scenes/settings/SupportSettingsScene 1.82 kB 0 B
frontend/dist-report/posthog-app/_parent/products/conversations/frontend/scenes/ticket/SupportTicketScene 26.6 kB 0 B
frontend/dist-report/posthog-app/_parent/products/conversations/frontend/scenes/tickets/SupportTicketsScene 1.07 kB 0 B
frontend/dist-report/posthog-app/_parent/products/customer_analytics/frontend/CustomerAnalyticsScene 35.7 kB 0 B
frontend/dist-report/posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerAnalyticsConfigurationScene/CustomerAnalyticsConfigurationScene 2.65 kB 0 B
frontend/dist-report/posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyBuilderScene/CustomerJourneyBuilderScene 2.18 kB 0 B
frontend/dist-report/posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyTemplatesScene/CustomerJourneyTemplatesScene 7.86 kB 0 B
frontend/dist-report/posthog-app/_parent/products/data_warehouse/DataWarehouseScene 1.78 kB 0 B
frontend/dist-report/posthog-app/_parent/products/data_warehouse/frontend/scenes/NewSourceScene/NewSourceScene 1.15 kB 0 B
frontend/dist-report/posthog-app/_parent/products/data_warehouse/frontend/scenes/SchemaScene/SchemaScene 24.1 kB 0 B
frontend/dist-report/posthog-app/_parent/products/data_warehouse/frontend/scenes/SourceScene/SourceScene 1.06 kB 0 B
frontend/dist-report/posthog-app/_parent/products/data_warehouse/frontend/scenes/SourcesScene/SourcesScene 6.31 kB 0 B
frontend/dist-report/posthog-app/_parent/products/deployments/frontend/Deployment 4.05 kB 0 B
frontend/dist-report/posthog-app/_parent/products/deployments/frontend/DeploymentProject 5.58 kB 0 B
frontend/dist-report/posthog-app/_parent/products/deployments/frontend/Deployments 9.31 kB 0 B
frontend/dist-report/posthog-app/_parent/products/early_access_features/frontend/EarlyAccessFeature 1.16 kB 0 B
frontend/dist-report/posthog-app/_parent/products/early_access_features/frontend/EarlyAccessFeatures 3.24 kB 0 B
frontend/dist-report/posthog-app/_parent/products/endpoints/frontend/EndpointScene 40.7 kB 0 B
frontend/dist-report/posthog-app/_parent/products/endpoints/frontend/EndpointsScene 22.4 kB 0 B
frontend/dist-report/posthog-app/_parent/products/error_tracking/frontend/scenes/ErrorTrackingFingerprintsScene/ErrorTrackingIssueFingerprintsScene 7.44 kB 0 B
frontend/dist-report/posthog-app/_parent/products/error_tracking/frontend/scenes/ErrorTrackingIssueScene/ErrorTrackingIssueScene 101 kB 0 B
frontend/dist-report/posthog-app/_parent/products/error_tracking/frontend/scenes/ErrorTrackingScene/ErrorTrackingScene 27.2 kB 0 B
frontend/dist-report/posthog-app/_parent/products/feature_flags/frontend/FeatureFlagTemplatesScene 7.38 kB 0 B
frontend/dist-report/posthog-app/_parent/products/games/368Hedgehogs/368Hedgehogs 5.61 kB 0 B
frontend/dist-report/posthog-app/_parent/products/games/FlappyHog/FlappyHog 6.12 kB 0 B
frontend/dist-report/posthog-app/_parent/products/legal_documents/frontend/scenes/LegalDocumentNewScene 59.7 kB 0 B
frontend/dist-report/posthog-app/_parent/products/legal_documents/frontend/scenes/LegalDocumentsScene 5.32 kB 0 B
frontend/dist-report/posthog-app/_parent/products/links/frontend/LinkScene 25.2 kB 0 B
frontend/dist-report/posthog-app/_parent/products/links/frontend/LinksScene 4.55 kB 0 B
frontend/dist-report/posthog-app/_parent/products/live_debugger/frontend/LiveDebugger 19.5 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/clusters/LLMAnalyticsClusterScene 21.7 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/clusters/LLMAnalyticsClustersScene 55 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/datasets/LLMAnalyticsDatasetScene 21 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/datasets/LLMAnalyticsDatasetsScene 3.63 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/evaluations/EvaluationTemplates 915 B 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/evaluations/LLMAnalyticsEvaluation 59.8 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/evaluations/LLMAnalyticsEvaluationsScene 28.1 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/LLMAnalyticsScene 119 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/LLMAnalyticsSessionScene 16.8 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/LLMAnalyticsTraceScene 130 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/LLMAnalyticsUsers 866 B 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/LLMASessionFeedbackDisplay 5.19 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/playground/LLMAnalyticsPlaygroundScene 37.7 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/prompts/LLMPromptScene 29.2 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/prompts/LLMPromptsScene 4.82 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/skills/LLMSkillScene 929 B 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/skills/LLMSkillsScene 946 B 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/tags/LLMAnalyticsTag 27.3 kB 0 B
frontend/dist-report/posthog-app/_parent/products/llm_analytics/frontend/tags/LLMAnalyticsTagsScene 7.3 kB 0 B
frontend/dist-report/posthog-app/_parent/products/logs/frontend/LogsScene 17.8 kB 0 B
frontend/dist-report/posthog-app/_parent/products/logs/frontend/scenes/LogsAlertDetailScene/LogsAlertDetailScene 17.3 kB 0 B
frontend/dist-report/posthog-app/_parent/products/logs/frontend/scenes/LogsSamplingDetailScene/LogsSamplingDetailScene 5.31 kB 0 B
frontend/dist-report/posthog-app/_parent/products/logs/frontend/scenes/LogsSamplingNewScene/LogsSamplingNewScene 2.26 kB 0 B
frontend/dist-report/posthog-app/_parent/products/managed_migrations/frontend/ManagedMigration 14.9 kB 0 B
frontend/dist-report/posthog-app/_parent/products/mcp_analytics/frontend/MCPAnalyticsScene 40.2 kB 0 B
frontend/dist-report/posthog-app/_parent/products/mcp_analytics/frontend/MCPAnalyticsToolDetail 18.5 kB 0 B
frontend/dist-report/posthog-app/_parent/products/metrics/frontend/MetricsScene 1.18 kB 0 B
frontend/dist-report/posthog-app/_parent/products/product_analytics/frontend/insights/trends/StickinessBarChart/StickinessBarChart 3.31 kB 0 B
frontend/dist-report/posthog-app/_parent/products/product_analytics/frontend/insights/trends/StickinessLineChart/StickinessLineChart 3.14 kB 0 B
frontend/dist-report/posthog-app/_parent/products/product_analytics/frontend/insights/trends/TrendsBarChart/TrendsBarChart 7.15 kB 0 B
frontend/dist-report/posthog-app/_parent/products/product_analytics/frontend/insights/trends/TrendsLifecycleChart/TrendsLifecycleChart 4.1 kB 0 B
frontend/dist-report/posthog-app/_parent/products/product_analytics/frontend/insights/trends/TrendsLineChart/TrendsLineChart 4.6 kB 0 B
frontend/dist-report/posthog-app/_parent/products/product_analytics/frontend/insights/trends/TrendsPieChart/TrendsPieChart 4.35 kB 0 B
frontend/dist-report/posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ReplayScanner 20.6 kB 0 B
frontend/dist-report/posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ReplayScannersScene 12.5 kB 0 B
frontend/dist-report/posthog-app/_parent/products/revenue_analytics/frontend/RevenueAnalyticsScene 26.6 kB 0 B
frontend/dist-report/posthog-app/_parent/products/session_summaries/frontend/SessionGroupSummariesTable 5.05 kB 0 B
frontend/dist-report/posthog-app/_parent/products/session_summaries/frontend/SessionGroupSummaryScene 19.2 kB 0 B
frontend/dist-report/posthog-app/_parent/products/tasks/frontend/TaskDetailScene 23.6 kB 0 B
frontend/dist-report/posthog-app/_parent/products/tasks/frontend/TaskTracker 14.6 kB 0 B
frontend/dist-report/posthog-app/_parent/products/tracing/frontend/TracingScene 54.1 kB 0 B
frontend/dist-report/posthog-app/_parent/products/user_interviews/frontend/UserInterview 9.32 kB 0 B
frontend/dist-report/posthog-app/_parent/products/user_interviews/frontend/UserInterviewResponse 5.68 kB 0 B
frontend/dist-report/posthog-app/_parent/products/user_interviews/frontend/UserInterviews 6.08 kB 0 B
frontend/dist-report/posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewIndexScene 2.56 kB 0 B
frontend/dist-report/posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewRunScene 44.7 kB 0 B
frontend/dist-report/posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewRunsScene 7.32 kB 0 B
frontend/dist-report/posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewSettingsScene 11.1 kB 0 B
frontend/dist-report/posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewSnapshotHistoryScene 13.9 kB 0 B
frontend/dist-report/posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewSnapshotOverviewScene 19.6 kB 0 B
frontend/dist-report/posthog-app/_parent/products/workflows/frontend/TemplateLibrary/MessageTemplate 16.6 kB 0 B
frontend/dist-report/posthog-app/_parent/products/workflows/frontend/Workflows/WorkflowScene 104 kB 0 B
frontend/dist-report/posthog-app/_parent/products/workflows/frontend/WorkflowsScene 60.2 kB 0 B
frontend/dist-report/posthog-app/src/index 61 kB 0 B
frontend/dist-report/posthog-app/src/layout/panel-layout/ai-first/tabs/NavTabChat 7.16 kB 0 B
frontend/dist-report/posthog-app/src/lib/components/Cards/TextCard/TextCardMarkdownEditor 11.3 kB 0 B
frontend/dist-report/posthog-app/src/lib/components/MonacoDiffEditor 471 B 0 B
frontend/dist-report/posthog-app/src/lib/lemon-ui/LemonMarkdown/MermaidDiagram 2.25 kB 0 B
frontend/dist-report/posthog-app/src/lib/lemon-ui/LemonTextArea/LemonTextAreaMarkdown 842 B 0 B
frontend/dist-report/posthog-app/src/lib/lemon-ui/Link/Link 359 B 0 B
frontend/dist-report/posthog-app/src/lib/monaco/CodeEditorInline 832 B 0 B
frontend/dist-report/posthog-app/src/lib/monaco/vimMode 211 kB 0 B
frontend/dist-report/posthog-app/src/lib/ui/Button/ButtonPrimitives 426 B 0 B
frontend/dist-report/posthog-app/src/queries/nodes/WebVitals/WebVitals 7.52 kB 0 B
frontend/dist-report/posthog-app/src/queries/nodes/WebVitals/WebVitalsPathBreakdown 4.09 kB 0 B
frontend/dist-report/posthog-app/src/queries/schema 732 kB 0 B
frontend/dist-report/posthog-app/src/scenes/activity/explore/EventsScene 3.28 kB 0 B
frontend/dist-report/posthog-app/src/scenes/activity/explore/SessionsScene 4.69 kB 0 B
frontend/dist-report/posthog-app/src/scenes/activity/live/LiveEventsTable 5.58 kB 0 B
frontend/dist-report/posthog-app/src/scenes/agentic/AgenticAuthorize 5.84 kB 0 B
frontend/dist-report/posthog-app/src/scenes/approvals/ApprovalDetail 16.6 kB 0 B
frontend/dist-report/posthog-app/src/scenes/approvals/changeRequestsLogic 884 B 0 B
frontend/dist-report/posthog-app/src/scenes/audit-logs/AdvancedActivityLogsScene 40 kB 0 B
frontend/dist-report/posthog-app/src/scenes/AuthenticatedShell 171 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/AccountConnected 3.33 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/AgenticAccountMismatch 2.73 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/CLIAuthorize 11.7 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/CLILive 4.37 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/credential-review/CredentialReview 3.95 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/EmailMFAVerify 3.37 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/InviteSignup 15.4 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/Login 10.2 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/Login2FA 4.6 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/passkeyLogic 824 B 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/PasswordReset 4.71 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/PasswordResetComplete 3.34 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/signup/SignupContainer 28.5 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/signup/verify-email/VerifyEmail 5.13 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/TwoFactorReset 4.37 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/VercelConnect 5.33 kB 0 B
frontend/dist-report/posthog-app/src/scenes/authentication/VercelLinkError 2.61 kB 0 B
frontend/dist-report/posthog-app/src/scenes/billing/AuthorizationStatus 1.07 kB 0 B
frontend/dist-report/posthog-app/src/scenes/billing/Billing 833 B 0 B
frontend/dist-report/posthog-app/src/scenes/billing/BillingSection 21.1 kB 0 B
frontend/dist-report/posthog-app/src/scenes/cohorts/Cohort 28.4 kB 0 B
frontend/dist-report/posthog-app/src/scenes/cohorts/CohortCalculationHistory 6.58 kB 0 B
frontend/dist-report/posthog-app/src/scenes/cohorts/Cohorts 9.78 kB 0 B
frontend/dist-report/posthog-app/src/scenes/coupons/Coupons 1.06 kB 0 B
frontend/dist-report/posthog-app/src/scenes/dashboard/Dashboard 1.65 kB 0 B
frontend/dist-report/posthog-app/src/scenes/dashboard/dashboards/Dashboards 19.8 kB 0 B
frontend/dist-report/posthog-app/src/scenes/dashboard/dashboards/templates/DashboardTemplateCopyScene 6.06 kB 0 B
frontend/dist-report/posthog-app/src/scenes/data-management/DataManagementScene 986 B 0 B
frontend/dist-report/posthog-app/src/scenes/data-management/definition/DefinitionEdit 17.2 kB 0 B
frontend/dist-report/posthog-app/src/scenes/data-management/definition/DefinitionView 24.4 kB 0 B
frontend/dist-report/posthog-app/src/scenes/data-management/MaterializedColumns/MaterializedColumns 12 kB 0 B
frontend/dist-report/posthog-app/src/scenes/data-management/variables/SqlVariableEditScene 7.6 kB 0 B
frontend/dist-report/posthog-app/src/scenes/data-pipelines/batch-exports/BatchExportScene 61 kB 0 B
frontend/dist-report/posthog-app/src/scenes/data-pipelines/DataPipelinesNewScene 2.66 kB 0 B
frontend/dist-report/posthog-app/src/scenes/data-pipelines/DestinationsScene 3.03 kB 0 B
frontend/dist-report/posthog-app/src/scenes/data-pipelines/event-filtering/EventFilterScene 22.2 kB 0 B
frontend/dist-report/posthog-app/src/scenes/data-pipelines/legacy-plugins/LegacyPluginScene 21 kB 0 B
frontend/dist-report/posthog-app/src/scenes/data-pipelines/TransformationsScene 2.27 kB 0 B
frontend/dist-report/posthog-app/src/scenes/data-pipelines/WebScriptsScene 2.89 kB 0 B
frontend/dist-report/posthog-app/src/scenes/data-warehouse/DataWarehouseScene 1.72 kB 0 B
frontend/dist-report/posthog-app/src/scenes/data-warehouse/editor/EditorScene 1.48 kB 0 B
frontend/dist-report/posthog-app/src/scenes/debug/DebugScene 20.3 kB 0 B
frontend/dist-report/posthog-app/src/scenes/debug/hog/HogRepl 7.72 kB 0 B
frontend/dist-report/posthog-app/src/scenes/experiments/Experiment 207 kB 0 B
frontend/dist-report/posthog-app/src/scenes/experiments/Experiments 20.8 kB 0 B
frontend/dist-report/posthog-app/src/scenes/experiments/SharedMetrics/SharedMetric 6.41 kB 0 B
frontend/dist-report/posthog-app/src/scenes/experiments/SharedMetrics/SharedMetrics 889 B 0 B
frontend/dist-report/posthog-app/src/scenes/exports/ExportsScene 4.33 kB 0 B
frontend/dist-report/posthog-app/src/scenes/feature-flags/FeatureFlag 146 kB 0 B
frontend/dist-report/posthog-app/src/scenes/feature-flags/FeatureFlags 1.08 kB 0 B
frontend/dist-report/posthog-app/src/scenes/groups/Group 15.5 kB 0 B
frontend/dist-report/posthog-app/src/scenes/groups/Groups 4.26 kB 0 B
frontend/dist-report/posthog-app/src/scenes/groups/GroupsNew 7.7 kB 0 B
frontend/dist-report/posthog-app/src/scenes/health/categoryDetail/HealthCategoryDetailScene 7.59 kB 0 B
frontend/dist-report/posthog-app/src/scenes/health/HealthScene 12.5 kB 0 B
frontend/dist-report/posthog-app/src/scenes/health/pipelineStatus/PipelineStatusScene 9.45 kB 0 B
frontend/dist-report/posthog-app/src/scenes/heatmaps/scenes/heatmap/HeatmapNewScene 5.38 kB 0 B
frontend/dist-report/posthog-app/src/scenes/heatmaps/scenes/heatmap/HeatmapRecordingScene 4.27 kB 0 B
frontend/dist-report/posthog-app/src/scenes/heatmaps/scenes/heatmap/HeatmapScene 6.91 kB 0 B
frontend/dist-report/posthog-app/src/scenes/heatmaps/scenes/heatmaps/HeatmapsScene 4.23 kB 0 B
frontend/dist-report/posthog-app/src/scenes/hog-functions/HogFunctionScene 59.6 kB 0 B
frontend/dist-report/posthog-app/src/scenes/inbox/InboxScene 63.3 kB 0 B
frontend/dist-report/posthog-app/src/scenes/insights/InsightQuickStart/InsightQuickStart 5.77 kB 0 B
frontend/dist-report/posthog-app/src/scenes/insights/InsightScene 34.8 kB 0 B
frontend/dist-report/posthog-app/src/scenes/insights/views/BoxPlot/BoxPlot 5.39 kB 0 B
frontend/dist-report/posthog-app/src/scenes/insights/views/CalendarHeatMap/CalendarHeatMap 4.84 kB 0 B
frontend/dist-report/posthog-app/src/scenes/insights/views/RegionMap/RegionMap 29.8 kB 0 B
frontend/dist-report/posthog-app/src/scenes/insights/views/WorldMap/WorldMap 5.13 kB 0 B
frontend/dist-report/posthog-app/src/scenes/instance/AsyncMigrations/AsyncMigrations 13.5 kB 0 B
frontend/dist-report/posthog-app/src/scenes/instance/DeadLetterQueue/DeadLetterQueue 5.74 kB 0 B
frontend/dist-report/posthog-app/src/scenes/instance/QueryPerformance/QueryPerformance 8.97 kB 0 B
frontend/dist-report/posthog-app/src/scenes/instance/SystemStatus/SystemStatus 17.4 kB 0 B
frontend/dist-report/posthog-app/src/scenes/IntegrationsRedirect/IntegrationsRedirect 1.08 kB 0 B
frontend/dist-report/posthog-app/src/scenes/marketing-analytics/MarketingAnalyticsScene 40.5 kB 0 B
frontend/dist-report/posthog-app/src/scenes/max/Max 1.02 kB 0 B
frontend/dist-report/posthog-app/src/scenes/models/ModelsScene 19 kB 0 B
frontend/dist-report/posthog-app/src/scenes/models/NodeDetailScene 17.1 kB 0 B
frontend/dist-report/posthog-app/src/scenes/moveToPostHogCloud/MoveToPostHogCloud 4.81 kB 0 B
frontend/dist-report/posthog-app/src/scenes/new-tab/NewTabScene 1.82 kB 0 B
frontend/dist-report/posthog-app/src/scenes/notebooks/NotebookCanvasScene 3.89 kB 0 B
frontend/dist-report/posthog-app/src/scenes/notebooks/NotebookPanel/NotebookPanel 5.94 kB 0 B
frontend/dist-report/posthog-app/src/scenes/notebooks/NotebookScene 9.13 kB 0 B
frontend/dist-report/posthog-app/src/scenes/notebooks/NotebooksScene 7.95 kB 0 B
frontend/dist-report/posthog-app/src/scenes/oauth/OAuthAuthorize 980 B 0 B
frontend/dist-report/posthog-app/src/scenes/onboarding/coupon/OnboardingCouponRedemption 1.55 kB 0 B
frontend/dist-report/posthog-app/src/scenes/onboarding/Onboarding 791 kB 0 B
frontend/dist-report/posthog-app/src/scenes/onboarding/sdks/SdkDoctorScene 9.77 kB 0 B
frontend/dist-report/posthog-app/src/scenes/organization/ConfirmOrganization/ConfirmOrganization 4.88 kB 0 B
frontend/dist-report/posthog-app/src/scenes/organization/Create/Create 1 kB 0 B
frontend/dist-report/posthog-app/src/scenes/organization/Deactivated 1.48 kB 0 B
frontend/dist-report/posthog-app/src/scenes/organization/PendingDeletion 2.45 kB 0 B
frontend/dist-report/posthog-app/src/scenes/persons/PersonScene 19 kB 0 B
frontend/dist-report/posthog-app/src/scenes/persons/PersonsScene 6.09 kB 0 B
frontend/dist-report/posthog-app/src/scenes/PreflightCheck/PreflightCheck 5.91 kB 0 B
frontend/dist-report/posthog-app/src/scenes/product-tours/ProductTour 275 kB 0 B
frontend/dist-report/posthog-app/src/scenes/product-tours/ProductTours 5.03 kB 0 B
frontend/dist-report/posthog-app/src/scenes/project-homepage/ProjectHomepage 18.4 kB 0 B
frontend/dist-report/posthog-app/src/scenes/project/Create/Create 1.18 kB 0 B
frontend/dist-report/posthog-app/src/scenes/resource-transfer/ResourceTransfer 9.53 kB 0 B
frontend/dist-report/posthog-app/src/scenes/saved-insights/SavedInsights 1 kB 0 B
frontend/dist-report/posthog-app/src/scenes/session-recordings/detail/SessionRecordingDetail 2.1 kB 0 B
frontend/dist-report/posthog-app/src/scenes/session-recordings/file-playback/SessionRecordingFilePlaybackScene 4.82 kB 0 B
frontend/dist-report/posthog-app/src/scenes/session-recordings/kiosk/SessionRecordingsKiosk 10.3 kB 0 B
frontend/dist-report/posthog-app/src/scenes/session-recordings/player/snapshot-processing/DecompressionWorkerManager 329 B 0 B
frontend/dist-report/posthog-app/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistScene 5.45 kB 0 B
frontend/dist-report/posthog-app/src/scenes/session-recordings/SessionRecordings 1.12 kB 0 B
frontend/dist-report/posthog-app/src/scenes/session-recordings/settings/SessionRecordingsSettingsScene 2.31 kB 0 B
frontend/dist-report/posthog-app/src/scenes/sessions/SessionProfileScene 15.4 kB 0 B
frontend/dist-report/posthog-app/src/scenes/settings/SettingsScene 3.9 kB 0 B
frontend/dist-report/posthog-app/src/scenes/sites/Site 1.53 kB 0 B
frontend/dist-report/posthog-app/src/scenes/startups/StartupProgram 21.5 kB 0 B
frontend/dist-report/posthog-app/src/scenes/StripeConfirmInstall/StripeConfirmInstall 3.88 kB 0 B
frontend/dist-report/posthog-app/src/scenes/subscriptions/SubscriptionScene 14.7 kB 0 B
frontend/dist-report/posthog-app/src/scenes/subscriptions/SubscriptionsScene 5.53 kB 0 B
frontend/dist-report/posthog-app/src/scenes/surveys/forms/SurveyFormBuilder 1.89 kB 0 B
frontend/dist-report/posthog-app/src/scenes/surveys/Survey 1.36 kB 0 B
frontend/dist-report/posthog-app/src/scenes/surveys/Surveys 26.7 kB 0 B
frontend/dist-report/posthog-app/src/scenes/surveys/wizard/SurveyWizard 72.7 kB 0 B
frontend/dist-report/posthog-app/src/scenes/themes/CustomCssScene 3.91 kB 0 B
frontend/dist-report/posthog-app/src/scenes/toolbar-launch/ToolbarLaunch 2.82 kB 0 B
frontend/dist-report/posthog-app/src/scenes/Unsubscribe/Unsubscribe 2 kB 0 B
frontend/dist-report/posthog-app/src/scenes/web-analytics/SessionAttributionExplorer/SessionAttributionExplorerScene 6.97 kB 0 B
frontend/dist-report/posthog-app/src/scenes/web-analytics/WebAnalyticsScene 10.6 kB 0 B
frontend/dist-report/posthog-app/src/scenes/wizard/Wizard 4.8 kB 0 B
frontend/dist-report/posthog-app/src/sharedChunkAnchors 1.19 kB 0 B
frontend/dist-report/render-query/src/render-query/render-query 27.2 MB +388 B (0%)
frontend/dist-report/toolbar/src/toolbar/toolbar 15.7 MB 0 B

compressed-size-action

@posthog
Copy link
Copy Markdown
Contributor

posthog Bot commented May 25, 2026

Visual changes approved by @sampennington — baseline updated in 142f7a5.

View this run in PostHog

4 changed.

Copy link
Copy Markdown
Contributor Author

@sampennington sampennington left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review

Verdict: Needs changes — small ones

Clean three-layer split (pure geometry / canvas primitives / React wrapper) that mirrors BarChart and PieChart. Two findings are worth fixing before ship: (1) whisker caps draw on the box outline when min == p25 or max == p75, producing a visible cross-bar in the no-whisker case; (2) the consumer-facing tooltip callback leaks the internal BoxPlotAdaptedMeta adapter shape — once shipped, that's awkward to refactor away. Everything else is quick polish.

Next steps

  1. Guard whisker-cap draw against the no-whisker case (and add a visual-regression test that samples the cap pixel).
  2. Replace tooltip: (ctx: TooltipContext<BoxPlotAdaptedMeta<Meta>>) => … with a BoxPlotTooltipContext shape parallel to BoxPlotClickData — expose BoxPlotSeries<Meta> and BoxPlotDatum directly.
  3. Mop up: drop unused exports (nearestBoxIndex, cursorInsideBoxRect), replace local hexToRgba with the existing dimHex, decide on one channel between adaptedSeries whisker-append and valueRangeSeries, fix originalSeries's silent series[0] fallback, memoize the inline config object.

Non-anchored findings

  • JSDoc field-restating comments are pervasive across BoxPlot.tsx, BoxPlotTooltip.tsx, computeBoxLayout.ts, and canvas-renderer.ts (e.g., /** Lower whisker endpoint. */ on min, /** Median — drawn as a line across the box. */ on median, etc.). Per the project comment guidance, default to deleting these; keep only the few that carry non-obvious info (e.g., the useChartMargins whisker-append trick, the null-return cases on computeBoxRect).
  • Narrating inline comments in drawBox (// Whisker stem above the box, // Whisker caps at min and max, // Box rectangle (p25 → p75) — fill then outline, // Median line across the box, // Mean marker — filled circle outlined in the series color) — restate the next 2–4 lines; delete.
  • Test helper branching (computeBoxLayout.test.ts and boxes-under-cursor.test.ts) — both files define a makeScales helper with if (d) { … } guards and a seriesSpec.length > 1 ? 'grouped' : 'stacked' default. Either extract to a shared non-test fixture file (where branching is fine) or pass barLayout explicitly at every call site.
  • BoxPlotInner is ~300 lines — extract adaptedSeries/valueRangeSeries/datumsByKey to a useBoxPlotAdapter hook or module-scope helpers, same shape PieChart uses with computePieLayout.ts.
  • Magic-number defaults duplicated 3× (meanRadius=3, whiskerCapRatio=0.6, boxStrokeWidth=1.5) — defined in BoxPlot.tsx defaults, again in drawBox options defaults, again in JSDoc; pull to exported named constants near drawBox.
  • Global isFinite (coerces) used throughout BoxPlot.tsx and computeBoxLayout.ts; the test files correctly use Number.isFinite. Switch the production code to match — isFinite('1') returns true, which matters if a stray string slips into data.
  • Stories pass color: '' everywhere (BoxPlot.stories.tsx) — the empty-string sentinel relies on Chart.tsx's falsy fallback to theme.colors[i]. Series.color is optional; recommend stories omit it entirely so they document the actual consumer ergonomics.
  • Library generality is clean — no kea, no PostHog imports, no lib/utils, no module-level mutable state. Same external dependencies (d3, React) as existing chart types.

Reviewers ran: hog-charts, react, personal, reuse, quality, efficiency. Inline comments above are tagged with the reviewer that raised them and the level of concern. The generic code-reviewer was skipped — all changed paths are under frontend/src/lib/hog-charts/, so hog-charts-reviewer supersedes it.

Comment thread frontend/src/lib/hog-charts/core/canvas-renderer.ts Outdated
Comment thread frontend/src/lib/hog-charts/charts/BoxPlot/BoxPlot.tsx Outdated
Comment thread frontend/src/lib/hog-charts/charts/BoxPlot/BoxPlot.tsx Outdated
Comment thread frontend/src/lib/hog-charts/charts/BoxPlot/BoxPlot.tsx Outdated
Comment thread frontend/src/lib/hog-charts/charts/BoxPlot/BoxPlot.tsx Outdated
Comment thread frontend/src/lib/hog-charts/core/canvas-renderer.ts Outdated
Comment thread frontend/src/lib/hog-charts/charts/BoxPlot/BoxPlotTooltip.tsx
Comment thread frontend/src/lib/hog-charts/charts/BoxPlot/BoxPlotTooltip.tsx
Comment thread frontend/src/lib/hog-charts/charts/BoxPlot/BoxPlot.test.tsx Outdated
Comment thread frontend/src/lib/hog-charts/charts/BoxPlot/BoxPlotTooltip.test.tsx Outdated
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 25, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
posthog 9aac728 May 25 2026, 03:19 PM

Copy link
Copy Markdown
Contributor Author

@sampennington sampennington left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review

Verdict: Needs changes

Three blocking items: (1) core/canvas-renderer.ts imports a type from charts/BoxPlot/ — a core → charts layering inversion; (2) adaptedSeries smuggles whisker min/max past labels.length in Series.data, breaking the Series data[i] ↔ labels[i] invariant; (3) in grouped mode onBoxClick reports the first-visible series, not the box actually under the cursor (a real UX bug for the persons-modal wiring the follow-up needs). Plenty of strong primitives underneath — drawBoxes batches paths well, the three-layer test split is clean, error boundary + nativeTooltip mode are good additions — but the layering, click semantics, and Series-data hack should land before the wiring follow-up.

Next steps:

  1. Move BoxRect into core/canvas-renderer.ts (mirroring BarRect) and stop importing from charts/ in core/.
  2. Drive y-domain widening entirely through the existing valueRangeSeries / stackedSeries channel — don't push whisker extremes onto Series.data.
  3. Fix onBoxClick's primary series in grouped mode, or explicitly document the gap.

Non-anchored findings

Should Fix

  • computeBoxLayout.ts (top of file) — BoxPlotDatum extends Pick<SchemaBoxPlotDatum, ...> reaches into ~/queries/schema/schema-general from inside lib/hog-charts/. This is the only PostHog coupling in the library; even as a type-only Pick it crosses the "library is self-contained" line. Prefer defining a structural six-number type here and letting adapters map onto it.
  • index.ts:25-37 — wide public surface (10+ named exports including internal types like BoxPlotAdaptedMeta and three geometry helpers). Neighbour PieChart exports only the component + config/props. Narrow before this surface ships, because deleting later is hard.
  • boxes-under-cursor.test.ts (helper) — same branching patterns as computeBoxLayout.test.ts (ternary default param around barLayout, if (d) filter, scales.group!('b') ?? 0 nullish-coalesce as control flow in an assertion). Same fixes apply.

Suggestions

  • BoxPlot.tsx (band-axis math) and BarChart.tsx:43-58bandCenter / groupedBarCenter are reimplemented inline here (in both createScales.x and drawStatic.xScale). Promote them onto BarScaleSet or export from core/bar-layout.ts.
  • BoxPlot.tsx:26-32BoxPlotPrivate stashes scales, datumsByKey, and grouped. The latter two are derived from the series prop which the draw callbacks already capture via closure. Stash only scales, matching BarChartPrivate.
  • BoxPlotTooltip.tsx — its outer chrome (px-3 py-2 rounded-lg shadow-lg text-[13px] + DEFAULT_TOOLTIP_BG/DEFAULT_TOOLTIP_COLOR + the color-swatch span) is a third copy of the same JSX in overlays/DefaultTooltip.tsx and charts/PieChart/PieTooltip.tsx. Extract a <TooltipFrame> shared shell.
  • BoxPlotTooltip.tsx (entry build) — unlike BarTooltip, it doesn't narrow seriesData by cursor in grouped mode, so hovering in the gap between sub-band boxes still shows every series's stat table. The existing seriesKeysAtCursor can drive the same narrowing.
  • computeBoxLayout.ts (computeBoxRect) — two consecutive isFinite blocks and two consecutive scale-lookup-null blocks could be one each.
  • computeBoxLayout.ts (computeBoxRect) — silently swaps p25 > p75 / min > max via Math.min/Math.max and renders a misleading box. Consider returning null instead — a flipped six-number summary is almost certainly a data bug, not legitimate input.
  • BoxPlot.tsx (BoxPlotConfig) — the three new options meanRadius / whiskerCapRatio / boxStrokeWidth duplicate the DrawBoxOptions defaults (meanRadius, whiskerCapRatio, lineWidth) in canvas-renderer.ts. Consider a single boxStyle?: Partial<DrawBoxOptions> instead.
  • BoxPlot.tsx (grouped derivation) — series.filter(...).length > 1 allocates a filtered array just to check length; series.some(...) + a counter would be one pass. Tiny.
  • BoxPlotTooltip.tsx (root) — <>{userTooltip(ctx)}</> wraps a ReactNode in a Fragment to satisfy the ReactElement | null return type. Widen the return type to ReactNode instead.
  • Tests don't pin single-series boxplots in the 'stacked' barLayout branch — worth one explicit case so the grouped=false → barLayout='stacked' mapping is locked in.

Nits — unnecessary comments

  • Several JSDoc lines on BoxPlotClickData fields (seriesIndex, dataIndex, label, datum, crossSeriesData) restate the field name in prose.
  • Per-field JSDocs on BoxPlotSeries (key, label, data, meta, visibility) similarly restate types. The color and data-null docs encode real contracts — keep those.
  • BoxPlot.tsx (onBoxClick JSDoc) references "the product layer wires this to the persons modal in the BoxPlot insight" — drops downstream wiring context that doesn't belong in the library.
  • BoxPlotTooltip.tsx (ROWS constant) — "Rows shown per box, in the same order the legacy BoxPlotChart used" — references migration context that won't age well.
  • BoxPlot.tsx (around valueRangeSeries / adaptedSeries) — the same "why we widen the y-domain" explanation appears twice; pick one site.
  • canvas-renderer.ts (inside drawBoxes) — the numbered step comments (// 1. Whisker stems, // 4. Median lines) mostly restate the next line; keep only the load-bearing ones (the cap-skip reason, the per-box mean justification).

Positive observations

  • _private stash slot used correctly via ChartScales._private, matching BarChart's pattern — no useRef for chart-private state.
  • drawBoxes explicitly batches stems / caps / box outlines / medians into shared beginPath+stroke calls (4+N instead of 5N).
  • Three-layer test split (computeBoxLayout.test.ts pure geometry, boxes-under-cursor.test.ts pure hit-test, BoxPlot.test.tsx rendered component) is exactly the right shape.
  • Degenerate-input coverage (inverted p25 > p75, all-equal samples, non-finite stats, null entries) is thorough.
  • ChartErrorBoundary wired with onError forwarding.
  • nativeTooltip opt-in in renderHogChart is a clean accessor extension — lives in lib/hog-charts/testing/ so every chart inherits it.
  • No new module-level mutable state, no kea, no runtime PostHog imports (modulo the schema type Pick noted above).

Reviewers: hog-charts-reviewer, react-reviewer, personal, reuse, quality, efficiency. Inline comments above are tagged with the reviewer that raised them and the level of concern. Generic code-reviewer was skipped because 100% of the diff sits under lib/hog-charts/ and hog-charts-reviewer supersedes it there.

@@ -1,8 +1,11 @@
import * as d3 from 'd3'

import type { BoxRect } from '../charts/BoxPlot/computeBoxLayout'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 [hog-charts] · Critical

core/canvas-renderer.ts lives in core/, but this imports BoxRect from charts/BoxPlot/computeBoxLayout (and re-exports it on line 7). The hog-charts architecture is one-way: charts/ depends on core/, never the reverse. BarRect follows that rule — its definition lives in core/canvas-renderer.ts next to drawBars. Move BoxRect here too (and have computeBoxLayout.ts re-export it for ergonomics), or move drawBox* out of core/ into charts/BoxPlot/. The first is more consistent with BarRect.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ee66989 — moved BoxRect next to BarRect in core/canvas-renderer.ts. computeBoxLayout.ts re-exports it so consumers that pull the geometry helpers don't need a second import.

Comment on lines +125 to +145
const adaptedSeries = useMemo<Series<BoxPlotAdaptedMeta<Meta>>[]>(
() =>
series.map((s) => {
const data: number[] = Array.from({ length: labels.length }, (_, i) => {
const datum = s.data[i]
return datum && isFinite(datum.median) ? datum.median : Number.NaN
})
let seriesMin = Infinity
let seriesMax = -Infinity
for (const datum of s.data) {
if (!datum) {
continue
}
if (isFinite(datum.min) && datum.min < seriesMin) {
seriesMin = datum.min
}
if (isFinite(datum.max) && datum.max > seriesMax) {
seriesMax = datum.max
}
}
if (isFinite(seriesMin)) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 [hog-charts+reuse] · Should Fix

adaptedSeries.data carries labels.length median entries — and then pushes one or two synthetic min/max values past the end purely to coerce useChartMargins → seriesValueRange into sizing the y-tick column for the whisker range. This breaks the long-held invariant in this library that series.data[i] corresponds to labels[i], which is what every other consumer of Series assumes (e.g. createStackedData iterates labels.length).

The y-domain is already widened cleanly via valueRangeSeries + stackedSeries: ... in createBarScales. Extend the same channel to useChartMargins (an optional explicit value-range, or a parallel valueRangeSeries arg) — one concept, two call sites — and drop the data-array tail. At minimum, leave a comment that this is a workaround, not the API.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ee66989 — dropped the .push(seriesMin/Max) tail. Added a valueRangeSeries?: Series[] prop to ChartProps that forwards to a new valueRangeSeries override on useChartMargins, so the synthetic min/max series feeds both createBarScales (as stackedSeries) and the margin sizer through the same channel. adaptedSeries.data is now length-stable at labels.length.

[tooltip, grouped]
)

const onPointClick = useCallback(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 [hog-charts] · Should Fix

In grouped mode the upstream buildPointClickData picks the primary series by visible-series index, not by which sub-band the cursor was actually over. So clicking series B's box in a grouped layout fires onBoxClick({ series: A, ... }). The test at BoxPlot.test.tsx:441 codifies this as expected — for bars that's defensible, but for box plots the user is unambiguously clicking one box, and onBoxClick is what wires the persons modal. Either reuse seriesKeysAtCursor here at click time to pick the under-cursor series (same as the hover highlight already does), or document on BoxPlotClickData.series that it's "the first visible series in this column" so the product layer disambiguates via crossSeriesData + cursor x.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documented in ee66989. Went with option (b): BoxPlotClickData.series now explicitly carries "first visible series at the column, not necessarily under-cursor", with a pointer to crossSeriesData + cursor x for the precise box. Matches BarChart's contract — the persons modal layer can disambiguate.

boxStrokeWidth = 1.5,
} = config ?? {}

const grouped = useMemo(() => series.filter((s) => !s.visibility?.excluded).length > 1, [series])
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 [react] · Performance

useMemo on a cheap boolean — series.filter(...).length > 1 is O(small) and produces a primitive. Memoizing a value type buys nothing downstream because boolean is compared by value, not reference. Replace with a plain const grouped = series.filter(...).length > 1 (or series.some(...) + counter for one pass).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ee66989const grouped = series.filter(...).length > 1.

Comment on lines +395 to +398
const chartConfig = useMemo(
() => ({ ...config, axisOrientation: 'vertical' as const, xTickFormatter }),
[config, xTickFormatter]
)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 [react+quality] · Performance

Two issues here:

  1. config is almost always passed inline by callers (config={{ showGrid: true }} in stories — likely the same in real usage), so it's a fresh object each render and this useMemo never hits. The downstream <Chart> then receives a new chartConfig ref every render anyway. Either drop the memo, or destructure config's primitive fields into the deps to actually stabilize it.

  2. xTickFormatter was already destructured out of config above and the spread ...config carries the same value. The explicit xTickFormatter override is a no-op.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ee66989 on both counts. Dropped the chartConfig memo entirely — config is constructed inline at the <Chart> call site ({ ...config, axisOrientation: 'vertical' }). And removed the xTickFormatter destructure: it's already carried in ...config, so the explicit override was a no-op.

Comment on lines +419 to +425
/** Translucent variant of a series colour for box fills, mean markers, and the hover overlay.
* Uses `d3.color` so we cover hex (3/6 digit), `rgb(…)`, `rgba(…)`, named colors, and HSL —
* the previous hand-rolled hex parser only handled a subset of those. Falls back to the raw
* string when `d3.color` returns `null`, matching the BarChart pattern. */
function dimColor(color: string, alpha: number): string {
return d3.color(color)?.copy({ opacity: alpha }).toString() ?? color
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 [reuse+quality] · Suggestion

dimColor is a generic color-with-alpha helper — BarChart.tsx does the same thing inline with d3.color(s.color).copy({ opacity: ... }).toString(). Promote to a small core/color-utils.ts (or onto canvas-renderer.ts) so BarChart can switch to it.

Also the four-line JSDoc above ("the previous hand-rolled hex parser only handled a subset") references prior implementation that no longer exists in the diff — trim to a single line like // Translucent series color (alpha applied) — handles all CSS color forms.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Promoted in ee66989dimColor lives in core/color-utils.ts with a one-line JSDoc. Did not migrate BarChart's two inline call sites in this PR: one of them needs the rgba(0,0,0, alpha) fallback (the comment there is load-bearing — falling back to s.color would paint an opaque plot-height block) and the other uses .darker(0.6) rather than .copy({ opacity }). Both worth their own pass, but not a drop-in.

}

/** Paint a single box-and-whisker. Takes a pre-laid-out {@link BoxRect}; no scale access. */
export function drawBox(ctx: CanvasRenderingContext2D, box: BoxRect, options: DrawBoxOptions): void {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 [reuse+quality] · Suggestion

drawBox (singular) is a one-line wrapper around drawBoxes(ctx, [box], options) and is never called — BoxPlot.tsx only imports drawBoxes and drawBoxHighlight. BarChart only exposes drawBars (no singular sibling). Delete it, or document why the plural-only sibling needs a singular counterpart for downstream consumers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted in ee66989 — singular drawBox is gone; BoxPlot.tsx only used drawBoxes and drawBoxHighlight.

labels: string[],
seriesSpec: { key: string; data: (BoxPlotDatum | null)[] }[],
dims = PIXEL_TEST_DIMENSIONS,
barLayout: 'grouped' | 'stacked' = seriesSpec.length > 1 ? 'grouped' : 'stacked'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 [personal] · Should Fix

Ternary inside a test-helper default parameter: barLayout: 'grouped' | 'stacked' = seriesSpec.length > 1 ? 'grouped' : 'stacked'. Tests should be linear and deterministic — a conditional default means the helper does different things depending on input shape, and the test reader can't see which branch ran without reasoning about the caller.

Make barLayout a required parameter so each call site passes its layout explicitly, or split into makeGroupedScales / makeStackedScales.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ee66989barLayout is a required 3rd parameter on makeScales and every call site passes either 'stacked' or 'grouped' explicitly.

Comment on lines +33 to +39
for (const d of s.data) {
if (d) {
mins.push(d.min)
maxs.push(d.max)
}
}
if (mins.length > 0) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 [personal] · Should Fix

Branching inside the test helper: if (d) { ... } filtering null datums, plus two if (mins.length > 0) / if (maxs.length > 0) guards before valueRange.push. Every test fixture in this file supplies non-null entries, so both length guards are unconditionally true and the if (d) only matters for one fixture.

Fix shape: compute data arrays without nulls up front in each test (or use s.data.flatMap(d => d ? [{...}] : []) once at the call site so the helper has no branches). Or delete the helper and inline the two cases that need it as separate parameterized fixtures.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ee66989makeScales now takes BoxPlotDatum[] (no nulls). The min/max collection is a branchless flatMap. The one test that exercises null datums (drops null and unrenderable indices) passes the nullable list to computeSeriesBoxes and a filtered, non-null copy to makeScales — the helper has no branches.

Comment on lines +256 to +264
try {
const { chart } = renderHogChart(
<BoxPlot series={TWO_SERIES} labels={LABELS} theme={THEME} tooltip={tooltip} onError={onError} />
)
chart.hoverAtIndex(1)
await waitFor(() => expect(onError).toHaveBeenCalled())
} finally {
consoleErrorSpy.mockRestore()
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 [personal] · Should Fix

try { … } finally { consoleErrorSpy.mockRestore() } in a test body is exactly the branching shape that should live in setup hooks. Hoist into the describe('error handling') block:

let consoleErrorSpy: jest.SpyInstance
beforeEach(() => { consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}) })
afterEach(() => { consoleErrorSpy.mockRestore() })

The test body collapses to its actual assertions, and you can't forget the cleanup on a future test added to this describe.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ee66989 — hoisted consoleErrorSpy into beforeEach/afterEach on the error handling describe. Future tests added there inherit the cleanup automatically.

Copy link
Copy Markdown
Member

@gesh gesh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! 🙌

One more comment from the bot:

createBarScales clamps the low end to 0 (scales.ts:379), so boxes for data far from zero get squashed — the legacy Chart.js box plot auto-fits instead (BoxPlotChart.tsx:275). The call site is BoxPlot.tsx:186; BoxPlot likely needs its own tight-fit value scale rather than bar semantics.

/** Stash slot — survives a render via `ChartScales._private` so drawStatic/drawHover
* can read the d3 scales and the per-series datums lookup without recomputing them. */
interface BoxPlotPrivate {
__boxPlot: {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we use the double underscore convention across the frontend

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik not widely, but it is used in elsewhere in this library already

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good, makes sense to stick to it

scales,
grouped,
}: ComputeBoxRectOptions): BoxRect | null {
if (!isFinite(datum.min) || !isFinite(datum.max) || !isFinite(datum.p25) || !isFinite(datum.p75)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the bot:

Use Number.isFinite — the global coerces null→0 (passes), silently rendering a box at 0 instead of skipping it. Fix computeBoxLayout.ts:87,90,104,107 and BoxPlot.tsx:132,156,159.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a2e41c5 — swapped all seven sites (the four in computeBoxRect and the three in BoxPlot.tsx you flagged) to Number.isFinite. The global isFinite(null) evaluating true was exactly the silent-box-at-0 footgun you described. Tests still green (41/41 in the BoxPlot suite).

sampennington and others added 11 commits May 27, 2026 12:08
Adds a canvas-drawn box-and-whisker chart over a band x-axis. Each box
shows the p25–p75 box, a median line, a mean marker, whiskers to min/max,
hover-driven highlighting, and a per-x-position tooltip with the six
canonical stats. Supports multiple series via grouped side-by-side boxes
inside the same band, sharing the BarChart band/group scale infrastructure.

Includes:
- BoxPlotDatum / BoxPlotSeries types — per-point six-number summaries.
- computeBoxRect / computeSeriesBoxes — pure geometry helpers.
- drawBox / drawBoxHighlight — canvas drawing primitives.
- boxes-under-cursor — band-axis hit-test mirroring BarChart's pattern.
- BoxPlot React component — wraps the Chart base, computes scales from
  whisker min/max via the createBarScales stackedSeries hook, and exposes
  an onBoxClick callback (no persons-modal here — that lives at the
  product layer in a follow-up).
- BoxPlotTooltip — default tooltip with Max → p75 → Median → Mean →
  p25 → Min rows per series.
- Unit tests for geometry and hit-testing (degenerate cases, grouping,
  null data) and component tests for canvas mount, tooltip context, and
  click data.
- Storybook stories: single series, multi-series grouped, no-grid,
  with-gaps, and empty.

Library only — no Trends.tsx changes or feature flag wiring.

Generated-By: PostHog Code
Task-Id: 64e06e82-8970-4b3d-9ad5-0d8dde58147e
Applies oxfmt formatting (CI was failing on format check), plus two
trusted-bot review suggestions from Greptile:

- Replace the redundant `computeSeriesBoxes` + `.find` in drawHover with
  a direct `computeBoxRect` call — `datum` is already in hand on the
  hover path, no need to recompute every box.
- Fix the misleading JSDoc on `hexToRgba`: the function actually returns
  the original color unchanged on unrecognised input, not the documented
  `rgba(0,0,0,alpha)` fallback.

No behavior change.

Generated-By: PostHog Code
Task-Id: 64e06e82-8970-4b3d-9ad5-0d8dde58147e
`useChartMargins` derives the y-tick column width from
`seriesValueRange(series)`. BoxPlot's adapted inner Series only carried
medians on `data`, so when whiskers extend much further than medians
(e.g. medians ≤60 but max ~280), the margin estimator sized the y-tick
column for narrow labels like "0, 20, 40, 60" while the actual axis
rendered "0, 50, …, 250, 300" — the wider labels got clipped.

Append each series's whisker min and max to `data` past `labels.length`.
`seriesValueRange` walks every entry so margin estimation sees the real
range, while interaction (which indexes `[0, labels.length)`) and the
draw path (which reads `meta.datums`) never touch the extras.

Adds a regression test that builds a series with median-vs-whisker
divergence and asserts the rendered y ticks reach the whisker scale.

Generated-By: PostHog Code
Task-Id: 64e06e82-8970-4b3d-9ad5-0d8dde58147e
- canvas-renderer: skip whisker caps when stems are skipped, and batch per-series
  path/stroke pairs from 5N to 4+N (whisker stems, caps, box outlines, medians each
  become one shared path; mean markers stay per-box due to fill+stroke pairing)
- BoxPlot: use d3.color for translucent fills (drops bespoke hexToRgba which missed
  named colors / HSL); memoize chart config; build seriesByKey map for O(1) cross-
  series lookup and drop the silent series[0] fallback; hoist mousemove adapter out
  of the hot path so seriesKeysAtCursor doesn't reallocate per frame; drop the
  redundant whisker-min/max appended to adaptedSeries.data (valueRangeSeries already
  drives the y-domain); Omit axisOrientation from BoxPlotConfig since BoxPlot is
  vertical-only
- BoxPlot tooltip API: expose BoxPlotAdaptedMeta and BoxPlotTooltipContext on the
  barrel so consumers don't have to redeclare the adapter shape
- BoxPlot interaction: split seriesKeysAtCursor into band-only (cheap, hit-test path)
  and full-rect (highlight path) via computeBoxBand; drop unused nearestBoxIndex /
  cursorInsideBoxRect helpers and their tests
- BoxPlotDatum: alias from queries/schema-general so the chart type stays in lock-
  step with the canonical schema type
- BoxRect: single home in computeBoxLayout.ts; canvas-renderer re-exports for
  backwards compatibility
- BoxPlotTooltip: stable React key from series.key (was index)
- Tests: tighten row-extraction helpers in BoxPlotTooltip.test to non-null asserts;
  drop optional chaining in BoxPlot.test tooltip meta assertion

Generated-By: PostHog Code
Task-Id: 2acf2f65-f758-4264-ab6b-5c8a3e667816
Restore the whisker min/max append on adaptedSeries.data — useChartMargins
reads seriesValueRange(series) independently of the d3 y-domain that
valueRangeSeries drives, so both channels are needed. Without the append the
y-tick column was sized for medians and "300" got clipped on the left.

Generated-By: PostHog Code
Task-Id: 2acf2f65-f758-4264-ab6b-5c8a3e667816
The empty boxplot snapshot just shows a y-axis with no data — not a
meaningful regression-test target, and the default tick labels clip
against the left edge in the snapshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 updated
Run: a33927cf-cb13-4f7b-b623-6442e6cf7341

Co-authored-by: sampennington <56024559+sampennington@users.noreply.github.com>
Add `nativeTooltip` option to `renderHogChart` that skips the tooltip-prop
interception, letting the chart render its own internal tooltip component
(e.g. BoxPlotTooltip) so tests can assert on the real DOM output. The
default behavior — context capture via a DefaultTooltip fallback — is
unchanged for existing callers.

Migrate the BoxPlotTooltip row-ordering, header, grouped/single-series,
and user-tooltip pass-through tests into `BoxPlot.test.tsx` driven through
`renderHogChart(..., { nativeTooltip: true })` + `waitForHogChartTooltip()`.
This brings the BoxPlot tests in line with how Bar/Pie/TimeSeriesLine
exercise their tooltips, and the `grouped` decision is now driven by the
chart's own series-count logic rather than passed by hand.

Drop the null-datum-returns-null case: at the chart layer the hover only
fires on a band hit, so the empty-tooltip path isn't reachable through
the chart. The existing `tolerates null entries in series data` test
still covers crash-free rendering when datums are null.


Generated-By: PostHog Code
Task-Id: e84e756c-13e5-4591-98db-12fee0845eb1
Generated-By: PostHog Code
Task-Id: e84e756c-13e5-4591-98db-12fee0845eb1
- Move BoxRect into core/canvas-renderer (charts/ shouldn't be a core/ dep)
- Drop adaptedSeries.data tail hack; thread valueRangeSeries through Chart
  into useChartMargins so series.data[i] <-> labels[i] invariant holds
- Drop hitTestSeries adapter (structural identity of series)
- Drop chartConfig useMemo (defeated by inline config); xTickFormatter is
  already spread via ...config
- grouped: useMemo on a bool -> plain const
- Promote dimColor to core/color-utils
- Delete unused singular drawBox wrapper
- Clarify BoxPlotClickData.series grouped-mode semantics
- Test helpers: make barLayout required, strip unreachable branches,
  hoist consoleErrorSpy into beforeEach/afterEach
The global `isFinite` coerces `null` → `0`, so a null field on a BoxPlotDatum slipped past the guard and silently rendered a box at value 0. Switching to `Number.isFinite` makes the guard strictly numeric — null/undefined fields are now skipped rather than rendered at 0.

Touches the seven sites Gesh flagged: the four guards in `computeBoxRect` and the three in `BoxPlot.tsx` (the `adaptedSeries` median lookup and the `valueRangeSeries` min/max collectors).

Generated-By: PostHog Code
Task-Id: c0dd6782-d14c-44c3-bf81-5a67b128f749
@sampennington sampennington force-pushed the posthog-code/hog-charts-boxplot-primitive branch from a2e41c5 to 4e071b9 Compare May 27, 2026 11:10
sampennington and others added 2 commits May 27, 2026 14:18
4 updated
Run: 9089df71-ebcc-406d-92d7-b122d6ef32a2

Co-authored-by: sampennington <56024559+sampennington@users.noreply.github.com>
@sampennington sampennington merged commit 6b1d412 into master May 27, 2026
162 checks passed
@sampennington sampennington deleted the posthog-code/hog-charts-boxplot-primitive branch May 27, 2026 14:07
@deployment-status-posthog
Copy link
Copy Markdown

deployment-status-posthog Bot commented May 27, 2026

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-05-27 14:46 UTC Run
prod-us ✅ Deployed 2026-05-27 15:01 UTC Run
prod-eu ✅ Deployed 2026-05-27 15:07 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants