diff --git a/src/library/controller.js b/src/library/controller.js index 22cfb68f9..fffafb1f2 100644 --- a/src/library/controller.js +++ b/src/library/controller.js @@ -69,6 +69,10 @@ export function createDataLibrary({ let availability = new Map(); let serverPresent = false; let selection = { added: [], on: [] }; + // What this browser remembers as on. Like core layer state, a share + // decides what is on for the session but never rewrites it: only the + // viewer's explicit choices and a local restore do. + let savedOn = []; let started = false; // Set once availability is known; a share navigated to before then waits. let loaded = false; @@ -89,7 +93,11 @@ export function createDataLibrary({ const persist = () => { selection = normalizeLibrarySelection(selection, switchable); - writeStoredLibrarySelection(storage, selection); + savedOn = savedOn.filter((id) => selection.added.includes(id)); + writeStoredLibrarySelection(storage, { + added: selection.added, + on: savedOn, + }); shareLink?.onLibraryStateChange?.(); notify(); }; @@ -163,9 +171,16 @@ export function createDataLibrary({ return; } const on = new Set(selection.on); - if (change.enabled) on.add(id); - else on.delete(id); + const saved = new Set(savedOn); + if (change.enabled) { + on.add(id); + saved.add(id); + } else { + on.delete(id); + saved.delete(id); + } selection = { ...selection, on: [...on] }; + savedOn = [...saved]; persist(); }); @@ -217,9 +232,10 @@ export function createDataLibrary({ console.warn(`[Library] ${id} restore failed:`, error), ); } - writeStoredLibrarySelection(storage, selection); - shareLink?.onLibraryStateChange?.(); - notify(); + // A share's entries join the library; only a local restore sets what + // is remembered as on. + if (origin !== 'share-restore') savedOn = selection.on; + persist(); } /** @@ -262,6 +278,7 @@ export function createDataLibrary({ availability = answer.availability; serverPresent = answer.server; const stored = readStoredLibrarySelection(storage, switchable); + savedOn = stored.on; // A share opened in this tab while availability loaded wins over the // opening one. const linked = queuedShare ? idsForTokens(queuedShare) : openingShare(); diff --git a/src/library/controller.test.mjs b/src/library/controller.test.mjs index fa4305a5d..9aac21764 100644 --- a/src/library/controller.test.mjs +++ b/src/library/controller.test.mjs @@ -201,12 +201,48 @@ test('a link decides what is on and adds its entries to the library', async (t) await manager.waitForLayerSettled?.('lib-sea-marks'); assert.equal(manager.isEnabled('lib-sea-marks'), true); assert.equal(manager.isEnabled('lib-night-lights'), false); + // The link's entry joins the library; what is remembered as on stays + // the viewer's own. assert.deepEqual(JSON.parse(storage.store.get(LIBRARY_STORAGE_KEY)), { added: ['night-lights', 'sea-marks'], - on: ['sea-marks'], + on: ['night-lights'], }); }); +test('a share never rewrites what this browser remembers as on', async (t) => { + const saved = JSON.stringify({ + added: ['night-lights', 'sea-marks'], + on: ['night-lights'], + }); + const storage = memoryStorage(saved); + const manager = sealedManager(t); + const shareLink = fakeShareLink([]); + const library = createDataLibrary({ + entries, + ledger, + dataManager: manager, + services: {}, + storage, + shareLink, + source: source({}), + }); + await library.start(); + assert.equal(manager.isEnabled('lib-night-lights'), false); + shareLink.listener(['01']); + await manager.waitForLayerSettled?.('lib-sea-marks'); + assert.equal(manager.isEnabled('lib-sea-marks'), true); + assert.deepEqual(JSON.parse(storage.store.get(LIBRARY_STORAGE_KEY)), { + added: ['night-lights', 'sea-marks'], + on: ['night-lights'], + }); + // An explicit choice is remembered, and only that one. + await manager.setEnabled('lib-sea-marks', false, { origin: 'user' }); + await manager.setEnabled('lib-night-lights', true, { origin: 'user' }); + await manager.setEnabled('lib-night-lights', false, { origin: 'user' }); + assert.deepEqual(JSON.parse(storage.store.get(LIBRARY_STORAGE_KEY)).on, []); + library.destroy(); +}); + /** A ShareLinkManager stand-in: the opening share and in-tab navigation. */ function fakeShareLink(initialLibraryTokens) { const link = {