diff --git a/frontend/dashboard.js b/frontend/dashboard.js index 76f921a..77b83d7 100644 --- a/frontend/dashboard.js +++ b/frontend/dashboard.js @@ -112,6 +112,7 @@ } let queueCounts = {}; let preparationItems = {}; + let offlineWorkMode = false; let renderMobileQueuePresentation = () => {}; const followingQueue = attachFollowing(item => { searchPreviewReturnKind = 'following'; @@ -181,6 +182,7 @@ firstAction: name => qs('#my-work-list .my-work-card-main, #my-work-list .draft-resume, #my-work-list .draft-continue, #my-work-list .draft-edit'), announce: announceWork, getCounts: () => queueCounts, + isOnline: () => !offlineWorkMode, getPreparation: () => { const briefing = mobileStartDay.briefing(); return {...briefing, active:mobileStartDay.state().active}; @@ -354,7 +356,6 @@ qs('#empty-work-find').addEventListener('click', () => qs('#find-work').click()); qs('#empty-work-create').addEventListener('click', () => qs('#new-issue').click()); let liveMode = true; - let offlineWorkMode = false; const WORK_FILTER_KEY = 'stackchain.my-work-filter.v1'; const WORK_MILESTONE_KEY = 'stackchain.my-work-milestone.v1'; const WORK_FILTERS = ['all', 'today', 'agenda', 'attention', 'filed', 'authored', 'issue', 'pull', 'review', 'update', 'later', 'draft']; @@ -7937,6 +7938,7 @@ } function setOfflineWorkMode(value) { offlineWorkMode = value; + renderMobileQueuePresentation(); ['#find-work', '#start-work-session', '#load-more-work', '#load-more-notifications', '#bulk-mark-read'] .forEach(selector => { const button = qs(selector); if (button) button.disabled = value; }); if (value) { diff --git a/frontend/mobile-queue-launcher.js b/frontend/mobile-queue-launcher.js index c9edb36..1bfad5a 100644 --- a/frontend/mobile-queue-launcher.js +++ b/frontend/mobile-queue-launcher.js @@ -22,6 +22,7 @@ ['draft', 'Open Drafts'], ]; const activeQueueNames = continuation.map(([name]) => name); + const onlineOnlyQueues = new Set(['delivery', 'gate']); const allQueues = [ 'today', 'tomorrow', 'week', 'agenda', 'delivery', 'gate', 'attention', 'update', 'following', 'filed', 'authored', 'later', 'draft', 'find', 'recaps', @@ -29,7 +30,10 @@ function recommend() { const counts = options.getCounts ? options.getCounts() : {}; - const match = continuation.find(([name]) => Number(counts[name]) > 0); + const online = options.isOnline ? options.isOnline() : true; + const match = continuation.find(([name]) => + Number(counts[name]) > 0 && (online || !onlineOnlyQueues.has(name)) + ); if (!match) return {name: 'find', count: 0, label: 'Find Work'}; const [name, label] = match; const count = Math.max(0, Number(counts[name]) || 0); diff --git a/tests/e2e/test_adaptive_mobile_queues_release.py b/tests/e2e/test_adaptive_mobile_queues_release.py index c9788b0..867a95f 100644 --- a/tests/e2e/test_adaptive_mobile_queues_release.py +++ b/tests/e2e/test_adaptive_mobile_queues_release.py @@ -117,3 +117,52 @@ def test_adaptive_queues_open_existing_following_and_authored_work(viewport, que next_action.press("Enter") assert page.evaluate("window.destinations") == destination browser.close() + + +def test_adaptive_queues_keep_start_continue_actionable_offline_on_phone(): + viewport = {"width": 390, "height": 844} + with sync_playwright() as playwright: + browser = playwright.chromium.launch(headless=True) + page = browser.new_page(viewport=viewport) + page.set_content((FRONTEND / "index.html").read_text()) + page.add_style_tag(path=FRONTEND / "dashboard.css") + page.add_script_tag(path=FRONTEND / "mobile-queue-launcher.js") + page.evaluate("""() => { + const rows = Object.fromEntries(Array.from(document.querySelectorAll('[data-mobile-queue]')) + .map(row => [row.dataset.mobileQueue, row])); + window.online = false; + window.destinations = []; + window.adaptiveQueueLauncher = createMobileQueueLauncher({ + getCounts:() => ({delivery:1, gate:2, today:3}), + isOnline:() => window.online, + rows, + nextAction:document.querySelector('#mobile-queue-next-action'), + activeList:document.querySelector('#mobile-queue-active-list'), + planningList:document.querySelector('#mobile-queue-planning-list'), + allList:document.querySelector('#mobile-queue-all-list'), + activeSection:document.querySelector('#mobile-queue-active-list').parentElement, + openToday:() => window.destinations.push('today'), + }); + window.adaptiveQueueLauncher.renderPresentation(); + document.querySelector('#mobile-queue-next-action').addEventListener( + 'click', () => window.adaptiveQueueLauncher.continueWork() + ); + document.querySelector('#mobile-queue-sheet').showModal(); + }""") + + next_action = page.locator("#mobile-queue-next-action") + expect(next_action).to_be_visible() + expect(next_action).to_have_text("Continue Today (3)") + next_action.press("Enter") + assert page.evaluate("window.destinations") == ["today"] + + page.evaluate("""() => { + window.online = true; + window.adaptiveQueueLauncher.renderPresentation(); + }""") + expect(next_action).to_have_text("Recover Delivery (1)") + bounds = next_action.bounding_box() + assert bounds and bounds["height"] >= 44 + assert bounds["y"] + bounds["height"] <= viewport["height"] + assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth") + browser.close() diff --git a/tests/test_mobile_task_dock.py b/tests/test_mobile_task_dock.py index 9c03a8f..32f426f 100644 --- a/tests/test_mobile_task_dock.py +++ b/tests/test_mobile_task_dock.py @@ -300,6 +300,18 @@ async def test_mobile_queue_sheet_prioritizes_next_active_and_planning_without_d assert html.count(f'