mirror of
https://github.com/BradGroux/veritas-kanban.git
synced 2026-10-09 05:07:53 +00:00
fix: per-task timer exclusivity, not global
Multiple tasks can each have running timers simultaneously — this supports multi-agent workflows where different agents track different tasks. Removed: - getRunningTimerTask() method (global scan) - Auto-stop of other tasks' timers in startTimer() - otherRunningTask UI check that hid the Start button Kept: per-task guard (can't start a timer on a task that already has one running).
This commit is contained in:
parent
6d63c94c51
commit
b13ecce67a
3 changed files with 6 additions and 49 deletions
|
|
@ -27,15 +27,8 @@ router.get(
|
|||
router.post(
|
||||
'/:id/time/start',
|
||||
asyncHandler(async (req, res) => {
|
||||
const { task, stoppedTaskId } = await taskService.startTimer(req.params.id as string);
|
||||
|
||||
// Broadcast WS event for the auto-stopped task (if any) so other clients refresh
|
||||
if (stoppedTaskId) {
|
||||
broadcastTaskChange('updated', stoppedTaskId);
|
||||
}
|
||||
// Broadcast WS event for the started task
|
||||
const task = await taskService.startTimer(req.params.id as string);
|
||||
broadcastTaskChange('updated', task.id);
|
||||
|
||||
res.json(task);
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -680,48 +680,21 @@ export class TaskService {
|
|||
|
||||
// ============ Time Tracking Methods ============
|
||||
|
||||
/**
|
||||
* Find the task that currently has a running timer (if any).
|
||||
*/
|
||||
async getRunningTimerTask(): Promise<Task | null> {
|
||||
await this.initCache();
|
||||
for (const task of this.cache.values()) {
|
||||
if (task.timeTracking?.isRunning) {
|
||||
return task;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a timer for a task.
|
||||
* Enforces global exclusivity: only one timer may run at a time.
|
||||
* If another task has a running timer, it is auto-stopped first.
|
||||
* Returns { task, stoppedTaskId? } so the caller can broadcast both.
|
||||
* Per-task exclusivity: only one timer per task (but multiple tasks can
|
||||
* each have their own running timer — supports multi-agent workflows).
|
||||
*/
|
||||
async startTimer(taskId: string): Promise<{ task: Task; stoppedTaskId?: string }> {
|
||||
async startTimer(taskId: string): Promise<Task> {
|
||||
const task = await this.getTask(taskId);
|
||||
if (!task) {
|
||||
throw new NotFoundError('Task not found');
|
||||
}
|
||||
|
||||
// Check if timer is already running on THIS task
|
||||
if (task.timeTracking?.isRunning) {
|
||||
throw new ConflictError('Timer is already running for this task');
|
||||
}
|
||||
|
||||
// Global exclusivity: stop any other running timer first
|
||||
let stoppedTaskId: string | undefined;
|
||||
const runningTask = await this.getRunningTimerTask();
|
||||
if (runningTask && runningTask.id !== taskId) {
|
||||
await this.stopTimer(runningTask.id);
|
||||
stoppedTaskId = runningTask.id;
|
||||
log.info(
|
||||
{ stoppedTaskId, startedTaskId: taskId },
|
||||
'Auto-stopped timer for global exclusivity'
|
||||
);
|
||||
}
|
||||
|
||||
const entryId = `time_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
||||
const now = new Date().toISOString();
|
||||
|
||||
|
|
@ -737,8 +710,7 @@ export class TaskService {
|
|||
activeEntryId: entryId,
|
||||
};
|
||||
|
||||
const updated = (await this.updateTask(taskId, { timeTracking })) as Task;
|
||||
return { task: updated, stoppedTaskId };
|
||||
return (await this.updateTask(taskId, { timeTracking })) as Task;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -103,9 +103,6 @@ export function TimeTrackingSection({ task }: TimeTrackingSectionProps) {
|
|||
const entries = timeTracking?.entries ?? [];
|
||||
const activeEntry = entries.find((e) => e.id === timeTracking?.activeEntryId);
|
||||
|
||||
// Global exclusivity: is another task's timer running?
|
||||
const otherRunningTask = allTasks?.find((t) => t.id !== task.id && t.timeTracking?.isRunning);
|
||||
|
||||
// ── Handlers ──
|
||||
|
||||
const handleStartStop = async () => {
|
||||
|
|
@ -218,13 +215,8 @@ export function TimeTrackingSection({ task }: TimeTrackingSectionProps) {
|
|||
</>
|
||||
)}
|
||||
</Button>
|
||||
) : otherRunningTask ? (
|
||||
/* Another task has the active timer — Start is blocked */
|
||||
<span className="text-xs text-muted-foreground italic">
|
||||
Timer active on another task
|
||||
</span>
|
||||
) : (
|
||||
/* No timer running anywhere — show Start */
|
||||
/* Timer not running on this task — show Start */
|
||||
<Button variant="default" size="sm" onClick={handleStartStop} disabled={busy}>
|
||||
{busy ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue