@peaske7/readit 0.2.0 → 0.3.0-rc.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 (179) hide show
  1. package/.claude/CLAUDE.md +118 -76
  2. package/.claude/commands/review.md +1 -1
  3. package/.claude/roadmap.md +32 -9
  4. package/.claude/user-stories.md +100 -15
  5. package/AGENTS.md +30 -26
  6. package/Makefile +32 -0
  7. package/README.md +90 -2
  8. package/biome.json +18 -8
  9. package/bun.lock +426 -568
  10. package/bunfig.toml +2 -0
  11. package/docs/perf-baseline.md +56 -1
  12. package/docs/superpowers/specs/2026-03-27-go-server-rewrite-design.md +284 -0
  13. package/e2e/comments.spec.ts +14 -58
  14. package/e2e/document-load.spec.ts +1 -23
  15. package/e2e/export.spec.ts +4 -4
  16. package/e2e/perf/add-comment.spec.ts +9 -11
  17. package/e2e/perf/fixtures/generate.ts +1 -5
  18. package/e2e/perf/screenshot-final.png +0 -0
  19. package/e2e/perf/utils/metrics.ts +73 -9
  20. package/e2e/persistence-file.spec.ts +41 -26
  21. package/e2e/utils/selection.ts +17 -73
  22. package/go/cmd/readit/main.go +416 -0
  23. package/go/go.mod +20 -0
  24. package/go/go.sum +41 -0
  25. package/go/internal/server/anchor.go +302 -0
  26. package/go/internal/server/anchor_test.go +111 -0
  27. package/go/internal/server/comments.go +390 -0
  28. package/go/internal/server/documents.go +113 -0
  29. package/go/internal/server/embed.go +17 -0
  30. package/go/internal/server/headings.go +33 -0
  31. package/go/internal/server/headings_test.go +75 -0
  32. package/go/internal/server/htmltext.go +123 -0
  33. package/go/internal/server/markdown.go +157 -0
  34. package/go/internal/server/markdown_bench_test.go +42 -0
  35. package/go/internal/server/markdown_test.go +79 -0
  36. package/go/internal/server/server.go +453 -0
  37. package/go/internal/server/server_bench_test.go +122 -0
  38. package/go/internal/server/settings.go +110 -0
  39. package/go/internal/server/sse.go +140 -0
  40. package/go/internal/server/storage.go +275 -0
  41. package/go/internal/server/storage_test.go +152 -0
  42. package/go/internal/server/template.go +66 -0
  43. package/go/internal/server/types.go +101 -0
  44. package/go/internal/server/watcher.go +74 -0
  45. package/index.html +4 -14
  46. package/nvim-readit/lua/readit/health.lua +64 -0
  47. package/nvim-readit/lua/readit/init.lua +463 -0
  48. package/nvim-readit/plugin/readit.lua +19 -0
  49. package/package.json +20 -28
  50. package/shell/_readit +158 -0
  51. package/shell/readit.zsh +87 -0
  52. package/src/App.svelte +890 -0
  53. package/src/cli.ts +183 -21
  54. package/src/components/ActionsMenu.svelte +95 -0
  55. package/src/components/CommentBadge.svelte +67 -0
  56. package/src/components/CommentErrorBanner.svelte +33 -0
  57. package/src/components/CommentInput.svelte +75 -0
  58. package/src/components/CommentListItem.svelte +95 -0
  59. package/src/components/CommentManager.svelte +129 -0
  60. package/src/components/CommentNav.svelte +109 -0
  61. package/src/components/DocumentViewer.svelte +233 -0
  62. package/src/components/FloatingComment.svelte +107 -0
  63. package/src/components/Header.svelte +76 -0
  64. package/src/components/InlineEditor.svelte +72 -0
  65. package/src/components/MarginNote.svelte +167 -0
  66. package/src/components/MarginNotesContainer.svelte +33 -0
  67. package/src/components/MermaidEnhancer.svelte +218 -0
  68. package/src/components/MermaidModal.svelte +67 -0
  69. package/src/components/RawModal.svelte +126 -0
  70. package/src/components/ReanchorConfirm.svelte +30 -0
  71. package/src/components/SettingsModal.svelte +220 -0
  72. package/src/components/ShortcutCapture.svelte +82 -0
  73. package/src/components/ShortcutList.svelte +145 -0
  74. package/src/components/TabBar.svelte +52 -0
  75. package/src/components/TableOfContents.svelte +125 -0
  76. package/src/components/ui/ActionLink.svelte +40 -0
  77. package/src/components/ui/{Button.tsx → Button.svelte} +19 -20
  78. package/src/components/ui/Dialog.svelte +97 -0
  79. package/src/components/ui/DropdownMenu.svelte +85 -0
  80. package/src/components/ui/DropdownMenuItem.svelte +38 -0
  81. package/src/components/ui/DropdownMenuSeparator.svelte +11 -0
  82. package/src/components/ui/{Text.tsx → Text.svelte} +18 -23
  83. package/src/env.d.ts +6 -0
  84. package/src/index.css +141 -166
  85. package/src/lib/__fixtures__/bench-data.ts +0 -13
  86. package/src/lib/anchor.bench.ts +1 -12
  87. package/src/lib/anchor.test.ts +0 -8
  88. package/src/lib/anchor.ts +0 -4
  89. package/src/lib/comment-storage.bench.ts +49 -0
  90. package/src/lib/comment-storage.test.ts +103 -33
  91. package/src/lib/comment-storage.ts +25 -18
  92. package/src/lib/export.bench.ts +21 -0
  93. package/src/lib/export.ts +0 -1
  94. package/src/lib/fetch-or-throw.test.ts +59 -0
  95. package/src/lib/fetch-or-throw.ts +12 -0
  96. package/src/{hooks/useHeadings.test.ts → lib/headings.test.ts} +10 -24
  97. package/src/{hooks/useHeadings.ts → lib/headings.ts} +11 -13
  98. package/src/lib/highlight/core.test.ts +0 -5
  99. package/src/lib/highlight/dom.ts +52 -216
  100. package/src/lib/highlight/highlight-registry.ts +221 -0
  101. package/src/lib/highlight/highlight.bench.ts +92 -0
  102. package/src/lib/highlight/highlighter.ts +112 -132
  103. package/src/lib/highlight/resolver.ts +5 -79
  104. package/src/lib/highlight/types.ts +0 -5
  105. package/src/lib/html-text.test.ts +162 -0
  106. package/src/lib/html-text.ts +161 -0
  107. package/src/lib/i18n/en.ts +34 -0
  108. package/src/lib/i18n/ja.ts +34 -0
  109. package/src/lib/i18n/types.ts +33 -0
  110. package/src/lib/key-lock.test.ts +104 -0
  111. package/src/lib/key-lock.ts +23 -0
  112. package/src/lib/margin-layout.bench.ts +61 -0
  113. package/src/lib/margin-layout.ts +0 -7
  114. package/src/lib/markdown-renderer.test.ts +154 -0
  115. package/src/lib/markdown-renderer.ts +178 -0
  116. package/src/lib/mermaid-config.ts +38 -0
  117. package/src/lib/mermaid-renderer.ts +162 -0
  118. package/src/lib/mermaid-worker.ts +60 -0
  119. package/src/lib/positions.ts +31 -24
  120. package/src/lib/shortcut-registry.ts +244 -0
  121. package/src/lib/utils.ts +0 -29
  122. package/src/main.ts +16 -0
  123. package/src/schema.ts +16 -5
  124. package/src/server.ts +355 -95
  125. package/src/stores/app.svelte.ts +231 -0
  126. package/src/stores/locale.svelte.ts +46 -0
  127. package/src/stores/settings.svelte.ts +90 -0
  128. package/src/stores/shortcuts.svelte.ts +104 -0
  129. package/src/stores/ui.svelte.ts +12 -0
  130. package/src/template.ts +104 -0
  131. package/src/test-setup.ts +47 -0
  132. package/svelte.config.js +5 -0
  133. package/tsconfig.json +2 -2
  134. package/vite.config.ts +23 -3
  135. package/vscode-readit/.mcp.json +7 -0
  136. package/vscode-readit/.vscodeignore +7 -0
  137. package/vscode-readit/bun.lock +78 -0
  138. package/vscode-readit/icon.svg +10 -0
  139. package/vscode-readit/package.json +110 -0
  140. package/vscode-readit/src/extension.ts +117 -0
  141. package/vscode-readit/src/server-manager.ts +272 -0
  142. package/vscode-readit/src/webview-provider.ts +204 -0
  143. package/vscode-readit/tsconfig.json +20 -0
  144. package/e2e/fixtures/sample.html +0 -13
  145. package/src/App.tsx +0 -368
  146. package/src/components/ActionsMenu.tsx +0 -91
  147. package/src/components/DocumentViewer/CodeBlock.tsx +0 -160
  148. package/src/components/DocumentViewer/DocumentViewer.tsx +0 -230
  149. package/src/components/DocumentViewer/MermaidDiagram.tsx +0 -136
  150. package/src/components/Header.tsx +0 -54
  151. package/src/components/InlineEditor.tsx +0 -74
  152. package/src/components/MarginNote.tsx +0 -185
  153. package/src/components/MarginNotes.tsx +0 -23
  154. package/src/components/RawModal.tsx +0 -144
  155. package/src/components/ReanchorConfirm.tsx +0 -36
  156. package/src/components/SettingsModal.tsx +0 -232
  157. package/src/components/TabBar.tsx +0 -60
  158. package/src/components/TableOfContents.tsx +0 -108
  159. package/src/components/comments/CommentBadge.tsx +0 -49
  160. package/src/components/comments/CommentInput.tsx +0 -86
  161. package/src/components/comments/CommentListItem.tsx +0 -90
  162. package/src/components/comments/CommentManager.tsx +0 -129
  163. package/src/components/comments/CommentNav.tsx +0 -109
  164. package/src/components/ui/ActionLink.tsx +0 -28
  165. package/src/components/ui/Dialog.tsx +0 -116
  166. package/src/components/ui/DropdownMenu.tsx +0 -158
  167. package/src/contexts/CommentContext.tsx +0 -198
  168. package/src/contexts/LocaleContext.tsx +0 -76
  169. package/src/contexts/PositionsContext.tsx +0 -16
  170. package/src/contexts/SettingsContext.tsx +0 -133
  171. package/src/hooks/useClickOutside.ts +0 -31
  172. package/src/hooks/useCommentNavigation.ts +0 -107
  173. package/src/hooks/useComments.ts +0 -311
  174. package/src/hooks/useDocument.ts +0 -157
  175. package/src/hooks/useScrollSpy.ts +0 -77
  176. package/src/hooks/useTextSelection.ts +0 -86
  177. package/src/lib/highlight/worker.ts +0 -45
  178. package/src/main.tsx +0 -13
  179. package/src/store.ts +0 -222
