@peaske7/readit 0.1.4 → 0.1.5

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 (320) hide show
  1. package/.agents/skills/remotion-best-practices/SKILL.md +61 -0
  2. package/.agents/skills/remotion-best-practices/rules/3d.md +86 -0
  3. package/.agents/skills/remotion-best-practices/rules/animations.md +27 -0
  4. package/.agents/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +178 -0
  5. package/.agents/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  6. package/.agents/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
  7. package/.agents/skills/remotion-best-practices/rules/assets.md +78 -0
  8. package/.agents/skills/remotion-best-practices/rules/audio-visualization.md +198 -0
  9. package/.agents/skills/remotion-best-practices/rules/audio.md +169 -0
  10. package/.agents/skills/remotion-best-practices/rules/calculate-metadata.md +134 -0
  11. package/.agents/skills/remotion-best-practices/rules/can-decode.md +75 -0
  12. package/.agents/skills/remotion-best-practices/rules/charts.md +120 -0
  13. package/.agents/skills/remotion-best-practices/rules/compositions.md +154 -0
  14. package/.agents/skills/remotion-best-practices/rules/display-captions.md +184 -0
  15. package/.agents/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  16. package/.agents/skills/remotion-best-practices/rules/ffmpeg.md +38 -0
  17. package/.agents/skills/remotion-best-practices/rules/fonts.md +152 -0
  18. package/.agents/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  19. package/.agents/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  20. package/.agents/skills/remotion-best-practices/rules/get-video-duration.md +60 -0
  21. package/.agents/skills/remotion-best-practices/rules/gifs.md +141 -0
  22. package/.agents/skills/remotion-best-practices/rules/images.md +134 -0
  23. package/.agents/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
  24. package/.agents/skills/remotion-best-practices/rules/light-leaks.md +73 -0
  25. package/.agents/skills/remotion-best-practices/rules/lottie.md +70 -0
  26. package/.agents/skills/remotion-best-practices/rules/maps.md +412 -0
  27. package/.agents/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
  28. package/.agents/skills/remotion-best-practices/rules/measuring-text.md +140 -0
  29. package/.agents/skills/remotion-best-practices/rules/parameters.md +109 -0
  30. package/.agents/skills/remotion-best-practices/rules/sequencing.md +118 -0
  31. package/.agents/skills/remotion-best-practices/rules/sfx.md +26 -0
  32. package/.agents/skills/remotion-best-practices/rules/subtitles.md +36 -0
  33. package/.agents/skills/remotion-best-practices/rules/tailwind.md +11 -0
  34. package/.agents/skills/remotion-best-practices/rules/text-animations.md +20 -0
  35. package/.agents/skills/remotion-best-practices/rules/timing.md +179 -0
  36. package/.agents/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
  37. package/.agents/skills/remotion-best-practices/rules/transitions.md +197 -0
  38. package/.agents/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
  39. package/.agents/skills/remotion-best-practices/rules/trimming.md +51 -0
  40. package/.agents/skills/remotion-best-practices/rules/videos.md +171 -0
  41. package/.agents/skills/remotion-best-practices/rules/voiceover.md +99 -0
  42. package/.agents/skills/simple/SKILL.md +52 -0
  43. package/.agents/skills/vercel-react-best-practices/AGENTS.md +3254 -0
  44. package/.agents/skills/vercel-react-best-practices/README.md +123 -0
  45. package/.agents/skills/vercel-react-best-practices/SKILL.md +141 -0
  46. package/.agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  47. package/.agents/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
  48. package/.agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
  49. package/.agents/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
  50. package/.agents/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
  51. package/.agents/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
  52. package/.agents/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
  53. package/.agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  54. package/.agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
  55. package/.agents/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
  56. package/.agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  57. package/.agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  58. package/.agents/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
  59. package/.agents/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
  60. package/.agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
  61. package/.agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
  62. package/.agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
  63. package/.agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
  64. package/.agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
  65. package/.agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
  66. package/.agents/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
  67. package/.agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
  68. package/.agents/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
  69. package/.agents/skills/vercel-react-best-practices/rules/js-flatmap-filter.md +60 -0
  70. package/.agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
  71. package/.agents/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
  72. package/.agents/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
  73. package/.agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
  74. package/.agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
  75. package/.agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  76. package/.agents/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
  77. package/.agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  78. package/.agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
  79. package/.agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
  80. package/.agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  81. package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  82. package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  83. package/.agents/skills/vercel-react-best-practices/rules/rendering-resource-hints.md +85 -0
  84. package/.agents/skills/vercel-react-best-practices/rules/rendering-script-defer-async.md +68 -0
  85. package/.agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
  86. package/.agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  87. package/.agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
  88. package/.agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
  89. package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  90. package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
  91. package/.agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  92. package/.agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  93. package/.agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  94. package/.agents/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
  95. package/.agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  96. package/.agents/skills/vercel-react-best-practices/rules/rerender-no-inline-components.md +82 -0
  97. package/.agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  98. package/.agents/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
  99. package/.agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  100. package/.agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
  101. package/.agents/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
  102. package/.agents/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
  103. package/.agents/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
  104. package/.agents/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
  105. package/.agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md +142 -0
  106. package/.agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
  107. package/.agents/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
  108. package/.claude/CLAUDE.md +142 -0
  109. package/.claude/commands/review.md +120 -0
  110. package/.claude/commands/sync-docs.md +71 -0
  111. package/.claude/roadmap.md +98 -0
  112. package/.claude/rules/style-guide.md +830 -0
  113. package/.claude/settings.json +18 -0
  114. package/.claude/user-stories.md +248 -0
  115. package/AGENTS.md +64 -0
  116. package/README.md +7 -7
  117. package/biome.json +69 -0
  118. package/bun.lock +1124 -0
  119. package/docs/design.md +563 -0
  120. package/docs/plans/2026-03-13-keyboard-shortcuts-design.md +129 -0
  121. package/docs/plans/2026-03-13-keyboard-shortcuts-plan.md +1471 -0
  122. package/docs/plans/2026-03-13-multi-document-design.md +183 -0
  123. package/docs/plans/2026-03-13-performance-benchmarks-design.md +121 -0
  124. package/e2e/comments.spec.ts +125 -0
  125. package/e2e/document-load.spec.ts +54 -0
  126. package/e2e/export.spec.ts +58 -0
  127. package/e2e/fixtures/sample.html +13 -0
  128. package/e2e/fixtures/sample.md +7 -0
  129. package/e2e/persistence-file.spec.ts +342 -0
  130. package/e2e/utils/cli.ts +84 -0
  131. package/e2e/utils/selection.ts +135 -0
  132. package/{dist/index.html → index.html} +8 -2
  133. package/lefthook.yml +8 -0
  134. package/package.json +17 -39
  135. package/playwright.config.ts +22 -0
  136. package/skills-lock.json +20 -0
  137. package/src/App.tsx +396 -0
  138. package/src/cli/index.ts +328 -0
  139. package/src/components/ActionsMenu.tsx +110 -0
  140. package/src/components/DocumentViewer/CodeBlock.tsx +83 -0
  141. package/src/components/DocumentViewer/DocumentViewer.tsx +257 -0
  142. package/src/components/DocumentViewer/IframeContainer.tsx +251 -0
  143. package/src/components/DocumentViewer/MermaidDiagram.tsx +137 -0
  144. package/src/components/DocumentViewer/index.ts +1 -0
  145. package/src/components/FloatingTOC.tsx +59 -0
  146. package/src/components/Header.tsx +63 -0
  147. package/src/components/InlineEditor.tsx +72 -0
  148. package/src/components/MarginNote.tsx +198 -0
  149. package/src/components/MarginNotes.tsx +50 -0
  150. package/src/components/RawModal.tsx +141 -0
  151. package/src/components/ReanchorConfirm.tsx +33 -0
  152. package/src/components/SettingsModal.tsx +221 -0
  153. package/src/components/ShortcutCapture.tsx +45 -0
  154. package/src/components/ShortcutList.tsx +157 -0
  155. package/src/components/TabBar.tsx +60 -0
  156. package/src/components/TableOfContents.tsx +108 -0
  157. package/src/components/comments/CommentBadge.tsx +43 -0
  158. package/src/components/comments/CommentInput.tsx +119 -0
  159. package/src/components/comments/CommentListItem.tsx +82 -0
  160. package/src/components/comments/CommentManager.tsx +106 -0
  161. package/src/components/comments/CommentMinimap.tsx +62 -0
  162. package/src/components/comments/CommentNav.tsx +104 -0
  163. package/src/components/ui/ActionBar.tsx +16 -0
  164. package/src/components/ui/ActionLink.tsx +32 -0
  165. package/src/components/ui/Button.tsx +55 -0
  166. package/src/components/ui/Dialog.tsx +156 -0
  167. package/src/components/ui/DropdownMenu.tsx +114 -0
  168. package/src/components/ui/SeparatorDot.tsx +9 -0
  169. package/src/components/ui/Text.tsx +54 -0
  170. package/src/contexts/CommentContext.tsx +222 -0
  171. package/src/contexts/LayoutContext.tsx +76 -0
  172. package/src/hooks/useClickOutside.ts +35 -0
  173. package/src/hooks/useClipboard.ts +79 -0
  174. package/src/hooks/useCommentNavigation.ts +130 -0
  175. package/src/hooks/useComments.ts +323 -0
  176. package/src/hooks/useDocument.ts +131 -0
  177. package/src/hooks/useFontPreference.ts +76 -0
  178. package/src/hooks/useHeadings.test.ts +159 -0
  179. package/src/hooks/useHeadings.ts +129 -0
  180. package/src/hooks/useKeybindings.ts +120 -0
  181. package/src/hooks/useKeyboardShortcuts.ts +63 -0
  182. package/src/hooks/useLayoutMode.ts +44 -0
  183. package/src/hooks/useReanchorMode.ts +33 -0
  184. package/src/hooks/useScrollMetrics.ts +56 -0
  185. package/src/hooks/useScrollSpy.ts +81 -0
  186. package/src/hooks/useTextSelection.ts +123 -0
  187. package/src/hooks/useThemePreference.ts +66 -0
  188. package/src/index.css +823 -0
  189. package/src/lib/__fixtures__/bench-data.ts +167 -0
  190. package/src/lib/anchor.bench.ts +112 -0
  191. package/src/lib/anchor.test.ts +531 -0
  192. package/src/lib/anchor.ts +465 -0
  193. package/src/lib/comment-storage.bench.ts +63 -0
  194. package/src/lib/comment-storage.test.ts +624 -0
  195. package/src/lib/comment-storage.ts +263 -0
  196. package/src/lib/context.bench.ts +41 -0
  197. package/src/lib/context.test.ts +224 -0
  198. package/src/lib/context.ts +193 -0
  199. package/src/lib/export.bench.ts +35 -0
  200. package/src/lib/export.ts +43 -0
  201. package/src/lib/highlight/colors.ts +37 -0
  202. package/src/lib/highlight/core.test.ts +98 -0
  203. package/src/lib/highlight/core.ts +54 -0
  204. package/src/lib/highlight/dom.ts +342 -0
  205. package/src/lib/highlight/highlighter.ts +427 -0
  206. package/src/lib/highlight/index.ts +23 -0
  207. package/src/lib/highlight/script-builder.ts +485 -0
  208. package/src/lib/highlight/types.ts +57 -0
  209. package/src/lib/html-processor.test.tsx +170 -0
  210. package/src/lib/html-processor.tsx +95 -0
  211. package/src/lib/layout-constants.ts +12 -0
  212. package/src/lib/margin-layout.bench.ts +28 -0
  213. package/src/lib/margin-layout.ts +100 -0
  214. package/src/lib/scroll.test.ts +118 -0
  215. package/src/lib/scroll.ts +47 -0
  216. package/src/lib/shortcut-registry.test.ts +173 -0
  217. package/src/lib/shortcut-registry.ts +209 -0
  218. package/src/lib/utils.test.ts +110 -0
  219. package/src/lib/utils.ts +50 -0
  220. package/src/main.tsx +10 -0
  221. package/src/server/index.ts +766 -0
  222. package/src/store/index.test.ts +220 -0
  223. package/src/store/index.ts +234 -0
  224. package/src/test-setup.ts +1 -0
  225. package/src/types/index.ts +115 -0
  226. package/test.md +74 -0
  227. package/tsconfig.cli.json +12 -0
  228. package/tsconfig.json +20 -0
  229. package/vite.config.ts +19 -0
  230. package/vitest.config.ts +15 -0
  231. package/dist/assets/_basePickBy-hOr-yGsE.js +0 -1
  232. package/dist/assets/_baseUniq-b7bzdUSn.js +0 -1
  233. package/dist/assets/arc-D65wG9gm.js +0 -1
  234. package/dist/assets/architecture-PBZL5I3N-DBa6CAv_.js +0 -1
  235. package/dist/assets/architectureDiagram-2XIMDMQ5-Djwpsh98.js +0 -36
  236. package/dist/assets/array-DOVTz2Mq.js +0 -1
  237. package/dist/assets/blockDiagram-WCTKOSBZ-BdW5TTxj.js +0 -132
  238. package/dist/assets/c4Diagram-IC4MRINW-DTmkHEXu.js +0 -10
  239. package/dist/assets/channel-B3MUFipN.js +0 -1
  240. package/dist/assets/chunk-4BX2VUAB-DEqzsvDc.js +0 -1
  241. package/dist/assets/chunk-55IACEB6-BzVuSUV8.js +0 -1
  242. package/dist/assets/chunk-7E7YKBS2-CZ8IcA4c.js +0 -1
  243. package/dist/assets/chunk-7R4GIKGN-CWVVC8HX.js +0 -79
  244. package/dist/assets/chunk-C72U2L5F-B1Tso5TH.js +0 -1
  245. package/dist/assets/chunk-EGIJ26TM-Cx_7CFik.js +0 -1
  246. package/dist/assets/chunk-FMBD7UC4-Cfk_iGhv.js +0 -15
  247. package/dist/assets/chunk-GEFDOKGD-C_5hRbJt.js +0 -2
  248. package/dist/assets/chunk-GLR3WWYH-CkY7IyBj.js +0 -2
  249. package/dist/assets/chunk-HHEYEP7N-B0I4X5cr.js +0 -1
  250. package/dist/assets/chunk-JSJVCQXG-CAjwlVLg.js +0 -1
  251. package/dist/assets/chunk-KX2RTZJC-DWqnZZ02.js +0 -1
  252. package/dist/assets/chunk-KYZI473N-gjRVhJgJ.js +0 -53
  253. package/dist/assets/chunk-L3YUKLVL-D7C9GuxL.js +0 -1
  254. package/dist/assets/chunk-MX3YWQON-i-77iuVj.js +0 -1
  255. package/dist/assets/chunk-NQ4KR5QH-B22Pvemm.js +0 -220
  256. package/dist/assets/chunk-O4XLMI2P-ZQd5L6ZD.js +0 -7
  257. package/dist/assets/chunk-OZEHJAEY-BaPKTELw.js +0 -1
  258. package/dist/assets/chunk-PQ6SQG4A-DqE1eupT.js +0 -1
  259. package/dist/assets/chunk-PU5JKC2W-BTqWqedh.js +0 -70
  260. package/dist/assets/chunk-QZHKN3VN-Nm9TvMss.js +0 -1
  261. package/dist/assets/chunk-R5LLSJPH-DkiNs1dN.js +0 -1
  262. package/dist/assets/chunk-WL4C6EOR-CioD2fv2.js +0 -189
  263. package/dist/assets/chunk-XIRO2GV7-B4GGQONY.js +0 -1
  264. package/dist/assets/chunk-XPW4576I-C0IbbQos.js +0 -32
  265. package/dist/assets/chunk-XZSTWKYB-DMOqFWmT.js +0 -94
  266. package/dist/assets/chunk-YBOYWFTD-CoeQgeVY.js +0 -1
  267. package/dist/assets/classDiagram-VBA2DB6C-DV9ltQ7h.js +0 -1
  268. package/dist/assets/classDiagram-v2-RAHNMMFH-C6nD9wmM.js +0 -1
  269. package/dist/assets/clone-DuY6BQEm.js +0 -1
  270. package/dist/assets/cose-bilkent-S5V4N54A-B6FexK6p.js +0 -1
  271. package/dist/assets/cytoscape.esm-DoTFyJaN.js +0 -321
  272. package/dist/assets/dagre-CCcocoCU.js +0 -1
  273. package/dist/assets/dagre-KLK3FWXG-DIELowj9.js +0 -4
  274. package/dist/assets/defaultLocale-Ck2Xxk-C.js +0 -1
  275. package/dist/assets/diagram-E7M64L7V-D1mm0PoO.js +0 -24
  276. package/dist/assets/diagram-IFDJBPK2-7DVjly8y.js +0 -43
  277. package/dist/assets/diagram-P4PSJMXO-jO7pfyMb.js +0 -24
  278. package/dist/assets/dist-BywRdrPx.js +0 -1
  279. package/dist/assets/erDiagram-INFDFZHY-DSRxlRFy.js +0 -70
  280. package/dist/assets/flowDiagram-PKNHOUZH-CgKzzNdR.js +0 -162
  281. package/dist/assets/ganttDiagram-A5KZAMGK-CtsE7Y4E.js +0 -292
  282. package/dist/assets/gitGraph-HDMCJU4V-BU9uhwtz.js +0 -1
  283. package/dist/assets/gitGraphDiagram-K3NZZRJ6-DOU8RGdw.js +0 -65
  284. package/dist/assets/graphlib-WkJoBgka.js +0 -1
  285. package/dist/assets/index-CKVArt9D.js +0 -562
  286. package/dist/assets/index-DzRKJazf.css +0 -2
  287. package/dist/assets/info-3K5VOQVL-CPpvM-SG.js +0 -1
  288. package/dist/assets/infoDiagram-LFFYTUFH-VKLs5DsF.js +0 -2
  289. package/dist/assets/init-Bft5Ffpj.js +0 -1
  290. package/dist/assets/isArrayLikeObject-icl0H0jo.js +0 -1
  291. package/dist/assets/isEmpty-Du8sNmkE.js +0 -1
  292. package/dist/assets/ishikawaDiagram-PHBUUO56-CsWvEjux.js +0 -70
  293. package/dist/assets/journeyDiagram-4ABVD52K-BzJGTdIT.js +0 -139
  294. package/dist/assets/kanban-definition-K7BYSVSG-B_9ClJ1A.js +0 -89
  295. package/dist/assets/katex-BJrMXEjr.js +0 -261
  296. package/dist/assets/line-CC_tDGId.js +0 -1
  297. package/dist/assets/linear-Cts_d04Y.js +0 -1
  298. package/dist/assets/math-CNhlSIO3.js +0 -1
  299. package/dist/assets/mermaid-parser.core-Vb9KKv1R.js +0 -4
  300. package/dist/assets/mermaid.core-C_7xsp3d.js +0 -11
  301. package/dist/assets/mindmap-definition-YRQLILUH-BWmfy5wB.js +0 -68
  302. package/dist/assets/ordinal-DIg8h6NI.js +0 -1
  303. package/dist/assets/packet-RMMSAZCW-Q-WG6o3b.js +0 -1
  304. package/dist/assets/path-DfRbCp9y.js +0 -1
  305. package/dist/assets/pie-UPGHQEXC-Cwi2tLlt.js +0 -1
  306. package/dist/assets/pieDiagram-SKSYHLDU-Dyf3X_in.js +0 -30
  307. package/dist/assets/quadrantDiagram-337W2JSQ-B5_5m61Q.js +0 -7
  308. package/dist/assets/radar-KQ55EAFF-Dtw2VzxY.js +0 -1
  309. package/dist/assets/requirementDiagram-Z7DCOOCP-BSERBnlW.js +0 -73
  310. package/dist/assets/rough.esm-KjoEK0it.js +0 -1
  311. package/dist/assets/sankeyDiagram-WA2Y5GQK-CMcEY8Cz.js +0 -10
  312. package/dist/assets/sequenceDiagram-2WXFIKYE-D28qcXwC.js +0 -145
  313. package/dist/assets/src-C8kkzlHX.js +0 -1
  314. package/dist/assets/stateDiagram-RAJIS63D-7oVrCmRl.js +0 -1
  315. package/dist/assets/stateDiagram-v2-FVOUBMTO-DtFptQAd.js +0 -1
  316. package/dist/assets/timeline-definition-YZTLITO2-rbCfBEvG.js +0 -61
  317. package/dist/assets/treemap-KZPCXAKY-BlRvF0um.js +0 -1
  318. package/dist/assets/vennDiagram-LZ73GAT5-DBit3zWa.js +0 -34
  319. package/dist/assets/xychartDiagram-JWTSCODW-BVYXv51y.js +0 -7
  320. package/dist/index.js +0 -1040
