@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,7 @@
1
+ {
2
+ "mcpServers": {
3
+ "forge_extension": {
4
+ "url": "http://localhost:51079/mcp"
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ .vscode/**
2
+ src/**
3
+ node_modules/**
4
+ tsconfig.json
5
+ .gitignore
6
+ .mcp.json
7
+ **/*.map
@@ -0,0 +1,78 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "vscode-readit",
7
+ "devDependencies": {
8
+ "@types/node": "^25.5.2",
9
+ "@types/vscode": "^1.110.0",
10
+ "esbuild": "^0.25.12",
11
+ "typescript": "^5.9.3",
12
+ },
13
+ },
14
+ },
15
+ "packages": {
16
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "https://npm.flatt.tech/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
17
+
18
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "https://npm.flatt.tech/@esbuild/android-arm/-/android-arm-0.25.12.tgz", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
19
+
20
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
21
+
22
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "https://npm.flatt.tech/@esbuild/android-x64/-/android-x64-0.25.12.tgz", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
23
+
24
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
25
+
26
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "https://npm.flatt.tech/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
27
+
28
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
29
+
30
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "https://npm.flatt.tech/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
31
+
32
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "https://npm.flatt.tech/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
33
+
34
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
35
+
36
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "https://npm.flatt.tech/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
37
+
38
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "https://npm.flatt.tech/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
39
+
40
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "https://npm.flatt.tech/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
41
+
42
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "https://npm.flatt.tech/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
43
+
44
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "https://npm.flatt.tech/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
45
+
46
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "https://npm.flatt.tech/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
47
+
48
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "https://npm.flatt.tech/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
49
+
50
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
51
+
52
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "https://npm.flatt.tech/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
53
+
54
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
55
+
56
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "https://npm.flatt.tech/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
57
+
58
+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
59
+
60
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "https://npm.flatt.tech/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
61
+
62
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
63
+
64
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "https://npm.flatt.tech/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
65
+
66
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "https://npm.flatt.tech/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
67
+
68
+ "@types/node": ["@types/node@25.5.2", "https://npm.flatt.tech/@types/node/-/node-25.5.2.tgz", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="],
69
+
70
+ "@types/vscode": ["@types/vscode@1.110.0", "https://npm.flatt.tech/@types/vscode/-/vscode-1.110.0.tgz", {}, "sha512-AGuxUEpU4F4mfuQjxPPaQVyuOMhs+VT/xRok1jiHVBubHK7lBRvCuOMZG0LKUwxncrPorJ5qq/uil3IdZBd5lA=="],
71
+
72
+ "esbuild": ["esbuild@0.25.12", "https://npm.flatt.tech/esbuild/-/esbuild-0.25.12.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
73
+
74
+ "typescript": ["typescript@5.9.3", "https://npm.flatt.tech/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
75
+
76
+ "undici-types": ["undici-types@7.18.2", "https://npm.flatt.tech/undici-types/-/undici-types-7.18.2.tgz", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
77
+ }
78
+ }
@@ -0,0 +1,10 @@
1
+ <svg viewBox="-75 -80 150 160" xmlns="http://www.w3.org/2000/svg">
2
+ <g fill="#E07A50">
3
+ <path d="M-8 -68 C-24 -66, -48 -52, -56 -30 C-64 -8, -58 18, -44 36 C-30 54, -8 66, 14 66 C36 66, 54 52, 60 32 C66 12, 58 -12, 44 -30 C30 -48, 10 -56, -2 -62 C-6 -64, -4 -70, -8 -68Z"/>
4
+ <circle cx="6" cy="-4" r="26" fill="#fff"/>
5
+ <circle cx="6" cy="-4" r="18" fill="#E07A50"/>
6
+ <circle cx="6" cy="-4" r="10" fill="#fff"/>
7
+ <circle cx="10" cy="-6" r="4.5"/>
8
+ <path d="M30 -18 C38 -26, 50 -34, 56 -44 C60 -50, 56 -56, 50 -52 C44 -46, 36 -32, 30 -22Z"/>
9
+ </g>
10
+ </svg>
@@ -0,0 +1,110 @@
1
+ {
2
+ "name": "vscode-readit",
3
+ "displayName": "readit",
4
+ "description": "Review Markdown documents with inline comments — powered by readit",
5
+ "version": "0.0.1",
6
+ "publisher": "peaske7",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/peaske7/readit"
11
+ },
12
+ "engines": {
13
+ "vscode": "^1.85.0"
14
+ },
15
+ "categories": [
16
+ "Other",
17
+ "Visualization"
18
+ ],
19
+ "keywords": [
20
+ "markdown",
21
+ "review",
22
+ "comments",
23
+ "preview"
24
+ ],
25
+ "activationEvents": [
26
+ "onLanguage:markdown",
27
+ "onCommand:readit.openPreview",
28
+ "onCommand:readit.openPreviewToSide"
29
+ ],
30
+ "icon": "icon.svg",
31
+ "main": "./dist/extension.js",
32
+ "contributes": {
33
+ "commands": [
34
+ {
35
+ "command": "readit.openPreview",
36
+ "title": "readit: Open Preview",
37
+ "icon": {
38
+ "light": "icon.svg",
39
+ "dark": "icon.svg"
40
+ }
41
+ },
42
+ {
43
+ "command": "readit.openPreviewToSide",
44
+ "title": "readit: Open Preview to the Side",
45
+ "icon": {
46
+ "light": "icon.svg",
47
+ "dark": "icon.svg"
48
+ }
49
+ }
50
+ ],
51
+ "menus": {
52
+ "editor/title": [
53
+ {
54
+ "when": "resourceLangId == markdown",
55
+ "command": "readit.openPreviewToSide",
56
+ "group": "navigation"
57
+ }
58
+ ],
59
+ "explorer/context": [
60
+ {
61
+ "when": "resourceLangId == markdown",
62
+ "command": "readit.openPreview",
63
+ "group": "navigation"
64
+ }
65
+ ],
66
+ "editor/context": [
67
+ {
68
+ "when": "resourceLangId == markdown",
69
+ "command": "readit.openPreview",
70
+ "group": "navigation"
71
+ }
72
+ ]
73
+ },
74
+ "keybindings": [
75
+ {
76
+ "command": "readit.openPreviewToSide",
77
+ "key": "ctrl+shift+r",
78
+ "mac": "cmd+shift+r",
79
+ "when": "resourceLangId == markdown"
80
+ }
81
+ ],
82
+ "configuration": {
83
+ "title": "readit",
84
+ "properties": {
85
+ "readit.bunPath": {
86
+ "type": "string",
87
+ "default": "bun",
88
+ "description": "Path to the Bun runtime executable."
89
+ },
90
+ "readit.serverPort": {
91
+ "type": "number",
92
+ "default": 0,
93
+ "description": "Port for the readit server. 0 = auto-select a free port."
94
+ }
95
+ }
96
+ }
97
+ },
98
+ "scripts": {
99
+ "build": "esbuild src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node --minify",
100
+ "watch": "esbuild src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node --sourcemap --watch",
101
+ "typecheck": "tsc --noEmit",
102
+ "package": "vsce package"
103
+ },
104
+ "devDependencies": {
105
+ "@types/node": "^25.5.2",
106
+ "@types/vscode": "^1.110.0",
107
+ "esbuild": "^0.25.12",
108
+ "typescript": "^5.9.3"
109
+ }
110
+ }
@@ -0,0 +1,117 @@
1
+ import { existsSync } from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as vscode from "vscode";
4
+ import { ServerManager } from "./server-manager";
5
+ import { ReaditWebviewProvider } from "./webview-provider";
6
+
7
+ let serverManager: ServerManager | undefined;
8
+ let webviewProvider: ReaditWebviewProvider | undefined;
9
+ let outputChannel: vscode.OutputChannel | undefined;
10
+
11
+ /**
12
+ * Resolves the readit CLI dist/ directory for the mono-repo layout and throws
13
+ * if the built CLI is not available before the extension tries to start it.
14
+ */
15
+ function resolveReaditDistDir(extensionPath: string): string {
16
+ const monoRepoDist = path.resolve(extensionPath, "..", "dist");
17
+ if (!existsSync(path.join(monoRepoDist, "index.js"))) {
18
+ throw new Error(
19
+ "Could not find readit/dist/index.js. Build the readit CLI before using the VS Code extension.",
20
+ );
21
+ }
22
+ return monoRepoDist;
23
+ }
24
+
25
+ /**
26
+ * Gets the absolute file path from a command argument or the active editor.
27
+ */
28
+ function resolveFilePath(arg?: vscode.Uri): string | undefined {
29
+ // When invoked from explorer context menu or editor title, arg is a Uri
30
+ if (arg instanceof vscode.Uri) {
31
+ return arg.fsPath;
32
+ }
33
+
34
+ // Fall back to active editor
35
+ const editor = vscode.window.activeTextEditor;
36
+ if (editor && editor.document.languageId === "markdown") {
37
+ return editor.document.uri.fsPath;
38
+ }
39
+
40
+ return undefined;
41
+ }
42
+
43
+ export function activate(context: vscode.ExtensionContext): void {
44
+ outputChannel = vscode.window.createOutputChannel("readit");
45
+ serverManager = new ServerManager(outputChannel);
46
+ webviewProvider = new ReaditWebviewProvider(
47
+ context.extensionUri,
48
+ serverManager,
49
+ outputChannel,
50
+ );
51
+
52
+ let readitDistDir: string;
53
+ try {
54
+ readitDistDir = resolveReaditDistDir(context.extensionPath);
55
+ } catch (err) {
56
+ const message = err instanceof Error ? err.message : String(err);
57
+ outputChannel.appendLine(message);
58
+ void vscode.window.showErrorMessage(`readit: ${message}`);
59
+ return;
60
+ }
61
+
62
+ // readit.openPreview — opens in the active column
63
+ const openPreview = vscode.commands.registerCommand(
64
+ "readit.openPreview",
65
+ async (arg?: vscode.Uri) => {
66
+ const filePath = resolveFilePath(arg);
67
+ if (!filePath) {
68
+ vscode.window.showWarningMessage(
69
+ "readit: No Markdown file is open or selected.",
70
+ );
71
+ return;
72
+ }
73
+
74
+ await webviewProvider!.openPreview(
75
+ filePath,
76
+ readitDistDir,
77
+ vscode.ViewColumn.Active,
78
+ );
79
+ },
80
+ );
81
+
82
+ // readit.openPreviewToSide — opens beside the current editor
83
+ const openPreviewToSide = vscode.commands.registerCommand(
84
+ "readit.openPreviewToSide",
85
+ async (arg?: vscode.Uri) => {
86
+ const filePath = resolveFilePath(arg);
87
+ if (!filePath) {
88
+ vscode.window.showWarningMessage(
89
+ "readit: No Markdown file is open or selected.",
90
+ );
91
+ return;
92
+ }
93
+
94
+ await webviewProvider!.openPreview(
95
+ filePath,
96
+ readitDistDir,
97
+ vscode.ViewColumn.Beside,
98
+ );
99
+ },
100
+ );
101
+
102
+ context.subscriptions.push(
103
+ outputChannel,
104
+ serverManager,
105
+ webviewProvider,
106
+ openPreview,
107
+ openPreviewToSide,
108
+ );
109
+ }
110
+
111
+ export function deactivate(): void {
112
+ // Disposables registered on context.subscriptions are cleaned up automatically.
113
+ // Explicit cleanup as a safety net:
114
+ webviewProvider?.dispose();
115
+ serverManager?.dispose();
116
+ outputChannel?.dispose();
117
+ }
@@ -0,0 +1,272 @@
1
+ import { type ChildProcess, spawn } from "node:child_process";
2
+ import * as net from "node:net";
3
+ import * as vscode from "vscode";
4
+
5
+ /**
6
+ * Manages the lifecycle of a readit Bun server process.
7
+ *
8
+ * Spawns `bun dist/index.js <file> --no-open --port <port>` as a child process.
9
+ * Subsequent files are attached via POST /api/documents to the running server.
10
+ * The server is killed when the extension deactivates or the last panel closes.
11
+ */
12
+ export class ServerManager implements vscode.Disposable {
13
+ private process: ChildProcess | null = null;
14
+ private port: number | null = null;
15
+ private outputChannel: vscode.OutputChannel;
16
+ private startPromise: Promise<number> | null = null;
17
+
18
+ constructor(outputChannel: vscode.OutputChannel) {
19
+ this.outputChannel = outputChannel;
20
+ }
21
+
22
+ /** Returns the port of the running server, or null if not started. */
23
+ getPort(): number | null {
24
+ return this.port;
25
+ }
26
+
27
+ /** Returns the base URL of the running server. */
28
+ getBaseUrl(): string | null {
29
+ if (this.port === null) return null;
30
+ return `http://127.0.0.1:${this.port}`;
31
+ }
32
+
33
+ /** True if the server process is running. */
34
+ isRunning(): boolean {
35
+ return this.process !== null && this.process.exitCode === null;
36
+ }
37
+
38
+ /**
39
+ * Ensures the server is running and the given file is loaded.
40
+ * If the server is not started, spawns it with the file.
41
+ * If already running, attaches the file via the HTTP API.
42
+ * Returns the port number.
43
+ */
44
+ async ensureRunning(
45
+ filePath: string,
46
+ readitDistDir: string,
47
+ ): Promise<number> {
48
+ if (this.startPromise) {
49
+ const port = await this.startPromise;
50
+ await this.attachFile(filePath);
51
+ return port;
52
+ }
53
+
54
+ if (this.isRunning() && this.port !== null) {
55
+ await this.attachFile(filePath);
56
+ return this.port;
57
+ }
58
+
59
+ return this.start(filePath, readitDistDir);
60
+ }
61
+
62
+ /**
63
+ * Starts the Bun server process.
64
+ * Resolves with the port once the server is healthy.
65
+ */
66
+ private start(filePath: string, readitDistDir: string): Promise<number> {
67
+ this.startPromise = this.doStart(filePath, readitDistDir);
68
+ return this.startPromise;
69
+ }
70
+
71
+ private async doStart(
72
+ filePath: string,
73
+ readitDistDir: string,
74
+ ): Promise<number> {
75
+ const config = vscode.workspace.getConfiguration("readit");
76
+ const bunPath = config.get<string>("bunPath", "bun");
77
+ const configPort = config.get<number>("serverPort", 0);
78
+
79
+ const port = configPort > 0 ? configPort : await findFreePort();
80
+
81
+ const cliEntry = vscode.Uri.joinPath(
82
+ vscode.Uri.file(readitDistDir),
83
+ "index.js",
84
+ ).fsPath;
85
+
86
+ const args = [cliEntry, filePath, "--no-open", "--port", String(port)];
87
+
88
+ this.outputChannel.appendLine(
89
+ `Starting readit server: ${bunPath} ${args.join(" ")}`,
90
+ );
91
+
92
+ return new Promise<number>((resolve, reject) => {
93
+ const child = spawn(bunPath, args, {
94
+ stdio: ["ignore", "pipe", "pipe"],
95
+ env: {
96
+ ...process.env,
97
+ NODE_ENV: "production",
98
+ },
99
+ });
100
+
101
+ this.process = child;
102
+ let settled = false;
103
+ let timer: NodeJS.Timeout | undefined;
104
+
105
+ const rejectStartup = (message: string) => {
106
+ if (settled) return;
107
+ settled = true;
108
+ if (timer) {
109
+ clearInterval(timer);
110
+ }
111
+ this.cleanup(child);
112
+ reject(new Error(message));
113
+ };
114
+
115
+ child.stdout?.on("data", (data: Buffer) => {
116
+ this.outputChannel.appendLine(data.toString().trimEnd());
117
+ });
118
+
119
+ child.stderr?.on("data", (data: Buffer) => {
120
+ this.outputChannel.appendLine(`[stderr] ${data.toString().trimEnd()}`);
121
+ });
122
+
123
+ child.on("error", (err) => {
124
+ this.outputChannel.appendLine(`Server process error: ${err.message}`);
125
+ rejectStartup(
126
+ `Failed to start readit server. Is Bun installed? (${err.message})`,
127
+ );
128
+ });
129
+
130
+ const handleProcessExit = (
131
+ code: number | null,
132
+ signal: NodeJS.Signals | null,
133
+ ) => {
134
+ this.outputChannel.appendLine(
135
+ `Server process exited with code ${code}${signal ? ` (signal: ${signal})` : ""}`,
136
+ );
137
+ if (!settled) {
138
+ rejectStartup(
139
+ `readit server exited before it became ready (${code === null ? (signal ?? "unknown") : `code ${code}`})`,
140
+ );
141
+ return;
142
+ }
143
+ this.cleanup(child);
144
+ };
145
+
146
+ child.on("exit", handleProcessExit);
147
+
148
+ // Poll for server readiness
149
+ const maxWaitMs = 15_000;
150
+ const intervalMs = 200;
151
+ const startTime = Date.now();
152
+
153
+ timer = setInterval(async () => {
154
+ if (Date.now() - startTime > maxWaitMs) {
155
+ clearInterval(timer);
156
+ child.kill("SIGTERM");
157
+ rejectStartup("readit server failed to start within 15 seconds");
158
+ return;
159
+ }
160
+
161
+ try {
162
+ const res = await fetch(`http://127.0.0.1:${port}/api/health`);
163
+ if (res.ok) {
164
+ clearInterval(timer);
165
+ settled = true;
166
+ this.port = port;
167
+ this.outputChannel.appendLine(`Server ready on port ${port}`);
168
+ resolve(port);
169
+ }
170
+ } catch {
171
+ // Not ready yet, keep polling
172
+ }
173
+ }, intervalMs);
174
+ });
175
+ }
176
+
177
+ /**
178
+ * Attaches a file to the running server via POST /api/documents.
179
+ */
180
+ private async attachFile(filePath: string): Promise<void> {
181
+ if (this.port === null) return;
182
+
183
+ try {
184
+ const res = await fetch(`http://127.0.0.1:${this.port}/api/documents`, {
185
+ method: "POST",
186
+ headers: { "Content-Type": "application/json" },
187
+ body: JSON.stringify({ path: filePath }),
188
+ });
189
+
190
+ if (!res.ok) {
191
+ const raw = await res.text();
192
+ let errorMessage = res.statusText;
193
+ if (raw) {
194
+ try {
195
+ const data = JSON.parse(raw) as { error?: string };
196
+ errorMessage = data.error ?? raw;
197
+ } catch {
198
+ errorMessage = raw;
199
+ }
200
+ }
201
+ this.outputChannel.appendLine(
202
+ `Failed to attach file ${filePath}: ${errorMessage}`,
203
+ );
204
+ } else {
205
+ const data = (await res.json()) as {
206
+ status?: string;
207
+ fileName?: string;
208
+ };
209
+ this.outputChannel.appendLine(
210
+ `File ${data.fileName ?? filePath}: ${data.status ?? "attached"}`,
211
+ );
212
+ }
213
+ } catch (err) {
214
+ this.outputChannel.appendLine(
215
+ `Error attaching file: ${err instanceof Error ? err.message : String(err)}`,
216
+ );
217
+ }
218
+ }
219
+
220
+ /** Stops the server process. */
221
+ stop(): void {
222
+ const child = this.process;
223
+ if (child) {
224
+ this.outputChannel.appendLine("Stopping readit server...");
225
+ child.kill("SIGTERM");
226
+ let hasExited = false;
227
+
228
+ // Force kill after 3 seconds
229
+ const forceKillTimer = setTimeout(() => {
230
+ if (!hasExited && child.exitCode === null) {
231
+ child.kill("SIGKILL");
232
+ }
233
+ }, 3_000);
234
+
235
+ child.once("exit", () => {
236
+ hasExited = true;
237
+ clearTimeout(forceKillTimer);
238
+ });
239
+ }
240
+ this.cleanup();
241
+ }
242
+
243
+ private cleanup(child?: ChildProcess): void {
244
+ if (child && this.process !== child) {
245
+ return;
246
+ }
247
+ this.process = null;
248
+ this.port = null;
249
+ this.startPromise = null;
250
+ }
251
+
252
+ dispose(): void {
253
+ this.stop();
254
+ }
255
+ }
256
+
257
+ /** Finds a free TCP port by binding to port 0 and reading the assigned port. */
258
+ function findFreePort(): Promise<number> {
259
+ return new Promise((resolve, reject) => {
260
+ const server = net.createServer();
261
+ server.listen(0, "127.0.0.1", () => {
262
+ const addr = server.address();
263
+ if (addr && typeof addr === "object") {
264
+ const port = addr.port;
265
+ server.close(() => resolve(port));
266
+ } else {
267
+ server.close(() => reject(new Error("Could not determine free port")));
268
+ }
269
+ });
270
+ server.on("error", reject);
271
+ });
272
+ }