feat: add unread updates to My Work (#133)
CI / lint (pull_request) Successful in 12s
CI / build-frontend (pull_request) Successful in 5s

This commit is contained in:
timmy committed 2026-08-06 19:59:39 +00:00
1 parent d75a55f3f7
commit f34fc8b5f5
7 files changed
+336 -13

No files matched your search

+20 -6
View File
@@ -131,6 +131,7 @@ textarea { resize: vertical; min-height: 120px; }
<button class="work-filter" data-work-filter="issue" aria-pressed="false">Issues <span data-work-count="issue">0</span></button>
<button class="work-filter" data-work-filter="pull" aria-pressed="false">PRs <span data-work-count="pull">0</span></button>
<button class="work-filter" data-work-filter="review" aria-pressed="false">Reviews <span data-work-count="review">0</span></button>
<button class="work-filter" data-work-filter="update" aria-pressed="false">Updates <span data-work-count="update">0</span></button>
</div>
</div>
<div class="my-work-list" id="my-work-list"></div>
@@ -300,11 +301,12 @@ textarea { resize: vertical; min-height: 120px; }
let selectedWorkFilter = 'all';
try {
const savedFilter = sessionStorage.getItem(WORK_FILTER_KEY);
if (['all', 'issue', 'pull', 'review'].includes(savedFilter)) selectedWorkFilter = savedFilter;
if (['all', 'issue', 'pull', 'review', 'update'].includes(savedFilter)) selectedWorkFilter = savedFilter;
} catch (e) {
console.warn('Could not restore My Work filter', e);
}
let lastMyWork = [];
let lastNotifications = [];
let hasContextSnapshot = false;
let selectedReview = null;
let reviewTrigger = null;
@@ -381,7 +383,7 @@ textarea { resize: vertical; min-height: 120px; }
});
qs('#my-work').removeAttribute('data-stale');
qs('#my-work-status').textContent = lastMyWork.length ?
summarizeMyWork(lastMyWork) : 'No assigned work or review requests.';
summarizeMyWork(lastMyWork) : 'No assigned work, review requests, or unread updates.';
renderMyWork();
}
@@ -389,7 +391,7 @@ textarea { resize: vertical; min-height: 120px; }
const visible = filterMyWork(lastMyWork, selectedWorkFilter);
qs('#my-work-list').innerHTML = visible.length ? visible.map(item => {
const contents =
'<span class="small">' + escapeHtml(item.key) + ' · ' + escapeHtml(item.kind === 'pull' ? 'PR' : 'Issue') + '</span>' +
'<span class="small">' + escapeHtml(item.key) + ' · ' + escapeHtml(item.kind === 'pull' ? 'PR' : (item.kind === 'update' ? 'Update' : 'Issue')) + '</span>' +
'<span class="my-work-card-title">' + escapeHtml(item.title) + '</span>' +
'<span class="pill">' + escapeHtml(item.reason) + '</span>' +
(item.updated_at ? '<span class="small"> · Updated ' + escapeHtml(fmt(item.updated_at)) + '</span>' : '');
@@ -398,7 +400,7 @@ textarea { resize: vertical; min-height: 120px; }
return '<button class="my-work-card review-trigger" data-review-index="' + index + '">' + contents + '</button>';
}
return '<a class="my-work-card" href="' + escAttr(item.url) + '" target="_blank" rel="noopener noreferrer">' + contents + '</a>';
}).join('') : '<div class="muted">No ' + (selectedWorkFilter === 'review' ? 'reviews' : (selectedWorkFilter === 'all' ? 'work' : selectedWorkFilter + ' items')) + '.</div>';
}).join('') : '<div class="muted">No ' + (selectedWorkFilter === 'review' ? 'reviews' : (selectedWorkFilter === 'update' ? 'unread updates' : (selectedWorkFilter === 'all' ? 'work' : selectedWorkFilter + ' items'))) + '.</div>';
document.querySelectorAll('[data-review-index]').forEach(button => {
button.addEventListener('click', () => openReviewSheet(lastMyWork[Number(button.dataset.reviewIndex)], button));
});
@@ -533,6 +535,13 @@ textarea { resize: vertical; min-height: 120px; }
'Update failed · showing last known work' : 'Work inbox unavailable.';
}
function markNotificationsStale() {
qs('#my-work').setAttribute('data-stale', 'true');
qs('#my-work-status').textContent = lastNotifications.length ?
'Unread updates unavailable · showing last known updates' :
'Unread updates unavailable · assigned work is fresh';
}
function paintDeltas(deltas) {
const el = qs('#ai');
el.innerHTML = deltas.length ? deltas.map(d => '<div class="suggestion ' + d.priority + '"><span class="pill">' + escapeHtml(d.priority) + '</span> <strong>' + escapeHtml(d.action) + '</strong> ' + escapeHtml(d.target || '') + '<div class="muted">' + escapeHtml(d.panel) + '</div></div>').join('') : '<div class="muted">No suggestions yet.</div>';
@@ -553,8 +562,13 @@ textarea { resize: vertical; min-height: 120px; }
}
function renderLiveSnapshot(snapshot) {
if (snapshot.context) renderContextSnapshot(snapshot.context);
else handleContextError(new Error('Context section unavailable'));
const notificationsFresh = Array.isArray(snapshot.notifications);
if (notificationsFresh) lastNotifications = snapshot.notifications;
if (snapshot.context) {
snapshot.context.notifications = lastNotifications;
renderContextSnapshot(snapshot.context);
if (!notificationsFresh) markNotificationsStale();
} else handleContextError(new Error('Context section unavailable'));
if (snapshot.events) {
paintEventStream(snapshot.events);
setEventStreamStatus('Updated ' + fmt(new Date()));