Skip to content

feat(replay): shareable link to an exact moment + tab - #381

Open
ayushjhanwar-png wants to merge 8 commits into
mainfrom
feat/replay-share-link
Open

feat(replay): shareable link to an exact moment + tab#381
ayushjhanwar-png wants to merge 8 commits into
mainfrom
feat/replay-share-link

Conversation

@ayushjhanwar-png

@ayushjhanwar-png ayushjhanwar-png commented Jul 18, 2026

Copy link
Copy Markdown

What

A link icon in the replay player controls (next to the fullscreen button, Mixpanel-style) that copies a shareable deep link to the current moment and current tab:

…/session-replays?session=<id>&t=<seconds>&tab=<window_id>

Opening that link on the Session Replays page auto-selects the tab and seeks to that exact timestamp once the player is ready.

Why

People sharing replays want to point a teammate at a specific moment ("watch what happens at 3:55 on tab 2"), not the whole recording — same as Mixpanel/PostHog.

How

  • CopyLinkButton (in the player controls) reads the current playback offset + active window_id from the replay context/props → writes ?session=&t=&tab= to the clipboard → toasts "Opens at MM:SS".
  • SeekBridge does a one-shot deep-link seek when the player becomes ready (uses the existing smart-seek, so a far jump into a long recording still works).
  • ReplayShell accepts initialWindowId / initialSeekMs; the view reads ?t=/?tab= (nuqs) and applies them only to the deep-linked session (clicking another replay doesn't wrongly re-seek).

Notes

  • Frontend-only, no new deps (uses existing sonner toast + formatDuration).
  • t is in seconds (readable URLs); tab is the stable window_id.
  • Typecheck passes.

Test

Session Replays → play a replay → scrub / switch tab → click the 🔗 → paste in a new tab → lands on that moment + tab.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added shareable session replay links that preserve the selected window and playback timestamp.
    • Added a “Copy replay link” action with confirmation feedback.
    • Opening a shared link automatically selects the specified window and seeks to the saved moment.

Prevents OOM on Aiven CH (43GB per-query cap) hit by the
device_first_seen aggregation + two large anti-joins.
Aggregation + sort buffers spill to disk if they exceed limits.
Correctness unchanged.

Tested against prod CH: 12k candidates in 14s (same as baseline).
07-06 sequential single-INSERT at threads=6 took 7h 17m for a 157M-event day.
The old 5h ceiling would abort any full-day run that hits normal load. 10h
gives cushion for peak-traffic days while still bounding runaway queries.
)

* Revert "perf(db): extend property-MV router to breakdown-by-property charts (redo) (#359)"

This reverts commit 8213214.

* Revert "safety: gate property-MV route on explicit project allowlist (#358)"

This reverts commit 5f9a286.

* Revert "perf(db): route event-property-filter charts to profile_event_property_summary_mv (#356)"

This reverts commit a1d884f.
* feat(db): session-replay blob archival (ClickHouse → Azure Blob)

Adds an archive-replay-chunks CLI that copies session_replay_chunks to
Azure Blob as Parquet — ClickHouse writes directly via azureBlobStorage(),
so replays are kept forever while ClickHouse holds only a rolling hot window.

- Watermark via set-difference on replay_archive_index (resumable, gapless)
- Settled-day (today-2) filter + future-dated (clock-skew) partition guard
- Adaptive session-hash bucketing ceil(GiB/5) so big days fit in memory;
  cityHash64(session_id) keeps each session's chunks in one file
- Memory guardrails on every export (a whole-day sort OOMs at ~44 GiB)
- Verify blob==CH count before writing the index; profile_id joined from events
- Idempotent (overwrite) + ReplacingMergeTree index

Requires the replay_archive_index table and AZURE_BLOB_CONNECTION_STRING.
Design: docs/session-replay-blob-archive.md

* fix(db): address CodeRabbit review on replay archival

- planDays: mark a day done only when index chunk-sum == source rows, so a
  partially-written index (populate failed mid-run) is retried, not skipped
  (was: presence-only detection → silent gaps). [critical]
- planDays: filter system.parts by database = currentDatabase() and skip
  empty (0-row) partitions.
- blobPathExpr: toString(project_id) inside concat (defensive).
- Add code-migration 23-add-replay-archive-index (ReplacingMergeTree,
  ORDER BY (project_id, session_id, dt), no TTL) + TABLE_NAMES entry — makes
  the index reproducible instead of hand-created.
- Docs: align to the shipped design (Parquet + CH-native azureBlobStorage +
  K8s CronJob, not Databricks/Postgres/blocks); mark Blob serving as a planned
  follow-up; fix fenced-code languages.

* fix(db): overwrite blob on re-export (azure_truncate_on_insert)

Without it, re-exporting a day's bucket to its existing path throws
'Object ... already exists' — so the partial-day retry/self-heal path
(Level 1) would crash instead of overwriting. Verified on dev: truncate
insert keeps the file at the correct row count (no duplication).
Removes the customAlerts scheduler entry so the 15-min chart
threshold/anomaly cron stops firing. Handler code is intact
so any in-flight scheduled job still executes cleanly; the
bootCron() flow clears all existing schedulers before re-adding
only the listed ones, so the entry is removed from Redis on
next worker-cron deploy.

