review-store.ts
1import type { ReviewThread, ReviewComment, Review } from '$lib/types/index';
2import { randomUUID } from 'crypto';
3
4const threads = new Map<string, ReviewThread>();
5let reviewStatus: 'pending' | 'approved' | 'changes_requested' = 'pending';
6let reviewSummary: string | undefined;
7const contextFiles = new Set<string>();
8const worktreeApprovals = new Set<string>();
9// jj only: the change currently being viewed (null = working copy @).
10let selectedChangeId: string | null = null;
11
12export function addContextFile(filePath: string): void {
13 contextFiles.add(filePath);
14}
15
16export function removeContextFile(filePath: string): void {
17 contextFiles.delete(filePath);
18}
19
20export function getContextFiles(): string[] {
21 return Array.from(contextFiles);
22}
23
24export function addComment(
25 filePath: string,
26 lineNumber: number,
27 side: 'old' | 'new',
28 body: string,
29 type: 'comment' | 'suggestion' | 'question' = 'comment',
30 threadId?: string,
31 originalLineContent?: string
32): ReviewThread {
33 const comment: ReviewComment = {
34 id: randomUUID(),
35 filePath,
36 lineNumber,
37 side,
38 body,
39 type,
40 createdAt: new Date().toISOString()
41 };
42
43 if (threadId && threads.has(threadId)) {
44 const thread = threads.get(threadId)!;
45 thread.comments.push(comment);
46 return thread;
47 }
48
49 const thread: ReviewThread = {
50 id: randomUUID(),
51 filePath,
52 lineNumber,
53 side,
54 comments: [comment],
55 resolved: false,
56 outdated: false,
57 originalLineContent
58 };
59 threads.set(thread.id, thread);
60 return thread;
61}
62
63export function getThreads(filePath?: string): ReviewThread[] {
64 const all = Array.from(threads.values());
65 if (filePath) return all.filter((t) => t.filePath === filePath);
66 return all;
67}
68
69export function getThread(threadId: string): ReviewThread | undefined {
70 return threads.get(threadId);
71}
72
73export function resolveThread(threadId: string): boolean {
74 const thread = threads.get(threadId);
75 if (!thread) return false;
76 thread.resolved = !thread.resolved;
77 return true;
78}
79
80export function deleteComment(commentId: string): boolean {
81 for (const [threadId, thread] of threads) {
82 const idx = thread.comments.findIndex((c) => c.id === commentId);
83 if (idx !== -1) {
84 thread.comments.splice(idx, 1);
85 if (thread.comments.length === 0) {
86 threads.delete(threadId);
87 }
88 return true;
89 }
90 }
91 return false;
92}
93
94export function deleteThread(threadId: string): boolean {
95 return threads.delete(threadId);
96}
97
98export function getReview(): Review {
99 return {
100 id: 'current',
101 threads: Array.from(threads.values()),
102 status: reviewStatus,
103 createdAt: new Date().toISOString(),
104 summary: reviewSummary
105 };
106}
107
108export function setReviewStatus(status: 'pending' | 'approved' | 'changes_requested'): void {
109 reviewStatus = status;
110}
111
112export function setReviewSummary(summary: string): void {
113 reviewSummary = summary;
114}
115
116export function approveWorktreeFile(filePath: string): void {
117 worktreeApprovals.add(filePath);
118}
119
120export function unapproveWorktreeFile(filePath: string): void {
121 worktreeApprovals.delete(filePath);
122}
123
124export function getWorktreeApprovals(): Set<string> {
125 return worktreeApprovals;
126}
127
128export function clearApprovals(): void {
129 worktreeApprovals.clear();
130}
131
132export function getSelectedChangeId(): string | null {
133 return selectedChangeId;
134}
135
136export function setSelectedChangeId(changeId: string | null): void {
137 selectedChangeId = changeId;
138}
139
140export function clearAll(): void {
141 threads.clear();
142 reviewStatus = 'pending';
143 reviewSummary = undefined;
144 contextFiles.clear();
145 worktreeApprovals.clear();
146 selectedChangeId = null;
147}
148
149export function markOutdated(threadId: string, outdated: boolean): boolean {
150 const thread = threads.get(threadId);
151 if (!thread) return false;
152 thread.outdated = outdated;
153 return true;
154}
155
156export function getStats(): { totalThreads: number; totalComments: number; filesWithComments: number } {
157 const all = Array.from(threads.values());
158 const files = new Set(all.map((t) => t.filePath));
159 return {
160 totalThreads: all.length,
161 totalComments: all.reduce((sum, t) => sum + t.comments.length, 0),
162 filesWithComments: files.size
163 };
164}