fix(tasks): preserve schedule when moving between backlog and regular list (#8603)

Moving a task between the backlog and the regular list now only changes its
list position. Previously, moving to the regular list force-scheduled the task
for today (planTasksForToday) and moving to the backlog cleared a schedule that
fell on today, so the task's due date silently changed.

Fixes #8592
This commit is contained in:
John Costa 2026-06-26 03:16:07 -07:00 committed by GitHub
parent 13b23c09dc
commit 9e23832879
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 59 additions and 14 deletions

View file

@ -18,6 +18,8 @@ import { DateAdapter } from '@angular/material/core';
import { of } from 'rxjs';
import { selectTaskByIdWithSubTaskData } from '../../store/task.selectors';
import { addSubTask } from '../../store/task.actions';
import { TaskSharedActions } from '../../../../root-store/meta/task-shared.actions';
import { DateService } from '../../../../core/date/date.service';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { AddSubtaskInputService } from '../../add-subtask-input/add-subtask-input.service';
import { Project } from '../../../project/project.model';
@ -89,6 +91,8 @@ describe('TaskContextMenuInnerComponent', () => {
projectInTreeOrder('project-a', 'Project A'),
]),
getByIdOnce$: () => of({}),
moveTaskToTodayList: () => {},
moveTaskToBacklog: () => {},
},
},
{
@ -339,4 +343,51 @@ describe('TaskContextMenuInnerComponent', () => {
expect(addSubtaskInputService.requestOpen).toHaveBeenCalledWith('PARENT_ID');
});
});
// Moving a task between the backlog and the regular list is a list-position
// change only; it must not touch the task's schedule (issue #8592).
describe('moveToToday() / moveToBacklog() schedule preservation (#8592)', () => {
let projectService: ProjectService;
beforeEach(() => {
projectService = TestBed.inject(ProjectService);
});
it('moveToToday() moves to the regular list without scheduling for today', () => {
const moveSpy = spyOn(projectService, 'moveTaskToTodayList');
const dispatchSpy = spyOn(store, 'dispatch');
component.task = {
...DEFAULT_TASK,
id: 'task-1',
projectId: 'project-current',
} as Task;
component.moveToToday();
expect(moveSpy).toHaveBeenCalledWith('task-1', 'project-current');
const dispatchedTypes = dispatchSpy.calls
.allArgs()
.map((args) => (args[0] as unknown as { type: string }).type);
expect(dispatchedTypes).not.toContain(TaskSharedActions.planTasksForToday.type);
});
it('moveToBacklog() moves to the backlog without clearing a schedule set for today', () => {
const moveSpy = spyOn(projectService, 'moveTaskToBacklog');
const dispatchSpy = spyOn(store, 'dispatch');
component.task = {
...DEFAULT_TASK,
id: 'task-1',
projectId: 'project-current',
dueDay: TestBed.inject(DateService).todayStr(),
} as Task;
component.moveToBacklog();
expect(moveSpy).toHaveBeenCalledWith('task-1', 'project-current');
const dispatchedTypes = dispatchSpy.calls
.allArgs()
.map((args) => (args[0] as unknown as { type: string }).type);
expect(dispatchedTypes).not.toContain(TaskSharedActions.unscheduleTask.type);
});
});
});

View file

@ -674,20 +674,17 @@ export class TaskContextMenuInnerComponent implements AfterViewInit, OnDestroy {
moveToBacklog(): void {
if (this.task.projectId && !this.task.parentId) {
// Moving to the backlog is a list-position change only; it must not
// alter the task's schedule (#8592).
this._projectService.moveTaskToBacklog(this.task.id, this.task.projectId);
if (
this.task.dueDay === this._dateService.todayStr() ||
(this.task.dueWithTime && this._dateService.isToday(this.task.dueWithTime))
) {
this.unschedule();
}
}
}
moveToToday(): void {
if (this.task.projectId && !this.task.parentId) {
// Moving to the regular list is a list-position change only; it must not
// schedule the task for today (#8592).
this._projectService.moveTaskToTodayList(this.task.id, this.task.projectId);
this.addToMyDay();
}
}

View file

@ -230,9 +230,6 @@ export class TaskComponent implements OnDestroy, AfterViewInit {
return 'chat';
});
isTaskOnTodayList = computed(() =>
this._taskService.todayListSet().has(this.task().id),
);
isTodayListActive = computed(() => this.workContextService.isTodayList);
taskIdWithPrefix = computed(() => 't-' + this.task().id);
isRepeatTaskCreatedToday = computed(
@ -1335,18 +1332,18 @@ export class TaskComponent implements OnDestroy, AfterViewInit {
moveToBacklog(): void {
const t = this.task();
if (t.projectId && !t.parentId) {
// Moving to the backlog is a list-position change only; it must not
// alter the task's schedule (#8592).
this._projectService.moveTaskToBacklog(t.id, t.projectId);
if (this.isTaskOnTodayList()) {
this.unschedule();
}
}
}
moveToToday(): void {
const t = this.task();
if (t.projectId) {
// Moving to the regular list is a list-position change only; it must not
// schedule the task for today (#8592).
this._projectService.moveTaskToTodayList(t.id, t.projectId);
this.addToMyDay();
}
}