Refs #214
…ackfill (#380)

Whole-day INSERTs against `events` cause ~2x scan over-read because the
sort key is (project_id, toDate(created_at), created_at, name). Without
project_id in WHERE, cross-project boundary granules within each part
contain rows from two projects with unrelated date ranges — CH reads the
whole granule and filters at row level.

Adding project_id to WHERE lets the primary key jump directly to that
project's contiguous block within each part, and within it toDate pruning
is precise. Measured on 07-06→07-09 whole-day runs: 315M reads for 157M
actual events (~2x). Expected with per-project split: ~1.05x (only start/
end boundary granules).

New --projects flag:

  pnpm migrate:deploy:code -- 22 --cluster \
    --start="2026-06-30 00:00:00" --end="2026-07-01 00:00:00" \
    --projects=dashreels,shortreels,frameo,studio,growth-tool --no-record

Fires N sequential INSERTs (one per project). Step 0 prints per-project
expected event count; Step 2 logs per-project INSERT elapsed. Whole-day
mode (without --projects) is preserved for legacy invocations.

Failed project doesn't abort — logs the error and continues with next
project. Failed projects can be re-run individually via --projects=<name>.
Adds a link icon to the replay player controls (next to fullscreen) that
copies a deep link to the current playback position and active tab:
?session=<id>&t=<seconds>&tab=<window_id>

Opening the link on the Session Replays page pre-selects that tab and seeks
to that exact timestamp once the player is ready (reuses the existing
smart-seek, so far jumps into long recordings work).

- CopyLinkButton reads current time + window_id from replay context/props
- SeekBridge performs the one-shot deep-link seek on player-ready
- ReplayShell accepts initialWindowId / initialSeekMs; the view reads ?t=/?tab=
  and only applies them to the deep-linked session
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ayushjhanwar-png, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 26e781a8-68e4-44d5-a1d0-78febeaf6f23

📥 Commits

Reviewing files that changed from the base of the PR and between 8ada2b8 and fbd1abb.

📒 Files selected for processing (2)
  • apps/start/src/components/session-replays/session-replays-view.tsx
  • apps/start/src/components/sessions/replay/index.tsx
📝 Walkthrough

Walkthrough

Session replay URLs now preserve the selected session, playback timestamp, and window. Replay playback initializes from those parameters, and the toolbar can copy the current replay position as a shareable link.

Changes

Replay deep links

Layer / File(s) Summary
Query-state capture and replay initialization
apps/start/src/components/session-replays/session-replays-view.tsx, apps/start/src/components/sessions/replay/index.tsx
t and tab query parameters are captured with the initial session and passed to ReplayShell, which initializes the selected window and seek position.
Playback seeking and link copying
apps/start/src/components/sessions/replay/index.tsx
CopyLinkButton copies the current replay URL, while SeekBridge performs one guarded seek after replay data becomes ready.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SessionReplaysView
  participant ReplayShell
  participant ReplayContent
  participant SeekBridge
  participant Clipboard
  participant Toast
  SessionReplaysView->>ReplayShell: pass initialWindowId and initialSeekMs
  ReplayShell->>ReplayContent: pass initialSeekMs for active window
  ReplayContent->>SeekBridge: provide initialSeekMs
  SeekBridge->>SeekBridge: seek once after startTime is ready
  ReplayShell->>Clipboard: copy session, time, and tab URL
  Clipboard-->>Toast: report successful copy
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: shareable replay links that restore an exact moment and tab.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/replay-share-link

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/start/src/components/session-replays/session-replays-view.tsx`:
- Around line 89-101: Update the initialSeekRef handling in the session replay
view so its deep-link state is cleared when selectedSessionId changes. Ensure
returning to the original session cannot reuse the previously consumed t or tab
values, while preserving the one-time initialization behavior for the initially
selected session.

In `@apps/start/src/components/sessions/replay/index.tsx`:
- Around line 175-178: Update the replay link copy flow around
navigator.clipboard.writeText to await its promise and only show the “Replay
link copied” success toast after it resolves; handle rejected writes so failures
do not display a success toast.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 92b23830-d589-47db-81c3-0aaf314c3555

📥 Commits

Reviewing files that changed from the base of the PR and between af4355b and 8ada2b8.

📒 Files selected for processing (2)
  • apps/start/src/components/session-replays/session-replays-view.tsx
  • apps/start/src/components/sessions/replay/index.tsx

Comment thread apps/start/src/components/session-replays/session-replays-view.tsx
Comment thread apps/start/src/components/sessions/replay/index.tsx Outdated
- CopyLinkButton: await clipboard write; only toast success on resolve,
  toast an error on rejection (focus lost / permission denied).
- session-replays-view: clear the deep-link ref once the user navigates to a
  different session, so returning to the shared session doesn't re-seek.
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