feat(FN-3277): add review tab reviewState persistence and enriched UI state
This merge completes a review tab feature with persistent `reviewState` — adding the core types, TaskReviewTab UI enhancements (enriched states and interactions), GitHub integration routes for refresh/address flows, and full regression coverage for both PR and non-PR modes. The architecture docs wer Fusion-Task-Id: FN-3277
This commit is contained in:
@@ -753,6 +753,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
return deduped.length > 0 ? deduped : undefined;
|
||||
})(),
|
||||
review: fromJson<import("./types.js").TaskReview>(row.review) ?? undefined,
|
||||
reviewState: fromJson<import("./types.js").TaskReviewState>(row.review) ?? undefined,
|
||||
workflowStepResults: (() => { const w = fromJson<import("./types.js").WorkflowStepResult[]>(row.workflowStepResults); return w && w.length > 0 ? w : undefined; })(),
|
||||
prInfo: fromJson<import("./types.js").PrInfo>(row.prInfo),
|
||||
issueInfo: fromJson<import("./types.js").IssueInfo>(row.issueInfo),
|
||||
@@ -940,6 +941,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
attachments: task.attachments,
|
||||
comments: task.comments,
|
||||
review: task.review,
|
||||
reviewState: task.reviewState,
|
||||
prompt,
|
||||
...agentLogFields,
|
||||
log: [{ timestamp: archivedAt, action: "Task archived" }],
|
||||
@@ -1254,7 +1256,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
toJson(task.attachments || []),
|
||||
toJson(task.steeringComments || []),
|
||||
toJson(task.comments || []),
|
||||
toJsonNullable(task.review),
|
||||
toJsonNullable(task.reviewState ?? task.review),
|
||||
toJson(task.workflowStepResults || []),
|
||||
toJsonNullable(task.prInfo),
|
||||
toJsonNullable(task.issueInfo),
|
||||
@@ -3188,7 +3190,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
|
||||
async updateTask(
|
||||
id: string,
|
||||
updates: { title?: string; description?: string; priority?: TaskPriority | null; prompt?: string; worktree?: string | null; status?: string | null; dependencies?: string[]; steps?: import("./types.js").TaskStep[]; currentStep?: number; blockedBy?: string | null; assignedAgentId?: string | null; pausedByAgentId?: string | null; assigneeUserId?: string | null; nodeId?: string | null; effectiveNodeId?: string | null; effectiveNodeSource?: string | null; checkedOutBy?: string | null; checkedOutAt?: string | null; paused?: boolean; baseBranch?: string | null; branch?: string | null; executionStartBranch?: string | null; baseCommitSha?: string | null; size?: "S" | "M" | "L"; reviewLevel?: number; executionMode?: import("./types.js").ExecutionMode | null; mergeRetries?: number; workflowStepRetries?: number; stuckKillCount?: number | null; postReviewFixCount?: number | null; recoveryRetryCount?: number | null; taskDoneRetryCount?: number | null; verificationFailureCount?: number | null; mergeConflictBounceCount?: number | null; nextRecoveryAt?: string | null; enabledWorkflowSteps?: string[]; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; planningModelProvider?: string | null; planningModelId?: string | null; thinkingLevel?: string | null; error?: string | null; summary?: string | null; sessionFile?: string | null; executionStartedAt?: string | null; executionCompletedAt?: string | null; review?: import("./types.js").TaskReview | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; mergeDetails?: import("./types.js").MergeDetails | null; sourceIssue?: import("./types.js").TaskSourceIssue | null; tokenUsage?: import("./types.js").TaskTokenUsage | null; modifiedFiles?: string[] | null; missionId?: string | null; sliceId?: string | null },
|
||||
updates: { title?: string; description?: string; priority?: TaskPriority | null; prompt?: string; worktree?: string | null; status?: string | null; dependencies?: string[]; steps?: import("./types.js").TaskStep[]; currentStep?: number; blockedBy?: string | null; assignedAgentId?: string | null; pausedByAgentId?: string | null; assigneeUserId?: string | null; nodeId?: string | null; effectiveNodeId?: string | null; effectiveNodeSource?: string | null; checkedOutBy?: string | null; checkedOutAt?: string | null; paused?: boolean; baseBranch?: string | null; branch?: string | null; executionStartBranch?: string | null; baseCommitSha?: string | null; size?: "S" | "M" | "L"; reviewLevel?: number; executionMode?: import("./types.js").ExecutionMode | null; mergeRetries?: number; workflowStepRetries?: number; stuckKillCount?: number | null; postReviewFixCount?: number | null; recoveryRetryCount?: number | null; taskDoneRetryCount?: number | null; verificationFailureCount?: number | null; mergeConflictBounceCount?: number | null; nextRecoveryAt?: string | null; enabledWorkflowSteps?: string[]; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; planningModelProvider?: string | null; planningModelId?: string | null; thinkingLevel?: string | null; error?: string | null; summary?: string | null; sessionFile?: string | null; executionStartedAt?: string | null; executionCompletedAt?: string | null; review?: import("./types.js").TaskReview | null; reviewState?: import("./types.js").TaskReviewState | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; mergeDetails?: import("./types.js").MergeDetails | null; sourceIssue?: import("./types.js").TaskSourceIssue | null; tokenUsage?: import("./types.js").TaskTokenUsage | null; modifiedFiles?: string[] | null; missionId?: string | null; sliceId?: string | null },
|
||||
runContext?: RunMutationContext,
|
||||
): Promise<Task> {
|
||||
return this.withTaskLock(id, async () => {
|
||||
@@ -3463,6 +3465,11 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
} else if (updates.review !== undefined) {
|
||||
task.review = updates.review;
|
||||
}
|
||||
if (updates.reviewState === null) {
|
||||
task.reviewState = undefined;
|
||||
} else if (updates.reviewState !== undefined) {
|
||||
task.reviewState = updates.reviewState;
|
||||
}
|
||||
if (updates.workflowStepResults === null) {
|
||||
task.workflowStepResults = undefined;
|
||||
} else if (updates.workflowStepResults !== undefined) {
|
||||
|
||||
@@ -730,6 +730,74 @@ export interface TaskReview {
|
||||
items: TaskReviewItem[];
|
||||
}
|
||||
|
||||
export type PrCheckState =
|
||||
| "success"
|
||||
| "pending"
|
||||
| "failure"
|
||||
| "cancelled"
|
||||
| "timed_out"
|
||||
| "action_required"
|
||||
| "neutral"
|
||||
| "skipped"
|
||||
| "stale"
|
||||
| "startup_failure";
|
||||
|
||||
export interface PrCheckStatus {
|
||||
name: string;
|
||||
required: boolean;
|
||||
state: PrCheckState;
|
||||
}
|
||||
|
||||
export interface TaskReviewAuthor {
|
||||
login: string;
|
||||
}
|
||||
|
||||
export interface PrTaskReviewSummaryReviewer {
|
||||
login: string;
|
||||
state: "APPROVED" | "CHANGES_REQUESTED" | "COMMENTED" | "PENDING";
|
||||
submittedAt?: string;
|
||||
}
|
||||
|
||||
export interface PrTaskReviewSummary {
|
||||
reviewDecision: "APPROVED" | "CHANGES_REQUESTED" | "REVIEW_REQUIRED" | null;
|
||||
reviewers: PrTaskReviewSummaryReviewer[];
|
||||
blockingReasons: string[];
|
||||
checks: PrCheckStatus[];
|
||||
}
|
||||
|
||||
export interface TaskReviewStateItem {
|
||||
id: string;
|
||||
threadId?: string;
|
||||
githubCommentId?: number;
|
||||
path?: string;
|
||||
diffSide?: string;
|
||||
body: string;
|
||||
author: TaskReviewAuthor;
|
||||
createdAt: string;
|
||||
updatedAt?: string;
|
||||
state?: string;
|
||||
htmlUrl?: string;
|
||||
isResolved?: boolean;
|
||||
}
|
||||
|
||||
export interface ReviewAddressingRecord {
|
||||
itemId: string;
|
||||
status: "queued" | "in-progress" | "addressed" | "failed";
|
||||
selectedAt: string;
|
||||
startedAt?: string;
|
||||
completedAt?: string;
|
||||
error?: string;
|
||||
stale?: boolean;
|
||||
}
|
||||
|
||||
export interface TaskReviewState {
|
||||
source: "pull-request" | "reviewer-agent";
|
||||
lastRefreshedAt?: string;
|
||||
summary?: PrTaskReviewSummary;
|
||||
items: TaskReviewStateItem[];
|
||||
addressing: ReviewAddressingRecord[];
|
||||
}
|
||||
|
||||
export interface TaskDocument {
|
||||
/** UUID primary key */
|
||||
id: string;
|
||||
@@ -949,8 +1017,10 @@ export interface Task {
|
||||
attachments?: TaskAttachment[];
|
||||
steeringComments?: SteeringComment[];
|
||||
comments?: TaskComment[];
|
||||
/** Structured review metadata shown in the Review tab. */
|
||||
/** Structured review metadata shown in the Review tab (legacy contract). */
|
||||
review?: TaskReview;
|
||||
/** Structured review metadata shown in the Review tab (canonical contract). */
|
||||
reviewState?: TaskReviewState;
|
||||
/** PR information for tasks linked to GitHub pull requests */
|
||||
prInfo?: PrInfo;
|
||||
mergeDetails?: MergeDetails;
|
||||
@@ -2372,8 +2442,10 @@ export interface ArchivedTaskEntry {
|
||||
attachments?: TaskAttachment[];
|
||||
/** User and agent comments remain searchable in the archive DB. */
|
||||
comments?: TaskComment[];
|
||||
/** Structured review metadata shown in the Review tab. */
|
||||
/** Structured review metadata shown in the Review tab (legacy contract). */
|
||||
review?: TaskReview;
|
||||
/** Structured review metadata shown in the Review tab (canonical contract). */
|
||||
reviewState?: TaskReviewState;
|
||||
/** Reconstructed prompt content at archive time, without attachment blobs. */
|
||||
prompt?: string;
|
||||
/** Agent log retention mode used when this archive entry was written. */
|
||||
|
||||
Reference in New Issue
Block a user