@@ -0,0 +1,183 @@
1
+ # Multi-Document Support Design
2
+
3
+ **Date:** 2026-03-13
4
+ **Status:** Approved
5
+
6
+ ## Overview
7
+
8
+ Evolve readit from single-document to multi-document support with tabbed navigation, full state preservation across tab switches, and Zustand for state management.
9
+
10
+ ## Motivation
11
+
12
+ readit currently opens one file per session. Reviewers often need to cross-reference multiple documents — reviewing a spec alongside its implementation notes, or scanning an entire docs directory. Multi-document support makes readit useful for real review workflows.
13
+
14
+ ## Invocation
15
+
16
+ ```bash
17
+ npx readit file1.md file2.md # Multiple files
18
+ npx readit ./docs/ # Directory (all .md and .html files)
19
+ npx readit README.md ./docs/ notes.html # Mixed
20
+ npx readit file.md # Single file (unchanged)
21
+ ```
22
+
23
+ ## Interaction Model
24
+
25
+ - **Tabbed view** — one document visible at a time, tab bar to switch
26
+ - **Full state preservation** — selection, half-typed comments, scroll position all restored on tab switch
27
+ - **Lazy loading** — document content fetched on first tab activation, not all at once
28
+ - **Split view deferred** — tabs are the foundation; split view ships later
29
+
30
+ ## Store Design (Zustand)
31
+
32
+ ### Why Zustand
33
+
34
+ "Preserve everything on tab switch" means state must survive component unmounting. With tabs, only one document renders — inactive documents' React trees are gone. React Context dies with the tree. Zustand stores state outside React, solving this naturally.
35
+
36
+ ### Shape
37
+
38
+ ```ts
39
+ interface DocumentState {
40
+ // Document data
41
+ document: Document;
42
+
43
+ // Comments
44
+ comments: Comment[];
45
+ commentsError: string | null;
46
+
47
+ // Selection & input
48
+ selection: SelectionRange | null;
49
+ pendingSelectionTop: number | undefined;
50
+ pendingCommentText: string;
51
+
52
+ // Highlight positions (DOM-derived, recomputed on mount)
53
+ highlightPositions: Record<string, number>;
54
+ documentPositions: Record<string, number>;
55
+
56
+ // Navigation
57
+ scrollY: number;
58
+ hoveredCommentId: string | undefined;
59
+
60
+ // Re-anchor
61
+ reanchorTarget: { commentId: string } | null;
62
+ }
63
+
64
+ interface AppStore {
65
+ documents: Map<string, DocumentState>;
66
+ activeDocumentPath: string | null;
67
+
68
+ // Global actions
69
+ openDocument: (doc: Document) => void;
70
+ closeDocument: (filePath: string) => void;
71
+ setActiveDocument: (filePath: string) => void;
72
+
73
+ // Per-document actions (default to active document)
74
+ addComment: (
75
+ text: string,
76
+ comment: string,
77
+ start: number,
78
+ end: number,
79
+ ) => void;
80
+ editComment: (id: string, newText: string) => void;
81
+ deleteComment: (id: string) => void;
82
+ setSelection: (selection: SelectionRange | null) => void;
83
+ setHoveredComment: (id: string | undefined) => void;
84
+ // ...
85
+ }
86
+ ```
87
+
88
+ `highlightPositions` and `documentPositions` are DOM-derived — stale when unmounted, recomputed on mount. `scrollY` is the critical value to preserve; highlight positions are ephemeral.
89
+
90
+ Per-document actions take an optional `filePath` parameter but default to `activeDocumentPath`. This keeps call sites simple while enabling future split-view.
91
+
92
+ ## Component Architecture
93
+
94
+ ```
95
+ <App> # Thin shell: store init, tab bar, active DocumentView
96
+ +- <Toaster />
97
+ +- <TabBar /> # NEW - tab strip
98
+ +- <Header /> # Simplified - reads from store, ~8 props down from 14
99
+ +- <DocumentView> # NEW - per-document layout
100
+ | +- <TableOfContents /> or <TOCPopover /> # Renamed from FloatingTOC
101
+ | +- <DocumentViewer />
102
+ | +- Margin area
103
+ | | +- <ReanchorConfirmation /> # NEW - extracted from inline JSX
104
+ | | +- <NewCommentForm /> # Renamed from CommentInputArea
105
+ | | +- <MarginNotes /> # Renamed from MarginNotesContainer
106
+ | | +- <MarginNote /> # Reads actions from store directly
107
+ | +- <CommentMinimap />
108
+ | +- <CommentNavigator />
109
+ +- <footer />
110
+ ```
111
+
112
+ ### Component changes
113
+
114
+ - **App**: God component (~520 lines) becomes thin shell
115
+ - **DocumentView** (new): Owns per-document layout. On mount: restores scroll, re-applies highlights. On unmount: saves scrollY to store
116
+ - **MarginNote**: 6 callback props eliminated — calls `useAppStore()` directly
117
+ - **Header**: 14 props reduced to ~8 — reads comment data from store
118
+ - **CommentListDropdown** (renamed from CommentManagerDropdown): Reads from store directly
119
+ - **TabBar** (new): Renders open document tabs, close buttons, active indicator
120
+
121
+ ## Server API Changes
122
+
123
+ | Endpoint | Before | After |
124
+ | --------------------- | ----------------------- | ------------------------------------------ |
125
+ | `GET /api/document` | Returns single document | Requires `?path=` query param |
126
+ | `GET /api/documents` | (new) | Returns list of open file paths + metadata |
127
+ | `POST /api/documents` | (new, deferred) | Add document at runtime (add-from-browser) |
128
+ | `GET /api/comments` | Single file scoped | Requires `?path=` query param |
129
+ | `POST /api/comments` | Single file scoped | Requires `path` in body |
130
+
131
+ `GET /api/documents` returns only metadata (path, fileName, type) — lightweight enough to populate the tab bar immediately. Content loads lazily via `GET /api/document?path=`.
132
+
133
+ Comment storage unchanged — already per-file in `~/.readit/comments/`.
134
+
135
+ ## Tab Switch Lifecycle
136
+
137
+ ### Saving (unmount)
138
+
139
+ Most state is already in the store as it changes. The only explicit "save on unmount" is `scrollY`, since scroll isn't React-controlled.
140
+
141
+ ### Restoring (mount)
142
+
143
+ 1. Read document content from store (or fetch if first visit)
144
+ 2. Highlighter applies marks, emits fresh highlightPositions
145
+ 3. `window.scrollTo(0, scrollY)` after highlights are painted
146
+ 4. If selection exists, highlight pending selection
147
+ 5. If pendingCommentText, NewCommentForm renders pre-filled
148
+ 6. If reanchorTarget, show re-anchor UI
149
+
150
+ Scroll restoration uses the existing double-rAF pattern extended with scrollTo as the final step.
151
+
152
+ ### Edge cases
153
+
154
+ | Scenario | Behavior |
155
+ | --------------------------------------------- | ---------------------------------------------------------------------------- |
156
+ | Close active tab | Activate nearest tab (right, then left). Last tab shows empty state |
157
+ | Close tab with unsaved comment text | Close immediately — ephemeral state, expected behavior |
158
+ | Document changes on disk while on another tab | SSE live-reload updates store for that path; content is fresh on switch-back |
159
+ | Same file via different paths | Deduplicate by resolved absolute path in CLI |
160
+
161
+ ## Deferrals
162
+
163
+ | Feature | Reason |
164
+ | -------------------------------------- | --------------------------------------------------- |
165
+ | Split view | Separate feature; tabs are the foundation |
166
+ | Add-from-browser | CLI + directory mode covers primary use cases first |
167
+ | Tab reordering | Polish, not essential |
168
+ | Cross-document comment search | No current demand |
169
+ | Tab persistence across server restarts | Overkill for a dev tool |
170
+ | Bulk export across documents | Per-document export works; bulk is convenience |
171
+
172
+ ## Migration Path
173
+
174
+ Incremental — store introduced alongside existing hooks, components migrate one at a time:
175
+
176
+ 1. Create store with `openDocument` / `setActiveDocument`
177
+ 2. Add `TabBar`, wrap existing App content in `DocumentView`
178
+ 3. Migrate `useComments` into store (biggest change)
179
+ 4. Migrate selection, scroll, hover state into store
180
+ 5. Remove emptied hooks, rename components
181
+ 6. Add multi-file CLI + server endpoints
182
+
183
+ Each step is independently shippable and testable.
@@ -0,0 +1,121 @@
1
+ # Performance Benchmarks Design
2
+
3
+ ## Context
4
+
5
+ readit feels slow across startup, rendering, and interactions. The main JS bundle is 748 KB (230 KB gzipped), with no code splitting. Heavy libraries (react-markdown, mermaid, react-syntax-highlighter) load upfront.
6
+
7
+ ## Goal
8
+
9
+ Establish Vitest bench benchmarks for critical server-side and library operations, with a <50ms target per operation. CI-gated to prevent regressions.
10
+
11
+ ## Scope
12
+
13
+ Benchmark the four core user flows:
14
+
15
+ 1. **Adding comments** — anchor resolution, comment serialization, file I/O
16
+ 2. **Copying/exporting comments** — export formatting, context extraction
17
+ 3. **Startup** — time-to-first-byte once server is up (exception: process spawn, browser open)
18
+ 4. **Rendering/reading** — margin layout calculations
19
+
20
+ Client-side rendering (react-markdown, syntax highlighting) is out of scope for Vitest bench — those require Playwright, which can be added later.
21
+
22
+ ## Approach: Vitest Bench
23
+
24
+ Use Vitest's built-in `bench` mode (powered by tinybench). Benchmark files are co-located with source, following the existing `*.test.ts` pattern.
25
+
26
+ ### Benchmark files
27
+
28
+ ```
29
+ src/lib/
30
+ ├── anchor.bench.ts # findAnchorWithFallback
31
+ ├── comment-storage.bench.ts # parse, serialize, computeHash
32
+ ├── margin-layout.bench.ts # position calculations
33
+ ├── export.bench.ts # export formatting
34
+ ├── context.bench.ts # LLM context extraction
35
+ └── __fixtures__/
36
+ └── bench-data.ts # shared synthetic test data
37
+ ```
38
+
39
+ ### What each benchmark measures
40
+
41
+ #### `anchor.bench.ts` — hottest path
42
+
43
+ - `findAnchorWithFallback()` with exact match (best case)
44
+ - `findAnchorWithFallback()` with fuzzy match (worst case)
45
+ - Both against a ~300-line synthetic Markdown document
46
+
47
+ #### `comment-storage.bench.ts`
48
+
49
+ - `parseCommentFile()` with 1, 10, 50 comments
50
+ - `serializeComments()` with 1, 10, 50 comments
51
+ - `computeHash()` on a ~300-line document
52
+
53
+ #### `margin-layout.bench.ts`
54
+
55
+ - Position resolution with 1, 10, 50 comments
56
+ - Overlap resolution (worst case: all comments on adjacent lines)
57
+
58
+ #### `export.bench.ts` + `context.bench.ts`
59
+
60
+ - Export to JSON format with 10, 50 comments
61
+ - Export to prompt format with 10, 50 comments
62
+ - Context extraction for a ~300-line document
63
+
64
+ ### Test fixtures (`__fixtures__/bench-data.ts`)
65
+
66
+ Shared synthetic data:
67
+
68
+ - A ~300-line Markdown document representative of real usage
69
+ - Comment sets at various sizes (1, 10, 50)
70
+ - Pre-computed anchors for deterministic benchmarks
71
+
72
+ ### Running benchmarks
73
+
74
+ ```bash
75
+ pnpm bench # vitest bench
76
+ ```
77
+
78
+ Package.json script:
79
+
80
+ ```json
81
+ "bench": "vitest bench"
82
+ ```
83
+
84
+ ### CI integration
85
+
86
+ GitHub Actions runs `pnpm bench` after tests pass. Vitest bench outputs timing statistics (min, max, mean, p99).
87
+
88
+ For hard CI gating, each benchmark file includes a companion assertion in the existing test file:
89
+
90
+ ```ts
91
+ test("anchor resolution completes under 50ms", () => {
92
+ const start = performance.now();
93
+ for (let i = 0; i < 100; i++) {
94
+ findAnchorWithFallback({
95
+ source: doc,
96
+ selectedText: text,
97
+ lineHint: "L150",
98
+ });
99
+ }
100
+ const elapsed = (performance.now() - start) / 100;
101
+ expect(elapsed).toBeLessThan(50);
102
+ });
103
+ ```
104
+
105
+ This gives both:
106
+
107
+ - **Benchmark data** (vitest bench) — for tracking performance over time
108
+ - **Hard assertions** (vitest test) — for CI gating at <50ms
109
+
110
+ ## Performance target
111
+
112
+ | Operation | Target |
113
+ | ----------------------------------------------------- | ------------------- |
114
+ | All benchmarked operations | < 50ms |
115
+ | Exceptions: process spawn, browser open, port binding | As fast as possible |
116
+
117
+ ## Future work
118
+
119
+ - Playwright benchmarks for client-side rendering (react-markdown, syntax highlighting, comment highlight positioning)
120
+ - Bundle size budgets if bundle growth becomes a concern
121
+ - `Server-Timing` headers on API routes for production observability
@@ -0,0 +1,125 @@
1
+ import { existsSync, rmSync } from "node:fs";
2
+ import * as os from "node:os";
3
+ import { join, resolve } from "node:path";
4
+ import { expect, test } from "@playwright/test";
5
+ import { spawnCli } from "./utils/cli";
6
+ import {
7
+ addComment,
8
+ selectTextInArticle,
9
+ selectTextInIframe,
10
+ } from "./utils/selection";
11
+
12
+ const FIXTURES_DIR = resolve(import.meta.dirname, "fixtures");
13
+
14
+ /**
15
+ * Get the expected comment file path for a source file.
16
+ */
17
+ function getCommentPath(sourcePath: string): string {
18
+ const absolute = resolve(sourcePath);
19
+ const normalized = absolute.replace(/^\//, "").replace(/^[A-Z]:[\\/]/, "");
20
+ const ext = normalized.lastIndexOf(".");
21
+ const withoutExt = ext > 0 ? normalized.slice(0, ext) : normalized;
22
+ return join(os.homedir(), ".readit", "comments", `${withoutExt}.comments.md`);
23
+ }
24
+
25
+ /**
26
+ * Clean up comment file for a source file.
27
+ */
28
+ function cleanupCommentFile(sourcePath: string): void {
29
+ const commentPath = getCommentPath(sourcePath);
30
+ if (existsSync(commentPath)) {
31
+ rmSync(commentPath);
32
+ }
33
+ }
34
+
35
+ test.describe("Comment Creation", () => {
36
+ const sampleMdPath = resolve(FIXTURES_DIR, "sample.md");
37
+ const sampleHtmlPath = resolve(FIXTURES_DIR, "sample.html");
38
+
39
+ test.beforeEach(() => {
40
+ // Clean up any existing comment files before each test
41
+ cleanupCommentFile(sampleMdPath);
42
+ cleanupCommentFile(sampleHtmlPath);
43
+ });
44
+
45
+ test.afterEach(() => {
46
+ // Clean up after each test
47
+ cleanupCommentFile(sampleMdPath);
48
+ cleanupCommentFile(sampleHtmlPath);
49
+ });
50
+
51
+ test("adds comment to selected text in markdown document", async ({
52
+ page,
53
+ }) => {
54
+ const { url, cleanup } = await spawnCli(sampleMdPath, { port: 4572 });
55
+
56
+ try {
57
+ await page.goto(url);
58
+
59
+ // Wait for document to load
60
+ const article = page.locator("article");
61
+ await expect(article).toBeVisible();
62
+
63
+ // Select text in the article
64
+ const textToSelect = "testing text selection";
65
+ await selectTextInArticle(page, textToSelect);
66
+
67
+ // Add a comment
68
+ const commentText = "This is my test comment";
69
+ await addComment(page, commentText);
70
+
71
+ // Verify: highlight exists with data-comment-id
72
+ const highlight = article.locator("mark[data-comment-id]").first();
73
+ await expect(highlight).toBeVisible();
74
+ await expect(highlight).toContainText(textToSelect);
75
+
76
+ // Verify: margin note shows the comment
77
+ await expect(page.locator("body")).toContainText(commentText);
78
+ } finally {
79
+ await cleanup();
80
+ }
81
+ });
82
+
83
+ test("adds comment to selected text in HTML document (iframe)", async ({
84
+ page,
85
+ }) => {
86
+ const { url, cleanup } = await spawnCli(sampleHtmlPath, { port: 4573 });
87
+
88
+ try {
89
+ await page.goto(url);
90
+
91
+ // Wait for iframe to load and its script to initialize
92
+ const iframe = page.frameLocator("iframe");
93
+ await expect(iframe.locator("body")).toBeVisible();
94
+
95
+ // Wait for iframe script to execute and send iframeReady
96
+ await page.waitForTimeout(500);
97
+
98
+ // Select text inside iframe - use test event which bypasses tree walking
99
+ // The test event directly sends offsets to the parent
100
+ const textToSelect = "testing text selection";
101
+ await selectTextInIframe(page, iframe, textToSelect);
102
+
103
+ // Verify pending highlight exists in iframe
104
+ const pendingMark = iframe.locator("mark[data-pending]");
105
+ await expect(pendingMark).toBeVisible({ timeout: 5000 });
106
+
107
+ // Add a comment (input is in parent frame)
108
+ const commentText = "Comment on HTML content";
109
+ await addComment(page, commentText);
110
+
111
+ // Wait for the comment to be saved via API and highlights to be applied
112
+ await page.waitForTimeout(500);
113
+
114
+ // Verify: highlight exists inside iframe with comment ID
115
+ const highlight = iframe.locator("mark[data-comment-id]").first();
116
+ await expect(highlight).toBeVisible();
117
+ await expect(highlight).toContainText(textToSelect);
118
+
119
+ // Verify: margin note shows the comment (in parent frame)
120
+ await expect(page.locator("body")).toContainText(commentText);
121
+ } finally {
122
+ await cleanup();
123
+ }
124
+ });
125
+ });
@@ -0,0 +1,54 @@
1
+ import { resolve } from "node:path";
2
+ import { expect, test } from "@playwright/test";
3
+ import { spawnCli } from "./utils/cli";
4
+
5
+ const FIXTURES_DIR = resolve(import.meta.dirname, "fixtures");
6
+
7
+ test.describe("Document Loading", () => {
8
+ test("loads markdown document and displays content", async ({ page }) => {
9
+ const { url, cleanup } = await spawnCli(
10
+ resolve(FIXTURES_DIR, "sample.md"),
11
+ { port: 4570 },
12
+ );
13
+
14
+ try {
15
+ await page.goto(url);
16
+
17
+ // Wait for document to load - use article scope to avoid header h1
18
+ const article = page.locator("article");
19
+ await expect(article.locator("h1")).toContainText("Test Document");
20
+
21
+ // Verify paragraph content is rendered
22
+ await expect(article).toContainText(
23
+ "This is a paragraph for testing text selection",
24
+ );
25
+
26
+ // Verify second section is visible
27
+ await expect(article.locator("h2")).toContainText("Second Section");
28
+ } finally {
29
+ await cleanup();
30
+ }
31
+ });
32
+
33
+ test("loads HTML document in iframe", async ({ page }) => {
34
+ const { url, cleanup } = await spawnCli(
35
+ resolve(FIXTURES_DIR, "sample.html"),
36
+ { port: 4571 },
37
+ );
38
+
39
+ try {
40
+ await page.goto(url);
41
+
42
+ // Wait for iframe to exist
43
+ const iframe = page.frameLocator("iframe");
44
+
45
+ // Verify content inside iframe
46
+ await expect(iframe.locator("h1")).toContainText("Test Document");
47
+ await expect(iframe.locator("p").first()).toContainText(
48
+ "This is a paragraph for testing text selection",
49
+ );
50
+ } finally {
51
+ await cleanup();
52
+ }
53
+ });
54
+ });
@@ -0,0 +1,58 @@
1
+ import { resolve } from "node:path";
2
+ import { expect, test } from "@playwright/test";
3
+ import { spawnCli } from "./utils/cli";
4
+ import { addComment, selectTextInArticle } from "./utils/selection";
5
+
6
+ const FIXTURES_DIR = resolve(import.meta.dirname, "fixtures");
7
+
8
+ test.describe("Comment Export", () => {
9
+ test("Copy All generates valid prompt format", async ({ page, context }) => {
10
+ // Grant clipboard permissions
11
+ await context.grantPermissions(["clipboard-read", "clipboard-write"]);
12
+
13
+ const { url, cleanup } = await spawnCli(
14
+ resolve(FIXTURES_DIR, "sample.md"),
15
+ { port: 4590 },
16
+ );
17
+
18
+ try {
19
+ await page.goto(url);
20
+
21
+ // Wait for document to load
22
+ const article = page.locator("article");
23
+ await expect(article).toBeVisible();
24
+
25
+ // Add a comment
26
+ const textToSelect = "testing text selection";
27
+ const commentText = "This is my review comment";
28
+ await selectTextInArticle(page, textToSelect);
29
+ await addComment(page, commentText);
30
+
31
+ // Verify comment was added
32
+ await expect(page.locator("body")).toContainText(commentText);
33
+
34
+ // Open the actions menu and click "Copy All"
35
+ const menuButton = page.getByRole("button", { name: /actions menu/i });
36
+ await menuButton.click();
37
+
38
+ const copyButton = page.getByRole("menuitem", { name: /copy all/i });
39
+ await copyButton.click();
40
+
41
+ // Read clipboard content
42
+ const clipboardContent = await page.evaluate(() =>
43
+ navigator.clipboard.readText(),
44
+ );
45
+
46
+ // Verify the format contains expected parts
47
+ expect(clipboardContent).toContain("# Review Comments for sample.md");
48
+ expect(clipboardContent).toContain(textToSelect);
49
+ expect(clipboardContent).toContain(commentText);
50
+
51
+ // Verify it follows the prompt format (selected text + comment structure)
52
+ expect(clipboardContent).toContain("Selected text:");
53
+ expect(clipboardContent).toContain("Comment:");
54
+ } finally {
55
+ await cleanup();
56
+ }
57
+ });
58
+ });
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Test Document</title>
6
+ </head>
7
+ <body>
8
+ <h1>Test Document</h1>
9
+ <p>This is a paragraph for testing text selection.</p>
10
+ <h2>Second Section</h2>
11
+ <p>Here is another paragraph with some more text to select and comment on.</p>
12
+ </body>
13
+ </html>
@@ -0,0 +1,7 @@
1
+ # Test Document
2
+
3
+ This is a paragraph for testing text selection.
4
+
5
+ ## Second Section
6
+
7
+ Here is another paragraph with some more text to select and comment on.