diff --git a/plugins/activity/frontend/src/index.js b/plugins/activity/frontend/src/index.js index 581883a..10858c5 100644 --- a/plugins/activity/frontend/src/index.js +++ b/plugins/activity/frontend/src/index.js @@ -8,6 +8,7 @@ var PLUGIN_ID = 'verstak.activity'; var MAX_EVENTS = 250; + var LEGACY_KEY = 'events'; var GLOBAL_KEY = 'events:global'; var WORKSPACE_PREFIX = 'events:workspace:'; var ACTIVITY_EVENTS = [ @@ -197,7 +198,7 @@ occurredAt: text(item.occurredAt || item.timestamp || item.receivedAt), receivedAt: text(item.receivedAt), sourcePluginId: text(item.sourcePluginId || item.pluginId), - workspaceRootPath: cleanWorkspace(item.workspaceRootPath || (item.payload && (item.payload.workspaceRootPath || item.payload.workspaceName))), + workspaceRootPath: cleanWorkspace(item.workspaceRootPath || workspaceFromPayload(item.payload || {})), _storageKey: storageKey || '', payload: item.payload && typeof item.payload === 'object' ? item.payload : {} }; @@ -221,13 +222,20 @@ } function sortEvents(activityList) { - return activityList.slice().sort(function (a, b) { + var seen = {}; + return activityList.filter(function (item) { + var key = item && item.activityId; + if (!key) return false; + if (seen[key]) return false; + seen[key] = true; + return true; + }).slice().sort(function (a, b) { return text(b.occurredAt || b.receivedAt).localeCompare(text(a.occurredAt || a.receivedAt)); }).slice(0, MAX_EVENTS); } function globalEventKeys(settings) { - var keys = [GLOBAL_KEY]; + var keys = [LEGACY_KEY, GLOBAL_KEY]; Object.keys(settings || {}).forEach(function (key) { if (key.indexOf(WORKSPACE_PREFIX) === 0 && keys.indexOf(key) === -1) keys.push(key); }); @@ -377,8 +385,12 @@ statusClass = 'error'; }); } - return api.settings.read(scope.key).then(function (stored) { - events = normalizeStoredEvents(stored, scope.key); + return api.settings.read().then(function (settings) { + var scopedEvents = normalizeStoredEvents((settings || {})[scope.key], scope.key); + var legacyEvents = normalizeStoredEvents((settings || {})[LEGACY_KEY], LEGACY_KEY).filter(function (item) { + return item.workspaceRootPath === scope.workspaceRoot; + }); + events = sortEvents(scopedEvents.concat(legacyEvents)); }).catch(function (err) { statusText = 'Could not load activity: ' + (err && err.message ? err.message : String(err)); statusClass = 'error'; diff --git a/plugins/browser-inbox/frontend/src/index.js b/plugins/browser-inbox/frontend/src/index.js index de64a29..3fe715e 100644 --- a/plugins/browser-inbox/frontend/src/index.js +++ b/plugins/browser-inbox/frontend/src/index.js @@ -9,6 +9,7 @@ var PLUGIN_ID = 'verstak.browser-inbox'; var CAPTURE_EVENTS = ['browser.capture.page', 'browser.capture.selection', 'browser.capture.link']; var MAX_CAPTURES = 100; + var LEGACY_KEY = 'captures'; var GLOBAL_KEY = 'captures:global'; var WORKSPACE_PREFIX = 'captures:workspace:'; @@ -185,13 +186,20 @@ } function sortCaptures(captureList) { - return captureList.slice().sort(function (a, b) { + var seen = {}; + return captureList.filter(function (item) { + var key = item && item.captureId; + if (!key) return false; + if (seen[key]) return false; + seen[key] = true; + return true; + }).slice().sort(function (a, b) { return text(b.capturedAt || b.receivedAt).localeCompare(text(a.capturedAt || a.receivedAt)); }).slice(0, MAX_CAPTURES); } function globalCaptureKeys(settings) { - var keys = [GLOBAL_KEY]; + var keys = [LEGACY_KEY, GLOBAL_KEY]; Object.keys(settings || {}).forEach(function (key) { if (key.indexOf(WORKSPACE_PREFIX) === 0 && keys.indexOf(key) === -1) keys.push(key); }); @@ -411,8 +419,12 @@ statusClass = 'error'; }); } - return api.settings.read(scope.key).then(function (stored) { - captures = normalizeStoredCaptures(stored, scope.key); + return api.settings.read().then(function (settings) { + var scopedCaptures = normalizeStoredCaptures((settings || {})[scope.key], scope.key); + var legacyCaptures = normalizeStoredCaptures((settings || {})[LEGACY_KEY], LEGACY_KEY).filter(function (item) { + return item.workspaceRootPath === scope.workspaceRoot; + }); + captures = sortCaptures(scopedCaptures.concat(legacyCaptures)); if (!selectedId && captures[0]) selectedId = captures[0].captureId; }).catch(function (err) { statusText = 'Could not load inbox: ' + (err && err.message ? err.message : String(err)); diff --git a/scripts/smoke-activity-plugin.js b/scripts/smoke-activity-plugin.js index 7e5a4df..8fee102 100644 --- a/scripts/smoke-activity-plugin.js +++ b/scripts/smoke-activity-plugin.js @@ -238,6 +238,34 @@ async function mountWithApi(api, props = { workspaceNode: { name: 'Project' }, w const persisted = await mountWithApi(persistedApi); if (!persisted.container.textContent.includes('Saved note')) throw new Error('persisted activity was not rendered'); + const legacyApi = makeApi({ + events: [ + { + activityId: 'legacy-global', + type: 'browser.capture.page', + title: 'Legacy global capture', + occurredAt: '2026-06-27T02:00:00Z', + sourcePluginId: 'verstak.browser-inbox', + }, + { + activityId: 'legacy-project', + type: 'note.saved', + title: 'Legacy project note', + occurredAt: '2026-06-27T02:10:00Z', + sourcePluginId: 'verstak.notes', + payload: { path: 'Project/Notes/Legacy.md' }, + }, + ], + }); + const legacyGlobal = await mountWithApi(legacyApi, {}); + if (!legacyGlobal.container.textContent.includes('Legacy global capture')) throw new Error('legacy global activity was not rendered in global view'); + if (!legacyGlobal.container.textContent.includes('Legacy project note')) throw new Error('legacy workspace activity was not rendered in global view'); + component.unmount && component.unmount(legacyGlobal.container); + + const legacyProject = await mountWithApi(legacyApi, { workspaceNode: { name: 'Project' }, workspaceRootPath: 'Project' }); + if (!legacyProject.container.textContent.includes('Legacy project note')) throw new Error('legacy workspace activity was not rendered in matching workspace'); + if (legacyProject.container.textContent.includes('Legacy global capture')) throw new Error('legacy global activity leaked into workspace view'); + console.log('activity plugin smoke passed'); })().catch((err) => { console.error(err); diff --git a/scripts/smoke-browser-inbox-plugin.js b/scripts/smoke-browser-inbox-plugin.js index 37661f2..4453aaa 100644 --- a/scripts/smoke-browser-inbox-plugin.js +++ b/scripts/smoke-browser-inbox-plugin.js @@ -274,6 +274,44 @@ async function mountWithApi(api, props = { workspaceNode: { name: 'Project' }, w throw new Error('persisted capture was not rendered on mount'); } + const legacyApi = makeApi({ + captures: [ + { + captureId: 'legacy-global-capture', + capturedAt: '2026-06-27T02:00:00.000Z', + kind: 'page', + url: 'https://legacy.example.com/', + title: 'Legacy Global Capture', + domain: 'legacy.example.com', + }, + { + captureId: 'legacy-project-capture', + capturedAt: '2026-06-27T02:10:00.000Z', + kind: 'page', + url: 'https://project.example.com/', + title: 'Legacy Project Capture', + domain: 'project.example.com', + workspaceRootPath: 'Project', + }, + ], + }); + const legacyGlobal = await mountWithApi(legacyApi, {}); + if (!walk(legacyGlobal.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'legacy-global-capture')) { + throw new Error('legacy global capture was not rendered in global view'); + } + if (!walk(legacyGlobal.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'legacy-project-capture')) { + throw new Error('legacy workspace capture was not rendered in global view'); + } + component.unmount && component.unmount(legacyGlobal.container); + + const legacyProject = await mountWithApi(legacyApi, { workspaceNode: { name: 'Project' }, workspaceRootPath: 'Project' }); + if (!walk(legacyProject.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'legacy-project-capture')) { + throw new Error('legacy workspace capture was not rendered in matching workspace'); + } + if (walk(legacyProject.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'legacy-global-capture')) { + throw new Error('legacy global capture leaked into workspace view'); + } + console.log('browser inbox plugin smoke passed'); })().catch((err) => { console.error(err);