Skip to content

Product View: live embedded browser with a PostHog analytics overlay - #4054

Draft
DanielVisca wants to merge 10 commits into
mainfrom
posthog-code/product-view
Draft

Product View: live embedded browser with a PostHog analytics overlay#4054
DanielVisca wants to merge 10 commits into
mainfrom
posthog-code/product-view

Conversation

@DanielVisca

Copy link
Copy Markdown

Problem

Product owners using PostHog Code have all the data about their product — events, errors, latency, sessions, PRs — but no way to see it on the product itself. Answering "is this button healthy? who uses it? what breaks for them? which PR touched it?" means hopping between the app, PostHog cloud, and GitHub.

Changes

A new Product tab: a live embedded browser showing your own site (or a local checkout, e.g. hogli start) with your PostHog project's analytics overlaid per element, and a one-click Investigate that starts an agent task seeded with everything the panel knows.

┌────────────────────────────────────────────────────────────────────┐
│ ◀ ▶ ⟳  [ https://posthog.com ]  [Inspect]  [Production ▾]  Data: … │
├──────────────────────────────────────────────┬─────────────────────┤
│                                              │  <selected element> │
│   YOUR LIVE SITE (WebContentsView)           │  Usage · 30d  ▂▄▆█  │
│                                              │  Errors (sessions)  │
│   ┌──────────────┐   halos + badges          │  Latency p50/95/99  │
│   │ Get started  │◄─ rendered in-page by     │  Network trace      │
│   └──────────────┘   an isolated preload     │  Sessions (replay)  │
│                                              │  Pull requests      │
│                                              │  [ Investigate ]    │
└──────────────────────────────────────────────┴─────────────────────┘

How it flows (page source and data source are decoupled — a localhost checkout can render production analytics):

preload (isolated world, closed shadow root)      main process                 PostHog cloud
  enumerate interactive elements ───────────────▶ ProductViewService ────────▶ /elements/stats + HogQL
  ◀─────────────── display-ready halo items ───── match + health-score ◀────── (data project)
  clicks/requests ─▶ CDP capture (traceparent) ─▶ live latency + trace ids
  select element ──────────────────────────────▶ detail queries + PR impact ─▶ details panel
  • Three interaction tiers: ambient (thin health halos only — unhealthy elements always shown, plus top-usage), inspect mode (hover badges, click to select), details panel (usage trend, correlated error issues, live latency percentiles, request waterfall with trace ids, replay links, PR impact).
  • Element ↔ data matching uses the toolbar's own avenue: /elements/stats chains matched against live DOM descriptors, keyed data-attr > id > href > tag+text, with ambiguous keys dropped. Health comes from rage/dead-click share with a minimum-sample guard.
  • PR impact is deterministic: git-grep the local checkout for the element's stable keys → merged PRs from squash-commit subjects (git log -- <files>), open PRs via gh pr list (degrades to git-only without gh).
  • Investigate starts an auto-mode cloud task whose prompt seeds every concrete identifier (element keys, error issue ids, session ids, live trace ids, source files, PRs) and instructs the agent to re-derive the numbers with the posthog MCP tools before concluding.
  • Security: fully sandboxed guest with its own cookie partition; PostHog credentials never enter the page (queries run host-side; only display-ready JSON crosses IPC); everything page-derived is capped, Zod-validated, and escaped before HogQL; CDP capture forwards only timing + traceparent (never cookies/auth headers); http(s)-only navigation, window.open denied.
  • Purely additive: new @posthog/platform capability (IEmbeddedBrowser + HostCapabilities.embeddedBrowser, stubbed false on web), new product-view modules in core/workspace-server/host-router/ui, one additive sqlite migration, a product appView tab target, and a command-palette entry. No existing behavior changed.

How did you test this?

  • TDD unit suites (all green): shared/core/workspace-server — element matching against a fixture captured from the live /elements/stats endpoint, health thresholds, HogQL builders incl. escaping of hostile page-derived values, overlay pipeline (reported elements → project-scoped stats fetch → halos pushed), environments persistence through the real sqlite migration, PR-subject parsing, investigate prompt content.
  • Live query validation: every detail-panel HogQL shape (trend, session-correlated exceptions, sessions, web vitals) was run against the real posthog.com project via MCP before being baked into builders.
  • Real overlay engine, live site: drove the built productViewPreload.js in a real WebContentsView loading live posthog.com under Xvfb, matched the reported DOM against real project element stats with the real matcher, and screenshotted both ambient halos and inspect-mode badges rendering correctly. (Screenshots carry usage figures, so they're in the task thread rather than this public PR; the steps below reproduce them.)
  • pnpm typecheck (24/24 incl. web portability), biome lint packages/core (zero noRestrictedImports), node scripts/check-host-boundaries.mjs (no new violations), electron-vite build (both preload bundles), Playwright e2e: 12/13 passed, the one failure ("window minimum dimensions") reproduces on a clean tree under Xvfb — pre-existing environment issue, not this change.
  • Signed-in app validation needs a real session (this sandbox can't complete OAuth) — see steps below.

Validate it yourself

  1. gh pr checkout <this PR> && pnpm install && pnpm dev
  2. Cmd/Ctrl+K → type "Product" → open the Product tab.
  3. Pick the suggested origin (derived from your project's app_urls + pageview hosts) — e.g. posthog.com for the PostHog App + Website project. Green/amber/red halos appear on elements with autocapture history within a few seconds.
  4. Toggle Inspect, hover elements to see usage badges, click one (e.g. the signup CTA) — the details panel opens with usage trend, correlated errors, sessions, and PRs touching its source.
  5. Click around the page, re-open the panel: live latency percentiles + the network trace populate (with trace markers where traceparent is present).
  6. Click Investigate — an auto-mode cloud task starts, seeded with the element's full context.
  7. Local-mode proof: run your product locally (e.g. hogli start), add http://localhost:8010 as an environment via the switcher — the same production halos overlay the local page.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Created with PostHog Code

…istry

Stage 1 of the Product View: a top-level Product tab that embeds the user's
own site (live origin or localhost dev server) in a sandboxed WebContentsView.

- @posthog/platform: IEmbeddedBrowser interface + EMBEDDED_BROWSER token,
  HostCapabilities.embeddedBrowser flag (desktop true, web false)
- apps/code: ElectronEmbeddedBrowser adapter (own cookie partition,
  http(s)-only navigation, window.open denied → external browser)
- @posthog/core: ProductViewService (main container) — navigation guards,
  page-state stream, product-URL suggestions from app_urls + $pageview hosts
- @posthog/workspace-server: product_environments sqlite table + service
  (per-project page-origin/data-project registry, tab restore via currentUrl)
- host-router: productView router (one-line forwards + event subscription)
- @posthog/ui: /product route, ProductView (toolbar, URL bar, environment
  switcher, data-project badge), environment picker with data-derived
  suggestions; Product appView tab target + command palette entry

Generated-By: PostHog Code
Task-Id: 1120fcea-fc07-4c54-8dbb-50b7b712f9cb
Stage 2: the analytics overlay, driven end to end by real PostHog data.

- product-view preload (isolated world, closed shadow root): enumerates
  interactive elements, reports capped descriptors to the host, paints
  green/amber/red halos + inspect-mode hover/select — no credentials or page
  access ever cross the boundary
- core elementMatching: parses /elements/stats/ rows (fixture captured from
  the live posthog.com project) and attributes autocapture/rage/dead click
  counts to live DOM descriptors, keyed data-attr > id > href > tag+text with
  ambiguity dropping
- core healthScore: frustration-share health (rage+dead clicks) with a
  min-sample guard, and a clutter budget (all unhealthy + top-usage healthy)
- ProductViewService overlay pipeline: inspector reports → per-page cached
  elements/stats fetch against the environment's data project → matched
  halos pushed back into the page
- UI: Inspect toggle; view opens with the environment's dataProjectId

Generated-By: PostHog Code
Task-Id: 1120fcea-fc07-4c54-8dbb-50b7b712f9cb
Stage 3: select an element, see its full story.

- core detail queries (all shapes validated against the live posthog.com
  project first): 30-day usage trend, exceptions correlated via shared
  sessions, recent replay sessions, page web-vitals p75 — built from the
  element's strongest chain key (data-attr > id > href > text) with HogQL
  string/LIKE escaping of all page-derived values
- LatencySampleBuffer: bounded rolling p50/p95/p99 over live requests
- adapter CDP capture: fetch/XHR timing + traceparent trace ids only (never
  cookies/auth headers); requests within 2s of a click are attributed to that
  element; degrades cleanly when a debugger is already attached
- preload: ambient (non-blocking) interaction reporting for attribution
- ElementDetailsPanel: usage sparkline, frustration, error issues, live
  latency percentiles, request waterfall with trace markers, replay links —
  deep links open in PostHog cloud scoped to the environment's data project

Generated-By: PostHog Code
Task-Id: 1120fcea-fc07-4c54-8dbb-50b7b712f9cb
Stage 4: act on what the overlay shows.

- ProductCodeContextService (workspace-server): grep the local checkout for
  the element's stable keys (data-attr/id/text via git grep), surface
  recently-merged PRs from squash-commit subjects in `git log -- <files>`
  (pure parsing, tested) and open PRs whose diff touches those files via the
  gh CLI — degrading to git-only history when gh is unavailable
- buildInvestigatePrompt (core, tested): verify-then-conclude prompt seeding
  page URL, element keys, usage/frustration totals, correlated error issue
  IDs, session IDs, live trace IDs, source files, and implicated PRs, with an
  ordered procedure over the posthog MCP tools and explicit drift caveats
- InvestigateSection in the details panel: open/merged PR list + Investigate
  button that starts an auto-mode cloud task via the shared inbox task runner

Generated-By: PostHog Code
Task-Id: 1120fcea-fc07-4c54-8dbb-50b7b712f9cb
Generated-By: PostHog Code
Task-Id: 1120fcea-fc07-4c54-8dbb-50b7b712f9cb
@trunk-io

trunk-io Bot commented Jul 31, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

React Doctor found 1 issue in 1 file · 1 warning.

1 warning

src/features/product-view/ElementDetailsPanel.tsx

Reviewed by React Doctor for commit 15f5337.

…rkline a11y role

The quality check runs `biome ci` repo-wide, which is stricter than the
scoped `biome check` used during development: import/export sorting in the
DI wiring files and shared index, formatting in the host containers, and
role="img" so aria-label is valid on the sparkline div.

Generated-By: PostHog Code
Task-Id: 1120fcea-fc07-4c54-8dbb-50b7b712f9cb
Google (and other IdPs) reject OAuth from anything identifying as an
embedded webview, and popup-based SSO can't complete when window.open is
bounced to the system browser (no shared session).

- strip Electron/app tokens from the view's user agent (plain Chrome UA),
  fixing Google's disallowed_useragent rejection
- allow http(s) popups as sandboxed, preload-less child windows on the same
  persist:product-view partition so popup SSO completes; child windows get
  the same clean UA, still block non-web schemes, and can't nest popups

Verified with an Electron harness: UA drops the Electron token, and
window.open('https://accounts.google.com/…') creates a shared-partition
child window with a clean UA.

Generated-By: PostHog Code
Task-Id: 1120fcea-fc07-4c54-8dbb-50b7b712f9cb
…lbar menus

The environment dropdown opened underneath the embedded page — the native
WebContentsView paints above every renderer popover, so any menu over the
page area was invisible.

- environments are now inline toolbar pills: one click switches instantly
  (each environment keeps its own live view, so page state is preserved),
  nothing opens over the page in the hot path; "+" adds an environment and
  right-click on a pill removes it
- new productViewObscuredStore: renderer popovers that can overlap the page
  (the pill context menu, alongside the existing command-menu case) hide the
  native view while open, with an unmount guard so a leaked acquire can't
  keep the page hidden

Generated-By: PostHog Code
Task-Id: 1120fcea-fc07-4c54-8dbb-50b7b712f9cb
- parseGithubRemote: trim the remote URL — `git remote get-url` output ends
  with a newline, which stopped the ".git" suffix from being stripped, so
  merged-PR links pointed at github.com/<owner>/<repo>.git/pull/N (404) and
  the gh open-PR lookup got a garbage --repo value. Also accept ssh:// remotes.
- toolbar: flex-wrap the row and truncate pill labels / the data badge so
  environments flow to a second line instead of crushing the URL bar.
- stop resetting the embedded page mid-session: create() no longer navigates
  an existing view back to the environment's debounce-persisted URL, and the
  slot effect no longer tears down/reopens when the environments query
  refetches — either could yank a multi-step login back to a stale page.

Generated-By: PostHog Code
Task-Id: 1120fcea-fc07-4c54-8dbb-50b7b712f9cb
…on, log popup decisions

- saveEnvironment now updates in place when the project already has an
  environment with the same normalized origin — running the picker twice
  created duplicate pills (no way to end up with three identical
  us.posthog.com entries anymore)
- each environment pill gets a visible ✕ (right-click remove stays) —
  closing was undiscoverable
- the embedded view's window-open handler logs each popup request and
  verdict so "the SSO popup never opened" is diagnosable from main logs

Generated-By: PostHog Code
Task-Id: 1120fcea-fc07-4c54-8dbb-50b7b712f9cb
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.

1 participant