@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,342 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import * as os from "node:os";
9
+ import { dirname, join, resolve } from "node:path";
10
+ import { expect, test } from "@playwright/test";
11
+ import { spawnCli } from "./utils/cli";
12
+ import { addComment, selectTextInArticle } from "./utils/selection";
13
+
14
+ const FIXTURES_DIR = resolve(import.meta.dirname, "fixtures");
15
+
16
+ /**
17
+ * Get the expected comment file path for a source file.
18
+ */
19
+ function getCommentPath(sourcePath: string): string {
20
+ const absolute = resolve(sourcePath);
21
+ const normalized = absolute.replace(/^\//, "").replace(/^[A-Z]:[\\/]/, "");
22
+ const ext = normalized.lastIndexOf(".");
23
+ const withoutExt = ext > 0 ? normalized.slice(0, ext) : normalized;
24
+ return join(os.homedir(), ".readit", "comments", `${withoutExt}.comments.md`);
25
+ }
26
+
27
+ /**
28
+ * Clean up comment file for a source file.
29
+ */
30
+ function cleanupCommentFile(sourcePath: string): void {
31
+ const commentPath = getCommentPath(sourcePath);
32
+ if (existsSync(commentPath)) {
33
+ rmSync(commentPath);
34
+ }
35
+ }
36
+
37
+ test.describe("File-Based Comment Persistence", () => {
38
+ const sampleMdPath = resolve(FIXTURES_DIR, "sample.md");
39
+
40
+ test.beforeEach(() => {
41
+ // Clean up any existing comment file before each test
42
+ cleanupCommentFile(sampleMdPath);
43
+ });
44
+
45
+ test.afterEach(() => {
46
+ // Clean up after each test
47
+ cleanupCommentFile(sampleMdPath);
48
+ });
49
+
50
+ test("comments are saved to markdown file", async ({ page }) => {
51
+ const { url, cleanup } = await spawnCli(sampleMdPath, {
52
+ port: 4590,
53
+ clean: false,
54
+ });
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
+ // Add a comment
64
+ const textToSelect = "testing text selection";
65
+ await selectTextInArticle(page, textToSelect);
66
+
67
+ const commentText = "This is a test comment for file persistence";
68
+ await addComment(page, commentText);
69
+
70
+ // Wait for comment to be visible in UI
71
+ await expect(page.locator("body")).toContainText(commentText);
72
+
73
+ // Give time for the file to be written
74
+ await page.waitForTimeout(500);
75
+
76
+ // Verify the comment file was created
77
+ const commentPath = getCommentPath(sampleMdPath);
78
+ expect(existsSync(commentPath)).toBe(true);
79
+
80
+ // Verify the file contains the comment
81
+ const fileContent = readFileSync(commentPath, "utf-8");
82
+ expect(fileContent).toContain(textToSelect);
83
+ expect(fileContent).toContain(commentText);
84
+ expect(fileContent).toContain("version: 1");
85
+ } finally {
86
+ await cleanup();
87
+ }
88
+ });
89
+
90
+ test("comments persist across page reload", async ({ page }) => {
91
+ const { url, cleanup } = await spawnCli(sampleMdPath, {
92
+ port: 4591,
93
+ clean: false,
94
+ });
95
+
96
+ try {
97
+ await page.goto(url);
98
+
99
+ const article = page.locator("article");
100
+ await expect(article).toBeVisible();
101
+
102
+ // Add a comment
103
+ const textToSelect = "testing text selection";
104
+ await selectTextInArticle(page, textToSelect);
105
+
106
+ const commentText = "Persistent comment test";
107
+ await addComment(page, commentText);
108
+
109
+ // Wait for comment to appear
110
+ await expect(page.locator("body")).toContainText(commentText);
111
+
112
+ // Reload the page
113
+ await page.reload();
114
+
115
+ // Wait for document to reload
116
+ await expect(article).toBeVisible();
117
+
118
+ // Verify comment still exists after reload
119
+ await expect(page.locator("body")).toContainText(commentText);
120
+
121
+ // Verify highlight still exists
122
+ const highlight = article.locator("mark[data-comment-id]").first();
123
+ await expect(highlight).toBeVisible();
124
+ } finally {
125
+ await cleanup();
126
+ }
127
+ });
128
+
129
+ test("comments persist across server restart", async ({ page }) => {
130
+ const PORT = 4592;
131
+
132
+ // First session: add a comment
133
+ const { url: url1, cleanup: cleanup1 } = await spawnCli(sampleMdPath, {
134
+ port: PORT,
135
+ clean: false,
136
+ });
137
+
138
+ const commentText = "Comment that survives restart";
139
+
140
+ try {
141
+ await page.goto(url1);
142
+
143
+ const article = page.locator("article");
144
+ await expect(article).toBeVisible();
145
+
146
+ await selectTextInArticle(page, "testing text selection");
147
+ await addComment(page, commentText);
148
+
149
+ // Wait for comment to appear
150
+ await expect(page.locator("body")).toContainText(commentText);
151
+ } finally {
152
+ await cleanup1();
153
+ }
154
+
155
+ // Wait for server to fully shut down
156
+ await page.waitForTimeout(1000);
157
+
158
+ // Second session: verify comment persists
159
+ const { url: url2, cleanup: cleanup2 } = await spawnCli(sampleMdPath, {
160
+ port: PORT,
161
+ clean: false,
162
+ });
163
+
164
+ try {
165
+ await page.goto(url2);
166
+
167
+ const article = page.locator("article");
168
+ await expect(article).toBeVisible();
169
+
170
+ // Comment should still exist from previous session
171
+ await expect(page.locator("body")).toContainText(commentText);
172
+
173
+ // Highlight should still exist
174
+ const highlight = article.locator("mark[data-comment-id]").first();
175
+ await expect(highlight).toBeVisible();
176
+ } finally {
177
+ await cleanup2();
178
+ }
179
+ });
180
+
181
+ test("--clean flag deletes comment file", async ({ page }) => {
182
+ const PORT = 4593;
183
+
184
+ // First, create a comment file manually
185
+ const commentPath = getCommentPath(sampleMdPath);
186
+ const commentDir = dirname(commentPath);
187
+ mkdirSync(commentDir, { recursive: true });
188
+ writeFileSync(
189
+ commentPath,
190
+ `---
191
+ source: ${sampleMdPath}
192
+ hash: abc123
193
+ version: 1
194
+ ---
195
+
196
+ <!-- c:12345678|L5|2024-12-24T10:00:00Z -->
197
+ > testing text selection
198
+
199
+ Pre-existing comment to be cleared.
200
+
201
+ ---
202
+ `,
203
+ "utf-8",
204
+ );
205
+
206
+ expect(existsSync(commentPath)).toBe(true);
207
+
208
+ // Start server with --clean flag
209
+ const { url, cleanup } = await spawnCli(sampleMdPath, {
210
+ port: PORT,
211
+ clean: true,
212
+ });
213
+
214
+ try {
215
+ await page.goto(url);
216
+
217
+ const article = page.locator("article");
218
+ await expect(article).toBeVisible();
219
+
220
+ // Wait for clean operation to complete
221
+ await page.waitForTimeout(500);
222
+
223
+ // Verify no comments in UI
224
+ const highlight = article.locator("mark[data-comment-id]");
225
+ await expect(highlight).toHaveCount(0);
226
+ } finally {
227
+ await cleanup();
228
+ }
229
+ });
230
+
231
+ test("edit updates the comment file", async ({ page }) => {
232
+ const { url, cleanup } = await spawnCli(sampleMdPath, {
233
+ port: 4594,
234
+ clean: false,
235
+ });
236
+
237
+ try {
238
+ await page.goto(url);
239
+
240
+ const article = page.locator("article");
241
+ await expect(article).toBeVisible();
242
+
243
+ // Add initial comment
244
+ const textToSelect = "testing text selection";
245
+ await selectTextInArticle(page, textToSelect);
246
+ const initialComment = "Initial comment text";
247
+ await addComment(page, initialComment);
248
+
249
+ await expect(page.locator("body")).toContainText(initialComment);
250
+
251
+ // Wait for file to be written
252
+ await page.waitForTimeout(500);
253
+
254
+ // Verify initial comment in file
255
+ const commentPath = getCommentPath(sampleMdPath);
256
+ let fileContent = readFileSync(commentPath, "utf-8");
257
+ expect(fileContent).toContain(initialComment);
258
+
259
+ // Find the margin note containing the selected text (stable identifier)
260
+ const marginNote = page
261
+ .locator(".group")
262
+ .filter({ hasText: textToSelect })
263
+ .first();
264
+ await marginNote.hover();
265
+
266
+ // Find and click edit button
267
+ const editButton = marginNote.locator('button:has-text("Edit")');
268
+ await editButton.click();
269
+
270
+ // Wait for edit mode to activate
271
+ const textarea = marginNote.locator("textarea");
272
+ await expect(textarea).toBeVisible();
273
+
274
+ // Clear and type new text
275
+ await textarea.fill("Updated comment text");
276
+
277
+ // Save edit
278
+ const saveButton = marginNote.locator('button:has-text("Save")');
279
+ await saveButton.click();
280
+
281
+ // Wait for file to be updated
282
+ await page.waitForTimeout(500);
283
+
284
+ // Verify updated comment in file
285
+ fileContent = readFileSync(commentPath, "utf-8");
286
+ expect(fileContent).toContain("Updated comment text");
287
+ } finally {
288
+ await cleanup();
289
+ }
290
+ });
291
+
292
+ test("delete removes comment from file", async ({ page }) => {
293
+ const { url, cleanup } = await spawnCli(sampleMdPath, {
294
+ port: 4595,
295
+ clean: false,
296
+ });
297
+
298
+ try {
299
+ await page.goto(url);
300
+
301
+ const article = page.locator("article");
302
+ await expect(article).toBeVisible();
303
+
304
+ // Add a comment
305
+ await selectTextInArticle(page, "testing text selection");
306
+ const commentText = "Comment to be deleted";
307
+ await addComment(page, commentText);
308
+
309
+ await expect(page.locator("body")).toContainText(commentText);
310
+
311
+ // Wait for file to be written
312
+ await page.waitForTimeout(500);
313
+
314
+ // Verify comment in file
315
+ const commentPath = getCommentPath(sampleMdPath);
316
+ let fileContent = readFileSync(commentPath, "utf-8");
317
+ expect(fileContent).toContain(commentText);
318
+
319
+ // Find the margin note containing the comment
320
+ const marginNote = page
321
+ .locator(".group")
322
+ .filter({ hasText: commentText })
323
+ .first();
324
+ await marginNote.hover();
325
+
326
+ // Click delete button
327
+ const deleteButton = marginNote.locator('button:has-text("Delete")');
328
+ await deleteButton.click();
329
+
330
+ // Wait for file to be updated
331
+ await page.waitForTimeout(500);
332
+
333
+ // Verify comment removed from file (file may be deleted if no comments left)
334
+ if (existsSync(commentPath)) {
335
+ fileContent = readFileSync(commentPath, "utf-8");
336
+ expect(fileContent).not.toContain(commentText);
337
+ }
338
+ } finally {
339
+ await cleanup();
340
+ }
341
+ });
342
+ });
@@ -0,0 +1,84 @@
1
+ import type { ChildProcess } from "node:child_process";
2
+ import { spawn } from "node:child_process";
3
+ import { resolve } from "node:path";
4
+
5
+ interface SpawnCliResult {
6
+ url: string;
7
+ process: ChildProcess;
8
+ cleanup: () => Promise<void>;
9
+ }
10
+
11
+ interface SpawnCliOptions {
12
+ port?: number;
13
+ clean?: boolean;
14
+ }
15
+
16
+ const CLI_PATH = resolve(import.meta.dirname, "../../dist/index.js");
17
+
18
+ export async function spawnCli(
19
+ fixturePath: string,
20
+ options: SpawnCliOptions = {},
21
+ ): Promise<SpawnCliResult> {
22
+ const { port = 4567, clean = true } = options;
23
+
24
+ const args = [CLI_PATH, fixturePath, "--no-open", "--port", String(port)];
25
+ if (clean) {
26
+ args.push("--clean");
27
+ }
28
+
29
+ const cliProcess = spawn("bun", args, {
30
+ cwd: resolve(import.meta.dirname, "../.."),
31
+ stdio: ["pipe", "pipe", "pipe"],
32
+ });
33
+
34
+ const url = await waitForServerReady(cliProcess);
35
+
36
+ return {
37
+ url,
38
+ process: cliProcess,
39
+ cleanup: async () => {
40
+ cliProcess.kill("SIGTERM");
41
+ await new Promise<void>((resolve) => {
42
+ cliProcess.once("exit", () => resolve());
43
+ setTimeout(resolve, 1000); // Fallback timeout
44
+ });
45
+ },
46
+ };
47
+ }
48
+
49
+ function waitForServerReady(cliProcess: ChildProcess): Promise<string> {
50
+ return new Promise((resolve, reject) => {
51
+ let output = "";
52
+
53
+ const timeout = setTimeout(() => {
54
+ reject(new Error("Server did not start within timeout"));
55
+ }, 10_000);
56
+
57
+ cliProcess.stdout?.on("data", (data: Buffer) => {
58
+ output += data.toString();
59
+
60
+ // Look for "URL: http://..." in output
61
+ const urlMatch = output.match(/URL:\s+(http:\/\/[^\s]+)/);
62
+ if (urlMatch) {
63
+ clearTimeout(timeout);
64
+ resolve(urlMatch[1]);
65
+ }
66
+ });
67
+
68
+ cliProcess.stderr?.on("data", (data: Buffer) => {
69
+ console.error("[CLI stderr]", data.toString());
70
+ });
71
+
72
+ cliProcess.on("error", (err) => {
73
+ clearTimeout(timeout);
74
+ reject(err);
75
+ });
76
+
77
+ cliProcess.on("exit", (code) => {
78
+ if (code !== 0 && code !== null) {
79
+ clearTimeout(timeout);
80
+ reject(new Error(`CLI exited with code ${code}: ${output}`));
81
+ }
82
+ });
83
+ });
84
+ }
@@ -0,0 +1,135 @@
1
+ import type { FrameLocator, Page } from "@playwright/test";
2
+
3
+ /**
4
+ * Select text within an article element (for markdown documents)
5
+ * Uses custom event to trigger selection handler (workaround for Playwright mouse issues)
6
+ */
7
+ export async function selectTextInArticle(
8
+ page: Page,
9
+ textToSelect: string,
10
+ ): Promise<void> {
11
+ // Find text and calculate offsets, then dispatch custom event
12
+ await page.evaluate((text) => {
13
+ const article = document.querySelector("article");
14
+ if (!article) throw new Error("Article element not found");
15
+
16
+ const walker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT);
17
+ let currentOffset = 0;
18
+
19
+ while (walker.nextNode()) {
20
+ const textNode = walker.currentNode as Text;
21
+ const content = textNode.textContent || "";
22
+ const index = content.indexOf(text);
23
+
24
+ if (index !== -1) {
25
+ const startOffset = currentOffset + index;
26
+ const endOffset = startOffset + text.length;
27
+
28
+ // Dispatch custom event that DocumentViewer listens for
29
+ const event = new CustomEvent("test:select-text", {
30
+ detail: { text, startOffset, endOffset },
31
+ });
32
+ window.dispatchEvent(event);
33
+ return;
34
+ }
35
+
36
+ currentOffset += content.length;
37
+ }
38
+
39
+ throw new Error(`Text "${text}" not found in article`);
40
+ }, textToSelect);
41
+
42
+ // Wait for React to process the selection
43
+ await page.waitForTimeout(100);
44
+ }
45
+
46
+ /**
47
+ * Select text within an iframe (for HTML documents)
48
+ * Uses custom event to trigger selection handler (same as markdown)
49
+ */
50
+ export async function selectTextInIframe(
51
+ page: Page,
52
+ _iframe: FrameLocator,
53
+ textToSelect: string,
54
+ ): Promise<void> {
55
+ // Get the actual frame object for evaluation
56
+ const frame = page.frame({ url: /^about:srcdoc/ }) || page.frames()[1];
57
+ if (!frame) throw new Error("Could not find iframe frame");
58
+
59
+ // Calculate offsets in iframe, then dispatch custom event to parent
60
+ const offsets = await frame.evaluate((text) => {
61
+ const body = document.body;
62
+ const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT);
63
+ let currentOffset = 0;
64
+
65
+ while (walker.nextNode()) {
66
+ const textNode = walker.currentNode as Text;
67
+ const content = textNode.textContent || "";
68
+ const index = content.indexOf(text);
69
+
70
+ if (index !== -1) {
71
+ const startOffset = currentOffset + index;
72
+ const endOffset = startOffset + text.length;
73
+ return { text, startOffset, endOffset };
74
+ }
75
+
76
+ currentOffset += content.length;
77
+ }
78
+
79
+ throw new Error(`Text "${text}" not found in iframe`);
80
+ }, textToSelect);
81
+
82
+ // Dispatch custom event to parent window (IframeContainer listens for this)
83
+ await page.evaluate((detail) => {
84
+ const event = new CustomEvent("test:select-text", { detail });
85
+ window.dispatchEvent(event);
86
+ }, offsets);
87
+
88
+ // Wait for React to process state update
89
+ await page.waitForTimeout(200);
90
+
91
+ // Manually send applyHighlights to iframe (workaround for isReadyRef timing)
92
+ // This ensures the iframe gets the highlight even if React's useEffect hasn't fired
93
+ await page.evaluate(
94
+ (selection) => {
95
+ const iframe = document.querySelector("iframe");
96
+ if (iframe?.contentWindow) {
97
+ iframe.contentWindow.postMessage(
98
+ {
99
+ type: "applyHighlights",
100
+ comments: [],
101
+ pendingSelection: selection,
102
+ },
103
+ "*",
104
+ );
105
+ }
106
+ },
107
+ { startOffset: offsets.startOffset, endOffset: offsets.endOffset },
108
+ );
109
+
110
+ // Wait for iframe to apply highlights
111
+ await page.waitForTimeout(200);
112
+ }
113
+
114
+ /**
115
+ * Add a comment to the current selection
116
+ * Assumes CommentInputArea is visible
117
+ */
118
+ export async function addComment(
119
+ page: Page,
120
+ commentText: string,
121
+ ): Promise<void> {
122
+ // Wait for the comment input textarea to appear
123
+ // Give more time for iframe postMessage round-trip
124
+ const textarea = page.locator('textarea[placeholder="Add your comment..."]');
125
+ await textarea.waitFor({ state: "visible", timeout: 10000 });
126
+
127
+ // Fill in the comment
128
+ await textarea.fill(commentText);
129
+
130
+ // Click the Add button
131
+ await page.getByRole("button", { name: "Add" }).click();
132
+
133
+ // Wait for the textarea to disappear (comment submitted)
134
+ await textarea.waitFor({ state: "hidden", timeout: 5000 });
135
+ }
@@ -7,10 +7,16 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
  <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📖</text></svg>">
