Compare commits
No commits in common. "a2f83b0955c4260e983fcc51b054474cae740adb" and "467c8c665dd3ee6469fc4dd8a9d3090560ee2991" have entirely different histories.
a2f83b0955
...
467c8c665d
|
|
@ -147,7 +147,6 @@
|
||||||
let lastContextSnapshot = null;
|
let lastContextSnapshot = null;
|
||||||
let notificationPagination = { page: 1, total: 0, has_more: false };
|
let notificationPagination = { page: 1, total: 0, has_more: false };
|
||||||
let workPagination = {};
|
let workPagination = {};
|
||||||
let agendaChecking = false;
|
|
||||||
let hasContextSnapshot = false;
|
let hasContextSnapshot = false;
|
||||||
let selectedReview = null;
|
let selectedReview = null;
|
||||||
let reviewTrigger = null;
|
let reviewTrigger = null;
|
||||||
|
|
@ -2203,9 +2202,6 @@
|
||||||
'No assigned work, review requests, or unread updates.';
|
'No assigned work, review requests, or unread updates.';
|
||||||
updateWorkPaginationControls();
|
updateWorkPaginationControls();
|
||||||
renderMyWork();
|
renderMyWork();
|
||||||
if (selectedWorkFilter === 'agenda' && workPagination.issue?.has_more && !agendaChecking) {
|
|
||||||
completeAgendaIssues();
|
|
||||||
}
|
|
||||||
if (reconcileSession && workSession.active()) workSession.reconcile();
|
if (reconcileSession && workSession.active()) workSession.reconcile();
|
||||||
updateWorkSessionActions();
|
updateWorkSessionActions();
|
||||||
}
|
}
|
||||||
|
|
@ -2221,20 +2217,6 @@
|
||||||
return [];
|
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() {
|
function renderDrafts() {
|
||||||
const list = qs('#my-work-list');
|
const list = qs('#my-work-list');
|
||||||
const displayedDrafts = findQueueItems(lastDrafts, queueFindQuery);
|
const displayedDrafts = findQueueItems(lastDrafts, queueFindQuery);
|
||||||
|
|
@ -2507,12 +2489,9 @@
|
||||||
return '<article class="' + cardClasses + '">' + selector + '<a class="my-work-card-main pull-trigger" href="' + escAttr(routeHref) + '" data-pull-index="' + index + '">' + contents + '</a>' + readUpdate + markRead + planningActions + '</article>';
|
return '<article class="' + cardClasses + '">' + selector + '<a class="my-work-card-main pull-trigger" href="' + escAttr(routeHref) + '" data-pull-index="' + index + '">' + contents + '</a>' + readUpdate + markRead + planningActions + '</article>';
|
||||||
}
|
}
|
||||||
return '<article class="' + cardClasses + '">' + selector + '<a class="my-work-card-main update-trigger" href="' + escAttr(routeHref) + '" data-update-index="' + index + '">' + contents + '</a>' + markRead + planningActions + '</article>';
|
return '<article class="' + cardClasses + '">' + selector + '<a class="my-work-card-main update-trigger" href="' + escAttr(routeHref) + '" data-update-index="' + index + '">' + contents + '</a>' + markRead + planningActions + '</article>';
|
||||||
}).join('') : (showEmptyStart ? '' : '<div class="muted">' +
|
}).join('') : (showEmptyStart ? '' : '<div class="muted">' + (incomplete ?
|
||||||
(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.' :
|
'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'))))) + '.')) + '</div>');
|
'No ' + (selectedWorkFilter === 'attention' ? 'items need attention' : (selectedWorkFilter === 'review' ? 'reviews' : (selectedWorkFilter === 'update' ? 'unread updates' : (selectedWorkFilter === 'later' ? 'deferred work' : (selectedWorkFilter === 'all' ? 'work' : selectedWorkFilter + ' items'))))) + '.') + '</div>');
|
||||||
cardPlanning.wire();
|
cardPlanning.wire();
|
||||||
document.querySelectorAll('[data-select-notification-id]').forEach(input => {
|
document.querySelectorAll('[data-select-notification-id]').forEach(input => {
|
||||||
input.addEventListener('change', () => {
|
input.addEventListener('change', () => {
|
||||||
|
|
@ -5799,10 +5778,6 @@
|
||||||
notificationPager.loadMore(lastNotifications)
|
notificationPager.loadMore(lastNotifications)
|
||||||
);
|
);
|
||||||
qs('#load-more-work').addEventListener('click', async () => {
|
qs('#load-more-work').addEventListener('click', async () => {
|
||||||
if (selectedWorkFilter === 'agenda') {
|
|
||||||
await completeAgendaIssues();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const stream = activeWorkStreams().find(item => workPagination[item]?.has_more);
|
const stream = activeWorkStreams().find(item => workPagination[item]?.has_more);
|
||||||
if (!stream || !lastContextSnapshot) return;
|
if (!stream || !lastContextSnapshot) return;
|
||||||
const button = qs('#load-more-work');
|
const button = qs('#load-more-work');
|
||||||
|
|
@ -5956,7 +5931,6 @@
|
||||||
qs('#active-work-queue').textContent = button.firstChild.textContent.trim() + ' (' + selectedCount + ')';
|
qs('#active-work-queue').textContent = button.firstChild.textContent.trim() + ' (' + selectedCount + ')';
|
||||||
renderMyWork();
|
renderMyWork();
|
||||||
updateWorkPaginationControls();
|
updateWorkPaginationControls();
|
||||||
if (selectedWorkFilter === 'agenda') completeAgendaIssues();
|
|
||||||
if (!preserveRoute) workRoute.queue(filter);
|
if (!preserveRoute) workRoute.queue(filter);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -277,13 +277,12 @@ function createNotificationPager({ load, onNotifications, onPagination, onStatus
|
||||||
function createWorkPager({ load, onItems, onPagination, onStatus }) {
|
function createWorkPager({ load, onItems, onPagination, onStatus }) {
|
||||||
let pagination = {};
|
let pagination = {};
|
||||||
const pending = new Set();
|
const pending = new Set();
|
||||||
const completing = new Map();
|
|
||||||
const labels = {
|
const labels = {
|
||||||
issue: 'issues',
|
issue: 'issues',
|
||||||
pull: 'pull requests',
|
pull: 'pull requests',
|
||||||
review: 'review requests',
|
review: 'review requests',
|
||||||
};
|
};
|
||||||
const pager = {
|
return {
|
||||||
reset(next) {
|
reset(next) {
|
||||||
Object.entries(next || {}).forEach(([stream, value]) => {
|
Object.entries(next || {}).forEach(([stream, value]) => {
|
||||||
const current = pagination[stream];
|
const current = pagination[stream];
|
||||||
|
|
@ -332,20 +331,7 @@ function createWorkPager({ load, onItems, onPagination, onStatus }) {
|
||||||
pending.delete(stream);
|
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({
|
function createNotificationReader({
|
||||||
|
|
|
||||||
|
|
@ -173,60 +173,6 @@ 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"]
|
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
|
@pytest.mark.anyio
|
||||||
async def test_mobile_queue_finder_is_labeled_thumb_safe_and_offers_older_search():
|
async def test_mobile_queue_finder_is_labeled_thumb_safe_and_offers_older_search():
|
||||||
html = await dashboard()
|
html = await dashboard()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user