@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
@@ -1,3 +1,6 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLButtonAttributes } from "svelte/elements";
1
4
  import { cn } from "../../lib/utils";
2
5
 
3
6
  const baseStyles =
@@ -25,30 +28,26 @@ const sizeStyles = {
25
28
  type ButtonVariant = keyof typeof variantStyles;
26
29
  type ButtonSize = keyof typeof sizeStyles;
27
30
 
28
- interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
31
+ interface Props extends HTMLButtonAttributes {
29
32
  variant?: ButtonVariant;
30
33
  size?: ButtonSize;
34
+ children: Snippet;
31
35
  }
32
36
 
33
- function Button({
34
- className,
37
+ let {
35
38
  variant = "default",
36
39
  size = "default",
37
- ...props
38
- }: ButtonProps) {
39
- return (
40
- <button
41
- type="button"
42
- className={cn(
43
- baseStyles,
44
- variantStyles[variant],
45
- sizeStyles[size],
46
- className,
47
- )}
48
- {...props}
49
- />
50
- );
51
- }
40
+ class: className,
41
+ type = "button",
42
+ children,
43
+ ...rest
44
+ }: Props = $props();
45
+ </script>
52
46
 
53
- export type { ButtonProps, ButtonSize, ButtonVariant };
54
- export { Button };
47
+ <button
48
+ {...rest}
49
+ {type}
50
+ class={cn(baseStyles, variantStyles[variant], sizeStyles[size], className)}
51
+ >
52
+ {@render children()}
53
+ </button>
@@ -0,0 +1,97 @@
1
+ <script lang="ts">
2
+ import { X } from "lucide-svelte";
3
+ import type { Snippet } from "svelte";
4
+ import { cn } from "../../lib/utils";
5
+
6
+ interface Props {
7
+ open: boolean;
8
+ onclose?: () => void;
9
+ contentClass?: string;
10
+ children: Snippet;
11
+ header?: Snippet;
12
+ headerActions?: Snippet;
13
+ }
14
+
15
+ let {
16
+ open = $bindable(false),
17
+ onclose,
18
+ contentClass,
19
+ children,
20
+ header,
21
+ headerActions,
22
+ }: Props = $props();
23
+
24
+ let dialogEl: HTMLDialogElement | undefined = $state();
25
+
26
+ $effect(() => {
27
+ if (!dialogEl) return;
28
+
29
+ if (open && !dialogEl.open) {
30
+ dialogEl.showModal();
31
+ } else if (!open && dialogEl.open) {
32
+ dialogEl.close();
33
+ }
34
+ });
35
+
36
+ $effect(() => {
37
+ if (!dialogEl) return;
38
+
39
+ const handleClose = () => {
40
+ open = false;
41
+ onclose?.();
42
+ };
43
+
44
+ dialogEl.addEventListener("close", handleClose);
45
+ return () => dialogEl!.removeEventListener("close", handleClose);
46
+ });
47
+
48
+ function handleBackdropClick(e: MouseEvent) {
49
+ if (e.target === dialogEl && dialogEl?.open) {
50
+ dialogEl.close();
51
+ }
52
+ }
53
+
54
+ function handleCloseClick() {
55
+ if (dialogEl?.open) dialogEl.close();
56
+ }
57
+ </script>
58
+
59
+ <dialog
60
+ bind:this={dialogEl}
61
+ onclick={handleBackdropClick}
62
+ class="backdrop:bg-black/20 dark:backdrop:bg-black/40 backdrop:backdrop-blur-sm bg-transparent p-0 m-auto max-w-none"
63
+ >
64
+ {#if open}
65
+ <div
66
+ class={cn(
67
+ "w-full bg-white/95 dark:bg-zinc-900/95 backdrop-blur-sm shadow-lg border border-zinc-200/40 dark:border-zinc-700/40 rounded-xl flex flex-col",
68
+ contentClass,
69
+ )}
70
+ >
71
+ {#if header}
72
+ <div
73
+ class="flex items-center justify-between pl-4 pr-12 py-3 border-b border-zinc-100 dark:border-zinc-800"
74
+ >
75
+ <h2 class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
76
+ {@render header()}
77
+ </h2>
78
+ {#if headerActions}
79
+ {@render headerActions()}
80
+ {/if}
81
+ </div>
82
+ {/if}
83
+
84
+ <div class="flex-1 overflow-visible p-4">
85
+ {@render children()}
86
+ </div>
87
+
88
+ <button
89
+ type="button"
90
+ onclick={handleCloseClick}
91
+ class="absolute top-3 right-3 size-7 inline-flex items-center justify-center rounded-lg text-zinc-600 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800"
92
+ >
93
+ <X class="w-4 h-4" />
94
+ </button>
95
+ </div>
96
+ {/if}
97
+ </dialog>
@@ -0,0 +1,85 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { cn } from "../../lib/utils";
4
+
5
+ interface Props {
6
+ open?: boolean;
7
+ trigger: Snippet;
8
+ align?: "start" | "end";
9
+ contentClass?: string;
10
+ children: Snippet;
11
+ }
12
+
13
+ let {
14
+ open = $bindable(false),
15
+ trigger,
16
+ align = "start",
17
+ contentClass,
18
+ children,
19
+ }: Props = $props();
20
+
21
+ let containerEl: HTMLDivElement | undefined = $state();
22
+
23
+ function toggle() {
24
+ open = !open;
25
+ }
26
+
27
+ function close() {
28
+ open = false;
29
+ }
30
+
31
+ function handleKeydown(e: KeyboardEvent) {
32
+ if (e.key === "Escape" && open) {
33
+ e.stopPropagation();
34
+ close();
35
+ }
36
+ if (e.key === "Enter" || e.key === " ") {
37
+ e.preventDefault();
38
+ e.stopPropagation();
39
+ toggle();
40
+ }
41
+ }
42
+
43
+ $effect(() => {
44
+ if (!open) return;
45
+
46
+ function handleClickOutside(e: MouseEvent) {
47
+ if (containerEl && !containerEl.contains(e.target as Node)) {
48
+ close();
49
+ }
50
+ }
51
+
52
+ document.addEventListener("mousedown", handleClickOutside);
53
+ return () => document.removeEventListener("mousedown", handleClickOutside);
54
+ });
55
+ </script>
56
+
57
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
58
+ <div
59
+ bind:this={containerEl}
60
+ class="relative inline-block"
61
+ onkeydown={handleKeydown}
62
+ >
63
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
64
+ <div onclick={toggle} role="button" tabindex="0" aria-expanded={open}>
65
+ {@render trigger()}
66
+ </div>
67
+
68
+ {#if open}
69
+ <div
70
+ class={cn(
71
+ "absolute top-full mt-1 z-50 min-w-[8rem] overflow-hidden rounded-xl py-1",
72
+ "bg-white/95 dark:bg-zinc-900/95 backdrop-blur-sm shadow-lg border border-zinc-200/40 dark:border-zinc-700/40",
73
+ align === "end" ? "right-0" : "left-0",
74
+ contentClass,
75
+ )}
76
+ >
77
+ {@render children()}
78
+ </div>
79
+ {/if}
80
+ </div>
81
+
82
+ <script lang="ts" module>
83
+ export { default as DropdownMenuItem } from "./DropdownMenuItem.svelte";
84
+ export { default as DropdownMenuSeparator } from "./DropdownMenuSeparator.svelte";
85
+ </script>
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { cn } from "../../lib/utils";
4
+
5
+ interface Props {
6
+ variant?: "default" | "destructive";
7
+ onselect?: () => void;
8
+ class?: string;
9
+ title?: string;
10
+ children: Snippet;
11
+ }
12
+
13
+ let {
14
+ variant = "default",
15
+ onselect,
16
+ class: className,
17
+ title,
18
+ children,
19
+ }: Props = $props();
20
+ </script>
21
+
22
+ <button
23
+ type="button"
24
+ role="menuitem"
25
+ {title}
26
+ class={cn(
27
+ "w-full px-3 py-1.5 text-left text-sm outline-none select-none transition-colors duration-150 flex items-center gap-2 cursor-default",
28
+ variant === "default" &&
29
+ "text-zinc-600 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:bg-zinc-800 hover:text-zinc-900 dark:hover:text-zinc-100 focus-visible:bg-zinc-50 dark:focus-visible:bg-zinc-800 focus-visible:text-zinc-900 dark:focus-visible:text-zinc-100",
30
+ variant === "destructive" &&
31
+ "text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950 hover:text-red-700 dark:hover:text-red-300 focus-visible:bg-red-50 dark:focus-visible:bg-red-950 focus-visible:text-red-700 dark:focus-visible:text-red-300",
32
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
33
+ className,
34
+ )}
35
+ onclick={() => onselect?.()}
36
+ >
37
+ {@render children()}
38
+ </button>
@@ -0,0 +1,11 @@
1
+ <script lang="ts">
2
+ import { cn } from "../../lib/utils";
3
+
4
+ interface Props {
5
+ class?: string;
6
+ }
7
+
8
+ let { class: className }: Props = $props();
9
+ </script>
10
+
11
+ <div class={cn("my-1 h-px bg-zinc-100 dark:bg-zinc-800", className)}></div>
@@ -1,7 +1,8 @@
1
- import { use } from "react";
2
- import { SettingsContext } from "../../contexts/SettingsContext";
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 TextProps extends React.HTMLAttributes<HTMLElement> {
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
- function Text({
26
- className,
28
+ let {
27
29
  variant = "body",
28
- as: Tag = "p",
29
- ...props
30
- }: TextProps) {
31
- const settings = use(SettingsContext);
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
- return (
39
- <Tag
40
- className={cn(fontClass, variantStyles[variant], className)}
41
- {...props}
42
- />
43
- );
44
- }
35
+ let fontClass = $derived(
36
+ settings.fontFamily === FontFamilies.SANS_SERIF ? "font-sans" : "font-serif",
37
+ );
38
+ </script>
45
39
 
46
- export type { TextVariant };
47
- export { Text, variantStyles };
40
+ <svelte:element this={as} class={cn(fontClass, variantStyles[variant], className)}>
41
+ {@render children()}
42
+ </svelte:element>
package/src/env.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /// <reference types="bun" />
2
+
3
+ declare module "*.css" {
4
+ const content: string;
5
+ export default content;
6
+ }