From 8d90cac7dc5e0501254fc0fa31798c663ce222d4 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 12 Nov 2026 15:59:14 -0800 Subject: [PATCH] Keep lib= in the URL before the library starts and for entries it cannot serve The share encoder wrote only registered entries. Until availability loaded nothing was registered, so any hash write in the meantime (a camera move during start-up) deleted lib= from the address bar, and an entry this instance could not serve (its proxy unreachable) was dropped for good: copying the link or reloading lost those entries. The URL now carries what the page opened with until the library starts, and every entry that is on afterwards, served here or not. --- src/library/controller.js | 8 +++++++- src/library/controller.test.mjs | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/library/controller.js b/src/library/controller.js index fffafb1f2..6be6d29ca 100644 --- a/src/library/controller.js +++ b/src/library/controller.js @@ -257,10 +257,16 @@ export function createDataLibrary({ } shareLink?.setLibraryShareListener?.(applyShare); + // The URL carries what is on. Until availability loads (or when it never + // does) that is still what the page opened with, so a hash write in the + // meantime keeps it; an entry this instance cannot serve stays in it too. shareLink?.setLibraryParamsEncoder?.((params) => encodeLibraryParam( params, - selection.on.filter((id) => isRegistered(id)), + loaded + ? selection.on + : ((queuedShare ? idsForTokens(queuedShare) : openingShare()) ?? + readStoredLibrarySelection(storage, switchable).on), tokens.tokenById, ), ); diff --git a/src/library/controller.test.mjs b/src/library/controller.test.mjs index 9aac21764..51daf7f2a 100644 --- a/src/library/controller.test.mjs +++ b/src/library/controller.test.mjs @@ -480,6 +480,40 @@ test('a layer that fails to tear down stays in the library', async (t) => { library.destroy(); }); +test('the URL keeps the opening lib= before availability loads and for entries it cannot serve', async (t) => { + const manager = sealedManager(t); + const shareLink = shareLinkDouble(); + let answer; + const pending = new Promise((resolve) => { + answer = resolve; + }); + const library = createDataLibrary({ + entries, + ledger, + dataManager: manager, + services: {}, + storage: memoryStorage(), + shareLink, + initialHash: '#lat=1&lon=2&lib=00.01', + source: { ...source({}), getAvailability: () => pending }, + }); + const started = library.start(); + // A camera move rewrites the hash before the catalog answers. + assert.equal(shareLink.encode(), '00.01'); + answer({ + server: false, + availability: new Map([ + ['night-lights', { available: false, reason: 'server-unavailable' }], + ['sea-marks', { available: true, reason: null }], + ]), + }); + await started; + await manager.waitForLayerSettled?.('lib-sea-marks'); + assert.equal(manager.layers.has('lib-night-lights'), false); + assert.equal(shareLink.encode(), '00.01'); + library.destroy(); +}); + test('zoom flies to an added layer through the caller, reset removes everything', async (t) => { const manager = sealedManager(t); const storage = memoryStorage();