@@ -0,0 +1,244 @@
1
+ import type { KeybindingOverride, ShortcutBinding } from "../schema";
2
+ import type { TranslationKey } from "./i18n/types";
3
+
4
+ const IS_MAC =
5
+ typeof navigator !== "undefined" && navigator.platform.includes("Mac");
6
+
7
+ export const ShortcutActions = {
8
+ COPY_ALL: "copyAll",
9
+ COPY_ALL_RAW: "copyAllRaw",
10
+ NAVIGATE_NEXT: "navigateNext",
11
+ NAVIGATE_PREVIOUS: "navigatePrevious",
12
+ COPY_SELECTION_RAW: "copySelectionRaw",
13
+ COPY_SELECTION_LLM: "copySelectionLLM",
14
+ CLEAR_SELECTION: "clearSelection",
15
+ } as const;
16
+
17
+ export type ShortcutAction =
18
+ (typeof ShortcutActions)[keyof typeof ShortcutActions];
19
+
20
+ export interface ShortcutDefinition {
21
+ id: ShortcutAction;
22
+ label: TranslationKey;
23
+ description: TranslationKey;
24
+ defaultBinding: ShortcutBinding;
25
+ binding: ShortcutBinding;
26
+ enabled: boolean;
27
+ }
28
+
29
+ export const DEFAULT_SHORTCUTS: ShortcutDefinition[] = [
30
+ {
31
+ id: ShortcutActions.COPY_ALL,
32
+ label: "shortcut.copyAll.label",
33
+ description: "shortcut.copyAll.description",
34
+ defaultBinding: { key: "c", alt: true },
35
+ binding: { key: "c", alt: true },
36
+ enabled: true,
37
+ },
38
+ {
39
+ id: ShortcutActions.COPY_ALL_RAW,
40
+ label: "shortcut.copyAllRaw.label",
41
+ description: "shortcut.copyAllRaw.description",
42
+ defaultBinding: { key: "c", alt: true, shift: true },
43
+ binding: { key: "c", alt: true, shift: true },
44
+ enabled: true,
45
+ },
46
+ {
47
+ id: ShortcutActions.NAVIGATE_NEXT,
48
+ label: "shortcut.navigateNext.label",
49
+ description: "shortcut.navigateNext.description",
50
+ defaultBinding: { key: "ArrowDown", alt: true },
51
+ binding: { key: "ArrowDown", alt: true },
52
+ enabled: true,
53
+ },
54
+ {
55
+ id: ShortcutActions.NAVIGATE_PREVIOUS,
56
+ label: "shortcut.navigatePrevious.label",
57
+ description: "shortcut.navigatePrevious.description",
58
+ defaultBinding: { key: "ArrowUp", alt: true },
59
+ binding: { key: "ArrowUp", alt: true },
60
+ enabled: true,
61
+ },
62
+ {
63
+ id: ShortcutActions.COPY_SELECTION_RAW,
64
+ label: "shortcut.copySelectionRaw.label",
65
+ description: "shortcut.copySelectionRaw.description",
66
+ defaultBinding: IS_MAC
67
+ ? { key: "c", meta: true, shift: true }
68
+ : { key: "c", ctrl: true, shift: true },
69
+ binding: IS_MAC
70
+ ? { key: "c", meta: true, shift: true }
71
+ : { key: "c", ctrl: true, shift: true },
72
+ enabled: true,
73
+ },
74
+ {
75
+ id: ShortcutActions.COPY_SELECTION_LLM,
76
+ label: "shortcut.copySelectionLLM.label",
77
+ description: "shortcut.copySelectionLLM.description",
78
+ defaultBinding: IS_MAC
79
+ ? { key: "c", meta: true, alt: true }
80
+ : { key: "c", ctrl: true, alt: true },
81
+ binding: IS_MAC
82
+ ? { key: "c", meta: true, alt: true }
83
+ : { key: "c", ctrl: true, alt: true },
84
+ enabled: true,
85
+ },
86
+ {
87
+ id: ShortcutActions.CLEAR_SELECTION,
88
+ label: "shortcut.clearSelection.label",
89
+ description: "shortcut.clearSelection.description",
90
+ defaultBinding: { key: "Escape" },
91
+ binding: { key: "Escape" },
92
+ enabled: true,
93
+ },
94
+ ];
95
+
96
+ export function matchesBinding(
97
+ event: KeyboardEvent,
98
+ binding: ShortcutBinding,
99
+ ): boolean {
100
+ if (event.key.toLowerCase() !== binding.key.toLowerCase()) return false;
101
+ if (!!binding.alt !== event.altKey) return false;
102
+ if (!!binding.ctrl !== event.ctrlKey) return false;
103
+ if (!!binding.meta !== event.metaKey) return false;
104
+ if (!!binding.shift !== event.shiftKey) return false;
105
+ return true;
106
+ }
107
+
108
+ export function formatBinding(
109
+ binding: ShortcutBinding,
110
+ isMac: boolean,
111
+ ): string {
112
+ const parts: string[] = [];
113
+
114
+ if (binding.ctrl) {
115
+ parts.push(isMac ? "\u2303" : "Ctrl");
116
+ }
117
+ if (binding.meta) {
118
+ parts.push(isMac ? "\u2318" : "Meta");
119
+ }
120
+ if (binding.alt) {
121
+ parts.push(isMac ? "\u2325" : "Alt");
122
+ }
123
+ if (binding.shift) {
124
+ parts.push(isMac ? "\u21E7" : "Shift");
125
+ }
126
+
127
+ const keyDisplay = KEY_DISPLAY_MAP[binding.key] ?? binding.key.toUpperCase();
128
+ parts.push(keyDisplay);
129
+
130
+ return parts.join(isMac ? "" : "+");
131
+ }
132
+
133
+ const KEY_DISPLAY_MAP: Record<string, string> = {
134
+ ArrowUp: "\u2191",
135
+ ArrowDown: "\u2193",
136
+ ArrowLeft: "\u2190",
137
+ ArrowRight: "\u2192",
138
+ Escape: "Esc",
139
+ Enter: "\u21B5",
140
+ Backspace: "\u232B",
141
+ Delete: "\u2326",
142
+ Tab: "\u21E5",
143
+ " ": "Space",
144
+ };
145
+
146
+ export function resolveShortcuts(
147
+ overrides: KeybindingOverride[],
148
+ ): ShortcutDefinition[] {
149
+ return DEFAULT_SHORTCUTS.map((def) => {
150
+ const override = overrides.find((o) => o.id === def.id);
151
+ if (!override) return { ...def };
152
+ return {
153
+ ...def,
154
+ binding: override.binding ?? def.defaultBinding,
155
+ enabled: override.enabled,
156
+ };
157
+ });
158
+ }
159
+
160
+ // Mac-specific reserved bindings (Cmd+key)
161
+ export const RESERVED_BINDINGS_MAC: ShortcutBinding[] = [
162
+ { key: "r", meta: true },
163
+ { key: "w", meta: true },
164
+ { key: "t", meta: true },
165
+ { key: "n", meta: true },
166
+ { key: "q", meta: true },
167
+ { key: "l", meta: true },
168
+ { key: "a", meta: true },
169
+ { key: "f", meta: true },
170
+ { key: "p", meta: true },
171
+ ];
172
+
173
+ // Windows/Linux reserved bindings (Ctrl+key consumed by browser)
174
+ export const RESERVED_BINDINGS_OTHER: ShortcutBinding[] = [
175
+ { key: "r", ctrl: true },
176
+ { key: "w", ctrl: true },
177
+ { key: "t", ctrl: true },
178
+ { key: "n", ctrl: true },
179
+ { key: "l", ctrl: true },
180
+ { key: "a", ctrl: true },
181
+ { key: "f", ctrl: true },
182
+ { key: "p", ctrl: true },
183
+ ];
184
+
185
+ export function isReservedBinding(
186
+ binding: ShortcutBinding,
187
+ isMac: boolean,
188
+ ): boolean {
189
+ const reserved = isMac ? RESERVED_BINDINGS_MAC : RESERVED_BINDINGS_OTHER;
190
+ // Ignore shift when checking reserved bindings because browsers also
191
+ // consume the shifted variants (e.g. Cmd+Shift+R, Ctrl+Shift+T).
192
+ return reserved.some(
193
+ (r) =>
194
+ r.key.toLowerCase() === binding.key.toLowerCase() &&
195
+ !!r.alt === !!binding.alt &&
196
+ !!r.ctrl === !!binding.ctrl &&
197
+ !!r.meta === !!binding.meta,
198
+ );
199
+ }
200
+
201
+ export function eventToBinding(event: KeyboardEvent): ShortcutBinding {
202
+ return {
203
+ key: event.key,
204
+ ...(event.altKey && { alt: true }),
205
+ ...(event.ctrlKey && { ctrl: true }),
206
+ ...(event.metaKey && { meta: true }),
207
+ ...(event.shiftKey && { shift: true }),
208
+ };
209
+ }
210
+
211
+ export function bindingsEqual(a: ShortcutBinding, b: ShortcutBinding): boolean {
212
+ return (
213
+ a.key.toLowerCase() === b.key.toLowerCase() &&
214
+ !!a.alt === !!b.alt &&
215
+ !!a.ctrl === !!b.ctrl &&
216
+ !!a.meta === !!b.meta &&
217
+ !!a.shift === !!b.shift
218
+ );
219
+ }
220
+
221
+ export interface ShortcutGroup {
222
+ label: TranslationKey;
223
+ ids: ShortcutAction[];
224
+ }
225
+
226
+ export const SHORTCUT_GROUPS: ShortcutGroup[] = [
227
+ {
228
+ label: "shortcutGroup.copy",
229
+ ids: [
230
+ ShortcutActions.COPY_ALL,
231
+ ShortcutActions.COPY_ALL_RAW,
232
+ ShortcutActions.COPY_SELECTION_RAW,
233
+ ShortcutActions.COPY_SELECTION_LLM,
234
+ ],
235
+ },
236
+ {
237
+ label: "shortcutGroup.navigate",
238
+ ids: [ShortcutActions.NAVIGATE_NEXT, ShortcutActions.NAVIGATE_PREVIOUS],
239
+ },
240
+ {
241
+ label: "shortcutGroup.other",
242
+ ids: [ShortcutActions.CLEAR_SELECTION],
243
+ },
244
+ ];
package/src/lib/utils.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { type ClassValue, clsx } from "clsx";
2
- import type { ReactNode } from "react";
3
2
  import { twMerge } from "tailwind-merge";
