@peaske7/readit 0.1.7 → 0.2.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 (118) hide show
  1. package/README.md +0 -3
  2. package/biome.json +1 -1
  3. package/bun.lock +43 -185
  4. package/docs/perf-baseline.md +75 -0
  5. package/docs/superpowers/plans/2026-03-26-surgical-pruning.md +1176 -0
  6. package/e2e/perf/add-comment.spec.ts +118 -0
  7. package/e2e/perf/fixtures/generate.ts +331 -0
  8. package/e2e/perf/initial-load.spec.ts +49 -0
  9. package/e2e/perf/perf.setup.ts +23 -0
  10. package/e2e/perf/perf.teardown.ts +9 -0
  11. package/e2e/perf/scroll.spec.ts +39 -0
  12. package/e2e/perf/tab-switch.spec.ts +69 -0
  13. package/e2e/perf/text-selection.spec.ts +119 -0
  14. package/e2e/perf/utils/metrics.ts +286 -0
  15. package/e2e/perf/utils/perf-cli.ts +86 -0
  16. package/package.json +9 -18
  17. package/playwright.config.ts +12 -0
  18. package/src/App.tsx +133 -178
  19. package/src/{cli/index.ts → cli.ts} +211 -107
  20. package/src/components/ActionsMenu.tsx +6 -27
  21. package/src/components/DocumentViewer/DocumentViewer.tsx +78 -105
  22. package/src/components/DocumentViewer/MermaidDiagram.tsx +6 -7
  23. package/src/components/Header.tsx +9 -20
  24. package/src/components/InlineEditor.tsx +5 -5
  25. package/src/components/MarginNote.tsx +71 -93
  26. package/src/components/MarginNotes.tsx +7 -34
  27. package/src/components/RawModal.tsx +9 -8
  28. package/src/components/ReanchorConfirm.tsx +2 -2
  29. package/src/components/SettingsModal.tsx +11 -89
  30. package/src/components/TabBar.tsx +4 -4
  31. package/src/components/TableOfContents.tsx +5 -5
  32. package/src/components/comments/CommentInput.tsx +7 -35
  33. package/src/components/comments/CommentListItem.tsx +9 -11
  34. package/src/components/comments/CommentManager.tsx +53 -37
  35. package/src/components/comments/CommentNav.tsx +14 -14
  36. package/src/components/ui/ActionLink.tsx +14 -18
  37. package/src/components/ui/Button.tsx +42 -43
  38. package/src/components/ui/Dialog.tsx +73 -113
  39. package/src/components/ui/DropdownMenu.tsx +113 -69
  40. package/src/components/ui/Text.tsx +30 -37
  41. package/src/contexts/CommentContext.tsx +75 -106
  42. package/src/contexts/LocaleContext.tsx +45 -4
  43. package/src/contexts/PositionsContext.tsx +16 -0
  44. package/src/contexts/SettingsContext.tsx +133 -0
  45. package/src/hooks/useClickOutside.ts +0 -4
  46. package/src/hooks/useCommentNavigation.ts +6 -29
  47. package/src/hooks/useComments.ts +6 -18
  48. package/src/hooks/useDocument.ts +35 -34
  49. package/src/hooks/useHeadings.test.ts +8 -50
  50. package/src/hooks/useHeadings.ts +5 -88
  51. package/src/hooks/useScrollSpy.ts +10 -14
  52. package/src/hooks/useTextSelection.ts +1 -38
  53. package/src/lib/__fixtures__/bench-data.ts +1 -41
  54. package/src/lib/anchor.bench.ts +57 -67
  55. package/src/lib/anchor.test.ts +5 -1
  56. package/src/lib/anchor.ts +13 -93
  57. package/src/lib/comment-storage.test.ts +4 -4
  58. package/src/lib/comment-storage.ts +2 -46
  59. package/src/lib/export.ts +7 -13
  60. package/src/lib/highlight/core.test.ts +1 -1
  61. package/src/lib/highlight/dom.ts +5 -68
  62. package/src/lib/highlight/highlighter.ts +102 -262
  63. package/src/lib/highlight/resolver.ts +112 -0
  64. package/src/lib/highlight/types.ts +0 -35
  65. package/src/lib/highlight/worker.ts +45 -0
  66. package/src/lib/i18n/en.ts +1 -50
  67. package/src/lib/i18n/ja.ts +1 -50
  68. package/src/lib/i18n/types.ts +1 -49
  69. package/src/lib/margin-layout.ts +5 -27
  70. package/src/lib/positions.ts +150 -0
  71. package/src/lib/utils.ts +2 -19
  72. package/src/schema.ts +81 -0
  73. package/src/{server/index.ts → server.ts} +111 -81
  74. package/src/{store/index.ts → store.ts} +14 -46
  75. package/vite.config.ts +8 -0
  76. package/src/components/DocumentViewer/IframeContainer.tsx +0 -251
  77. package/src/components/DocumentViewer/InlineCode.tsx +0 -60
  78. package/src/components/DocumentViewer/index.ts +0 -1
  79. package/src/components/FloatingTOC.tsx +0 -61
  80. package/src/components/ShortcutCapture.tsx +0 -48
  81. package/src/components/ShortcutList.tsx +0 -198
  82. package/src/components/comments/CommentMinimap.tsx +0 -62
  83. package/src/components/ui/ActionBar.tsx +0 -16
  84. package/src/components/ui/SeparatorDot.tsx +0 -9
  85. package/src/contexts/LayoutContext.tsx +0 -88
  86. package/src/hooks/useClipboard.ts +0 -82
  87. package/src/hooks/useEditorScheme.ts +0 -51
  88. package/src/hooks/useFontPreference.ts +0 -59
  89. package/src/hooks/useKeybindings.ts +0 -108
  90. package/src/hooks/useKeyboardShortcuts.ts +0 -63
  91. package/src/hooks/useLayoutMode.ts +0 -44
  92. package/src/hooks/useLocalePreference.ts +0 -42
  93. package/src/hooks/useReanchorMode.ts +0 -33
  94. package/src/hooks/useScrollMetrics.ts +0 -56
  95. package/src/hooks/useThemePreference.ts +0 -66
  96. package/src/lib/comment-storage.bench.ts +0 -63
  97. package/src/lib/context.bench.ts +0 -41
  98. package/src/lib/context.test.ts +0 -224
  99. package/src/lib/context.ts +0 -193
  100. package/src/lib/editor-links.ts +0 -59
  101. package/src/lib/export.bench.ts +0 -35
  102. package/src/lib/highlight/colors.ts +0 -37
  103. package/src/lib/highlight/core.ts +0 -54
  104. package/src/lib/highlight/index.ts +0 -23
  105. package/src/lib/highlight/script-builder.ts +0 -485
  106. package/src/lib/html-processor.test.tsx +0 -170
  107. package/src/lib/html-processor.tsx +0 -95
  108. package/src/lib/i18n/completeness.test.ts +0 -51
  109. package/src/lib/i18n/translations.test.ts +0 -39
  110. package/src/lib/layout-constants.ts +0 -12
  111. package/src/lib/margin-layout.bench.ts +0 -28
  112. package/src/lib/scroll.test.ts +0 -118
  113. package/src/lib/scroll.ts +0 -47
  114. package/src/lib/shortcut-registry.test.ts +0 -173
  115. package/src/lib/shortcut-registry.ts +0 -209
  116. package/src/lib/utils.test.ts +0 -110
  117. package/src/store/index.test.ts +0 -242
  118. package/src/types/index.ts +0 -127
