@peaske7/readit 0.1.4 → 0.1.6

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 (322) 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-client-mode-design.md +86 -0
  121. package/docs/plans/2026-03-13-client-mode-plan.md +605 -0
  122. package/docs/plans/2026-03-13-keyboard-shortcuts-design.md +129 -0
  123. package/docs/plans/2026-03-13-keyboard-shortcuts-plan.md +1471 -0
  124. package/docs/plans/2026-03-13-multi-document-design.md +183 -0
  125. package/docs/plans/2026-03-13-performance-benchmarks-design.md +121 -0
  126. package/e2e/comments.spec.ts +125 -0
  127. package/e2e/document-load.spec.ts +54 -0
  128. package/e2e/export.spec.ts +58 -0
  129. package/e2e/fixtures/sample.html +13 -0
  130. package/e2e/fixtures/sample.md +7 -0
  131. package/e2e/persistence-file.spec.ts +342 -0
  132. package/e2e/utils/cli.ts +84 -0
  133. package/e2e/utils/selection.ts +135 -0
  134. package/{dist/index.html → index.html} +8 -2
  135. package/lefthook.yml +8 -0
  136. package/package.json +17 -39
  137. package/playwright.config.ts +22 -0
  138. package/skills-lock.json +20 -0
  139. package/src/App.tsx +396 -0
  140. package/src/cli/index.ts +467 -0
  141. package/src/components/ActionsMenu.tsx +110 -0
  142. package/src/components/DocumentViewer/CodeBlock.tsx +83 -0
  143. package/src/components/DocumentViewer/DocumentViewer.tsx +257 -0
  144. package/src/components/DocumentViewer/IframeContainer.tsx +251 -0
  145. package/src/components/DocumentViewer/MermaidDiagram.tsx +137 -0
  146. package/src/components/DocumentViewer/index.ts +1 -0
  147. package/src/components/FloatingTOC.tsx +59 -0
  148. package/src/components/Header.tsx +63 -0
  149. package/src/components/InlineEditor.tsx +72 -0
  150. package/src/components/MarginNote.tsx +198 -0
  151. package/src/components/MarginNotes.tsx +50 -0
  152. package/src/components/RawModal.tsx +141 -0
  153. package/src/components/ReanchorConfirm.tsx +33 -0
  154. package/src/components/SettingsModal.tsx +221 -0
  155. package/src/components/ShortcutCapture.tsx +45 -0
  156. package/src/components/ShortcutList.tsx +157 -0
  157. package/src/components/TabBar.tsx +60 -0
  158. package/src/components/TableOfContents.tsx +108 -0
  159. package/src/components/comments/CommentBadge.tsx +43 -0
  160. package/src/components/comments/CommentInput.tsx +119 -0
  161. package/src/components/comments/CommentListItem.tsx +82 -0
  162. package/src/components/comments/CommentManager.tsx +106 -0
  163. package/src/components/comments/CommentMinimap.tsx +62 -0
  164. package/src/components/comments/CommentNav.tsx +104 -0
  165. package/src/components/ui/ActionBar.tsx +16 -0
  166. package/src/components/ui/ActionLink.tsx +32 -0
  167. package/src/components/ui/Button.tsx +55 -0
  168. package/src/components/ui/Dialog.tsx +156 -0
  169. package/src/components/ui/DropdownMenu.tsx +114 -0
  170. package/src/components/ui/SeparatorDot.tsx +9 -0
  171. package/src/components/ui/Text.tsx +54 -0
  172. package/src/contexts/CommentContext.tsx +222 -0
  173. package/src/contexts/LayoutContext.tsx +76 -0
  174. package/src/hooks/useClickOutside.ts +35 -0
  175. package/src/hooks/useClipboard.ts +79 -0
  176. package/src/hooks/useCommentNavigation.ts +130 -0
  177. package/src/hooks/useComments.ts +323 -0
  178. package/src/hooks/useDocument.ts +141 -0
  179. package/src/hooks/useFontPreference.ts +76 -0
  180. package/src/hooks/useHeadings.test.ts +159 -0
  181. package/src/hooks/useHeadings.ts +129 -0
  182. package/src/hooks/useKeybindings.ts +120 -0
  183. package/src/hooks/useKeyboardShortcuts.ts +63 -0
  184. package/src/hooks/useLayoutMode.ts +44 -0
  185. package/src/hooks/useReanchorMode.ts +33 -0
  186. package/src/hooks/useScrollMetrics.ts +56 -0
  187. package/src/hooks/useScrollSpy.ts +81 -0
  188. package/src/hooks/useTextSelection.ts +123 -0
  189. package/src/hooks/useThemePreference.ts +66 -0
  190. package/src/index.css +823 -0
  191. package/src/lib/__fixtures__/bench-data.ts +167 -0
  192. package/src/lib/anchor.bench.ts +112 -0
  193. package/src/lib/anchor.test.ts +531 -0
  194. package/src/lib/anchor.ts +465 -0
  195. package/src/lib/comment-storage.bench.ts +63 -0
  196. package/src/lib/comment-storage.test.ts +624 -0
  197. package/src/lib/comment-storage.ts +263 -0
  198. package/src/lib/context.bench.ts +41 -0
  199. package/src/lib/context.test.ts +224 -0
  200. package/src/lib/context.ts +193 -0
  201. package/src/lib/export.bench.ts +35 -0
  202. package/src/lib/export.ts +43 -0
  203. package/src/lib/highlight/colors.ts +37 -0
  204. package/src/lib/highlight/core.test.ts +98 -0
  205. package/src/lib/highlight/core.ts +54 -0
  206. package/src/lib/highlight/dom.ts +342 -0
  207. package/src/lib/highlight/highlighter.ts +427 -0
  208. package/src/lib/highlight/index.ts +23 -0
  209. package/src/lib/highlight/script-builder.ts +485 -0
  210. package/src/lib/highlight/types.ts +57 -0
  211. package/src/lib/html-processor.test.tsx +170 -0
  212. package/src/lib/html-processor.tsx +95 -0
  213. package/src/lib/layout-constants.ts +12 -0
  214. package/src/lib/margin-layout.bench.ts +28 -0
  215. package/src/lib/margin-layout.ts +100 -0
  216. package/src/lib/scroll.test.ts +118 -0
  217. package/src/lib/scroll.ts +47 -0
  218. package/src/lib/shortcut-registry.test.ts +173 -0
  219. package/src/lib/shortcut-registry.ts +209 -0
  220. package/src/lib/utils.test.ts +110 -0
  221. package/src/lib/utils.ts +61 -0
  222. package/src/main.tsx +10 -0
  223. package/src/server/index.ts +883 -0
  224. package/src/store/index.test.ts +220 -0
  225. package/src/store/index.ts +234 -0
  226. package/src/test-setup.ts +1 -0
  227. package/src/types/index.ts +115 -0
  228. package/test.md +74 -0
  229. package/tsconfig.cli.json +12 -0
  230. package/tsconfig.json +20 -0
  231. package/vite.config.ts +19 -0
  232. package/vitest.config.ts +15 -0
  233. package/dist/assets/_basePickBy-hOr-yGsE.js +0 -1
  234. package/dist/assets/_baseUniq-b7bzdUSn.js +0 -1
  235. package/dist/assets/arc-D65wG9gm.js +0 -1
  236. package/dist/assets/architecture-PBZL5I3N-DBa6CAv_.js +0 -1
  237. package/dist/assets/architectureDiagram-2XIMDMQ5-Djwpsh98.js +0 -36
  238. package/dist/assets/array-DOVTz2Mq.js +0 -1
  239. package/dist/assets/blockDiagram-WCTKOSBZ-BdW5TTxj.js +0 -132
  240. package/dist/assets/c4Diagram-IC4MRINW-DTmkHEXu.js +0 -10
  241. package/dist/assets/channel-B3MUFipN.js +0 -1
  242. package/dist/assets/chunk-4BX2VUAB-DEqzsvDc.js +0 -1
  243. package/dist/assets/chunk-55IACEB6-BzVuSUV8.js +0 -1
  244. package/dist/assets/chunk-7E7YKBS2-CZ8IcA4c.js +0 -1
  245. package/dist/assets/chunk-7R4GIKGN-CWVVC8HX.js +0 -79
  246. package/dist/assets/chunk-C72U2L5F-B1Tso5TH.js +0 -1
  247. package/dist/assets/chunk-EGIJ26TM-Cx_7CFik.js +0 -1
  248. package/dist/assets/chunk-FMBD7UC4-Cfk_iGhv.js +0 -15
  249. package/dist/assets/chunk-GEFDOKGD-C_5hRbJt.js +0 -2
  250. package/dist/assets/chunk-GLR3WWYH-CkY7IyBj.js +0 -2
  251. package/dist/assets/chunk-HHEYEP7N-B0I4X5cr.js +0 -1
  252. package/dist/assets/chunk-JSJVCQXG-CAjwlVLg.js +0 -1
  253. package/dist/assets/chunk-KX2RTZJC-DWqnZZ02.js +0 -1
  254. package/dist/assets/chunk-KYZI473N-gjRVhJgJ.js +0 -53
  255. package/dist/assets/chunk-L3YUKLVL-D7C9GuxL.js +0 -1
  256. package/dist/assets/chunk-MX3YWQON-i-77iuVj.js +0 -1
  257. package/dist/assets/chunk-NQ4KR5QH-B22Pvemm.js +0 -220
  258. package/dist/assets/chunk-O4XLMI2P-ZQd5L6ZD.js +0 -7
  259. package/dist/assets/chunk-OZEHJAEY-BaPKTELw.js +0 -1
  260. package/dist/assets/chunk-PQ6SQG4A-DqE1eupT.js +0 -1
  261. package/dist/assets/chunk-PU5JKC2W-BTqWqedh.js +0 -70
  262. package/dist/assets/chunk-QZHKN3VN-Nm9TvMss.js +0 -1
  263. package/dist/assets/chunk-R5LLSJPH-DkiNs1dN.js +0 -1
  264. package/dist/assets/chunk-WL4C6EOR-CioD2fv2.js +0 -189
  265. package/dist/assets/chunk-XIRO2GV7-B4GGQONY.js +0 -1
  266. package/dist/assets/chunk-XPW4576I-C0IbbQos.js +0 -32
  267. package/dist/assets/chunk-XZSTWKYB-DMOqFWmT.js +0 -94
  268. package/dist/assets/chunk-YBOYWFTD-CoeQgeVY.js +0 -1
  269. package/dist/assets/classDiagram-VBA2DB6C-DV9ltQ7h.js +0 -1
  270. package/dist/assets/classDiagram-v2-RAHNMMFH-C6nD9wmM.js +0 -1
  271. package/dist/assets/clone-DuY6BQEm.js +0 -1
  272. package/dist/assets/cose-bilkent-S5V4N54A-B6FexK6p.js +0 -1
  273. package/dist/assets/cytoscape.esm-DoTFyJaN.js +0 -321
  274. package/dist/assets/dagre-CCcocoCU.js +0 -1
  275. package/dist/assets/dagre-KLK3FWXG-DIELowj9.js +0 -4
  276. package/dist/assets/defaultLocale-Ck2Xxk-C.js +0 -1
  277. package/dist/assets/diagram-E7M64L7V-D1mm0PoO.js +0 -24
  278. package/dist/assets/diagram-IFDJBPK2-7DVjly8y.js +0 -43
  279. package/dist/assets/diagram-P4PSJMXO-jO7pfyMb.js +0 -24
  280. package/dist/assets/dist-BywRdrPx.js +0 -1
  281. package/dist/assets/erDiagram-INFDFZHY-DSRxlRFy.js +0 -70
  282. package/dist/assets/flowDiagram-PKNHOUZH-CgKzzNdR.js +0 -162
  283. package/dist/assets/ganttDiagram-A5KZAMGK-CtsE7Y4E.js +0 -292
  284. package/dist/assets/gitGraph-HDMCJU4V-BU9uhwtz.js +0 -1
  285. package/dist/assets/gitGraphDiagram-K3NZZRJ6-DOU8RGdw.js +0 -65
  286. package/dist/assets/graphlib-WkJoBgka.js +0 -1
  287. package/dist/assets/index-CKVArt9D.js +0 -562
  288. package/dist/assets/index-DzRKJazf.css +0 -2
  289. package/dist/assets/info-3K5VOQVL-CPpvM-SG.js +0 -1
  290. package/dist/assets/infoDiagram-LFFYTUFH-VKLs5DsF.js +0 -2
  291. package/dist/assets/init-Bft5Ffpj.js +0 -1
  292. package/dist/assets/isArrayLikeObject-icl0H0jo.js +0 -1
  293. package/dist/assets/isEmpty-Du8sNmkE.js +0 -1
  294. package/dist/assets/ishikawaDiagram-PHBUUO56-CsWvEjux.js +0 -70
  295. package/dist/assets/journeyDiagram-4ABVD52K-BzJGTdIT.js +0 -139
  296. package/dist/assets/kanban-definition-K7BYSVSG-B_9ClJ1A.js +0 -89
  297. package/dist/assets/katex-BJrMXEjr.js +0 -261
  298. package/dist/assets/line-CC_tDGId.js +0 -1
  299. package/dist/assets/linear-Cts_d04Y.js +0 -1
  300. package/dist/assets/math-CNhlSIO3.js +0 -1
  301. package/dist/assets/mermaid-parser.core-Vb9KKv1R.js +0 -4
  302. package/dist/assets/mermaid.core-C_7xsp3d.js +0 -11
  303. package/dist/assets/mindmap-definition-YRQLILUH-BWmfy5wB.js +0 -68
  304. package/dist/assets/ordinal-DIg8h6NI.js +0 -1
  305. package/dist/assets/packet-RMMSAZCW-Q-WG6o3b.js +0 -1
  306. package/dist/assets/path-DfRbCp9y.js +0 -1
  307. package/dist/assets/pie-UPGHQEXC-Cwi2tLlt.js +0 -1
  308. package/dist/assets/pieDiagram-SKSYHLDU-Dyf3X_in.js +0 -30
  309. package/dist/assets/quadrantDiagram-337W2JSQ-B5_5m61Q.js +0 -7
  310. package/dist/assets/radar-KQ55EAFF-Dtw2VzxY.js +0 -1
  311. package/dist/assets/requirementDiagram-Z7DCOOCP-BSERBnlW.js +0 -73
  312. package/dist/assets/rough.esm-KjoEK0it.js +0 -1
  313. package/dist/assets/sankeyDiagram-WA2Y5GQK-CMcEY8Cz.js +0 -10
  314. package/dist/assets/sequenceDiagram-2WXFIKYE-D28qcXwC.js +0 -145
  315. package/dist/assets/src-C8kkzlHX.js +0 -1
  316. package/dist/assets/stateDiagram-RAJIS63D-7oVrCmRl.js +0 -1
  317. package/dist/assets/stateDiagram-v2-FVOUBMTO-DtFptQAd.js +0 -1
  318. package/dist/assets/timeline-definition-YZTLITO2-rbCfBEvG.js +0 -61
  319. package/dist/assets/treemap-KZPCXAKY-BlRvF0um.js +0 -1
  320. package/dist/assets/vennDiagram-LZ73GAT5-DBit3zWa.js +0 -34
  321. package/dist/assets/xychartDiagram-JWTSCODW-BVYXv51y.js +0 -7
  322. package/dist/index.js +0 -1040
