diff --git a/frontend/dashboard.js b/frontend/dashboard.js index bbee033..59012c2 100644 --- a/frontend/dashboard.js +++ b/frontend/dashboard.js @@ -143,6 +143,7 @@ const mobileFirstTask = createMobileFirstTask({ getLogin: () => confirmedOwnerLogin, hasWork: () => todayMyWork.length > 0 || activeMyWork.length > 0, + isTodayActive: () => workSession.checkpointed(), }); mobileFirstTask.start(); const mobileWorkEntry = createMobileWorkEntry({ diff --git a/frontend/index.html b/frontend/index.html index 6ecd390..ecee3c9 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1838,8 +1838,8 @@

Choose work, start it, then return to Work whenever you want to continue.

- - + +
diff --git a/frontend/mobile-first-task.js b/frontend/mobile-first-task.js index 1cb108b..3369c59 100644 --- a/frontend/mobile-first-task.js +++ b/frontend/mobile-first-task.js @@ -10,11 +10,13 @@ mediaQuery: win?.matchMedia('(max-width: 600px)') || {matches:false}, eventTarget: win, sheet: doc?.querySelector('#mobile-first-task'), + title: doc?.querySelector('#mobile-first-task-title'), findButton: doc?.querySelector('#mobile-first-task-find'), createButton: doc?.querySelector('#mobile-first-task-create'), setupButton: doc?.querySelector('#mobile-first-task-setup'), closeButton: doc?.querySelector('#close-mobile-first-task'), status: doc?.querySelector('#mobile-first-task-status'), + isTodayActive: () => false, onFind: () => doc?.querySelector('#find-work')?.click(), onCreate: () => doc?.querySelector('#new-issue')?.click(), onSetup: () => doc?.querySelector('#open-device-setup')?.click(), @@ -38,7 +40,7 @@ } function required() { - return Boolean(options.mediaQuery.matches && account() && !options.hasWork() && !completed()); + return Boolean(options.mediaQuery.matches && account() && !options.isTodayActive() && !completed()); } function markComplete() { @@ -50,9 +52,14 @@ function render() { const online = options.isOnline(); + const awaitingStart = options.hasWork(); options.findButton.disabled = !online; - options.status.textContent = online ? - 'Choose a task to claim or create one of your own.' : + if (options.title) options.title.textContent = awaitingStart ? 'Finish starting your first task' : 'Start your first task'; + options.findButton.textContent = 'Find & start'; + options.createButton.textContent = 'Create & start'; + options.status.textContent = awaitingStart ? + 'Your task is ready. Start it from Find or create and start another task.' : online ? + 'Choose a task to claim and start, or create and start one of your own.' : 'You are offline. Create a task now and it will stay in Drafts until you reconnect.'; } @@ -65,12 +72,13 @@ } function refresh() { - if (account() && options.hasWork() && !completed()) { + if (account() && options.isTodayActive() && !completed()) { markComplete(); if (options.sheet.open) options.sheet.close(); return 'completed'; } if (options.sheet.open) render(); + if (required() && options.hasWork()) return 'awaiting-start'; return required() ? 'required' : 'inactive'; } diff --git a/tests/test_mobile_first_task.py b/tests/test_mobile_first_task.py index 69c478d..5968b13 100644 --- a/tests/test_mobile_first_task.py +++ b/tests/test_mobile_first_task.py @@ -28,7 +28,7 @@ class Element {{ return json.loads(result.stdout) -def test_first_task_activation_is_account_bound_and_completes_when_work_appears(): +def test_first_task_activation_is_account_bound_and_completes_only_when_today_is_active(): result = run_node( """ const values = new Map(); @@ -36,24 +36,30 @@ const storage = {getItem:key => values.get(key) || null, setItem:(key,value) => const sheet = new Element(); let login = 'Timmy'; let hasWork = false; +let todayActive = false; const controller = createFirstTask({ - storage, getLogin:() => login, hasWork:() => hasWork, isOnline:() => true, - mediaQuery:{matches:true}, sheet, findButton:new Element(), createButton:new Element(), - setupButton:new Element(), closeButton:new Element(), status:new Element(), - onFind() {}, onCreate() {}, onSetup() {}, + storage, getLogin:() => login, hasWork:() => hasWork, isTodayActive:() => todayActive, + isOnline:() => true, mediaQuery:{matches:true}, sheet, title:new Element(), + findButton:new Element(), createButton:new Element(), setupButton:new Element(), + closeButton:new Element(), status:new Element(), onFind() {}, onCreate() {}, onSetup() {}, }); const before = [controller.required(), controller.open(), sheet.open]; hasWork = true; +const awaitingStart = controller.refresh(); +const stillRequired = controller.required(); +todayActive = true; const completed = controller.refresh(); const timmyRequired = controller.required(); -login = 'alexander'; hasWork = false; +login = 'alexander'; hasWork = false; todayActive = false; const alexanderRequired = controller.required(); -process.stdout.write(JSON.stringify({before, completed, sheetOpen:sheet.open, timmyRequired, alexanderRequired, values:[...values]})); +process.stdout.write(JSON.stringify({before, awaitingStart, stillRequired, completed, sheetOpen:sheet.open, timmyRequired, alexanderRequired, values:[...values]})); """ ) assert result == { "before": [True, True, True], + "awaitingStart": "awaiting-start", + "stillRequired": True, "completed": "completed", "sheetOpen": False, "timmyRequired": False, @@ -62,6 +68,37 @@ process.stdout.write(JSON.stringify({before, completed, sheetOpen:sheet.open, ti } +def test_first_task_activation_resumes_with_finish_starting_guidance(): + result = run_node( + """ +const sheet = new Element(); +const title = new Element(); +const findButton = new Element(); +const createButton = new Element(); +const status = new Element(); +const controller = createFirstTask({ + storage:{getItem:() => null, setItem() {}}, getLogin:() => 'timmy', hasWork:() => true, + isTodayActive:() => false, isOnline:() => true, mediaQuery:{matches:true}, sheet, title, + findButton, createButton, setupButton:new Element(), closeButton:new Element(), status, + onFind() {}, onCreate() {}, onSetup() {}, +}); +controller.open(); +process.stdout.write(JSON.stringify({ + required:controller.required(), title:title.textContent, status:status.textContent, + find:findButton.textContent, create:createButton.textContent, +})); +""" + ) + + assert result == { + "required": True, + "title": "Finish starting your first task", + "status": "Your task is ready. Start it from Find or create and start another task.", + "find": "Find & start", + "create": "Create & start", + } + + def test_first_task_activation_routes_existing_flows_and_keeps_create_available_offline(): result = run_node( """ @@ -112,11 +149,12 @@ async def test_dashboard_renders_and_wires_phone_safe_first_task_activation(): assert '' in html assert '

Start your first task

' in html - assert 'id="mobile-first-task-find" type="button">Find a task' in html - assert 'id="mobile-first-task-create" type="button">Create a task' in html + assert 'id="mobile-first-task-find" type="button">Find & start' in html + assert 'id="mobile-first-task-create" type="button">Create & start' in html assert 'id="mobile-first-task-setup" type="button">Make this phone work-ready' in html assert '' in html assert "const mobileFirstTask = createMobileFirstTask({" in html + assert "isTodayActive: () => workSession.checkpointed()" in html assert "shouldActivate: () => mobileFirstTask.required()" in html assert "openActivation: () => mobileFirstTask.open()" in html assert "mobileFirstTask.refresh()" in html