feat: resolve shared My Work routes
CI / lint (pull_request) Successful in 17s
CI / build-frontend (pull_request) Successful in 5s

This commit is contained in:
timmy committed 2026-08-07 15:58:56 +00:00
1 parent 2fecccfbde
commit b5545e380e
6 files changed
+328 -3

No files matched your search

+35
View File
@@ -76,6 +76,8 @@ textarea { resize: vertical; min-height: 120px; }
.load-more-notifications[hidden] { display:none; }
.load-more-work { min-height:44px; width:100%; margin-top:10px; }
.load-more-work[hidden] { display:none; }
.retry-work-route { min-height:44px; width:100%; margin-top:10px; }
.retry-work-route[hidden] { display:none; }
.my-work-bulk { position:sticky; bottom:0; z-index:4; margin:10px -4px -12px; padding:10px 4px; padding-bottom:calc(10px + env(safe-area-inset-bottom)); background:rgba(11,21,38,.98); border-top:1px solid #2a496e; }
.my-work-bulk button { min-height:44px; width:100%; }
.my-work[data-stale="true"] { border-color:#fcd34d; }
@@ -287,6 +289,7 @@ textarea { resize: vertical; min-height: 120px; }
<div class="small" id="notification-page-status" aria-live="polite"></div>
<button class="load-more-notifications" id="load-more-notifications" type="button" hidden>Load older updates</button>
<div class="small" id="my-work-action-status" aria-live="assertive"></div>
<button class="retry-work-route" id="retry-work-route" type="button" hidden>Retry shared work item</button>
<div class="small" id="work-route-share-status" aria-live="polite"></div>
<div class="my-work-bulk" id="bulk-mark-read-bar" hidden>
<button id="bulk-mark-read" type="button"></button>
@@ -733,6 +736,17 @@ textarea { resize: vertical; min-height: 120px; }
if (!response.ok) throw new Error(payload.error || 'Review request failed.');
return payload;
}
async function api(url) {
const response = await fetch(url, { headers: { Accept: 'application/json' } });
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
const error = new Error(payload.error || payload.detail || 'Shared work request failed.');
error.unavailable = response.status === 404;
throw error;
}
return payload;
}
const reviewController = createReviewController({ fetchJson: fetchReviewJson, storage: localStorage });
const issueController = createIssueSheet({ fetchJson: fetchReviewJson, storage: localStorage });
const issueCapture = createIssueCapture({ fetchJson: fetchReviewJson, storage: localStorage });
@@ -942,7 +956,22 @@ textarea { resize: vertical; min-height: 120px; }
location: window.location,
history: window.history,
eventTarget: window,
resolve: route => {
const params = new URLSearchParams({ kind:route.kind });
if (route.kind === 'update') params.set('notification_id', route.notification_id);
else {
params.set('repository', route.repository);
params.set('number', route.number);
}
return api('api/v1/work-route?' + params.toString());
},
onResolving: () => {
qs('#retry-work-route').hidden = true;
qs('#my-work-action-status').textContent = 'Loading shared work item…';
},
onOpen: item => {
qs('#retry-work-route').hidden = true;
qs('#my-work-action-status').textContent = '';
closeOpenWorkSheets();
if (item.kind === 'update') notificationReader.open(item, lastMyWork);
else if (item.kind === 'review') openReviewSheet(item, reviewTrigger);
@@ -956,13 +985,19 @@ textarea { resize: vertical; min-height: 120px; }
if (selectedUpdate) closeUpdateSheet(true, false);
},
onInvalid: () => {
qs('#retry-work-route').hidden = true;
window.history.replaceState(null, '', window.location.pathname + window.location.search);
closeOpenWorkSheets();
qs('#my-work-action-status').textContent = 'Route unavailable · this item is no longer in My Work.';
qs('#my-work').scrollIntoView({ block:'start' });
},
onError: () => {
qs('#my-work-action-status').textContent = 'Could not load shared work item. The link is preserved; retry when connected.';
qs('#retry-work-route').hidden = false;
},
});
workRoute.start();
qs('#retry-work-route').addEventListener('click', () => workRoute.sync());
function closeOpenWorkSheets() {
['#issue-sheet', '#pull-sheet', '#review-sheet', '#update-sheet'].forEach(selector =>
+39 -2
View File
@@ -46,31 +46,68 @@
Number(item.number) === route.number;
}
function createController({ location, history, eventTarget, onOpen, onClose, onInvalid }) {
function createController({
location, history, eventTarget, onOpen, onClose, onInvalid,
resolve, onResolving = function () {}, onError = function () {},
}) {
let items = [];
let started = false;
let active = '';
let ready = false;
let resolving = '';
let resolution = 0;
function resolveMissing(fragment, route) {
if (typeof resolve !== 'function') {
onInvalid();
return;
}
if (resolving === fragment) return;
resolving = fragment;
const request = ++resolution;
onResolving(route);
Promise.resolve(resolve(route)).then(item => {
if (request !== resolution || String(location.hash || '') !== fragment) return;
resolving = '';
if (!item || !sameRoute(item, route)) {
onInvalid();
return;
}
active = fragment;
onOpen({ ...item, kind: route.kind });
}).catch(error => {
if (request !== resolution || String(location.hash || '') !== fragment) return;
resolving = '';
if (error?.unavailable) onInvalid();
else onError(error, route);
});
}
function sync() {
const fragment = String(location.hash || '');
if (!fragment) {
resolution += 1;
resolving = '';
if (active) onClose();
active = '';
return;
}
const route = parse(fragment);
if (!route) {
resolution += 1;
resolving = '';
if (fragment.startsWith('#/my-work/')) onInvalid();
active = '';
return;
}
const item = items.find(candidate => sameRoute(candidate, route));
if (!item) {
if (ready) onInvalid();
if (ready) resolveMissing(fragment, route);
return;
}
if (active === fragment) return;
resolution += 1;
resolving = '';
active = fragment;
onOpen({ ...item, kind: route.kind });
}