chore(team): checkpoint follow-up frontend edits

This commit is contained in:
777genius 2026-05-08 21:49:29 +03:00
parent 651cfa1846
commit 88b3ea2358
2 changed files with 47 additions and 9 deletions

View file

@ -66,19 +66,36 @@ function normalizeActorKey(value) {
return typeof value === 'string' && value.trim() ? value.trim().toLowerCase() : '';
}
function closeTimestampForInterval(interval, timestamp) {
const startedAtMs = Date.parse(interval.startedAt);
const timestampMs = Date.parse(timestamp);
if (Number.isFinite(startedAtMs) && Number.isFinite(timestampMs) && timestampMs < startedAtMs) {
return interval.startedAt;
}
return timestamp;
}
function openReviewInterval(task, reviewer, timestamp = new Date().toISOString()) {
const reviewerName = typeof reviewer === 'string' && reviewer.trim() ? reviewer.trim() : '';
if (!reviewerName) return false;
const reviewerKey = normalizeActorKey(reviewerName);
const intervals = Array.isArray(task.reviewIntervals) ? [...task.reviewIntervals] : [];
const hasOpenForReviewer = intervals.some(
(interval) => !interval.completedAt && normalizeActorKey(interval.reviewer) === reviewerKey
);
let changed = false;
let hasOpenForReviewer = false;
const nextIntervals = intervals.map((interval) => {
if (interval.completedAt) return interval;
if (normalizeActorKey(interval.reviewer) === reviewerKey) {
hasOpenForReviewer = true;
return interval;
}
changed = true;
return { ...interval, completedAt: closeTimestampForInterval(interval, timestamp) };
});
if (hasOpenForReviewer) {
task.reviewIntervals = intervals;
return false;
task.reviewIntervals = nextIntervals;
return changed;
}
task.reviewIntervals = [...intervals, { reviewer: reviewerName, startedAt: timestamp }];
task.reviewIntervals = [...nextIntervals, { reviewer: reviewerName, startedAt: timestamp }];
return true;
}
@ -88,7 +105,7 @@ function closeReviewIntervals(task, timestamp = new Date().toISOString()) {
task.reviewIntervals = task.reviewIntervals.map((interval) => {
if (interval.completedAt) return interval;
changed = true;
return { ...interval, completedAt: timestamp };
return { ...interval, completedAt: closeTimestampForInterval(interval, timestamp) };
});
return changed;
}
@ -315,7 +332,7 @@ function startReview(context, taskId, flags = {}) {
});
} else {
tasks.updateTask(context, task.id, (t) => {
openReviewInterval(t, existingActor);
openReviewInterval(t, existingActor, latestReviewEvent.timestamp);
return t;
});
}

View file

@ -1,7 +1,9 @@
import { getTasksBasePath } from '@main/utils/pathDecoder';
import { getTasksBasePath, getTeamsBasePath } from '@main/utils/pathDecoder';
import { createLogger } from '@shared/utils/logger';
import * as fs from 'fs';
import * as path from 'path';
import { withFileLockSync } from './fileLock';
import { TeamTaskReader } from './TeamTaskReader';
import type {
@ -20,6 +22,7 @@ type MutableTeamTask = TeamTask & {
};
const CRASH_REPAIR_GRACE_MS = 5_000;
const logger = createLogger('Service:TeamTaskActivityIntervalService');
function normalizeMemberName(value: string | null | undefined): string {
return typeof value === 'string' && value.trim() ? value.trim().toLowerCase() : '';
@ -142,6 +145,23 @@ export class TeamTaskActivityIntervalService {
private mutateTeamTasks(
teamName: string,
mutate: (task: MutableTeamTask) => boolean
): ActivityIntervalResult {
const lockScope = path.join(getTeamsBasePath(), teamName, 'board-state');
try {
return withFileLockSync(lockScope, () => this.mutateTeamTasksUnlocked(teamName, mutate));
} catch (error) {
logger.warn(
`[${teamName}] Failed to update task activity intervals: ${
error instanceof Error ? error.message : String(error)
}`
);
return { changedTasks: 0 };
}
}
private mutateTeamTasksUnlocked(
teamName: string,
mutate: (task: MutableTeamTask) => boolean
): ActivityIntervalResult {
const tasksDir = path.join(getTasksBasePath(), teamName);
let entries: string[];
@ -216,6 +236,7 @@ export class TeamTaskActivityIntervalService {
const activeReviewer = getActiveReviewActor(task);
if (
task.status === 'completed' &&
activeReviewer &&
normalizeMemberName(activeReviewer) === memberKey &&
!hasOpenReviewInterval(task, activeReviewer)