10
- <script type="module" crossorigin src="/assets/index-CKVArt9D.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-DzRKJazf.css">
10
+ <script>
11
+ (() => {
12
+ var t = localStorage.getItem("readit:theme");
13
+ var d = t === "dark" || (t !== "light" && matchMedia("(prefers-color-scheme: dark)").matches);
14
+ if (d) document.documentElement.classList.add("dark");
15
+ })();
16
+ </script>
12
17
  </head>
13
18
  <body class="min-h-screen">
14
19
  <div id="root"></div>
20
+ <script type="module" src="/src/main.tsx"></script>
15
21
  </body>
16
22
  </html>
package/lefthook.yml ADDED
@@ -0,0 +1,8 @@
1
+ pre-commit:
2
+ parallel: true
3
+ commands:
4
+ check:
5
+ glob: "*.{ts,tsx,json}"
6
+ run: bunx biome check --no-errors-on-unmatched {staged_files}
7
+ typecheck:
8
+ run: bun run typecheck
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@peaske7/readit",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "A CLI tool to review Markdown documents with inline comments",
5
- "type": "module",
5
+ "author": "Jay Shimada <peaske@pm.me>",
6
+ "license": "MIT",
6
7
  "keywords": [
7
8
  "markdown",
8
9
  "review",
@@ -10,32 +11,15 @@
10
11
  "comments",
11
12
  "documentation"
12
13
  ],
