feat: share progressive live snapshot (Closes #1425)
CI / lint (pull_request) Successful in 3m52s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 6m54s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-26 08:55:08 +00:00
1 parent bea0e0e429
commit 15d7bc9234
19 files changed
+221 -34

No files matched your search

+1
View File
@@ -2323,6 +2323,7 @@
<script src="static/offline-work.js"></script>
<script src="static/offline-today.js"></script>
<script src="static/my-work.js"></script>
<script src="static/progressive-live-snapshot.js"></script>
<script src="static/progressive-my-work.js"></script>
<script src="static/progressive-capture.js"></script>
<script src="static/agenda-replan.js"></script>
+6 -2
View File
@@ -10,8 +10,10 @@ function createProgressiveHumanGates(options = {}) {
if (!response.ok) throw new Error(payload.error || payload.detail?.message || payload.detail || 'Review request failed.');
return payload;
});
const liveSnapshot = options.liveSnapshot || null;
const getIdentity = options.getIdentity || (async () => {
const response = await fetchJson('api/v1/live', {headers:{Accept:'application/json'}});
const response = liveSnapshot ? await liveSnapshot.acquire() :
await fetchJson('api/v1/live', {headers:{Accept:'application/json'}});
const user = response?.context?.user || {};
const login = String(user.login || '').trim();
return {login, accountKey:login && user.id ? String(user.id) + ':' + login : login};
@@ -91,7 +93,9 @@ function createProgressiveHumanGates(options = {}) {
}
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
window.stackchainProgressiveHumanGates = createProgressiveHumanGates({document});
window.stackchainProgressiveHumanGates = createProgressiveHumanGates({
document, liveSnapshot:window.stackchainProgressiveLiveSnapshot,
});
void window.stackchainProgressiveHumanGates.start().catch(() => {});
}
if (typeof module !== 'undefined' && module.exports) {
+40
View File
@@ -0,0 +1,40 @@
function createProgressiveLiveSnapshot({fetchSnapshot}) {
let value = null;
let flight = null;
const acquire = () => {
if (value) return Promise.resolve(value);
if (flight) return flight;
flight = Promise.resolve().then(() => fetchSnapshot()).then(snapshot => {
value = snapshot;
flight = null;
return snapshot;
}, error => {
flight = null;
throw error;
});
return flight;
};
return {
acquire,
snapshot:() => value,
pending:() => flight,
identity() {
const user = value?.context?.user || {};
const login = String(user.login || '').trim();
return {login, accountKey:login && user.id ? String(user.id) + ':' + login : login};
},
};
}
if (typeof window !== 'undefined') {
window.stackchainProgressiveLiveSnapshot = createProgressiveLiveSnapshot({
fetchSnapshot:async () => {
const response = await fetch('api/v1/live', {headers:{Accept:'application/json'}});
if (!response.ok) throw new Error('HTTP ' + response.status);
return response.json();
},
});
}
if (typeof module !== 'undefined' && module.exports) module.exports = createProgressiveLiveSnapshot;
+8 -3
View File
@@ -1,5 +1,6 @@
function createProgressiveMyWork({
document, fetchSnapshot, pollerOptions = {}, lifecycleTarget = globalThis,
document, fetchSnapshot, liveSnapshot: snapshotBroker = null,
pollerOptions = {}, lifecycleTarget = globalThis,
}) {
const list = document.querySelector('#my-work-list');
const status = document.querySelector('#my-work-status');
@@ -25,6 +26,9 @@ function createProgressiveMyWork({
const deferredQueues = {
today:'Today', agenda:'Agenda', later:'Later', draft:'Drafts',
};
const fetchProgressiveSnapshot = (revisions = {}, options = {}) =>
snapshotBroker && Object.keys(revisions || {}).length === 0 ?
snapshotBroker.acquire() : fetchSnapshot(revisions, options);
const escapeHtml = value => String(value || '').replace(/[&<>"']/g, character => ({
'&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#39;',
@@ -128,7 +132,7 @@ function createProgressiveMyWork({
if (typeof createContextPoller === 'function') {
poller = createContextPoller({
...pollerOptions,
fetchContext: fetchSnapshot,
fetchContext: fetchProgressiveSnapshot,
onSnapshot: applySnapshot,
onError: () => {
if (status && !stopped) status.textContent = 'Assigned work is reconnecting…';
@@ -175,7 +179,7 @@ function createProgressiveMyWork({
));
return Boolean(await request);
}
const request = Promise.resolve().then(() => fetchSnapshot());
const request = Promise.resolve().then(() => fetchProgressiveSnapshot());
liveSnapshotPromise = request.then(snapshot => (
snapshot && typeof snapshot === 'object' &&
Object.prototype.hasOwnProperty.call(snapshot, 'context') ? snapshot : null
@@ -208,6 +212,7 @@ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
window.stackchainProgressiveMyWork = createProgressiveMyWork({
document,
lifecycleTarget: window,
liveSnapshot:window.stackchainProgressiveLiveSnapshot,
fetchSnapshot: async (revisions = {}, { signal } = {}) => {
const query = createContextPoller.buildRevisionQuery(revisions);
const response = await fetch('api/v1/live' + (query ? '?' + query : ''), {
+2 -1
View File
@@ -1,7 +1,7 @@
const BASE = new URL('./', self.location.href).pathname;
importScripts(BASE + 'static/private-data-registry.js');
importScripts(BASE + 'static/background-issue-sync.js');
const CACHE = 'stackchain-dashboard-shell-v141';
const CACHE = 'stackchain-dashboard-shell-v142';
const OFFLINE_LEASE_URL = new URL(BASE + '__offline-session-lease', self.location.origin).href;
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
const NAVIGATION_TIMEOUT_MS = self.__STACKCHAIN_NAVIGATION_TIMEOUT_MS || 4000;
@@ -188,6 +188,7 @@ const SHELL = [
BASE + 'static/offline-work.js',
BASE + 'static/offline-today.js',
BASE + 'static/my-work.js',
BASE + 'static/progressive-live-snapshot.js',
BASE + 'static/progressive-my-work.js',
BASE + 'static/progressive-capture.js',
BASE + 'static/agenda-replan.js',