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

+13 -2
View File
@@ -75,13 +75,24 @@ export class VisualSettings {
panelState,
styleParams,
} = state || {};
// Ignore the retired 'ai-edit' style from older share links.
if (style && style !== 'normal' && style !== 'ai-edit') {
// Ignore the retired 'ai-edit' style from older share links. Normal is
// the start-up style, but a share opened later in the tab must still leave
// a filtered style for it.
if (
style &&
style !== 'ai-edit' &&
(style !== 'normal' || this.activeStyle !== 'normal')
) {
const alreadyActive = style === this.activeStyle;
this.setStyle(style, {
applyPreset: true,
revealParameters: false,
restore: true,
});
// setStyle leaves an already-active style untouched; the incoming share
// still starts from that preset before its own parameters apply.
if (alreadyActive && style !== 'normal')
this._applyStylePresetDefaults(style);
}
if (styleParams && style && this.stages[style] && STYLES[style]?.uniforms) {
for (const [uniformName, uniformValue] of Object.entries(styleParams)) {