fix: make activity view passive

This commit is contained in:
mirivlad 2026-06-28 16:14:59 +08:00
parent 8010d6c601
commit 3b7258ed3e
2 changed files with 36 additions and 92 deletions

View File

@ -115,76 +115,10 @@
};
}
function activityId() {
return 'activity-' + Date.now() + '-' + Math.random().toString(36).slice(2);
}
function eventPayload(event) {
return event && event.payload && typeof event.payload === 'object' ? event.payload : {};
}
function firstText(values) {
for (var i = 0; i < values.length; i += 1) {
var value = text(values[i]).trim();
if (value) return value;
}
return '';
}
function titleFromEvent(event, payload) {
return firstText([
payload.title,
payload.name,
payload.path,
payload.url,
payload.captureId,
event.name,
'Activity event'
]);
}
function summaryFromEvent(event, payload) {
if (payload.text) return text(payload.text).trim();
return firstText([
payload.summary,
payload.description,
payload.path,
payload.url,
payload.domain,
event.name
]);
}
function eventToActivity(event, scope) {
var payload = eventPayload(event);
var workspaceRoot = workspaceFromPayload(payload) || (scope && scope.workspaceRoot) || '';
return {
activityId: activityId(),
type: text(event && event.name).trim() || 'activity.event',
title: titleFromEvent(event || {}, payload),
summary: summaryFromEvent(event || {}, payload),
occurredAt: text(payload.occurredAt || payload.capturedAt || (event && event.timestamp) || new Date().toISOString()),
receivedAt: new Date().toISOString(),
sourcePluginId: text((event && event.pluginId) || payload.pluginId || payload.sourcePluginId),
workspaceRootPath: workspaceRoot,
payload: payload
};
}
function manualActivity(scope) {
return {
activityId: activityId(),
type: 'activity.manual',
title: 'Manual activity',
summary: 'Manually recorded activity event',
occurredAt: new Date().toISOString(),
receivedAt: new Date().toISOString(),
sourcePluginId: PLUGIN_ID,
workspaceRootPath: (scope && scope.workspaceRoot) || '',
payload: {}
};
}
function normalizeStoredEvents(value, storageKey) {
if (!Array.isArray(value)) return [];
return value.filter(function (item) {
@ -268,14 +202,6 @@
var titleEl = el('span', { className: 'activity-title', textContent: scope.mode === 'global' ? 'Activity' : 'Activity · ' + scope.label });
var countEl = el('span', { className: 'activity-count' });
var statusEl = el('span', { className: 'activity-status' });
var manualBtn = el('button', {
className: 'activity-btn',
'data-activity-action': 'manual',
textContent: 'Record',
onClick: function () {
addActivity(manualActivity(scope));
}
});
var clearBtn = el('button', {
className: 'activity-btn danger',
'data-activity-action': 'clear',
@ -293,7 +219,6 @@
toolbar.appendChild(countEl);
toolbar.appendChild(el('span', { className: 'activity-spacer' }));
toolbar.appendChild(statusEl);
toolbar.appendChild(manualBtn);
toolbar.appendChild(clearBtn);
var listEl = el('div', { className: 'activity-list' });
@ -331,14 +256,6 @@
});
}
function addActivity(activity) {
activity._storageKey = scope.key;
events = sortEvents([activity].concat(events));
statusText = 'Activity recorded';
statusClass = '';
return persist().then(render);
}
function renderList() {
listEl.innerHTML = '';
if (events.length === 0) {
@ -406,7 +323,7 @@
return api.events.subscribe(eventName, function (event) {
var eventWorkspace = workspaceFromPayload(eventPayload(event));
if (scope.mode === 'workspace' && eventWorkspace && eventWorkspace !== scope.workspaceRoot) return Promise.resolve();
return addActivity(eventToActivity(event, scope));
return loadStored().then(render);
}).then(function (unsubscribe) {
if (typeof unsubscribe === 'function') unsubscribers.push(unsubscribe);
});

View File

@ -157,11 +157,31 @@ async function mountWithApi(api, props = { workspaceNode: { name: 'Project' }, w
(async () => {
const api = makeApi();
const { component, container } = await mountWithApi(api);
const projectKey = 'events:workspace:Project';
const clientKey = 'events:workspace:ClientA';
const globalKey = 'events:global';
for (const name of ['file.opened', 'file.changed', 'note.saved', 'action.started', 'browser.capture.received', 'case.selected', 'browser.capture.selection']) {
if (typeof api.handlers[name] !== 'function') throw new Error(`${name} subscription missing`);
}
await api.settings.write(projectKey, [{
activityId: 'capture-1',
type: 'browser.capture.selection',
title: 'Example Article',
summary: 'Selected text',
occurredAt: '2026-06-27T00:00:00Z',
sourcePluginId: 'verstak.browser-inbox',
workspaceRootPath: 'Project',
payload: {
captureId: 'capture-1',
kind: 'selection',
title: 'Example Article',
url: 'https://example.com/article',
text: 'Selected text',
workspaceRootPath: 'Project',
},
}]);
await api.handlers['browser.capture.selection']({
name: 'browser.capture.selection',
pluginId: 'verstak.browser-inbox',
@ -176,9 +196,6 @@ async function mountWithApi(api, props = { workspaceNode: { name: 'Project' }, w
});
await flush();
const projectKey = 'events:workspace:Project';
const clientKey = 'events:workspace:ClientA';
const globalKey = 'events:global';
const stored = api.storedEvents(projectKey);
if (stored.length !== 1) throw new Error(`expected one stored activity event, got ${stored.length}`);
if (stored[0].type !== 'browser.capture.selection') throw new Error('stored event type mismatch');
@ -189,6 +206,20 @@ async function mountWithApi(api, props = { workspaceNode: { name: 'Project' }, w
const clientView = await mountWithApi(api, { workspaceNode: { name: 'ClientA' }, workspaceRootPath: 'ClientA' });
if (clientView.container.textContent.includes('Example Article')) throw new Error('Project activity leaked into ClientA workspace view');
await api.settings.write(clientKey, [{
activityId: 'client-note',
type: 'note.saved',
title: 'Client note',
summary: 'ClientA/Notes/Client.md',
occurredAt: '2026-06-27T00:10:00Z',
sourcePluginId: 'verstak.notes',
workspaceRootPath: 'ClientA',
payload: {
title: 'Client note',
path: 'ClientA/Notes/Client.md',
workspaceRootPath: 'ClientA',
},
}]);
await api.handlers['note.saved']({
name: 'note.saved',
pluginId: 'verstak.notes',
@ -210,11 +241,7 @@ async function mountWithApi(api, props = { workspaceNode: { name: 'Project' }, w
component.unmount && component.unmount(globalView.container);
const manualButton = walk(container, (node) => node.getAttribute && node.getAttribute('data-activity-action') === 'manual');
if (!manualButton) throw new Error('manual activity button not found');
manualButton.click();
await flush();
if (api.storedEvents(projectKey).length !== 2) throw new Error('manual activity was not stored');
if (!container.textContent.includes('Manual activity')) throw new Error('manual activity was not rendered');
if (manualButton) throw new Error('manual activity button should not be rendered');
const clearButton = walk(container, (node) => node.getAttribute && node.getAttribute('data-activity-action') === 'clear');
if (!clearButton) throw new Error('clear activity button not found');