diff --git a/frontend/dashboard.js b/frontend/dashboard.js
index 90566e7..e2d9d29 100644
--- a/frontend/dashboard.js
+++ b/frontend/dashboard.js
@@ -147,6 +147,7 @@
let lastContextSnapshot = null;
let notificationPagination = { page: 1, total: 0, has_more: false };
let workPagination = {};
+ let agendaChecking = false;
let hasContextSnapshot = false;
let selectedReview = null;
let reviewTrigger = null;
@@ -2202,6 +2203,9 @@
'No assigned work, review requests, or unread updates.';
updateWorkPaginationControls();
renderMyWork();
+ if (selectedWorkFilter === 'agenda' && workPagination.issue?.has_more && !agendaChecking) {
+ completeAgendaIssues();
+ }
if (reconcileSession && workSession.active()) workSession.reconcile();
updateWorkSessionActions();
}
@@ -2217,6 +2221,20 @@
return [];
}
+ async function completeAgendaIssues() {
+ if (selectedWorkFilter !== 'agenda' || !workPagination.issue?.has_more || !lastContextSnapshot) return;
+ agendaChecking = true;
+ qs('#my-work-action-status').textContent = 'Checking all assigned deadlines…';
+ renderMyWork();
+ const complete = await workPager.loadAll('issue', () => lastContextSnapshot?.issues || []);
+ agendaChecking = false;
+ if (selectedWorkFilter !== 'agenda') return;
+ qs('#my-work-action-status').textContent = complete ?
+ 'All assigned deadlines checked.' :
+ 'Agenda check paused. Retry to check older assigned deadlines.';
+ renderMyWork();
+ }
+
function renderDrafts() {
const list = qs('#my-work-list');
const displayedDrafts = findQueueItems(lastDrafts, queueFindQuery);
@@ -2489,9 +2507,12 @@
return '' + selector + '' + contents + '' + readUpdate + markRead + planningActions + '';
}
return '' + selector + '' + contents + '' + markRead + planningActions + '';
- }).join('') : (showEmptyStart ? '' : '
' + (incomplete ?
+ }).join('') : (showEmptyStart ? '' : '
' +
+ (selectedWorkFilter === 'agenda' && workPagination.issue?.has_more ?
+ (agendaChecking ? 'Checking all assigned deadlines…' :
+ 'Older assigned deadlines remain unchecked. Retry the Agenda check.') : (incomplete ?
'More work is available. Load the next page.' :
- 'No ' + (selectedWorkFilter === 'attention' ? 'items need attention' : (selectedWorkFilter === 'review' ? 'reviews' : (selectedWorkFilter === 'update' ? 'unread updates' : (selectedWorkFilter === 'later' ? 'deferred work' : (selectedWorkFilter === 'all' ? 'work' : selectedWorkFilter + ' items'))))) + '.') + '
');
+ 'No ' + (selectedWorkFilter === 'attention' ? 'items need attention' : (selectedWorkFilter === 'review' ? 'reviews' : (selectedWorkFilter === 'update' ? 'unread updates' : (selectedWorkFilter === 'later' ? 'deferred work' : (selectedWorkFilter === 'all' ? 'work' : selectedWorkFilter + ' items'))))) + '.')) + '
');
cardPlanning.wire();
document.querySelectorAll('[data-select-notification-id]').forEach(input => {
input.addEventListener('change', () => {
@@ -5778,6 +5799,10 @@
notificationPager.loadMore(lastNotifications)
);
qs('#load-more-work').addEventListener('click', async () => {
+ if (selectedWorkFilter === 'agenda') {
+ await completeAgendaIssues();
+ return;
+ }
const stream = activeWorkStreams().find(item => workPagination[item]?.has_more);
if (!stream || !lastContextSnapshot) return;
const button = qs('#load-more-work');
@@ -5931,6 +5956,7 @@
qs('#active-work-queue').textContent = button.firstChild.textContent.trim() + ' (' + selectedCount + ')';
renderMyWork();
updateWorkPaginationControls();
+ if (selectedWorkFilter === 'agenda') completeAgendaIssues();
if (!preserveRoute) workRoute.queue(filter);
return true;
}
diff --git a/frontend/my-work.js b/frontend/my-work.js
index 06a72a3..ca8a4a1 100644
--- a/frontend/my-work.js
+++ b/frontend/my-work.js
@@ -277,12 +277,13 @@ function createNotificationPager({ load, onNotifications, onPagination, onStatus
function createWorkPager({ load, onItems, onPagination, onStatus }) {
let pagination = {};
const pending = new Set();
+ const completing = new Map();
const labels = {
issue: 'issues',
pull: 'pull requests',
review: 'review requests',
};
- return {
+ const pager = {
reset(next) {
Object.entries(next || {}).forEach(([stream, value]) => {
const current = pagination[stream];
@@ -331,7 +332,20 @@ function createWorkPager({ load, onItems, onPagination, onStatus }) {
pending.delete(stream);
}
},
+ loadAll(stream, getExisting) {
+ if (completing.has(stream)) return completing.get(stream);
+ const completion = (async () => {
+ while (pagination[stream]?.has_more) {
+ const loaded = await pager.loadMore(stream, getExisting());
+ if (!loaded) return false;
+ }
+ return true;
+ })().finally(() => completing.delete(stream));
+ completing.set(stream, completion);
+ return completion;
+ },
};
+ return pager;
}
function createNotificationReader({
diff --git a/tests/test_my_work.py b/tests/test_my_work.py
index 351a571..99c2550 100644
--- a/tests/test_my_work.py
+++ b/tests/test_my_work.py
@@ -173,6 +173,60 @@ process.stdout.write(JSON.stringify(work.agendaMyWork(items, new Date('2026-08-1
assert json.loads(result.stdout) == ["a/r#2", "a/r#10", "z/r#1"]
+def test_agenda_pager_loads_every_issue_page_single_flight_and_retries_failed_page():
+ script = f"""
+const work = require({json.dumps(str(MY_WORK))});
+const calls = [];
+let failPage = 3;
+let items = [{{id:1, title:'first'}}];
+let pagination = {{issue:{{page:1,total:4,has_more:true}}}};
+const pager = work.createWorkPager({{
+ load: async (stream, page) => {{
+ calls.push(page);
+ await new Promise(resolve => setTimeout(resolve, 5));
+ if (page === failPage) throw new Error('offline');
+ return {{page,total:4,has_more:page < 4,items:[{{id:page,title:'page '+page}}]}};
+ }},
+ onItems: (_stream, next) => {{ items = next; }},
+ onPagination: next => {{ pagination = next; }},
+ onStatus: () => {{}},
+}});
+pager.reset(pagination);
+async function run() {{
+ const first = pager.loadAll('issue', () => items);
+ const duplicate = pager.loadAll('issue', () => items);
+ const failed = await first;
+ const samePromise = first === duplicate;
+ failPage = 0;
+ const retried = await pager.loadAll('issue', () => items);
+ process.stdout.write(JSON.stringify({{
+ failed, samePromise, retried, calls, ids:items.map(item => item.id), pagination,
+ }}));
+}}
+run();
+"""
+ result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
+ assert result.returncode == 0, result.stderr
+ assert json.loads(result.stdout) == {
+ "failed": False,
+ "samePromise": True,
+ "retried": True,
+ "calls": [2, 3, 3, 4],
+ "ids": [1, 2, 3, 4],
+ "pagination": {"issue": {"page": 4, "total": 4, "has_more": False}},
+ }
+
+
+@pytest.mark.anyio
+async def test_agenda_activation_checks_all_issue_pages_before_showing_empty_state():
+ html = await dashboard()
+
+ assert "workPager.loadAll('issue'" in html
+ assert "Checking all assigned deadlines…" in html
+ assert "Agenda check paused. Retry to check older assigned deadlines." in html
+ assert "selectedWorkFilter === 'agenda' && workPagination.issue?.has_more" in html
+
+
@pytest.mark.anyio
async def test_mobile_queue_finder_is_labeled_thumb_safe_and_offers_older_search():
html = await dashboard()