@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.
- package/.agents/skills/remotion-best-practices/SKILL.md +61 -0
- package/.agents/skills/remotion-best-practices/rules/3d.md +86 -0
- package/.agents/skills/remotion-best-practices/rules/animations.md +27 -0
- package/.agents/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +178 -0
- package/.agents/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
- package/.agents/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
- package/.agents/skills/remotion-best-practices/rules/assets.md +78 -0
- package/.agents/skills/remotion-best-practices/rules/audio-visualization.md +198 -0
- package/.agents/skills/remotion-best-practices/rules/audio.md +169 -0
- package/.agents/skills/remotion-best-practices/rules/calculate-metadata.md +134 -0
- package/.agents/skills/remotion-best-practices/rules/can-decode.md +75 -0
- package/.agents/skills/remotion-best-practices/rules/charts.md +120 -0
- package/.agents/skills/remotion-best-practices/rules/compositions.md +154 -0
- package/.agents/skills/remotion-best-practices/rules/display-captions.md +184 -0
- package/.agents/skills/remotion-best-practices/rules/extract-frames.md +229 -0
- package/.agents/skills/remotion-best-practices/rules/ffmpeg.md +38 -0
- package/.agents/skills/remotion-best-practices/rules/fonts.md +152 -0
- package/.agents/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
- package/.agents/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
- package/.agents/skills/remotion-best-practices/rules/get-video-duration.md +60 -0
- package/.agents/skills/remotion-best-practices/rules/gifs.md +141 -0
- package/.agents/skills/remotion-best-practices/rules/images.md +134 -0
- package/.agents/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
- package/.agents/skills/remotion-best-practices/rules/light-leaks.md +73 -0
- package/.agents/skills/remotion-best-practices/rules/lottie.md +70 -0
- package/.agents/skills/remotion-best-practices/rules/maps.md +412 -0
- package/.agents/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
- package/.agents/skills/remotion-best-practices/rules/measuring-text.md +140 -0
- package/.agents/skills/remotion-best-practices/rules/parameters.md +109 -0
- package/.agents/skills/remotion-best-practices/rules/sequencing.md +118 -0
- package/.agents/skills/remotion-best-practices/rules/sfx.md +26 -0
- package/.agents/skills/remotion-best-practices/rules/subtitles.md +36 -0
- package/.agents/skills/remotion-best-practices/rules/tailwind.md +11 -0
- package/.agents/skills/remotion-best-practices/rules/text-animations.md +20 -0
- package/.agents/skills/remotion-best-practices/rules/timing.md +179 -0
- package/.agents/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
- package/.agents/skills/remotion-best-practices/rules/transitions.md +197 -0
- package/.agents/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
- package/.agents/skills/remotion-best-practices/rules/trimming.md +51 -0
- package/.agents/skills/remotion-best-practices/rules/videos.md +171 -0
- package/.agents/skills/remotion-best-practices/rules/voiceover.md +99 -0
- package/.agents/skills/simple/SKILL.md +52 -0
- package/.agents/skills/vercel-react-best-practices/AGENTS.md +3254 -0
- package/.agents/skills/vercel-react-best-practices/README.md +123 -0
- package/.agents/skills/vercel-react-best-practices/SKILL.md +141 -0
- package/.agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/.agents/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/.agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/.agents/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/.agents/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
- package/.agents/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/.agents/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/.agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/.agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/.agents/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/.agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/.agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/.agents/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/.agents/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/.agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/.agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/.agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-flatmap-filter.md +60 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-resource-hints.md +85 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-script-defer-async.md +68 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-no-inline-components.md +82 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md +142 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/.claude/CLAUDE.md +142 -0
- package/.claude/commands/review.md +120 -0
- package/.claude/commands/sync-docs.md +71 -0
- package/.claude/roadmap.md +98 -0
- package/.claude/rules/style-guide.md +830 -0
- package/.claude/settings.json +18 -0
- package/.claude/user-stories.md +248 -0
- package/AGENTS.md +64 -0
- package/README.md +7 -7
- package/biome.json +69 -0
- package/bun.lock +1124 -0
- package/docs/design.md +563 -0
- package/docs/plans/2026-03-13-client-mode-design.md +86 -0
- package/docs/plans/2026-03-13-client-mode-plan.md +605 -0
- package/docs/plans/2026-03-13-keyboard-shortcuts-design.md +129 -0
- package/docs/plans/2026-03-13-keyboard-shortcuts-plan.md +1471 -0
- package/docs/plans/2026-03-13-multi-document-design.md +183 -0
- package/docs/plans/2026-03-13-performance-benchmarks-design.md +121 -0
- package/e2e/comments.spec.ts +125 -0
- package/e2e/document-load.spec.ts +54 -0
- package/e2e/export.spec.ts +58 -0
- package/e2e/fixtures/sample.html +13 -0
- package/e2e/fixtures/sample.md +7 -0
- package/e2e/persistence-file.spec.ts +342 -0
- package/e2e/utils/cli.ts +84 -0
- package/e2e/utils/selection.ts +135 -0
- package/{dist/index.html → index.html} +8 -2
- package/lefthook.yml +8 -0
- package/package.json +17 -39
- package/playwright.config.ts +22 -0
- package/skills-lock.json +20 -0
- package/src/App.tsx +396 -0
- package/src/cli/index.ts +467 -0
- package/src/components/ActionsMenu.tsx +110 -0
- package/src/components/DocumentViewer/CodeBlock.tsx +83 -0
- package/src/components/DocumentViewer/DocumentViewer.tsx +257 -0
- package/src/components/DocumentViewer/IframeContainer.tsx +251 -0
- package/src/components/DocumentViewer/MermaidDiagram.tsx +137 -0
- package/src/components/DocumentViewer/index.ts +1 -0
- package/src/components/FloatingTOC.tsx +59 -0
- package/src/components/Header.tsx +63 -0
- package/src/components/InlineEditor.tsx +72 -0
- package/src/components/MarginNote.tsx +198 -0
- package/src/components/MarginNotes.tsx +50 -0
- package/src/components/RawModal.tsx +141 -0
- package/src/components/ReanchorConfirm.tsx +33 -0
- package/src/components/SettingsModal.tsx +221 -0
- package/src/components/ShortcutCapture.tsx +45 -0
- package/src/components/ShortcutList.tsx +157 -0
- package/src/components/TabBar.tsx +60 -0
- package/src/components/TableOfContents.tsx +108 -0
- package/src/components/comments/CommentBadge.tsx +43 -0
- package/src/components/comments/CommentInput.tsx +119 -0
- package/src/components/comments/CommentListItem.tsx +82 -0
- package/src/components/comments/CommentManager.tsx +106 -0
- package/src/components/comments/CommentMinimap.tsx +62 -0
- package/src/components/comments/CommentNav.tsx +104 -0
- package/src/components/ui/ActionBar.tsx +16 -0
- package/src/components/ui/ActionLink.tsx +32 -0
- package/src/components/ui/Button.tsx +55 -0
- package/src/components/ui/Dialog.tsx +156 -0
- package/src/components/ui/DropdownMenu.tsx +114 -0
- package/src/components/ui/SeparatorDot.tsx +9 -0
- package/src/components/ui/Text.tsx +54 -0
- package/src/contexts/CommentContext.tsx +222 -0
- package/src/contexts/LayoutContext.tsx +76 -0
- package/src/hooks/useClickOutside.ts +35 -0
- package/src/hooks/useClipboard.ts +79 -0
- package/src/hooks/useCommentNavigation.ts +130 -0
- package/src/hooks/useComments.ts +323 -0
- package/src/hooks/useDocument.ts +141 -0
- package/src/hooks/useFontPreference.ts +76 -0
- package/src/hooks/useHeadings.test.ts +159 -0
- package/src/hooks/useHeadings.ts +129 -0
- package/src/hooks/useKeybindings.ts +120 -0
- package/src/hooks/useKeyboardShortcuts.ts +63 -0
- package/src/hooks/useLayoutMode.ts +44 -0
- package/src/hooks/useReanchorMode.ts +33 -0
- package/src/hooks/useScrollMetrics.ts +56 -0
- package/src/hooks/useScrollSpy.ts +81 -0
- package/src/hooks/useTextSelection.ts +123 -0
- package/src/hooks/useThemePreference.ts +66 -0
- package/src/index.css +823 -0
- package/src/lib/__fixtures__/bench-data.ts +167 -0
- package/src/lib/anchor.bench.ts +112 -0
- package/src/lib/anchor.test.ts +531 -0
- package/src/lib/anchor.ts +465 -0
- package/src/lib/comment-storage.bench.ts +63 -0
- package/src/lib/comment-storage.test.ts +624 -0
- package/src/lib/comment-storage.ts +263 -0
- package/src/lib/context.bench.ts +41 -0
- package/src/lib/context.test.ts +224 -0
- package/src/lib/context.ts +193 -0
- package/src/lib/export.bench.ts +35 -0
- package/src/lib/export.ts +43 -0
- package/src/lib/highlight/colors.ts +37 -0
- package/src/lib/highlight/core.test.ts +98 -0
- package/src/lib/highlight/core.ts +54 -0
- package/src/lib/highlight/dom.ts +342 -0
- package/src/lib/highlight/highlighter.ts +427 -0
- package/src/lib/highlight/index.ts +23 -0
- package/src/lib/highlight/script-builder.ts +485 -0
- package/src/lib/highlight/types.ts +57 -0
- package/src/lib/html-processor.test.tsx +170 -0
- package/src/lib/html-processor.tsx +95 -0
- package/src/lib/layout-constants.ts +12 -0
- package/src/lib/margin-layout.bench.ts +28 -0
- package/src/lib/margin-layout.ts +100 -0
- package/src/lib/scroll.test.ts +118 -0
- package/src/lib/scroll.ts +47 -0
- package/src/lib/shortcut-registry.test.ts +173 -0
- package/src/lib/shortcut-registry.ts +209 -0
- package/src/lib/utils.test.ts +110 -0
- package/src/lib/utils.ts +61 -0
- package/src/main.tsx +10 -0
- package/src/server/index.ts +883 -0
- package/src/store/index.test.ts +220 -0
- package/src/store/index.ts +234 -0
- package/src/test-setup.ts +1 -0
- package/src/types/index.ts +115 -0
- package/test.md +74 -0
- package/tsconfig.cli.json +12 -0
- package/tsconfig.json +20 -0
- package/vite.config.ts +19 -0
- package/vitest.config.ts +15 -0
- package/dist/assets/_basePickBy-hOr-yGsE.js +0 -1
- package/dist/assets/_baseUniq-b7bzdUSn.js +0 -1
- package/dist/assets/arc-D65wG9gm.js +0 -1
- package/dist/assets/architecture-PBZL5I3N-DBa6CAv_.js +0 -1
- package/dist/assets/architectureDiagram-2XIMDMQ5-Djwpsh98.js +0 -36
- package/dist/assets/array-DOVTz2Mq.js +0 -1
- package/dist/assets/blockDiagram-WCTKOSBZ-BdW5TTxj.js +0 -132
- package/dist/assets/c4Diagram-IC4MRINW-DTmkHEXu.js +0 -10
- package/dist/assets/channel-B3MUFipN.js +0 -1
- package/dist/assets/chunk-4BX2VUAB-DEqzsvDc.js +0 -1
- package/dist/assets/chunk-55IACEB6-BzVuSUV8.js +0 -1
- package/dist/assets/chunk-7E7YKBS2-CZ8IcA4c.js +0 -1
- package/dist/assets/chunk-7R4GIKGN-CWVVC8HX.js +0 -79
- package/dist/assets/chunk-C72U2L5F-B1Tso5TH.js +0 -1
- package/dist/assets/chunk-EGIJ26TM-Cx_7CFik.js +0 -1
- package/dist/assets/chunk-FMBD7UC4-Cfk_iGhv.js +0 -15
- package/dist/assets/chunk-GEFDOKGD-C_5hRbJt.js +0 -2
- package/dist/assets/chunk-GLR3WWYH-CkY7IyBj.js +0 -2
- package/dist/assets/chunk-HHEYEP7N-B0I4X5cr.js +0 -1
- package/dist/assets/chunk-JSJVCQXG-CAjwlVLg.js +0 -1
- package/dist/assets/chunk-KX2RTZJC-DWqnZZ02.js +0 -1
- package/dist/assets/chunk-KYZI473N-gjRVhJgJ.js +0 -53
- package/dist/assets/chunk-L3YUKLVL-D7C9GuxL.js +0 -1
- package/dist/assets/chunk-MX3YWQON-i-77iuVj.js +0 -1
- package/dist/assets/chunk-NQ4KR5QH-B22Pvemm.js +0 -220
- package/dist/assets/chunk-O4XLMI2P-ZQd5L6ZD.js +0 -7
- package/dist/assets/chunk-OZEHJAEY-BaPKTELw.js +0 -1
- package/dist/assets/chunk-PQ6SQG4A-DqE1eupT.js +0 -1
- package/dist/assets/chunk-PU5JKC2W-BTqWqedh.js +0 -70
- package/dist/assets/chunk-QZHKN3VN-Nm9TvMss.js +0 -1
- package/dist/assets/chunk-R5LLSJPH-DkiNs1dN.js +0 -1
- package/dist/assets/chunk-WL4C6EOR-CioD2fv2.js +0 -189
- package/dist/assets/chunk-XIRO2GV7-B4GGQONY.js +0 -1
- package/dist/assets/chunk-XPW4576I-C0IbbQos.js +0 -32
- package/dist/assets/chunk-XZSTWKYB-DMOqFWmT.js +0 -94
- package/dist/assets/chunk-YBOYWFTD-CoeQgeVY.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-DV9ltQ7h.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-C6nD9wmM.js +0 -1
- package/dist/assets/clone-DuY6BQEm.js +0 -1
- package/dist/assets/cose-bilkent-S5V4N54A-B6FexK6p.js +0 -1
- package/dist/assets/cytoscape.esm-DoTFyJaN.js +0 -321
- package/dist/assets/dagre-CCcocoCU.js +0 -1
- package/dist/assets/dagre-KLK3FWXG-DIELowj9.js +0 -4
- package/dist/assets/defaultLocale-Ck2Xxk-C.js +0 -1
- package/dist/assets/diagram-E7M64L7V-D1mm0PoO.js +0 -24
- package/dist/assets/diagram-IFDJBPK2-7DVjly8y.js +0 -43
- package/dist/assets/diagram-P4PSJMXO-jO7pfyMb.js +0 -24
- package/dist/assets/dist-BywRdrPx.js +0 -1
- package/dist/assets/erDiagram-INFDFZHY-DSRxlRFy.js +0 -70
- package/dist/assets/flowDiagram-PKNHOUZH-CgKzzNdR.js +0 -162
- package/dist/assets/ganttDiagram-A5KZAMGK-CtsE7Y4E.js +0 -292
- package/dist/assets/gitGraph-HDMCJU4V-BU9uhwtz.js +0 -1
- package/dist/assets/gitGraphDiagram-K3NZZRJ6-DOU8RGdw.js +0 -65
- package/dist/assets/graphlib-WkJoBgka.js +0 -1
- package/dist/assets/index-CKVArt9D.js +0 -562
- package/dist/assets/index-DzRKJazf.css +0 -2
- package/dist/assets/info-3K5VOQVL-CPpvM-SG.js +0 -1
- package/dist/assets/infoDiagram-LFFYTUFH-VKLs5DsF.js +0 -2
- package/dist/assets/init-Bft5Ffpj.js +0 -1
- package/dist/assets/isArrayLikeObject-icl0H0jo.js +0 -1
- package/dist/assets/isEmpty-Du8sNmkE.js +0 -1
- package/dist/assets/ishikawaDiagram-PHBUUO56-CsWvEjux.js +0 -70
- package/dist/assets/journeyDiagram-4ABVD52K-BzJGTdIT.js +0 -139
- package/dist/assets/kanban-definition-K7BYSVSG-B_9ClJ1A.js +0 -89
- package/dist/assets/katex-BJrMXEjr.js +0 -261
- package/dist/assets/line-CC_tDGId.js +0 -1
- package/dist/assets/linear-Cts_d04Y.js +0 -1
- package/dist/assets/math-CNhlSIO3.js +0 -1
- package/dist/assets/mermaid-parser.core-Vb9KKv1R.js +0 -4
- package/dist/assets/mermaid.core-C_7xsp3d.js +0 -11
- package/dist/assets/mindmap-definition-YRQLILUH-BWmfy5wB.js +0 -68
- package/dist/assets/ordinal-DIg8h6NI.js +0 -1
- package/dist/assets/packet-RMMSAZCW-Q-WG6o3b.js +0 -1
- package/dist/assets/path-DfRbCp9y.js +0 -1
- package/dist/assets/pie-UPGHQEXC-Cwi2tLlt.js +0 -1
- package/dist/assets/pieDiagram-SKSYHLDU-Dyf3X_in.js +0 -30
- package/dist/assets/quadrantDiagram-337W2JSQ-B5_5m61Q.js +0 -7
- package/dist/assets/radar-KQ55EAFF-Dtw2VzxY.js +0 -1
- package/dist/assets/requirementDiagram-Z7DCOOCP-BSERBnlW.js +0 -73
- package/dist/assets/rough.esm-KjoEK0it.js +0 -1
- package/dist/assets/sankeyDiagram-WA2Y5GQK-CMcEY8Cz.js +0 -10
- package/dist/assets/sequenceDiagram-2WXFIKYE-D28qcXwC.js +0 -145
- package/dist/assets/src-C8kkzlHX.js +0 -1
- package/dist/assets/stateDiagram-RAJIS63D-7oVrCmRl.js +0 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-DtFptQAd.js +0 -1
- package/dist/assets/timeline-definition-YZTLITO2-rbCfBEvG.js +0 -61
- package/dist/assets/treemap-KZPCXAKY-BlRvF0um.js +0 -1
- package/dist/assets/vennDiagram-LZ73GAT5-DBit3zWa.js +0 -34
- package/dist/assets/xychartDiagram-JWTSCODW-BVYXv51y.js +0 -7
- package/dist/index.js +0 -1040
package/docs/design.md
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
# Comment Storage Design
|
|
2
|
+
|
|
3
|
+
This document describes the design for storing highlights, comments, and document state in plain markdown files instead of localStorage.
|
|
4
|
+
|
|
5
|
+
## Design Goals
|
|
6
|
+
|
|
7
|
+
| Priority | Goal | Description |
|
|
8
|
+
|----------|------|-------------|
|
|
9
|
+
| 1 | **Hackability** | Simple, understandable, editable with any text editor |
|
|
10
|
+
| 2 | **Simplicity** | Minimal complexity, easy to implement |
|
|
11
|
+
| 3 | **Versioning** | Git-native, full history support |
|
|
12
|
+
| 4 | **Diffing** | Human-readable, line-based diffs |
|
|
13
|
+
| 5 | **Performance** | Fast reads/writes for typical use cases |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Current State (localStorage)
|
|
18
|
+
|
|
19
|
+
The current implementation uses browser localStorage:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
interface Comment {
|
|
23
|
+
id: string; // UUID
|
|
24
|
+
selectedText: string; // Highlighted text
|
|
25
|
+
comment: string; // User's comment
|
|
26
|
+
createdAt: string; // ISO 8601 timestamp
|
|
27
|
+
startOffset: number; // Character offset (start)
|
|
28
|
+
endOffset: number; // Character offset (end)
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Storage key**: `readit-comments-{filePath}`
|
|
33
|
+
|
|
34
|
+
### Limitations
|
|
35
|
+
|
|
36
|
+
1. **Browser-only**: Comments don't persist across devices
|
|
37
|
+
2. **No versioning**: No history, no git integration
|
|
38
|
+
3. **Offset fragility**: Character offsets break when document changes
|
|
39
|
+
4. **Not shareable**: Manual export required to share reviews
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Storage Location
|
|
44
|
+
|
|
45
|
+
### Global Home Directory: `~/.readit/`
|
|
46
|
+
|
|
47
|
+
readit is a global CLI tool. All comment files are stored in a single location:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
~/.readit/
|
|
51
|
+
└── comments/
|
|
52
|
+
└── {path-based-structure}/
|
|
53
|
+
└── {filename}.comments.md
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Why Global `~/.readit/`?
|
|
57
|
+
|
|
58
|
+
| Concern | Solution |
|
|
59
|
+
|---------|----------|
|
|
60
|
+
| No scattered `.readit/` folders | Single location for all comments |
|
|
61
|
+
| Easy to find | `~/.readit/comments/` contains everything |
|
|
62
|
+
| Easy to backup | Copy one directory |
|
|
63
|
+
| Cross-project | Works the same for any file, anywhere |
|
|
64
|
+
| Precedent | Similar to `~/.ssh/`, `~/.config/`, `~/.docker/` |
|
|
65
|
+
|
|
66
|
+
### Path Structure
|
|
67
|
+
|
|
68
|
+
Comments are stored using the **absolute path** of the source file, converted to a directory structure:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Source file: /home/user/projects/app/README.md
|
|
72
|
+
Comment file: ~/.readit/comments/home/user/projects/app/README.comments.md
|
|
73
|
+
|
|
74
|
+
Source file: /Users/jay/docs/design.md
|
|
75
|
+
Comment file: ~/.readit/comments/Users/jay/docs/design.comments.md
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Path Resolution Algorithm
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import * as path from 'path';
|
|
82
|
+
import * as os from 'os';
|
|
83
|
+
|
|
84
|
+
function getCommentPath(sourcePath: string): string {
|
|
85
|
+
// Resolve to absolute path
|
|
86
|
+
const absolute = path.resolve(sourcePath);
|
|
87
|
+
|
|
88
|
+
// Remove leading slash and drive letter (Windows)
|
|
89
|
+
const normalized = absolute.replace(/^\//, '').replace(/^[A-Z]:/, '');
|
|
90
|
+
|
|
91
|
+
// Get filename without extension
|
|
92
|
+
const ext = path.extname(normalized);
|
|
93
|
+
const withoutExt = normalized.slice(0, -ext.length);
|
|
94
|
+
|
|
95
|
+
// Construct comment file path
|
|
96
|
+
return path.join(
|
|
97
|
+
os.homedir(),
|
|
98
|
+
'.readit',
|
|
99
|
+
'comments',
|
|
100
|
+
`${withoutExt}.comments.md`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Examples
|
|
106
|
+
|
|
107
|
+
| Source Path | Comment File Path |
|
|
108
|
+
|-------------|-------------------|
|
|
109
|
+
| `./README.md` (from `/home/user/project`) | `~/.readit/comments/home/user/project/README.comments.md` |
|
|
110
|
+
| `/tmp/notes.md` | `~/.readit/comments/tmp/notes.comments.md` |
|
|
111
|
+
| `../shared/doc.md` (from `/home/user/project`) | `~/.readit/comments/home/user/shared/doc.comments.md` |
|
|
112
|
+
| `~/Desktop/review.html` | `~/.readit/comments/Users/jay/Desktop/review.comments.md` |
|
|
113
|
+
|
|
114
|
+
### Edge Cases
|
|
115
|
+
|
|
116
|
+
**Same filename, different directories**: No collision—absolute paths differ.
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
/home/user/project-a/README.md → ~/.readit/comments/home/user/project-a/README.comments.md
|
|
120
|
+
/home/user/project-b/README.md → ~/.readit/comments/home/user/project-b/README.comments.md
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Relative paths from different CWDs**: Resolve to same absolute path → same comment file.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# From /project/src/
|
|
127
|
+
readit ../README.md # → /project/README.md
|
|
128
|
+
|
|
129
|
+
# From /project/src/deep/
|
|
130
|
+
readit ../../README.md # → /project/README.md (same file, same comments)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## File Format
|
|
136
|
+
|
|
137
|
+
```markdown
|
|
138
|
+
---
|
|
139
|
+
source: /home/user/project/README.md
|
|
140
|
+
hash: e3b0c44298fc1c14
|
|
141
|
+
version: 1
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
<!-- c:550e8400|L42|2025-12-24T10:30:00+09:00 -->
|
|
145
|
+
> the exact selected text from the document
|
|
146
|
+
|
|
147
|
+
My review comment here. Full markdown supported.
|
|
148
|
+
Can span multiple paragraphs.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
<!-- c:660f9511|L57-59|2025-12-24T11:00:00+09:00 -->
|
|
153
|
+
> another piece of selected text
|
|
154
|
+
> that spans multiple lines
|
|
155
|
+
|
|
156
|
+
Another comment with my thoughts.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Format Breakdown
|
|
162
|
+
|
|
163
|
+
#### 1. YAML Front Matter
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
---
|
|
167
|
+
source: /home/user/project/README.md # Absolute path to source file
|
|
168
|
+
hash: e3b0c44298fc1c14 # SHA-256 prefix (16 chars) of source content
|
|
169
|
+
version: 1 # Format version for future compatibility
|
|
170
|
+
---
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Purpose**: Document-level metadata for validation and future-proofing.
|
|
174
|
+
|
|
175
|
+
#### 2. Comment Metadata (HTML Comment)
|
|
176
|
+
|
|
177
|
+
```html
|
|
178
|
+
<!-- c:550e8400|L42|2025-12-24T10:30:00+09:00 -->
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Format**: `c:{id}|{line-hint}|{timestamp}`
|
|
182
|
+
|
|
183
|
+
| Field | Description | Example |
|
|
184
|
+
|-------|-------------|---------|
|
|
185
|
+
| `id` | UUID prefix (8 chars) | `550e8400` |
|
|
186
|
+
| `line-hint` | Line number(s) in source | `L42` or `L42-45` |
|
|
187
|
+
| `timestamp` | ISO 8601 with timezone | `2025-12-24T10:30:00+09:00` |
|
|
188
|
+
|
|
189
|
+
**Why HTML comment?**
|
|
190
|
+
|
|
191
|
+
- Invisible when rendered in markdown viewers
|
|
192
|
+
- Single line = clean git diffs
|
|
193
|
+
- Grep-friendly for tooling
|
|
194
|
+
|
|
195
|
+
#### 3. Selected Text (Blockquote)
|
|
196
|
+
|
|
197
|
+
```markdown
|
|
198
|
+
> the exact selected text from the document
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Purpose**: Primary anchor for matching. The text itself is the anchor.
|
|
202
|
+
|
|
203
|
+
**Multi-line selections**:
|
|
204
|
+
|
|
205
|
+
```markdown
|
|
206
|
+
> first line of selection
|
|
207
|
+
> second line of selection
|
|
208
|
+
> third line of selection
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
#### 4. Comment Body (Plain Text)
|
|
212
|
+
|
|
213
|
+
```markdown
|
|
214
|
+
My review comment here. Full markdown supported.
|
|
215
|
+
Can span multiple paragraphs.
|
|
216
|
+
|
|
217
|
+
- Lists work
|
|
218
|
+
- **Bold** and *italic* work
|
|
219
|
+
- `code` works
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### 5. Separator
|
|
223
|
+
|
|
224
|
+
```markdown
|
|
225
|
+
---
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Purpose**: Visual separation between comments, easy parsing boundary.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Anchoring Strategy
|
|
233
|
+
|
|
234
|
+
### The Problem
|
|
235
|
+
|
|
236
|
+
Character offsets are fragile. When the source document changes:
|
|
237
|
+
|
|
238
|
+
- Insertions shift all following offsets
|
|
239
|
+
- Deletions shift all following offsets
|
|
240
|
+
- Even whitespace changes break anchors
|
|
241
|
+
|
|
242
|
+
### The Solution: Text-First Anchoring
|
|
243
|
+
|
|
244
|
+
**Primary anchor**: The selected text itself
|
|
245
|
+
**Secondary hint**: Line number for fast lookup
|
|
246
|
+
|
|
247
|
+
### Matching Algorithm
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
findAnchor(source, selectedText, lineHint):
|
|
251
|
+
1. Fast path: Search near line hint (±500 chars)
|
|
252
|
+
- If found, return position
|
|
253
|
+
|
|
254
|
+
2. Fallback: Global search in document
|
|
255
|
+
- If found, return position
|
|
256
|
+
|
|
257
|
+
3. Fuzzy: Levenshtein distance matching (optional)
|
|
258
|
+
- For handling minor edits to selected text
|
|
259
|
+
|
|
260
|
+
4. Fail: Return null, show warning in UI
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Resilience
|
|
264
|
+
|
|
265
|
+
| Document Change | Effect on Anchors |
|
|
266
|
+
|-----------------|-------------------|
|
|
267
|
+
| Insert text before selection | Line hint shifts, text match still works |
|
|
268
|
+
| Insert text after selection | No effect |
|
|
269
|
+
| Edit unrelated text | No effect |
|
|
270
|
+
| Edit selected text | Fuzzy match or manual re-anchor |
|
|
271
|
+
| Delete selected text | Anchor fails, user notified |
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Edge Cases
|
|
276
|
+
|
|
277
|
+
### Same text appears multiple times
|
|
278
|
+
|
|
279
|
+
```markdown
|
|
280
|
+
> the
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Solution**: Line hint disambiguates. Take the match closest to the hinted line.
|
|
284
|
+
|
|
285
|
+
### Selected text was edited
|
|
286
|
+
|
|
287
|
+
**Solution**:
|
|
288
|
+
|
|
289
|
+
1. Fuzzy matching with Levenshtein distance
|
|
290
|
+
2. Show "anchor uncertain" warning
|
|
291
|
+
3. User can re-anchor manually
|
|
292
|
+
|
|
293
|
+
### Document hash mismatch
|
|
294
|
+
|
|
295
|
+
**UI Warning**:
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
Document has changed since review.
|
|
299
|
+
Some comments may be misaligned.
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Empty selection or whitespace-only
|
|
303
|
+
|
|
304
|
+
**Prevention**: Reject selections that are empty or whitespace-only at input time.
|
|
305
|
+
|
|
306
|
+
### Very long selections
|
|
307
|
+
|
|
308
|
+
**Practical limit**: Store full text up to 1000 chars. Beyond that, store first 500 + `...` + last 500.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Known Limitations
|
|
313
|
+
|
|
314
|
+
### Single-Tab Per File
|
|
315
|
+
|
|
316
|
+
readit does not support multiple browser tabs reviewing the same file simultaneously.
|
|
317
|
+
If two tabs edit comments for the same file, the last write wins and earlier changes may be lost.
|
|
318
|
+
|
|
319
|
+
**Workaround**: Close other tabs before starting a new review session.
|
|
320
|
+
|
|
321
|
+
### Cross-Machine Path Differences
|
|
322
|
+
|
|
323
|
+
Comments are stored using absolute paths. If you sync `~/.readit/` across machines
|
|
324
|
+
with different path structures (e.g., `/Users/jay/` on Mac vs `/home/jay/` on Linux),
|
|
325
|
+
comments will not be associated with the same source files.
|
|
326
|
+
|
|
327
|
+
**Workaround**: Use readit on a single machine, or ensure identical absolute paths.
|
|
328
|
+
|
|
329
|
+
### Export is a Transform
|
|
330
|
+
|
|
331
|
+
Export (JSON, prompt format) is a transformation of in-memory comments, not a storage format.
|
|
332
|
+
The canonical format is `.comments.md`.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Storage Protocol
|
|
337
|
+
|
|
338
|
+
### Data Types
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
interface CommentFile {
|
|
342
|
+
source: string; // Absolute path to source file
|
|
343
|
+
hash: string; // SHA-256 prefix (16 chars) of source content
|
|
344
|
+
version: number; // Format version
|
|
345
|
+
comments: Comment[];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
interface Comment {
|
|
349
|
+
id: string; // Full UUID (stored as 8-char prefix in file)
|
|
350
|
+
selectedText: string; // Primary anchor (the quoted text)
|
|
351
|
+
lineHint: string; // "L42" or "L42-45"
|
|
352
|
+
createdAt: string; // ISO 8601 with timezone
|
|
353
|
+
body: string; // Comment text (markdown)
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Read Protocol
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
1. Resolve source path to absolute path
|
|
361
|
+
2. Compute comment file path: ~/.readit/comments/{path}.comments.md
|
|
362
|
+
3. Check if comment file exists (no file = no comments)
|
|
363
|
+
4. Parse YAML front matter
|
|
364
|
+
5. Verify source path matches (warn if different)
|
|
365
|
+
6. Compare hash with current source content (warn if mismatch)
|
|
366
|
+
7. Split body by "---" separator
|
|
367
|
+
8. For each block:
|
|
368
|
+
a. Extract metadata from HTML comment
|
|
369
|
+
b. Extract selected text from blockquote
|
|
370
|
+
c. Extract body (remaining text)
|
|
371
|
+
9. Resolve anchors to character offsets using text matching
|
|
372
|
+
10. Return resolved comments for rendering
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Write Protocol
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
1. Resolve source path to absolute path
|
|
379
|
+
2. Compute comment file path: ~/.readit/comments/{path}.comments.md
|
|
380
|
+
3. Ensure parent directories exist (mkdir -p)
|
|
381
|
+
4. Compute source content hash (SHA-256, take first 16 chars)
|
|
382
|
+
5. Generate YAML front matter with source, hash, version
|
|
383
|
+
6. For each comment:
|
|
384
|
+
a. Compute current line number for selected text
|
|
385
|
+
b. Format metadata as HTML comment
|
|
386
|
+
c. Format selected text as blockquote (prefix each line with "> ")
|
|
387
|
+
d. Append comment body
|
|
388
|
+
e. Add "---" separator
|
|
389
|
+
7. Write to temp file
|
|
390
|
+
8. Atomic rename to final path
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Sync on Source Change
|
|
394
|
+
|
|
395
|
+
```
|
|
396
|
+
On source file load:
|
|
397
|
+
1. Compute current source hash
|
|
398
|
+
2. Compare with stored hash in comment file
|
|
399
|
+
3. If different:
|
|
400
|
+
- Re-run anchor matching for all comments
|
|
401
|
+
- Update line hints to current positions
|
|
402
|
+
- Flag any unresolved anchors
|
|
403
|
+
- Update stored hash
|
|
404
|
+
- Save updated comment file
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## CLI Integration
|
|
410
|
+
|
|
411
|
+
### Commands
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
# Review a file (opens browser UI)
|
|
415
|
+
readit <file>
|
|
416
|
+
|
|
417
|
+
# Review with options
|
|
418
|
+
readit <file> --port 3000 # Custom port
|
|
419
|
+
readit <file> --no-open # Don't auto-open browser
|
|
420
|
+
readit <file> --clean # Clear existing comments for this file
|
|
421
|
+
|
|
422
|
+
# List all commented files
|
|
423
|
+
readit list
|
|
424
|
+
|
|
425
|
+
# Show comments for a file (without opening UI)
|
|
426
|
+
readit show <file>
|
|
427
|
+
|
|
428
|
+
# Export comments
|
|
429
|
+
readit export <file> --format json
|
|
430
|
+
readit export <file> --format prompt
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Server API
|
|
434
|
+
|
|
435
|
+
The Express server provides endpoints for the browser UI:
|
|
436
|
+
|
|
437
|
+
```
|
|
438
|
+
GET /api/comments # Get comments for current file
|
|
439
|
+
POST /api/comments # Add a new comment
|
|
440
|
+
PUT /api/comments/:id # Update a comment
|
|
441
|
+
DELETE /api/comments/:id # Delete a comment
|
|
442
|
+
GET /api/source # Get source file info (path, hash)
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Performance
|
|
448
|
+
|
|
449
|
+
| Operation | Complexity | Notes |
|
|
450
|
+
|-----------|------------|-------|
|
|
451
|
+
| Load comments | O(n) | Parse markdown once, n = number of comments |
|
|
452
|
+
| Find anchor | O(1) avg | Line hint + local text search |
|
|
453
|
+
| Save comments | O(n) | Full file rewrite |
|
|
454
|
+
| Add comment | O(n) | Append + rewrite |
|
|
455
|
+
|
|
456
|
+
For typical use (<100 comments per file), all operations are instantaneous.
|
|
457
|
+
|
|
458
|
+
### File System Considerations
|
|
459
|
+
|
|
460
|
+
- Comment files are small (typically <100KB)
|
|
461
|
+
- Directory structure mirrors source paths (may be deep)
|
|
462
|
+
- Atomic writes via temp file + rename
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## Migration from localStorage
|
|
467
|
+
|
|
468
|
+
### Strategy
|
|
469
|
+
|
|
470
|
+
1. On app load, check for localStorage data for current file
|
|
471
|
+
2. If localStorage has data and no comment file exists:
|
|
472
|
+
- Prompt user to migrate
|
|
473
|
+
- Convert comments to new format
|
|
474
|
+
- Write to `~/.readit/comments/...`
|
|
475
|
+
- Optionally clear localStorage
|
|
476
|
+
3. If both exist:
|
|
477
|
+
- Prefer file-based (authoritative)
|
|
478
|
+
- Warn about localStorage orphan
|
|
479
|
+
|
|
480
|
+
### Conversion
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
function migrateComment(old: OldComment, sourceContent: string): NewComment {
|
|
484
|
+
return {
|
|
485
|
+
id: old.id.slice(0, 8),
|
|
486
|
+
selectedText: old.selectedText,
|
|
487
|
+
lineHint: `L${getLineNumber(sourceContent, old.startOffset)}`,
|
|
488
|
+
createdAt: old.createdAt,
|
|
489
|
+
body: old.comment,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function getLineNumber(content: string, offset: number): number {
|
|
494
|
+
return content.slice(0, offset).split('\n').length;
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## Future Considerations
|
|
501
|
+
|
|
502
|
+
### Local `.readit/` Override
|
|
503
|
+
|
|
504
|
+
For projects that want to commit comments with the source code:
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
# Initialize local .readit/ in current directory
|
|
508
|
+
readit init --local
|
|
509
|
+
|
|
510
|
+
# Now comments for files in this directory tree use local storage
|
|
511
|
+
./project/.readit/comments/README.comments.md
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
**Lookup order** (future):
|
|
515
|
+
|
|
516
|
+
1. Check for `.readit/` in source file's directory (or ancestors)
|
|
517
|
+
2. If found, use local storage
|
|
518
|
+
3. Otherwise, use global `~/.readit/`
|
|
519
|
+
|
|
520
|
+
### Potential Extensions
|
|
521
|
+
|
|
522
|
+
1. **Author field**: `<!-- c:abc123|L42|2025-12-24|@username -->`
|
|
523
|
+
2. **Status/resolution**: Resolved, won't fix, etc.
|
|
524
|
+
3. **Replies**: Nested comments (threaded discussion)
|
|
525
|
+
4. **Tags/categories**: Bug, question, suggestion, etc.
|
|
526
|
+
|
|
527
|
+
### Format Versioning
|
|
528
|
+
|
|
529
|
+
The `version: 1` field in front matter allows future format changes:
|
|
530
|
+
|
|
531
|
+
```yaml
|
|
532
|
+
---
|
|
533
|
+
version: 2
|
|
534
|
+
# New fields in v2...
|
|
535
|
+
---
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
Parser can handle multiple versions for backwards compatibility.
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## Decision Log
|
|
543
|
+
|
|
544
|
+
| Date | Decision | Rationale |
|
|
545
|
+
|------|----------|-----------|
|
|
546
|
+
| 2025-12-24 | Global `~/.readit/` storage | readit is a global CLI; avoids scattered `.readit/` folders |
|
|
547
|
+
| 2025-12-24 | Path-based directory structure | Intuitive mapping from source path to comment file |
|
|
548
|
+
| 2025-12-24 | Absolute path in front matter | Self-documenting, enables validation |
|
|
549
|
+
| 2025-12-24 | Text-based anchoring over offsets | Resilient to document changes |
|
|
550
|
+
| 2025-12-24 | Markdown format over YAML/JSON | Human-readable, hackable, editable |
|
|
551
|
+
| 2025-12-24 | HTML comments for metadata | Invisible in viewers, clean git diffs |
|
|
552
|
+
| 2025-12-24 | Blockquotes for selected text | Standard markdown, visually distinct |
|
|
553
|
+
| 2025-12-24 | 8-char UUID prefix | Collision-resistant, grep-friendly |
|
|
554
|
+
| 2025-12-24 | Local `.readit/` as future option | Deferred to keep v1 simple |
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## References
|
|
559
|
+
|
|
560
|
+
- Original localStorage implementation: `src/hooks/useComments.ts`
|
|
561
|
+
- Current types: `src/types/index.ts`
|
|
562
|
+
- Export utilities: `src/lib/export.ts`
|
|
563
|
+
- Inspiration: [difit](https://github.com/yoshiko-pg/difit) - local code review for AI era
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Client Mode: `readit open`
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
readit starts a long-running server per invocation. When used as a Claude Code PostToolUse hook, every Write/Edit on a `.md` file would spawn a new server instance. We need a way to send files to an already-running server.
|
|
6
|
+
|
|
7
|
+
## Design
|
|
8
|
+
|
|
9
|
+
### Overview
|
|
10
|
+
|
|
11
|
+
Add a `readit open <files...>` CLI command that hot-adds files to a running readit server via HTTP, or starts a new server if none exists. Uses a PID file for server discovery and the existing SSE infrastructure for browser notifications.
|
|
12
|
+
|
|
13
|
+
### 1. PID File (`~/.readit/server.json`)
|
|
14
|
+
|
|
15
|
+
**On server start** (in `startServer`):
|
|
16
|
+
```json
|
|
17
|
+
{ "port": 4567, "pid": 12345 }
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**On shutdown** (SIGINT handler): delete the file.
|
|
21
|
+
|
|
22
|
+
**Client discovery:**
|
|
23
|
+
1. Read `~/.readit/server.json`
|
|
24
|
+
2. Verify PID is alive (`process.kill(pid, 0)`)
|
|
25
|
+
3. Confirm via `GET /api/health`
|
|
26
|
+
4. Any failure → treat as no server running
|
|
27
|
+
|
|
28
|
+
### 2. Server Endpoint: `POST /api/files`
|
|
29
|
+
|
|
30
|
+
Request body: `{ "path": "/absolute/path/to/file.md" }`
|
|
31
|
+
|
|
32
|
+
Behavior:
|
|
33
|
+
- **File already loaded** → re-read from disk, update `fileMap` content, broadcast SSE `{ type: "update", path }`
|
|
34
|
+
- **New file** → validate type, read content, add to `fileMap` + `fileOrder`, set up file watcher, broadcast SSE `{ type: "file-added", path, fileName, fileType }`
|
|
35
|
+
|
|
36
|
+
Response: `200 { path, fileName, type }`
|
|
37
|
+
|
|
38
|
+
### 3. Frontend: Handle `file-added` SSE Event
|
|
39
|
+
|
|
40
|
+
The Zustand store's SSE listener handles a new event type:
|
|
41
|
+
- `file-added` → add document to store, render new tab (lazy content fetch via existing pattern)
|
|
42
|
+
|
|
43
|
+
### 4. CLI Command: `readit open`
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
readit open <files...> # Add files to running server or start new one
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Logic:
|
|
50
|
+
1. Resolve and validate file paths (must exist, must be supported type)
|
|
51
|
+
2. Discover running server via PID file + health check
|
|
52
|
+
3. If server found → `POST /api/files` for each file
|
|
53
|
+
4. If no server → start server in foreground with the given files (same as default command)
|
|
54
|
+
|
|
55
|
+
### 5. Hook Configuration
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"PostToolUse": [
|
|
60
|
+
{
|
|
61
|
+
"matcher": "Write|Edit",
|
|
62
|
+
"hooks": [
|
|
63
|
+
{
|
|
64
|
+
"type": "command",
|
|
65
|
+
"command": "bash -c 'FILE=$(cat | jq -r \".tool_input.file_path\"); if [[ \"$FILE\" == *.md ]]; then bunx readit open \"$FILE\"; fi; exit 0'"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Files to Modify
|
|
74
|
+
|
|
75
|
+
- `src/server/index.ts` — PID file write/cleanup, `POST /api/files` endpoint, file watcher setup for hot-added files
|
|
76
|
+
- `src/cli/index.ts` — new `open` subcommand with server discovery logic
|
|
77
|
+
- Frontend store — handle `file-added` SSE event type
|
|
78
|
+
|
|
79
|
+
## Verification
|
|
80
|
+
|
|
81
|
+
1. Start `readit test.md`
|
|
82
|
+
2. Run `readit open other.md` in another terminal
|
|
83
|
+
3. Confirm new tab appears in browser with `other.md`
|
|
84
|
+
4. Edit `other.md` on disk → confirm live reload works
|
|
85
|
+
5. Kill server, run `readit open test.md` → confirm new server starts
|
|
86
|
+
6. Run `readit open test.md` again (already loaded) → confirm content refreshes without duplicate tab
|