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