fix(viewer): don't pin the default surface in the URL on session open#170
Merged
Conversation
When a session auto-opens, the engine reflected it in the route (/session/:id) and then also auto-focused the topmost surface, writing that into the URL (/session/:id/s/:id) — an unnecessary second history write that read like a redirect chain in the address bar. The surface should only appear in the route when the user means it. The topmost card's IntersectionObserver fires on mount (the card is already in view), so discard that initial fire: it's an auto-focus, not a user choice. A card that mounts off-screen fires isIntersecting:false first (nothing to write), so its first real user-driven intersecting fire still reflects in the route. Deep links are unaffected — they write via select()/pollScrollIntoView and the IO is already suppressed during deepLinkScrolling, so a loaded /session/:id/s/:id stays focused and keeps its URL. The fix lives in the engine's navigation emission (Card.tsx), not a host, so every host (self-hosted + cloud embed) benefits uniformly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When the viewer opens a session that has posts, it first reflects the session in the URL (good): `/session/:id`. Then it also auto-focuses the topmost/default surface and writes that into the URL: `/session/:id/s/:surfaceId`. Landing at the top of a session feed pinned the topmost post's surface in the URL — an unnecessary second history write that read like a redirect chain in the address bar (both the default self-hosted host and the Cloud embed saw it as two router navigations on boot).
```
router.navigate({ sessionId }, { replace: true }) // session auto-select — KEEP
router.navigate({ sessionId, surfaceId }, { replace: true }) // default surface auto-focus — DROP
```
Root cause
On boot, `select()` writes `/session/:id` (good). Then each `Card` mounts an `IntersectionObserver` (`viewer/src/Card.tsx`). The topmost card is already in view, so its IO fires on mount and calls `focusPost(topmostId)` → `router.navigate({ sessionId, surfaceId })` → the unwanted second URL write.
Fix
The surface should only appear in the route when the user means it. The IO's first fire always reports the card's mount-time position — a card already in view is an auto-focus, not a user choice, so discard that initial fire:
The fix is a per-card `let initialFire = true` gate in the IO callback — no module state, timers, or host changes, so every host (self-hosted + Cloud embed) benefits uniformly. It lives in the engine's navigation emission, not a host, per the architecture contract.
Behavior after the fix
Invariant preserved: a real surface deep-link (loaded from the URL, or opened by the user) is always honored and preserved.
Tests
In `e2e/url-routing.spec.ts`:
Validation
Changeset
`patch` — see `.changeset/viewer-boot-no-default-surface-url.md`.