@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,263 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import type { Comment, CommentFile } from "../types";
5
+
6
+ const FORMAT_VERSION = 1;
7
+ const HASH_LENGTH = 16;
8
+ const MAX_SELECTION_LENGTH = 1000;
9
+ const TRUNCATION_MARKER = "\n...\n";
10
+ const ANCHOR_PREFIX_LENGTH = 200; // chars stored for anchor matching when text is truncated
11
+
12
+ /**
13
+ * Truncate very long selections to first ~500 + ... + last ~500 chars.
14
+ */
15
+ export function truncateSelection(text: string): string {
16
+ if (text.length <= MAX_SELECTION_LENGTH) {
17
+ return text;
18
+ }
19
+ const half = Math.floor(
20
+ (MAX_SELECTION_LENGTH - TRUNCATION_MARKER.length) / 2,
21
+ );
22
+ return text.slice(0, half) + TRUNCATION_MARKER + text.slice(-half);
23
+ }
24
+
25
+ /**
26
+ * Compute the path where comments for a source file should be stored.
27
+ * Comments are stored in ~/.readit/comments/{absolute-path-structure}/{filename}.comments.md
28
+ */
29
+ export function getCommentPath(sourcePath: string): string {
30
+ // Resolve to absolute path
31
+ const absolute = path.resolve(sourcePath);
32
+
33
+ // Remove leading slash and drive letter (Windows)
34
+ const normalized = absolute.replace(/^\//, "").replace(/^[A-Z]:[\\/]/, "");
35
+
36
+ // Get filename without extension, add .comments.md
37
+ const ext = path.extname(normalized);
38
+ const withoutExt = normalized.slice(0, -ext.length || undefined);
39
+
40
+ return path.join(
41
+ os.homedir(),
42
+ ".readit",
43
+ "comments",
44
+ `${withoutExt}.comments.md`,
45
+ );
46
+ }
47
+
48
+ /**
49
+ * Compute SHA-256 hash of content, returning first 16 characters.
50
+ */
51
+ export function computeHash(content: string): string {
52
+ return crypto
53
+ .createHash("sha256")
54
+ .update(content)
55
+ .digest("hex")
56
+ .slice(0, HASH_LENGTH);
57
+ }
58
+
59
+ /**
60
+ * Get line number (1-indexed) for a character offset in content.
61
+ */
62
+ export function getLineNumber(content: string, offset: number): number {
63
+ if (offset <= 0 || content.length === 0) return 1;
64
+ const clampedOffset = Math.min(offset, content.length);
65
+ return content.slice(0, clampedOffset).split("\n").length;
66
+ }
67
+
68
+ /**
69
+ * Get line range string for a selection (e.g., "L42" or "L42-45").
70
+ */
71
+ export function getLineHint(
72
+ content: string,
73
+ startOffset: number,
74
+ endOffset: number,
75
+ ): string {
76
+ const startLine = getLineNumber(content, startOffset);
77
+ const endLine = getLineNumber(content, endOffset);
78
+ return startLine === endLine ? `L${startLine}` : `L${startLine}-${endLine}`;
79
+ }
80
+
81
+ /**
82
+ * Parse a comment file's markdown content into a CommentFile structure.
83
+ */
84
+ export function parseCommentFile(content: string): CommentFile {
85
+ const result: CommentFile = {
86
+ source: "",
87
+ hash: "",
88
+ version: FORMAT_VERSION,
89
+ comments: [],
90
+ };
91
+
92
+ if (!content.trim()) {
93
+ return result;
94
+ }
95
+
96
+ // Parse YAML front matter
97
+ const frontMatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
98
+ if (frontMatterMatch) {
99
+ const frontMatter = frontMatterMatch[1];
100
+ const sourceMatch = frontMatter.match(/^source:\s*(.+)$/m);
101
+ const hashMatch = frontMatter.match(/^hash:\s*(.+)$/m);
102
+ const versionMatch = frontMatter.match(/^version:\s*(\d+)$/m);
103
+
104
+ if (sourceMatch) result.source = sourceMatch[1].trim();
105
+ if (hashMatch) result.hash = hashMatch[1].trim();
106
+ if (versionMatch) result.version = Number.parseInt(versionMatch[1], 10);
107
+
108
+ // Validate version compatibility
109
+ if (result.version > FORMAT_VERSION) {
110
+ throw new Error(
111
+ `Comment file requires readit v${result.version} or higher. ` +
112
+ `Current version supports format v${FORMAT_VERSION}.`,
113
+ );
114
+ }
115
+ }
116
+
117
+ // Remove front matter and split by separator
118
+ const bodyContent = content.replace(/^---\n[\s\S]*?\n---\n*/, "");
119
+ const blocks = bodyContent.split(/\n---\n/).filter((block) => block.trim());
120
+
121
+ for (const block of blocks) {
122
+ const comment = parseCommentBlock(block);
123
+ if (comment) {
124
+ result.comments.push(comment);
125
+ }
126
+ }
127
+
128
+ return result;
129
+ }
130
+
131
+ /**
132
+ * Parse a single comment block.
133
+ */
134
+ function parseCommentBlock(block: string): Comment | undefined {
135
+ // Extract metadata from HTML comment: <!-- c:{id}|{lineHint}|{timestamp} -->
136
+ const metadataMatch = block.match(/<!--\s*c:([^|]+)\|([^|]+)\|([^>]+)\s*-->/);
137
+ if (!metadataMatch) {
138
+ return undefined;
139
+ }
140
+
141
+ const [, id, lineHint, createdAt] = metadataMatch;
142
+
143
+ // Extract anchor prefix if present: <!-- anchor:{prefix} -->
144
+ const anchorMatch = block.match(/<!--\s*anchor:(.+?)\s*-->/);
145
+ const anchorPrefix = anchorMatch ? anchorMatch[1] : undefined;
146
+
147
+ // Extract selected text from blockquote
148
+ const blockquoteMatch = block.match(/^>\s*(.+(?:\n>\s*.+)*)$/m);
149
+ if (!blockquoteMatch) {
150
+ return undefined;
151
+ }
152
+
153
+ // Remove the "> " prefix from each line
154
+ const selectedText = blockquoteMatch[1]
155
+ .split("\n")
156
+ .map((line) => line.replace(/^>\s*/, ""))
157
+ .join("\n");
158
+
159
+ // Extract comment body (everything after blockquote)
160
+ const afterBlockquote = block.slice(
161
+ block.indexOf(blockquoteMatch[0]) + blockquoteMatch[0].length,
162
+ );
163
+ const commentBody = afterBlockquote.trim();
164
+
165
+ return {
166
+ id,
167
+ selectedText,
168
+ comment: commentBody,
169
+ createdAt: createdAt.trim(),
170
+ lineHint,
171
+ anchorPrefix,
172
+ // Offsets will be resolved by anchor matching when loading
173
+ startOffset: 0,
174
+ endOffset: 0,
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Serialize a CommentFile structure to markdown content.
180
+ */
181
+ export function serializeComments(file: CommentFile): string {
182
+ const lines: string[] = [];
183
+
184
+ // YAML front matter
185
+ lines.push("---");
186
+ lines.push(`source: ${file.source}`);
187
+ lines.push(`hash: ${file.hash}`);
188
+ lines.push(`version: ${file.version}`);
189
+ lines.push("---");
190
+ lines.push("");
191
+
192
+ // Comments
193
+ for (const comment of file.comments) {
194
+ lines.push(serializeComment(comment));
195
+ lines.push("");
196
+ lines.push("---");
197
+ lines.push("");
198
+ }
199
+
200
+ return lines.join("\n");
201
+ }
202
+
203
+ /**
204
+ * Serialize a single comment to markdown block.
205
+ */
206
+ function serializeComment(comment: Comment): string {
207
+ const lines: string[] = [];
208
+
209
+ // Metadata as HTML comment
210
+ const lineHint = comment.lineHint || "L0";
211
+ lines.push(`<!-- c:${comment.id}|${lineHint}|${comment.createdAt} -->`);
212
+
213
+ // Anchor prefix for long selections (used for anchor matching when text is truncated)
214
+ if (comment.anchorPrefix) {
215
+ lines.push(`<!-- anchor:${comment.anchorPrefix} -->`);
216
+ }
217
+
218
+ // Selected text as blockquote
219
+ const quotedLines = comment.selectedText
220
+ .split("\n")
221
+ .map((line) => `> ${line}`);
222
+ lines.push(...quotedLines);
223
+
224
+ // Comment body
225
+ if (comment.comment) {
226
+ lines.push("");
227
+ lines.push(comment.comment);
228
+ }
229
+
230
+ return lines.join("\n");
231
+ }
232
+
233
+ /**
234
+ * Create a new comment with a generated ID and current timestamp.
235
+ */
236
+ export function createComment(
237
+ selectedText: string,
238
+ commentText: string,
239
+ startOffset: number,
240
+ endOffset: number,
241
+ sourceContent: string,
242
+ ): Comment {
243
+ const id = crypto.randomUUID().slice(0, 8);
244
+ const lineHint = getLineHint(sourceContent, startOffset, endOffset);
245
+ const now = new Date();
246
+ const createdAt = now.toISOString();
247
+
248
+ const needsTruncation = selectedText.length > MAX_SELECTION_LENGTH;
249
+
250
+ return {
251
+ id,
252
+ selectedText: truncateSelection(selectedText),
253
+ comment: commentText,
254
+ createdAt,
255
+ startOffset,
256
+ endOffset,
257
+ lineHint,
258
+ // Store first N chars for anchor matching when text is truncated
259
+ anchorPrefix: needsTruncation
260
+ ? selectedText.slice(0, ANCHOR_PREFIX_LENGTH)
261
+ : undefined,
262
+ };
263
+ }
@@ -0,0 +1,41 @@
1
+ import { bench, describe } from "vitest";
2
+ import { HTML_DOC, LARGE_DOC } from "./__fixtures__/bench-data";
3
+ import { extractContext, formatForLLM, stripHtmlTags } from "./context";
4
+
5
+ describe("stripHtmlTags", () => {
6
+ bench("200-line HTML document", () => {
7
+ stripHtmlTags(HTML_DOC);
8
+ });
9
+
10
+ bench("short HTML string", () => {
11
+ stripHtmlTags(
12
+ "<p>Hello &amp; <strong>world</strong> with &#60;entities&#62;</p>",
13
+ );
14
+ });
15
+ });
16
+
17
+ describe("extractContext", () => {
18
+ bench("single-line selection, markdown", () => {
19
+ extractContext({ content: LARGE_DOC, startOffset: 500, endOffset: 530 });
20
+ });
21
+
22
+ bench("multi-line selection, markdown", () => {
23
+ extractContext({ content: LARGE_DOC, startOffset: 500, endOffset: 800 });
24
+ });
25
+
26
+ bench("HTML content (triggers stripHtmlTags)", () => {
27
+ extractContext({ content: HTML_DOC, startOffset: 100, endOffset: 200 });
28
+ });
29
+ });
30
+
31
+ describe("formatForLLM", () => {
32
+ const ctx = extractContext({
33
+ content: LARGE_DOC,
34
+ startOffset: 500,
35
+ endOffset: 530,
36
+ });
37
+
38
+ bench("format with comment", () => {
39
+ formatForLLM({ context: ctx, fileName: "doc.md", comment: "Needs review" });
40
+ });
41
+ });
@@ -0,0 +1,224 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { extractContext, formatForLLM, stripHtmlTags } from "./context";
3
+
4
+ describe("stripHtmlTags", () => {
5
+ it("removes simple HTML tags", () => {
6
+ expect(stripHtmlTags("<p>Hello</p>")).toBe("Hello");
7
+ expect(stripHtmlTags("<div><span>Nested</span></div>")).toBe("Nested");
8
+ });
9
+
10
+ it("removes script and style content entirely", () => {
11
+ expect(stripHtmlTags('<script>alert("xss")</script>text')).toBe("text");
12
+ expect(stripHtmlTags("<style>.foo { color: red }</style>text")).toBe(
13
+ "text",
14
+ );
15
+ });
16
+
17
+ it("decodes common named entities", () => {
18
+ expect(stripHtmlTags("&lt;tag&gt;")).toBe("<tag>");
19
+ expect(stripHtmlTags("&amp;&nbsp;&quot;&#39;")).toBe("& \"'");
20
+ });
21
+
22
+ it("decodes numeric entities (decimal)", () => {
23
+ expect(stripHtmlTags("&#65;&#66;&#67;")).toBe("ABC");
24
+ });
25
+
26
+ it("decodes numeric entities (hex)", () => {
27
+ expect(stripHtmlTags("&#x41;&#x42;&#x43;")).toBe("ABC");
28
+ expect(stripHtmlTags("&#X41;&#X42;&#X43;")).toBe("ABC"); // case-insensitive
29
+ });
30
+
31
+ it("handles mixed content", () => {
32
+ const html = "<p>Hello &amp; <strong>World</strong>!</p>";
33
+ expect(stripHtmlTags(html)).toBe("Hello & World!");
34
+ });
35
+
36
+ it("preserves plain text", () => {
37
+ expect(stripHtmlTags("plain text")).toBe("plain text");
38
+ });
39
+ });
40
+
41
+ describe("extractContext", () => {
42
+ it("extracts single-line selection with markers", () => {
43
+ const content = "line1\nline2\nline3\nline4\nline5";
44
+ // "line2" starts at offset 6, ends at 11
45
+ const result = extractContext({ content, startOffset: 6, endOffset: 11 });
46
+
47
+ expect(result.startLine).toBe(2);
48
+ expect(result.endLine).toBe(2);
49
+ // Should have >>> and <<< markers around "line2"
50
+ expect(result.lines.some((l) => l.includes(">>> line2 <<<"))).toBe(true);
51
+ });
52
+
53
+ it("includes context lines before and after", () => {
54
+ const content = "line1\nline2\nline3\nline4\nline5";
55
+ // Select "line3" at offset 12
56
+ const result = extractContext({
57
+ content,
58
+ startOffset: 12,
59
+ endOffset: 17,
60
+ contextLines: 2,
61
+ });
62
+
63
+ expect(result.startLine).toBe(3);
64
+ expect(result.endLine).toBe(3);
65
+ // Should include 2 lines before (line1, line2) and 2 after (line4, line5)
66
+ expect(result.lines.length).toBe(5);
67
+ });
68
+
69
+ it("handles multi-line selection", () => {
70
+ const content = "line1\nline2\nline3\nline4\nline5";
71
+ // Select from "line2" to "line3" (offset 6 to 17)
72
+ const result = extractContext({ content, startOffset: 6, endOffset: 17 });
73
+
74
+ expect(result.startLine).toBe(2);
75
+ expect(result.endLine).toBe(3);
76
+ // Start line should have >>> marker
77
+ expect(result.lines.some((l) => l.includes(">>>"))).toBe(true);
78
+ // End line should have <<< marker
79
+ expect(result.lines.some((l) => l.includes("<<<"))).toBe(true);
80
+ });
81
+
82
+ it("handles selection at start of document", () => {
83
+ const content = "line1\nline2\nline3";
84
+ // Select "line1" at start
85
+ const result = extractContext({ content, startOffset: 0, endOffset: 5 });
86
+
87
+ expect(result.startLine).toBe(1);
88
+ expect(result.endLine).toBe(1);
89
+ expect(result.lines[0]).toContain(">>> line1 <<<");
90
+ });
91
+
92
+ it("handles selection at end of document", () => {
93
+ const content = "line1\nline2\nline3";
94
+ // Select "line3" (offset 12 to 17)
95
+ const result = extractContext({ content, startOffset: 12, endOffset: 17 });
96
+
97
+ expect(result.startLine).toBe(3);
98
+ expect(result.endLine).toBe(3);
99
+ expect(result.lines.some((l) => l.includes(">>> line3 <<<"))).toBe(true);
100
+ });
101
+
102
+ it("truncates very long lines", () => {
103
+ const longLine = "a".repeat(250);
104
+ const content = `short\n${longLine}\nshort`;
105
+ // Select part of the long line
106
+ const result = extractContext({ content, startOffset: 6, endOffset: 256 });
107
+
108
+ // Long line should be truncated with ...
109
+ const longLineOutput = result.lines.find((l) => l.length > 100);
110
+ expect(longLineOutput?.endsWith("...")).toBe(true);
111
+ });
112
+
113
+ it("handles HTML content by stripping tags", () => {
114
+ const html = "<p>paragraph</p>\n<div>div content</div>";
115
+ // After stripping: "paragraph\ndiv content"
116
+ // Select "paragraph" at offset 0-9
117
+ const result = extractContext({
118
+ content: html,
119
+ startOffset: 0,
120
+ endOffset: 9,
121
+ });
122
+
123
+ expect(result.lines.some((l) => l.includes(">>> paragraph <<<"))).toBe(
124
+ true,
125
+ );
126
+ });
127
+
128
+ it("normalizes CRLF to LF", () => {
129
+ const content = "line1\r\nline2\r\nline3";
130
+ // After normalization: "line1\nline2\nline3"
131
+ // Select "line2" at offset 6
132
+ const result = extractContext({ content, startOffset: 6, endOffset: 11 });
133
+
134
+ expect(result.startLine).toBe(2);
135
+ expect(result.lines.some((l) => l.includes(">>> line2 <<<"))).toBe(true);
136
+ });
137
+
138
+ it("limits context lines to document bounds", () => {
139
+ const content = "only\ntwo\nlines";
140
+ // Select middle line with 5 context lines requested
141
+ const result = extractContext({
142
+ content,
143
+ startOffset: 5,
144
+ endOffset: 8,
145
+ contextLines: 5,
146
+ });
147
+
148
+ // Should not go beyond document bounds
149
+ expect(result.lines.length).toBeLessThanOrEqual(3);
150
+ });
151
+
152
+ it("truncates very long selections with ellipsis", () => {
153
+ // Create content with more than MAX_SELECTION_LINES (10) lines
154
+ const lines = Array.from({ length: 15 }, (_, i) => `line${i + 1}`);
155
+ const content = lines.join("\n");
156
+ // Select all content
157
+ const result = extractContext({
158
+ content,
159
+ startOffset: 0,
160
+ endOffset: content.length,
161
+ contextLines: 0,
162
+ });
163
+
164
+ // Should include ... for truncated middle
165
+ expect(result.lines.some((l) => l === "...")).toBe(true);
166
+ });
167
+ });
168
+
169
+ describe("formatForLLM", () => {
170
+ it("formats context with header and line range", () => {
171
+ const context = {
172
+ lines: ["before", ">>> selected <<<", "after"],
173
+ startLine: 5,
174
+ endLine: 5,
175
+ };
176
+
177
+ const result = formatForLLM({ context, fileName: "test.md" });
178
+
179
+ expect(result).toContain("# From: test.md");
180
+ expect(result).toContain("Lines 5-5:");
181
+ expect(result).toContain("---");
182
+ expect(result).toContain(">>> selected <<<");
183
+ });
184
+
185
+ it("includes optional comment", () => {
186
+ const context = {
187
+ lines: ["text"],
188
+ startLine: 1,
189
+ endLine: 1,
190
+ };
191
+
192
+ const result = formatForLLM({
193
+ context,
194
+ fileName: "test.md",
195
+ comment: "This needs review",
196
+ });
197
+
198
+ expect(result).toContain("Comment: This needs review");
199
+ });
200
+
201
+ it("omits comment section when not provided", () => {
202
+ const context = {
203
+ lines: ["text"],
204
+ startLine: 1,
205
+ endLine: 1,
206
+ };
207
+
208
+ const result = formatForLLM({ context, fileName: "test.md" });
209
+
210
+ expect(result).not.toContain("Comment:");
211
+ });
212
+
213
+ it("formats multi-line range correctly", () => {
214
+ const context = {
215
+ lines: [">>> start", "middle", "end <<<"],
216
+ startLine: 10,
217
+ endLine: 12,
218
+ };
219
+
220
+ const result = formatForLLM({ context, fileName: "doc.html" });
221
+
222
+ expect(result).toContain("Lines 10-12:");
223
+ });
224
+ });