13
- "author": "Jay Shimada <peaske@pm.me>",
14
- "license": "MIT",
15
- "bin": {
16
- "readit": "dist/index.js"
17
- },
14
+ "type": "module",
18
15
  "main": "./dist/index.js",
19
- "directories": {
20
- "doc": "docs"
21
- },
22
- "repository": {
23
- "type": "git",
24
- "url": "git+https://github.com/peaske7/readit.git"
25
- },
26
- "bugs": {
27
- "url": "https://github.com/peaske7/readit/issues"
28
- },
29
- "homepage": "https://github.com/peaske7/readit#readme",
30
- "files": [
31
- "dist"
32
- ],
33
16
  "scripts": {
34
- "dev": "concurrently \"NODE_ENV=development tsx watch src/cli/index.ts -- test.md\" \"wait-on tcp:4567 && vite\"",
35
- "build": "vite build && npm run build:cli",
36
- "build:cli": "tsup src/cli/index.ts --format esm --outDir dist --target es2022",
37
- "start": "npm run build && node dist/index.js",
17
+ "dev": "NODE_ENV=development bun --watch src/cli/index.ts -- test.md & bunx vite",
18
+ "build": "bunx vite build && bun run build:cli",
19
+ "build:cli": "bun build src/cli/index.ts --outdir dist --target bun --format esm --packages external",
20
+ "start": "bun run build && bun dist/index.js",
38
21
  "test": "vitest run",
22
+ "bench": "vitest bench",
39
23
  "test:watch": "vitest",
40
24
  "test:e2e": "playwright test",
41
25
  "test:e2e:ui": "playwright test --ui",
@@ -44,15 +28,17 @@
44
28
  "format": "biome format --write .",
45
29
  "lint": "biome lint .",
46
30
  "typecheck": "tsc --noEmit",
47
- "prepack": "pnpm run build",
31
+ "prepack": "bun run build",
48
32
  "prepare": "lefthook install || true"
49
33
  },
