@spader/spall-tui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/App.js +3636 -0
  2. package/dist/App.js.map +39 -0
  3. package/dist/bash-nbhfht5r.scm +56 -0
  4. package/dist/c-4zy7d3fw.scm +81 -0
  5. package/dist/c_sharp-tb7n62t2.scm +212 -0
  6. package/dist/cpp-stxr9ffp.scm +77 -0
  7. package/dist/css-b1p1h6ys.scm +76 -0
  8. package/dist/dart-t89b32ej.scm +13 -0
  9. package/dist/elisp-ax9wx1b9.scm +72 -0
  10. package/dist/elixir-77300bss.scm +223 -0
  11. package/dist/elm-k2q5knqb.scm +76 -0
  12. package/dist/embedded_template-k7ypjkba.scm +12 -0
  13. package/dist/go-9wx8m64k.scm +123 -0
  14. package/dist/html-g95aq3vw.scm +13 -0
  15. package/dist/index.js +3635 -0
  16. package/dist/index.js.map +39 -0
  17. package/dist/java-wasz6t7a.scm +149 -0
  18. package/dist/javascript-cswtymww.scm +204 -0
  19. package/dist/json-pf4aw8w1.scm +16 -0
  20. package/dist/kotlin-m6cj2y7m.scm +380 -0
  21. package/dist/lib/git.js +117 -0
  22. package/dist/lib/git.js.map +10 -0
  23. package/dist/lua-7h6jk3pd.scm +204 -0
  24. package/dist/objc-6yjdwy1z.scm +216 -0
  25. package/dist/ocaml-jfj520qk.scm +148 -0
  26. package/dist/parsers/index.d.ts +8 -0
  27. package/dist/parsers/index.d.ts.map +1 -0
  28. package/dist/php-bds7v5h1.scm +203 -0
  29. package/dist/python-k2cbtg72.scm +137 -0
  30. package/dist/ql-qs2vxbeq.scm +154 -0
  31. package/dist/ruby-3ybh5bp2.scm +154 -0
  32. package/dist/rust-edtnatpk.scm +161 -0
  33. package/dist/scala-91f2sb1z.scm +260 -0
  34. package/dist/src/App.d.ts +6 -0
  35. package/dist/src/App.d.ts.map +1 -0
  36. package/dist/src/Review.d.ts +6 -0
  37. package/dist/src/Review.d.ts.map +1 -0
  38. package/dist/src/components/CommandPalette.d.ts +2 -0
  39. package/dist/src/components/CommandPalette.d.ts.map +1 -0
  40. package/dist/src/components/DiffPanel.d.ts +16 -0
  41. package/dist/src/components/DiffPanel.d.ts.map +1 -0
  42. package/dist/src/components/EditorPanel.d.ts +9 -0
  43. package/dist/src/components/EditorPanel.d.ts.map +1 -0
  44. package/dist/src/components/ErrorFallback.d.ts +6 -0
  45. package/dist/src/components/ErrorFallback.d.ts.map +1 -0
  46. package/dist/src/components/HalfLineShadow.d.ts +18 -0
  47. package/dist/src/components/HalfLineShadow.d.ts.map +1 -0
  48. package/dist/src/components/index.d.ts +7 -0
  49. package/dist/src/components/index.d.ts.map +1 -0
  50. package/dist/src/components/sidebar/CommentList.d.ts +10 -0
  51. package/dist/src/components/sidebar/CommentList.d.ts.map +1 -0
  52. package/dist/src/components/sidebar/FileList.d.ts +13 -0
  53. package/dist/src/components/sidebar/FileList.d.ts.map +1 -0
  54. package/dist/src/components/sidebar/PatchList.d.ts +14 -0
  55. package/dist/src/components/sidebar/PatchList.d.ts.map +1 -0
  56. package/dist/src/components/sidebar/ProjectStatus.d.ts +7 -0
  57. package/dist/src/components/sidebar/ProjectStatus.d.ts.map +1 -0
  58. package/dist/src/components/sidebar/Section.d.ts +12 -0
  59. package/dist/src/components/sidebar/Section.d.ts.map +1 -0
  60. package/dist/src/components/sidebar/ServerStatus.d.ts +2 -0
  61. package/dist/src/components/sidebar/ServerStatus.d.ts.map +1 -0
  62. package/dist/src/components/sidebar/index.d.ts +7 -0
  63. package/dist/src/components/sidebar/index.d.ts.map +1 -0
  64. package/dist/src/context/command.d.ts +10 -0
  65. package/dist/src/context/command.d.ts.map +1 -0
  66. package/dist/src/context/dialog.d.ts +11 -0
  67. package/dist/src/context/dialog.d.ts.map +1 -0
  68. package/dist/src/context/exit.d.ts +10 -0
  69. package/dist/src/context/exit.d.ts.map +1 -0
  70. package/dist/src/context/review.d.ts +47 -0
  71. package/dist/src/context/review.d.ts.map +1 -0
  72. package/dist/src/context/server.d.ts +10 -0
  73. package/dist/src/context/server.d.ts.map +1 -0
  74. package/dist/src/context/sidebar.d.ts +13 -0
  75. package/dist/src/context/sidebar.d.ts.map +1 -0
  76. package/dist/src/context/theme.d.ts +47 -0
  77. package/dist/src/context/theme.d.ts.map +1 -0
  78. package/dist/src/index.d.ts +3 -0
  79. package/dist/src/index.d.ts.map +1 -0
  80. package/dist/src/lib/diff.d.ts +24 -0
  81. package/dist/src/lib/diff.d.ts.map +1 -0
  82. package/dist/src/lib/git.d.ts +15 -0
  83. package/dist/src/lib/git.d.ts.map +1 -0
  84. package/dist/src/lib/keybind.d.ts +23 -0
  85. package/dist/src/lib/keybind.d.ts.map +1 -0
  86. package/dist/src/lib/tree.d.ts +39 -0
  87. package/dist/src/lib/tree.d.ts.map +1 -0
  88. package/dist/src/store/comment.d.ts +20 -0
  89. package/dist/src/store/comment.d.ts.map +1 -0
  90. package/dist/src/store/db.d.ts +6 -0
  91. package/dist/src/store/db.d.ts.map +1 -0
  92. package/dist/src/store/index.d.ts +7 -0
  93. package/dist/src/store/index.d.ts.map +1 -0
  94. package/dist/src/store/patch.d.ts +22 -0
  95. package/dist/src/store/patch.d.ts.map +1 -0
  96. package/dist/src/store/repo.d.ts +8 -0
  97. package/dist/src/store/repo.d.ts.map +1 -0
  98. package/dist/src/store/review.d.ts +18 -0
  99. package/dist/src/store/review.d.ts.map +1 -0
  100. package/dist/store/index.js +303 -0
  101. package/dist/store/index.js.map +14 -0
  102. package/dist/swift-a4tt704b.scm +336 -0
  103. package/dist/toml-hcqfz0bg.scm +53 -0
  104. package/dist/tsx-c3k3mmc9.scm +239 -0
  105. package/dist/typescript-hz4pg6ze.scm +239 -0
  106. package/dist/vue-xbeea1aj.scm +9 -0
  107. package/dist/yaml-mbfb25g0.scm +79 -0
  108. package/dist/zig-ds3q8m26.scm +283 -0
  109. package/package.json +74 -0
