feat: run Agenda as a work session (Closes #731)
This commit is contained in:
parent
af27bb06e0
commit
523968f5a3
|
|
@ -1148,7 +1148,9 @@
|
|||
}
|
||||
function updateWorkSessionActions() {
|
||||
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('#end-today-session').hidden = !active;
|
||||
updateDetailDeferLabels(active);
|
||||
|
|
@ -1157,9 +1159,11 @@
|
|||
}
|
||||
|
||||
const workSession = createWorkSession({
|
||||
getItems: () => selectedWorkFilter === 'today' ? todayMyWork : activeMyWork,
|
||||
getFilter: () => selectedWorkFilter === 'today' ? 'all' : selectedWorkFilter,
|
||||
getMilestone: () => selectedWorkMilestone,
|
||||
getItems: () => selectedWorkFilter === 'today' ? todayMyWork :
|
||||
selectedWorkFilter === 'agenda' ? agendaMyWork(activeMyWork) : activeMyWork,
|
||||
getFilter: () => selectedWorkFilter === 'today' ? 'all' :
|
||||
selectedWorkFilter === 'agenda' ? 'all' : selectedWorkFilter,
|
||||
getMilestone: () => selectedWorkFilter === 'agenda' ? 'all' : selectedWorkMilestone,
|
||||
checkpoint: sessionCheckpoint,
|
||||
checkpointEnabled: () => selectedWorkFilter === 'today',
|
||||
onOpen: openWorkSessionItem,
|
||||
|
|
@ -1178,7 +1182,15 @@
|
|||
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() {
|
||||
qs('[data-work-filter="today"]').click();
|
||||
|
|
@ -5868,7 +5880,7 @@
|
|||
qs('#save-today-plan').addEventListener('click', event => commitPlanToday(false, event.currentTarget));
|
||||
qs('#save-and-start-today').addEventListener('click', event => commitPlanToday(true, event.currentTarget));
|
||||
qs('#start-work-session').addEventListener('click', () => {
|
||||
const sessionItems = selectedWorkFilter === 'today' ? todayMyWork : filterMyWork(lastMyWork, selectedWorkFilter);
|
||||
const sessionItems = workSession.items();
|
||||
if (!sessionItems.length) {
|
||||
qs('#my-work-action-status').textContent = 'No visible work to start.';
|
||||
return;
|
||||
|
|
@ -6053,6 +6065,7 @@
|
|||
const selectedCount = button.querySelector('[data-work-count]').textContent;
|
||||
qs('#active-work-queue').textContent = button.firstChild.textContent.trim() + ' (' + selectedCount + ')';
|
||||
renderMyWork();
|
||||
updateWorkSessionActions();
|
||||
updateWorkPaginationControls();
|
||||
if (selectedWorkFilter === 'agenda') completeAgendaIssues();
|
||||
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"]
|
||||
|
||||
|
||||
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"])
|
||||
def test_mobile_agenda_preserves_the_gitea_calendar_day_in_every_timezone(timezone_name):
|
||||
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 'class="pill milestone-badge"' 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
|
||||
|
||||
|
||||
|
|
@ -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 "buildMyWork(data)" in html
|
||||
assert "markMyWorkStale()" in html
|
||||
assert "filterMyWork(lastMyWork, selectedWorkFilter)" in html
|
||||
assert "const sessionItems = workSession.items()" in html
|
||||
|
||||
|
||||
@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 "getTodayCount: () => todayMyWork.length" 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-remove' in html
|
||||
assert 'data-today-move="up"' in html
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user