50
34
  "dependencies": {
35
+ "@radix-ui/react-dialog": "^1.1.15",
36
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
37
+ "@radix-ui/react-slot": "^1.2.4",
51
38
  "class-variance-authority": "^0.7.1",
52
39
  "clsx": "^2.1.1",
53
40
  "commander": "^14.0.3",
54
41
  "dompurify": "^3.3.3",
55
- "express": "^5.2.1",
56
42
  "lucide-react": "^0.577.0",
57
43
  "mermaid": "^11.13.0",
58
44
  "open": "^11.0.0",
@@ -66,7 +52,8 @@
66
52
  "tailwind-merge": "^3.5.0",
67
53
  "unified": "^11.0.5",
68
54
  "unist-util-visit": "^5.1.0",
69
- "zod": "^4.3.6"
55
+ "zod": "^4.3.6",
56
+ "zustand": "^5.0.11"
70
57
  },
71
58
  "devDependencies": {
72
59
  "@biomejs/biome": "^2.4.6",
@@ -74,29 +61,20 @@
74
61
  "@tailwindcss/vite": "^4.2.1",
75
62
  "@testing-library/jest-dom": "^6.9.1",
76
63
  "@testing-library/react": "^16.3.2",
64
+ "@types/bun": "^1.3.10",
77
65
  "@types/dompurify": "^3.2.0",
78
- "@types/express": "^5.0.6",
79
66
  "@types/jsdom": "^28.0.0",
80
- "@types/node": "^25.4.0",
81
67
  "@types/react": "^19.2.14",
82
68
  "@types/react-dom": "^19.2.3",
83
69
  "@types/react-syntax-highlighter": "^15.5.13",
84
70
  "@vitejs/plugin-react": "^6.0.0",
85
- "concurrently": "^9.2.1",
86
71
  "jsdom": "^28.1.0",
87
72
  "lefthook": "^2.1.4",
88
73
  "react": "^19.2.4",
89
74
  "react-dom": "^19.2.4",
90
75
  "tailwindcss": "^4.2.1",
91
- "tsup": "^8.5.1",
92
- "tsx": "^4.21.0",
93
76
  "typescript": "^5.9.3",
94
77
  "vite": "^8.0.0",
95
- "vitest": "^4.1.0",
96
- "wait-on": "^9.0.4"
97
- },
98
- "packageManager": "pnpm@10.26.0",
99
- "engines": {
100
- "node": ">=22.0.0"
78
+ "vitest": "^4.1.0"
101
79
  }
102
80
  }