@peaske7/readit 0.2.0 → 0.2.1
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.
- package/.claude/CLAUDE.md +118 -76
- package/.claude/commands/review.md +1 -1
- package/.claude/roadmap.md +32 -9
- package/.claude/user-stories.md +100 -15
- package/AGENTS.md +30 -26
- package/Makefile +32 -0
- package/README.md +90 -2
- package/biome.json +18 -8
- package/bun.lock +426 -568
- package/bunfig.toml +2 -0
- package/docs/perf-baseline.md +56 -1
- package/docs/superpowers/specs/2026-03-27-go-server-rewrite-design.md +284 -0
- package/e2e/comments.spec.ts +14 -58
- package/e2e/document-load.spec.ts +1 -23
- package/e2e/export.spec.ts +4 -4
- package/e2e/perf/add-comment.spec.ts +9 -11
- package/e2e/perf/fixtures/generate.ts +1 -5
- package/e2e/perf/screenshot-final.png +0 -0
- package/e2e/perf/utils/metrics.ts +73 -9
- package/e2e/persistence-file.spec.ts +41 -26
- package/e2e/utils/selection.ts +17 -73
- package/go/cmd/readit/main.go +416 -0
- package/go/go.mod +20 -0
- package/go/go.sum +41 -0
- package/go/internal/server/anchor.go +302 -0
- package/go/internal/server/anchor_test.go +111 -0
- package/go/internal/server/comments.go +390 -0
- package/go/internal/server/documents.go +113 -0
- package/go/internal/server/embed.go +17 -0
- package/go/internal/server/headings.go +33 -0
- package/go/internal/server/headings_test.go +75 -0
- package/go/internal/server/htmltext.go +123 -0
- package/go/internal/server/markdown.go +157 -0
- package/go/internal/server/markdown_bench_test.go +42 -0
- package/go/internal/server/markdown_test.go +79 -0
- package/go/internal/server/server.go +453 -0
- package/go/internal/server/server_bench_test.go +122 -0
- package/go/internal/server/settings.go +110 -0
- package/go/internal/server/sse.go +140 -0
- package/go/internal/server/storage.go +275 -0
- package/go/internal/server/storage_test.go +118 -0
- package/go/internal/server/template.go +66 -0
- package/go/internal/server/types.go +101 -0
- package/go/internal/server/watcher.go +74 -0
- package/index.html +4 -14
- package/nvim-readit/lua/readit/health.lua +64 -0
- package/nvim-readit/lua/readit/init.lua +463 -0
- package/nvim-readit/plugin/readit.lua +19 -0
- package/package.json +20 -28
- package/shell/_readit +158 -0
- package/shell/readit.zsh +87 -0
- package/src/App.svelte +881 -0
- package/src/cli.ts +183 -21
- package/src/components/ActionsMenu.svelte +95 -0
- package/src/components/CommentBadge.svelte +67 -0
- package/src/components/CommentErrorBanner.svelte +33 -0
- package/src/components/CommentInput.svelte +75 -0
- package/src/components/CommentListItem.svelte +95 -0
- package/src/components/CommentManager.svelte +129 -0
- package/src/components/CommentNav.svelte +109 -0
- package/src/components/DocumentViewer.svelte +218 -0
- package/src/components/FloatingComment.svelte +107 -0
- package/src/components/Header.svelte +76 -0
- package/src/components/InlineEditor.svelte +72 -0
- package/src/components/MarginNote.svelte +167 -0
- package/src/components/MarginNotesContainer.svelte +33 -0
- package/src/components/RawModal.svelte +126 -0
- package/src/components/ReanchorConfirm.svelte +30 -0
- package/src/components/SettingsModal.svelte +220 -0
- package/src/components/ShortcutCapture.svelte +82 -0
- package/src/components/ShortcutList.svelte +145 -0
- package/src/components/TabBar.svelte +52 -0
- package/src/components/TableOfContents.svelte +125 -0
- package/src/components/ui/ActionLink.svelte +40 -0
- package/src/components/ui/{Button.tsx → Button.svelte} +19 -20
- package/src/components/ui/Dialog.svelte +97 -0
- package/src/components/ui/DropdownMenu.svelte +85 -0
- package/src/components/ui/DropdownMenuItem.svelte +38 -0
- package/src/components/ui/DropdownMenuSeparator.svelte +11 -0
- package/src/components/ui/{Text.tsx → Text.svelte} +18 -23
- package/src/env.d.ts +6 -0
- package/src/index.css +36 -166
- package/src/lib/__fixtures__/bench-data.ts +0 -13
- package/src/lib/anchor.bench.ts +1 -12
- package/src/lib/anchor.test.ts +0 -8
- package/src/lib/anchor.ts +0 -4
- package/src/lib/comment-storage.bench.ts +49 -0
- package/src/lib/comment-storage.test.ts +41 -33
- package/src/lib/comment-storage.ts +21 -18
- package/src/lib/export.bench.ts +21 -0
- package/src/lib/export.ts +0 -1
- package/src/{hooks/useHeadings.test.ts → lib/headings.test.ts} +10 -24
- package/src/{hooks/useHeadings.ts → lib/headings.ts} +11 -13
- package/src/lib/highlight/core.test.ts +0 -5
- package/src/lib/highlight/dom.ts +52 -216
- package/src/lib/highlight/highlight-registry.ts +221 -0
- package/src/lib/highlight/highlight.bench.ts +92 -0
- package/src/lib/highlight/highlighter.ts +112 -132
- package/src/lib/highlight/resolver.ts +5 -79
- package/src/lib/highlight/types.ts +0 -5
- package/src/lib/html-text.test.ts +162 -0
- package/src/lib/html-text.ts +161 -0
- package/src/lib/i18n/en.ts +26 -0
- package/src/lib/i18n/ja.ts +26 -0
- package/src/lib/i18n/types.ts +25 -0
- package/src/lib/margin-layout.bench.ts +61 -0
- package/src/lib/margin-layout.ts +0 -7
- package/src/lib/markdown-renderer.test.ts +154 -0
- package/src/lib/markdown-renderer.ts +177 -0
- package/src/lib/mermaid-config.ts +38 -0
- package/src/lib/mermaid-renderer.ts +162 -0
- package/src/lib/mermaid-worker.ts +60 -0
- package/src/lib/positions.ts +31 -24
- package/src/lib/shortcut-registry.ts +244 -0
- package/src/lib/utils.ts +0 -29
- package/src/main.ts +16 -0
- package/src/schema.ts +16 -5
- package/src/server.ts +355 -91
- package/src/stores/app.svelte.ts +231 -0
- package/src/stores/locale.svelte.ts +46 -0
- package/src/stores/settings.svelte.ts +90 -0
- package/src/stores/shortcuts.svelte.ts +104 -0
- package/src/stores/ui.svelte.ts +12 -0
- package/src/template.ts +104 -0
- package/src/test-setup.ts +47 -0
- package/svelte.config.js +5 -0
- package/tsconfig.json +2 -2
- package/vite.config.ts +23 -3
- package/vscode-readit/.mcp.json +7 -0
- package/vscode-readit/.vscodeignore +7 -0
- package/vscode-readit/bun.lock +78 -0
- package/vscode-readit/icon.svg +10 -0
- package/vscode-readit/package.json +110 -0
- package/vscode-readit/src/extension.ts +117 -0
- package/vscode-readit/src/server-manager.ts +272 -0
- package/vscode-readit/src/webview-provider.ts +204 -0
- package/vscode-readit/tsconfig.json +20 -0
- package/e2e/fixtures/sample.html +0 -13
- package/src/App.tsx +0 -368
- package/src/components/ActionsMenu.tsx +0 -91
- package/src/components/DocumentViewer/CodeBlock.tsx +0 -160
- package/src/components/DocumentViewer/DocumentViewer.tsx +0 -230
- package/src/components/DocumentViewer/MermaidDiagram.tsx +0 -136
- package/src/components/Header.tsx +0 -54
- package/src/components/InlineEditor.tsx +0 -74
- package/src/components/MarginNote.tsx +0 -185
- package/src/components/MarginNotes.tsx +0 -23
- package/src/components/RawModal.tsx +0 -144
- package/src/components/ReanchorConfirm.tsx +0 -36
- package/src/components/SettingsModal.tsx +0 -232
- package/src/components/TabBar.tsx +0 -60
- package/src/components/TableOfContents.tsx +0 -108
- package/src/components/comments/CommentBadge.tsx +0 -49
- package/src/components/comments/CommentInput.tsx +0 -86
- package/src/components/comments/CommentListItem.tsx +0 -90
- package/src/components/comments/CommentManager.tsx +0 -129
- package/src/components/comments/CommentNav.tsx +0 -109
- package/src/components/ui/ActionLink.tsx +0 -28
- package/src/components/ui/Dialog.tsx +0 -116
- package/src/components/ui/DropdownMenu.tsx +0 -158
- package/src/contexts/CommentContext.tsx +0 -198
- package/src/contexts/LocaleContext.tsx +0 -76
- package/src/contexts/PositionsContext.tsx +0 -16
- package/src/contexts/SettingsContext.tsx +0 -133
- package/src/hooks/useClickOutside.ts +0 -31
- package/src/hooks/useCommentNavigation.ts +0 -107
- package/src/hooks/useComments.ts +0 -311
- package/src/hooks/useDocument.ts +0 -157
- package/src/hooks/useScrollSpy.ts +0 -77
- package/src/hooks/useTextSelection.ts +0 -86
- package/src/lib/highlight/worker.ts +0 -45
- package/src/main.tsx +0 -13
- package/src/store.ts +0 -222
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
3
|
import { cn } from "../../lib/utils";
|
|
4
4
|
import { FontFamilies } from "../../schema";
|
|
5
|
+
import { settings } from "../../stores/settings.svelte";
|
|
5
6
|
|
|
6
7
|
const variantStyles = {
|
|
7
8
|
title:
|
|
@@ -17,31 +18,25 @@ const variantStyles = {
|
|
|
17
18
|
|
|
18
19
|
type TextVariant = keyof typeof variantStyles;
|
|
19
20
|
|
|
20
|
-
interface
|
|
21
|
+
interface Props {
|
|
21
22
|
variant?: TextVariant;
|
|
22
23
|
as?: "p" | "span" | "div" | "h1" | "h2" | "h3" | "label" | "pre";
|
|
24
|
+
class?: string;
|
|
25
|
+
children: Snippet;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
className,
|
|
28
|
+
let {
|
|
27
29
|
variant = "body",
|
|
28
|
-
as
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const fontClass = settings
|
|
33
|
-
? settings.fontFamily === FontFamilies.SANS_SERIF
|
|
34
|
-
? "font-sans"
|
|
35
|
-
: "font-serif"
|
|
36
|
-
: undefined;
|
|
30
|
+
as = "p",
|
|
31
|
+
class: className,
|
|
32
|
+
children,
|
|
33
|
+
}: Props = $props();
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
/>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
35
|
+
let fontClass = $derived(
|
|
36
|
+
settings.fontFamily === FontFamilies.SANS_SERIF ? "font-sans" : "font-serif",
|
|
37
|
+
);
|
|
38
|
+
</script>
|
|
45
39
|
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
<svelte:element this={as} class={cn(fontClass, variantStyles[variant], className)}>
|
|
41
|
+
{@render children()}
|
|
42
|
+
</svelte:element>
|
package/src/env.d.ts
ADDED
package/src/index.css
CHANGED
|
@@ -101,8 +101,8 @@ html {
|
|
|
101
101
|
--comment-color-3-bg-focused: rgba(205, 145, 155, 0.65);
|
|
102
102
|
--comment-color-3-border: #b86b78;
|
|
103
103
|
|
|
104
|
-
/* Pending -
|
|
105
|
-
--pending-bg: rgba(
|
|
104
|
+
/* Pending - warm amber tint (matches comment highlight palette) */
|
|
105
|
+
--pending-bg: rgba(245, 222, 160, 0.45);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
.dark {
|
|
@@ -151,127 +151,38 @@ html {
|
|
|
151
151
|
--comment-color-3-bg-focused: rgba(184, 107, 120, 0.55);
|
|
152
152
|
--comment-color-3-border: #d08a98;
|
|
153
153
|
|
|
154
|
-
--pending-bg: rgba(
|
|
154
|
+
--pending-bg: rgba(202, 168, 74, 0.35);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/* ========================================
|
|
158
|
-
Comment Highlight Styles
|
|
158
|
+
Comment Highlight Styles (CSS Custom Highlight API)
|
|
159
159
|
======================================== */
|
|
160
160
|
|
|
161
|
-
/*
|
|
162
|
-
|
|
163
|
-
background-color: var(--
|
|
164
|
-
cursor: pointer;
|
|
165
|
-
transition:
|
|
166
|
-
background-color 150ms ease,
|
|
167
|
-
opacity 150ms ease;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/* Color variants based on data-color-index */
|
|
171
|
-
mark[data-comment-id][data-color-index="0"] {
|
|
172
|
-
--highlight-bg: var(--comment-color-0-bg);
|
|
173
|
-
--highlight-bg-focused: var(--comment-color-0-bg-focused);
|
|
174
|
-
--highlight-border: var(--comment-color-0-border);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
mark[data-comment-id][data-color-index="1"] {
|
|
178
|
-
--highlight-bg: var(--comment-color-1-bg);
|
|
179
|
-
--highlight-bg-focused: var(--comment-color-1-bg-focused);
|
|
180
|
-
--highlight-border: var(--comment-color-1-border);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
mark[data-comment-id][data-color-index="2"] {
|
|
184
|
-
--highlight-bg: var(--comment-color-2-bg);
|
|
185
|
-
--highlight-bg-focused: var(--comment-color-2-bg-focused);
|
|
186
|
-
--highlight-border: var(--comment-color-2-border);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
mark[data-comment-id][data-color-index="3"] {
|
|
190
|
-
--highlight-bg: var(--comment-color-3-bg);
|
|
191
|
-
--highlight-bg-focused: var(--comment-color-3-bg-focused);
|
|
192
|
-
--highlight-border: var(--comment-color-3-border);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/* Focused state - this highlight is hovered */
|
|
196
|
-
mark[data-comment-id][data-focused="true"] {
|
|
197
|
-
background-color: var(
|
|
198
|
-
--highlight-bg-focused,
|
|
199
|
-
var(--comment-color-0-bg-focused)
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/* Hover on highlight itself */
|
|
204
|
-
mark[data-comment-id]:hover {
|
|
205
|
-
background-color: var(
|
|
206
|
-
--highlight-bg-focused,
|
|
207
|
-
var(--comment-color-0-bg-focused)
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/* ========================================
|
|
212
|
-
Bracket Mode for Long Selections
|
|
213
|
-
======================================== */
|
|
214
|
-
|
|
215
|
-
/* Container for bracket-mode highlights */
|
|
216
|
-
mark[data-comment-id][data-bracket-mode="true"] {
|
|
217
|
-
background-color: transparent;
|
|
218
|
-
position: relative;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/* Only show background on first segment in bracket mode */
|
|
222
|
-
mark[data-comment-id][data-bracket-start="true"] {
|
|
223
|
-
background-color: var(--highlight-bg, var(--comment-color-0-bg));
|
|
224
|
-
border-radius: 2px 2px 0 0;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/* Focused bracket mode shows full highlight */
|
|
228
|
-
mark[data-comment-id][data-bracket-mode="true"][data-focused="true"],
|
|
229
|
-
mark[data-comment-id][data-bracket-mode="true"]:hover {
|
|
230
|
-
background-color: var(
|
|
231
|
-
--highlight-bg-focused,
|
|
232
|
-
var(--comment-color-0-bg-focused)
|
|
233
|
-
);
|
|
161
|
+
/* Color variants — paint-time only, zero DOM mutations */
|
|
162
|
+
::highlight(comment-color-0) {
|
|
163
|
+
background-color: var(--comment-color-0-bg);
|
|
234
164
|
}
|
|
235
165
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
position: absolute;
|
|
239
|
-
left: -20px;
|
|
240
|
-
width: 2px;
|
|
241
|
-
background-color: var(--bracket-color, var(--comment-color-0-border));
|
|
242
|
-
border-radius: 1px;
|
|
243
|
-
opacity: 0.6;
|
|
244
|
-
transition: opacity 150ms ease;
|
|
245
|
-
pointer-events: none;
|
|
166
|
+
::highlight(comment-color-1) {
|
|
167
|
+
background-color: var(--comment-color-1-bg);
|
|
246
168
|
}
|
|
247
169
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
opacity: 1;
|
|
170
|
+
::highlight(comment-color-2) {
|
|
171
|
+
background-color: var(--comment-color-2-bg);
|
|
251
172
|
}
|
|
252
173
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
position: absolute;
|
|
256
|
-
left: -24px;
|
|
257
|
-
font-family: monospace;
|
|
258
|
-
font-size: 10px;
|
|
259
|
-
color: var(--bracket-color, var(--comment-color-0-border));
|
|
260
|
-
opacity: 0.6;
|
|
261
|
-
pointer-events: none;
|
|
262
|
-
transition: opacity 150ms ease;
|
|
174
|
+
::highlight(comment-color-3) {
|
|
175
|
+
background-color: var(--comment-color-3-bg);
|
|
263
176
|
}
|
|
264
177
|
|
|
265
|
-
|
|
266
|
-
|
|
178
|
+
/* Focused state — higher priority, overrides color group */
|
|
179
|
+
::highlight(comment-focused) {
|
|
180
|
+
background-color: var(--comment-color-0-bg-focused);
|
|
267
181
|
}
|
|
268
182
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
.bracket-marker[data-focused="true"] {
|
|
274
|
-
opacity: 1;
|
|
183
|
+
/* Pending selection highlight — neutral gray */
|
|
184
|
+
::highlight(pending-selection) {
|
|
185
|
+
background-color: var(--pending-bg);
|
|
275
186
|
}
|
|
276
187
|
|
|
277
188
|
/* ========================================
|
|
@@ -301,64 +212,6 @@ mark[data-comment-id][data-bracket-mode="true"]:hover {
|
|
|
301
212
|
opacity: 0.7;
|
|
302
213
|
}
|
|
303
214
|
|
|
304
|
-
/* Pending selection highlight - neutral gray */
|
|
305
|
-
mark[data-pending] {
|
|
306
|
-
background-color: var(--pending-bg);
|
|
307
|
-
cursor: text;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/* Ensure highlights are visible in all contexts including headings */
|
|
311
|
-
.prose mark[data-pending],
|
|
312
|
-
.prose h1 mark[data-pending],
|
|
313
|
-
.prose h2 mark[data-pending],
|
|
314
|
-
.prose h3 mark[data-pending],
|
|
315
|
-
.prose h4 mark[data-pending],
|
|
316
|
-
.prose h5 mark[data-pending],
|
|
317
|
-
.prose h6 mark[data-pending] {
|
|
318
|
-
background-color: var(--pending-bg) !important;
|
|
319
|
-
display: inline !important;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/* Ensure comment highlights are visible inside headings (prose overrides) */
|
|
323
|
-
.prose h1 mark[data-comment-id],
|
|
324
|
-
.prose h2 mark[data-comment-id],
|
|
325
|
-
.prose h3 mark[data-comment-id],
|
|
326
|
-
.prose h4 mark[data-comment-id],
|
|
327
|
-
.prose h5 mark[data-comment-id],
|
|
328
|
-
.prose h6 mark[data-comment-id] {
|
|
329
|
-
background-color: var(--highlight-bg, var(--comment-color-0-bg)) !important;
|
|
330
|
-
display: inline !important;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/* Bracket mode in headings - override transparent background */
|
|
334
|
-
.prose h1 mark[data-comment-id][data-bracket-mode="true"],
|
|
335
|
-
.prose h2 mark[data-comment-id][data-bracket-mode="true"],
|
|
336
|
-
.prose h3 mark[data-comment-id][data-bracket-mode="true"],
|
|
337
|
-
.prose h4 mark[data-comment-id][data-bracket-mode="true"],
|
|
338
|
-
.prose h5 mark[data-comment-id][data-bracket-mode="true"],
|
|
339
|
-
.prose h6 mark[data-comment-id][data-bracket-mode="true"] {
|
|
340
|
-
background-color: var(--highlight-bg, var(--comment-color-0-bg)) !important;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/* Hover state for comment highlights in headings */
|
|
344
|
-
.prose h1 mark[data-comment-id]:hover,
|
|
345
|
-
.prose h2 mark[data-comment-id]:hover,
|
|
346
|
-
.prose h3 mark[data-comment-id]:hover,
|
|
347
|
-
.prose h4 mark[data-comment-id]:hover,
|
|
348
|
-
.prose h5 mark[data-comment-id]:hover,
|
|
349
|
-
.prose h6 mark[data-comment-id]:hover,
|
|
350
|
-
.prose h1 mark[data-comment-id][data-focused="true"],
|
|
351
|
-
.prose h2 mark[data-comment-id][data-focused="true"],
|
|
352
|
-
.prose h3 mark[data-comment-id][data-focused="true"],
|
|
353
|
-
.prose h4 mark[data-comment-id][data-focused="true"],
|
|
354
|
-
.prose h5 mark[data-comment-id][data-focused="true"],
|
|
355
|
-
.prose h6 mark[data-comment-id][data-focused="true"] {
|
|
356
|
-
background-color: var(
|
|
357
|
-
--highlight-bg-focused,
|
|
358
|
-
var(--comment-color-0-bg-focused)
|
|
359
|
-
) !important;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
215
|
/* Toast animation */
|
|
363
216
|
[data-sonner-toast] {
|
|
364
217
|
animation: toast-in 0.2s ease-out;
|
|
@@ -579,6 +432,23 @@ mark[data-pending] {
|
|
|
579
432
|
margin: 1.75em 0;
|
|
580
433
|
}
|
|
581
434
|
|
|
435
|
+
/* Shiki syntax-highlighted code blocks (server-rendered) */
|
|
436
|
+
.prose pre.shiki {
|
|
437
|
+
margin: 1.5em 0;
|
|
438
|
+
border-radius: 0.5em;
|
|
439
|
+
font-size: 0.875em;
|
|
440
|
+
padding: 1em;
|
|
441
|
+
overflow-x: auto;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.prose pre.shiki code {
|
|
445
|
+
background: transparent;
|
|
446
|
+
padding: 0;
|
|
447
|
+
font-size: inherit;
|
|
448
|
+
font-weight: 400;
|
|
449
|
+
color: inherit;
|
|
450
|
+
}
|
|
451
|
+
|
|
582
452
|
.prose pre code {
|
|
583
453
|
background: transparent;
|
|
584
454
|
padding: 0;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { Comment, CommentFile } from "../../schema";
|
|
2
2
|
import { serializeComments } from "../comment-storage";
|
|
3
3
|
|
|
4
|
-
// --- Document fixtures ---
|
|
5
|
-
|
|
6
4
|
function generateMarkdownDoc(lineCount: number): string {
|
|
7
5
|
const lines: string[] = [];
|
|
8
6
|
lines.push("# Performance Test Document");
|
|
@@ -21,13 +19,11 @@ function generateMarkdownDoc(lineCount: number): string {
|
|
|
21
19
|
);
|
|
22
20
|
lines.push("");
|
|
23
21
|
|
|
24
|
-
// Add a list
|
|
25
22
|
for (let j = 1; j <= 3; j++) {
|
|
26
23
|
lines.push(`- Item ${j} in section ${sectionNum}`);
|
|
27
24
|
}
|
|
28
25
|
lines.push("");
|
|
29
26
|
|
|
30
|
-
// Add a code block
|
|
31
27
|
lines.push("```typescript");
|
|
32
28
|
lines.push(`function section${sectionNum}() {`);
|
|
33
29
|
lines.push(` const value = ${sectionNum} * 42;`);
|
|
@@ -36,13 +32,11 @@ function generateMarkdownDoc(lineCount: number): string {
|
|
|
36
32
|
lines.push("```");
|
|
37
33
|
lines.push("");
|
|
38
34
|
|
|
39
|
-
// Add a paragraph
|
|
40
35
|
lines.push(
|
|
41
36
|
`The conclusion of section ${sectionNum} summarizes the key findings and provides actionable recommendations for the reader to follow.`,
|
|
42
37
|
);
|
|
43
38
|
lines.push("");
|
|
44
39
|
|
|
45
|
-
// Add a table every few sections
|
|
46
40
|
if (sectionNum % 3 === 0) {
|
|
47
41
|
lines.push("| Column A | Column B | Column C |");
|
|
48
42
|
lines.push("|----------|----------|----------|");
|
|
@@ -65,11 +59,8 @@ export const SMALL_DOC = generateMarkdownDoc(30);
|
|
|
65
59
|
export const MEDIUM_DOC = generateMarkdownDoc(150);
|
|
66
60
|
export const LARGE_DOC = generateMarkdownDoc(300);
|
|
67
61
|
|
|
68
|
-
// --- Comment fixtures ---
|
|
69
|
-
|
|
70
62
|
function makeComment(index: number, doc: string): Comment {
|
|
71
63
|
const lines = doc.split("\n");
|
|
72
|
-
// Distribute comments across the document
|
|
73
64
|
const targetLine = Math.min(
|
|
74
65
|
Math.floor((index + 1) * (lines.length / 55)),
|
|
75
66
|
lines.length - 1,
|
|
@@ -81,7 +72,6 @@ function makeComment(index: number, doc: string): Comment {
|
|
|
81
72
|
? lineText.slice(0, Math.min(30, lineText.length))
|
|
82
73
|
: lineText || "default text";
|
|
83
74
|
|
|
84
|
-
// Calculate actual character offset
|
|
85
75
|
let startOffset = 0;
|
|
86
76
|
for (let i = 0; i < targetLine; i++) {
|
|
87
77
|
startOffset += lines[i].length + 1; // +1 for \n
|
|
@@ -92,7 +82,6 @@ function makeComment(index: number, doc: string): Comment {
|
|
|
92
82
|
id: `bench${String(index).padStart(3, "0")}`,
|
|
93
83
|
selectedText,
|
|
94
84
|
comment: `Benchmark comment ${index}: This text needs to be reviewed and updated.`,
|
|
95
|
-
createdAt: "2025-01-01T00:00:00.000Z",
|
|
96
85
|
startOffset,
|
|
97
86
|
endOffset,
|
|
98
87
|
lineHint: `L${targetLine + 1}`,
|
|
@@ -107,8 +96,6 @@ export const COMMENTS_1 = makeComments(1);
|
|
|
107
96
|
export const COMMENTS_10 = makeComments(10);
|
|
108
97
|
export const COMMENTS_50 = makeComments(50);
|
|
109
98
|
|
|
110
|
-
// --- Serialized comment file fixtures ---
|
|
111
|
-
|
|
112
99
|
function makeCommentFile(comments: Comment[]): CommentFile {
|
|
113
100
|
return {
|
|
114
101
|
source: "/bench/test-doc.md",
|
package/src/lib/anchor.bench.ts
CHANGED
|
@@ -13,8 +13,6 @@ import {
|
|
|
13
13
|
findAnchorWithFallback,
|
|
14
14
|
} from "./anchor";
|
|
15
15
|
|
|
16
|
-
// --- Exact match (best case) ---
|
|
17
|
-
|
|
18
16
|
describe("findAnchor — exact match", () => {
|
|
19
17
|
const comment = COMMENTS_10[5];
|
|
20
18
|
|
|
@@ -35,10 +33,7 @@ describe("findAnchor — exact match", () => {
|
|
|
35
33
|
});
|
|
36
34
|
});
|
|
37
35
|
|
|
38
|
-
// --- Normalized match ---
|
|
39
|
-
|
|
40
36
|
describe("findAnchorNormalized", () => {
|
|
41
|
-
// Create text with extra whitespace to force normalization path
|
|
42
37
|
const comment = COMMENTS_10[5];
|
|
43
38
|
const normalizedText = comment.selectedText.replace(/ /g, " ");
|
|
44
39
|
|
|
@@ -51,13 +46,9 @@ describe("findAnchorNormalized", () => {
|
|
|
51
46
|
});
|
|
52
47
|
});
|
|
53
48
|
|
|
54
|
-
// --- Fuzzy match (worst case) ---
|
|
55
|
-
|
|
56
49
|
describe("findAnchorFuzzy", () => {
|
|
57
|
-
// Slightly mutate text to force Levenshtein search
|
|
58
50
|
const comment = COMMENTS_10[5];
|
|
59
|
-
const mutated =
|
|
60
|
-
"X" + comment.selectedText.slice(1, -1) + "Z";
|
|
51
|
+
const mutated = `X${comment.selectedText.slice(1, -1)}Z`;
|
|
61
52
|
|
|
62
53
|
bench("large doc — fuzzy (mutated text)", () => {
|
|
63
54
|
findAnchorFuzzy({
|
|
@@ -68,8 +59,6 @@ describe("findAnchorFuzzy", () => {
|
|
|
68
59
|
});
|
|
69
60
|
});
|
|
70
61
|
|
|
71
|
-
// --- Full fallback chain ---
|
|
72
|
-
|
|
73
62
|
describe("findAnchorWithFallback", () => {
|
|
74
63
|
bench("1 comment — exact hit", () => {
|
|
75
64
|
const c = COMMENTS_1[0];
|
package/src/lib/anchor.test.ts
CHANGED
|
@@ -64,12 +64,10 @@ describe("levenshteinDistance", () => {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
it("returns Infinity when exceeding maxDistance threshold", () => {
|
|
67
|
-
// Length difference alone exceeds threshold
|
|
68
67
|
expect(levenshteinDistance("hello", "hi", 1)).toBe(
|
|
69
68
|
Number.POSITIVE_INFINITY,
|
|
70
69
|
);
|
|
71
70
|
|
|
72
|
-
// Content difference exceeds threshold during computation
|
|
73
71
|
expect(levenshteinDistance("hello", "world", 2)).toBe(
|
|
74
72
|
Number.POSITIVE_INFINITY,
|
|
75
73
|
);
|
|
@@ -252,7 +250,6 @@ line eight`;
|
|
|
252
250
|
|
|
253
251
|
describe("findAnchorNormalized", () => {
|
|
254
252
|
it("finds text with collapsed whitespace", () => {
|
|
255
|
-
// Original had "hello world" but source was reformatted
|
|
256
253
|
const source = "hello\n world";
|
|
257
254
|
const result = findAnchorNormalized({
|
|
258
255
|
source,
|
|
@@ -275,7 +272,6 @@ describe("findAnchorNormalized", () => {
|
|
|
275
272
|
});
|
|
276
273
|
|
|
277
274
|
it("returns null when text has no collapsible whitespace", () => {
|
|
278
|
-
// If original text has no extra whitespace, exact match would have worked
|
|
279
275
|
const result = findAnchorNormalized({
|
|
280
276
|
source: "hello world",
|
|
281
277
|
selectedText: "hello world",
|
|
@@ -411,7 +407,6 @@ describe("findAnchorWithFallback", () => {
|
|
|
411
407
|
});
|
|
412
408
|
|
|
413
409
|
it("falls back to normalized match when exact fails", () => {
|
|
414
|
-
// Source was reformatted (newlines instead of spaces)
|
|
415
410
|
const result = findAnchorWithFallback({
|
|
416
411
|
source: "hello\nworld",
|
|
417
412
|
selectedText: "hello world",
|
|
@@ -444,7 +439,6 @@ describe("findAnchorWithFallback", () => {
|
|
|
444
439
|
describe("findClosestOccurrence", () => {
|
|
445
440
|
it("finds the occurrence closest to hint", () => {
|
|
446
441
|
const content = "the cat sat on the mat and the rat";
|
|
447
|
-
// "the" appears at positions 0, 15, and 27
|
|
448
442
|
|
|
449
443
|
const result = findClosestOccurrence({
|
|
450
444
|
source: content,
|
|
@@ -452,7 +446,6 @@ describe("findClosestOccurrence", () => {
|
|
|
452
446
|
lineHint: "L1",
|
|
453
447
|
});
|
|
454
448
|
expect(result).not.toBeUndefined();
|
|
455
|
-
// Should find the first "the" at position 0 since hint is L1
|
|
456
449
|
expect(result?.start).toBe(0);
|
|
457
450
|
});
|
|
458
451
|
|
|
@@ -463,7 +456,6 @@ line three the
|
|
|
463
456
|
line four
|
|
464
457
|
line five the`;
|
|
465
458
|
|
|
466
|
-
// Test finding closest to line 3
|
|
467
459
|
const result = findClosestOccurrence({
|
|
468
460
|
source: content,
|
|
469
461
|
selectedText: "the",
|
package/src/lib/anchor.ts
CHANGED
|
@@ -31,7 +31,6 @@ export function normalizeWhitespace(text: string): string {
|
|
|
31
31
|
return text.replace(/\s+/g, " ").trim();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
/** Wagner-Fischer with O(min(m,n)) space. Returns Infinity when > maxDistance. */
|
|
35
34
|
export function levenshteinDistance(
|
|
36
35
|
a: string,
|
|
37
36
|
b: string,
|
|
@@ -100,7 +99,6 @@ export function getLineOffset(content: string, lineNumber: number): number {
|
|
|
100
99
|
return content.length;
|
|
101
100
|
}
|
|
102
101
|
|
|
103
|
-
/** Supports "L42", "L42-L55", and legacy "L42-45" format. */
|
|
104
102
|
export function parseLineHint(lineHint: string): {
|
|
105
103
|
start: number;
|
|
106
104
|
end: number;
|
|
@@ -222,7 +220,6 @@ export function findAnchorNormalized({
|
|
|
222
220
|
const originalStart = windowStart + toOriginal[normalizedIndex];
|
|
223
221
|
const endNormIndex = normalizedIndex + normalizedText.length - 1;
|
|
224
222
|
let originalEnd = windowStart + toOriginal[endNormIndex] + 1;
|
|
225
|
-
// Extend past trailing whitespace that was collapsed during normalization
|
|
226
223
|
while (originalEnd < source.length && /\s/.test(source[originalEnd])) {
|
|
227
224
|
originalEnd++;
|
|
228
225
|
}
|
|
@@ -318,7 +315,6 @@ export function findAnchorFuzzy({
|
|
|
318
315
|
return bestMatch;
|
|
319
316
|
}
|
|
320
317
|
|
|
321
|
-
/** Fallback chain: exact → normalized → fuzzy. */
|
|
322
318
|
export function findAnchorWithFallback({
|
|
323
319
|
source,
|
|
324
320
|
selectedText,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { bench, describe } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
COMMENT_FILE_LARGE,
|
|
4
|
+
COMMENT_FILE_MEDIUM,
|
|
5
|
+
COMMENT_FILE_OBJ_LARGE,
|
|
6
|
+
COMMENT_FILE_OBJ_MEDIUM,
|
|
7
|
+
COMMENT_FILE_OBJ_SMALL,
|
|
8
|
+
COMMENT_FILE_SMALL,
|
|
9
|
+
LARGE_DOC,
|
|
10
|
+
} from "./__fixtures__/bench-data";
|
|
11
|
+
import {
|
|
12
|
+
computeHash,
|
|
13
|
+
parseCommentFile,
|
|
14
|
+
serializeComments,
|
|
15
|
+
} from "./comment-storage";
|
|
16
|
+
|
|
17
|
+
describe("parseCommentFile", () => {
|
|
18
|
+
bench("1 comment", () => {
|
|
19
|
+
parseCommentFile(COMMENT_FILE_SMALL);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
bench("10 comments", () => {
|
|
23
|
+
parseCommentFile(COMMENT_FILE_MEDIUM);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
bench("50 comments", () => {
|
|
27
|
+
parseCommentFile(COMMENT_FILE_LARGE);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("serializeComments", () => {
|
|
32
|
+
bench("1 comment", () => {
|
|
33
|
+
serializeComments(COMMENT_FILE_OBJ_SMALL);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
bench("10 comments", () => {
|
|
37
|
+
serializeComments(COMMENT_FILE_OBJ_MEDIUM);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
bench("50 comments", () => {
|
|
41
|
+
serializeComments(COMMENT_FILE_OBJ_LARGE);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("computeHash", () => {
|
|
46
|
+
bench("300-line document", () => {
|
|
47
|
+
computeHash(LARGE_DOC);
|
|
48
|
+
});
|
|
49
|
+
});
|