@peaske7/readit 0.1.8 → 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 +124 -172
  19. package/src/{cli/index.ts → cli.ts} +37 -53
  20. package/src/components/ActionsMenu.tsx +6 -27
  21. package/src/components/DocumentViewer/DocumentViewer.tsx +77 -106
  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} +74 -74
  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
@@ -0,0 +1,112 @@
1
+ import type { HighlightComment, TextPosition } from "./types";
2
+
3
+ export function findTextPosition(
4
+ textContent: string,
5
+ selectedText: string,
6
+ hintOffset?: number,
7
+ ): TextPosition | undefined {
8
+ if (!selectedText || !textContent) return undefined;
9
+
10
+ const occurrences: number[] = [];
11
+ let idx = 0;
12
+ for (;;) {
13
+ idx = textContent.indexOf(selectedText, idx);
14
+ if (idx === -1) break;
15
+ occurrences.push(idx);
16
+ idx += 1;
17
+ }
18
+
19
+ if (occurrences.length === 0) return undefined;
20
+ if (occurrences.length === 1) {
21
+ return { start: occurrences[0], end: occurrences[0] + selectedText.length };
22
+ }
23
+
24
+ const target = hintOffset ?? 0;
25
+ let closest = occurrences[0];
26
+ let minDist = Math.abs(closest - target);
27
+ for (const occ of occurrences) {
28
+ const dist = Math.abs(occ - target);
29
+ if (dist < minDist) {
30
+ minDist = dist;
31
+ closest = occ;
32
+ }
33
+ }
34
+ return { start: closest, end: closest + selectedText.length };
35
+ }
36
+
37
+ export class Resolver {
38
+ private worker: Worker | null = null;
39
+ private seq = 0;
40
+ private pending = new Map<
41
+ number,
42
+ (results: Map<string, TextPosition>) => void
43
+ >();
44
+
45
+ constructor() {
46
+ try {
47
+ this.worker = new Worker(new URL("./worker.ts", import.meta.url), {
48
+ type: "module",
49
+ });
50
+ this.worker.onmessage = this.onMessage;
51
+ this.worker.onerror = () => {
52
+ this.worker?.terminate();
53
+ this.worker = null;
54
+ };
55
+ } catch {
56
+ this.worker = null;
57
+ }
58
+ }
59
+
60
+ resolve(
61
+ text: string,
62
+ comments: HighlightComment[],
63
+ ): Promise<Map<string, TextPosition>> {
64
+ if (!this.worker || comments.length === 0) {
65
+ return Promise.resolve(this.sync(text, comments));
66
+ }
67
+
68
+ return new Promise((resolve) => {
69
+ const id = this.seq++;
70
+ this.pending.set(id, resolve);
71
+ this.worker!.postMessage({
72
+ id,
73
+ textContent: text,
74
+ comments: comments.map((c) => ({
75
+ id: c.id,
76
+ selectedText: c.selectedText,
77
+ startOffset: c.startOffset,
78
+ })),
79
+ });
80
+ });
81
+ }
82
+
83
+ dispose() {
84
+ this.worker?.terminate();
85
+ this.worker = null;
86
+ for (const resolve of this.pending.values()) resolve(new Map());
87
+ this.pending.clear();
88
+ }
89
+
90
+ private onMessage = (e: MessageEvent) => {
91
+ const { id, results } = e.data;
92
+ const resolve = this.pending.get(id);
93
+ if (!resolve) return;
94
+
95
+ this.pending.delete(id);
96
+ const map = new Map<string, TextPosition>();
97
+ for (const r of results) map.set(r.id, { start: r.start, end: r.end });
98
+ resolve(map);
99
+ };
100
+
101
+ private sync(
102
+ text: string,
103
+ comments: HighlightComment[],
104
+ ): Map<string, TextPosition> {
105
+ const map = new Map<string, TextPosition>();
106
+ for (const c of comments) {
107
+ const pos = findTextPosition(text, c.selectedText, c.startOffset);
108
+ if (pos) map.set(c.id, pos);
109
+ }
110
+ return map;
111
+ }
112
+ }
@@ -1,34 +1,13 @@
1
- /**
2
- * Shared types for the highlighting system.
3
- */
4
-
5
- /**
6
- * Style configuration for highlight marks.
7
- */
8
1
  export interface HighlightStyle {
9
2
  attribute: string;
10
3
  attributeValue: string;
11
4
  }