package/dist/index.js ADDED
@@ -0,0 +1,3635 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+
17
+ // src/lib/git.ts
18
+ var {$ } = globalThis.Bun;
19
+ var Git;
20
+ ((Git) => {
21
+ async function root(startPath) {
22
+ try {
23
+ const result = await $`git -C ${startPath} rev-parse --show-toplevel`.quiet();
24
+ return result.stdout.toString().trim() || null;
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+ Git.root = root;
30
+ async function head(repoPath) {
31
+ try {
32
+ const result = await $`git -C ${repoPath} rev-parse HEAD`.quiet();
33
+ return result.stdout.toString().trim() || null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+ Git.head = head;
39
+ async function diff(repoPath) {
40
+ const e = await entries(repoPath);
41
+ return e.map((x) => x.content).join(`
42
+ `);
43
+ }
44
+ Git.diff = diff;
45
+ async function hash(repoPath) {
46
+ const trackedResult = await $`git -C ${repoPath} diff HEAD --name-only --relative`.quiet();
47
+ const untrackedResult = await $`git -C ${repoPath} ls-files --others --exclude-standard`.quiet();
48
+ const combined = trackedResult.stdout.toString() + "\x00" + untrackedResult.stdout.toString();
49
+ return Number(Bun.hash(combined));
50
+ }
51
+ Git.hash = hash;
52
+ async function entries(repoPath) {
53
+ const context = 0;
54
+ const result = [];
55
+ const filesResult = await $`git -C ${repoPath} diff -U${context} HEAD --name-only --relative`.quiet();
56
+ const filesOutput = filesResult.stdout.toString().trim();
57
+ const trackedFiles = filesOutput ? filesOutput.split(`
58
+ `).filter(Boolean) : [];
59
+ const deletedResult = await $`git -C ${repoPath} diff -U${context} HEAD --name-only --diff-filter=D --relative`.quiet();
60
+ const deletedOutput = deletedResult.stdout.toString().trim();
61
+ const deletedFiles = new Set(deletedOutput ? deletedOutput.split(`
62
+ `).filter(Boolean) : []);
63
+ for (const file of trackedFiles) {
64
+ const diffResult = await $`git -C ${repoPath} diff -U${context} HEAD -- ${file}`.quiet();
65
+ const content = diffResult.stdout.toString();
66
+ const isDeleted = deletedFiles.has(file);
67
+ result.push({ file, content, isNew: false, isDeleted });
68
+ }
69
+ const untrackedResult = await $`git -C ${repoPath} ls-files --others --exclude-standard`.quiet();
70
+ const untrackedOutput = untrackedResult.stdout.toString().trim();
71
+ const untrackedFiles = untrackedOutput ? untrackedOutput.split(`
72
+ `).filter(Boolean) : [];
73
+ for (const file of untrackedFiles) {
74
+ const fileContent = await Bun.file(`${repoPath}/${file}`).text();
75
+ const lines2 = fileContent.split(`
76
+ `);
77
+ const diffLines = lines2.map((line) => `+${line}`);
78
+ const content = `diff --git a/${file} b/${file}
79
+ new file mode 100644
80
+ --- /dev/null
81
+ +++ b/${file}
82
+ @@ -0,0 +1,${lines2.length} @@
83
+ ${diffLines.join(`
84
+ `)}`;
85
+ result.push({ file, content, isNew: true, isDeleted: false });
86
+ }
87
+ return result;
88
+ }
89
+ Git.entries = entries;
90
+ function lines(diffContent) {
91
+ if (!diffContent)
92
+ return 0;
93
+ const allLines = diffContent.split(`
94
+ `);
95
+ let count = 0;
96
+ let inHunk = false;
97
+ for (const line of allLines) {
98
+ if (line.startsWith("@@")) {
99
+ inHunk = true;
100
+ continue;
101
+ }
102
+ if (!inHunk)
103
+ continue;
104
+ const firstChar = line[0];
105
+ if (firstChar === " " || firstChar === "+" || firstChar === "-") {
106
+ count++;
107
+ }
108
+ }
109
+ return count;
110
+ }
111
+ Git.lines = lines;
112
+ })(Git ||= {});
113
+
114
+ // src/store/db.ts
115
+ var exports_db = {};
116
+ __export(exports_db, {
117
+ path: () => path,
118
+ init: () => init,
119
+ get: () => get,
120
+ close: () => close
121
+ });
122
+ import { Database } from "bun:sqlite";
123
+ import { mkdirSync, existsSync } from "fs";
124
+ import { dirname, join } from "path";
125
+ import { Config } from "@spader/spall-core/config";
126
+ var DB_PATH = join(Config.get().dirs.data, "tui.db");
127
+ var CREATE_REPOS_TABLE = `
128
+ CREATE TABLE IF NOT EXISTS repos (
129
+ id INTEGER PRIMARY KEY,
130
+ path TEXT NOT NULL UNIQUE
131
+ )
132
+ `;
133
+ var CREATE_REVIEWS_TABLE = `
134
+ CREATE TABLE IF NOT EXISTS reviews (
135
+ id INTEGER PRIMARY KEY,
136
+ repo INTEGER NOT NULL,
137
+ commit_sha TEXT NOT NULL,
138
+ name TEXT,
139
+ created_at INTEGER NOT NULL,
140
+ FOREIGN KEY (repo) REFERENCES repos(id)
141
+ )
142
+ `;
143
+ var CREATE_PATCHES_TABLE = `
144
+ CREATE TABLE IF NOT EXISTS patches (
145
+ id INTEGER PRIMARY KEY,
146
+ review INTEGER NOT NULL,
147
+ seq INTEGER NOT NULL,
148
+ hash TEXT NOT NULL,
149
+ content TEXT NOT NULL,
150
+ created_at INTEGER NOT NULL,
151
+ FOREIGN KEY (review) REFERENCES reviews(id),
152
+ UNIQUE (review, seq)
153
+ )
154
+ `;
155
+ var CREATE_REVIEW_COMMENTS_TABLE = `
156
+ CREATE TABLE IF NOT EXISTS review_comments (
157
+ id INTEGER PRIMARY KEY,
158
+ review INTEGER NOT NULL,
159
+ note_id INTEGER NOT NULL,
160
+ file TEXT NOT NULL,
161
+ patch_id INTEGER NOT NULL,
162
+ start_row INTEGER NOT NULL,
163
+ end_row INTEGER NOT NULL,
164
+ created_at INTEGER NOT NULL,
165
+ FOREIGN KEY (review) REFERENCES reviews(id)
166
+ )
167
+ `;
168
+ var db = null;
169
+ function path() {
170
+ return DB_PATH;
171
+ }
172
+ function init() {
173
+ if (db)
174
+ return db;
175
+ const dir = dirname(DB_PATH);
176
+ if (!existsSync(dir)) {
177
+ mkdirSync(dir, { recursive: true });
178
+ }
179
+ db = new Database(DB_PATH);
180
+ db.exec(CREATE_REPOS_TABLE);
181
+ db.exec(CREATE_REVIEWS_TABLE);
182
+ db.exec(CREATE_PATCHES_TABLE);
183
+ db.exec(CREATE_REVIEW_COMMENTS_TABLE);
184
+ return db;
185
+ }
186
+ function get() {
187
+ if (!db) {
188
+ throw new Error("Store not initialized. Call init() first.");
189
+ }
190
+ return db;
191
+ }
192
+ function close() {
193
+ if (db) {
194
+ db.close();
195
+ db = null;
196
+ }
197
+ }
198
+
199
+ // src/store/repo.ts
200
+ var exports_repo = {};
201
+ __export(exports_repo, {
202
+ getOrCreate: () => getOrCreate,
203
+ getByPath: () => getByPath,
204
+ create: () => create
205
+ });
206
+ function create(path2) {
207
+ const row = get().prepare(`INSERT INTO repos (path) VALUES (?) RETURNING id`).get(path2);
208
+ return { id: row.id, path: path2 };
209
+ }
210
+ function getByPath(path2) {
211
+ const row = get().prepare(`SELECT * FROM repos WHERE path = ?`).get(path2);
212
+ if (!row)
213
+ return null;
214
+ return { id: row.id, path: row.path };
215
+ }
216
+ function getOrCreate(path2) {
217
+ const existing = getByPath(path2);
218
+ if (existing)
219
+ return existing;
220
+ return create(path2);
221
+ }
222
+
223
+ // src/store/review.ts
224
+ var exports_review = {};
225
+ __export(exports_review, {
226
+ list: () => list,
227
+ latest: () => latest,
228
+ getOrCreate: () => getOrCreate2,
229
+ getByRepoAndCommit: () => getByRepoAndCommit,
230
+ get: () => get2,
231
+ create: () => create2
232
+ });
233
+ function rowToInfo(row) {
234
+ return {
235
+ id: row.id,
236
+ repo: row.repo,
237
+ commitSha: row.commit_sha,
238
+ name: row.name,
239
+ createdAt: row.created_at
240
+ };
241
+ }
242
+ function create2(input) {
243
+ const createdAt = Date.now();
244
+ const row = get().prepare(`INSERT INTO reviews (repo, commit_sha, name, created_at)
245
+ VALUES (?, ?, ?, ?) RETURNING *`).get(input.repo, input.commitSha, input.name ?? null, createdAt);
246
+ return rowToInfo(row);
247
+ }
248
+ function list(repo) {
249
+ const rows = get().prepare(`SELECT * FROM reviews WHERE repo = ? ORDER BY created_at DESC`).all(repo);
250
+ return rows.map(rowToInfo);
251
+ }
252
+ function get2(id) {
253
+ const row = get().prepare(`SELECT * FROM reviews WHERE id = ?`).get(id);
254
+ if (!row)
255
+ return null;
256
+ return rowToInfo(row);
257
+ }
258
+ function latest(repo) {
259
+ const row = get().prepare(`SELECT * FROM reviews WHERE repo = ? ORDER BY created_at DESC LIMIT 1`).get(repo);
260
+ if (!row)
261
+ return null;
262
+ return rowToInfo(row);
263
+ }
264
+ function getByRepoAndCommit(repo, commitSha) {
265
+ const row = get().prepare(`SELECT * FROM reviews WHERE repo = ? AND commit_sha = ?`).get(repo, commitSha);
266
+ if (!row)
267
+ return null;
268
+ return rowToInfo(row);
269
+ }
270
+ function getOrCreate2(repo, commitSha) {
271
+ const existing = getByRepoAndCommit(repo, commitSha);
272
+ if (existing)
273
+ return existing;
274
+ return create2({ repo, commitSha });
275
+ }
276
+
277
+ // src/store/patch.ts
278
+ var exports_patch = {};
279
+ __export(exports_patch, {
280
+ pruneUnreferenced: () => pruneUnreferenced,
281
+ nextSeq: () => nextSeq,
282
+ list: () => list2,
283
+ latest: () => latest2,
284
+ getOrCreate: () => getOrCreate3,
285
+ getByHash: () => getByHash,
286
+ get: () => get3,
287
+ create: () => create3
288
+ });
289
+ function rowToInfo2(row) {
290
+ return {
291
+ id: row.id,
292
+ review: row.review,
293
+ seq: row.seq,
294
+ hash: row.hash,
295
+ content: row.content,
296
+ createdAt: row.created_at
297
+ };
298
+ }
299
+ function create3(input) {
300
+ const row = get().prepare(`INSERT INTO patches (review, seq, hash, content, created_at)
301
+ VALUES (?, ?, ?, ?, ?) RETURNING *`).get(input.review, input.seq, input.hash, input.content, Date.now());
302
+ return rowToInfo2(row);
303
+ }
304
+ function get3(id) {
305
+ const row = get().prepare(`SELECT * FROM patches WHERE id = ?`).get(id);
306
+ if (!row)
307
+ return null;
308
+ return rowToInfo2(row);
309
+ }
310
+ function getByHash(review, hash) {
311
+ const row = get().prepare(`SELECT * FROM patches WHERE review = ? AND hash = ?`).get(review, hash);
312
+ if (!row)
313
+ return null;
314
+ return rowToInfo2(row);
315
+ }
316
+ function latest2(review) {
317
+ const row = get().prepare(`SELECT * FROM patches WHERE review = ? ORDER BY seq DESC LIMIT 1`).get(review);
318
+ if (!row)
319
+ return null;
320
+ return rowToInfo2(row);
321
+ }
322
+ function list2(review) {
323
+ const rows = get().prepare(`SELECT * FROM patches WHERE review = ? ORDER BY seq ASC`).all(review);
324
+ return rows.map(rowToInfo2);
325
+ }
326
+ function pruneUnreferenced(review, keepPatchIds = []) {
327
+ const keep = keepPatchIds.filter((x) => Number.isFinite(x));
328
+ const keepSql = keep.length > 0 ? ` AND id NOT IN (${keep.map(() => "?").join(",")})` : "";
329
+ const stmt = get().prepare(`DELETE FROM patches
330
+ WHERE review = ?
331
+ AND id NOT IN (
332
+ SELECT DISTINCT patch_id FROM review_comments WHERE review = ?
333
+ )${keepSql}`);
334
+ const result = stmt.run(review, review, ...keep);
335
+ return Number(result.changes ?? 0);
336
+ }
337
+ function nextSeq(review) {
338
+ const last = latest2(review);
339
+ return last ? last.seq + 1 : 0;
340
+ }
341
+ function getOrCreate3(review, content) {
342
+ const hash = String(Bun.hash(content));
343
+ const existing = getByHash(review, hash);
344
+ if (existing)
345
+ return existing;
346
+ const seq = nextSeq(review);
347
+ return create3({ review, seq, hash, content });
348
+ }
349
+
350
+ // src/store/comment.ts
351
+ var exports_comment = {};
352
+ __export(exports_comment, {
353
+ list: () => list3,
354
+ create: () => create4
355
+ });
356
+ function create4(input) {
357
+ const now = Date.now();
358
+ const row = get().prepare(`INSERT INTO review_comments (
359
+ review,
360
+ note_id,
361
+ file,
362
+ patch_id,
363
+ start_row,
364
+ end_row,
365
+ created_at
366
+ )
367
+ VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING id`).get(input.review, input.noteId, input.file, input.patchId, input.startRow, input.endRow, now);
368
+ return {
369
+ id: row.id,
370
+ review: input.review,
371
+ noteId: input.noteId,
372
+ file: input.file,
373
+ patchId: input.patchId,
374
+ startRow: input.startRow,
375
+ endRow: input.endRow,
376
+ createdAt: now
377
+ };
378
+ }
379
+ function list3(review) {
380
+ const rows = get().prepare(`SELECT * FROM review_comments WHERE review = ? ORDER BY created_at ASC`).all(review);
381
+ return rows.map((row) => ({
382
+ id: row.id,
383
+ review: row.review,
384
+ noteId: row.note_id,
385
+ file: row.file,
386
+ patchId: row.patch_id,
387
+ startRow: row.start_row,
388
+ endRow: row.end_row,
389
+ createdAt: row.created_at
390
+ }));
391
+ }
392
+ // src/App.tsx
393
+ import { render } from "@opentui/solid";
394
+ import { SliderRenderable, addDefaultParsers } from "@opentui/core";
395
+ import { ErrorBoundary } from "solid-js";
396
+
397
+ // src/Review.tsx
398
+ import { useKeyboard as useKeyboard3, useTerminalDimensions as useTerminalDimensions2 } from "@opentui/solid";
399
+ import { createSignal as createSignal8, createEffect as createEffect6, createMemo as createMemo4, Show as Show7 } from "solid-js";
400
+
401
+ // src/components/DiffPanel.tsx
402
+ import { Show, For, createMemo as createMemo2, createEffect, Index } from "solid-js";
403
+
404
+ // src/lib/diff.ts
405
+ import { parsePatch } from "diff";
406
+ function parseFileDiff(content, file) {
407
+ if (!content)
408
+ return { hunks: [], totalRows: 0 };
409
+ try {
410
+ const patches = parsePatch(content);
411
+ if (patches.length === 0 && !content.endsWith(`
412
+ `)) {
413
+ patches.push(...parsePatch(`${content}
414
+ `));
415
+ }
416
+ if (patches.length === 0)
417
+ return { hunks: [], totalRows: 0 };
418
+ const rawHunks = patches[0]?.hunks ?? [];
419
+ const hunks = [];
420
+ const header = `diff --git a/${file} b/${file}
421
+ --- a/${file}
422
+ +++ b/${file}`;
423
+ let currentRow = 1;
424
+ for (let i = 0;i < rawHunks.length; i++) {
425
+ const hunk = rawHunks[i];
426
+ let skipBefore = 0;
427
+ if (i > 0) {
428
+ const prev = rawHunks[i - 1];
429
+ const prevEnd = prev.oldStart + prev.oldLines;
430
+ skipBefore = hunk.oldStart - prevEnd;
431
+ }
432
+ const hunkHeader = `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`;
433
+ const diffString = `${header}
434
+ ${hunkHeader}
435
+ ${hunk.lines.join(`
436
+ `)}`;
437
+ let lineCount = 0;
438
+ for (const line of hunk.lines) {
439
+ const c = line[0];
440
+ if (c === " " || c === "+" || c === "-")
441
+ lineCount++;
442
+ }
443
+ const startRow = currentRow;
444
+ const endRow = currentRow + lineCount - 1;
445
+ currentRow = endRow + 1;
446
+ hunks.push({
447
+ diffString,
448
+ oldStart: hunk.oldStart,
449
+ oldLines: hunk.oldLines,
450
+ newStart: hunk.newStart,
451
+ newLines: hunk.newLines,
452
+ skipBefore,
453
+ lineCount,
454
+ startRow,
455
+ endRow
456
+ });
457
+ }
458
+ return { hunks, totalRows: Math.max(0, currentRow - 1) };
459
+ } catch {
460
+ return { hunks: [], totalRows: 0 };
461
+ }
462
+ }
463
+ function getHunkIndexForRow(content, file, row) {
464
+ const { hunks } = parseFileDiff(content, file);
465
+ for (let i = 0;i < hunks.length; i++) {
466
+ const hunk = hunks[i];
467
+ if (row >= hunk.startRow && row <= hunk.endRow)
468
+ return i;
469
+ }
470
+ return null;
471
+ }
472
+ function stripPrefix(path2) {
473
+ if (!path2)
474
+ return "";
475
+ return path2.replace(/^a\//, "").replace(/^b\//, "");
476
+ }
477
+ function parsePatchEntries(content) {
478
+ if (!content.trim())
479
+ return [];
480
+ const chunks = content.trimEnd().split(/\n(?=diff --git )/);
481
+ const entries = [];
482
+ for (const chunk of chunks) {
483
+ if (!chunk.trim())
484
+ continue;
485
+ const parsed = parsePatch(chunk);
486
+ const patch = parsed[0];
487
+ let file = "";
488
+ if (patch) {
489
+ const newName = stripPrefix(patch.newFileName);
490
+ const oldName = stripPrefix(patch.oldFileName);
491
+ file = newName !== "/dev/null" ? newName : oldName;
492
+ }
493
+ if (!file) {
494
+ const first = chunk.split(`
495
+ `)[0] ?? "";
496
+ const match = first.match(/^diff --git a\/(.+?) b\/(.+)$/);
497
+ if (match)
498
+ file = match[2] ?? match[1] ?? "";
499
+ }
500
+ if (!file)
501
+ continue;
502
+ const isNew = patch?.oldFileName === "/dev/null" || chunk.includes(`
503
+ new file mode`);
504
+ const isDeleted = patch?.newFileName === "/dev/null" || chunk.includes(`
505
+ deleted file mode`);
506
+ entries.push({ file, content: chunk, isNew, isDeleted });
507
+ }
508
+ return entries;
509
+ }
510
+
511
+ // src/context/theme.tsx
512
+ import {
513
+ createContext,
514
+ useContext,
515
+ createSignal,
516
+ createMemo
517
+ } from "solid-js";
518
+ import { SyntaxStyle } from "@opentui/core";
519
+ import { jsxDEV } from "@opentui/solid/jsx-dev-runtime";
520
+ var ThemeContext = createContext();
521
+ var defaultTheme = {
522
+ background: "#0a0a0a",
523
+ backgroundPanel: "#141414",
524
+ backgroundElement: "#1e1e1e",
525
+ text: "#eeeeee",
526
+ textMuted: "#808080",
527
+ primary: "#63a088",
528
+ primaryDark: "#1d2f28",
529
+ secondary: "#608999",
530
+ secondaryDark: "#182226",
531
+ added: "#284036",
532
+ removed: "#e06c75",
533
+ modified: "#e5c07b",
534
+ indicatorDefault: "#4a4a4a",
535
+ connected: "#73d936",
536
+ disconnected: "#ff5555",
537
+ diffAddedBg: "#20303b",
538
+ diffRemovedBg: "#37222c",
539
+ diffContextBg: "111111",
540
+ diffSignAdded: "#b8db87",
541
+ diffSignRemoved: "#e26a75",
542
+ diffAddedLineNumberBg: "#1a2a30",
543
+ diffRemovedLineNumberBg: "#2d1a22",
544
+ diffLineNumberFg: "#888888",
545
+ syntaxComment: "#5c6773",
546
+ syntaxKeyword: "#ff7733",
547
+ syntaxFunction: "#ffb454",
548
+ syntaxVariable: "#cbccc6",
549
+ syntaxString: "#bae67e",
550
+ syntaxNumber: "#ffcc66",
551
+ syntaxType: "#73d0ff",
552
+ syntaxOperator: "#f29e74",
553
+ syntaxPunctuation: "#cbccc6"
554
+ };
555
+ var systemTheme = {
556
+ background: "transparent",
557
+ backgroundPanel: "brightBlack",
558
+ backgroundElement: "gray",
559
+ text: "white",
560
+ textMuted: "brightBlack",
561
+ primary: "cyan",
562
+ primaryDark: "blue",
563
+ secondary: "red",
564
+ secondaryDark: "magenta",
565
+ added: "green",
566
+ removed: "red",
567
+ modified: "yellow",
568
+ indicatorDefault: "gray",
569
+ connected: "brightGreen",
570
+ disconnected: "brightRed",
571
+ diffAddedBg: "green",
572
+ diffRemovedBg: "red",
573
+ diffContextBg: "transparent",
574
+ diffSignAdded: "brightGreen",
575
+ diffSignRemoved: "brightRed",
576
+ diffAddedLineNumberBg: "green",
577
+ diffRemovedLineNumberBg: "red",
578
+ diffLineNumberFg: "brightBlack",
579
+ syntaxComment: "brightBlack",
580
+ syntaxKeyword: "magenta",
581
+ syntaxFunction: "blue",
582
+ syntaxVariable: "white",
583
+ syntaxString: "green",
584
+ syntaxNumber: "yellow",
585
+ syntaxType: "cyan",
586
+ syntaxOperator: "white",
587
+ syntaxPunctuation: "white"
588
+ };
589
+ var themes = {
590
+ default: defaultTheme,
591
+ system: systemTheme
592
+ };
593
+ function getSyntaxRules(theme) {
594
+ return [
595
+ {
596
+ scope: ["comment", "comment.documentation"],
597
+ style: { foreground: theme.syntaxComment, italic: true }
598
+ },
599
+ {
600
+ scope: ["string", "string.special", "character"],
601
+ style: { foreground: theme.syntaxString }
602
+ },
603
+ {
604
+ scope: ["number", "boolean", "constant"],
605
+ style: { foreground: theme.syntaxNumber }
606
+ },
607
+ {
608
+ scope: [
609
+ "keyword",
610
+ "keyword.return",
611
+ "keyword.conditional",
612
+ "keyword.repeat"
613
+ ],
614
+ style: { foreground: theme.syntaxKeyword, italic: true }
615
+ },
616
+ {
617
+ scope: ["keyword.function", "keyword.import"],
618
+ style: { foreground: theme.syntaxKeyword }
619
+ },
620
+ {
621
+ scope: ["function", "function.call", "function.method", "constructor"],
622
+ style: { foreground: theme.syntaxFunction }
623
+ },
624
+ {
625
+ scope: ["variable", "variable.parameter", "variable.member"],
626
+ style: { foreground: theme.syntaxVariable }
627
+ },
628
+ {
629
+ scope: ["type", "type.builtin", "module"],
630
+ style: { foreground: theme.syntaxType }
631
+ },
632
+ {
633
+ scope: ["operator", "keyword.operator"],
634
+ style: { foreground: theme.syntaxOperator }
635
+ },
636
+ {
637
+ scope: ["punctuation", "punctuation.delimiter", "punctuation.bracket"],
638
+ style: { foreground: theme.syntaxPunctuation }
639
+ },
640
+ {
641
+ scope: ["property"],
642
+ style: { foreground: theme.syntaxVariable }
643
+ },
644
+ {
645
+ scope: ["tag", "tag.attribute"],
646
+ style: { foreground: theme.syntaxKeyword }
647
+ }
648
+ ];
649
+ }
650
+ function ThemeProvider(props) {
651
+ const [themeName, setThemeName] = createSignal("default");
652
+ const syntax = createMemo(() => {
653
+ const theme = themes[themeName()];
654
+ return SyntaxStyle.fromTheme(getSyntaxRules(theme));
655
+ });
656
+ const value = {
657
+ get theme() {
658
+ return themes[themeName()];
659
+ },
660
+ themeName,
661
+ setTheme: setThemeName,
662
+ syntax
663
+ };
664
+ return /* @__PURE__ */ jsxDEV(ThemeContext.Provider, {
665
+ value,
666
+ children: props.children
667
+ }, undefined, false, undefined, this);
668
+ }
669
+ function useTheme() {
670
+ const ctx = useContext(ThemeContext);
671
+ if (!ctx)
672
+ throw new Error("useTheme must be used within ThemeProvider");
673
+ return ctx;
674
+ }
675
+
676
+ // src/components/HalfLineShadow.tsx
677
+ import { jsxDEV as jsxDEV2 } from "@opentui/solid/jsx-dev-runtime";
678
+ var EmptyBorder = {
679
+ topLeft: "",
680
+ bottomLeft: "",
681
+ vertical: "",
682
+ topRight: "",
683
+ bottomRight: "",
684
+ horizontal: " ",
685
+ bottomT: "",
686
+ topT: "",
687
+ cross: "",
688
+ leftT: "",
689
+ rightT: ""
690
+ };
691
+
692
+ // src/components/DiffPanel.tsx
693
+ import { jsxDEV as jsxDEV3, Fragment } from "@opentui/solid/jsx-dev-runtime";
694
+ var LINE_SCROLL_BUFFER = 8;
695
+ function getFiletype(filename) {
696
+ const ext = filename.split(".").pop()?.toLowerCase() ?? "";
697
+ const extMap = {
698
+ ts: "typescript",
699
+ tsx: "tsx",
700
+ js: "javascript",
701
+ jsx: "javascript",
702
+ mjs: "javascript",
703
+ cjs: "javascript",
704
+ html: "html",
705
+ htm: "html",
706
+ css: "css",
707
+ scss: "css",
708
+ vue: "vue",
709
+ json: "json",
710
+ yaml: "yaml",
711
+ yml: "yaml",
712
+ toml: "toml",
713
+ c: "c",
714
+ h: "c",
715
+ cpp: "cpp",
716
+ cc: "cpp",
717
+ cxx: "cpp",
718
+ hpp: "cpp",
719
+ hxx: "cpp",
720
+ rs: "rust",
721
+ go: "go",
722
+ zig: "zig",
723
+ py: "python",
724
+ rb: "ruby",
725
+ lua: "lua",
726
+ sh: "bash",
727
+ bash: "bash",
728
+ zsh: "bash",
729
+ php: "php",
730
+ java: "java",
731
+ kt: "kotlin",
732
+ kts: "kotlin",
733
+ scala: "scala",
734
+ cs: "c_sharp",
735
+ swift: "swift",
736
+ m: "objc",
737
+ mm: "objc",
738
+ dart: "dart",
739
+ ex: "elixir",
740
+ exs: "elixir",
741
+ elm: "elm",
742
+ ml: "ocaml",
743
+ mli: "ocaml",
744
+ el: "elisp",
745
+ sql: "ql",
746
+ erb: "embedded_template",
747
+ ejs: "embedded_template",
748
+ md: "markdown",
749
+ mdx: "markdown"
750
+ };
751
+ return extMap[ext] ?? "text";
752
+ }
753
+ function DiffPanel(props) {
754
+ const { theme, syntax } = useTheme();
755
+ const title = () => {
756
+ const entry = props.entry();
757
+ if (!entry)
758
+ return "Diff";
759
+ const hunkCount = props.hunkCount();
760
+ if (hunkCount === 0)
761
+ return entry.file;
762
+ if (props.selectionMode() === "line") {
763
+ const range = props.selectedRange();
764
+ if (range && range.startRow !== range.endRow) {
765
+ return `[lines ${range.startRow}-${range.endRow}]`;
766
+ }
767
+ const row = range?.startRow ?? props.cursorRow();
768
+ return `[line ${row}]`;
769
+ }
770
+ if (props.focused()) {
771
+ return `[hunk ${props.selectedHunkIndex() + 1}/${hunkCount}]`;
772
+ }
773
+ return `[${hunkCount} hunk${hunkCount === 1 ? "" : "s"}]`;
774
+ };
775
+ return /* @__PURE__ */ jsxDEV3("box", {
776
+ flexGrow: 1,
777
+ flexDirection: "column",
778
+ backgroundColor: theme.backgroundPanel,
779
+ paddingTop: 1,
780
+ paddingBottom: 1,
781
+ paddingLeft: 2,
782
+ paddingRight: 2,
783
+ children: [
784
+ /* @__PURE__ */ jsxDEV3("box", {
785
+ height: 1,
786
+ flexDirection: "row",
787
+ justifyContent: "flex-start",
788
+ gap: 1,
789
+ backgroundColor: props.focused() ? theme.primaryDark : theme.backgroundPanel,
790
+ border: ["left"],
791
+ borderColor: props.focused() ? theme.primary : theme.indicatorDefault,
792
+ customBorderChars: {
793
+ ...EmptyBorder,
794
+ vertical: "\u258C"
795
+ },
796
+ children: /* @__PURE__ */ jsxDEV3("box", {
797
+ paddingLeft: 1,
798
+ flexDirection: "row",
799
+ gap: 1,
800
+ children: [
801
+ /* @__PURE__ */ jsxDEV3("text", {
802
+ children: /* @__PURE__ */ jsxDEV3("span", {
803
+ style: { italic: true },
804
+ children: props.entry() ? props.entry().file : "none"
805
+ }, undefined, false, undefined, this)
806
+ }, undefined, false, undefined, this),
807
+ /* @__PURE__ */ jsxDEV3("text", {
808
+ children: /* @__PURE__ */ jsxDEV3("span", {
809
+ style: { bold: true },
810
+ children: title()
811
+ }, undefined, false, undefined, this)
812
+ }, undefined, false, undefined, this)
813
+ ]
814
+ }, undefined, true, undefined, this)
815
+ }, undefined, false, undefined, this),
816
+ /* @__PURE__ */ jsxDEV3(Show, {
817
+ when: props.entry(),
818
+ children: (() => {
819
+ const entry = props.entry();
820
+ const model = createMemo2(() => parseFileDiff(entry.content, entry.file));
821
+ const hunks = createMemo2(() => model().hunks);
822
+ const filetype = getFiletype(entry.file);
823
+ let scrollbox = null;
824
+ createEffect(() => {
825
+ const hunkIdx = props.selectedHunkIndex();
826
+ const hunkList = hunks();
827
+ if (!scrollbox || hunkList.length === 0)
828
+ return;
829
+ let scrollLine = 0;
830
+ if (props.selectionMode() === "line") {
831
+ const row = props.cursorRow();
832
+ const rowHunkIndex = getHunkIndexForRow(entry.content, entry.file, row);
833
+ const separatorOffset2 = rowHunkIndex ? rowHunkIndex : 0;
834
+ scrollLine = Math.max(0, row - 1 + separatorOffset2);
835
+ const scrollTop2 = scrollbox.scrollTop;
836
+ const viewportHeight2 = scrollbox.viewport.height;
837
+ const topBound = scrollTop2 + LINE_SCROLL_BUFFER;
838
+ const bottomBound = scrollTop2 + viewportHeight2 - 1 - LINE_SCROLL_BUFFER;
839
+ if (scrollLine < topBound) {
840
+ scrollbox.scrollTo(Math.max(0, scrollLine - LINE_SCROLL_BUFFER));
841
+ } else if (scrollLine > bottomBound) {
842
+ scrollbox.scrollTo(Math.max(0, scrollLine + LINE_SCROLL_BUFFER - viewportHeight2 + 1));
843
+ }
844
+ return;
845
+ }
846
+ if (hunkIdx > 0) {
847
+ const prev = hunkList[Math.min(hunkIdx - 1, hunkList.length - 1)];
848
+ if (prev)
849
+ scrollLine = prev.endRow + hunkIdx;
850
+ }
851
+ const activeHunk = hunkList[hunkIdx];
852
+ if (!activeHunk)
853
+ return;
854
+ const scrollTop = scrollbox.scrollTop;
855
+ const viewportHeight = scrollbox.viewport.height;
856
+ const separatorOffset = hunkIdx;
857
+ const hunkTop = Math.max(0, activeHunk.startRow - 1 + separatorOffset);
858
+ const hunkBottom = Math.max(0, activeHunk.endRow - 1 + separatorOffset);
859
+ const viewportBottom = scrollTop + viewportHeight - 1;
860
+ if (hunkTop < scrollTop) {
861
+ scrollbox.scrollTo(hunkTop);
862
+ } else if (hunkBottom > viewportBottom) {
863
+ scrollbox.scrollTo(scrollLine);
864
+ }
865
+ });
866
+ return /* @__PURE__ */ jsxDEV3("scrollbox", {
867
+ ref: (r) => {
868
+ scrollbox = r;
869
+ },
870
+ focused: false,
871
+ flexGrow: 1,
872
+ children: /* @__PURE__ */ jsxDEV3("box", {
873
+ flexDirection: "column",
874
+ backgroundColor: theme.background,
875
+ children: /* @__PURE__ */ jsxDEV3(Index, {
876
+ each: hunks(),
877
+ children: (hunk, i) => {
878
+ const isFocused = () => props.focused() && props.selectedHunkIndex() === i;
879
+ const indicatorColor = (row) => {
880
+ if (props.selectionMode() === "line") {
881
+ const range = props.selectedRange();
882
+ if (range && row >= range.startRow && row <= range.endRow) {
883
+ return theme.primary;
884
+ }
885
+ return theme.indicatorDefault;
886
+ }
887
+ if (isFocused())
888
+ return theme.primary;
889
+ return theme.indicatorDefault;
890
+ };
891
+ return /* @__PURE__ */ jsxDEV3(Fragment, {
892
+ children: [
893
+ /* @__PURE__ */ jsxDEV3(Show, {
894
+ when: i > 0,
895
+ children: /* @__PURE__ */ jsxDEV3("box", {
896
+ height: 1,
897
+ backgroundColor: theme.backgroundPanel
898
+ }, undefined, false, undefined, this)
899
+ }, undefined, false, undefined, this),
900
+ /* @__PURE__ */ jsxDEV3("box", {
901
+ flexDirection: "row",
902
+ children: [
903
+ /* @__PURE__ */ jsxDEV3("box", {
904
+ flexDirection: "column",
905
+ width: 1,
906
+ flexShrink: 0,
907
+ children: /* @__PURE__ */ jsxDEV3(For, {
908
+ each: Array.from({ length: hunk().lineCount }, (_, j) => j),
909
+ children: (j) => {
910
+ const row = hunk().startRow + j;
911
+ return /* @__PURE__ */ jsxDEV3("text", {
912
+ height: 1,
913
+ fg: indicatorColor(row),
914
+ children: "\u258C"
915
+ }, undefined, false, undefined, this);
916
+ }
917
+ }, undefined, false, undefined, this)
918
+ }, undefined, false, undefined, this),
919
+ /* @__PURE__ */ jsxDEV3("diff", {
920
+ diff: hunk().diffString,
921
+ view: "unified",
922
+ filetype,
923
+ syntaxStyle: syntax(),
924
+ showLineNumbers: true,
925
+ flexGrow: 0,
926
+ fg: theme.text,
927
+ addedBg: theme.diffAddedBg,
928
+ removedBg: theme.diffRemovedBg,
929
+ contextBg: theme.diffContextBg,
930
+ addedSignColor: theme.diffSignAdded,
931
+ removedSignColor: theme.diffSignRemoved,
932
+ lineNumberFg: theme.diffLineNumberFg,
933
+ lineNumberBg: theme.diffContextBg,
934
+ addedLineNumberBg: theme.diffAddedLineNumberBg,
935
+ removedLineNumberBg: theme.diffRemovedLineNumberBg
936
+ }, undefined, false, undefined, this)
937
+ ]
938
+ }, undefined, true, undefined, this)
939
+ ]
940
+ }, undefined, true, undefined, this);
941
+ }
942
+ }, undefined, false, undefined, this)
943
+ }, undefined, false, undefined, this)
944
+ }, undefined, false, undefined, this);
945
+ })()
946
+ }, undefined, false, undefined, this),
947
+ /* @__PURE__ */ jsxDEV3(Show, {
948
+ when: !props.entry(),
949
+ children: /* @__PURE__ */ jsxDEV3("box", {
950
+ padding: 1,
951
+ flexGrow: 1,
952
+ children: /* @__PURE__ */ jsxDEV3("text", {
953
+ children: "Select a file to view diff"
954
+ }, undefined, false, undefined, this)
955
+ }, undefined, false, undefined, this)
956
+ }, undefined, false, undefined, this)
957
+ ]
958
+ }, undefined, true, undefined, this);
959
+ }
960
+ // src/components/EditorPanel.tsx
961
+ import { onMount as onMount3 } from "solid-js";
962
+ import { spawn } from "child_process";
963
+ import { writeFileSync, readFileSync } from "fs";
964
+ import { useRenderer } from "@opentui/solid";
965
+
966
+ // src/context/review.tsx
967
+ import {
968
+ createContext as createContext3,
969
+ useContext as useContext3,
970
+ createSignal as createSignal3,
971
+ createEffect as createEffect2,
972
+ onMount as onMount2
973
+ } from "solid-js";
974
+
975
+ // src/context/server.tsx
976
+ import {
977
+ createContext as createContext2,
978
+ useContext as useContext2,
979
+ createSignal as createSignal2,
980
+ onMount,
981
+ onCleanup
982
+ } from "solid-js";
983
+ import { Client } from "@spader/spall-sdk/client";
984
+ import { jsxDEV as jsxDEV4 } from "@opentui/solid/jsx-dev-runtime";
985
+ var ServerContext = createContext2();
986
+ function ServerProvider(props) {
987
+ const [url, setUrl] = createSignal2(null);
988
+ const [connected, setConnected] = createSignal2(false);
989
+ const [client, setClient] = createSignal2(null);
990
+ let shutdown = false;
991
+ const controller = new AbortController;
992
+ onMount(() => {
993
+ const connect = async () => {
994
+ while (!shutdown) {
995
+ try {
996
+ const c = await Client.connect(controller.signal);
997
+ const health = await c.health();
998
+ if (!health?.response?.ok) {
999
+ throw new Error("Health check failed");
1000
+ }
1001
+ setUrl(health.response.url.replace("/health", ""));
1002
+ setConnected(true);
1003
+ setClient(c);
1004
+ const { stream } = await c.events({
1005
+ onSseError: () => {
1006
+ setConnected(false);
1007
+ setClient(null);
1008
+ },
1009
+ sseMaxRetryAttempts: 0
1010
+ });
1011
+ for await (const event of stream) {}
1012
+ } catch {
1013
+ setConnected(false);
1014
+ setClient(null);
1015
+ setUrl(null);
1016
+ await Bun.sleep(100);
1017
+ }
1018
+ }
1019
+ };
1020
+ connect();
1021
+ });
1022
+ onCleanup(() => {
1023
+ shutdown = true;
1024
+ controller.abort();
1025
+ });
1026
+ const value = {
1027
+ url,
1028
+ connected,
1029
+ client
1030
+ };
1031
+ return /* @__PURE__ */ jsxDEV4(ServerContext.Provider, {
1032
+ value,
1033
+ children: props.children
1034
+ }, undefined, false, undefined, this);
1035
+ }
1036
+ function useServer() {
1037
+ const ctx = useContext2(ServerContext);
1038
+ if (!ctx)
1039
+ throw new Error("useServer must be used within ServerProvider");
1040
+ return ctx;
1041
+ }
1042
+
1043
+ // src/context/review.tsx
1044
+ import { jsxDEV as jsxDEV5 } from "@opentui/solid/jsx-dev-runtime";
1045
+ function pickCommentForRange(list4, preferredPatchId, file, startRow, endRow) {
1046
+ if (preferredPatchId !== null) {
1047
+ const exact = list4.find((c) => c.patchId === preferredPatchId && c.file === file && c.startRow === startRow && c.endRow === endRow) ?? null;
1048
+ if (exact)
1049
+ return exact;
1050
+ }
1051
+ const matches = list4.filter((c) => c.file === file && c.startRow === startRow && c.endRow === endRow);
1052
+ if (matches.length === 0)
1053
+ return null;
1054
+ return matches.reduce((best, c) => c.createdAt > best.createdAt ? c : best);
1055
+ }
1056
+ var ReviewContext = createContext3();
1057
+ function repoName(path2) {
1058
+ const segments = path2.split("/").filter(Boolean);
1059
+ return segments[segments.length - 1] || "unknown";
1060
+ }
1061
+ var REVIEW_CORPUS_NAME = "spall-review";
1062
+ function repoKey(root) {
1063
+ const base = repoName(root).replace(/[^a-zA-Z0-9._-]+/g, "-");
1064
+ const h = Bun.hash(root);
1065
+ const n = typeof h === "bigint" ? Number(h % 0xffffffffn) : h;
1066
+ const suffix = Math.abs(n).toString(36).slice(0, 6);
1067
+ return `${base}-${suffix}`;
1068
+ }
1069
+ function reviewNotePath(input) {
1070
+ const filePath = input.file.replace(/\\/g, "/");
1071
+ return `review/${repoKey(input.root)}/${input.commitSha}/p${input.patchSeq}/${filePath}__${input.startRow}-${input.endRow}.md`;
1072
+ }
1073
+ function ReviewProvider(props) {
1074
+ const server = useServer();
1075
+ const [corpusId, setCorpusId] = createSignal3(null);
1076
+ const corpusName = () => REVIEW_CORPUS_NAME;
1077
+ const [repoRoot, setRepoRoot] = createSignal3(null);
1078
+ const [reviewId, setReviewId] = createSignal3(null);
1079
+ const [commitSha, setCommitSha] = createSignal3(null);
1080
+ const [activePatchId, setActivePatchIdState] = createSignal3(null);
1081
+ const [workingTreePatchId, setWorkingTreePatchId] = createSignal3(null);
1082
+ const [workspaceEntries, setWorkspaceEntries] = createSignal3([]);
1083
+ const [workspaceDiff, setWorkspaceDiff] = createSignal3("");
1084
+ const [entries, setEntries] = createSignal3([]);
1085
+ const [loading, setLoading] = createSignal3(true);
1086
+ const ensureReviewId = (root, head) => {
1087
+ const current = reviewId();
1088
+ if (current !== null)
1089
+ return current;
1090
+ const repo = exports_repo.getOrCreate(root);
1091
+ const review = exports_review.getOrCreate(repo.id, head);
1092
+ setReviewId(review.id);
1093
+ return review.id;
1094
+ };
1095
+ const [patches, setPatches] = createSignal3([]);
1096
+ const [patchesLoading, setPatchesLoading] = createSignal3(false);
1097
+ const [comments, setComments] = createSignal3([]);
1098
+ const [commentsLoading, setCommentsLoading] = createSignal3(false);
1099
+ const [selectedFilePath, setSelectedFilePath] = createSignal3(null);
1100
+ const preserveSelectedFilePath = (list4, preferred) => {
1101
+ if (!preferred)
1102
+ return null;
1103
+ return list4.some((e) => e.file === preferred) ? preferred : null;
1104
+ };
1105
+ const setActivePatch = (patchId, preserveFilePath) => {
1106
+ if (patchId === null) {
1107
+ const wt = workingTreePatchId();
1108
+ if (wt !== null) {
1109
+ setActivePatch(wt, preserveFilePath);
1110
+ return;
1111
+ }
1112
+ setActivePatchIdState(null);
1113
+ const ws = parsePatchEntries(workspaceDiff());
1114
+ setEntries(ws);
1115
+ setSelectedFilePath(preserveSelectedFilePath(ws, preserveFilePath ?? selectedFilePath()));
1116
+ return;
1117
+ }
1118
+ const patch = exports_patch.get(patchId);
1119
+ if (!patch)
1120
+ return;
1121
+ setActivePatchIdState(patchId);
1122
+ const newEntries = parsePatchEntries(patch.content);
1123
+ setEntries(newEntries);
1124
+ setSelectedFilePath(preserveSelectedFilePath(newEntries, preserveFilePath ?? selectedFilePath()));
1125
+ };
1126
+ const refreshWorkingTree = async (preserveFilePath) => {
1127
+ const newEntries = await Git.entries(props.repoPath);
1128
+ setWorkspaceEntries(newEntries);
1129
+ const fullDiff = newEntries.map((x) => x.content).join(`
1130
+ `);
1131
+ setWorkspaceDiff(fullDiff);
1132
+ const root = repoRoot();
1133
+ const head = commitSha();
1134
+ if (!root || !head) {
1135
+ if (activePatchId() === null) {
1136
+ const ws = parsePatchEntries(fullDiff);
1137
+ setEntries(ws);
1138
+ setSelectedFilePath(preserveSelectedFilePath(ws, preserveFilePath ?? selectedFilePath()));
1139
+ }
1140
+ setWorkingTreePatchId(null);
1141
+ return;
1142
+ }
1143
+ const revId = ensureReviewId(root, head);
1144
+ const patch = exports_patch.getOrCreate(revId, fullDiff);
1145
+ const prevWt = workingTreePatchId();
1146
+ setWorkingTreePatchId(patch.id);
1147
+ setPatches((prev) => {
1148
+ if (prev.some((p) => p.id === patch.id))
1149
+ return prev;
1150
+ return [...prev, patch].sort((a, b) => a.seq - b.seq);
1151
+ });
1152
+ const active = activePatchId();
1153
+ if (active === null || prevWt !== null && active === prevWt) {
1154
+ setActivePatch(patch.id, preserveFilePath);
1155
+ }
1156
+ };
1157
+ const [initialized, setInitialized] = createSignal3(false);
1158
+ let corpusInitInFlight = false;
1159
+ let commentsHydrated = false;
1160
+ createEffect2(() => {
1161
+ const client = server.client();
1162
+ if (!client || !initialized())
1163
+ return;
1164
+ if (corpusId() === null && !corpusInitInFlight) {
1165
+ corpusInitInFlight = true;
1166
+ (async () => {
1167
+ try {
1168
+ const result = await client.corpus.create({
1169
+ name: REVIEW_CORPUS_NAME
1170
+ });
1171
+ if (result.data) {
1172
+ setCorpusId(result.data.id);
1173
+ }
1174
+ } catch {} finally {
1175
+ corpusInitInFlight = false;
1176
+ }
1177
+ })();
1178
+ }
1179
+ if (commentsHydrated)
1180
+ return;
1181
+ const pending = comments().filter((c) => c.notePath === null || c.noteContent === null);
1182
+ if (pending.length === 0) {
1183
+ commentsHydrated = true;
1184
+ return;
1185
+ }
1186
+ commentsHydrated = true;
1187
+ setCommentsLoading(true);
1188
+ (async () => {
1189
+ try {
1190
+ const results = await Promise.all(pending.map(async (comment) => {
1191
+ try {
1192
+ const result = await client.note.getById({
1193
+ id: comment.noteId.toString()
1194
+ });
1195
+ if (!result.data)
1196
+ return null;
1197
+ return {
1198
+ id: comment.id,
1199
+ notePath: result.data.path,
1200
+ noteContent: result.data.content
1201
+ };
1202
+ } catch {
1203
+ return null;
1204
+ }
1205
+ }));
1206
+ const byId = new Map(results.filter(Boolean).map((r) => [r.id, r]));
1207
+ setComments((prev) => prev.map((c) => {
1208
+ const upd = byId.get(c.id);
1209
+ return upd ? { ...c, notePath: upd.notePath, noteContent: upd.noteContent } : c;
1210
+ }));
1211
+ } finally {
1212
+ setCommentsLoading(false);
1213
+ }
1214
+ })();
1215
+ });
1216
+ onMount2(async () => {
1217
+ const root = await Git.root(props.repoPath);
1218
+ setRepoRoot(root);
1219
+ const head = await Git.head(props.repoPath);
1220
+ setCommitSha(head);
1221
+ if (root && head) {
1222
+ ensureReviewId(root, head);
1223
+ }
1224
+ const revId = reviewId();
1225
+ if (revId) {
1226
+ const local = exports_comment.list(revId);
1227
+ setComments(local.map((c) => ({
1228
+ id: c.id,
1229
+ reviewId: c.review,
1230
+ noteId: c.noteId,
1231
+ file: c.file,
1232
+ patchId: c.patchId,
1233
+ startRow: c.startRow,
1234
+ endRow: c.endRow,
1235
+ createdAt: c.createdAt,
1236
+ notePath: null,
1237
+ noteContent: null
1238
+ })));
1239
+ setPatchesLoading(true);
1240
+ setPatches(exports_patch.list(revId));
1241
+ setPatchesLoading(false);
1242
+ }
1243
+ await refreshWorkingTree(selectedFilePath() ?? undefined);
1244
+ if (revId) {
1245
+ const wt = workingTreePatchId();
1246
+ exports_patch.pruneUnreferenced(revId, wt !== null ? [wt] : []);
1247
+ setPatches(exports_patch.list(revId));
1248
+ }
1249
+ setLoading(false);
1250
+ setInitialized(true);
1251
+ });
1252
+ const getCommentById = (commentId) => {
1253
+ return comments().find((c) => c.id === commentId) ?? null;
1254
+ };
1255
+ const getCommentForRange = (file, startRow, endRow) => {
1256
+ const patchId = activePatchId();
1257
+ return pickCommentForRange(comments(), patchId, file, startRow, endRow);
1258
+ };
1259
+ const createComment = async (file, startRow, endRow, content) => {
1260
+ const root = repoRoot();
1261
+ const head = commitSha();
1262
+ const c = server.client();
1263
+ let cid = corpusId();
1264
+ if (!root || !head)
1265
+ return null;
1266
+ if (!content.trim())
1267
+ return null;
1268
+ const repo = exports_repo.getOrCreate(root);
1269
+ let revId = reviewId();
1270
+ if (!revId) {
1271
+ const review = exports_review.getOrCreate(repo.id, head);
1272
+ revId = review.id;
1273
+ setReviewId(revId);
1274
+ }
1275
+ let patch = activePatchId() ? exports_patch.get(activePatchId()) : null;
1276
+ if (!patch) {
1277
+ const wt = workingTreePatchId();
1278
+ patch = wt !== null ? exports_patch.get(wt) : null;
1279
+ if (!patch) {
1280
+ const fullDiff = workspaceDiff();
1281
+ patch = exports_patch.getOrCreate(revId, fullDiff);
1282
+ setWorkingTreePatchId(patch.id);
1283
+ setPatches((prev) => {
1284
+ if (prev.some((p) => p.id === patch.id))
1285
+ return prev;
1286
+ return [...prev, patch].sort((a, b) => a.seq - b.seq);
1287
+ });
1288
+ if (activePatchId() === null)
1289
+ setActivePatch(patch.id);
1290
+ }
1291
+ }
1292
+ if (!patch)
1293
+ return null;
1294
+ if (c) {
1295
+ if (cid === null) {
1296
+ try {
1297
+ const ensured = await c.corpus.create({ name: REVIEW_CORPUS_NAME });
1298
+ if (ensured.data) {
1299
+ cid = ensured.data.id;
1300
+ setCorpusId(cid);
1301
+ }
1302
+ } catch {}
1303
+ }
1304
+ if (cid === null)
1305
+ return null;
1306
+ const path2 = reviewNotePath({
1307
+ root,
1308
+ commitSha: head,
1309
+ patchSeq: patch.seq,
1310
+ file,
1311
+ startRow,
1312
+ endRow
1313
+ });
1314
+ const result = await c.note.upsert({
1315
+ id: cid.toString(),
1316
+ path: path2,
1317
+ content,
1318
+ dupe: true
1319
+ });
1320
+ if (result.error || !result.data) {
1321
+ return null;
1322
+ }
1323
+ const noteId = result.data.id;
1324
+ const localComment = exports_comment.create({
1325
+ review: revId,
1326
+ noteId,
1327
+ file,
1328
+ patchId: patch.id,
1329
+ startRow,
1330
+ endRow
1331
+ });
1332
+ const newComment = {
1333
+ id: localComment.id,
1334
+ reviewId: revId,
1335
+ noteId,
1336
+ file,
1337
+ patchId: localComment.patchId,
1338
+ startRow: localComment.startRow,
1339
+ endRow: localComment.endRow,
1340
+ createdAt: localComment.createdAt,
1341
+ notePath: path2,
1342
+ noteContent: content
1343
+ };
1344
+ setComments((prev) => [...prev, newComment]);
1345
+ return newComment;
1346
+ }
1347
+ return null;
1348
+ };
1349
+ const updateComment = async (commentId, content) => {
1350
+ const c = server.client();
1351
+ if (!c)
1352
+ return;
1353
+ if (!content.trim())
1354
+ return;
1355
+ const comment = comments().find((c2) => c2.id === commentId);
1356
+ if (!comment)
1357
+ return;
1358
+ const result = await c.note.update({
1359
+ id: comment.noteId.toString(),
1360
+ content,
1361
+ dupe: true
1362
+ });
1363
+ if (result.error)
1364
+ return;
1365
+ setComments((prev) => prev.map((c2) => c2.id === commentId ? { ...c2, noteContent: content } : c2));
1366
+ };
1367
+ const value = {
1368
+ corpusId,
1369
+ corpusName,
1370
+ workingTreePatchId,
1371
+ repoRoot,
1372
+ repoPath: () => props.repoPath,
1373
+ reviewId,
1374
+ commitSha,
1375
+ activePatchId,
1376
+ workspaceEntries,
1377
+ entries,
1378
+ loading,
1379
+ patches,
1380
+ patchesLoading,
1381
+ comments,
1382
+ commentsLoading,
1383
+ selectedFilePath,
1384
+ setSelectedFilePath,
1385
+ setActivePatch,
1386
+ refreshWorkingTree,
1387
+ getCommentById,
1388
+ getCommentForRange,
1389
+ createComment,
1390
+ updateComment
1391
+ };
1392
+ return /* @__PURE__ */ jsxDEV5(ReviewContext.Provider, {
1393
+ value,
1394
+ children: props.children
1395
+ }, undefined, false, undefined, this);
1396
+ }
1397
+ function useReview() {
1398
+ const ctx = useContext3(ReviewContext);
1399
+ if (!ctx)
1400
+ throw new Error("useReview must be used within ReviewProvider");
1401
+ return ctx;
1402
+ }
1403
+
1404
+ // src/components/EditorPanel.tsx
1405
+ import { jsxDEV as jsxDEV6 } from "@opentui/solid/jsx-dev-runtime";
1406
+ function EditorPanel(props) {
1407
+ const { theme } = useTheme();
1408
+ const review = useReview();
1409
+ const renderer = useRenderer();
1410
+ let textareaRef = null;
1411
+ let initialized = false;
1412
+ const existingComment = props.commentId ? review.getCommentById(props.commentId) : review.getCommentForRange(props.file, props.startRow, props.endRow);
1413
+ const initialContent = existingComment?.noteContent ?? "";
1414
+ const shortFile = props.file.split("/").pop() ?? props.file;
1415
+ const rangeLabel = `rows-${props.startRow}-${props.endRow}`;
1416
+ const filename = `${shortFile}_${rangeLabel}.md`;
1417
+ onMount3(() => {
1418
+ setTimeout(() => {
1419
+ initialized = true;
1420
+ }, 0);
1421
+ });
1422
+ const submit = async () => {
1423
+ if (!textareaRef)
1424
+ return;
1425
+ const content = textareaRef.editBuffer.getText();
1426
+ if (!content.trim()) {
1427
+ props.onClose();
1428
+ return;
1429
+ }
1430
+ if (existingComment) {
1431
+ await review.updateComment(existingComment.id, content);
1432
+ } else {
1433
+ await review.createComment(props.file, props.startRow, props.endRow, content);
1434
+ }
1435
+ props.onClose();
1436
+ };
1437
+ const openInEditor = async () => {
1438
+ if (!textareaRef)
1439
+ return;
1440
+ const tmpFile = `/tmp/spall-${filename}`;
1441
+ writeFileSync(tmpFile, textareaRef.editBuffer.getText());
1442
+ renderer.suspend();
1443
+ await new Promise((resolve, reject) => {
1444
+ const editor = process.env.EDITOR || "nvim";
1445
+ const child = spawn(editor, [tmpFile], { stdio: "inherit" });
1446
+ child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`exit ${code}`)));
1447
+ child.on("error", reject);
1448
+ });
1449
+ const content = readFileSync(tmpFile, "utf-8");
1450
+ renderer.resume();
1451
+ if (!content.trim()) {
1452
+ props.onClose();
1453
+ return;
1454
+ }
1455
+ if (textareaRef) {
1456
+ textareaRef.editBuffer.setText(content);
1457
+ textareaRef.cursorOffset = content.length;
1458
+ }
1459
+ };
1460
+ const handleKeyDown = (e) => {
1461
+ if (!initialized && (e.name === "c" || e.name === "return")) {
1462
+ e.preventDefault();
1463
+ return;
1464
+ }
1465
+ if (e.name === "escape") {
1466
+ e.preventDefault();
1467
+ props.onClose();
1468
+ return;
1469
+ }
1470
+ if (e.name === "return") {
1471
+ e.preventDefault();
1472
+ submit();
1473
+ return;
1474
+ }
1475
+ if (e.name === "j" && e.ctrl) {
1476
+ e.preventDefault();
1477
+ textareaRef?.editBuffer.newLine();
1478
+ return;
1479
+ }
1480
+ if (e.name === "e" && e.ctrl) {
1481
+ e.preventDefault();
1482
+ openInEditor();
1483
+ return;
1484
+ }
1485
+ };
1486
+ return /* @__PURE__ */ jsxDEV6("box", {
1487
+ flexDirection: "column",
1488
+ backgroundColor: theme.backgroundPanel,
1489
+ flexShrink: 0,
1490
+ children: [
1491
+ /* @__PURE__ */ jsxDEV6("box", {
1492
+ paddingLeft: 1,
1493
+ paddingRight: 1,
1494
+ paddingTop: 1,
1495
+ paddingBottom: 1,
1496
+ children: /* @__PURE__ */ jsxDEV6("textarea", {
1497
+ ref: (r) => {
1498
+ textareaRef = r;
1499
+ },
1500
+ initialValue: initialContent || undefined,
1501
+ focused: true,
1502
+ showCursor: true,
1503
+ wrapMode: "word",
1504
+ minHeight: 1,
1505
+ maxHeight: 6,
1506
+ width: "100%",
1507
+ onKeyDown: handleKeyDown
1508
+ }, undefined, false, undefined, this)
1509
+ }, undefined, false, undefined, this),
1510
+ /* @__PURE__ */ jsxDEV6("box", {
1511
+ height: 1,
1512
+ paddingLeft: 1,
1513
+ children: /* @__PURE__ */ jsxDEV6("text", {
1514
+ fg: theme.textMuted,
1515
+ children: filename
1516
+ }, undefined, false, undefined, this)
1517
+ }, undefined, false, undefined, this)
1518
+ ]
1519
+ }, undefined, true, undefined, this);
1520
+ }
1521
+ // src/components/ErrorFallback.tsx
1522
+ import { useKeyboard, useRenderer as useRenderer2 } from "@opentui/solid";
1523
+ import { jsxDEV as jsxDEV7 } from "@opentui/solid/jsx-dev-runtime";
1524
+ function ErrorFallback(props) {
1525
+ const renderer = useRenderer2();
1526
+ useKeyboard((key) => {
1527
+ if (key.name === "r") {
1528
+ props.reset();
1529
+ }
1530
+ if (key.name === "q" || key.raw === "\x03") {
1531
+ renderer.destroy();
1532
+ }
1533
+ });
1534
+ return /* @__PURE__ */ jsxDEV7("box", {
1535
+ flexDirection: "column",
1536
+ padding: 2,
1537
+ children: [
1538
+ /* @__PURE__ */ jsxDEV7("text", {
1539
+ fg: "red",
1540
+ children: [
1541
+ "Error: ",
1542
+ props.error.message
1543
+ ]
1544
+ }, undefined, true, undefined, this),
1545
+ /* @__PURE__ */ jsxDEV7("text", {}, undefined, false, undefined, this),
1546
+ /* @__PURE__ */ jsxDEV7("text", {
1547
+ fg: "gray",
1548
+ children: "Press 'r' to retry or 'q' to quit"
1549
+ }, undefined, false, undefined, this)
1550
+ ]
1551
+ }, undefined, true, undefined, this);
1552
+ }
1553
+ // src/components/CommandPalette.tsx
1554
+ import { For as For2, createSignal as createSignal6, createMemo as createMemo3, Show as Show2 } from "solid-js";
1555
+ import { useKeyboard as useKeyboard2, useRenderer as useRenderer3, useTerminalDimensions } from "@opentui/solid";
1556
+ import { RGBA } from "@opentui/core";
1557
+
1558
+ // src/context/command.tsx
1559
+ import { createContext as createContext4, useContext as useContext4, createSignal as createSignal4 } from "solid-js";
1560
+ import { jsxDEV as jsxDEV8 } from "@opentui/solid/jsx-dev-runtime";
1561
+ var CommandContext = createContext4();
1562
+ function CommandProvider(props) {
1563
+ const [registrations, setRegistrations] = createSignal4([]);
1564
+ const value = {
1565
+ register: (commands) => {
1566
+ setRegistrations((prev) => [...prev, commands]);
1567
+ },
1568
+ entries: () => registrations().flatMap((r) => r())
1569
+ };
1570
+ return /* @__PURE__ */ jsxDEV8(CommandContext.Provider, {
1571
+ value,
1572
+ children: props.children
1573
+ }, undefined, false, undefined, this);
1574
+ }
1575
+ function useCommand() {
1576
+ const ctx = useContext4(CommandContext);
1577
+ if (!ctx)
1578
+ throw new Error("useCommand must be used within CommandProvider");
1579
+ return ctx;
1580
+ }
1581
+
1582
+ // src/context/dialog.tsx
1583
+ import { createContext as createContext5, useContext as useContext5, createSignal as createSignal5 } from "solid-js";
1584
+ import { jsxDEV as jsxDEV9 } from "@opentui/solid/jsx-dev-runtime";
1585
+ var DialogContext = createContext5();
1586
+ function DialogProvider(props) {
1587
+ const [content, setContent] = createSignal5(null);
1588
+ const value = {
1589
+ show: (element) => setContent(() => element),
1590
+ clear: () => setContent(null),
1591
+ isOpen: () => content() !== null,
1592
+ content: () => {
1593
+ const c = content();
1594
+ return c ? c() : null;
1595
+ }
1596
+ };
1597
+ return /* @__PURE__ */ jsxDEV9(DialogContext.Provider, {
1598
+ value,
1599
+ children: props.children
1600
+ }, undefined, false, undefined, this);
1601
+ }
1602
+ function useDialog() {
1603
+ const ctx = useContext5(DialogContext);
1604
+ if (!ctx)
1605
+ throw new Error("useDialog must be used within DialogProvider");
1606
+ return ctx;
1607
+ }
1608
+
1609
+ // src/lib/keybind.ts
1610
+ function matchKey(key, bind) {
1611
+ if (key.name !== bind.name)
1612
+ return false;
1613
+ if ((bind.ctrl ?? false) !== key.ctrl)
1614
+ return false;
1615
+ if ((bind.meta ?? false) !== key.meta)
1616
+ return false;
1617
+ if ((bind.shift ?? false) !== key.shift)
1618
+ return false;
1619
+ return true;
1620
+ }
1621
+ function matchAny(key, binds) {
1622
+ return binds.some((bind) => matchKey(key, bind));
1623
+ }
1624
+ function formatKeybind(bind) {
1625
+ const parts = [];
1626
+ if (bind.ctrl)
1627
+ parts.push("C");
1628
+ if (bind.meta)
1629
+ parts.push("M");
1630
+ if (bind.shift)
1631
+ parts.push("S");
1632
+ parts.push(bind.name);
1633
+ return parts.join("-");
1634
+ }
1635
+
1636
+ // src/components/CommandPalette.tsx
1637
+ import { jsxDEV as jsxDEV10, Fragment as Fragment2 } from "@opentui/solid/jsx-dev-runtime";
1638
+ var CATEGORY_ORDER = ["movement", "selection", "actions"];
1639
+ function CommandPalette() {
1640
+ const dims = useTerminalDimensions();
1641
+ const renderer = useRenderer3();
1642
+ const command = useCommand();
1643
+ const dialog = useDialog();
1644
+ const { theme } = useTheme();
1645
+ const [selectedIndex, setSelectedIndex] = createSignal6(0);
1646
+ const entries = createMemo3(() => {
1647
+ const active = command.entries().filter((cmd) => cmd.isActive());
1648
+ const seen = new Set;
1649
+ return active.filter((cmd) => {
1650
+ if (seen.has(cmd.title))
1651
+ return false;
1652
+ seen.add(cmd.title);
1653
+ return true;
1654
+ });
1655
+ });
1656
+ const groupedEntries = createMemo3(() => {
1657
+ const groups = [];
1658
+ const byCategory = new Map;
1659
+ for (const cmd of entries()) {
1660
+ const list4 = byCategory.get(cmd.category) ?? [];
1661
+ list4.push(cmd);
1662
+ byCategory.set(cmd.category, list4);
1663
+ }
1664
+ for (const cat of CATEGORY_ORDER) {
1665
+ const cmds = byCategory.get(cat);
1666
+ if (cmds && cmds.length > 0) {
1667
+ groups.push({ category: cat, commands: cmds });
1668
+ }
1669
+ }
1670
+ return groups;
1671
+ });
1672
+ const flatEntries = createMemo3(() => {
1673
+ return groupedEntries().flatMap((g) => g.commands);
1674
+ });
1675
+ useKeyboard2((key) => {
1676
+ if (key.ctrl && (key.name === "c" || key.name === "d")) {
1677
+ renderer.destroy();
1678
+ return;
1679
+ }
1680
+ if (key.name === "escape") {
1681
+ dialog.clear();
1682
+ return;
1683
+ }
1684
+ if (key.name === "up" || key.name === "k") {
1685
+ setSelectedIndex((i) => Math.max(0, i - 1));
1686
+ return;
1687
+ }
1688
+ if (key.name === "down" || key.name === "j") {
1689
+ setSelectedIndex((i) => Math.min(flatEntries().length - 1, i + 1));
1690
+ return;
1691
+ }
1692
+ if (key.name === "return") {
1693
+ const entry = flatEntries()[selectedIndex()];
1694
+ if (entry) {
1695
+ dialog.clear();
1696
+ entry.onExecute();
1697
+ }
1698
+ return;
1699
+ }
1700
+ });
1701
+ const width = () => Math.min(40, dims().width - 4);
1702
+ let currentIndex = 0;
1703
+ return /* @__PURE__ */ jsxDEV10("box", {
1704
+ position: "absolute",
1705
+ left: 0,
1706
+ top: 0,
1707
+ width: dims().width,
1708
+ height: dims().height,
1709
+ justifyContent: "center",
1710
+ alignItems: "center",
1711
+ backgroundColor: RGBA.fromInts(0, 0, 0, 150),
1712
+ children: /* @__PURE__ */ jsxDEV10("box", {
1713
+ width: width(),
1714
+ flexDirection: "column",
1715
+ backgroundColor: theme.backgroundPanel,
1716
+ paddingTop: 1,
1717
+ paddingBottom: 1,
1718
+ children: [
1719
+ /* @__PURE__ */ jsxDEV10("box", {
1720
+ height: 1,
1721
+ justifyContent: "center",
1722
+ paddingLeft: 2,
1723
+ paddingRight: 2,
1724
+ children: /* @__PURE__ */ jsxDEV10("text", {
1725
+ children: /* @__PURE__ */ jsxDEV10("span", {
1726
+ style: { bold: true },
1727
+ children: "Commands"
1728
+ }, undefined, false, undefined, this)
1729
+ }, undefined, false, undefined, this)
1730
+ }, undefined, false, undefined, this),
1731
+ /* @__PURE__ */ jsxDEV10("box", {
1732
+ height: 1
1733
+ }, undefined, false, undefined, this),
1734
+ /* @__PURE__ */ jsxDEV10(For2, {
1735
+ each: groupedEntries(),
1736
+ children: (group, groupIndex) => {
1737
+ const startIndex = createMemo3(() => {
1738
+ let idx = 0;
1739
+ const groups = groupedEntries();
1740
+ for (let i = 0;i < groupIndex(); i++) {
1741
+ idx += groups[i].commands.length;
1742
+ }
1743
+ return idx;
1744
+ });
1745
+ return /* @__PURE__ */ jsxDEV10(Fragment2, {
1746
+ children: [
1747
+ /* @__PURE__ */ jsxDEV10(Show2, {
1748
+ when: groupIndex() > 0,
1749
+ children: /* @__PURE__ */ jsxDEV10("box", {
1750
+ height: 1
1751
+ }, undefined, false, undefined, this)
1752
+ }, undefined, false, undefined, this),
1753
+ /* @__PURE__ */ jsxDEV10("box", {
1754
+ paddingLeft: 2,
1755
+ paddingRight: 2,
1756
+ children: /* @__PURE__ */ jsxDEV10("text", {
1757
+ fg: theme.secondary,
1758
+ children: /* @__PURE__ */ jsxDEV10("span", {
1759
+ style: { bold: true },
1760
+ children: group.category
1761
+ }, undefined, false, undefined, this)
1762
+ }, undefined, false, undefined, this)
1763
+ }, undefined, false, undefined, this),
1764
+ /* @__PURE__ */ jsxDEV10(For2, {
1765
+ each: group.commands,
1766
+ children: (entry, cmdIndex) => {
1767
+ const globalIndex = () => startIndex() + cmdIndex();
1768
+ return /* @__PURE__ */ jsxDEV10("box", {
1769
+ flexDirection: "row",
1770
+ justifyContent: "space-between",
1771
+ paddingLeft: 2,
1772
+ paddingRight: 2,
1773
+ children: [
1774
+ /* @__PURE__ */ jsxDEV10("text", {
1775
+ fg: globalIndex() === selectedIndex() ? theme.primary : undefined,
1776
+ children: entry.title
1777
+ }, undefined, false, undefined, this),
1778
+ /* @__PURE__ */ jsxDEV10("box", {
1779
+ flexDirection: "row",
1780
+ gap: 1,
1781
+ children: /* @__PURE__ */ jsxDEV10(For2, {
1782
+ each: entry.keybinds,
1783
+ children: (bind) => /* @__PURE__ */ jsxDEV10("box", {
1784
+ children: /* @__PURE__ */ jsxDEV10("text", {
1785
+ fg: theme.textMuted,
1786
+ children: formatKeybind(bind)
1787
+ }, undefined, false, undefined, this)
1788
+ }, undefined, false, undefined, this)
1789
+ }, undefined, false, undefined, this)
1790
+ }, undefined, false, undefined, this)
1791
+ ]
1792
+ }, undefined, true, undefined, this);
1793
+ }
1794
+ }, undefined, false, undefined, this)
1795
+ ]
1796
+ }, undefined, true, undefined, this);
1797
+ }
1798
+ }, undefined, false, undefined, this)
1799
+ ]
1800
+ }, undefined, true, undefined, this)
1801
+ }, undefined, false, undefined, this);
1802
+ }
1803
+ // src/components/sidebar/FileList.tsx
1804
+ import { For as For3, Show as Show3, createEffect as createEffect3 } from "solid-js";
1805
+
1806
+ // src/context/sidebar.tsx
1807
+ import {
1808
+ createContext as createContext6,
1809
+ useContext as useContext6
1810
+ } from "solid-js";
1811
+ import { jsxDEV as jsxDEV11 } from "@opentui/solid/jsx-dev-runtime";
1812
+ var SidebarContext = createContext6();
1813
+ function SidebarProvider(props) {
1814
+ const value = {
1815
+ activeSection: props.activeSection,
1816
+ isFocused: props.isFocused
1817
+ };
1818
+ return /* @__PURE__ */ jsxDEV11(SidebarContext.Provider, {
1819
+ value,
1820
+ children: props.children
1821
+ }, undefined, false, undefined, this);
1822
+ }
1823
+ function useSidebar() {
1824
+ const ctx = useContext6(SidebarContext);
1825
+ if (!ctx)
1826
+ throw new Error("useSidebar must be used within SidebarProvider");
1827
+ return ctx;
1828
+ }
1829
+
1830
+ // src/components/sidebar/Section.tsx
1831
+ import { jsxDEV as jsxDEV12 } from "@opentui/solid/jsx-dev-runtime";
1832
+ function SectionTitle(props) {
1833
+ return /* @__PURE__ */ jsxDEV12("box", {
1834
+ flexShrink: 0,
1835
+ children: /* @__PURE__ */ jsxDEV12("text", {
1836
+ children: /* @__PURE__ */ jsxDEV12("span", {
1837
+ style: { bold: true },
1838
+ children: props.title
1839
+ }, undefined, false, undefined, this)
1840
+ }, undefined, false, undefined, this)
1841
+ }, undefined, false, undefined, this);
1842
+ }
1843
+ function Title(props) {
1844
+ const { theme } = useTheme();
1845
+ const sidebar = useSidebar();
1846
+ const isActive = () => sidebar.activeSection() === props.section && sidebar.isFocused();
1847
+ const title = () => props.section.charAt(0).toUpperCase() + props.section.slice(1);
1848
+ return /* @__PURE__ */ jsxDEV12("box", {
1849
+ flexShrink: 0,
1850
+ border: ["left"],
1851
+ borderColor: isActive() ? theme.primary : theme.indicatorDefault,
1852
+ backgroundColor: isActive() ? theme.backgroundElement : theme.backgroundPanel,
1853
+ customBorderChars: {
1854
+ ...EmptyBorder,
1855
+ vertical: "\u258C"
1856
+ },
1857
+ children: /* @__PURE__ */ jsxDEV12("text", {
1858
+ children: /* @__PURE__ */ jsxDEV12("span", {
1859
+ style: { bold: true },
1860
+ children: title()
1861
+ }, undefined, false, undefined, this)
1862
+ }, undefined, false, undefined, this)
1863
+ }, undefined, false, undefined, this);
1864
+ }
1865
+ function Section(props) {
1866
+ const { theme } = useTheme();
1867
+ return /* @__PURE__ */ jsxDEV12("box", {
1868
+ flexDirection: "column",
1869
+ backgroundColor: theme.backgroundPanel,
1870
+ flexShrink: 0,
1871
+ children: [
1872
+ /* @__PURE__ */ jsxDEV12(SectionTitle, {
1873
+ title: props.title
1874
+ }, undefined, false, undefined, this),
1875
+ props.children
1876
+ ]
1877
+ }, undefined, true, undefined, this);
1878
+ }
1879
+
1880
+ // src/components/sidebar/FileList.tsx
1881
+ import { jsxDEV as jsxDEV13 } from "@opentui/solid/jsx-dev-runtime";
1882
+ var SCROLL_BUFFER = 2;
1883
+ function fileDiffStats(content) {
1884
+ let added = 0;
1885
+ let removed = 0;
1886
+ for (const line of content.split(`
1887
+ `)) {
1888
+ if (line.startsWith("+++") || line.startsWith("---"))
1889
+ continue;
1890
+ if (line.startsWith("+"))
1891
+ added++;
1892
+ else if (line.startsWith("-"))
1893
+ removed++;
1894
+ }
1895
+ return { added, removed };
1896
+ }
1897
+ function FileList(props) {
1898
+ const { theme } = useTheme();
1899
+ let scrollbox = null;
1900
+ const isSelected = (item) => {
1901
+ if (item.node.type !== "file")
1902
+ return false;
1903
+ const selectedEntryIndex = props.fileIndices()[props.selectedFileIndex()];
1904
+ return item.node.entryIndex === selectedEntryIndex;
1905
+ };
1906
+ const getSelectedDisplayIndex = () => {
1907
+ const selectedEntryIndex = props.fileIndices()[props.selectedFileIndex()];
1908
+ return props.displayItems().findIndex((item) => item.node.type === "file" && item.node.entryIndex === selectedEntryIndex);
1909
+ };
1910
+ createEffect3(() => {
1911
+ const displayIndex = getSelectedDisplayIndex();
1912
+ if (displayIndex < 0 || !scrollbox)
1913
+ return;
1914
+ const viewportHeight = scrollbox.viewport.height;
1915
+ const scrollTop = scrollbox.scrollTop;
1916
+ const itemTop = displayIndex;
1917
+ const itemBottom = displayIndex + 1;
1918
+ if (itemTop < scrollTop + SCROLL_BUFFER) {
1919
+ scrollbox.scrollTo(Math.max(0, itemTop - SCROLL_BUFFER));
1920
+ } else if (itemBottom > scrollTop + viewportHeight - SCROLL_BUFFER) {
1921
+ scrollbox.scrollTo(itemBottom - viewportHeight + SCROLL_BUFFER);
1922
+ }
1923
+ });
1924
+ return /* @__PURE__ */ jsxDEV13("box", {
1925
+ flexGrow: 1,
1926
+ flexDirection: "column",
1927
+ backgroundColor: theme.backgroundPanel,
1928
+ children: [
1929
+ /* @__PURE__ */ jsxDEV13(Title, {
1930
+ section: "files"
1931
+ }, undefined, false, undefined, this),
1932
+ /* @__PURE__ */ jsxDEV13(Show3, {
1933
+ when: props.loading(),
1934
+ children: /* @__PURE__ */ jsxDEV13("box", {
1935
+ children: /* @__PURE__ */ jsxDEV13("text", {
1936
+ children: "Loading..."
1937
+ }, undefined, false, undefined, this)
1938
+ }, undefined, false, undefined, this)
1939
+ }, undefined, false, undefined, this),
1940
+ /* @__PURE__ */ jsxDEV13(Show3, {
1941
+ when: !props.loading() && props.displayItems().length === 0,
1942
+ children: /* @__PURE__ */ jsxDEV13("box", {
1943
+ children: /* @__PURE__ */ jsxDEV13("text", {
1944
+ children: "No changes found"
1945
+ }, undefined, false, undefined, this)
1946
+ }, undefined, false, undefined, this)
1947
+ }, undefined, false, undefined, this),
1948
+ /* @__PURE__ */ jsxDEV13(Show3, {
1949
+ when: !props.loading() && props.displayItems().length > 0,
1950
+ children: /* @__PURE__ */ jsxDEV13("scrollbox", {
1951
+ ref: (r) => scrollbox = r,
1952
+ flexGrow: 1,
1953
+ children: /* @__PURE__ */ jsxDEV13(For3, {
1954
+ each: props.displayItems(),
1955
+ children: (item) => {
1956
+ if (item.node.type === "dir") {
1957
+ return /* @__PURE__ */ jsxDEV13("box", {
1958
+ flexDirection: "row",
1959
+ children: [
1960
+ /* @__PURE__ */ jsxDEV13(Show3, {
1961
+ when: item.depth > 0,
1962
+ children: /* @__PURE__ */ jsxDEV13("text", {
1963
+ children: " ".repeat(item.depth)
1964
+ }, undefined, false, undefined, this)
1965
+ }, undefined, false, undefined, this),
1966
+ /* @__PURE__ */ jsxDEV13("text", {
1967
+ children: "\u25BE "
1968
+ }, undefined, false, undefined, this),
1969
+ /* @__PURE__ */ jsxDEV13("text", {
1970
+ children: item.node.name
1971
+ }, undefined, false, undefined, this)
1972
+ ]
1973
+ }, undefined, true, undefined, this);
1974
+ }
1975
+ const textColor = () => isSelected(item) ? theme.primary : undefined;
1976
+ const bgColor = () => isSelected(item) ? theme.backgroundElement : theme.backgroundPanel;
1977
+ const entryIndex = item.node.entryIndex;
1978
+ const entry = typeof entryIndex === "number" ? props.entries()[entryIndex] : undefined;
1979
+ const stats = entry ? fileDiffStats(entry.content) : null;
1980
+ return /* @__PURE__ */ jsxDEV13("box", {
1981
+ flexDirection: "row",
1982
+ gap: 1,
1983
+ backgroundColor: bgColor(),
1984
+ children: [
1985
+ /* @__PURE__ */ jsxDEV13(Show3, {
1986
+ when: item.depth > 0,
1987
+ children: /* @__PURE__ */ jsxDEV13("text", {
1988
+ children: " ".repeat(item.depth)
1989
+ }, undefined, false, undefined, this)
1990
+ }, undefined, false, undefined, this),
1991
+ /* @__PURE__ */ jsxDEV13("text", {
1992
+ fg: theme.textMuted,
1993
+ children: item.node.status
1994
+ }, undefined, false, undefined, this),
1995
+ /* @__PURE__ */ jsxDEV13("text", {
1996
+ fg: textColor(),
1997
+ children: item.node.name
1998
+ }, undefined, false, undefined, this),
1999
+ /* @__PURE__ */ jsxDEV13(Show3, {
2000
+ when: !!stats && stats.added > 0,
2001
+ children: /* @__PURE__ */ jsxDEV13("text", {
2002
+ fg: theme.diffSignAdded,
2003
+ children: `+${stats.added}`
2004
+ }, undefined, false, undefined, this)
2005
+ }, undefined, false, undefined, this),
2006
+ /* @__PURE__ */ jsxDEV13(Show3, {
2007
+ when: !!stats && stats.removed > 0,
2008
+ children: /* @__PURE__ */ jsxDEV13("text", {
2009
+ fg: theme.diffSignRemoved,
2010
+ children: `-${stats.removed}`
2011
+ }, undefined, false, undefined, this)
2012
+ }, undefined, false, undefined, this)
2013
+ ]
2014
+ }, undefined, true, undefined, this);
2015
+ }
2016
+ }, undefined, false, undefined, this)
2017
+ }, undefined, false, undefined, this)
2018
+ }, undefined, false, undefined, this)
2019
+ ]
2020
+ }, undefined, true, undefined, this);
2021
+ }
2022
+ // src/components/sidebar/CommentList.tsx
2023
+ import { For as For4, Show as Show4, createEffect as createEffect4 } from "solid-js";
2024
+ import { jsxDEV as jsxDEV14 } from "@opentui/solid/jsx-dev-runtime";
2025
+ var SCROLL_BUFFER2 = 2;
2026
+ function getShortFile(file) {
2027
+ const segments = file.split("/");
2028
+ return segments[segments.length - 1] || file;
2029
+ }
2030
+ function getPatchDisplay(patchId, patches, workingTreePatchId) {
2031
+ if (patchId === null)
2032
+ return "?";
2033
+ if (workingTreePatchId !== null && patchId === workingTreePatchId)
2034
+ return "WT";
2035
+ const patch = patches.find((p) => p.id === patchId);
2036
+ if (!patch)
2037
+ return "?";
2038
+ return `P${patch.seq}`;
2039
+ }
2040
+ function CommentList(props) {
2041
+ const { theme } = useTheme();
2042
+ const review = useReview();
2043
+ let scrollbox = null;
2044
+ createEffect4(() => {
2045
+ const idx = props.selectedIndex();
2046
+ if (idx < 0 || !scrollbox)
2047
+ return;
2048
+ const viewportHeight = scrollbox.viewport.height;
2049
+ const scrollTop = scrollbox.scrollTop;
2050
+ if (idx < scrollTop + SCROLL_BUFFER2) {
2051
+ scrollbox.scrollTo(Math.max(0, idx - SCROLL_BUFFER2));
2052
+ } else if (idx + 1 > scrollTop + viewportHeight - SCROLL_BUFFER2) {
2053
+ scrollbox.scrollTo(idx - viewportHeight + SCROLL_BUFFER2 + 1);
2054
+ }
2055
+ });
2056
+ const commentCount = () => props.comments().length;
2057
+ return /* @__PURE__ */ jsxDEV14("box", {
2058
+ flexDirection: "column",
2059
+ children: [
2060
+ /* @__PURE__ */ jsxDEV14(Title, {
2061
+ section: "comments"
2062
+ }, undefined, false, undefined, this),
2063
+ /* @__PURE__ */ jsxDEV14(Show4, {
2064
+ when: !props.loading() && commentCount() === 0,
2065
+ children: /* @__PURE__ */ jsxDEV14("box", {
2066
+ children: /* @__PURE__ */ jsxDEV14("text", {
2067
+ fg: theme.textMuted,
2068
+ children: "No comments"
2069
+ }, undefined, false, undefined, this)
2070
+ }, undefined, false, undefined, this)
2071
+ }, undefined, false, undefined, this),
2072
+ /* @__PURE__ */ jsxDEV14(Show4, {
2073
+ when: !props.loading() && commentCount() > 0,
2074
+ children: /* @__PURE__ */ jsxDEV14("scrollbox", {
2075
+ ref: (r) => scrollbox = r,
2076
+ flexGrow: 1,
2077
+ children: /* @__PURE__ */ jsxDEV14(For4, {
2078
+ each: props.comments(),
2079
+ children: (comment, index) => {
2080
+ const isSelected = () => index() === props.selectedIndex();
2081
+ const textColor = () => isSelected() && props.focused() ? theme.primary : undefined;
2082
+ const bgColor = () => isSelected() && props.focused() ? theme.backgroundElement : theme.backgroundPanel;
2083
+ return /* @__PURE__ */ jsxDEV14("box", {
2084
+ flexDirection: "row",
2085
+ justifyContent: "space-between",
2086
+ backgroundColor: bgColor(),
2087
+ children: [
2088
+ /* @__PURE__ */ jsxDEV14("box", {
2089
+ flexDirection: "row",
2090
+ gap: 1,
2091
+ children: [
2092
+ /* @__PURE__ */ jsxDEV14("text", {
2093
+ fg: textColor(),
2094
+ children: getShortFile(comment.file)
2095
+ }, undefined, false, undefined, this),
2096
+ /* @__PURE__ */ jsxDEV14("text", {
2097
+ fg: theme.textMuted,
2098
+ children: `${comment.startRow}:${comment.endRow}`
2099
+ }, undefined, false, undefined, this)
2100
+ ]
2101
+ }, undefined, true, undefined, this),
2102
+ /* @__PURE__ */ jsxDEV14("text", {
2103
+ fg: theme.textMuted,
2104
+ children: getPatchDisplay(comment.patchId, review.patches(), review.workingTreePatchId())
2105
+ }, undefined, false, undefined, this)
2106
+ ]
2107
+ }, undefined, true, undefined, this);
2108
+ }
2109
+ }, undefined, false, undefined, this)
2110
+ }, undefined, false, undefined, this)
2111
+ }, undefined, false, undefined, this)
2112
+ ]
2113
+ }, undefined, true, undefined, this);
2114
+ }
2115
+ // src/components/sidebar/PatchList.tsx
2116
+ import {
2117
+ For as For5,
2118
+ Show as Show5,
2119
+ createEffect as createEffect5,
2120
+ createSignal as createSignal7,
2121
+ onCleanup as onCleanup2,
2122
+ onMount as onMount4
2123
+ } from "solid-js";
2124
+ import { jsxDEV as jsxDEV15, Fragment as Fragment3 } from "@opentui/solid/jsx-dev-runtime";
2125
+ var SCROLL_BUFFER3 = 2;
2126
+ function formatTime(timestamp) {
2127
+ const date = new Date(timestamp);
2128
+ return date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
2129
+ }
2130
+ function diffStats(content) {
2131
+ let added = 0;
2132
+ let removed = 0;
2133
+ for (const line of content.split(`
2134
+ `)) {
2135
+ if (line.startsWith("+++") || line.startsWith("---"))
2136
+ continue;
2137
+ if (line.startsWith("+"))
2138
+ added++;
2139
+ else if (line.startsWith("-"))
2140
+ removed++;
2141
+ }
2142
+ return { added, removed };
2143
+ }
2144
+ function sumEntryStats(entries) {
2145
+ let added = 0;
2146
+ let removed = 0;
2147
+ for (const e of entries) {
2148
+ const s = diffStats(e.content);
2149
+ added += s.added;
2150
+ removed += s.removed;
2151
+ }
2152
+ return { added, removed };
2153
+ }
2154
+ function PatchList(props) {
2155
+ const { theme } = useTheme();
2156
+ let scrollbox = null;
2157
+ const [now, setNow] = createSignal7(Date.now());
2158
+ onMount4(() => {
2159
+ let interval = null;
2160
+ const start = () => {
2161
+ const ms = Date.now();
2162
+ const nextMinute = Math.ceil(ms / 60000) * 60000;
2163
+ const delay = Math.max(0, nextMinute - ms);
2164
+ const timeout = setTimeout(() => {
2165
+ setNow(Date.now());
2166
+ interval = setInterval(() => setNow(Date.now()), 60000);
2167
+ }, delay);
2168
+ onCleanup2(() => clearTimeout(timeout));
2169
+ };
2170
+ start();
2171
+ onCleanup2(() => {
2172
+ if (interval)
2173
+ clearInterval(interval);
2174
+ });
2175
+ });
2176
+ createEffect5(() => {
2177
+ const idx = props.selectedIndex();
2178
+ if (idx < 0 || !scrollbox)
2179
+ return;
2180
+ const viewportHeight = scrollbox.viewport.height;
2181
+ const scrollTop = scrollbox.scrollTop;
2182
+ if (idx < scrollTop + SCROLL_BUFFER3) {
2183
+ scrollbox.scrollTo(Math.max(0, idx - SCROLL_BUFFER3));
2184
+ } else if (idx + 1 > scrollTop + viewportHeight - SCROLL_BUFFER3) {
2185
+ scrollbox.scrollTo(idx - viewportHeight + SCROLL_BUFFER3 + 1);
2186
+ }
2187
+ });
2188
+ const items = () => {
2189
+ const patchList = props.patches();
2190
+ const wt = props.workingTreePatchId();
2191
+ return [
2192
+ { type: "workspace", id: wt, seq: -1, createdAt: now() },
2193
+ ...patchList.slice().filter((p) => wt === null ? true : p.id !== wt).sort((a, b) => b.seq - a.seq).map((p) => ({ type: "patch", ...p }))
2194
+ ];
2195
+ };
2196
+ const patchCount = () => {
2197
+ const wt = props.workingTreePatchId();
2198
+ const list4 = props.patches();
2199
+ return wt === null ? list4.length : list4.filter((p) => p.id !== wt).length;
2200
+ };
2201
+ return /* @__PURE__ */ jsxDEV15("box", {
2202
+ flexDirection: "column",
2203
+ children: [
2204
+ /* @__PURE__ */ jsxDEV15(Title, {
2205
+ section: "patches"
2206
+ }, undefined, false, undefined, this),
2207
+ /* @__PURE__ */ jsxDEV15(Show5, {
2208
+ when: props.loading(),
2209
+ children: /* @__PURE__ */ jsxDEV15("box", {
2210
+ children: /* @__PURE__ */ jsxDEV15("text", {
2211
+ fg: theme.textMuted,
2212
+ children: "Loading..."
2213
+ }, undefined, false, undefined, this)
2214
+ }, undefined, false, undefined, this)
2215
+ }, undefined, false, undefined, this),
2216
+ /* @__PURE__ */ jsxDEV15(Show5, {
2217
+ when: !props.loading() && patchCount() === 0,
2218
+ children: /* @__PURE__ */ jsxDEV15("box", {
2219
+ children: /* @__PURE__ */ jsxDEV15("box", {
2220
+ flexDirection: "row",
2221
+ justifyContent: "space-between",
2222
+ children: [
2223
+ /* @__PURE__ */ jsxDEV15("box", {
2224
+ flexDirection: "row",
2225
+ gap: 1,
2226
+ children: [
2227
+ /* @__PURE__ */ jsxDEV15(Show5, {
2228
+ when: props.workingTreePatchId() !== null && props.activePatchId() === props.workingTreePatchId(),
2229
+ children: /* @__PURE__ */ jsxDEV15("text", {
2230
+ fg: theme.primary,
2231
+ children: "\u25B8"
2232
+ }, undefined, false, undefined, this)
2233
+ }, undefined, false, undefined, this),
2234
+ /* @__PURE__ */ jsxDEV15(Show5, {
2235
+ when: props.workingTreePatchId() === null || props.activePatchId() !== props.workingTreePatchId(),
2236
+ children: /* @__PURE__ */ jsxDEV15("text", {
2237
+ fg: theme.textMuted,
2238
+ children: " "
2239
+ }, undefined, false, undefined, this)
2240
+ }, undefined, false, undefined, this),
2241
+ /* @__PURE__ */ jsxDEV15("text", {
2242
+ fg: props.focused() ? theme.primary : undefined,
2243
+ children: "Working tree"
2244
+ }, undefined, false, undefined, this),
2245
+ (() => {
2246
+ const s = sumEntryStats(props.workspaceEntries());
2247
+ if (s.added === 0 && s.removed === 0)
2248
+ return null;
2249
+ return /* @__PURE__ */ jsxDEV15(Fragment3, {
2250
+ children: [
2251
+ /* @__PURE__ */ jsxDEV15("text", {
2252
+ fg: theme.diffSignAdded,
2253
+ children: "+"
2254
+ }, undefined, false, undefined, this),
2255
+ /* @__PURE__ */ jsxDEV15("text", {
2256
+ fg: theme.textMuted,
2257
+ children: s.added
2258
+ }, undefined, false, undefined, this),
2259
+ /* @__PURE__ */ jsxDEV15("text", {
2260
+ fg: theme.diffSignRemoved,
2261
+ children: "-"
2262
+ }, undefined, false, undefined, this),
2263
+ /* @__PURE__ */ jsxDEV15("text", {
2264
+ fg: theme.textMuted,
2265
+ children: s.removed
2266
+ }, undefined, false, undefined, this)
2267
+ ]
2268
+ }, undefined, true, undefined, this);
2269
+ })()
2270
+ ]
2271
+ }, undefined, true, undefined, this),
2272
+ /* @__PURE__ */ jsxDEV15("text", {
2273
+ fg: theme.textMuted,
2274
+ children: formatTime(now())
2275
+ }, undefined, false, undefined, this)
2276
+ ]
2277
+ }, undefined, true, undefined, this)
2278
+ }, undefined, false, undefined, this)
2279
+ }, undefined, false, undefined, this),
2280
+ /* @__PURE__ */ jsxDEV15(Show5, {
2281
+ when: !props.loading() && patchCount() > 0,
2282
+ children: /* @__PURE__ */ jsxDEV15("scrollbox", {
2283
+ ref: (r) => scrollbox = r,
2284
+ flexGrow: 1,
2285
+ children: /* @__PURE__ */ jsxDEV15(For5, {
2286
+ each: items(),
2287
+ children: (item, index) => {
2288
+ const isSelected = () => index() === props.selectedIndex();
2289
+ const isActive = () => {
2290
+ if (item.type === "workspace")
2291
+ return item.id !== null && props.activePatchId() === item.id;
2292
+ return props.activePatchId() === item.id;
2293
+ };
2294
+ const textColor = () => isActive() ? theme.primary : undefined;
2295
+ const bgColor = () => isSelected() && props.focused() ? theme.backgroundElement : theme.backgroundPanel;
2296
+ const label = () => item.type === "workspace" ? "Working tree" : `P${item.seq}`;
2297
+ const delta = () => item.type === "workspace" ? sumEntryStats(props.workspaceEntries()) : diffStats(item.content);
2298
+ return /* @__PURE__ */ jsxDEV15("box", {
2299
+ flexDirection: "row",
2300
+ justifyContent: "space-between",
2301
+ backgroundColor: bgColor(),
2302
+ children: [
2303
+ /* @__PURE__ */ jsxDEV15("box", {
2304
+ flexDirection: "row",
2305
+ gap: 1,
2306
+ children: [
2307
+ /* @__PURE__ */ jsxDEV15("text", {
2308
+ fg: textColor(),
2309
+ children: label()
2310
+ }, undefined, false, undefined, this),
2311
+ /* @__PURE__ */ jsxDEV15(Show5, {
2312
+ when: delta() && (delta().added > 0 || delta().removed > 0),
2313
+ children: [
2314
+ /* @__PURE__ */ jsxDEV15("text", {
2315
+ fg: theme.diffSignAdded,
2316
+ children: [
2317
+ "+",
2318
+ delta().added
2319
+ ]
2320
+ }, undefined, true, undefined, this),
2321
+ /* @__PURE__ */ jsxDEV15("text", {
2322
+ fg: theme.diffSignRemoved,
2323
+ children: [
2324
+ "-",
2325
+ delta().removed
2326
+ ]
2327
+ }, undefined, true, undefined, this)
2328
+ ]
2329
+ }, undefined, true, undefined, this)
2330
+ ]
2331
+ }, undefined, true, undefined, this),
2332
+ /* @__PURE__ */ jsxDEV15("text", {
2333
+ fg: theme.textMuted,
2334
+ children: formatTime(item.createdAt)
2335
+ }, undefined, false, undefined, this)
2336
+ ]
2337
+ }, undefined, true, undefined, this);
2338
+ }
2339
+ }, undefined, false, undefined, this)
2340
+ }, undefined, false, undefined, this)
2341
+ }, undefined, false, undefined, this)
2342
+ ]
2343
+ }, undefined, true, undefined, this);
2344
+ }
2345
+ // src/components/sidebar/ServerStatus.tsx
2346
+ import { jsxDEV as jsxDEV16 } from "@opentui/solid/jsx-dev-runtime";
2347
+ function ServerStatus() {
2348
+ const { theme } = useTheme();
2349
+ const server = useServer();
2350
+ return /* @__PURE__ */ jsxDEV16(Section, {
2351
+ title: "Server",
2352
+ children: /* @__PURE__ */ jsxDEV16("box", {
2353
+ flexDirection: "row",
2354
+ gap: 1,
2355
+ backgroundColor: theme.backgroundPanel,
2356
+ height: 1,
2357
+ children: [
2358
+ /* @__PURE__ */ jsxDEV16("text", {
2359
+ fg: server.connected() ? theme.connected : theme.disconnected,
2360
+ children: "\u2022"
2361
+ }, undefined, false, undefined, this),
2362
+ /* @__PURE__ */ jsxDEV16("text", {
2363
+ fg: server.connected() ? theme.text : theme.textMuted,
2364
+ children: server.url() ?? "disconnected"
2365
+ }, undefined, false, undefined, this)
2366
+ ]
2367
+ }, undefined, true, undefined, this)
2368
+ }, undefined, false, undefined, this);
2369
+ }
2370
+ // src/components/sidebar/ProjectStatus.tsx
2371
+ import { Show as Show6 } from "solid-js";
2372
+ import { basename, dirname as dirname2 } from "path";
2373
+ import { jsxDEV as jsxDEV17 } from "@opentui/solid/jsx-dev-runtime";
2374
+ function ProjectStatus(props) {
2375
+ const { theme } = useTheme();
2376
+ const displayName = () => {
2377
+ const root = props.repoRoot();
2378
+ return root ? basename(root) : null;
2379
+ };
2380
+ return /* @__PURE__ */ jsxDEV17(Section, {
2381
+ title: "Review",
2382
+ children: [
2383
+ /* @__PURE__ */ jsxDEV17(Show6, {
2384
+ when: displayName(),
2385
+ children: /* @__PURE__ */ jsxDEV17("box", {
2386
+ flexDirection: "row",
2387
+ flexShrink: 0,
2388
+ children: [
2389
+ /* @__PURE__ */ jsxDEV17("text", {
2390
+ fg: theme.text,
2391
+ children: displayName()
2392
+ }, undefined, false, undefined, this),
2393
+ /* @__PURE__ */ jsxDEV17("text", {
2394
+ fg: theme.textMuted,
2395
+ children: [
2396
+ " (",
2397
+ props.commentCount(),
2398
+ " comments)"
2399
+ ]
2400
+ }, undefined, true, undefined, this)
2401
+ ]
2402
+ }, undefined, true, undefined, this)
2403
+ }, undefined, false, undefined, this),
2404
+ /* @__PURE__ */ jsxDEV17(Show6, {
2405
+ when: props.repoRoot(),
2406
+ children: /* @__PURE__ */ jsxDEV17("box", {
2407
+ flexDirection: "row",
2408
+ flexShrink: 0,
2409
+ children: [
2410
+ /* @__PURE__ */ jsxDEV17("text", {
2411
+ fg: theme.textMuted,
2412
+ children: [
2413
+ dirname2(props.repoRoot()),
2414
+ "/"
2415
+ ]
2416
+ }, undefined, true, undefined, this),
2417
+ /* @__PURE__ */ jsxDEV17("text", {
2418
+ fg: theme.text,
2419
+ children: basename(props.repoRoot())
2420
+ }, undefined, false, undefined, this)
2421
+ ]
2422
+ }, undefined, true, undefined, this)
2423
+ }, undefined, false, undefined, this)
2424
+ ]
2425
+ }, undefined, true, undefined, this);
2426
+ }
2427
+ // src/lib/tree.ts
2428
+ function buildFileTree(entries) {
2429
+ const root = [];
2430
+ for (let i = 0;i < entries.length; i++) {
2431
+ const entry = entries[i];
2432
+ const parts = entry.file.split("/");
2433
+ insertPath(root, parts, 0, entry, i);
2434
+ }
2435
+ sortTree(root);
2436
+ return root;
2437
+ }
2438
+ function insertPath(nodes, parts, partIndex, entry, entryIndex) {
2439
+ if (partIndex >= parts.length)
2440
+ return;
2441
+ const name = parts[partIndex];
2442
+ const isLastPart = partIndex === parts.length - 1;
2443
+ const fullPath = parts.slice(0, partIndex + 1).join("/");
2444
+ let node = nodes.find((n) => n.name === name);
2445
+ if (!node) {
2446
+ node = {
2447
+ name,
2448
+ path: fullPath,
2449
+ type: isLastPart ? "file" : "dir",
2450
+ children: []
2451
+ };
2452
+ if (isLastPart) {
2453
+ node.status = entry.isDeleted ? "D" : entry.isNew ? "A" : "M";
2454
+ node.entryIndex = entryIndex;
2455
+ }
2456
+ nodes.push(node);
2457
+ }
2458
+ if (!isLastPart && node) {
2459
+ insertPath(node.children, parts, partIndex + 1, entry, entryIndex);
2460
+ }
2461
+ }
2462
+ function sortTree(nodes) {
2463
+ nodes.sort((a, b) => {
2464
+ if (a.type !== b.type) {
2465
+ return a.type === "dir" ? -1 : 1;
2466
+ }
2467
+ return a.name.localeCompare(b.name);
2468
+ });
2469
+ for (const node of nodes) {
2470
+ if (node.children.length > 0) {
2471
+ sortTree(node.children);
2472
+ }
2473
+ }
2474
+ }
2475
+ function flattenTree(nodes) {
2476
+ const items = [];
2477
+ flattenNodes(nodes, 0, items);
2478
+ return items;
2479
+ }
2480
+ function flattenNodes(nodes, depth, items) {
2481
+ for (const node of nodes) {
2482
+ if (node.type === "dir") {
2483
+ const collapsed = collapseDir(node);
2484
+ items.push({ node: collapsed, depth });
2485
+ flattenNodes(collapsed.children, depth + 1, items);
2486
+ } else {
2487
+ items.push({ node, depth });
2488
+ }
2489
+ }
2490
+ }
2491
+ function collapseDir(node) {
2492
+ let current = node;
2493
+ const nameParts = [current.name];
2494
+ while (current.children.length === 1 && current.children[0].type === "dir") {
2495
+ current = current.children[0];
2496
+ nameParts.push(current.name);
2497
+ }
2498
+ if (nameParts.length === 1) {
2499
+ return node;
2500
+ }
2501
+ return {
2502
+ name: nameParts.join("/"),
2503
+ path: current.path,
2504
+ type: "dir",
2505
+ children: current.children
2506
+ };
2507
+ }
2508
+ function getFileIndices(items) {
2509
+ const indices = [];
2510
+ for (const item of items) {
2511
+ if (item.node.type === "file" && item.node.entryIndex !== undefined) {
2512
+ indices.push(item.node.entryIndex);
2513
+ }
2514
+ }
2515
+ return indices;
2516
+ }
2517
+
2518
+ // src/context/exit.tsx
2519
+ import { createContext as createContext7, useContext as useContext7 } from "solid-js";
2520
+ import { useRenderer as useRenderer4 } from "@opentui/solid";
2521
+ import { jsxDEV as jsxDEV18 } from "@opentui/solid/jsx-dev-runtime";
2522
+ var ExitContext = createContext7();
2523
+ function ExitProvider(props) {
2524
+ const renderer = useRenderer4();
2525
+ const cleanupFns = [];
2526
+ const registerCleanup = (fn) => {
2527
+ cleanupFns.push(fn);
2528
+ };
2529
+ const exit = async (reason) => {
2530
+ for (const fn of cleanupFns) {
2531
+ try {
2532
+ await fn();
2533
+ } catch {}
2534
+ }
2535
+ renderer.destroy();
2536
+ if (reason) {
2537
+ const message = reason instanceof Error ? reason.message : String(reason);
2538
+ process.stderr.write(message + `
2539
+ `);
2540
+ }
2541
+ process.exit(0);
2542
+ };
2543
+ return /* @__PURE__ */ jsxDEV18(ExitContext.Provider, {
2544
+ value: { exit, registerCleanup },
2545
+ children: props.children
2546
+ }, undefined, false, undefined, this);
2547
+ }
2548
+ function useExit() {
2549
+ const ctx = useContext7(ExitContext);
2550
+ if (!ctx)
2551
+ throw new Error("useExit must be used within ExitProvider");
2552
+ return ctx;
2553
+ }
2554
+
2555
+ // src/Review.tsx
2556
+ import { jsxDEV as jsxDEV19 } from "@opentui/solid/jsx-dev-runtime";
2557
+ function Review(props = {}) {
2558
+ const repoPath = props.repoPath ?? process.argv[2] ?? ".";
2559
+ return /* @__PURE__ */ jsxDEV19(ThemeProvider, {
2560
+ children: /* @__PURE__ */ jsxDEV19(ExitProvider, {
2561
+ children: /* @__PURE__ */ jsxDEV19(ServerProvider, {
2562
+ children: /* @__PURE__ */ jsxDEV19(ReviewProvider, {
2563
+ repoPath,
2564
+ children: /* @__PURE__ */ jsxDEV19(DialogProvider, {
2565
+ children: /* @__PURE__ */ jsxDEV19(CommandProvider, {
2566
+ children: /* @__PURE__ */ jsxDEV19(App, {
2567
+ repoPath
2568
+ }, undefined, false, undefined, this)
2569
+ }, undefined, false, undefined, this)
2570
+ }, undefined, false, undefined, this)
2571
+ }, undefined, false, undefined, this)
2572
+ }, undefined, false, undefined, this)
2573
+ }, undefined, false, undefined, this)
2574
+ }, undefined, false, undefined, this);
2575
+ }
2576
+ function App(props) {
2577
+ const dims = useTerminalDimensions2();
2578
+ const dialog = useDialog();
2579
+ const command = useCommand();
2580
+ const { theme } = useTheme();
2581
+ const { exit } = useExit();
2582
+ const review = useReview();
2583
+ const displayItems = createMemo4(() => {
2584
+ const e = review.entries();
2585
+ if (e.length === 0)
2586
+ return [];
2587
+ const tree = buildFileTree(e);
2588
+ return flattenTree(tree);
2589
+ });
2590
+ const fileIndices = createMemo4(() => getFileIndices(displayItems()));
2591
+ createEffect6(() => {
2592
+ const indices = fileIndices();
2593
+ const entries = review.entries();
2594
+ const current = review.selectedFilePath();
2595
+ if (indices.length === 0) {
2596
+ review.setSelectedFilePath(null);
2597
+ return;
2598
+ }
2599
+ if (!current || !entries.some((e) => e.file === current)) {
2600
+ const firstEntryIndex = indices[0];
2601
+ review.setSelectedFilePath(entries[firstEntryIndex]?.file ?? null);
2602
+ }
2603
+ });
2604
+ const [focusPanel, setFocusPanel] = createSignal8("sidebar");
2605
+ const [sidebarMode, setSidebarMode] = createSignal8("files");
2606
+ const [selectedHunkIndex, setSelectedHunkIndex] = createSignal8(0);
2607
+ const [selectionMode, setSelectionMode] = createSignal8("hunk");
2608
+ const [selectedRange, setSelectedRange] = createSignal8(null);
2609
+ const [cursorRow, setCursorRow] = createSignal8(1);
2610
+ const [editorAnchor, setEditorAnchor] = createSignal8(null);
2611
+ const [selectedCommentIndex, setSelectedCommentIndex] = createSignal8(0);
2612
+ const [selectedPatchIndex, setSelectedPatchIndex] = createSignal8(0);
2613
+ const selectedFileNavIndex = createMemo4(() => {
2614
+ const indices = fileIndices();
2615
+ if (indices.length === 0)
2616
+ return 0;
2617
+ const entries = review.entries();
2618
+ const path2 = review.selectedFilePath();
2619
+ if (!path2)
2620
+ return 0;
2621
+ const entryIndex = entries.findIndex((e) => e.file === path2);
2622
+ if (entryIndex === -1)
2623
+ return 0;
2624
+ const navIndex = indices.indexOf(entryIndex);
2625
+ return navIndex === -1 ? 0 : navIndex;
2626
+ });
2627
+ const selectedEntry = () => {
2628
+ const entries = review.entries();
2629
+ if (entries.length === 0)
2630
+ return;
2631
+ const path2 = review.selectedFilePath();
2632
+ if (path2) {
2633
+ const found = entries.find((e) => e.file === path2);
2634
+ if (found)
2635
+ return found;
2636
+ }
2637
+ const firstEntryIndex = fileIndices()[0];
2638
+ if (firstEntryIndex !== undefined)
2639
+ return entries[firstEntryIndex];
2640
+ return entries[0];
2641
+ };
2642
+ const diffModel = createMemo4(() => {
2643
+ const entry = selectedEntry();
2644
+ if (!entry)
2645
+ return { hunks: [], totalRows: 0 };
2646
+ return parseFileDiff(entry.content, entry.file);
2647
+ });
2648
+ const hunkCount = createMemo4(() => diffModel().hunks.length);
2649
+ const totalRows = createMemo4(() => diffModel().totalRows);
2650
+ const selectedHunkRange = createMemo4(() => {
2651
+ const entry = selectedEntry();
2652
+ if (!entry)
2653
+ return null;
2654
+ const hunk = diffModel().hunks[selectedHunkIndex()];
2655
+ if (!hunk)
2656
+ return null;
2657
+ return { startRow: hunk.startRow, endRow: hunk.endRow };
2658
+ });
2659
+ const [previousFile, setPreviousFile] = createSignal8(null);
2660
+ createEffect6(() => {
2661
+ review.selectedFilePath();
2662
+ review.activePatchId();
2663
+ const currentFile = selectedEntry()?.file ?? null;
2664
+ const prevFile = previousFile();
2665
+ if (currentFile !== prevFile) {
2666
+ setSelectedHunkIndex(0);
2667
+ setSelectionMode("hunk");
2668
+ setSelectedRange(null);
2669
+ setCursorRow(1);
2670
+ } else if (currentFile !== null) {
2671
+ const maxHunk = Math.max(0, hunkCount() - 1);
2672
+ setSelectedHunkIndex((i) => Math.min(i, maxHunk));
2673
+ const maxRow = totalRows();
2674
+ if (maxRow > 0) {
2675
+ setSelectedRange((range) => {
2676
+ if (!range)
2677
+ return null;
2678
+ const clippedStart = Math.min(range.startRow, maxRow);
2679
+ const clippedEnd = Math.min(range.endRow, maxRow);
2680
+ if (clippedStart === clippedEnd && clippedStart === maxRow && maxRow > 1) {
2681
+ return { startRow: maxRow - 1, endRow: maxRow };
2682
+ }
2683
+ return { startRow: clippedStart, endRow: clippedEnd };
2684
+ });
2685
+ setCursorRow((r) => Math.min(Math.max(1, r), maxRow));
2686
+ }
2687
+ }
2688
+ setPreviousFile(currentFile);
2689
+ });
2690
+ const clampRow = (row) => {
2691
+ const max = Math.max(1, totalRows());
2692
+ return Math.min(Math.max(1, row), max);
2693
+ };
2694
+ const setSelectedNavIndex = (navIndex) => {
2695
+ const entryIndex = fileIndices()[navIndex];
2696
+ const entry = entryIndex !== undefined ? review.entries()[entryIndex] : null;
2697
+ review.setSelectedFilePath(entry?.file ?? null);
2698
+ };
2699
+ const navigateUp = () => {
2700
+ const panel = focusPanel();
2701
+ if (panel === "sidebar") {
2702
+ if (sidebarMode() === "files") {
2703
+ setSelectedNavIndex(Math.max(0, selectedFileNavIndex() - 1));
2704
+ } else if (sidebarMode() === "patches") {
2705
+ setSelectedPatchIndex((i) => Math.max(0, i - 1));
2706
+ } else {
2707
+ setSelectedCommentIndex((i) => Math.max(0, i - 1));
2708
+ }
2709
+ return;
2710
+ }
2711
+ if (panel === "diff" && selectionMode() === "line") {
2712
+ const next = clampRow(cursorRow() - 1);
2713
+ setCursorRow(next);
2714
+ setSelectedRange({ startRow: next, endRow: next });
2715
+ return;
2716
+ }
2717
+ if (panel === "diff") {
2718
+ setSelectedHunkIndex((i) => Math.max(0, i - 1));
2719
+ }
2720
+ };
2721
+ const navigateDown = () => {
2722
+ const panel = focusPanel();
2723
+ if (panel === "sidebar") {
2724
+ if (sidebarMode() === "files") {
2725
+ setSelectedNavIndex(Math.min(fileIndices().length - 1, selectedFileNavIndex() + 1));
2726
+ } else if (sidebarMode() === "patches") {
2727
+ const wt = review.workingTreePatchId();
2728
+ const patchCount = (wt === null ? review.patches().length : review.patches().filter((p) => p.id !== wt).length) + 1;
2729
+ setSelectedPatchIndex((i) => Math.min(patchCount - 1, i + 1));
2730
+ } else {
2731
+ const maxIndex = review.comments().length - 1;
2732
+ setSelectedCommentIndex((i) => Math.min(maxIndex, i + 1));
2733
+ }
2734
+ return;
2735
+ }
2736
+ if (panel === "diff" && selectionMode() === "line") {
2737
+ const next = clampRow(cursorRow() + 1);
2738
+ setCursorRow(next);
2739
+ setSelectedRange({ startRow: next, endRow: next });
2740
+ return;
2741
+ }
2742
+ if (panel === "diff") {
2743
+ setSelectedHunkIndex((i) => Math.min(hunkCount() - 1, i + 1));
2744
+ }
2745
+ };
2746
+ const openCommentEditor = () => {
2747
+ const entry = selectedEntry();
2748
+ const range = selectionMode() === "line" ? selectedRange() : selectedHunkRange();
2749
+ if (!entry || !range)
2750
+ return;
2751
+ const existing = review.getCommentForRange(entry.file, range.startRow, range.endRow);
2752
+ setEditorAnchor({
2753
+ file: entry.file,
2754
+ startRow: range.startRow,
2755
+ endRow: range.endRow,
2756
+ commentId: existing?.id
2757
+ });
2758
+ setFocusPanel("editor");
2759
+ };
2760
+ const toggleSidebarMode = () => {
2761
+ setSidebarMode((m) => {
2762
+ if (m === "files")
2763
+ return "patches";
2764
+ if (m === "patches")
2765
+ return "comments";
2766
+ return "files";
2767
+ });
2768
+ };
2769
+ const toggleHunkSelection = () => {
2770
+ const range = selectedHunkRange();
2771
+ if (!range)
2772
+ return;
2773
+ if (selectedRange()) {
2774
+ setSelectedRange(null);
2775
+ return;
2776
+ }
2777
+ setSelectionMode("hunk");
2778
+ setSelectedRange(range);
2779
+ };
2780
+ const toggleLineMode = () => {
2781
+ if (totalRows() === 0)
2782
+ return;
2783
+ if (selectionMode() === "hunk") {
2784
+ const range2 = selectedRange() ?? selectedHunkRange();
2785
+ const row = clampRow(range2?.startRow ?? 1);
2786
+ setSelectionMode("line");
2787
+ setCursorRow(row);
2788
+ setSelectedRange({ startRow: row, endRow: row });
2789
+ return;
2790
+ }
2791
+ const entry = selectedEntry();
2792
+ const range = selectedRange();
2793
+ if (entry && range) {
2794
+ const hunkIndex = getHunkIndexForRow(entry.content, entry.file, range.startRow) ?? 0;
2795
+ setSelectedHunkIndex(hunkIndex);
2796
+ }
2797
+ setSelectionMode("hunk");
2798
+ setSelectedRange(null);
2799
+ };
2800
+ const extendSelectionDown = () => {
2801
+ if (selectionMode() !== "line")
2802
+ return;
2803
+ const range = selectedRange() ?? {
2804
+ startRow: cursorRow(),
2805
+ endRow: cursorRow()
2806
+ };
2807
+ const next = clampRow(range.endRow + 1);
2808
+ setSelectedRange({ startRow: range.startRow, endRow: next });
2809
+ setCursorRow(next);
2810
+ };
2811
+ const extendSelectionUp = () => {
2812
+ if (selectionMode() !== "line")
2813
+ return;
2814
+ const range = selectedRange() ?? {
2815
+ startRow: cursorRow(),
2816
+ endRow: cursorRow()
2817
+ };
2818
+ const next = clampRow(range.startRow - 1);
2819
+ setSelectedRange({ startRow: next, endRow: range.endRow });
2820
+ setCursorRow(next);
2821
+ };
2822
+ const commands = () => [
2823
+ {
2824
+ id: "up",
2825
+ title: "up",
2826
+ category: "movement",
2827
+ keybinds: [{ name: "up" }, { name: "k" }],
2828
+ isActive: () => focusPanel() !== "editor",
2829
+ onExecute: navigateUp
2830
+ },
2831
+ {
2832
+ id: "down",
2833
+ title: "down",
2834
+ category: "movement",
2835
+ keybinds: [{ name: "down" }, { name: "j" }],
2836
+ isActive: () => focusPanel() !== "editor",
2837
+ onExecute: navigateDown
2838
+ },
2839
+ {
2840
+ id: "back-from-diff",
2841
+ title: "back",
2842
+ category: "movement",
2843
+ keybinds: [{ name: "escape" }],
2844
+ isActive: () => focusPanel() === "diff",
2845
+ onExecute: () => {
2846
+ setFocusPanel("sidebar");
2847
+ setSelectedHunkIndex(0);
2848
+ }
2849
+ },
2850
+ {
2851
+ id: "quit",
2852
+ title: "quit",
2853
+ category: "movement",
2854
+ keybinds: [
2855
+ { name: "c", ctrl: true },
2856
+ { name: "d", ctrl: true },
2857
+ { name: "q", ctrl: true }
2858
+ ],
2859
+ isActive: () => focusPanel() !== "editor",
2860
+ onExecute: () => exit()
2861
+ },
2862
+ {
2863
+ id: "refresh-working-tree",
2864
+ title: "refresh working tree",
2865
+ category: "movement",
2866
+ keybinds: [{ name: "r", ctrl: true }],
2867
+ isActive: () => focusPanel() !== "editor",
2868
+ onExecute: () => {
2869
+ const preserve = selectedEntry()?.file ?? review.selectedFilePath();
2870
+ review.refreshWorkingTree(preserve ?? undefined);
2871
+ }
2872
+ },
2873
+ {
2874
+ id: "cycle-forward",
2875
+ title: "cycle forward",
2876
+ category: "movement",
2877
+ keybinds: [{ name: "tab" }],
2878
+ isActive: () => focusPanel() !== "editor",
2879
+ onExecute: () => {
2880
+ if (focusPanel() === "sidebar") {
2881
+ if (sidebarMode() === "files") {
2882
+ setSidebarMode("patches");
2883
+ } else if (sidebarMode() === "patches") {
2884
+ setSidebarMode("comments");
2885
+ } else {
2886
+ setFocusPanel("diff");
2887
+ setSelectedHunkIndex(0);
2888
+ }
2889
+ } else if (focusPanel() === "diff") {
2890
+ setFocusPanel("sidebar");
2891
+ setSidebarMode("files");
2892
+ }
2893
+ }
2894
+ },
2895
+ {
2896
+ id: "cycle-backward",
2897
+ title: "cycle backward",
2898
+ category: "movement",
2899
+ keybinds: [{ name: "tab", shift: true }],
2900
+ isActive: () => focusPanel() !== "editor",
2901
+ onExecute: () => {
2902
+ if (focusPanel() === "sidebar") {
2903
+ if (sidebarMode() === "files") {
2904
+ setFocusPanel("diff");
2905
+ setSelectedHunkIndex(0);
2906
+ } else if (sidebarMode() === "patches") {
2907
+ setSidebarMode("files");
2908
+ } else {
2909
+ setSidebarMode("patches");
2910
+ }
2911
+ } else if (focusPanel() === "diff") {
2912
+ setFocusPanel("sidebar");
2913
+ setSidebarMode("comments");
2914
+ }
2915
+ }
2916
+ },
2917
+ {
2918
+ id: "select-hunks",
2919
+ title: "select hunks",
2920
+ category: "selection",
2921
+ keybinds: [{ name: "return" }],
2922
+ isActive: () => focusPanel() === "sidebar" && sidebarMode() === "files" && !!selectedEntry() && hunkCount() > 0,
2923
+ onExecute: () => {
2924
+ setFocusPanel("diff");
2925
+ setSelectedHunkIndex(0);
2926
+ setSelectionMode("hunk");
2927
+ setSelectedRange(null);
2928
+ }
2929
+ },
2930
+ {
2931
+ id: "select-lines",
2932
+ title: "select by lines",
2933
+ category: "selection",
2934
+ keybinds: [{ name: "a" }],
2935
+ isActive: () => focusPanel() === "diff" && totalRows() > 0,
2936
+ onExecute: toggleLineMode
2937
+ },
2938
+ {
2939
+ id: "toggle-hunk-selection",
2940
+ title: "toggle hunk selection",
2941
+ category: "selection",
2942
+ keybinds: [{ name: "space" }],
2943
+ isActive: () => focusPanel() === "diff" && selectionMode() === "hunk" && selectedHunkRange() !== null,
2944
+ onExecute: toggleHunkSelection
2945
+ },
2946
+ {
2947
+ id: "extend-down",
2948
+ title: "extend selection down",
2949
+ category: "selection",
2950
+ keybinds: [
2951
+ { name: "j", shift: true },
2952
+ { name: "down", shift: true }
2953
+ ],
2954
+ isActive: () => focusPanel() === "diff" && selectionMode() === "line",
2955
+ onExecute: extendSelectionDown
2956
+ },
2957
+ {
2958
+ id: "extend-up",
2959
+ title: "extend selection up",
2960
+ category: "selection",
2961
+ keybinds: [
2962
+ { name: "k", shift: true },
2963
+ { name: "up", shift: true }
2964
+ ],
2965
+ isActive: () => focusPanel() === "diff" && selectionMode() === "line",
2966
+ onExecute: extendSelectionUp
2967
+ },
2968
+ {
2969
+ id: "open-comment",
2970
+ title: "open comment",
2971
+ category: "selection",
2972
+ keybinds: [{ name: "return" }],
2973
+ isActive: () => focusPanel() === "sidebar" && sidebarMode() === "comments" && review.comments().length > 0,
2974
+ onExecute: () => {
2975
+ const comments = review.comments();
2976
+ const idx = selectedCommentIndex();
2977
+ const comment = comments[idx];
2978
+ if (!comment)
2979
+ return;
2980
+ review.setActivePatch(comment.patchId, comment.file);
2981
+ review.setSelectedFilePath(comment.file);
2982
+ const entry = review.entries().find((e) => e.file === comment.file);
2983
+ const hunkIndex = entry ? getHunkIndexForRow(entry.content, entry.file, comment.startRow) ?? 0 : 0;
2984
+ setSelectedHunkIndex(hunkIndex);
2985
+ setEditorAnchor({
2986
+ file: comment.file,
2987
+ startRow: comment.startRow,
2988
+ endRow: comment.endRow,
2989
+ commentId: comment.id
2990
+ });
2991
+ setFocusPanel("editor");
2992
+ }
2993
+ },
2994
+ {
2995
+ id: "select-patch",
2996
+ title: "select patch",
2997
+ category: "selection",
2998
+ keybinds: [{ name: "return" }],
2999
+ isActive: () => focusPanel() === "sidebar" && sidebarMode() === "patches" && review.patches().length > 0,
3000
+ onExecute: () => {
3001
+ const wt = review.workingTreePatchId();
3002
+ const patches = review.patches().slice().filter((p) => wt === null ? true : p.id !== wt).sort((a, b) => b.seq - a.seq);
3003
+ const idx = selectedPatchIndex();
3004
+ const currentEntry = selectedEntry();
3005
+ const currentFile = currentEntry?.file;
3006
+ if (idx === 0) {
3007
+ if (wt !== null)
3008
+ review.setActivePatch(wt, currentFile);
3009
+ } else {
3010
+ const patch = patches[idx - 1];
3011
+ if (patch) {
3012
+ review.setActivePatch(patch.id, currentFile);
3013
+ }
3014
+ }
3015
+ }
3016
+ },
3017
+ {
3018
+ id: "comment",
3019
+ title: "comment",
3020
+ category: "actions",
3021
+ keybinds: [{ name: "c" }],
3022
+ isActive: () => focusPanel() === "diff" && (selectionMode() === "hunk" && selectedHunkRange() !== null || selectionMode() === "line" && selectedRange() !== null),
3023
+ onExecute: openCommentEditor
3024
+ }
3025
+ ];
3026
+ command.register(commands);
3027
+ useKeyboard3((key) => {
3028
+ if (key.ctrl && key.name === "p") {
3029
+ dialog.show(() => /* @__PURE__ */ jsxDEV19(CommandPalette, {}, undefined, false, undefined, this));
3030
+ return;
3031
+ }
3032
+ if (dialog.isOpen())
3033
+ return;
3034
+ for (const cmd of commands()) {
3035
+ if (cmd.isActive() && matchAny(key, cmd.keybinds)) {
3036
+ cmd.onExecute();
3037
+ return;
3038
+ }
3039
+ }
3040
+ });
3041
+ const Footer = () => /* @__PURE__ */ jsxDEV19("box", {
3042
+ height: 1,
3043
+ paddingLeft: 1,
3044
+ flexDirection: "row",
3045
+ gap: 2,
3046
+ children: [
3047
+ /* @__PURE__ */ jsxDEV19("box", {
3048
+ flexDirection: "row",
3049
+ children: [
3050
+ /* @__PURE__ */ jsxDEV19("text", {
3051
+ children: "j/k "
3052
+ }, undefined, false, undefined, this),
3053
+ /* @__PURE__ */ jsxDEV19("text", {
3054
+ fg: "brightBlack",
3055
+ children: "navigate"
3056
+ }, undefined, false, undefined, this)
3057
+ ]
3058
+ }, undefined, true, undefined, this),
3059
+ /* @__PURE__ */ jsxDEV19(Show7, {
3060
+ when: focusPanel() === "sidebar",
3061
+ children: /* @__PURE__ */ jsxDEV19("box", {
3062
+ flexDirection: "row",
3063
+ children: [
3064
+ /* @__PURE__ */ jsxDEV19("text", {
3065
+ children: "tab "
3066
+ }, undefined, false, undefined, this),
3067
+ /* @__PURE__ */ jsxDEV19("text", {
3068
+ fg: "brightBlack",
3069
+ children: "switch"
3070
+ }, undefined, false, undefined, this)
3071
+ ]
3072
+ }, undefined, true, undefined, this)
3073
+ }, undefined, false, undefined, this),
3074
+ /* @__PURE__ */ jsxDEV19(Show7, {
3075
+ when: focusPanel() === "diff" && (selectionMode() === "hunk" && selectedHunkRange() !== null || selectionMode() === "line" && selectedRange()),
3076
+ children: /* @__PURE__ */ jsxDEV19("box", {
3077
+ flexDirection: "row",
3078
+ children: [
3079
+ /* @__PURE__ */ jsxDEV19("text", {
3080
+ children: "c "
3081
+ }, undefined, false, undefined, this),
3082
+ /* @__PURE__ */ jsxDEV19("text", {
3083
+ fg: "brightBlack",
3084
+ children: "comment"
3085
+ }, undefined, false, undefined, this)
3086
+ ]
3087
+ }, undefined, true, undefined, this)
3088
+ }, undefined, false, undefined, this),
3089
+ /* @__PURE__ */ jsxDEV19(Show7, {
3090
+ when: focusPanel() === "editor",
3091
+ children: [
3092
+ /* @__PURE__ */ jsxDEV19("box", {
3093
+ flexDirection: "row",
3094
+ children: [
3095
+ /* @__PURE__ */ jsxDEV19("text", {
3096
+ children: "enter "
3097
+ }, undefined, false, undefined, this),
3098
+ /* @__PURE__ */ jsxDEV19("text", {
3099
+ fg: "brightBlack",
3100
+ children: "submit"
3101
+ }, undefined, false, undefined, this)
3102
+ ]
3103
+ }, undefined, true, undefined, this),
3104
+ /* @__PURE__ */ jsxDEV19("box", {
3105
+ flexDirection: "row",
3106
+ children: [
3107
+ /* @__PURE__ */ jsxDEV19("text", {
3108
+ children: "C-j "
3109
+ }, undefined, false, undefined, this),
3110
+ /* @__PURE__ */ jsxDEV19("text", {
3111
+ fg: "brightBlack",
3112
+ children: "newline"
3113
+ }, undefined, false, undefined, this)
3114
+ ]
3115
+ }, undefined, true, undefined, this),
3116
+ /* @__PURE__ */ jsxDEV19("box", {
3117
+ flexDirection: "row",
3118
+ children: [
3119
+ /* @__PURE__ */ jsxDEV19("text", {
3120
+ children: "C-e "
3121
+ }, undefined, false, undefined, this),
3122
+ /* @__PURE__ */ jsxDEV19("text", {
3123
+ fg: "brightBlack",
3124
+ children: "editor"
3125
+ }, undefined, false, undefined, this)
3126
+ ]
3127
+ }, undefined, true, undefined, this),
3128
+ /* @__PURE__ */ jsxDEV19("box", {
3129
+ flexDirection: "row",
3130
+ children: [
3131
+ /* @__PURE__ */ jsxDEV19("text", {
3132
+ children: "esc "
3133
+ }, undefined, false, undefined, this),
3134
+ /* @__PURE__ */ jsxDEV19("text", {
3135
+ fg: "brightBlack",
3136
+ children: "cancel"
3137
+ }, undefined, false, undefined, this)
3138
+ ]
3139
+ }, undefined, true, undefined, this)
3140
+ ]
3141
+ }, undefined, true, undefined, this),
3142
+ /* @__PURE__ */ jsxDEV19(Show7, {
3143
+ when: focusPanel() !== "editor",
3144
+ children: /* @__PURE__ */ jsxDEV19("box", {
3145
+ flexDirection: "row",
3146
+ children: [
3147
+ /* @__PURE__ */ jsxDEV19("text", {
3148
+ children: "esc "
3149
+ }, undefined, false, undefined, this),
3150
+ /* @__PURE__ */ jsxDEV19("text", {
3151
+ fg: "brightBlack",
3152
+ children: "back"
3153
+ }, undefined, false, undefined, this)
3154
+ ]
3155
+ }, undefined, true, undefined, this)
3156
+ }, undefined, false, undefined, this),
3157
+ /* @__PURE__ */ jsxDEV19(Show7, {
3158
+ when: focusPanel() !== "editor",
3159
+ children: /* @__PURE__ */ jsxDEV19("box", {
3160
+ flexDirection: "row",
3161
+ children: [
3162
+ /* @__PURE__ */ jsxDEV19("text", {
3163
+ children: "C-r "
3164
+ }, undefined, false, undefined, this),
3165
+ /* @__PURE__ */ jsxDEV19("text", {
3166
+ fg: "brightBlack",
3167
+ children: "refresh"
3168
+ }, undefined, false, undefined, this)
3169
+ ]
3170
+ }, undefined, true, undefined, this)
3171
+ }, undefined, false, undefined, this),
3172
+ /* @__PURE__ */ jsxDEV19(Show7, {
3173
+ when: focusPanel() !== "editor",
3174
+ children: /* @__PURE__ */ jsxDEV19("box", {
3175
+ flexDirection: "row",
3176
+ children: [
3177
+ /* @__PURE__ */ jsxDEV19("text", {
3178
+ children: "C-q "
3179
+ }, undefined, false, undefined, this),
3180
+ /* @__PURE__ */ jsxDEV19("text", {
3181
+ fg: "brightBlack",
3182
+ children: "quit"
3183
+ }, undefined, false, undefined, this)
3184
+ ]
3185
+ }, undefined, true, undefined, this)
3186
+ }, undefined, false, undefined, this)
3187
+ ]
3188
+ }, undefined, true, undefined, this);
3189
+ return /* @__PURE__ */ jsxDEV19("box", {
3190
+ flexDirection: "column",
3191
+ width: dims().width,
3192
+ height: dims().height,
3193
+ backgroundColor: theme.background,
3194
+ children: [
3195
+ /* @__PURE__ */ jsxDEV19("box", {
3196
+ flexGrow: 1,
3197
+ flexDirection: "row",
3198
+ gap: 1,
3199
+ children: [
3200
+ /* @__PURE__ */ jsxDEV19("box", {
3201
+ width: 40,
3202
+ flexDirection: "column",
3203
+ gap: 1,
3204
+ paddingTop: 1,
3205
+ paddingBottom: 1,
3206
+ paddingLeft: 2,
3207
+ paddingRight: 2,
3208
+ backgroundColor: theme.backgroundPanel,
3209
+ children: [
3210
+ /* @__PURE__ */ jsxDEV19(ServerStatus, {}, undefined, false, undefined, this),
3211
+ /* @__PURE__ */ jsxDEV19(ProjectStatus, {
3212
+ repoRoot: review.repoRoot,
3213
+ commentCount: () => review.comments().length
3214
+ }, undefined, false, undefined, this),
3215
+ /* @__PURE__ */ jsxDEV19(SidebarProvider, {
3216
+ activeSection: sidebarMode,
3217
+ isFocused: () => focusPanel() === "sidebar",
3218
+ children: /* @__PURE__ */ jsxDEV19("box", {
3219
+ flexGrow: 1,
3220
+ flexDirection: "column",
3221
+ overflow: "hidden",
3222
+ gap: 1,
3223
+ children: [
3224
+ /* @__PURE__ */ jsxDEV19("box", {
3225
+ flexGrow: 1,
3226
+ children: /* @__PURE__ */ jsxDEV19(FileList, {
3227
+ displayItems,
3228
+ selectedFileIndex: selectedFileNavIndex,
3229
+ fileIndices,
3230
+ entries: review.entries,
3231
+ loading: review.loading,
3232
+ focused: () => focusPanel() === "sidebar" && sidebarMode() === "files"
3233
+ }, undefined, false, undefined, this)
3234
+ }, undefined, false, undefined, this),
3235
+ /* @__PURE__ */ jsxDEV19("box", {
3236
+ flexGrow: 1,
3237
+ flexDirection: "column",
3238
+ children: /* @__PURE__ */ jsxDEV19(PatchList, {
3239
+ patches: review.patches,
3240
+ activePatchId: review.activePatchId,
3241
+ workingTreePatchId: review.workingTreePatchId,
3242
+ workspaceEntries: review.workspaceEntries,
3243
+ loading: review.patchesLoading,
3244
+ selectedIndex: selectedPatchIndex,
3245
+ focused: () => focusPanel() === "sidebar" && sidebarMode() === "patches"
3246
+ }, undefined, false, undefined, this)
3247
+ }, undefined, false, undefined, this),
3248
+ /* @__PURE__ */ jsxDEV19("box", {
3249
+ flexGrow: 1,
3250
+ flexDirection: "column",
3251
+ children: /* @__PURE__ */ jsxDEV19(CommentList, {
3252
+ comments: review.comments,
3253
+ loading: review.commentsLoading,
3254
+ selectedIndex: selectedCommentIndex,
3255
+ focused: () => focusPanel() === "sidebar" && sidebarMode() === "comments"
3256
+ }, undefined, false, undefined, this)
3257
+ }, undefined, false, undefined, this)
3258
+ ]
3259
+ }, undefined, true, undefined, this)
3260
+ }, undefined, false, undefined, this)
3261
+ ]
3262
+ }, undefined, true, undefined, this),
3263
+ /* @__PURE__ */ jsxDEV19("box", {
3264
+ flexGrow: 1,
3265
+ flexDirection: "column",
3266
+ gap: 1,
3267
+ children: [
3268
+ /* @__PURE__ */ jsxDEV19(DiffPanel, {
3269
+ entry: selectedEntry,
3270
+ hunkCount,
3271
+ selectedHunkIndex,
3272
+ selectionMode,
3273
+ selectedRange,
3274
+ cursorRow,
3275
+ focused: () => focusPanel() === "diff"
3276
+ }, undefined, false, undefined, this),
3277
+ /* @__PURE__ */ jsxDEV19(Show7, {
3278
+ when: focusPanel() === "editor" && editorAnchor(),
3279
+ children: /* @__PURE__ */ jsxDEV19(EditorPanel, {
3280
+ file: editorAnchor().file,
3281
+ startRow: editorAnchor().startRow,
3282
+ endRow: editorAnchor().endRow,
3283
+ commentId: editorAnchor().commentId,
3284
+ onClose: () => {
3285
+ setFocusPanel("diff");
3286
+ setEditorAnchor(null);
3287
+ }
3288
+ }, undefined, false, undefined, this)
3289
+ }, undefined, false, undefined, this)
3290
+ ]
3291
+ }, undefined, true, undefined, this)
3292
+ ]
3293
+ }, undefined, true, undefined, this),
3294
+ /* @__PURE__ */ jsxDEV19(Footer, {}, undefined, false, undefined, this),
3295
+ /* @__PURE__ */ jsxDEV19(Show7, {
3296
+ when: dialog.isOpen(),
3297
+ children: dialog.content()
3298
+ }, undefined, false, undefined, this)
3299
+ ]
3300
+ }, undefined, true, undefined, this);
3301
+ }
3302
+
3303
+ // src/App.tsx
3304
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
3305
+
3306
+ // parsers/index.ts
3307
+ import bash_wasm from "tree-sitter-wasms/out/tree-sitter-bash.wasm" with { type: "file" };
3308
+ import c_wasm from "tree-sitter-wasms/out/tree-sitter-c.wasm" with { type: "file" };
3309
+ import cpp_wasm from "tree-sitter-wasms/out/tree-sitter-cpp.wasm" with { type: "file" };
3310
+ import c_sharp_wasm from "tree-sitter-wasms/out/tree-sitter-c_sharp.wasm" with { type: "file" };
3311
+ import css_wasm from "tree-sitter-wasms/out/tree-sitter-css.wasm" with { type: "file" };
3312
+ import dart_wasm from "tree-sitter-wasms/out/tree-sitter-dart.wasm" with { type: "file" };
3313
+ import elisp_wasm from "tree-sitter-wasms/out/tree-sitter-elisp.wasm" with { type: "file" };
3314
+ import elixir_wasm from "tree-sitter-wasms/out/tree-sitter-elixir.wasm" with { type: "file" };
3315
+ import elm_wasm from "tree-sitter-wasms/out/tree-sitter-elm.wasm" with { type: "file" };
3316
+ import embedded_template_wasm from "tree-sitter-wasms/out/tree-sitter-embedded_template.wasm" with { type: "file" };
3317
+ import go_wasm from "tree-sitter-wasms/out/tree-sitter-go.wasm" with { type: "file" };
3318
+ import html_wasm from "tree-sitter-wasms/out/tree-sitter-html.wasm" with { type: "file" };
3319
+ import java_wasm from "tree-sitter-wasms/out/tree-sitter-java.wasm" with { type: "file" };
3320
+ import javascript_wasm from "tree-sitter-wasms/out/tree-sitter-javascript.wasm" with { type: "file" };
3321
+ import json_wasm from "tree-sitter-wasms/out/tree-sitter-json.wasm" with { type: "file" };
3322
+ import kotlin_wasm from "tree-sitter-wasms/out/tree-sitter-kotlin.wasm" with { type: "file" };
3323
+ import lua_wasm from "tree-sitter-wasms/out/tree-sitter-lua.wasm" with { type: "file" };
3324
+ import objc_wasm from "tree-sitter-wasms/out/tree-sitter-objc.wasm" with { type: "file" };
3325
+ import ocaml_wasm from "tree-sitter-wasms/out/tree-sitter-ocaml.wasm" with { type: "file" };
3326
+ import php_wasm from "tree-sitter-wasms/out/tree-sitter-php.wasm" with { type: "file" };
3327
+ import python_wasm from "tree-sitter-wasms/out/tree-sitter-python.wasm" with { type: "file" };
3328
+ import ql_wasm from "tree-sitter-wasms/out/tree-sitter-ql.wasm" with { type: "file" };
3329
+ import ruby_wasm from "tree-sitter-wasms/out/tree-sitter-ruby.wasm" with { type: "file" };
3330
+ import rust_wasm from "tree-sitter-wasms/out/tree-sitter-rust.wasm" with { type: "file" };
3331
+ import scala_wasm from "tree-sitter-wasms/out/tree-sitter-scala.wasm" with { type: "file" };
3332
+ import swift_wasm from "tree-sitter-wasms/out/tree-sitter-swift.wasm" with { type: "file" };
3333
+ import toml_wasm from "tree-sitter-wasms/out/tree-sitter-toml.wasm" with { type: "file" };
3334
+ import tsx_wasm from "tree-sitter-wasms/out/tree-sitter-tsx.wasm" with { type: "file" };
3335
+ import typescript_wasm from "tree-sitter-wasms/out/tree-sitter-typescript.wasm" with { type: "file" };
3336
+ import vue_wasm from "tree-sitter-wasms/out/tree-sitter-vue.wasm" with { type: "file" };
3337
+ import yaml_wasm from "tree-sitter-wasms/out/tree-sitter-yaml.wasm" with { type: "file" };
3338
+ import zig_wasm from "tree-sitter-wasms/out/tree-sitter-zig.wasm" with { type: "file" };
3339
+
3340
+ // parsers/queries/bash.scm
3341
+ var bash_default = "./bash-nbhfht5r.scm";
3342
+
3343
+ // parsers/queries/c.scm
3344
+ var c_default = "./c-4zy7d3fw.scm";
3345
+
3346
+ // parsers/queries/cpp.scm
3347
+ var cpp_default = "./cpp-stxr9ffp.scm";
3348
+
3349
+ // parsers/queries/c_sharp.scm
3350
+ var c_sharp_default = "./c_sharp-tb7n62t2.scm";
3351
+
3352
+ // parsers/queries/css.scm
3353
+ var css_default = "./css-b1p1h6ys.scm";
3354
+
3355
+ // parsers/queries/dart.scm
3356
+ var dart_default = "./dart-t89b32ej.scm";
3357
+
3358
+ // parsers/queries/elisp.scm
3359
+ var elisp_default = "./elisp-ax9wx1b9.scm";
3360
+
3361
+ // parsers/queries/elixir.scm
3362
+ var elixir_default = "./elixir-77300bss.scm";
3363
+
3364
+ // parsers/queries/elm.scm
3365
+ var elm_default = "./elm-k2q5knqb.scm";
3366
+
3367
+ // parsers/queries/embedded_template.scm
3368
+ var embedded_template_default = "./embedded_template-k7ypjkba.scm";
3369
+
3370
+ // parsers/queries/go.scm
3371
+ var go_default = "./go-9wx8m64k.scm";
3372
+
3373
+ // parsers/queries/html.scm
3374
+ var html_default = "./html-g95aq3vw.scm";
3375
+
3376
+ // parsers/queries/java.scm
3377
+ var java_default = "./java-wasz6t7a.scm";
3378
+
3379
+ // parsers/queries/javascript.scm
3380
+ var javascript_default = "./javascript-cswtymww.scm";
3381
+
3382
+ // parsers/queries/json.scm
3383
+ var json_default = "./json-pf4aw8w1.scm";
3384
+
3385
+ // parsers/queries/kotlin.scm
3386
+ var kotlin_default = "./kotlin-m6cj2y7m.scm";
3387
+
3388
+ // parsers/queries/lua.scm
3389
+ var lua_default = "./lua-7h6jk3pd.scm";
3390
+
3391
+ // parsers/queries/objc.scm
3392
+ var objc_default = "./objc-6yjdwy1z.scm";
3393
+
3394
+ // parsers/queries/ocaml.scm
3395
+ var ocaml_default = "./ocaml-jfj520qk.scm";
3396
+
3397
+ // parsers/queries/php.scm
3398
+ var php_default = "./php-bds7v5h1.scm";
3399
+
3400
+ // parsers/queries/python.scm
3401
+ var python_default = "./python-k2cbtg72.scm";
3402
+
3403
+ // parsers/queries/ql.scm
3404
+ var ql_default = "./ql-qs2vxbeq.scm";
3405
+
3406
+ // parsers/queries/ruby.scm
3407
+ var ruby_default = "./ruby-3ybh5bp2.scm";
3408
+
3409
+ // parsers/queries/rust.scm
3410
+ var rust_default = "./rust-edtnatpk.scm";
3411
+
3412
+ // parsers/queries/scala.scm
3413
+ var scala_default = "./scala-91f2sb1z.scm";
3414
+
3415
+ // parsers/queries/swift.scm
3416
+ var swift_default = "./swift-a4tt704b.scm";
3417
+
3418
+ // parsers/queries/toml.scm
3419
+ var toml_default = "./toml-hcqfz0bg.scm";
3420
+
3421
+ // parsers/queries/tsx.scm
3422
+ var tsx_default = "./tsx-c3k3mmc9.scm";
3423
+
3424
+ // parsers/queries/typescript.scm
3425
+ var typescript_default = "./typescript-hz4pg6ze.scm";
3426
+
3427
+ // parsers/queries/vue.scm
3428
+ var vue_default = "./vue-xbeea1aj.scm";
3429
+
3430
+ // parsers/queries/yaml.scm
3431
+ var yaml_default = "./yaml-mbfb25g0.scm";
3432
+
3433
+ // parsers/queries/zig.scm
3434
+ var zig_default = "./zig-ds3q8m26.scm";
3435
+
3436
+ // parsers/index.ts
3437
+ var parsers = [
3438
+ {
3439
+ filetype: "bash",
3440
+ wasm: bash_wasm,
3441
+ queries: { highlights: [bash_default] }
3442
+ },
3443
+ { filetype: "c", wasm: c_wasm, queries: { highlights: [c_default] } },
3444
+ {
3445
+ filetype: "cpp",
3446
+ wasm: cpp_wasm,
3447
+ queries: { highlights: [cpp_default] }
3448
+ },
3449
+ {
3450
+ filetype: "c_sharp",
3451
+ wasm: c_sharp_wasm,
3452
+ queries: { highlights: [c_sharp_default] }
3453
+ },
3454
+ {
3455
+ filetype: "css",
3456
+ wasm: css_wasm,
3457
+ queries: { highlights: [css_default] }
3458
+ },
3459
+ {
3460
+ filetype: "dart",
3461
+ wasm: dart_wasm,
3462
+ queries: { highlights: [dart_default] }
3463
+ },
3464
+ {
3465
+ filetype: "elisp",
3466
+ wasm: elisp_wasm,
3467
+ queries: { highlights: [elisp_default] }
3468
+ },
3469
+ {
3470
+ filetype: "elixir",
3471
+ wasm: elixir_wasm,
3472
+ queries: { highlights: [elixir_default] }
3473
+ },
3474
+ {
3475
+ filetype: "elm",
3476
+ wasm: elm_wasm,
3477
+ queries: { highlights: [elm_default] }
3478
+ },
3479
+ {
3480
+ filetype: "embedded_template",
3481
+ wasm: embedded_template_wasm,
3482
+ queries: { highlights: [embedded_template_default] }
3483
+ },
3484
+ { filetype: "go", wasm: go_wasm, queries: { highlights: [go_default] } },
3485
+ {
3486
+ filetype: "html",
3487
+ wasm: html_wasm,
3488
+ queries: { highlights: [html_default] }
3489
+ },
3490
+ {
3491
+ filetype: "java",
3492
+ wasm: java_wasm,
3493
+ queries: { highlights: [java_default] }
3494
+ },
3495
+ {
3496
+ filetype: "javascript",
3497
+ wasm: javascript_wasm,
3498
+ queries: { highlights: [javascript_default] }
3499
+ },
3500
+ {
3501
+ filetype: "json",
3502
+ wasm: json_wasm,
3503
+ queries: { highlights: [json_default] }
3504
+ },
3505
+ {
3506
+ filetype: "kotlin",
3507
+ wasm: kotlin_wasm,
3508
+ queries: { highlights: [kotlin_default] }
3509
+ },
3510
+ {
3511
+ filetype: "lua",
3512
+ wasm: lua_wasm,
3513
+ queries: { highlights: [lua_default] }
3514
+ },
3515
+ {
3516
+ filetype: "objc",
3517
+ wasm: objc_wasm,
3518
+ queries: { highlights: [objc_default] }
3519
+ },
3520
+ {
3521
+ filetype: "ocaml",
3522
+ wasm: ocaml_wasm,
3523
+ queries: { highlights: [ocaml_default] }
3524
+ },
3525
+ {
3526
+ filetype: "php",
3527
+ wasm: php_wasm,
3528
+ queries: { highlights: [php_default] }
3529
+ },
3530
+ {
3531
+ filetype: "python",
3532
+ wasm: python_wasm,
3533
+ queries: { highlights: [python_default] }
3534
+ },
3535
+ { filetype: "ql", wasm: ql_wasm, queries: { highlights: [ql_default] } },
3536
+ {
3537
+ filetype: "ruby",
3538
+ wasm: ruby_wasm,
3539
+ queries: { highlights: [ruby_default] }
3540
+ },
3541
+ {
3542
+ filetype: "rust",
3543
+ wasm: rust_wasm,
3544
+ queries: { highlights: [rust_default] }
3545
+ },
3546
+ {
3547
+ filetype: "scala",
3548
+ wasm: scala_wasm,
3549
+ queries: { highlights: [scala_default] }
3550
+ },
3551
+ {
3552
+ filetype: "swift",
3553
+ wasm: swift_wasm,
3554
+ queries: { highlights: [swift_default] }
3555
+ },
3556
+ {
3557
+ filetype: "toml",
3558
+ wasm: toml_wasm,
3559
+ queries: { highlights: [toml_default] }
3560
+ },
3561
+ {
3562
+ filetype: "tsx",
3563
+ wasm: tsx_wasm,
3564
+ queries: { highlights: [tsx_default] }
3565
+ },
3566
+ {
3567
+ filetype: "typescript",
3568
+ wasm: typescript_wasm,
3569
+ queries: { highlights: [typescript_default] }
3570
+ },
3571
+ {
3572
+ filetype: "vue",
3573
+ wasm: vue_wasm,
3574
+ queries: { highlights: [vue_default] }
3575
+ },
3576
+ {
3577
+ filetype: "yaml",
3578
+ wasm: yaml_wasm,
3579
+ queries: { highlights: [yaml_default] }
3580
+ },
3581
+ {
3582
+ filetype: "zig",
3583
+ wasm: zig_wasm,
3584
+ queries: { highlights: [zig_default] }
3585
+ }
3586
+ ];
3587
+
3588
+ // src/App.tsx
3589
+ import { jsxDEV as jsxDEV20 } from "@opentui/solid/jsx-dev-runtime";
3590
+ exports_db.init();
3591
+ addDefaultParsers(parsers);
3592
+ var originalSliderRenderSelf = SliderRenderable.prototype.renderSelf;
3593
+ SliderRenderable.prototype.renderSelf = function(buffer) {
3594
+ if (this.width <= 0 || this.height <= 0)
3595
+ return;
3596
+ if (this.orientation === "vertical" && this.height <= 1)
3597
+ return;
3598
+ if (this.orientation === "horizontal" && this.width <= 1)
3599
+ return;
3600
+ originalSliderRenderSelf.call(this, buffer);
3601
+ };
3602
+ var RECORD_FRAMES = process.env.RECORD_FRAMES === "1";
3603
+ var frameNumber = 0;
3604
+ var decoder = new TextDecoder;
3605
+ async function tui(options = {}) {
3606
+ const repoPath = options.repoPath ?? process.cwd();
3607
+ await render(() => /* @__PURE__ */ jsxDEV20(ErrorBoundary, {
3608
+ fallback: (err, reset) => /* @__PURE__ */ jsxDEV20(ErrorFallback, {
3609
+ error: err,
3610
+ reset
3611
+ }, undefined, false, undefined, this),
3612
+ children: /* @__PURE__ */ jsxDEV20(Review, {
3613
+ repoPath
3614
+ }, undefined, false, undefined, this)
3615
+ }, undefined, false, undefined, this), {
3616
+ exitOnCtrlC: false,
3617
+ useKittyKeyboard: {},
3618
+ postProcessFns: RECORD_FRAMES ? [
3619
+ (buffer) => {
3620
+ const frameBytes = buffer.getRealCharBytes(true);
3621
+ const frame = decoder.decode(frameBytes);
3622
+ mkdirSync2("frames", { recursive: true });
3623
+ writeFileSync2(`frames/frame-${String(frameNumber++).padStart(5, "0")}.txt`, frame);
3624
+ }
3625
+ ] : undefined
3626
+ });
3627
+ }
3628
+ if (import.meta.main) {
3629
+ tui();
3630
+ }
3631
+ export {
3632
+ tui
3633
+ };
3634
+
3635
+ //# debugId=75DD348E34E5E40964756E2164756E21