From 9edd3f6441763fa70776ce8867555bc03548effc Mon Sep 17 00:00:00 2001 From: timmy Date: Wed, 12 Aug 2026 02:24:10 +0000 Subject: [PATCH] feat: start empty Today planning from mobile dock (Closes #611) --- frontend/dashboard.js | 2 ++ frontend/mobile-task-dock.js | 1 + frontend/mobile-work-entry.js | 2 ++ tests/test_mobile_task_dock.py | 18 +++++++++++++----- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/frontend/dashboard.js b/frontend/dashboard.js index eba7921..3f618ca 100644 --- a/frontend/dashboard.js +++ b/frontend/dashboard.js @@ -67,9 +67,11 @@ isTodayActive: () => workSession.checkpointed(), isTodayResumable: () => workSession.resumable(), getTodayCount: () => todayMyWork.length, + getEligibleCount: () => activeMyWork.length, continueToday: continueTodaySession, resumeToday: resumeTodaySession, startToday: startTodaySession, + planToday: () => openPlanToday(mobileTaskButtons.work), openFallback: openMobileWorkFallback, }); const mobileTaskDock = createMobileTaskDock({ diff --git a/frontend/mobile-task-dock.js b/frontend/mobile-task-dock.js index d2b84c0..173bf92 100644 --- a/frontend/mobile-task-dock.js +++ b/frontend/mobile-task-dock.js @@ -43,6 +43,7 @@ continue: ['Continue', 'Continue Today'], resume: ['Resume', 'Resume Today'], start: ['Start', 'Start Today'], + plan: ['Plan', 'Plan Today'], work: ['Work', 'Work'], }; const workMode = labels[mode] ? mode : 'work'; diff --git a/frontend/mobile-work-entry.js b/frontend/mobile-work-entry.js index eb4ca45..af4aa47 100644 --- a/frontend/mobile-work-entry.js +++ b/frontend/mobile-work-entry.js @@ -6,6 +6,7 @@ if (options.isTodayActive()) return 'continue'; if (options.getTodayCount() > 0 && options.isTodayResumable()) return 'resume'; if (options.getTodayCount() > 0) return 'start'; + if (options.getEligibleCount() > 0) return 'plan'; return 'work'; } @@ -14,6 +15,7 @@ if (current === 'continue') options.continueToday(); else if (current === 'resume') options.resumeToday(); else if (current === 'start') options.startToday(); + else if (current === 'plan') options.planToday(); else options.openFallback(); return current; } diff --git a/tests/test_mobile_task_dock.py b/tests/test_mobile_task_dock.py index e8046bb..f677fbc 100644 --- a/tests/test_mobile_task_dock.py +++ b/tests/test_mobile_task_dock.py @@ -12,18 +12,20 @@ ENTRY = Path(__file__).resolve().parents[1] / "frontend" / "mobile-work-entry.js TIMER = Path(__file__).resolve().parents[1] / "frontend" / "today-timer.js" -def test_mobile_work_entry_prioritizes_continue_resume_start_then_fallback(): +def test_mobile_work_entry_prioritizes_continue_resume_start_plan_then_fallback(): script = f""" const createEntry = require({json.dumps(str(ENTRY))}); -const state = {{active:true, resumable:true, today:2}}; +const state = {{active:true, resumable:true, today:2, eligible:3}}; const calls = []; const entry = createEntry({{ isTodayActive: () => state.active, isTodayResumable: () => state.resumable, getTodayCount: () => state.today, + getEligibleCount: () => state.eligible, continueToday: () => calls.push('continue'), resumeToday: () => calls.push('resume'), startToday: () => calls.push('start'), + planToday: () => calls.push('plan'), openFallback: () => calls.push('fallback'), }}); const modes = []; @@ -31,6 +33,7 @@ modes.push(entry.open()); state.active = false; modes.push(entry.open()); state.resumable = false; modes.push(entry.open()); state.today = 0; modes.push(entry.open()); +state.eligible = 0; modes.push(entry.open()); process.stdout.write(JSON.stringify({{modes, calls}})); """ result = subprocess.run( @@ -39,8 +42,8 @@ process.stdout.write(JSON.stringify({{modes, calls}})); assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == { - "modes": ["continue", "resume", "start", "work"], - "calls": ["continue", "resume", "start", "fallback"], + "modes": ["continue", "resume", "start", "plan", "work"], + "calls": ["continue", "resume", "start", "plan", "fallback"], } @@ -125,9 +128,11 @@ dock.updateWork('resume', 0); const resuming = {{ text:workLabel.textContent, label:work.attributes['aria-label'] }}; dock.updateWork('start', 0); const starting = {{ text:workLabel.textContent, label:work.attributes['aria-label'] }}; +dock.updateWork('plan', 0); +const planning = {{ text:workLabel.textContent, label:work.attributes['aria-label'] }}; dock.updateWork('work', 0); process.stdout.write(JSON.stringify({{ - continuing, resuming, starting, + continuing, resuming, starting, planning, fallback:{{ text:workLabel.textContent, label:work.attributes['aria-label'] }}, }})); """ @@ -143,6 +148,7 @@ process.stdout.write(JSON.stringify({{ }, "resuming": {"text": "Resume", "label": "Resume Today"}, "starting": {"text": "Start", "label": "Start Today"}, + "planning": {"text": "Plan", "label": "Plan Today"}, "fallback": {"text": "Work", "label": "Work"}, } @@ -244,9 +250,11 @@ async def test_dashboard_wires_mobile_work_dock_into_today_session_lifecycle(): assert "isTodayActive: () => workSession.checkpointed()" in html assert "isTodayResumable: () => workSession.resumable()" in html assert "getTodayCount: () => todayMyWork.length" in html + assert "getEligibleCount: () => activeMyWork.length" in html assert "continueToday: continueTodaySession" in html assert "resumeToday: resumeTodaySession" in html assert "startToday: startTodaySession" in html + assert "planToday: () => openPlanToday(mobileTaskButtons.work)" in html assert "work: () => mobileWorkEntry.open()" in html assert "mobileTaskDock.updateWork(mobileWorkEntry.mode())" in html assert "mobileTaskDock.updateAttention(countMyWork(activeMyWork).attention)" in html