12
5
 
13
- /**
14
- * Text range with character offsets.
15
- */
16
- export interface TextRange {
17
- startOffset: number;
18
- endOffset: number;
19
- }
20
-
21
- /**
22
- * Resolved text position in content.
23
- */
24
6
  export interface TextPosition {
25
7
  start: number;
26
8
  end: number;
27
9
  }
28
10
 
29
- /**
30
- * Comment data needed for highlighting (subset of full Comment type).
31
- */
32
11
  export interface HighlightComment {
33
12
  id: string;
34
13
  selectedText: string;
@@ -36,20 +15,6 @@ export interface HighlightComment {
36
15
  endOffset: number;
37
16
  }
38
17
 
39
- /**
40
- * Highlight positions for margin note alignment and minimap.
41
- * - positions: Y-position relative to container (for margin notes)
42
- * - documentPositions: Y-position from document top (for minimap)
43
- */
44
- export interface HighlightPositions {
45
- positions: Record<string, number>;
46
- documentPositions: Record<string, number>;
47
- pendingTop?: number;
48
- }
49
-
50
- /**
51
- * Text node with its cumulative offset range in the document.
52
- */
53
18
  export interface TextNodeInfo {
54
19
  node: Text;
55
20
  start: number;
@@ -0,0 +1,45 @@
1
+ // Inlined to avoid module import issues in Worker context
2
+ function find(
3
+ text: string,
4
+ needle: string,
5
+ hint?: number,
6
+ ): { start: number; end: number } | undefined {
7
+ if (!needle || !text) return undefined;
8
+
9
+ const hits: number[] = [];
10
+ let i = 0;
11
+ for (;;) {
12
+ i = text.indexOf(needle, i);
13
+ if (i === -1) break;
14
+ hits.push(i);
15
+ i += 1;
16
+ }
17
+
18
+ if (hits.length === 0) return undefined;
19
+ if (hits.length === 1)
20
+ return { start: hits[0], end: hits[0] + needle.length };
21
+
22
+ const target = hint ?? 0;
23
+ let best = hits[0];
24
+ let bestDist = Math.abs(best - target);
25
+ for (const h of hits) {
26
+ const d = Math.abs(h - target);
27
+ if (d < bestDist) {
28
+ bestDist = d;
29
+ best = h;
30
+ }
31
+ }
32
+ return { start: best, end: best + needle.length };
33
+ }
34
+
35
+ self.onmessage = (e: MessageEvent) => {
36
+ const { id, textContent, comments } = e.data;
37
+ const results: { id: string; start: number; end: number }[] = [];
38
+
39
+ for (const c of comments) {
40
+ const pos = find(textContent, c.selectedText, c.startOffset);
41
+ if (pos) results.push({ id: c.id, ...pos });
42
+ }
43
+
44
+ self.postMessage({ id, results });
45
+ };
@@ -13,14 +13,9 @@ export const en: Translations = {
13
13
 
14
14
  // Actions menu
15
15
  "actions.ariaLabel": "Actions menu",
16
- "actions.centered": "Centered",
17
- "actions.fullscreen": "Fullscreen",
18
16
  "actions.settings": "Settings",
19
17
  "actions.reload": "Reload",
20
- "actions.copyAllAI": "Copy All (AI)",
21
- "actions.copyAllAITitle": "Copy in prompt format for AI assistants",
22
- "actions.copyAllRaw": "Copy All (Raw)",
23
- "actions.copyAllRawTitle": "Copy as plain text",
18
+ "actions.copyAll": "Copy All",
24
19
  "actions.exportJson": "Export JSON",
25
20
  "actions.viewRaw": "View Raw",
26
21
 
@@ -29,37 +24,23 @@ export const en: Translations = {
29
24
  "settings.theme": "Theme",
30
25
  "settings.font": "Font",
31
26
  "settings.language": "Language",
32
- "settings.keyboardShortcuts": "Keyboard Shortcuts",
33
- "settings.clickToRebind": "Click a key to rebind",
34
27
  "settings.theme.system": "System",
35
28
  "settings.theme.light": "Light",
36
29
  "settings.theme.dark": "Dark",
37
30
  "settings.font.serif": "Serif",
38
31
  "settings.font.sansSerif": "Sans-serif",
39
- "settings.editor": "Editor",
40
- "settings.editor.none": "None",
41
- "settings.editor.vscode": "VS Code",
42
- "settings.editor.vscodeInsiders": "VS Code Insiders",
43
- "settings.editor.cursor": "Cursor",
44
32
 
45
33
  // Comment input
46
34
  "comment.placeholder": "Add your comment...",
47
35
  "comment.cancel": "Cancel",
48
36
  "comment.addNote": "Add Note",
49
37
  "comment.highlight": "Highlight",
50
- "comment.copyRawTitle": "Copy raw text (⌘C)",
51
- "comment.copyRawLabel": "Copy raw text",
52
- "comment.copyLLMTitle": "Copy with context for LLM (⌘⇧C)",
53
- "comment.copyLLMLabel": "Copy for LLM",
54
38
 
55
39
  // Margin note
56
40
  "marginNote.addNote": "Add note",
57
41
  "marginNote.delete": "Delete",
58
42
  "marginNote.edit": "Edit",
59
43
  "marginNote.copy": "Copy",
60
- "marginNote.copyTitle": "Copy raw text (⌘C)",
61
- "marginNote.llm": "LLM",
62
- "marginNote.llmTitle": "Copy with context for LLM (⌘⇧C)",
63
44
 
64
45
  // Comment manager
65
46
  "commentManager.unresolved": "unresolved",
@@ -99,39 +80,9 @@ export const en: Translations = {
99
80
  "rawModal.copiedToClipboard": "Copied to clipboard",
100
81
  "rawModal.failedToCopy": "Failed to copy",
101
82
 
102
- // Shortcut groups
103
- "shortcutGroup.copy": "Copy",
104
- "shortcutGroup.navigate": "Navigate",
105
- "shortcutGroup.other": "Other",
106
- "shortcuts.resetToDefaults": "Reset to defaults",
107
- "shortcuts.enableDisable": "Enable/disable shortcut",
108
- "shortcutCapture.pressKeys": "Press keys...",
109
-
110
- // Shortcut labels
111
- "shortcut.copyAll.label": "Copy All (AI)",
112
- "shortcut.copyAll.description": "Copy all comments in AI prompt format",
113
- "shortcut.copyAllRaw.label": "Copy All (Raw)",
114
- "shortcut.copyAllRaw.description": "Copy all comments as raw text",
115
- "shortcut.navigateNext.label": "Next Comment",
116
- "shortcut.navigateNext.description": "Navigate to next comment",
117
- "shortcut.navigatePrevious.label": "Previous Comment",
118
- "shortcut.navigatePrevious.description": "Navigate to previous comment",
119
- "shortcut.copySelectionRaw.label": "Copy Selection",
120
- "shortcut.copySelectionRaw.description": "Copy selected text",
121
- "shortcut.copySelectionLLM.label": "Copy Selection (LLM)",
122
- "shortcut.copySelectionLLM.description":
123
- "Copy selected text with context for LLM",
124
- "shortcut.clearSelection.label": "Clear Selection",
125
- "shortcut.clearSelection.description": "Clear text selection",
126
-
127
83
  // Toast messages
128
84
  "toast.copied": 'Copied: "{{text}}"',
129
- "toast.copiedForLLM": 'Copied for LLM: "{{text}}"',
130
85
  "toast.copiedAllComments": "Copied all comments",
131
- "toast.copiedAllRaw": "Copied all comments as raw text",
132
-
133
- // Floating TOC
134
- "floatingTOC.label": "Table of Contents",
135
86
 
136
87
  // Comment badge
137
88
  "commentBadge.title": "{{count}} comment",
@@ -13,14 +13,9 @@ export const ja: Translations = {
13
13
 
14
14
  // Actions menu
15
15
  "actions.ariaLabel": "操作メニュー",
16
- "actions.centered": "中央揃え",
17
- "actions.fullscreen": "全画面",
18
16
  "actions.settings": "設定",
19
17
  "actions.reload": "再読み込み",
20
- "actions.copyAllAI": "全てコピー (AI)",
21
- "actions.copyAllAITitle": "AIアシスタント用プロンプト形式でコピー",
22
- "actions.copyAllRaw": "全てコピー (テキスト)",
23
- "actions.copyAllRawTitle": "プレーンテキストとしてコピー",
18
+ "actions.copyAll": "全てコピー",
24
19
  "actions.exportJson": "JSONエクスポート",
25
20
  "actions.viewRaw": "生データを表示",
26
21
 
@@ -29,37 +24,23 @@ export const ja: Translations = {
29
24
  "settings.theme": "テーマ",
30
25
  "settings.font": "フォント",
31
26
  "settings.language": "言語",
32
- "settings.keyboardShortcuts": "キーボードショートカット",
33
- "settings.clickToRebind": "キーをクリックして変更",
34
27
  "settings.theme.system": "システム",
35
28
  "settings.theme.light": "ライト",
36
29
  "settings.theme.dark": "ダーク",
37
30
  "settings.font.serif": "明朝体",
38
31
  "settings.font.sansSerif": "ゴシック体",
39
- "settings.editor": "エディター",
40
- "settings.editor.none": "なし",
41
- "settings.editor.vscode": "VS Code",
42
- "settings.editor.vscodeInsiders": "VS Code Insiders",
43
- "settings.editor.cursor": "Cursor",
44
32
 
45
33
  // Comment input
46
34
  "comment.placeholder": "コメントを入力...",
47
35
  "comment.cancel": "キャンセル",
48
36
  "comment.addNote": "メモを追加",
49
37
  "comment.highlight": "ハイライト",
50
- "comment.copyRawTitle": "テキストをコピー (⌘C)",
51
- "comment.copyRawLabel": "テキストをコピー",
52
- "comment.copyLLMTitle": "LLM用にコンテキスト付きでコピー (⌘⇧C)",
53
- "comment.copyLLMLabel": "LLM用にコピー",
54
38
 
55
39
  // Margin note
56
40
  "marginNote.addNote": "メモを追加",
57
41
  "marginNote.delete": "削除",
58
42
  "marginNote.edit": "編集",
59
43
  "marginNote.copy": "コピー",
60
- "marginNote.copyTitle": "テキストをコピー (⌘C)",
61
- "marginNote.llm": "LLM",
62
- "marginNote.llmTitle": "LLM用にコンテキスト付きでコピー (⌘⇧C)",
63
44
 
64
45
  // Comment manager
65
46
  "commentManager.unresolved": "未解決",
@@ -101,39 +82,9 @@ export const ja: Translations = {
101
82
  "rawModal.copiedToClipboard": "クリップボードにコピーしました",
102
83
  "rawModal.failedToCopy": "コピーに失敗しました",
103
84
 
104
- // Shortcut groups
105
- "shortcutGroup.copy": "コピー",
106
- "shortcutGroup.navigate": "ナビゲーション",
107
- "shortcutGroup.other": "その他",
108
- "shortcuts.resetToDefaults": "初期設定に戻す",
109
- "shortcuts.enableDisable": "ショートカットの有効/無効",
110
- "shortcutCapture.pressKeys": "キーを入力...",
111
-
112
- // Shortcut labels
113
- "shortcut.copyAll.label": "全てコピー (AI)",
114
- "shortcut.copyAll.description": "全コメントをAIプロンプト形式でコピー",
115
- "shortcut.copyAllRaw.label": "全てコピー (テキスト)",
116
- "shortcut.copyAllRaw.description": "全コメントをテキストとしてコピー",
117
- "shortcut.navigateNext.label": "次のコメント",
118
- "shortcut.navigateNext.description": "次のコメントに移動",
119
- "shortcut.navigatePrevious.label": "前のコメント",
120
- "shortcut.navigatePrevious.description": "前のコメントに移動",
121
- "shortcut.copySelectionRaw.label": "選択をコピー",
122
- "shortcut.copySelectionRaw.description": "選択テキストをコピー",
123
- "shortcut.copySelectionLLM.label": "選択をコピー (LLM)",
124
- "shortcut.copySelectionLLM.description":
125
- "選択テキストをLLM用コンテキスト付きでコピー",
126
- "shortcut.clearSelection.label": "選択を解除",
127
- "shortcut.clearSelection.description": "テキスト選択を解除",
128
-
129
85
  // Toast messages
130
86
  "toast.copied": 'コピーしました: "{{text}}"',
131
- "toast.copiedForLLM": 'LLM用にコピーしました: "{{text}}"',
132
87
  "toast.copiedAllComments": "全てのコメントをコピーしました",
133
- "toast.copiedAllRaw": "全てのコメントをテキストとしてコピーしました",
134
-
135
- // Floating TOC
136
- "floatingTOC.label": "目次",
137
88
 
138
89
  // Comment badge
139
90
  "commentBadge.title": "{{count}}件のコメント",
@@ -18,14 +18,9 @@ export interface Translations {
18
18
 
19
19
  // Actions menu
20
20
  "actions.ariaLabel": string;
21
- "actions.centered": string;
22
- "actions.fullscreen": string;
23
21
  "actions.settings": string;
24
22
  "actions.reload": string;
25
- "actions.copyAllAI": string;
26
- "actions.copyAllAITitle": string;
27
- "actions.copyAllRaw": string;
28
- "actions.copyAllRawTitle": string;
23
+ "actions.copyAll": string;
29
24
  "actions.exportJson": string;
30
25
  "actions.viewRaw": string;
31
26
 
@@ -34,37 +29,23 @@ export interface Translations {
34
29
  "settings.theme": string;
35
30
  "settings.font": string;
36
31
  "settings.language": string;
37
- "settings.keyboardShortcuts": string;
38
- "settings.clickToRebind": string;
39
32
  "settings.theme.system": string;
40
33
  "settings.theme.light": string;
41
34
  "settings.theme.dark": string;
42
35
  "settings.font.serif": string;
43
36
  "settings.font.sansSerif": string;
44
- "settings.editor": string;
45
- "settings.editor.none": string;
46
- "settings.editor.vscode": string;
47
- "settings.editor.vscodeInsiders": string;
48
- "settings.editor.cursor": string;
49
37
 
50
38
  // Comment input
51
39
  "comment.placeholder": string;
52
40
  "comment.cancel": string;
53
41
  "comment.addNote": string;
54
42
  "comment.highlight": string;
55
- "comment.copyRawTitle": string;
56
- "comment.copyRawLabel": string;
57
- "comment.copyLLMTitle": string;
58
- "comment.copyLLMLabel": string;
59
43
 
60
44
  // Margin note
61
45
  "marginNote.addNote": string;
62
46
  "marginNote.delete": string;
63
47
  "marginNote.edit": string;
64
48
  "marginNote.copy": string;
65
- "marginNote.copyTitle": string;
66
- "marginNote.llm": string;
67
- "marginNote.llmTitle": string;
68
49
 
69
50
  // Comment manager
70
51
  "commentManager.unresolved": string;
@@ -104,38 +85,9 @@ export interface Translations {
104
85
  "rawModal.copiedToClipboard": string;
105
86
  "rawModal.failedToCopy": string;
106
87
 
107
- // Shortcut groups
108
- "shortcutGroup.copy": string;
109
- "shortcutGroup.navigate": string;
110
- "shortcutGroup.other": string;
111
- "shortcuts.resetToDefaults": string;
112
- "shortcuts.enableDisable": string;
113
- "shortcutCapture.pressKeys": string;
114
-
115
- // Shortcut labels (rendered in ShortcutList)
116
- "shortcut.copyAll.label": string;
117
- "shortcut.copyAll.description": string;
118
- "shortcut.copyAllRaw.label": string;
119
- "shortcut.copyAllRaw.description": string;
120
- "shortcut.navigateNext.label": string;
121
- "shortcut.navigateNext.description": string;
122
- "shortcut.navigatePrevious.label": string;
123
- "shortcut.navigatePrevious.description": string;
124
- "shortcut.copySelectionRaw.label": string;
125
- "shortcut.copySelectionRaw.description": string;
126
- "shortcut.copySelectionLLM.label": string;
127
- "shortcut.copySelectionLLM.description": string;
128
- "shortcut.clearSelection.label": string;
129
- "shortcut.clearSelection.description": string;
130
-
131
88
  // Toast messages
132
89
  "toast.copied": string;
133
- "toast.copiedForLLM": string;
134
90
  "toast.copiedAllComments": string;
135
- "toast.copiedAllRaw": string;
136
-
137
- // Floating TOC
138
- "floatingTOC.label": string;
139
91
 
140
92
  // Comment badge
141
93
  "commentBadge.title": string;
@@ -1,7 +1,5 @@
1
- import {
2
- COMMENT_INPUT_HEIGHT_PX,
3
- MARGIN_NOTE_MIN_GAP_PX,
4
- } from "./layout-constants";
1
+ const MARGIN_NOTE_MIN_GAP_PX = 150;
2
+ const COMMENT_INPUT_HEIGHT_PX = 160;
5
3
 
6
4
  interface NotePosition {
7
5
  commentId: string;
@@ -10,18 +8,7 @@ interface NotePosition {
10
8
 
11
9
  /**
12
10
  * Resolves margin note positions to avoid overlaps.
13
- *
14
- * Algorithm:
15
- * 1. Build initial positions from highlight positions
16
- * 2. Handle input zone collision (push notes up or down to avoid input)
17
- * 3. Resolve note-to-note overlaps:
18
- * - Pass 1: Notes above input zone → push UP
19
- * - Pass 2: Notes at/below input zone → push DOWN
20
- *
21
- * @param commentIds - Comment IDs in document order (sorted by startOffset)
22
- * @param highlightPositions - Map of comment ID to top position
23
- * @param pendingSelectionTop - Top position of input zone (if any)
24
- * @returns Map of comment ID to resolved top position
11
+ * Pass 1: push notes above input zone UP. Pass 2: push notes at/below DOWN.
25
12
  */
26
13
  export function resolveMarginNotePositions(
27
14
  commentIds: string[],
@@ -36,10 +23,8 @@ export function resolveMarginNotePositions(
36
23
  top: highlightPositions[id],
37
24
  }));
38
25
 
39
- // Sort by top position
40
26
  positions.sort((a, b) => a.top - b.top);
41
27
 
42
- // Handle input zone collision - check visual overlap, not just top position
43
28
  if (pendingSelectionTop !== null && pendingSelectionTop !== undefined) {
44
29
  const inputStart = pendingSelectionTop;
45
30
  const inputEnd = pendingSelectionTop + COMMENT_INPUT_HEIGHT_PX;
@@ -47,48 +32,41 @@ export function resolveMarginNotePositions(
47
32
  for (const pos of positions) {
48
33
  const noteBottom = pos.top + MARGIN_NOTE_MIN_GAP_PX;
49
34
 
50
- // Check if note visually overlaps with input zone
51
35
  const overlaps = noteBottom > inputStart && pos.top < inputEnd;
52
36
 
53
37
  if (overlaps) {
54
38
  if (pos.top < inputStart) {
55
- // Note is above input but overlaps - push UP
56
39
  pos.top = Math.max(0, inputStart - MARGIN_NOTE_MIN_GAP_PX);
57
40
  } else {
58
- // Note is within/below input zone - push DOWN
59
41
  pos.top = inputEnd;
60
42
  }
61
43
  }
62
44
  }
63
- // Re-sort after potential position changes
64
45
  positions.sort((a, b) => a.top - b.top);
65
46
  }
66
47
 
67
- // Resolve note-to-note overlaps
68
48
  const inputStartForOverlap = pendingSelectionTop ?? Infinity;
69
49
  const inputEndForOverlap =
70
50
  pendingSelectionTop != null
71
51
  ? pendingSelectionTop + COMMENT_INPUT_HEIGHT_PX
72
52
  : Infinity;
73
53
 
74
- // Pass 1: Notes ABOVE input - resolve by pushing UP (bottom to top)
54
+ // Pass 1: push notes above input UP (bottom to top)
75
55
  for (let i = positions.length - 2; i >= 0; i--) {
76
56
  const curr = positions[i];
77
57
  const next = positions[i + 1];
78
- // Only process if next note is above the input zone
79
58
  if (next.top >= inputStartForOverlap) continue;
80
59
  if (next.top - curr.top < MARGIN_NOTE_MIN_GAP_PX) {
81
60
  curr.top = Math.max(0, next.top - MARGIN_NOTE_MIN_GAP_PX);
82
61
  }
83
62
  }
84
63
 
85
- // Pass 2: Notes at/below input - resolve by pushing DOWN (top to bottom)
64
+ // Pass 2: push notes at/below input DOWN (top to bottom)
86
65
  for (let i = 1; i < positions.length; i++) {
87
66
  const prev = positions[i - 1];
88
67
  const curr = positions[i];
89
68
  if (curr.top - prev.top < MARGIN_NOTE_MIN_GAP_PX) {
90
69
  let newTop = prev.top + MARGIN_NOTE_MIN_GAP_PX;
91
- // If new position lands in input zone, skip to below input
92
70
  if (newTop >= inputStartForOverlap && newTop < inputEndForOverlap) {
93
71
  newTop = inputEndForOverlap;
94
72
  }