4
3
 
5
4
  export function isMarkdownFile(filePath: string): boolean {
@@ -14,31 +13,3 @@ export function truncate(text: string, maxLength = 30): string {
14
13
  if (text.length <= maxLength) return text;
15
14
  return `${text.slice(0, maxLength)}…`;
16
15
  }
17
-
18
- export function getTextContent(children: ReactNode): string {
19
- if (typeof children === "string" || typeof children === "number") {
20
- return String(children);
21
- }
22
- if (Array.isArray(children)) {
23
- return children.map(getTextContent).join("");
24
- }
25
- if (
26
- typeof children === "object" &&
27
- children !== null &&
28
- "props" in children
29
- ) {
30
- return getTextContent(
31
- (children as { props: { children?: ReactNode } }).props.children,
32
- );
33
- }
34
- return "";
35
- }
36
-
37
- export function slugify(text: string): string {
38
- return text
39
- .toLowerCase()
40
- .trim()
41
- .replace(/[^\w\s-]/g, "")
42
- .replace(/\s+/g, "-")
43
- .replace(/-+/g, "-");
44
- }
package/src/main.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { mount } from "svelte";
2
+ import "./index.css";
3
+ import App from "./App.svelte";
4
+ import { hydrateFromInlineData } from "./stores/app.svelte";
5
+ import { initSettings } from "./stores/settings.svelte";
6
+ import { initShortcuts } from "./stores/shortcuts.svelte";
7
+
8
+ const dataEl = document.getElementById("__readit");
9
+ if (dataEl) {
10
+ const data = JSON.parse(dataEl.textContent ?? "{}");
11
+ hydrateFromInlineData(data);
12
+ initSettings(data.settings);
13
+ initShortcuts(data.settings?.keybindings ?? []);
14
+ }
15
+
16
+ mount(App, { target: document.getElementById("app")! });
package/src/schema.ts CHANGED
@@ -17,19 +17,16 @@ export interface Comment {
17
17
  id: string;
18
18
  selectedText: string;
19
19
  comment: string;
20
- createdAt: string;
20
+ createdAt?: string;
21
21
  startOffset: number;
22
22
  endOffset: number;
23
- /** e.g. "L42" or "L42-L55" */
24
23
  lineHint?: string;
25
24
  anchorConfidence?: AnchorConfidence;
26
- /** First N chars of original text for anchor matching when selectedText is truncated */
27
25
  anchorPrefix?: string;
28
26
  }
29
27
 
30
28
  export interface CommentFile {
31
29
  source: string;
32
- /** SHA-256 prefix (16 chars) */
33
30
  hash: string;
34
31
  version: number;
35
32
  comments: Comment[];
@@ -53,7 +50,7 @@ export interface Selection extends SelectionRange {
53
50
  }
54
51
 
55
52
  export interface Document {
56
- content: string;
53
+ html: string;
57
54
  filePath: string;
58
55
  fileName: string;
59
56
  clean: boolean;
@@ -79,3 +76,17 @@ export interface DocumentSettings {
79
76
  fontFamily: FontFamily;
80
77
  onboarded?: boolean;
81
78
  }
79
+
80
+ export interface ShortcutBinding {
81
+ key: string;
82
+ alt?: boolean;
83
+ ctrl?: boolean;
84
+ meta?: boolean;
85
+ shift?: boolean;
86
+ }
87
+
88
+ export interface KeybindingOverride {
89
+ id: string;
90
+ binding?: ShortcutBinding;
91
+ enabled: boolean;
92
+ }