@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
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context extraction and formatting utilities for LLM clipboard copy.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
interface ContextResult {
|
|
6
|
+
lines: string[]; // Lines with >>> markers inserted
|
|
7
|
+
startLine: number; // 1-based line number
|
|
8
|
+
endLine: number; // 1-based line number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface FormatOptions {
|
|
12
|
+
context: ContextResult;
|
|
13
|
+
fileName: string;
|
|
14
|
+
comment?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parameters for context extraction.
|
|
19
|
+
* Using object destructuring per style guide §3.5 for clarity.
|
|
20
|
+
*/
|
|
21
|
+
export interface ExtractContextParams {
|
|
22
|
+
content: string;
|
|
23
|
+
startOffset: number;
|
|
24
|
+
endOffset: number;
|
|
25
|
+
contextLines?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DEFAULT_CONTEXT_LINES = 2;
|
|
29
|
+
const MAX_SELECTION_LINES = 10;
|
|
30
|
+
const MAX_LINE_LENGTH = 200;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Strip HTML tags to get plain text matching TreeWalker offset calculation.
|
|
34
|
+
*/
|
|
35
|
+
export function stripHtmlTags(html: string): string {
|
|
36
|
+
// Remove script/style content entirely
|
|
37
|
+
let text = html.replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/gi, "");
|
|
38
|
+
// Remove all HTML tags
|
|
39
|
+
text = text.replace(/<[^>]+>/g, "");
|
|
40
|
+
// Decode common named entities
|
|
41
|
+
text = text.replace(/ /g, " ");
|
|
42
|
+
text = text.replace(/</g, "<");
|
|
43
|
+
text = text.replace(/>/g, ">");
|
|
44
|
+
text = text.replace(/&/g, "&");
|
|
45
|
+
text = text.replace(/"/g, '"');
|
|
46
|
+
text = text.replace(/'/g, "'");
|
|
47
|
+
// Decode numeric entities (decimal and hex)
|
|
48
|
+
text = text.replace(/&#(\d+);/g, (_, code) =>
|
|
49
|
+
String.fromCharCode(Number.parseInt(code, 10)),
|
|
50
|
+
);
|
|
51
|
+
text = text.replace(/&#x([0-9a-f]+);/gi, (_, code) =>
|
|
52
|
+
String.fromCharCode(Number.parseInt(code, 16)),
|
|
53
|
+
);
|
|
54
|
+
return text;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Detect if content is HTML based on presence of HTML tags.
|
|
59
|
+
*/
|
|
60
|
+
function isHtml(content: string): boolean {
|
|
61
|
+
return /<[a-z][\s\S]*>/i.test(content);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Truncate a line if it exceeds max length.
|
|
66
|
+
*/
|
|
67
|
+
function truncateLine(
|
|
68
|
+
line: string,
|
|
69
|
+
maxLength: number = MAX_LINE_LENGTH,
|
|
70
|
+
): string {
|
|
71
|
+
if (line.length <= maxLength) return line;
|
|
72
|
+
return `${line.slice(0, maxLength - 3)}...`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extract context around a selection using character offsets.
|
|
77
|
+
* Returns lines with >>> and <<< markers inserted at selection boundaries.
|
|
78
|
+
*/
|
|
79
|
+
export function extractContext({
|
|
80
|
+
content,
|
|
81
|
+
startOffset,
|
|
82
|
+
endOffset,
|
|
83
|
+
contextLines = DEFAULT_CONTEXT_LINES,
|
|
84
|
+
}: ExtractContextParams): ContextResult {
|
|
85
|
+
// For HTML, strip tags to match offset calculation
|
|
86
|
+
const textContent = isHtml(content) ? stripHtmlTags(content) : content;
|
|
87
|
+
// Normalize CRLF to LF for consistent offset calculation
|
|
88
|
+
const normalizedContent = textContent.replace(/\r\n/g, "\n");
|
|
89
|
+
|
|
90
|
+
const lines = normalizedContent.split("\n");
|
|
91
|
+
let currentOffset = 0;
|
|
92
|
+
let startLineIndex = -1;
|
|
93
|
+
let endLineIndex = -1;
|
|
94
|
+
let startCharInLine = 0;
|
|
95
|
+
let endCharInLine = 0;
|
|
96
|
+
|
|
97
|
+
// Find lines containing the selection
|
|
98
|
+
for (let i = 0; i < lines.length; i++) {
|
|
99
|
+
const lineLength = lines[i].length;
|
|
100
|
+
const lineEnd = currentOffset + lineLength;
|
|
101
|
+
|
|
102
|
+
if (startLineIndex === -1 && lineEnd >= startOffset) {
|
|
103
|
+
startLineIndex = i;
|
|
104
|
+
startCharInLine = startOffset - currentOffset;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (lineEnd >= endOffset) {
|
|
108
|
+
endLineIndex = i;
|
|
109
|
+
endCharInLine = endOffset - currentOffset;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
currentOffset += lineLength + 1; // +1 for newline
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle edge case: couldn't find lines
|
|
117
|
+
if (startLineIndex === -1) startLineIndex = 0;
|
|
118
|
+
if (endLineIndex === -1) endLineIndex = lines.length - 1;
|
|
119
|
+
|
|
120
|
+
// Calculate context range
|
|
121
|
+
const contextStart = Math.max(0, startLineIndex - contextLines);
|
|
122
|
+
const contextEnd = Math.min(lines.length - 1, endLineIndex + contextLines);
|
|
123
|
+
|
|
124
|
+
// Build output lines with markers
|
|
125
|
+
const outputLines: string[] = [];
|
|
126
|
+
const selectionSpan = endLineIndex - startLineIndex + 1;
|
|
127
|
+
const shouldTruncateMiddle = selectionSpan > MAX_SELECTION_LINES;
|
|
128
|
+
|
|
129
|
+
for (let i = contextStart; i <= contextEnd; i++) {
|
|
130
|
+
let line = lines[i];
|
|
131
|
+
|
|
132
|
+
// Handle truncation for very long selections
|
|
133
|
+
if (shouldTruncateMiddle) {
|
|
134
|
+
const showStart = startLineIndex + 2;
|
|
135
|
+
const showEnd = endLineIndex - 2;
|
|
136
|
+
|
|
137
|
+
if (i > showStart && i < showEnd) {
|
|
138
|
+
if (i === showStart + 1) {
|
|
139
|
+
outputLines.push("...");
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Insert markers for selection boundaries
|
|
146
|
+
if (i === startLineIndex && i === endLineIndex) {
|
|
147
|
+
// Single line selection
|
|
148
|
+
const before = line.slice(0, startCharInLine);
|
|
149
|
+
const selected = line.slice(startCharInLine, endCharInLine);
|
|
150
|
+
const after = line.slice(endCharInLine);
|
|
151
|
+
line = `${before}>>> ${selected} <<<${after}`;
|
|
152
|
+
} else if (i === startLineIndex) {
|
|
153
|
+
// Start of multi-line selection
|
|
154
|
+
const before = line.slice(0, startCharInLine);
|
|
155
|
+
const selected = line.slice(startCharInLine);
|
|
156
|
+
line = `${before}>>> ${selected}`;
|
|
157
|
+
} else if (i === endLineIndex) {
|
|
158
|
+
// End of multi-line selection
|
|
159
|
+
const selected = line.slice(0, endCharInLine);
|
|
160
|
+
const after = line.slice(endCharInLine);
|
|
161
|
+
line = `${selected} <<<${after}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
outputLines.push(truncateLine(line));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
lines: outputLines,
|
|
169
|
+
startLine: startLineIndex + 1, // 1-based
|
|
170
|
+
endLine: endLineIndex + 1, // 1-based
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Format extracted context for LLM clipboard copy.
|
|
176
|
+
*/
|
|
177
|
+
export function formatForLLM({
|
|
178
|
+
context,
|
|
179
|
+
fileName,
|
|
180
|
+
comment,
|
|
181
|
+
}: FormatOptions): string {
|
|
182
|
+
const header = `# From: ${fileName}`;
|
|
183
|
+
const lineRange = `Lines ${context.startLine}-${context.endLine}:`;
|
|
184
|
+
const body = ["---", ...context.lines, "---"].join("\n");
|
|
185
|
+
|
|
186
|
+
const parts = [header, "", lineRange, body];
|
|
187
|
+
|
|
188
|
+
if (comment) {
|
|
189
|
+
parts.push("", `Comment: ${comment}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return parts.join("\n");
|
|
193
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { bench, describe } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
COMMENTS_1,
|
|
4
|
+
COMMENTS_10,
|
|
5
|
+
COMMENTS_50,
|
|
6
|
+
} from "./__fixtures__/bench-data";
|
|
7
|
+
import { generatePrompt, generateRawText } from "./export";
|
|
8
|
+
|
|
9
|
+
describe("generatePrompt", () => {
|
|
10
|
+
bench("1 comment", () => {
|
|
11
|
+
generatePrompt(COMMENTS_1, "test.md");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
bench("10 comments", () => {
|
|
15
|
+
generatePrompt(COMMENTS_10, "test.md");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
bench("50 comments", () => {
|
|
19
|
+
generatePrompt(COMMENTS_50, "test.md");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("generateRawText", () => {
|
|
24
|
+
bench("1 comment", () => {
|
|
25
|
+
generateRawText(COMMENTS_1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
bench("10 comments", () => {
|
|
29
|
+
generateRawText(COMMENTS_10);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
bench("50 comments", () => {
|
|
33
|
+
generateRawText(COMMENTS_50);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Comment, Document } from "../types";
|
|
2
|
+
|
|
3
|
+
export function generatePrompt(comments: Comment[], fileName: string): string {
|
|
4
|
+
const prompt = comments
|
|
5
|
+
.map((c) => {
|
|
6
|
+
return `---\nSelected text: "${c.selectedText}"\nComment: ${c.comment}`;
|
|
7
|
+
})
|
|
8
|
+
.join("\n\n");
|
|
9
|
+
|
|
10
|
+
return `# Review Comments for ${fileName}\n\n${prompt}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function generateRawText(comments: Comment[]): string {
|
|
14
|
+
return comments
|
|
15
|
+
.map((c) => `${c.selectedText}\n\n${c.comment}`)
|
|
16
|
+
.join("\n\n---\n\n");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function exportCommentsAsJson(
|
|
20
|
+
comments: Comment[],
|
|
21
|
+
document: Document,
|
|
22
|
+
): void {
|
|
23
|
+
const data = {
|
|
24
|
+
filePath: document.filePath,
|
|
25
|
+
fileName: document.fileName,
|
|
26
|
+
exportedAt: new Date().toISOString(),
|
|
27
|
+
comments: comments.map((c) => ({
|
|
28
|
+
selectedText: c.selectedText,
|
|
29
|
+
comment: c.comment,
|
|
30
|
+
createdAt: c.createdAt,
|
|
31
|
+
})),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const blob = new Blob([JSON.stringify(data, null, 2)], {
|
|
35
|
+
type: "application/json",
|
|
36
|
+
});
|
|
37
|
+
const url = URL.createObjectURL(blob);
|
|
38
|
+
const a = window.document.createElement("a");
|
|
39
|
+
a.href = url;
|
|
40
|
+
a.download = `${document.fileName}-comments.json`;
|
|
41
|
+
a.click();
|
|
42
|
+
URL.revokeObjectURL(url);
|
|
43
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color palette for comment highlights.
|
|
3
|
+
* Colors are assigned by document position (top-to-bottom).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const COMMENT_COLORS = [
|
|
7
|
+
{
|
|
8
|
+
name: "amber",
|
|
9
|
+
bg: "rgba(245, 222, 160, 0.5)",
|
|
10
|
+
bgFocused: "rgba(228, 195, 110, 0.65)",
|
|
11
|
+
border: "#c9a84a",
|
|
12
|
+
text: "#8b6914",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "blue",
|
|
16
|
+
bg: "rgba(168, 196, 228, 0.5)",
|
|
17
|
+
bgFocused: "rgba(130, 168, 210, 0.65)",
|
|
18
|
+
border: "#5b7fa8",
|
|
19
|
+
text: "#3d5f8a",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: "green",
|
|
23
|
+
bg: "rgba(170, 210, 170, 0.5)",
|
|
24
|
+
bgFocused: "rgba(130, 185, 135, 0.65)",
|
|
25
|
+
border: "#5a9a62",
|
|
26
|
+
text: "#3d6e45",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "rose",
|
|
30
|
+
bg: "rgba(225, 180, 185, 0.5)",
|
|
31
|
+
bgFocused: "rgba(205, 145, 155, 0.65)",
|
|
32
|
+
border: "#b86b78",
|
|
33
|
+
text: "#8a4a55",
|
|
34
|
+
},
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
export type CommentColor = (typeof COMMENT_COLORS)[number];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { findTextPosition } from "./core";
|
|
3
|
+
|
|
4
|
+
describe("findTextPosition", () => {
|
|
5
|
+
it("finds single occurrence", () => {
|
|
6
|
+
const result = findTextPosition("hello world", "world");
|
|
7
|
+
expect(result).toEqual({ start: 6, end: 11 });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("finds text at start", () => {
|
|
11
|
+
const result = findTextPosition("hello world", "hello");
|
|
12
|
+
expect(result).toEqual({ start: 0, end: 5 });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("returns null for no match", () => {
|
|
16
|
+
expect(findTextPosition("hello", "xyz")).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("returns null for empty selectedText", () => {
|
|
20
|
+
expect(findTextPosition("hello world", "")).toBeUndefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("returns null for empty textContent", () => {
|
|
24
|
+
expect(findTextPosition("", "hello")).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns null for both empty", () => {
|
|
28
|
+
expect(findTextPosition("", "")).toBeUndefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("multiple occurrences", () => {
|
|
32
|
+
it("finds closest occurrence to hint (before)", () => {
|
|
33
|
+
const text = "the cat and the dog and the bird";
|
|
34
|
+
// "the" occurs at: 0, 12, 24
|
|
35
|
+
|
|
36
|
+
// Hint at 10 should find "the" at 12 (closest)
|
|
37
|
+
const result = findTextPosition(text, "the", 10);
|
|
38
|
+
expect(result?.start).toBe(12);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("finds closest occurrence to hint (after)", () => {
|
|
42
|
+
const text = "the cat and the dog and the bird";
|
|
43
|
+
// "the" occurs at: 0, 12, 24
|
|
44
|
+
|
|
45
|
+
// Hint at 30 should find "the" at 24 (closest)
|
|
46
|
+
const result = findTextPosition(text, "the", 30);
|
|
47
|
+
expect(result?.start).toBe(24);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("finds first occurrence when hint is 0", () => {
|
|
51
|
+
const text = "the cat and the dog and the bird";
|
|
52
|
+
const result = findTextPosition(text, "the", 0);
|
|
53
|
+
expect(result?.start).toBe(0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("finds first occurrence when no hint provided", () => {
|
|
57
|
+
const text = "the cat and the dog and the bird";
|
|
58
|
+
const result = findTextPosition(text, "the");
|
|
59
|
+
expect(result?.start).toBe(0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("handles exact match at hint position", () => {
|
|
63
|
+
const text = "abc abc abc";
|
|
64
|
+
// "abc" occurs at: 0, 4, 8
|
|
65
|
+
const result = findTextPosition(text, "abc", 4);
|
|
66
|
+
expect(result?.start).toBe(4);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("edge cases", () => {
|
|
71
|
+
it("handles single character search", () => {
|
|
72
|
+
const result = findTextPosition("hello", "l");
|
|
73
|
+
expect(result).toEqual({ start: 2, end: 3 });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("handles overlapping matches (returns first)", () => {
|
|
77
|
+
const result = findTextPosition("aaa", "aa");
|
|
78
|
+
expect(result).toEqual({ start: 0, end: 2 });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("handles multiline content", () => {
|
|
82
|
+
const text = "line one\nline two\nline three";
|
|
83
|
+
const result = findTextPosition(text, "two");
|
|
84
|
+
expect(result).toEqual({ start: 14, end: 17 });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("handles unicode", () => {
|
|
88
|
+
const text = "こんにちは世界";
|
|
89
|
+
const result = findTextPosition(text, "世界");
|
|
90
|
+
expect(result).toEqual({ start: 5, end: 7 });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("is case sensitive", () => {
|
|
94
|
+
const result = findTextPosition("Hello World", "hello");
|
|
95
|
+
expect(result).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { TextPosition } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Find text position in content, handling duplicate occurrences.
|
|
5
|
+
* Returns the occurrence closest to hintOffset when multiple exist.
|
|
6
|
+
*/
|
|
7
|
+
export function findTextPosition(
|
|
8
|
+
textContent: string,
|
|
9
|
+
selectedText: string,
|
|
10
|
+
hintOffset?: number,
|
|
11
|
+
): TextPosition | undefined {
|
|
12
|
+
if (!selectedText || !textContent) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const occurrences: number[] = [];
|
|
17
|
+
let idx = 0;
|
|
18
|
+
|
|
19
|
+
for (;;) {
|
|
20
|
+
idx = textContent.indexOf(selectedText, idx);
|
|
21
|
+
if (idx === -1) break;
|
|
22
|
+
occurrences.push(idx);
|
|
23
|
+
idx += 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (occurrences.length === 0) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (occurrences.length === 1) {
|
|
31
|
+
return {
|
|
32
|
+
start: occurrences[0],
|
|
33
|
+
end: occurrences[0] + selectedText.length,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Multiple occurrences: find closest to hint offset
|
|
38
|
+
const target = hintOffset ?? 0;
|
|
39
|
+
let closest = occurrences[0];
|
|
40
|
+
let minDist = Math.abs(closest - target);
|
|
41
|
+
|
|
42
|
+
for (const occ of occurrences) {
|
|
43
|
+
const dist = Math.abs(occ - target);
|
|
44
|
+
if (dist < minDist) {
|
|
45
|
+
minDist = dist;
|
|
46
|
+
closest = occ;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
start: closest,
|
|
52
|
+
end: closest + selectedText.length,
|
|
53
|
+
};
|
|
54
|
+
}
|