@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.
- 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-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 +328 -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 +131 -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 +50 -0
- package/src/main.tsx +10 -0
- package/src/server/index.ts +766 -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,183 @@
|
|
|
1
|
+
# Multi-Document Support Design
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-03-13
|
|
4
|
+
**Status:** Approved
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
Evolve readit from single-document to multi-document support with tabbed navigation, full state preservation across tab switches, and Zustand for state management.
|
|
9
|
+
|
|
10
|
+
## Motivation
|
|
11
|
+
|
|
12
|
+
readit currently opens one file per session. Reviewers often need to cross-reference multiple documents — reviewing a spec alongside its implementation notes, or scanning an entire docs directory. Multi-document support makes readit useful for real review workflows.
|
|
13
|
+
|
|
14
|
+
## Invocation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx readit file1.md file2.md # Multiple files
|
|
18
|
+
npx readit ./docs/ # Directory (all .md and .html files)
|
|
19
|
+
npx readit README.md ./docs/ notes.html # Mixed
|
|
20
|
+
npx readit file.md # Single file (unchanged)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Interaction Model
|
|
24
|
+
|
|
25
|
+
- **Tabbed view** — one document visible at a time, tab bar to switch
|
|
26
|
+
- **Full state preservation** — selection, half-typed comments, scroll position all restored on tab switch
|
|
27
|
+
- **Lazy loading** — document content fetched on first tab activation, not all at once
|
|
28
|
+
- **Split view deferred** — tabs are the foundation; split view ships later
|
|
29
|
+
|
|
30
|
+
## Store Design (Zustand)
|
|
31
|
+
|
|
32
|
+
### Why Zustand
|
|
33
|
+
|
|
34
|
+
"Preserve everything on tab switch" means state must survive component unmounting. With tabs, only one document renders — inactive documents' React trees are gone. React Context dies with the tree. Zustand stores state outside React, solving this naturally.
|
|
35
|
+
|
|
36
|
+
### Shape
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
interface DocumentState {
|
|
40
|
+
// Document data
|
|
41
|
+
document: Document;
|
|
42
|
+
|
|
43
|
+
// Comments
|
|
44
|
+
comments: Comment[];
|
|
45
|
+
commentsError: string | null;
|
|
46
|
+
|
|
47
|
+
// Selection & input
|
|
48
|
+
selection: SelectionRange | null;
|
|
49
|
+
pendingSelectionTop: number | undefined;
|
|
50
|
+
pendingCommentText: string;
|
|
51
|
+
|
|
52
|
+
// Highlight positions (DOM-derived, recomputed on mount)
|
|
53
|
+
highlightPositions: Record<string, number>;
|
|
54
|
+
documentPositions: Record<string, number>;
|
|
55
|
+
|
|
56
|
+
// Navigation
|
|
57
|
+
scrollY: number;
|
|
58
|
+
hoveredCommentId: string | undefined;
|
|
59
|
+
|
|
60
|
+
// Re-anchor
|
|
61
|
+
reanchorTarget: { commentId: string } | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface AppStore {
|
|
65
|
+
documents: Map<string, DocumentState>;
|
|
66
|
+
activeDocumentPath: string | null;
|
|
67
|
+
|
|
68
|
+
// Global actions
|
|
69
|
+
openDocument: (doc: Document) => void;
|
|
70
|
+
closeDocument: (filePath: string) => void;
|
|
71
|
+
setActiveDocument: (filePath: string) => void;
|
|
72
|
+
|
|
73
|
+
// Per-document actions (default to active document)
|
|
74
|
+
addComment: (
|
|
75
|
+
text: string,
|
|
76
|
+
comment: string,
|
|
77
|
+
start: number,
|
|
78
|
+
end: number,
|
|
79
|
+
) => void;
|
|
80
|
+
editComment: (id: string, newText: string) => void;
|
|
81
|
+
deleteComment: (id: string) => void;
|
|
82
|
+
setSelection: (selection: SelectionRange | null) => void;
|
|
83
|
+
setHoveredComment: (id: string | undefined) => void;
|
|
84
|
+
// ...
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`highlightPositions` and `documentPositions` are DOM-derived — stale when unmounted, recomputed on mount. `scrollY` is the critical value to preserve; highlight positions are ephemeral.
|
|
89
|
+
|
|
90
|
+
Per-document actions take an optional `filePath` parameter but default to `activeDocumentPath`. This keeps call sites simple while enabling future split-view.
|
|
91
|
+
|
|
92
|
+
## Component Architecture
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
<App> # Thin shell: store init, tab bar, active DocumentView
|
|
96
|
+
+- <Toaster />
|
|
97
|
+
+- <TabBar /> # NEW - tab strip
|
|
98
|
+
+- <Header /> # Simplified - reads from store, ~8 props down from 14
|
|
99
|
+
+- <DocumentView> # NEW - per-document layout
|
|
100
|
+
| +- <TableOfContents /> or <TOCPopover /> # Renamed from FloatingTOC
|
|
101
|
+
| +- <DocumentViewer />
|
|
102
|
+
| +- Margin area
|
|
103
|
+
| | +- <ReanchorConfirmation /> # NEW - extracted from inline JSX
|
|
104
|
+
| | +- <NewCommentForm /> # Renamed from CommentInputArea
|
|
105
|
+
| | +- <MarginNotes /> # Renamed from MarginNotesContainer
|
|
106
|
+
| | +- <MarginNote /> # Reads actions from store directly
|
|
107
|
+
| +- <CommentMinimap />
|
|
108
|
+
| +- <CommentNavigator />
|
|
109
|
+
+- <footer />
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Component changes
|
|
113
|
+
|
|
114
|
+
- **App**: God component (~520 lines) becomes thin shell
|
|
115
|
+
- **DocumentView** (new): Owns per-document layout. On mount: restores scroll, re-applies highlights. On unmount: saves scrollY to store
|
|
116
|
+
- **MarginNote**: 6 callback props eliminated — calls `useAppStore()` directly
|
|
117
|
+
- **Header**: 14 props reduced to ~8 — reads comment data from store
|
|
118
|
+
- **CommentListDropdown** (renamed from CommentManagerDropdown): Reads from store directly
|
|
119
|
+
- **TabBar** (new): Renders open document tabs, close buttons, active indicator
|
|
120
|
+
|
|
121
|
+
## Server API Changes
|
|
122
|
+
|
|
123
|
+
| Endpoint | Before | After |
|
|
124
|
+
| --------------------- | ----------------------- | ------------------------------------------ |
|
|
125
|
+
| `GET /api/document` | Returns single document | Requires `?path=` query param |
|
|
126
|
+
| `GET /api/documents` | (new) | Returns list of open file paths + metadata |
|
|
127
|
+
| `POST /api/documents` | (new, deferred) | Add document at runtime (add-from-browser) |
|
|
128
|
+
| `GET /api/comments` | Single file scoped | Requires `?path=` query param |
|
|
129
|
+
| `POST /api/comments` | Single file scoped | Requires `path` in body |
|
|
130
|
+
|
|
131
|
+
`GET /api/documents` returns only metadata (path, fileName, type) — lightweight enough to populate the tab bar immediately. Content loads lazily via `GET /api/document?path=`.
|
|
132
|
+
|
|
133
|
+
Comment storage unchanged — already per-file in `~/.readit/comments/`.
|
|
134
|
+
|
|
135
|
+
## Tab Switch Lifecycle
|
|
136
|
+
|
|
137
|
+
### Saving (unmount)
|
|
138
|
+
|
|
139
|
+
Most state is already in the store as it changes. The only explicit "save on unmount" is `scrollY`, since scroll isn't React-controlled.
|
|
140
|
+
|
|
141
|
+
### Restoring (mount)
|
|
142
|
+
|
|
143
|
+
1. Read document content from store (or fetch if first visit)
|
|
144
|
+
2. Highlighter applies marks, emits fresh highlightPositions
|
|
145
|
+
3. `window.scrollTo(0, scrollY)` after highlights are painted
|
|
146
|
+
4. If selection exists, highlight pending selection
|
|
147
|
+
5. If pendingCommentText, NewCommentForm renders pre-filled
|
|
148
|
+
6. If reanchorTarget, show re-anchor UI
|
|
149
|
+
|
|
150
|
+
Scroll restoration uses the existing double-rAF pattern extended with scrollTo as the final step.
|
|
151
|
+
|
|
152
|
+
### Edge cases
|
|
153
|
+
|
|
154
|
+
| Scenario | Behavior |
|
|
155
|
+
| --------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
156
|
+
| Close active tab | Activate nearest tab (right, then left). Last tab shows empty state |
|
|
157
|
+
| Close tab with unsaved comment text | Close immediately — ephemeral state, expected behavior |
|
|
158
|
+
| Document changes on disk while on another tab | SSE live-reload updates store for that path; content is fresh on switch-back |
|
|
159
|
+
| Same file via different paths | Deduplicate by resolved absolute path in CLI |
|
|
160
|
+
|
|
161
|
+
## Deferrals
|
|
162
|
+
|
|
163
|
+
| Feature | Reason |
|
|
164
|
+
| -------------------------------------- | --------------------------------------------------- |
|
|
165
|
+
| Split view | Separate feature; tabs are the foundation |
|
|
166
|
+
| Add-from-browser | CLI + directory mode covers primary use cases first |
|
|
167
|
+
| Tab reordering | Polish, not essential |
|
|
168
|
+
| Cross-document comment search | No current demand |
|
|
169
|
+
| Tab persistence across server restarts | Overkill for a dev tool |
|
|
170
|
+
| Bulk export across documents | Per-document export works; bulk is convenience |
|
|
171
|
+
|
|
172
|
+
## Migration Path
|
|
173
|
+
|
|
174
|
+
Incremental — store introduced alongside existing hooks, components migrate one at a time:
|
|
175
|
+
|
|
176
|
+
1. Create store with `openDocument` / `setActiveDocument`
|
|
177
|
+
2. Add `TabBar`, wrap existing App content in `DocumentView`
|
|
178
|
+
3. Migrate `useComments` into store (biggest change)
|
|
179
|
+
4. Migrate selection, scroll, hover state into store
|
|
180
|
+
5. Remove emptied hooks, rename components
|
|
181
|
+
6. Add multi-file CLI + server endpoints
|
|
182
|
+
|
|
183
|
+
Each step is independently shippable and testable.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Performance Benchmarks Design
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
readit feels slow across startup, rendering, and interactions. The main JS bundle is 748 KB (230 KB gzipped), with no code splitting. Heavy libraries (react-markdown, mermaid, react-syntax-highlighter) load upfront.
|
|
6
|
+
|
|
7
|
+
## Goal
|
|
8
|
+
|
|
9
|
+
Establish Vitest bench benchmarks for critical server-side and library operations, with a <50ms target per operation. CI-gated to prevent regressions.
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
Benchmark the four core user flows:
|
|
14
|
+
|
|
15
|
+
1. **Adding comments** — anchor resolution, comment serialization, file I/O
|
|
16
|
+
2. **Copying/exporting comments** — export formatting, context extraction
|
|
17
|
+
3. **Startup** — time-to-first-byte once server is up (exception: process spawn, browser open)
|
|
18
|
+
4. **Rendering/reading** — margin layout calculations
|
|
19
|
+
|
|
20
|
+
Client-side rendering (react-markdown, syntax highlighting) is out of scope for Vitest bench — those require Playwright, which can be added later.
|
|
21
|
+
|
|
22
|
+
## Approach: Vitest Bench
|
|
23
|
+
|
|
24
|
+
Use Vitest's built-in `bench` mode (powered by tinybench). Benchmark files are co-located with source, following the existing `*.test.ts` pattern.
|
|
25
|
+
|
|
26
|
+
### Benchmark files
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
src/lib/
|
|
30
|
+
├── anchor.bench.ts # findAnchorWithFallback
|
|
31
|
+
├── comment-storage.bench.ts # parse, serialize, computeHash
|
|
32
|
+
├── margin-layout.bench.ts # position calculations
|
|
33
|
+
├── export.bench.ts # export formatting
|
|
34
|
+
├── context.bench.ts # LLM context extraction
|
|
35
|
+
└── __fixtures__/
|
|
36
|
+
└── bench-data.ts # shared synthetic test data
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### What each benchmark measures
|
|
40
|
+
|
|
41
|
+
#### `anchor.bench.ts` — hottest path
|
|
42
|
+
|
|
43
|
+
- `findAnchorWithFallback()` with exact match (best case)
|
|
44
|
+
- `findAnchorWithFallback()` with fuzzy match (worst case)
|
|
45
|
+
- Both against a ~300-line synthetic Markdown document
|
|
46
|
+
|
|
47
|
+
#### `comment-storage.bench.ts`
|
|
48
|
+
|
|
49
|
+
- `parseCommentFile()` with 1, 10, 50 comments
|
|
50
|
+
- `serializeComments()` with 1, 10, 50 comments
|
|
51
|
+
- `computeHash()` on a ~300-line document
|
|
52
|
+
|
|
53
|
+
#### `margin-layout.bench.ts`
|
|
54
|
+
|
|
55
|
+
- Position resolution with 1, 10, 50 comments
|
|
56
|
+
- Overlap resolution (worst case: all comments on adjacent lines)
|
|
57
|
+
|
|
58
|
+
#### `export.bench.ts` + `context.bench.ts`
|
|
59
|
+
|
|
60
|
+
- Export to JSON format with 10, 50 comments
|
|
61
|
+
- Export to prompt format with 10, 50 comments
|
|
62
|
+
- Context extraction for a ~300-line document
|
|
63
|
+
|
|
64
|
+
### Test fixtures (`__fixtures__/bench-data.ts`)
|
|
65
|
+
|
|
66
|
+
Shared synthetic data:
|
|
67
|
+
|
|
68
|
+
- A ~300-line Markdown document representative of real usage
|
|
69
|
+
- Comment sets at various sizes (1, 10, 50)
|
|
70
|
+
- Pre-computed anchors for deterministic benchmarks
|
|
71
|
+
|
|
72
|
+
### Running benchmarks
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pnpm bench # vitest bench
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Package.json script:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
"bench": "vitest bench"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### CI integration
|
|
85
|
+
|
|
86
|
+
GitHub Actions runs `pnpm bench` after tests pass. Vitest bench outputs timing statistics (min, max, mean, p99).
|
|
87
|
+
|
|
88
|
+
For hard CI gating, each benchmark file includes a companion assertion in the existing test file:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
test("anchor resolution completes under 50ms", () => {
|
|
92
|
+
const start = performance.now();
|
|
93
|
+
for (let i = 0; i < 100; i++) {
|
|
94
|
+
findAnchorWithFallback({
|
|
95
|
+
source: doc,
|
|
96
|
+
selectedText: text,
|
|
97
|
+
lineHint: "L150",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const elapsed = (performance.now() - start) / 100;
|
|
101
|
+
expect(elapsed).toBeLessThan(50);
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This gives both:
|
|
106
|
+
|
|
107
|
+
- **Benchmark data** (vitest bench) — for tracking performance over time
|
|
108
|
+
- **Hard assertions** (vitest test) — for CI gating at <50ms
|
|
109
|
+
|
|
110
|
+
## Performance target
|
|
111
|
+
|
|
112
|
+
| Operation | Target |
|
|
113
|
+
| ----------------------------------------------------- | ------------------- |
|
|
114
|
+
| All benchmarked operations | < 50ms |
|
|
115
|
+
| Exceptions: process spawn, browser open, port binding | As fast as possible |
|
|
116
|
+
|
|
117
|
+
## Future work
|
|
118
|
+
|
|
119
|
+
- Playwright benchmarks for client-side rendering (react-markdown, syntax highlighting, comment highlight positioning)
|
|
120
|
+
- Bundle size budgets if bundle growth becomes a concern
|
|
121
|
+
- `Server-Timing` headers on API routes for production observability
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { existsSync, rmSync } from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { expect, test } from "@playwright/test";
|
|
5
|
+
import { spawnCli } from "./utils/cli";
|
|
6
|
+
import {
|
|
7
|
+
addComment,
|
|
8
|
+
selectTextInArticle,
|
|
9
|
+
selectTextInIframe,
|
|
10
|
+
} from "./utils/selection";
|
|
11
|
+
|
|
12
|
+
const FIXTURES_DIR = resolve(import.meta.dirname, "fixtures");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get the expected comment file path for a source file.
|
|
16
|
+
*/
|
|
17
|
+
function getCommentPath(sourcePath: string): string {
|
|
18
|
+
const absolute = resolve(sourcePath);
|
|
19
|
+
const normalized = absolute.replace(/^\//, "").replace(/^[A-Z]:[\\/]/, "");
|
|
20
|
+
const ext = normalized.lastIndexOf(".");
|
|
21
|
+
const withoutExt = ext > 0 ? normalized.slice(0, ext) : normalized;
|
|
22
|
+
return join(os.homedir(), ".readit", "comments", `${withoutExt}.comments.md`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Clean up comment file for a source file.
|
|
27
|
+
*/
|
|
28
|
+
function cleanupCommentFile(sourcePath: string): void {
|
|
29
|
+
const commentPath = getCommentPath(sourcePath);
|
|
30
|
+
if (existsSync(commentPath)) {
|
|
31
|
+
rmSync(commentPath);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test.describe("Comment Creation", () => {
|
|
36
|
+
const sampleMdPath = resolve(FIXTURES_DIR, "sample.md");
|
|
37
|
+
const sampleHtmlPath = resolve(FIXTURES_DIR, "sample.html");
|
|
38
|
+
|
|
39
|
+
test.beforeEach(() => {
|
|
40
|
+
// Clean up any existing comment files before each test
|
|
41
|
+
cleanupCommentFile(sampleMdPath);
|
|
42
|
+
cleanupCommentFile(sampleHtmlPath);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test.afterEach(() => {
|
|
46
|
+
// Clean up after each test
|
|
47
|
+
cleanupCommentFile(sampleMdPath);
|
|
48
|
+
cleanupCommentFile(sampleHtmlPath);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("adds comment to selected text in markdown document", async ({
|
|
52
|
+
page,
|
|
53
|
+
}) => {
|
|
54
|
+
const { url, cleanup } = await spawnCli(sampleMdPath, { port: 4572 });
|
|
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
|
+
// Select text in the article
|
|
64
|
+
const textToSelect = "testing text selection";
|
|
65
|
+
await selectTextInArticle(page, textToSelect);
|
|
66
|
+
|
|
67
|
+
// Add a comment
|
|
68
|
+
const commentText = "This is my test comment";
|
|
69
|
+
await addComment(page, commentText);
|
|
70
|
+
|
|
71
|
+
// Verify: highlight exists with data-comment-id
|
|
72
|
+
const highlight = article.locator("mark[data-comment-id]").first();
|
|
73
|
+
await expect(highlight).toBeVisible();
|
|
74
|
+
await expect(highlight).toContainText(textToSelect);
|
|
75
|
+
|
|
76
|
+
// Verify: margin note shows the comment
|
|
77
|
+
await expect(page.locator("body")).toContainText(commentText);
|
|
78
|
+
} finally {
|
|
79
|
+
await cleanup();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("adds comment to selected text in HTML document (iframe)", async ({
|
|
84
|
+
page,
|
|
85
|
+
}) => {
|
|
86
|
+
const { url, cleanup } = await spawnCli(sampleHtmlPath, { port: 4573 });
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await page.goto(url);
|
|
90
|
+
|
|
91
|
+
// Wait for iframe to load and its script to initialize
|
|
92
|
+
const iframe = page.frameLocator("iframe");
|
|
93
|
+
await expect(iframe.locator("body")).toBeVisible();
|
|
94
|
+
|
|
95
|
+
// Wait for iframe script to execute and send iframeReady
|
|
96
|
+
await page.waitForTimeout(500);
|
|
97
|
+
|
|
98
|
+
// Select text inside iframe - use test event which bypasses tree walking
|
|
99
|
+
// The test event directly sends offsets to the parent
|
|
100
|
+
const textToSelect = "testing text selection";
|
|
101
|
+
await selectTextInIframe(page, iframe, textToSelect);
|
|
102
|
+
|
|
103
|
+
// Verify pending highlight exists in iframe
|
|
104
|
+
const pendingMark = iframe.locator("mark[data-pending]");
|
|
105
|
+
await expect(pendingMark).toBeVisible({ timeout: 5000 });
|
|
106
|
+
|
|
107
|
+
// Add a comment (input is in parent frame)
|
|
108
|
+
const commentText = "Comment on HTML content";
|
|
109
|
+
await addComment(page, commentText);
|
|
110
|
+
|
|
111
|
+
// Wait for the comment to be saved via API and highlights to be applied
|
|
112
|
+
await page.waitForTimeout(500);
|
|
113
|
+
|
|
114
|
+
// Verify: highlight exists inside iframe with comment ID
|
|
115
|
+
const highlight = iframe.locator("mark[data-comment-id]").first();
|
|
116
|
+
await expect(highlight).toBeVisible();
|
|
117
|
+
await expect(highlight).toContainText(textToSelect);
|
|
118
|
+
|
|
119
|
+
// Verify: margin note shows the comment (in parent frame)
|
|
120
|
+
await expect(page.locator("body")).toContainText(commentText);
|
|
121
|
+
} finally {
|
|
122
|
+
await cleanup();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { expect, test } from "@playwright/test";
|
|
3
|
+
import { spawnCli } from "./utils/cli";
|
|
4
|
+
|
|
5
|
+
const FIXTURES_DIR = resolve(import.meta.dirname, "fixtures");
|
|
6
|
+
|
|
7
|
+
test.describe("Document Loading", () => {
|
|
8
|
+
test("loads markdown document and displays content", async ({ page }) => {
|
|
9
|
+
const { url, cleanup } = await spawnCli(
|
|
10
|
+
resolve(FIXTURES_DIR, "sample.md"),
|
|
11
|
+
{ port: 4570 },
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await page.goto(url);
|
|
16
|
+
|
|
17
|
+
// Wait for document to load - use article scope to avoid header h1
|
|
18
|
+
const article = page.locator("article");
|
|
19
|
+
await expect(article.locator("h1")).toContainText("Test Document");
|
|
20
|
+
|
|
21
|
+
// Verify paragraph content is rendered
|
|
22
|
+
await expect(article).toContainText(
|
|
23
|
+
"This is a paragraph for testing text selection",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// Verify second section is visible
|
|
27
|
+
await expect(article.locator("h2")).toContainText("Second Section");
|
|
28
|
+
} finally {
|
|
29
|
+
await cleanup();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("loads HTML document in iframe", async ({ page }) => {
|
|
34
|
+
const { url, cleanup } = await spawnCli(
|
|
35
|
+
resolve(FIXTURES_DIR, "sample.html"),
|
|
36
|
+
{ port: 4571 },
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await page.goto(url);
|
|
41
|
+
|
|
42
|
+
// Wait for iframe to exist
|
|
43
|
+
const iframe = page.frameLocator("iframe");
|
|
44
|
+
|
|
45
|
+
// Verify content inside iframe
|
|
46
|
+
await expect(iframe.locator("h1")).toContainText("Test Document");
|
|
47
|
+
await expect(iframe.locator("p").first()).toContainText(
|
|
48
|
+
"This is a paragraph for testing text selection",
|
|
49
|
+
);
|
|
50
|
+
} finally {
|
|
51
|
+
await cleanup();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { expect, test } from "@playwright/test";
|
|
3
|
+
import { spawnCli } from "./utils/cli";
|
|
4
|
+
import { addComment, selectTextInArticle } from "./utils/selection";
|
|
5
|
+
|
|
6
|
+
const FIXTURES_DIR = resolve(import.meta.dirname, "fixtures");
|
|
7
|
+
|
|
8
|
+
test.describe("Comment Export", () => {
|
|
9
|
+
test("Copy All generates valid prompt format", async ({ page, context }) => {
|
|
10
|
+
// Grant clipboard permissions
|
|
11
|
+
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
|
|
12
|
+
|
|
13
|
+
const { url, cleanup } = await spawnCli(
|
|
14
|
+
resolve(FIXTURES_DIR, "sample.md"),
|
|
15
|
+
{ port: 4590 },
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
await page.goto(url);
|
|
20
|
+
|
|
21
|
+
// Wait for document to load
|
|
22
|
+
const article = page.locator("article");
|
|
23
|
+
await expect(article).toBeVisible();
|
|
24
|
+
|
|
25
|
+
// Add a comment
|
|
26
|
+
const textToSelect = "testing text selection";
|
|
27
|
+
const commentText = "This is my review comment";
|
|
28
|
+
await selectTextInArticle(page, textToSelect);
|
|
29
|
+
await addComment(page, commentText);
|
|
30
|
+
|
|
31
|
+
// Verify comment was added
|
|
32
|
+
await expect(page.locator("body")).toContainText(commentText);
|
|
33
|
+
|
|
34
|
+
// Open the actions menu and click "Copy All"
|
|
35
|
+
const menuButton = page.getByRole("button", { name: /actions menu/i });
|
|
36
|
+
await menuButton.click();
|
|
37
|
+
|
|
38
|
+
const copyButton = page.getByRole("menuitem", { name: /copy all/i });
|
|
39
|
+
await copyButton.click();
|
|
40
|
+
|
|
41
|
+
// Read clipboard content
|
|
42
|
+
const clipboardContent = await page.evaluate(() =>
|
|
43
|
+
navigator.clipboard.readText(),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Verify the format contains expected parts
|
|
47
|
+
expect(clipboardContent).toContain("# Review Comments for sample.md");
|
|
48
|
+
expect(clipboardContent).toContain(textToSelect);
|
|
49
|
+
expect(clipboardContent).toContain(commentText);
|
|
50
|
+
|
|
51
|
+
// Verify it follows the prompt format (selected text + comment structure)
|
|
52
|
+
expect(clipboardContent).toContain("Selected text:");
|
|
53
|
+
expect(clipboardContent).toContain("Comment:");
|
|
54
|
+
} finally {
|
|
55
|
+
await cleanup();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Test Document</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<h1>Test Document</h1>
|
|
9
|
+
<p>This is a paragraph for testing text selection.</p>
|
|
10
|
+
<h2>Second Section</h2>
|
|
11
|
+
<p>Here is another paragraph with some more text to select and comment on.</p>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|