@@ -0,0 +1,624 @@
1
+ import type * as os from "node:os";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import type { CommentFile } from "../types";
4
+ import { COMMENT_FILE_LARGE } from "./__fixtures__/bench-data";
5
+ import {
6
+ computeHash,
7
+ createComment,
8
+ getCommentPath,
9
+ getLineHint,
10
+ getLineNumber,
11
+ parseCommentFile,
12
+ serializeComments,
13
+ truncateSelection,
14
+ } from "./comment-storage";
15
+
16
+ // Mock os.homedir for consistent test results
17
+ vi.mock("node:os", async () => {
18
+ const actual = await vi.importActual<typeof os>("node:os");
19
+ return {
20
+ ...actual,
21
+ homedir: vi.fn(() => "/home/testuser"),
22
+ };
23
+ });
24
+
25
+ describe("getCommentPath", () => {
26
+ it("handles absolute path", () => {
27
+ const result = getCommentPath("/home/user/doc.md");
28
+ expect(result).toBe(
29
+ "/home/testuser/.readit/comments/home/user/doc.comments.md",
30
+ );
31
+ });
32
+
33
+ it("handles deep nesting", () => {
34
+ const result = getCommentPath("/a/b/c/d/e/f.md");
35
+ expect(result).toBe(
36
+ "/home/testuser/.readit/comments/a/b/c/d/e/f.comments.md",
37
+ );
38
+ });
39
+
40
+ it("handles HTML files", () => {
41
+ const result = getCommentPath("/docs/api.html");
42
+ expect(result).toBe("/home/testuser/.readit/comments/docs/api.comments.md");
43
+ });
44
+
45
+ it("handles root path", () => {
46
+ const result = getCommentPath("/file.md");
47
+ expect(result).toBe("/home/testuser/.readit/comments/file.comments.md");
48
+ });
49
+
50
+ it("handles files with spaces in path", () => {
51
+ const result = getCommentPath("/path/to/my file.md");
52
+ expect(result).toBe(
53
+ "/home/testuser/.readit/comments/path/to/my file.comments.md",
54
+ );
55
+ });
56
+ });
57
+
58
+ describe("computeHash", () => {
59
+ it("returns 16 character hash", () => {
60
+ const hash = computeHash("test content");
61
+ expect(hash).toHaveLength(16);
62
+ });
63
+
64
+ it("returns consistent hash for same content", () => {
65
+ const hash1 = computeHash("hello world");
66
+ const hash2 = computeHash("hello world");
67
+ expect(hash1).toBe(hash2);
68
+ });
69
+
70
+ it("returns different hash for different content", () => {
71
+ const hash1 = computeHash("hello");
72
+ const hash2 = computeHash("world");
73
+ expect(hash1).not.toBe(hash2);
74
+ });
75
+
76
+ it("handles empty string", () => {
77
+ const hash = computeHash("");
78
+ expect(hash).toHaveLength(16);
79
+ });
80
+
81
+ it("handles unicode content", () => {
82
+ const hash = computeHash("こんにちは");
83
+ expect(hash).toHaveLength(16);
84
+ });
85
+ });
86
+
87
+ describe("getLineNumber", () => {
88
+ it("returns 1 for first line", () => {
89
+ expect(getLineNumber("abc\ndef", 0)).toBe(1);
90
+ expect(getLineNumber("abc\ndef", 2)).toBe(1);
91
+ });
92
+
93
+ it("returns 2 for second line", () => {
94
+ expect(getLineNumber("abc\ndef", 4)).toBe(2);
95
+ });
96
+
97
+ it("returns 1 for end of first line", () => {
98
+ expect(getLineNumber("abc\ndef", 3)).toBe(1);
99
+ });
100
+
101
+ it("returns 1 for empty content", () => {
102
+ expect(getLineNumber("", 0)).toBe(1);
103
+ });
104
+
105
+ it("handles many lines", () => {
106
+ // "line\n" repeated 100 times, each segment is 5 chars
107
+ const content = Array(100).fill("line").join("\n");
108
+ expect(getLineNumber(content, 0)).toBe(1); // Start of line 1
109
+ expect(getLineNumber(content, 250)).toBe(51); // After 50 newlines = line 51
110
+ expect(getLineNumber(content, 495)).toBe(100); // Start of line 100
111
+ });
112
+
113
+ it("handles offset beyond content length", () => {
114
+ const line = getLineNumber("abc\ndef", 1000);
115
+ expect(line).toBe(2);
116
+ });
117
+ });
118
+
119
+ describe("getLineHint", () => {
120
+ it("returns single line hint for same line", () => {
121
+ const content = "line one\nline two\nline three";
122
+ expect(getLineHint(content, 0, 4)).toBe("L1");
123
+ });
124
+
125
+ it("returns range hint for multiple lines", () => {
126
+ const content = "line one\nline two\nline three";
127
+ expect(getLineHint(content, 0, 20)).toBe("L1-3");
128
+ });
129
+ });
130
+
131
+ describe("parseCommentFile", () => {
132
+ it("returns empty result for empty file", () => {
133
+ const result = parseCommentFile("");
134
+ expect(result.comments).toEqual([]);
135
+ expect(result.source).toBe("");
136
+ expect(result.hash).toBe("");
137
+ });
138
+
139
+ it("parses front matter only", () => {
140
+ const content = `---
141
+ source: /path/to/doc.md
142
+ hash: abc123def456
143
+ version: 1
144
+ ---`;
145
+ const result = parseCommentFile(content);
146
+ expect(result.source).toBe("/path/to/doc.md");
147
+ expect(result.hash).toBe("abc123def456");
148
+ expect(result.version).toBe(1);
149
+ expect(result.comments).toEqual([]);
150
+ });
151
+
152
+ it("parses single comment", () => {
153
+ const content = `---
154
+ source: /path/to/doc.md
155
+ hash: abc123def456
156
+ version: 1
157
+ ---
158
+
159
+ <!-- c:12345678|L42|2024-12-24T10:30:00Z -->
160
+ > selected text here
161
+
162
+ This is my comment.
163
+
164
+ ---
165
+ `;
166
+ const result = parseCommentFile(content);
167
+ expect(result.comments).toHaveLength(1);
168
+ expect(result.comments[0].id).toBe("12345678");
169
+ expect(result.comments[0].selectedText).toBe("selected text here");
170
+ expect(result.comments[0].comment).toBe("This is my comment.");
171
+ expect(result.comments[0].lineHint).toBe("L42");
172
+ expect(result.comments[0].createdAt).toBe("2024-12-24T10:30:00Z");
173
+ });
174
+
175
+ it("parses multiple comments", () => {
176
+ const content = `---
177
+ source: /path/to/doc.md
178
+ hash: abc123def456
179
+ version: 1
180
+ ---
181
+
182
+ <!-- c:11111111|L10|2024-12-24T10:00:00Z -->
183
+ > first selection
184
+
185
+ First comment.
186
+
187
+ ---
188
+
189
+ <!-- c:22222222|L20|2024-12-24T11:00:00Z -->
190
+ > second selection
191
+
192
+ Second comment.
193
+
194
+ ---
195
+
196
+ <!-- c:33333333|L30|2024-12-24T12:00:00Z -->
197
+ > third selection
198
+
199
+ Third comment.
200
+
201
+ ---
202
+ `;
203
+ const result = parseCommentFile(content);
204
+ expect(result.comments).toHaveLength(3);
205
+ expect(result.comments[0].id).toBe("11111111");
206
+ expect(result.comments[1].id).toBe("22222222");
207
+ expect(result.comments[2].id).toBe("33333333");
208
+ });
209
+
210
+ it("parses multiline blockquote", () => {
211
+ const content = `---
212
+ source: /test.md
213
+ hash: abc
214
+ version: 1
215
+ ---
216
+
217
+ <!-- c:12345678|L42|2024-12-24T10:30:00Z -->
218
+ > line one
219
+ > line two
220
+ > line three
221
+
222
+ Comment here.
223
+
224
+ ---
225
+ `;
226
+ const result = parseCommentFile(content);
227
+ expect(result.comments[0].selectedText).toBe(
228
+ "line one\nline two\nline three",
229
+ );
230
+ });
231
+
232
+ it("skips blocks without metadata", () => {
233
+ const content = `---
234
+ source: /test.md
235
+ hash: abc
236
+ version: 1
237
+ ---
238
+
239
+ > Just a blockquote without metadata
240
+
241
+ Some text.
242
+
243
+ ---
244
+
245
+ <!-- c:12345678|L42|2024-12-24T10:30:00Z -->
246
+ > valid selection
247
+
248
+ Valid comment.
249
+
250
+ ---
251
+ `;
252
+ const result = parseCommentFile(content);
253
+ expect(result.comments).toHaveLength(1);
254
+ expect(result.comments[0].id).toBe("12345678");
255
+ });
256
+
257
+ it("handles malformed metadata gracefully", () => {
258
+ const content = `---
259
+ source: /test.md
260
+ hash: abc
261
+ version: 1
262
+ ---
263
+
264
+ <!-- c:bad-format -->
265
+ > some text
266
+
267
+ Comment.
268
+
269
+ ---
270
+ `;
271
+ const result = parseCommentFile(content);
272
+ expect(result.comments).toHaveLength(0);
273
+ });
274
+
275
+ it("parses anchorPrefix when present", () => {
276
+ const content = `---
277
+ source: /test.md
278
+ hash: abc
279
+ version: 1
280
+ ---
281
+
282
+ <!-- c:12345678|L42|2024-12-24T10:30:00Z -->
283
+ <!-- anchor:first 200 chars of original text -->
284
+ > truncated display text...
285
+
286
+ Comment here.
287
+
288
+ ---
289
+ `;
290
+ const result = parseCommentFile(content);
291
+ expect(result.comments).toHaveLength(1);
292
+ expect(result.comments[0].anchorPrefix).toBe(
293
+ "first 200 chars of original text",
294
+ );
295
+ });
296
+
297
+ it("parses comment without anchorPrefix", () => {
298
+ const content = `---
299
+ source: /test.md
300
+ hash: abc
301
+ version: 1
302
+ ---
303
+
304
+ <!-- c:12345678|L42|2024-12-24T10:30:00Z -->
305
+ > short selected text
306
+
307
+ Comment here.
308
+
309
+ ---
310
+ `;
311
+ const result = parseCommentFile(content);
312
+ expect(result.comments).toHaveLength(1);
313
+ expect(result.comments[0].anchorPrefix).toBeUndefined();
314
+ });
315
+ });
316
+
317
+ describe("serializeComments", () => {
318
+ it("serializes empty array to front matter only", () => {
319
+ const file: CommentFile = {
320
+ source: "/path/to/doc.md",
321
+ hash: "abc123def456",
322
+ version: 1,
323
+ comments: [],
324
+ };
325
+ const result = serializeComments(file);
326
+ expect(result).toContain("source: /path/to/doc.md");
327
+ expect(result).toContain("hash: abc123def456");
328
+ expect(result).toContain("version: 1");
329
+ expect(result).not.toContain("<!-- c:");
330
+ });
331
+
332
+ it("serializes single comment", () => {
333
+ const file: CommentFile = {
334
+ source: "/test.md",
335
+ hash: "abc",
336
+ version: 1,
337
+ comments: [
338
+ {
339
+ id: "12345678",
340
+ selectedText: "selected text",
341
+ comment: "My comment",
342
+ createdAt: "2024-12-24T10:30:00Z",
343
+ lineHint: "L42",
344
+ startOffset: 100,
345
+ endOffset: 113,
346
+ },
347
+ ],
348
+ };
349
+ const result = serializeComments(file);
350
+ expect(result).toContain("<!-- c:12345678|L42|2024-12-24T10:30:00Z -->");
351
+ expect(result).toContain("> selected text");
352
+ expect(result).toContain("My comment");
353
+ });
354
+
355
+ it("serializes multiline selected text", () => {
356
+ const file: CommentFile = {
357
+ source: "/test.md",
358
+ hash: "abc",
359
+ version: 1,
360
+ comments: [
361
+ {
362
+ id: "12345678",
363
+ selectedText: "line one\nline two",
364
+ comment: "Comment",
365
+ createdAt: "2024-12-24T10:30:00Z",
366
+ lineHint: "L42-43",
367
+ startOffset: 100,
368
+ endOffset: 120,
369
+ },
370
+ ],
371
+ };
372
+ const result = serializeComments(file);
373
+ expect(result).toContain("> line one");
374
+ expect(result).toContain("> line two");
375
+ });
376
+
377
+ it("serializes anchorPrefix when present", () => {
378
+ const file: CommentFile = {
379
+ source: "/test.md",
380
+ hash: "abc",
381
+ version: 1,
382
+ comments: [
383
+ {
384
+ id: "12345678",
385
+ selectedText: "truncated text...",
386
+ comment: "Comment",
387
+ createdAt: "2024-12-24T10:30:00Z",
388
+ lineHint: "L42",
389
+ startOffset: 100,
390
+ endOffset: 500,
391
+ anchorPrefix: "first 200 chars of original text",
392
+ },
393
+ ],
394
+ };
395
+ const result = serializeComments(file);
396
+ expect(result).toContain(
397
+ "<!-- anchor:first 200 chars of original text -->",
398
+ );
399
+ });
400
+
401
+ it("does not serialize anchor when anchorPrefix is undefined", () => {
402
+ const file: CommentFile = {
403
+ source: "/test.md",
404
+ hash: "abc",
405
+ version: 1,
406
+ comments: [
407
+ {
408
+ id: "12345678",
409
+ selectedText: "short text",
410
+ comment: "Comment",
411
+ createdAt: "2024-12-24T10:30:00Z",
412
+ lineHint: "L42",
413
+ startOffset: 100,
414
+ endOffset: 110,
415
+ },
416
+ ],
417
+ };
418
+ const result = serializeComments(file);
419
+ expect(result).not.toContain("<!-- anchor:");
420
+ });
421
+
422
+ it("roundtrip: parse(serialize(file)) equals original", () => {
423
+ const original: CommentFile = {
424
+ source: "/test.md",
425
+ hash: "abc123def456",
426
+ version: 1,
427
+ comments: [
428
+ {
429
+ id: "12345678",
430
+ selectedText: "selected text",
431
+ comment: "My comment",
432
+ createdAt: "2024-12-24T10:30:00Z",
433
+ lineHint: "L42",
434
+ startOffset: 100,
435
+ endOffset: 113,
436
+ },
437
+ {
438
+ id: "87654321",
439
+ selectedText: "another\nmultiline\nselection",
440
+ comment: "Another comment with\n\nmultiple paragraphs.",
441
+ createdAt: "2024-12-24T11:00:00Z",
442
+ lineHint: "L50-52",
443
+ startOffset: 200,
444
+ endOffset: 230,
445
+ },
446
+ ],
447
+ };
448
+
449
+ const serialized = serializeComments(original);
450
+ const parsed = parseCommentFile(serialized);
451
+
452
+ expect(parsed.source).toBe(original.source);
453
+ expect(parsed.hash).toBe(original.hash);
454
+ expect(parsed.version).toBe(original.version);
455
+ expect(parsed.comments).toHaveLength(original.comments.length);
456
+
457
+ for (let i = 0; i < original.comments.length; i++) {
458
+ expect(parsed.comments[i].id).toBe(original.comments[i].id);
459
+ expect(parsed.comments[i].selectedText).toBe(
460
+ original.comments[i].selectedText,
461
+ );
462
+ expect(parsed.comments[i].lineHint).toBe(original.comments[i].lineHint);
463
+ expect(parsed.comments[i].createdAt).toBe(original.comments[i].createdAt);
464
+ }
465
+ });
466
+
467
+ it("roundtrip: preserves anchorPrefix through serialize/parse", () => {
468
+ const original: CommentFile = {
469
+ source: "/test.md",
470
+ hash: "abc123",
471
+ version: 1,
472
+ comments: [
473
+ {
474
+ id: "12345678",
475
+ selectedText: "truncated text\n...\nend of text",
476
+ comment: "Comment on long selection",
477
+ createdAt: "2024-12-24T10:30:00Z",
478
+ lineHint: "L42",
479
+ startOffset: 100,
480
+ endOffset: 2000,
481
+ anchorPrefix: "first 200 chars of the original long text",
482
+ },
483
+ ],
484
+ };
485
+
486
+ const serialized = serializeComments(original);
487
+ const parsed = parseCommentFile(serialized);
488
+
489
+ expect(parsed.comments[0].anchorPrefix).toBe(
490
+ original.comments[0].anchorPrefix,
491
+ );
492
+ });
493
+ });
494
+
495
+ describe("createComment", () => {
496
+ it("creates comment with generated ID", () => {
497
+ const comment = createComment(
498
+ "text",
499
+ "comment",
500
+ 10,
501
+ 20,
502
+ "some content\nmore lines",
503
+ );
504
+ expect(comment.id).toHaveLength(8);
505
+ expect(comment.selectedText).toBe("text");
506
+ expect(comment.comment).toBe("comment");
507
+ expect(comment.startOffset).toBe(10);
508
+ expect(comment.endOffset).toBe(20);
509
+ });
510
+
511
+ it("generates line hint based on offsets", () => {
512
+ const content = "line one\nline two\nline three";
513
+ const comment = createComment("two", "comment", 14, 17, content);
514
+ expect(comment.lineHint).toBe("L2");
515
+ });
516
+
517
+ it("generates ISO timestamp", () => {
518
+ const comment = createComment("text", "comment", 0, 4, "text");
519
+ expect(comment.createdAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
520
+ });
521
+
522
+ it("truncates very long selections", () => {
523
+ const longText = "a".repeat(2000);
524
+ const comment = createComment(longText, "comment", 0, 2000, longText);
525
+ expect(comment.selectedText.length).toBeLessThan(1010);
526
+ expect(comment.selectedText).toContain("...");
527
+ });
528
+
529
+ it("does not set anchorPrefix for short selections", () => {
530
+ const comment = createComment("short text", "comment", 0, 10, "short text");
531
+ expect(comment.anchorPrefix).toBeUndefined();
532
+ });
533
+
534
+ it("sets anchorPrefix for truncated selections", () => {
535
+ const longText = "x".repeat(2000);
536
+ const comment = createComment(longText, "comment", 0, 2000, longText);
537
+ expect(comment.anchorPrefix).toBeDefined();
538
+ expect(comment.anchorPrefix).toHaveLength(200);
539
+ expect(comment.anchorPrefix).toBe("x".repeat(200));
540
+ });
541
+
542
+ it("anchorPrefix preserves first 200 chars of original text", () => {
543
+ const prefix = "PREFIX_";
544
+ const middle = "m".repeat(1500);
545
+ const suffix = "_SUFFIX";
546
+ const longText = prefix + middle + suffix;
547
+ const comment = createComment(
548
+ longText,
549
+ "comment",
550
+ 0,
551
+ longText.length,
552
+ longText,
553
+ );
554
+ expect(comment.anchorPrefix?.startsWith("PREFIX_")).toBe(true);
555
+ expect(comment.anchorPrefix).toHaveLength(200);
556
+ });
557
+ });
558
+
559
+ describe("truncateSelection", () => {
560
+ it("returns short text unchanged", () => {
561
+ expect(truncateSelection("short text")).toBe("short text");
562
+ });
563
+
564
+ it("returns text at exactly max length unchanged", () => {
565
+ const exactLength = "a".repeat(1000);
566
+ expect(truncateSelection(exactLength)).toBe(exactLength);
567
+ });
568
+
569
+ it("truncates text over 1000 chars", () => {
570
+ const longText = "a".repeat(2000);
571
+ const result = truncateSelection(longText);
572
+ expect(result.length).toBeLessThan(1010);
573
+ expect(result).toContain("\n...\n");
574
+ });
575
+
576
+ it("preserves start and end of text", () => {
577
+ const start = "START".repeat(100);
578
+ const end = "END".repeat(100);
579
+ const longText = start + "middle".repeat(100) + end;
580
+ const result = truncateSelection(longText);
581
+ expect(result.startsWith("START")).toBe(true);
582
+ expect(result.endsWith("END")).toBe(true);
583
+ });
584
+ });
585
+
586
+ describe("parseCommentFile version check", () => {
587
+ it("accepts current version", () => {
588
+ const content = `---
589
+ source: /test.md
590
+ hash: abc
591
+ version: 1
592
+ ---`;
593
+ expect(() => parseCommentFile(content)).not.toThrow();
594
+ });
595
+
596
+ it("throws on unsupported future version", () => {
597
+ const content = `---
598
+ source: /test.md
599
+ hash: abc
600
+ version: 99
601
+ ---`;
602
+ expect(() => parseCommentFile(content)).toThrow(/requires readit v99/);
603
+ });
604
+
605
+ it("includes current version in error message", () => {
606
+ const content = `---
607
+ source: /test.md
608
+ hash: abc
609
+ version: 99
610
+ ---`;
611
+ expect(() => parseCommentFile(content)).toThrow(/supports format v1/);
612
+ });
613
+ });
614
+
615
+ describe("performance", () => {
616
+ it("parseCommentFile with 50 comments completes within 50ms (100 iterations)", () => {
617
+ const start = performance.now();
618
+ for (let i = 0; i < 100; i++) {
619
+ parseCommentFile(COMMENT_FILE_LARGE);
620
+ }
621
+ const elapsed = performance.now() - start;
622
+ expect(elapsed).toBeLessThan(50);
623
+ });
624
+ });