Run the mobile Agenda as a sequential work session #732
|
|
@ -1148,7 +1148,9 @@
|
||||||
}
|
}
|
||||||
function updateWorkSessionActions() {
|
function updateWorkSessionActions() {
|
||||||
const active = workSession.active();
|
const active = workSession.active();
|
||||||
qs('#start-work-session').hidden = active;
|
const startWorkSession = qs('#start-work-session');
|
||||||
|
startWorkSession.hidden = active;
|
||||||
|
startWorkSession.textContent = selectedWorkFilter === 'agenda' ? 'Start Agenda' : 'Start work';
|
||||||
qs('#resume-today-session').hidden = active || !todayMyWork.length || !workSession.resumable();
|
qs('#resume-today-session').hidden = active || !todayMyWork.length || !workSession.resumable();
|
||||||
qs('#end-today-session').hidden = !active;
|
qs('#end-today-session').hidden = !active;
|
||||||
updateDetailDeferLabels(active);
|
updateDetailDeferLabels(active);
|
||||||
|
|
@ -1157,9 +1159,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const workSession = createWorkSession({
|
const workSession = createWorkSession({
|
||||||
getItems: () => selectedWorkFilter === 'today' ? todayMyWork : activeMyWork,
|
getItems: () => selectedWorkFilter === 'today' ? todayMyWork :
|
||||||
getFilter: () => selectedWorkFilter === 'today' ? 'all' : selectedWorkFilter,
|
selectedWorkFilter === 'agenda' ? agendaMyWork(activeMyWork) : activeMyWork,
|
||||||
getMilestone: () => selectedWorkMilestone,
|
getFilter: () => selectedWorkFilter === 'today' ? 'all' :
|
||||||
|
selectedWorkFilter === 'agenda' ? 'all' : selectedWorkFilter,
|
||||||
|
getMilestone: () => selectedWorkFilter === 'agenda' ? 'all' : selectedWorkMilestone,
|
||||||
checkpoint: sessionCheckpoint,
|
checkpoint: sessionCheckpoint,
|
||||||
checkpointEnabled: () => selectedWorkFilter === 'today',
|
checkpointEnabled: () => selectedWorkFilter === 'today',
|
||||||
onOpen: openWorkSessionItem,
|
onOpen: openWorkSessionItem,
|
||||||
|
|
@ -1178,7 +1182,15 @@
|
||||||
button.hidden = !workSession.checkpointed();
|
button.hidden = !workSession.checkpointed();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onFinish: () => todayRecapView.finish(selectedWorkFilter),
|
onFinish: () => {
|
||||||
|
updateWorkSessionActions();
|
||||||
|
if (selectedWorkFilter === 'agenda') {
|
||||||
|
closeOpenWorkSheets();
|
||||||
|
qs('#my-work-action-status').textContent = 'Agenda complete.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
todayRecapView.finish(selectedWorkFilter);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
function selectTodayWork() {
|
function selectTodayWork() {
|
||||||
qs('[data-work-filter="today"]').click();
|
qs('[data-work-filter="today"]').click();
|
||||||
|
|
@ -5868,7 +5880,7 @@
|
||||||
qs('#save-today-plan').addEventListener('click', event => commitPlanToday(false, event.currentTarget));
|
qs('#save-today-plan').addEventListener('click', event => commitPlanToday(false, event.currentTarget));
|
||||||
qs('#save-and-start-today').addEventListener('click', event => commitPlanToday(true, event.currentTarget));
|
qs('#save-and-start-today').addEventListener('click', event => commitPlanToday(true, event.currentTarget));
|
||||||
qs('#start-work-session').addEventListener('click', () => {
|
qs('#start-work-session').addEventListener('click', () => {
|
||||||
const sessionItems = selectedWorkFilter === 'today' ? todayMyWork : filterMyWork(lastMyWork, selectedWorkFilter);
|
const sessionItems = workSession.items();
|
||||||
if (!sessionItems.length) {
|
if (!sessionItems.length) {
|
||||||
qs('#my-work-action-status').textContent = 'No visible work to start.';
|
qs('#my-work-action-status').textContent = 'No visible work to start.';
|
||||||
return;
|
return;
|
||||||
|
|
@ -6053,6 +6065,7 @@
|
||||||
const selectedCount = button.querySelector('[data-work-count]').textContent;
|
const selectedCount = button.querySelector('[data-work-count]').textContent;
|
||||||
qs('#active-work-queue').textContent = button.firstChild.textContent.trim() + ' (' + selectedCount + ')';
|
qs('#active-work-queue').textContent = button.firstChild.textContent.trim() + ' (' + selectedCount + ')';
|
||||||
renderMyWork();
|
renderMyWork();
|
||||||
|
updateWorkSessionActions();
|
||||||
updateWorkPaginationControls();
|
updateWorkPaginationControls();
|
||||||
if (selectedWorkFilter === 'agenda') completeAgendaIssues();
|
if (selectedWorkFilter === 'agenda') completeAgendaIssues();
|
||||||
if (!preserveRoute) workRoute.queue(filter);
|
if (!preserveRoute) workRoute.queue(filter);
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,62 @@ 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_mobile_agenda_runs_as_an_ordered_non_durable_work_session():
|
||||||
|
script = f"""
|
||||||
|
const buildMyWork = require({json.dumps(str(MY_WORK))});
|
||||||
|
const values = new Map([['stackchain.today-session.v1', 'preserve-today']]);
|
||||||
|
const checkpoint = {{
|
||||||
|
read:() => null,
|
||||||
|
save:() => {{ throw new Error('Agenda must not save Today'); }},
|
||||||
|
clear:() => {{ throw new Error('Agenda must not clear Today'); }},
|
||||||
|
}};
|
||||||
|
const active = [
|
||||||
|
{{kind:'issue',repository:'o/r',number:3,title:'Tomorrow',is_assigned:true,due_date:'2026-08-14'}},
|
||||||
|
{{kind:'issue',repository:'o/r',number:1,title:'Overdue',is_assigned:true,due_date:'2026-08-12'}},
|
||||||
|
{{kind:'issue',repository:'o/r',number:2,title:'Today',is_assigned:true,due_date:'2026-08-13'}},
|
||||||
|
{{kind:'issue',repository:'o/r',number:4,title:'No deadline',is_assigned:true}},
|
||||||
|
];
|
||||||
|
const opened = [];
|
||||||
|
let finished = 0;
|
||||||
|
const session = buildMyWork.createWorkSession({{
|
||||||
|
getItems:() => buildMyWork.agendaMyWork(active, new Date('2026-08-13T12:00:00')),
|
||||||
|
getFilter:() => 'all', checkpoint, checkpointEnabled:() => false,
|
||||||
|
onOpen:item => opened.push(item.title), onProgress:() => {{}},
|
||||||
|
onFinish:() => {{ finished += 1; }},
|
||||||
|
}});
|
||||||
|
const started = session.start();
|
||||||
|
session.next();
|
||||||
|
session.next();
|
||||||
|
const completed = session.next();
|
||||||
|
process.stdout.write(JSON.stringify({{
|
||||||
|
started, completed, opened, finished, active:session.active(), today:values.get('stackchain.today-session.v1'),
|
||||||
|
}}));
|
||||||
|
"""
|
||||||
|
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||||
|
|
||||||
|
assert result.returncode == 0, result.stderr
|
||||||
|
assert json.loads(result.stdout) == {
|
||||||
|
"started": True,
|
||||||
|
"completed": False,
|
||||||
|
"opened": ["Overdue", "Today", "Tomorrow"],
|
||||||
|
"finished": 1,
|
||||||
|
"active": False,
|
||||||
|
"today": "preserve-today",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_mobile_agenda_launcher_starts_the_rendered_queue_and_finishes_in_agenda():
|
||||||
|
html = await dashboard()
|
||||||
|
|
||||||
|
assert "selectedWorkFilter === 'agenda' ? agendaMyWork(activeMyWork)" in html
|
||||||
|
assert "selectedWorkFilter === 'agenda' ? 'all'" in html
|
||||||
|
assert "startWorkSession.textContent = selectedWorkFilter === 'agenda' ? 'Start Agenda' : 'Start work'" in html
|
||||||
|
assert "const sessionItems = workSession.items()" in html
|
||||||
|
assert "qs('#my-work-action-status').textContent = 'Agenda complete.'" in html
|
||||||
|
assert "checkpointEnabled: () => selectedWorkFilter === 'today'" in html
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("timezone_name", ["Asia/Tokyo", "America/Los_Angeles"])
|
@pytest.mark.parametrize("timezone_name", ["Asia/Tokyo", "America/Los_Angeles"])
|
||||||
def test_mobile_agenda_preserves_the_gitea_calendar_day_in_every_timezone(timezone_name):
|
def test_mobile_agenda_preserves_the_gitea_calendar_day_in_every_timezone(timezone_name):
|
||||||
script = f"""
|
script = f"""
|
||||||
|
|
@ -1475,7 +1531,7 @@ async def test_mobile_my_work_exposes_touch_safe_milestone_lane_and_issue_editor
|
||||||
assert 'id="save-issue-milestone"' in html
|
assert 'id="save-issue-milestone"' in html
|
||||||
assert 'class="pill milestone-badge"' in html
|
assert 'class="pill milestone-badge"' in html
|
||||||
assert '.work-milestone-filter, .issue-milestone-editor select, .issue-milestone-editor button { min-height:44px;' in html
|
assert '.work-milestone-filter, .issue-milestone-editor select, .issue-milestone-editor button { min-height:44px;' in html
|
||||||
assert 'getMilestone: () => selectedWorkMilestone' in html
|
assert "getMilestone: () => selectedWorkFilter === 'agenda' ? 'all' : selectedWorkMilestone" in html
|
||||||
assert 'buildMyWork.replaceIssueMilestone(' in html
|
assert 'buildMyWork.replaceIssueMilestone(' in html
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -5821,7 +5877,7 @@ async def test_mobile_dashboard_puts_filterable_my_work_before_auxiliary_panels(
|
||||||
assert '<script src="static/my-work.js"></script>' in html
|
assert '<script src="static/my-work.js"></script>' in html
|
||||||
assert "buildMyWork(data)" in html
|
assert "buildMyWork(data)" in html
|
||||||
assert "markMyWorkStale()" in html
|
assert "markMyWorkStale()" in html
|
||||||
assert "filterMyWork(lastMyWork, selectedWorkFilter)" in html
|
assert "const sessionItems = workSession.items()" in html
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
|
|
|
||||||
|
|
@ -344,7 +344,8 @@ async def test_dashboard_runs_the_curated_today_queue_as_a_mobile_work_flow():
|
||||||
assert "const todayWork = createTodayWork({" in html
|
assert "const todayWork = createTodayWork({" in html
|
||||||
assert "getTodayCount: () => todayMyWork.length" in html
|
assert "getTodayCount: () => todayMyWork.length" in html
|
||||||
assert "startToday: startTodaySession" in html
|
assert "startToday: startTodaySession" in html
|
||||||
assert "selectedWorkFilter === 'today' ? todayMyWork : activeMyWork" in html
|
assert "selectedWorkFilter === 'today' ? todayMyWork :" in html
|
||||||
|
assert "selectedWorkFilter === 'agenda' ? agendaMyWork(activeMyWork) : activeMyWork" in html
|
||||||
assert 'data-today-add' in html
|
assert 'data-today-add' in html
|
||||||
assert 'data-today-remove' in html
|
assert 'data-today-remove' in html
|
||||||
assert 'data-today-move="up"' in html
|
assert 'data-today-move="up"' in html
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user