fix(share): restore a share link opened in an already-open tab (#1416)

* fix(share): restore a share link opened by hash navigation

Opening a different share link in an already-open tab (pasting it into
the address bar, following a link, or Back/Forward) changed the URL but
left the camera, visual style, map, panels and layers on the old view
until a reload.

The share manager now listens for hashchange and popstate. A new hash
that parses as a share and differs from the last hash the app read or
wrote runs the start-up restore pipeline: lane authority is reserved, URL
writes are held (including a pending debounced write of the old view),
the camera restore takes a new navigation generation, and the layer
coordinator replaces any in-flight layer restore with the new share's
layers through the passive share origin. A newer navigation supersedes an
older restore end to end; only the newest one releases URL writes. The
app's own replaceState writes never restore. Listeners are removed on
destroy.

Returning to the Normal style and resetting an already-active preset now
also apply when a share is restored after start-up, so the result matches
a fresh load of the same link.

* docs: changelog for #1416
This commit is contained in:
Sameh Khamis authored and GitHub committed 2026-10-26 13:56:19 -07:00
1 parent 301bc2e0b3
commit b7ece1d4d1
8 files changed
+688 -58

No files matched your search

+4 -2
View File
@@ -94,11 +94,13 @@ test('a deferred lookup that never flies leaves the readout standing', () => {
/stamp: \(\) =>\s*this\._stampNavigation\(\{\s*cancelPendingSelection,\s*clearSearchedLocation: false,?\s*\}\)/,
);
const reassert = ui.indexOf(' _reassertNavigationHandoff(generation) {');
const reassert = ui.indexOf(
" _reassertNavigationHandoff(generation, { trackingOrigin = 'tool' } = {}) {",
);
assert.ok(reassert > 0, '_reassertNavigationHandoff is missing');
assert.match(
ui.slice(reassert, reassert + 900),
/release: \(\) => \{[\s\S]{0,320}?this\.clearLocation\(\);[\s\S]{0,200}?this\._releaseFollowCamera\(\)/,
/release: \(\) => \{[\s\S]{0,320}?this\.clearLocation\(\);[\s\S]{0,200}?this\._releaseFollowCamera\(\{ trackingOrigin \}\)/,
);
// …and the policy only reaches `release` after its authority checks pass.