@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,463 @@
1
+ -- readit.nvim — Neovim plugin for rendering Markdown with readit
2
+ --
3
+ -- Manages a readit server process and opens documents in the browser
4
+ -- or a split terminal. Supports live-reload: edits in Neovim are
5
+ -- reflected in the browser automatically (via readit's file watcher).
6
+
7
+ local M = {}
8
+
9
+ --- @class ReaditConfig
10
+ --- @field bun_path string Path to bun executable
11
+ --- @field port number Preferred server port (0 = auto)
12
+ --- @field host string Server host
13
+ --- @field auto_open boolean Open browser automatically
14
+ --- @field auto_reload boolean Auto-reload on BufWritePost (enabled by default via readit's fs watcher)
15
+ --- @field open_cmd string|nil Custom browser open command
16
+ --- @field keymap_prefix string Key prefix for mappings
17
+ --- @field keymaps table<string, string|false> Keymap overrides (false to disable)
18
+ --- @field float_opts table Floating window options for terminal
19
+
20
+ --- @type ReaditConfig
21
+ M.config = {
22
+ bun_path = "bun",
23
+ port = 0,
24
+ host = "127.0.0.1",
25
+ auto_open = true,
26
+ auto_reload = true,
27
+ open_cmd = nil,
28
+ keymap_prefix = "<leader>r",
29
+ keymaps = {
30
+ open = "o", -- Open current file in readit
31
+ open_side = "s", -- Open in side browser (if available)
32
+ stop = "q", -- Stop the server
33
+ status = "i", -- Show server status
34
+ reload = "r", -- Force reload in browser
35
+ list = "l", -- List commented files
36
+ },
37
+ float_opts = {
38
+ relative = "editor",
39
+ width = 0.8,
40
+ height = 0.8,
41
+ border = "rounded",
42
+ },
43
+ }
44
+
45
+ --- @type number|nil
46
+ M._server_port = nil
47
+ --- @type number|nil
48
+ M._server_job_id = nil
49
+ --- @type table<string, boolean>
50
+ M._attached_files = {}
51
+
52
+ -- ── Setup ────────────────────────────────────────────────────────────
53
+
54
+ --- Setup the plugin with user configuration
55
+ --- @param opts? ReaditConfig
56
+ --- @type boolean
57
+ M._setup_done = false
58
+
59
+ function M.setup(opts)
60
+ M.config = vim.tbl_deep_extend("force", M.config, opts or {})
61
+ M._setup_commands()
62
+ M._setup_keymaps()
63
+ M._setup_autocmds()
64
+ M._setup_done = true
65
+ end
66
+
67
+ -- ── Server Management ────────────────────────────────────────────────
68
+
69
+ --- Discover an already-running readit server from ~/.readit/server.json
70
+ --- @return {port: number, pid: number}|nil
71
+ function M._discover_server()
72
+ local path = vim.fn.expand("~/.readit/server.json")
73
+ local ok, content = pcall(vim.fn.readfile, path)
74
+ if not ok or #content == 0 then
75
+ return nil
76
+ end
77
+
78
+ local decoded = vim.json.decode(table.concat(content, "\n"))
79
+ if not decoded or not decoded.port or not decoded.pid then
80
+ return nil
81
+ end
82
+
83
+ -- Check if the process is alive
84
+ local alive = vim.fn.system("kill -0 " .. decoded.pid .. " 2>/dev/null; echo $?")
85
+ if vim.trim(alive) ~= "0" then
86
+ return nil
87
+ end
88
+
89
+ -- Health check
90
+ local health = vim.fn.system(
91
+ "curl -sf http://127.0.0.1:" .. decoded.port .. "/api/health 2>/dev/null"
92
+ )
93
+ if health == "" or vim.v.shell_error ~= 0 then
94
+ return nil
95
+ end
96
+
97
+ return decoded
98
+ end
99
+
100
+ --- Start the readit server for a given file
101
+ --- @param file_path string Absolute path to the markdown file
102
+ --- @param callback? fun(port: number) Called when server is ready
103
+ function M.start_server(file_path, callback)
104
+ -- Check for existing server first
105
+ local existing = M._discover_server()
106
+ if existing then
107
+ M._server_port = existing.port
108
+ M._attach_file(file_path, callback)
109
+ return
110
+ end
111
+
112
+ -- If we already have a running job, attach to it
113
+ if M._server_job_id and vim.fn.jobwait({ M._server_job_id }, 0)[1] == -1 then
114
+ if M._server_port then
115
+ M._attach_file(file_path, callback)
116
+ return
117
+ end
118
+ end
119
+
120
+ local port = M.config.port
121
+ local args = {
122
+ M.config.bun_path,
123
+ "run",
124
+ "--bun",
125
+ vim.fn.expand("~/.readit/dist/index.js"), -- Try installed version first
126
+ file_path,
127
+ "--no-open",
128
+ }
129
+
130
+ -- Check if the global dist exists; otherwise try npx
131
+ if vim.fn.filereadable(vim.fn.expand("~/.readit/dist/index.js")) == 0 then
132
+ -- Try to find readit in the project or globally
133
+ local readit_bin = vim.fn.exepath("readit")
134
+ if readit_bin ~= "" then
135
+ args = { readit_bin, file_path, "--no-open" }
136
+ else
137
+ -- Fall back to bunx
138
+ args = { M.config.bun_path .. "x", "@peaske7/readit", file_path, "--no-open" }
139
+ end
140
+ end
141
+
142
+ if port > 0 then
143
+ table.insert(args, "--port")
144
+ table.insert(args, tostring(port))
145
+ end
146
+
147
+ vim.notify("readit: starting server...", vim.log.levels.INFO)
148
+
149
+ M._server_job_id = vim.fn.jobstart(args, {
150
+ on_stdout = function(_, data)
151
+ for _, line in ipairs(data) do
152
+ -- Parse the URL from server output
153
+ local found_port = line:match("URL:%s*http://[^:]+:(%d+)")
154
+ if found_port then
155
+ M._server_port = tonumber(found_port)
156
+ M._attached_files[file_path] = true
157
+ vim.notify("readit: server ready on port " .. M._server_port, vim.log.levels.INFO)
158
+ if callback then
159
+ vim.schedule(function()
160
+ callback(M._server_port)
161
+ end)
162
+ end
163
+ end
164
+ end
165
+ end,
166
+ on_stderr = function(_, data)
167
+ for _, line in ipairs(data) do
168
+ if line ~= "" then
169
+ vim.notify("readit: " .. line, vim.log.levels.WARN)
170
+ end
171
+ end
172
+ end,
173
+ on_exit = function(_, code)
174
+ M._server_job_id = nil
175
+ M._server_port = nil
176
+ M._attached_files = {}
177
+ if code ~= 0 then
178
+ vim.notify("readit: server exited with code " .. code, vim.log.levels.ERROR)
179
+ end
180
+ end,
181
+ detach = false,
182
+ })
183
+
184
+ if M._server_job_id <= 0 then
185
+ vim.notify("readit: failed to start server", vim.log.levels.ERROR)
186
+ M._server_job_id = nil
187
+ end
188
+ end
189
+
190
+ --- Attach a file to the running server via HTTP API
191
+ --- @param file_path string
192
+ --- @param callback? fun(port: number)
193
+ function M._attach_file(file_path, callback)
194
+ if M._attached_files[file_path] then
195
+ if callback and M._server_port then
196
+ callback(M._server_port)
197
+ end
198
+ return
199
+ end
200
+
201
+ if not M._server_port then
202
+ vim.notify("readit: no server running", vim.log.levels.WARN)
203
+ return
204
+ end
205
+
206
+ local cmd = string.format(
207
+ 'curl -sf -X POST -H "Content-Type: application/json" -d \'{"path":"%s"}\' http://127.0.0.1:%d/api/documents 2>/dev/null',
208
+ file_path:gsub("'", "'\\''"),
209
+ M._server_port
210
+ )
211
+
212
+ vim.fn.jobstart({ "sh", "-c", cmd }, {
213
+ on_exit = function(_, code)
214
+ if code == 0 then
215
+ M._attached_files[file_path] = true
216
+ vim.schedule(function()
217
+ if callback and M._server_port then
218
+ callback(M._server_port)
219
+ end
220
+ end)
221
+ else
222
+ vim.schedule(function()
223
+ vim.notify("readit: failed to attach file", vim.log.levels.ERROR)
224
+ end)
225
+ end
226
+ end,
227
+ })
228
+ end
229
+
230
+ --- Stop the readit server
231
+ function M.stop_server()
232
+ if M._server_job_id then
233
+ vim.fn.jobstop(M._server_job_id)
234
+ M._server_job_id = nil
235
+ M._server_port = nil
236
+ M._attached_files = {}
237
+ vim.notify("readit: server stopped", vim.log.levels.INFO)
238
+ else
239
+ vim.notify("readit: no server running", vim.log.levels.INFO)
240
+ end
241
+ end
242
+
243
+ --- Get server status info
244
+ --- @return string
245
+ function M.server_status()
246
+ if M._server_port then
247
+ local files = vim.tbl_keys(M._attached_files)
248
+ return string.format(
249
+ "readit server running on port %d (%d file%s)",
250
+ M._server_port,
251
+ #files,
252
+ #files == 1 and "" or "s"
253
+ )
254
+ end
255
+ return "readit server not running"
256
+ end
257
+
258
+ -- ── Actions ──────────────────────────────────────────────────────────
259
+
260
+ --- Open the current markdown buffer in readit
261
+ --- @param opts? {browser?: boolean}
262
+ function M.open(opts)
263
+ opts = opts or { browser = true }
264
+ local bufname = vim.api.nvim_buf_get_name(0)
265
+
266
+ if bufname == "" then
267
+ vim.notify("readit: buffer has no file", vim.log.levels.WARN)
268
+ return
269
+ end
270
+
271
+ if not bufname:match("%.md$") and not bufname:match("%.markdown$") then
272
+ vim.notify("readit: not a markdown file", vim.log.levels.WARN)
273
+ return
274
+ end
275
+
276
+ -- Save the buffer first to ensure file watcher picks up latest
277
+ if vim.bo.modified then
278
+ vim.cmd("write")
279
+ end
280
+
281
+ local file_path = vim.fn.fnamemodify(bufname, ":p")
282
+
283
+ M.start_server(file_path, function(port)
284
+ if opts.browser and M.config.auto_open then
285
+ M._open_browser(port)
286
+ end
287
+ end)
288
+ end
289
+
290
+ --- Open the browser to the readit server
291
+ --- @param port number
292
+ function M._open_browser(port)
293
+ local url = "http://" .. M.config.host .. ":" .. port
294
+
295
+ if M.config.open_cmd then
296
+ vim.fn.system(M.config.open_cmd .. " " .. vim.fn.shellescape(url))
297
+ return
298
+ end
299
+
300
+ -- Platform-specific open
301
+ local open_cmd
302
+ if vim.fn.has("mac") == 1 then
303
+ open_cmd = "open"
304
+ elseif vim.fn.has("unix") == 1 then
305
+ open_cmd = "xdg-open"
306
+ elseif vim.fn.has("win32") == 1 then
307
+ open_cmd = "start"
308
+ end
309
+
310
+ if open_cmd then
311
+ vim.fn.jobstart({ open_cmd, url }, { detach = true })
312
+ end
313
+ end
314
+
315
+ --- Force reload the current document in the browser
316
+ function M.reload()
317
+ if not M._server_port then
318
+ vim.notify("readit: no server running", vim.log.levels.WARN)
319
+ return
320
+ end
321
+
322
+ -- Save first so the file watcher triggers reload
323
+ if vim.bo.modified then
324
+ vim.cmd("write")
325
+ end
326
+
327
+ vim.notify("readit: document will reload via file watcher", vim.log.levels.INFO)
328
+ end
329
+
330
+ --- List files with comments (in a floating window)
331
+ function M.list_comments()
332
+ local comments_dir = vim.fn.expand("~/.readit/comments")
333
+ if vim.fn.isdirectory(comments_dir) == 0 then
334
+ vim.notify("readit: no comments found", vim.log.levels.INFO)
335
+ return
336
+ end
337
+
338
+ local cmd = string.format(
339
+ "grep -rh '^source:' %s 2>/dev/null | sed 's/^source: *//' | sort -u",
340
+ vim.fn.shellescape(comments_dir)
341
+ )
342
+ local result = vim.fn.system(cmd)
343
+ local files = vim.split(vim.trim(result), "\n", { trimempty = true })
344
+
345
+ if #files == 0 then
346
+ vim.notify("readit: no comments found", vim.log.levels.INFO)
347
+ return
348
+ end
349
+
350
+ vim.ui.select(files, {
351
+ prompt = "readit: files with comments",
352
+ format_item = function(item)
353
+ return vim.fn.fnamemodify(item, ":~:.")
354
+ end,
355
+ }, function(choice)
356
+ if choice then
357
+ vim.cmd("edit " .. vim.fn.fnameescape(choice))
358
+ M.open()
359
+ end
360
+ end)
361
+ end
362
+
363
+ -- ── Commands ─────────────────────────────────────────────────────────
364
+
365
+ function M._setup_commands()
366
+ vim.api.nvim_create_user_command("ReaditOpen", function()
367
+ M.open()
368
+ end, { desc = "Open current markdown file in readit" })
369
+
370
+ vim.api.nvim_create_user_command("ReaditStop", function()
371
+ M.stop_server()
372
+ end, { desc = "Stop the readit server" })
373
+
374
+ vim.api.nvim_create_user_command("ReaditStatus", function()
375
+ vim.notify(M.server_status(), vim.log.levels.INFO)
376
+ end, { desc = "Show readit server status" })
377
+
378
+ vim.api.nvim_create_user_command("ReaditReload", function()
379
+ M.reload()
380
+ end, { desc = "Reload current document in readit" })
381
+
382
+ vim.api.nvim_create_user_command("ReaditList", function()
383
+ M.list_comments()
384
+ end, { desc = "List files with readit comments" })
385
+
386
+ vim.api.nvim_create_user_command("ReaditOpenFile", function(cmd_opts)
387
+ local file = cmd_opts.args
388
+ if file == "" then
389
+ vim.notify("readit: specify a file path", vim.log.levels.WARN)
390
+ return
391
+ end
392
+ local abs = vim.fn.fnamemodify(file, ":p")
393
+ M.start_server(abs, function(port)
394
+ if M.config.auto_open then
395
+ M._open_browser(port)
396
+ end
397
+ end)
398
+ end, {
399
+ nargs = 1,
400
+ complete = function(_, cmd_line, _)
401
+ -- Complete markdown files
402
+ local files = vim.fn.glob("**/*.md", false, true)
403
+ local markdown_files = vim.fn.glob("**/*.markdown", false, true)
404
+ vim.list_extend(files, markdown_files)
405
+ return files
406
+ end,
407
+ desc = "Open a specific file in readit",
408
+ })
409
+ end
410
+
411
+ -- ── Keymaps ──────────────────────────────────────────────────────────
412
+
413
+ function M._setup_keymaps()
414
+ local prefix = M.config.keymap_prefix
415
+ local maps = M.config.keymaps
416
+
417
+ local function map(suffix, action, desc)
418
+ if suffix == false then
419
+ return
420
+ end
421
+ vim.keymap.set("n", prefix .. suffix, action, {
422
+ desc = "readit: " .. desc,
423
+ silent = true,
424
+ })
425
+ end
426
+
427
+ if maps.open then
428
+ map(maps.open, function() M.open() end, "Open in readit")
429
+ end
430
+ if maps.open_side then
431
+ map(maps.open_side, function() M.open({ browser = true }) end, "Open in browser")
432
+ end
433
+ if maps.stop then
434
+ map(maps.stop, function() M.stop_server() end, "Stop server")
435
+ end
436
+ if maps.status then
437
+ map(maps.status, function() vim.notify(M.server_status()) end, "Server status")
438
+ end
439
+ if maps.reload then
440
+ map(maps.reload, function() M.reload() end, "Reload document")
441
+ end
442
+ if maps.list then
443
+ map(maps.list, function() M.list_comments() end, "List commented files")
444
+ end
445
+ end
446
+
447
+ -- ── Autocommands ─────────────────────────────────────────────────────
448
+
449
+ function M._setup_autocmds()
450
+ local group = vim.api.nvim_create_augroup("readit", { clear = true })
451
+
452
+ -- Clean up server on Neovim exit
453
+ vim.api.nvim_create_autocmd("VimLeavePre", {
454
+ group = group,
455
+ callback = function()
456
+ if M._server_job_id then
457
+ vim.fn.jobstop(M._server_job_id)
458
+ end
459
+ end,
460
+ })
461
+ end
462
+
463
+ return M
@@ -0,0 +1,19 @@
1
+ -- readit.nvim plugin loader
2
+ -- Auto-loaded by Neovim's plugin system
3
+
4
+ if vim.g.loaded_readit then
5
+ return
6
+ end
7
+ vim.g.loaded_readit = true
8
+
9
+ -- The plugin is configured via require("readit").setup({...}).
10
+ -- If the user hasn't called setup() (e.g. lazy.nvim with `ft`-trigger
11
+ -- and no `config`), bootstrap with defaults on the next event-loop
12
+ -- tick so any explicit setup({...}) in user config wins, but commands
13
+ -- and keymaps still register out of the box.
14
+ vim.schedule(function()
15
+ local ok, readit = pcall(require, "readit")
16
+ if ok and not readit._setup_done then
17
+ readit.setup()
18
+ end
19
+ end)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peaske7/readit",
3
- "version": "0.2.0",
3
+ "version": "0.3.0-rc.0",
4
4
  "description": "A CLI tool to review Markdown documents with inline comments",
5
5
  "author": "Jay Shimada <peaske@pm.me>",
6
6
  "license": "MIT",
@@ -17,7 +17,7 @@
17
17
  "dev": "NODE_ENV=development bun --watch src/cli.ts --",
18
18
  "dev:client": "bunx vite",
19
19
  "build": "bunx vite build && bun run build:cli",
20
- "build:cli": "NODE_ENV=production bun build src/cli.ts --outdir dist --target bun --format esm --packages external",
20
+ "build:cli": "NODE_ENV=production bun build src/cli.ts --outfile dist/index.js --target bun --format esm --packages external",
21
21
  "start": "bun run build && bun dist/index.js",
22
22
  "test": "vitest run",
23
23
  "bench": "vitest bench",
@@ -36,37 +36,29 @@
36
36
  "dependencies": {
37
37
  "clsx": "^2.1.1",
38
38
  "commander": "^14.0.3",
39
- "lucide-react": "^0.577.0",
40
- "mermaid": "^11.13.0",
39
+ "lucide-svelte": "^0.577.0",
40
+ "markdown-it": "^14.1.1",
41
+ "mermaid": "^11.14.0",
41
42
  "open": "^11.0.0",
42
- "react-markdown": "^10.1.0",
43
- "react-syntax-highlighter": "^16.1.1",
44
- "rehype-raw": "^7.0.0",
45
- "remark-gfm": "^4.0.1",
46
- "sonner": "^2.0.7",
47
- "tailwind-merge": "^3.5.0",
48
- "zod": "^4.3.6",
49
- "zustand": "^5.0.12"
43
+ "jsdom": "^28.1.0",
44
+ "shiki": "^4.0.2",
45
+ "tailwind-merge": "^3.6.0"
50
46
  },
51
47
  "devDependencies": {
52
- "@biomejs/biome": "^2.4.9",
53
- "@playwright/test": "^1.58.2",
54
- "@tailwindcss/vite": "^4.2.2",
48
+ "@biomejs/biome": "^2.4.15",
49
+ "@playwright/test": "^1.59.1",
50
+ "@sveltejs/vite-plugin-svelte": "^5.1.1",
51
+ "@tailwindcss/vite": "^4.3.0",
55
52
  "@testing-library/jest-dom": "^6.9.1",
56
- "@testing-library/react": "^16.3.2",
57
- "@types/bun": "^1.3.11",
53
+ "@types/bun": "^1.3.13",
58
54
  "@types/jsdom": "^28.0.1",
59
- "@types/react": "^19.2.14",
60
- "@types/react-dom": "^19.2.3",
61
- "@types/react-syntax-highlighter": "^15.5.13",
62
- "@vitejs/plugin-react": "^6.0.1",
63
- "jsdom": "^28.1.0",
64
- "lefthook": "^2.1.4",
65
- "react": "^19.2.4",
66
- "react-dom": "^19.2.4",
67
- "tailwindcss": "^4.2.2",
55
+ "@types/markdown-it": "^14.1.2",
56
+ "@types/node": "^25.6.2",
57
+ "lefthook": "^2.1.6",
58
+ "svelte": "^5.55.5",
59
+ "tailwindcss": "^4.3.0",
68
60
  "typescript": "^5.9.3",
69
- "vite": "^8.0.3",
70
- "vitest": "^4.1.1"
61
+ "vite": "^8.0.11",
62
+ "vitest": "^4.1.5"
71
63
  }
72
64
  }