@@ -13,11 +13,10 @@ import * as os from "node:os";
13
13
  import { join, resolve } from "node:path";
14
14
  import { Command } from "commander";
15
15
  import open from "open";
16
- import { getCommentPath, parseCommentFile } from "../lib/comment-storage.js";
17
- import { getFileType } from "../lib/utils.js";
18
- import type { FileEntry } from "../server/index.js";
19
- import { removeServerInfo, startServer } from "../server/index.js";
20
- import type { DocumentType } from "../types/index.js";
16
+ import { getCommentPath, parseCommentFile } from "./lib/comment-storage.js";
17
+ import { isMarkdownFile } from "./lib/utils.js";
18
+ import type { FileEntry } from "./server.js";
19
+ import { removeServerInfo, startServer } from "./server.js";
21
20
 
22
21
  const program = new Command();
23
22
 
@@ -34,17 +33,113 @@ interface ServerInfo {
34
33
  pid: number;
35
34
  }
36
35
 
37
- async function discoverServer(): Promise<ServerInfo | null> {
38
- const serverInfoPath = join(os.homedir(), ".readit", "server.json");
36
+ interface ServerTarget {
37
+ kind: "existing" | "started";
38
+ port: number;
39
+ url: string;
40
+ server?: { stop(): void };
41
+ }
42
+
43
+ const READIT_DIR = join(os.homedir(), ".readit");
44
+ const SERVER_INFO_PATH = join(READIT_DIR, "server.json");
45
+ const SERVER_LOCK_PATH = join(READIT_DIR, "server.lock");
46
+ const SERVER_LOCK_MAX_AGE_MS = 30_000;
47
+ const SERVER_LOCK_TIMEOUT_MS = 10_000;
48
+ const SERVER_LOCK_WAIT_MS = 100;
49
+
50
+ function isAlive(pid: number): boolean {
51
+ try {
52
+ process.kill(pid, 0);
53
+ return true;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ function getErrnoCode(err: unknown): string | undefined {
60
+ return err instanceof Error && "code" in err
61
+ ? (err as NodeJS.ErrnoException).code
62
+ : undefined;
63
+ }
64
+
65
+ function sleep(ms: number): Promise<void> {
66
+ return new Promise((resolve) => setTimeout(resolve, ms));
67
+ }
68
+
69
+ async function clearStaleServerLock(): Promise<void> {
70
+ try {
71
+ const [stats, content] = await Promise.all([
72
+ fs.stat(SERVER_LOCK_PATH),
73
+ fs.readFile(SERVER_LOCK_PATH, "utf-8").catch(() => ""),
74
+ ]);
75
+
76
+ const age = Date.now() - stats.mtimeMs;
77
+ let pid: number | undefined;
78
+
79
+ if (content) {
80
+ try {
81
+ const lock = JSON.parse(content) as { pid?: number };
82
+ pid = lock.pid;
83
+ } catch {
84
+ // Ignore malformed lock files and fall back to age-based cleanup.
85
+ }
86
+ }
87
+
88
+ if (age > SERVER_LOCK_MAX_AGE_MS || (pid !== undefined && !isAlive(pid))) {
89
+ await fs.unlink(SERVER_LOCK_PATH).catch(() => {});
90
+ }
91
+ } catch (err) {
92
+ if (getErrnoCode(err) !== "ENOENT") throw err;
93
+ }
94
+ }
95
+
96
+ async function withServerLock<T>(run: () => Promise<T>): Promise<T> {
97
+ await fs.mkdir(READIT_DIR, { recursive: true });
98
+ const start = Date.now();
99
+
100
+ while (true) {
101
+ let handle: fs.FileHandle | undefined;
102
+
103
+ try {
104
+ handle = await fs.open(SERVER_LOCK_PATH, "wx");
105
+ await handle.writeFile(
106
+ JSON.stringify({ pid: process.pid, createdAt: Date.now() }),
107
+ "utf-8",
108
+ );
109
+
110
+ try {
111
+ return await run();
112
+ } finally {
113
+ await handle.close().catch(() => {});
114
+ await fs.unlink(SERVER_LOCK_PATH).catch(() => {});
115
+ }
116
+ } catch (err) {
117
+ if (handle) {
118
+ await handle.close().catch(() => {});
119
+ }
120
+
121
+ if (getErrnoCode(err) !== "EEXIST") {
122
+ throw err;
123
+ }
124
+
125
+ await clearStaleServerLock();
126
+
127
+ if (Date.now() - start >= SERVER_LOCK_TIMEOUT_MS) {
128
+ throw new Error("Timed out waiting for readit server lock");
129
+ }
39
130
 
131
+ await sleep(SERVER_LOCK_WAIT_MS);
132
+ }
133
+ }
134
+ }
135
+
136
+ async function discoverServer(): Promise<ServerInfo | null> {
40
137
  try {
41
- const content = readFileSync(serverInfoPath, "utf-8");
138
+ const content = readFileSync(SERVER_INFO_PATH, "utf-8");
42
139
  const info: ServerInfo = JSON.parse(content);
43
140
 
44
141
  // Verify the process is alive
45
- try {
46
- process.kill(info.pid, 0);
47
- } catch {
142
+ if (!isAlive(info.pid)) {
48
143
  return null;
49
144
  }
50
145
 
@@ -62,9 +157,65 @@ async function discoverServer(): Promise<ServerInfo | null> {
62
157
  }
63
158
  }
64
159
 
65
- /**
66
- * Recursively find all .comments.md files in a directory.
67
- */
160
+ async function attachFiles(
161
+ server: ServerInfo,
162
+ files: { path: string }[],
163
+ ): Promise<void> {
164
+ for (const file of files) {
165
+ try {
166
+ const res = await fetch(`http://127.0.0.1:${server.port}/api/documents`, {
167
+ method: "POST",
168
+ headers: { "Content-Type": "application/json" },
169
+ body: JSON.stringify({ path: file.path }),
170
+ });
171
+
172
+ if (!res.ok) {
173
+ const data = await res.json();
174
+ console.error(`error: failed to add ${file.path}: ${data.error}`);
175
+ process.exit(1);
176
+ }
177
+
178
+ const data = await res.json();
179
+ if (data.status === "added") {
180
+ console.log(`Added: ${data.fileName}`);
181
+ } else {
182
+ console.log(`Present: ${data.fileName}`);
183
+ }
184
+ } catch (err) {
185
+ console.error(
186
+ "error: failed to connect to server:",
187
+ err instanceof Error ? err.message : err,
188
+ );
189
+ process.exit(1);
190
+ }
191
+ }
192
+ }
193
+
194
+ async function getServerTarget(
195
+ files: FileEntry[],
196
+ port: number,
197
+ host: string,
198
+ ): Promise<ServerTarget> {
199
+ return withServerLock(async () => {
200
+ const server = await discoverServer();
201
+ if (server) {
202
+ return {
203
+ kind: "existing",
204
+ port: server.port,
205
+ url: `http://127.0.0.1:${server.port}`,
206
+ };
207
+ }
208
+
209
+ const started = await startServer({ files, port, host });
210
+ return {
211
+ kind: "started",
212
+ port: started.port,
213
+ url: started.url,
214
+ server: started.server,
215
+ };
216
+ });
217
+ }
218
+
68
219
  function findCommentFiles(dir: string): string[] {
69
220
  const results: string[] = [];
70
221
 
@@ -95,9 +246,6 @@ function findCommentFiles(dir: string): string[] {
95
246
  return results;
96
247
  }
97
248
 
98
- /**
99
- * Recursively find reviewable files (.md, .markdown, .html, .htm) in a directory.
100
- */
101
249
  function findReviewableFiles(dir: string): FileEntry[] {
102
250
  const results: FileEntry[] = [];
103
251
 
@@ -113,14 +261,8 @@ function findReviewableFiles(dir: string): FileEntry[] {
113
261
  if (lstat.isSymbolicLink()) continue;
114
262
  if (lstat.isDirectory()) {
115
263
  results.push(...findReviewableFiles(fullPath));
116
- } else {
117
- const type = getFileType(entry);
118
- if (type) {
119
- results.push({
120
- type,
121
- filePath: fullPath,
122
- });
123
- }
264
+ } else if (isMarkdownFile(entry)) {
265
+ results.push({ filePath: fullPath });
124
266
  }
125
267
  } catch (err) {
126
268
  if (isPermissionError(err)) {
@@ -137,9 +279,6 @@ function findReviewableFiles(dir: string): FileEntry[] {
137
279
  return results;
138
280
  }
139
281
 
140
- /**
141
- * Resolve CLI arguments into a deduplicated list of FileEntry objects.
142
- */
143
282
  function resolveFiles(args: string[]): FileEntry[] {
144
283
  const seen = new Set<string>();
145
284
  const files: FileEntry[] = [];
@@ -167,27 +306,21 @@ function resolveFiles(args: string[]): FileEntry[] {
167
306
  } else {
168
307
  if (seen.has(filePath)) continue;
169
308
 
170
- const type = getFileType(filePath);
171
- if (!type) {
309
+ if (!isMarkdownFile(filePath)) {
172
310
  console.error(
173
- `error: unsupported file type: ${arg} (expected .md, .markdown, .html, or .htm)`,
311
+ `error: unsupported file type: ${arg} (expected .md or .markdown)`,
174
312
  );
175
313
  process.exit(1);
176
314
  }
177
315
 
178
316
  seen.add(filePath);
179
- files.push({
180
- type,
181
- filePath,
182
- });
317
+ files.push({ filePath });
183
318
  }
184
319
  }
185
320
 
186
321
  return files;
187
322
  }
188
323
 
189
- // ─── Onboarding ──────────────────────────────────────────────────────
190
-
191
324
  const SETTINGS_PATH = join(os.homedir(), ".readit", "settings.json");
192
325
 
193
326
  function isOnboarded(): boolean {
@@ -292,14 +425,11 @@ Go ahead and add a few comments to this document. When you're done, export them
292
425
 
293
426
  const WELCOME_PATH = join(os.homedir(), ".readit", "welcome.md");
294
427
 
295
- // ─── Program ─────────────────────────────────────────────────────────
296
-
297
428
  program
298
429
  .name("readit")
299
- .description("Review Markdown and HTML documents with inline comments")
430
+ .description("Review Markdown documents with inline comments")
300
431
  .version("0.1.3");
301
432
 
302
- // List command: show all commented files
303
433
  program
304
434
  .command("list")
305
435
  .description("List all files with comments")
@@ -338,7 +468,6 @@ program
338
468
  }
339
469
  });
340
470
 
341
- // Show command: display comments for a file
342
471
  program
343
472
  .command("show <file>")
344
473
  .description("Show comments for a file")
@@ -382,9 +511,8 @@ program
382
511
  }
383
512
  });
384
513
 
385
- // Main review command (default) — accepts zero or more files/directories
386
514
  program
387
- .argument("[files...]", "Markdown or HTML files/directories to review")
515
+ .argument("[files...]", "Markdown files/directories to review")
388
516
  .option("-p, --port <number>", "Port to run server on", "4567")
389
517
  .option("--host <address>", "Host address to bind to", "127.0.0.1")
390
518
  .option("--no-open", "Don't automatically open browser")
@@ -408,7 +536,6 @@ program
408
536
  files = [
409
537
  {
410
538
  content: WELCOME_CONTENT,
411
- type: "markdown" as DocumentType,
412
539
  filePath: WELCOME_PATH,
413
540
  },
414
541
  ];
@@ -433,6 +560,17 @@ program
433
560
  process.exit(1);
434
561
  }
435
562
 
563
+ // Snapshot previous session before startServer() overwrites server.json
564
+ let previousPort: number | undefined;
565
+ try {
566
+ const info = JSON.parse(readFileSync(SERVER_INFO_PATH, "utf-8"));
567
+ if (!isAlive(info.pid)) {
568
+ previousPort = info.port;
569
+ }
570
+ } catch {
571
+ // No previous session — will open browser normally
572
+ }
573
+
436
574
  try {
437
575
  const { url, server } = await startServer({
438
576
  files,
@@ -453,7 +591,7 @@ readit - Document Review Tool
453
591
  Server running. Press Ctrl+C to stop.
454
592
  `);
455
593
  } else {
456
- const fileList = files.map((f) => ` ${f.filePath} (${f.type})`);
594
+ const fileList = files.map((f) => ` ${f.filePath}`);
457
595
 
458
596
  console.log(`
459
597
  readit - Document Review Tool
@@ -467,7 +605,11 @@ ${fileList.join("\n")}
467
605
  `);
468
606
  }
469
607
 
470
- if (options.open) {
608
+ const browserLikelyOpen =
609
+ previousPort === preferredPort ||
610
+ process.env.NODE_ENV === "development";
611
+
612
+ if (options.open && !browserLikelyOpen) {
471
613
  open(url);
472
614
  }
473
615
 
@@ -493,17 +635,16 @@ ${fileList.join("\n")}
493
635
  },
494
636
  );
495
637
 
496
- // Open command: add files to running server or start new one
497
638
  program
498
639
  .command("open")
499
- .argument("<files...>", "Markdown or HTML files to add to running server")
640
+ .argument("<files...>", "Markdown files to add to running server")
500
641
  .description("Add files to a running readit server, or start a new one")
501
642
  .option("-p, --port <number>", "Port for new server (if starting)", "4567")
502
643
  .option("--host <address>", "Host for new server (if starting)", "127.0.0.1")
503
644
  .action(
504
645
  async (fileArgs: string[], options: { port: string; host: string }) => {
505
646
  // Resolve and validate files
506
- const resolvedFiles: { path: string; type: DocumentType }[] = [];
647
+ const resolvedFiles: { path: string }[] = [];
507
648
  for (const arg of fileArgs) {
508
649
  const inputPath = resolve(process.cwd(), arg);
509
650
 
@@ -514,91 +655,54 @@ program
514
655
 
515
656
  const filePath = realpathSync(inputPath);
516
657
 
517
- const type = getFileType(filePath);
518
- if (!type) {
658
+ if (!isMarkdownFile(filePath)) {
519
659
  console.error(
520
- `error: unsupported file type: ${arg} (expected .md, .markdown, .html, or .htm)`,
660
+ `error: unsupported file type: ${arg} (expected .md or .markdown)`,
521
661
  );
522
662
  process.exit(1);
523
663
  }
524
664
 
525
- resolvedFiles.push({ path: filePath, type });
665
+ resolvedFiles.push({ path: filePath });
526
666
  }
527
667
 
528
- // Try to find running server
529
- const server = await discoverServer();
530
-
531
- if (server) {
532
- // Send files to running server
533
- for (const file of resolvedFiles) {
534
- try {
535
- const res = await fetch(
536
- `http://127.0.0.1:${server.port}/api/documents`,
537
- {
538
- method: "POST",
539
- headers: { "Content-Type": "application/json" },
540
- body: JSON.stringify({ path: file.path }),
541
- },
542
- );
543
-
544
- if (!res.ok) {
545
- const data = await res.json();
546
- console.error(`error: failed to add ${file.path}: ${data.error}`);
547
- process.exit(1);
548
- }
549
-
550
- const data = await res.json();
551
- if (data.status === "added") {
552
- console.log(`Added: ${data.fileName} (${data.type})`);
553
- } else {
554
- console.log(`Present: ${data.fileName} (${data.type})`);
555
- }
556
- } catch (err) {
557
- console.error(
558
- "error: failed to connect to server:",
559
- err instanceof Error ? err.message : err,
560
- );
561
- process.exit(1);
562
- }
563
- }
564
-
565
- console.log(`\nServer: http://127.0.0.1:${server.port}`);
566
- return;
567
- }
568
-
569
- // No running server — start one
570
- console.log("No running server found, starting new one...\n");
571
-
572
668
  const files = resolvedFiles.map((f) => ({
573
- type: f.type,
574
669
  filePath: f.path,
575
670
  }));
576
671
 
577
672
  const preferredPort = Number.parseInt(options.port, 10);
578
673
  try {
579
- const { url, server: newServer } = await startServer({
674
+ const target = await getServerTarget(
580
675
  files,
581
- port: preferredPort,
582
- host: options.host,
583
- });
676
+ preferredPort,
677
+ options.host,
678
+ );
584
679
 
585
- const fileList = files.map((f) => ` ${f.filePath} (${f.type})`);
680
+ if (target.kind === "existing") {
681
+ await attachFiles(
682
+ { port: target.port, pid: process.pid },
683
+ resolvedFiles,
684
+ );
685
+ console.log(`\nServer: ${target.url}`);
686
+ return;
687
+ }
688
+
689
+ const fileList = files.map((f) => ` ${f.filePath}`);
586
690
  console.log(`
587
691
  readit - Document Review Tool
588
692
 
589
693
  ${files.length === 1 ? "File:" : "Files:"}
590
694
  ${fileList.join("\n")}
591
- URL: ${url}
695
+ URL: ${target.url}
592
696
 
593
697
  Server running. Close browser tab to stop.
594
698
  Press Ctrl+C to force stop.
595
699
  `);
596
700
 
597
- open(url);
701
+ open(target.url);
598
702
 
599
703
  process.on("SIGINT", async () => {
600
704
  console.log("\n\nShutting down...");
601
- newServer.stop();
705
+ target.server?.stop();
602
706
  await removeServerInfo();
603
707
  process.exit(0);
604
708
  });
@@ -1,17 +1,13 @@
1
1
  import {
2
- BotMessageSquare,
2
+ ClipboardCopy,
3
3
  FileDown,
4
4
  FileText,
5
- Maximize2,
6
- Minimize2,
7
5
  MoreHorizontal,
8
6
  RefreshCw,
9
7
  Settings,
10
- TextQuote,
11
8
  } from "lucide-react";
12
9
  import { useState } from "react";
13
- import { useCommentContext } from "../contexts/CommentContext";
14
- import { useLayoutContext } from "../contexts/LayoutContext";
10
+ import { useCommentData } from "../contexts/CommentContext";
15
11
  import { useLocale } from "../contexts/LocaleContext";
16
12
  import { RawModal } from "./RawModal";
17
13
  import { SettingsModal } from "./SettingsModal";
@@ -26,19 +22,16 @@ import {
26
22
 
27
23
  interface ActionsMenuProps {
28
24
  onCopyAll: () => void;
29
- onCopyAllRaw: () => void;
30
25
  onExportJson: () => void;
31
26
  onReload: () => void;
32
27
  }
33
28
 
34
29
  export function ActionsMenu({
35
30
  onCopyAll,
36
- onCopyAllRaw,
37
31
  onExportJson,
38
32
  onReload,
39
33
  }: ActionsMenuProps) {
40
- const { commentCount } = useCommentContext();
41
- const { isFullscreen, toggleLayoutMode } = useLayoutContext();
34
+ const { commentCount } = useCommentData();
42
35
  const { t } = useLocale();
43
36
 
44
37
  const [menuOpen, setMenuOpen] = useState(false);
@@ -59,10 +52,6 @@ export function ActionsMenu({
59
52
  </Button>
60
53
  </DropdownMenuTrigger>
61
54
  <DropdownMenuContent align="end" className="min-w-[160px]">
62
- <DropdownMenuItem onSelect={() => toggleLayoutMode()}>
63
- {isFullscreen ? <Minimize2 /> : <Maximize2 />}
64
- {isFullscreen ? t("actions.centered") : t("actions.fullscreen")}
65
- </DropdownMenuItem>
66
55
  <DropdownMenuItem onSelect={() => setSettingsOpen(true)}>
67
56
  <Settings />
68
57
  {t("actions.settings")}
@@ -74,19 +63,9 @@ export function ActionsMenu({
74
63
  </DropdownMenuItem>
75
64
  {commentCount > 0 && (
76
65
  <>
77
- <DropdownMenuItem
78
- onSelect={() => onCopyAll()}
79
- title={t("actions.copyAllAITitle")}
80
- >
81
- <BotMessageSquare />
82
- {t("actions.copyAllAI")}
83
- </DropdownMenuItem>
84
- <DropdownMenuItem
85
- onSelect={() => onCopyAllRaw()}
86
- title={t("actions.copyAllRawTitle")}
87
- >
88
- <TextQuote />
89
- {t("actions.copyAllRaw")}
66
+ <DropdownMenuItem onSelect={() => onCopyAll()}>
67
+ <ClipboardCopy />
68
+ {t("actions.copyAll")}
90
69
  </DropdownMenuItem>
91
70
  <DropdownMenuItem onSelect={() => onExportJson()}>
92
71
  <FileDown />