vibe-reader 0.2.0__tar.gz → 0.2.2__tar.gz
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.
- vibe_reader-0.2.2/.github/dependabot.yml +18 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/.github/workflows/publish.yml +4 -4
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/PKG-INFO +9 -9
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/backend/files.py +33 -3
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/backend/rendering.py +10 -1
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/api.js +9 -4
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/files-view.js +43 -1
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/state.js +3 -1
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/pyproject.toml +8 -8
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/tests/test_files.py +58 -0
- vibe_reader-0.2.2/tests/test_frontend_modules.py +185 -0
- vibe_reader-0.2.2/uv.lock +413 -0
- vibe_reader-0.2.0/tests/test_frontend_modules.py +0 -65
- vibe_reader-0.2.0/uv.lock +0 -389
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/.gitignore +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/AGENTS.md +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/README.md +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/backend/__init__.py +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/backend/git.py +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/backend/main.py +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/backend/services/__init__.py +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/backend/services/git_diff_service.py +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/backend/services/path_service.py +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/backend/services/tmux_service.py +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/backend/tmux.py +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/doc/DESIGN.md +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/app.js +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/config.js +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/content-renderer.js +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/favicon.png +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/favicon.svg +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/file-status.js +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/index.html +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/manifest.json +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/mermaid-renderer.js +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/styles.css +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/sw.js +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/tmux-refresh.js +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/tmux-tree.js +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/tmux-view.js +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/ui.js +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/architecture-PBZL5I3N-X3DJOG6M.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/architecture-PBZL5I3N-X3DJOG6M.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/architectureDiagram-EJXTDGMB.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/architectureDiagram-EJXTDGMB.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/blockDiagram-HG7WUIX4.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/blockDiagram-HG7WUIX4.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/c4Diagram-BBK6TRR6.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/c4Diagram-BBK6TRR6.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-33NP3AWU.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-33NP3AWU.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-3UWU4A3N.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-3UWU4A3N.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-3YCYZ6SJ.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-3YCYZ6SJ.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-3ZBQQEZ6.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-3ZBQQEZ6.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-5YHUCXBM.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-5YHUCXBM.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-7RZVMHOQ.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-7RZVMHOQ.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-AEOMTBSW.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-AEOMTBSW.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-AHTCUU5T.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-AHTCUU5T.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-BDKIFH7H.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-BDKIFH7H.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-D2KP3OBD.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-D2KP3OBD.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-D4HHKWPY.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-D4HHKWPY.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-F3E5R7LK.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-F3E5R7LK.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-GAX3EE6F.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-GAX3EE6F.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-H3VCZNTA.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-H3VCZNTA.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-HN6EAY2L.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-HN6EAY2L.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-IWDTEBJL.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-IWDTEBJL.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-JIN56HTB.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-JIN56HTB.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-JVPIJFVP.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-JVPIJFVP.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-KSICW3F5.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-KSICW3F5.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-MFSPT3DZ.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-MFSPT3DZ.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-MGPAVIPZ.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-MGPAVIPZ.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-NHJX6F6M.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-NHJX6F6M.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-NN2NZ22Q.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-NN2NZ22Q.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-Q3CC2MQB.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-Q3CC2MQB.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-QU3B7NT4.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-QU3B7NT4.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-SK62O5VA.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-SK62O5VA.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-SYGLAQYM.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-SYGLAQYM.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-T4EQAHMB.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-T4EQAHMB.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-TBF5ZNIQ.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-TBF5ZNIQ.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-TFLKLN34.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-TFLKLN34.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-TJ4WWVTA.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-TJ4WWVTA.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-TU3PZOEN.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-TU3PZOEN.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-VELTKBKT.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-VELTKBKT.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-W2A4CRWB.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-W2A4CRWB.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-XBXGYYE5.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-XBXGYYE5.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-XODN6PIJ.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-XODN6PIJ.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-YLHEXJF3.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-YLHEXJF3.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-ZX4KS3OZ.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/chunk-ZX4KS3OZ.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/classDiagram-JG6F6YBT.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/classDiagram-JG6F6YBT.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/classDiagram-v2-CFK7UN3Z.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/classDiagram-v2-CFK7UN3Z.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/cose-bilkent-PNC4W37J.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/cose-bilkent-PNC4W37J.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/dagre-GNUAUHNH.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/dagre-GNUAUHNH.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/diagram-G7E7SVAT.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/diagram-G7E7SVAT.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/diagram-HOHITQNR.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/diagram-HOHITQNR.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/diagram-SUXTHJMA.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/diagram-SUXTHJMA.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/erDiagram-L2NYR2MQ.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/erDiagram-L2NYR2MQ.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/flowDiagram-IIOBCMXN.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/flowDiagram-IIOBCMXN.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/ganttDiagram-CBGYKTO2.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/ganttDiagram-CBGYKTO2.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/gitGraph-HDMCJU4V-C6Y2HLJ7.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/gitGraph-HDMCJU4V-C6Y2HLJ7.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/gitGraphDiagram-FCDOK2W6.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/gitGraphDiagram-FCDOK2W6.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/info-3K5VOQVL-GYCQBKJT.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/info-3K5VOQVL-GYCQBKJT.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/infoDiagram-JC2SMEGH.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/infoDiagram-JC2SMEGH.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/ishikawaDiagram-LCPTV24A.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/ishikawaDiagram-LCPTV24A.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/journeyDiagram-FZRSWBTQ.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/journeyDiagram-FZRSWBTQ.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/kanban-definition-ETIEC4YN.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/kanban-definition-ETIEC4YN.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/katex-GD7MH7QM.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/katex-GD7MH7QM.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/mindmap-definition-QTQOGDLH.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/mindmap-definition-QTQOGDLH.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/packet-RMMSAZCW-74OXTHQQ.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/packet-RMMSAZCW-74OXTHQQ.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/pie-UPGHQEXC-SNR2CZVS.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/pie-UPGHQEXC-SNR2CZVS.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/pieDiagram-O4VNXU6G.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/pieDiagram-O4VNXU6G.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/quadrantDiagram-DYU4IJOS.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/quadrantDiagram-DYU4IJOS.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/radar-KQ55EAFF-DTKK6RDY.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/radar-KQ55EAFF-DTKK6RDY.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/requirementDiagram-N2XL27SN.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/requirementDiagram-N2XL27SN.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/sankeyDiagram-HWP7ZNIN.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/sankeyDiagram-HWP7ZNIN.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/sequenceDiagram-PYXOKC54.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/sequenceDiagram-PYXOKC54.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/stateDiagram-UFOXNHOX.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/stateDiagram-UFOXNHOX.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/stateDiagram-v2-C774GKM7.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/stateDiagram-v2-C774GKM7.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/timeline-definition-GC5UGDFR.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/timeline-definition-GC5UGDFR.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/treemap-KZPCXAKY-RU5UWGQG.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/treemap-KZPCXAKY-RU5UWGQG.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/vennDiagram-R4MEU4WM.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/vennDiagram-R4MEU4WM.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/xychartDiagram-TMDUDL2A.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/chunks/mermaid.esm.min/xychartDiagram-TMDUDL2A.mjs.map +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/frontend/vendor/mermaid.esm.min.mjs +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/tests/test_git_diff.py +0 -0
- {vibe_reader-0.2.0 → vibe_reader-0.2.2}/tests/test_tmux.py +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
|
|
3
|
+
updates:
|
|
4
|
+
- package-ecosystem: "uv"
|
|
5
|
+
directory: "/"
|
|
6
|
+
schedule:
|
|
7
|
+
interval: "weekly"
|
|
8
|
+
day: "monday"
|
|
9
|
+
time: "09:00"
|
|
10
|
+
timezone: "Etc/UTC"
|
|
11
|
+
|
|
12
|
+
- package-ecosystem: "github-actions"
|
|
13
|
+
directory: "/"
|
|
14
|
+
schedule:
|
|
15
|
+
interval: "weekly"
|
|
16
|
+
day: "monday"
|
|
17
|
+
time: "09:00"
|
|
18
|
+
timezone: "Etc/UTC"
|
|
@@ -13,18 +13,18 @@ jobs:
|
|
|
13
13
|
contents: read
|
|
14
14
|
|
|
15
15
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
16
|
+
- uses: actions/checkout@v7.0.1
|
|
17
17
|
|
|
18
18
|
- name: Set up Python
|
|
19
|
-
uses: actions/setup-python@
|
|
19
|
+
uses: actions/setup-python@v7.0.0
|
|
20
20
|
with:
|
|
21
21
|
python-version: '3.13'
|
|
22
22
|
|
|
23
23
|
- name: Install uv
|
|
24
|
-
uses: astral-sh/setup-uv@
|
|
24
|
+
uses: astral-sh/setup-uv@v10.2.0
|
|
25
25
|
|
|
26
26
|
- name: Build package
|
|
27
27
|
run: uv build
|
|
28
28
|
|
|
29
29
|
- name: Publish to PyPI
|
|
30
|
-
uses: pypa/gh-action-pypi-publish@v1.14.
|
|
30
|
+
uses: pypa/gh-action-pypi-publish@v1.14.2
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: vibe-reader
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: E-ink friendly web reader for tmux panes, project files, Markdown, and git diffs.
|
|
5
5
|
Author-email: PsychArch <PsychArch@github.com>
|
|
6
6
|
Requires-Python: >=3.13
|
|
7
|
-
Requires-Dist: dulwich==1.2.
|
|
8
|
-
Requires-Dist: fastapi==0.
|
|
9
|
-
Requires-Dist: libtmux==0.
|
|
10
|
-
Requires-Dist: markdown==3.
|
|
11
|
-
Requires-Dist: pydantic==2.13.
|
|
7
|
+
Requires-Dist: dulwich==1.2.15
|
|
8
|
+
Requires-Dist: fastapi==0.141.1
|
|
9
|
+
Requires-Dist: libtmux==0.62.0
|
|
10
|
+
Requires-Dist: markdown==3.11
|
|
11
|
+
Requires-Dist: pydantic==2.13.5
|
|
12
12
|
Requires-Dist: pygments==2.20.0
|
|
13
|
-
Requires-Dist: uvicorn==0.
|
|
13
|
+
Requires-Dist: uvicorn==0.54.0
|
|
14
14
|
Provides-Extra: dev
|
|
15
15
|
Requires-Dist: httpx2>=2.4.0; extra == 'dev'
|
|
16
|
-
Requires-Dist: pytest==9.
|
|
16
|
+
Requires-Dist: pytest==9.1.1; extra == 'dev'
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
|
|
19
19
|
# Vibe Reader
|
|
@@ -3,6 +3,7 @@ from pathlib import Path
|
|
|
3
3
|
from typing import AsyncIterator
|
|
4
4
|
|
|
5
5
|
from fastapi import APIRouter, FastAPI, HTTPException
|
|
6
|
+
from starlette.concurrency import run_in_threadpool
|
|
6
7
|
|
|
7
8
|
from backend.rendering import render_text_content
|
|
8
9
|
from backend.services.path_service import (
|
|
@@ -15,6 +16,9 @@ from backend.services.path_service import (
|
|
|
15
16
|
|
|
16
17
|
# Maximum file size to read (10MB)
|
|
17
18
|
MAX_FILE_SIZE = 10 * 1024 * 1024
|
|
19
|
+
MAX_HIGHLIGHT_FILE_SIZE = 512 * 1024
|
|
20
|
+
MAX_MARKDOWN_RENDER_FILE_SIZE = 1024 * 1024
|
|
21
|
+
LARGE_FILE_RENDER_NOTE = "Large file rendered without syntax highlighting."
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
def get_project_root() -> Path:
|
|
@@ -37,6 +41,28 @@ async def files_lifespan(_: FastAPI) -> AsyncIterator[None]:
|
|
|
37
41
|
|
|
38
42
|
router = APIRouter(prefix="/api/files", tags=["files"], lifespan=files_lifespan)
|
|
39
43
|
|
|
44
|
+
|
|
45
|
+
def _should_force_plain_render(path: Path, file_size: int, highlight: bool) -> bool:
|
|
46
|
+
if path.suffix.lower() in {".md", ".markdown"}:
|
|
47
|
+
return file_size > MAX_MARKDOWN_RENDER_FILE_SIZE
|
|
48
|
+
return highlight and file_size > MAX_HIGHLIGHT_FILE_SIZE
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _read_and_render_file(target_path: Path, file_size: int, highlight: bool):
|
|
52
|
+
force_plain = _should_force_plain_render(target_path, file_size, highlight)
|
|
53
|
+
content = target_path.read_text()
|
|
54
|
+
render_result = render_text_content(
|
|
55
|
+
target_path,
|
|
56
|
+
content,
|
|
57
|
+
enable_highlighting=highlight,
|
|
58
|
+
force_plain=force_plain,
|
|
59
|
+
)
|
|
60
|
+
metadata = dict(render_result.metadata)
|
|
61
|
+
if force_plain:
|
|
62
|
+
metadata["large_file"] = "true"
|
|
63
|
+
metadata["render_note"] = LARGE_FILE_RENDER_NOTE
|
|
64
|
+
return render_result, metadata
|
|
65
|
+
|
|
40
66
|
@router.get("")
|
|
41
67
|
async def list_files(path: str = "."):
|
|
42
68
|
"""List files in a directory"""
|
|
@@ -67,14 +93,18 @@ async def get_file_content(path: str, highlight: bool = True):
|
|
|
67
93
|
if file_size > MAX_FILE_SIZE:
|
|
68
94
|
raise HTTPException(status_code=400, detail=f"File too large (max {MAX_FILE_SIZE // 1024 // 1024}MB)")
|
|
69
95
|
|
|
70
|
-
|
|
71
|
-
|
|
96
|
+
render_result, metadata = await run_in_threadpool(
|
|
97
|
+
_read_and_render_file,
|
|
98
|
+
target_path,
|
|
99
|
+
file_size,
|
|
100
|
+
highlight,
|
|
101
|
+
)
|
|
72
102
|
|
|
73
103
|
return {
|
|
74
104
|
"path": display_path(project_root, target_path),
|
|
75
105
|
"render_mode": render_result.mode,
|
|
76
106
|
"html": render_result.html,
|
|
77
|
-
"metadata":
|
|
107
|
+
"metadata": metadata,
|
|
78
108
|
}
|
|
79
109
|
except UnicodeDecodeError:
|
|
80
110
|
raise HTTPException(status_code=400, detail="Cannot read binary file")
|
|
@@ -28,9 +28,18 @@ class RenderResult:
|
|
|
28
28
|
metadata: Dict[str, str] = field(default_factory=dict)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def render_text_content(
|
|
31
|
+
def render_text_content(
|
|
32
|
+
path: Path,
|
|
33
|
+
content: str,
|
|
34
|
+
enable_highlighting: bool = True,
|
|
35
|
+
*,
|
|
36
|
+
force_plain: bool = False,
|
|
37
|
+
) -> RenderResult:
|
|
32
38
|
"""Render text content based on file type."""
|
|
33
39
|
|
|
40
|
+
if force_plain:
|
|
41
|
+
return _render_plain(content)
|
|
42
|
+
|
|
34
43
|
suffix = path.suffix.lower()
|
|
35
44
|
if suffix in _MARKDOWN_EXTENSIONS:
|
|
36
45
|
return _render_markdown(content, enable_highlighting=enable_highlighting)
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
export async function fetchJSON(url, errorContext) {
|
|
1
|
+
export async function fetchJSON(url, errorContext, options = {}) {
|
|
2
2
|
let response;
|
|
3
3
|
try {
|
|
4
|
-
response = await fetch(url);
|
|
4
|
+
response = await fetch(url, options);
|
|
5
5
|
} catch (networkError) {
|
|
6
|
+
if (networkError.name === 'AbortError') {
|
|
7
|
+
throw networkError;
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
const error = new Error(`${errorContext}: ${networkError.message}`);
|
|
7
11
|
error.context = errorContext;
|
|
8
12
|
error.url = url;
|
|
@@ -61,10 +65,11 @@ export function loadFiles(path) {
|
|
|
61
65
|
return fetchJSON(`/api/files?path=${encodeURIComponent(path)}`, 'load files');
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
export function loadFileContent(path, { enableHighlighting }) {
|
|
68
|
+
export function loadFileContent(path, { enableHighlighting, signal } = {}) {
|
|
65
69
|
return fetchJSON(
|
|
66
70
|
`/api/files/content?path=${encodeURIComponent(path)}&highlight=${enableHighlighting}`,
|
|
67
|
-
'load file'
|
|
71
|
+
'load file',
|
|
72
|
+
{ signal }
|
|
68
73
|
);
|
|
69
74
|
}
|
|
70
75
|
|
|
@@ -48,6 +48,20 @@ export function createFilesViewController({ dom, state, viewState, api, contentR
|
|
|
48
48
|
return source === 'staged' ? 'Staged' : 'Unstaged';
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function abortPendingFileContentRequest() {
|
|
52
|
+
if (!state.currentFileAbortController) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
state.currentFileAbortController.abort();
|
|
57
|
+
state.currentFileAbortController = null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function invalidateFileContentRequests() {
|
|
61
|
+
abortPendingFileContentRequest();
|
|
62
|
+
state.currentFileRequestId += 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
51
65
|
function openFile(path, { isDeleted = false } = {}) {
|
|
52
66
|
if (!path) {
|
|
53
67
|
return;
|
|
@@ -58,6 +72,7 @@ export function createFilesViewController({ dom, state, viewState, api, contentR
|
|
|
58
72
|
return;
|
|
59
73
|
}
|
|
60
74
|
if (isDeleted) {
|
|
75
|
+
invalidateFileContentRequests();
|
|
61
76
|
callbacks.showToast('Enable diff mode to inspect deleted files.');
|
|
62
77
|
contentRenderer.renderDiffPrompt();
|
|
63
78
|
return;
|
|
@@ -66,6 +81,8 @@ export function createFilesViewController({ dom, state, viewState, api, contentR
|
|
|
66
81
|
}
|
|
67
82
|
|
|
68
83
|
async function loadGitDiff(path, source = state.diffSource) {
|
|
84
|
+
invalidateFileContentRequests();
|
|
85
|
+
|
|
69
86
|
if (!path) {
|
|
70
87
|
contentRenderer.renderDiffPrompt();
|
|
71
88
|
return;
|
|
@@ -165,6 +182,7 @@ export function createFilesViewController({ dom, state, viewState, api, contentR
|
|
|
165
182
|
setCurrentDirectory(parentPath);
|
|
166
183
|
state.selectedFile = null;
|
|
167
184
|
state.selectedFileIsDeleted = false;
|
|
185
|
+
invalidateFileContentRequests();
|
|
168
186
|
contentRenderer.resetFilePreview();
|
|
169
187
|
loadFileList({ forceStatusRefresh: true });
|
|
170
188
|
}
|
|
@@ -187,6 +205,7 @@ export function createFilesViewController({ dom, state, viewState, api, contentR
|
|
|
187
205
|
setCurrentDirectory(file.path);
|
|
188
206
|
state.selectedFile = null;
|
|
189
207
|
state.selectedFileIsDeleted = false;
|
|
208
|
+
invalidateFileContentRequests();
|
|
190
209
|
contentRenderer.resetFilePreview();
|
|
191
210
|
loadFileList();
|
|
192
211
|
} else {
|
|
@@ -241,15 +260,38 @@ export function createFilesViewController({ dom, state, viewState, api, contentR
|
|
|
241
260
|
}
|
|
242
261
|
|
|
243
262
|
async function loadFileContent(path) {
|
|
263
|
+
abortPendingFileContentRequest();
|
|
264
|
+
|
|
265
|
+
const requestId = ++state.currentFileRequestId;
|
|
266
|
+
const abortController = new AbortController();
|
|
267
|
+
state.currentFileAbortController = abortController;
|
|
268
|
+
contentRenderer.renderInlineMessage(`Loading ${path}...`);
|
|
269
|
+
|
|
244
270
|
try {
|
|
245
271
|
const config = readConfig();
|
|
246
272
|
const data = await api.loadFileContent(path, {
|
|
247
|
-
enableHighlighting: config.enableSyntaxHighlighting
|
|
273
|
+
enableHighlighting: config.enableSyntaxHighlighting,
|
|
274
|
+
signal: abortController.signal
|
|
248
275
|
});
|
|
276
|
+
|
|
277
|
+
if (requestId !== state.currentFileRequestId) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
249
281
|
contentRenderer.renderFileContent(data);
|
|
250
282
|
dom.fileContentContainer.scrollTop = 0;
|
|
283
|
+
if (data.metadata?.render_note) {
|
|
284
|
+
callbacks.showToast(data.metadata.render_note);
|
|
285
|
+
}
|
|
251
286
|
} catch (error) {
|
|
287
|
+
if (requestId !== state.currentFileRequestId || error.name === 'AbortError') {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
252
290
|
contentRenderer.renderInlineMessage(error.message);
|
|
291
|
+
} finally {
|
|
292
|
+
if (requestId === state.currentFileRequestId) {
|
|
293
|
+
state.currentFileAbortController = null;
|
|
294
|
+
}
|
|
253
295
|
}
|
|
254
296
|
}
|
|
255
297
|
|
|
@@ -26,7 +26,9 @@ export function createFilesState() {
|
|
|
26
26
|
diffSource: 'unstaged',
|
|
27
27
|
currentDiffRequestId: 0,
|
|
28
28
|
selectedFileIsDeleted: false,
|
|
29
|
-
currentFileRenderToken: 0
|
|
29
|
+
currentFileRenderToken: 0,
|
|
30
|
+
currentFileRequestId: 0,
|
|
31
|
+
currentFileAbortController: null
|
|
30
32
|
};
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "vibe-reader"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "E-ink friendly web reader for tmux panes, project files, Markdown, and git diffs."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -8,13 +8,13 @@ authors = [
|
|
|
8
8
|
]
|
|
9
9
|
requires-python = ">=3.13"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"fastapi==0.
|
|
12
|
-
"libtmux==0.
|
|
13
|
-
"uvicorn==0.
|
|
11
|
+
"fastapi==0.141.1",
|
|
12
|
+
"libtmux==0.62.0",
|
|
13
|
+
"uvicorn==0.54.0",
|
|
14
14
|
"Pygments==2.20.0",
|
|
15
|
-
"markdown==3.
|
|
16
|
-
"dulwich==1.2.
|
|
17
|
-
"pydantic==2.13.
|
|
15
|
+
"markdown==3.11",
|
|
16
|
+
"dulwich==1.2.15",
|
|
17
|
+
"pydantic==2.13.5",
|
|
18
18
|
]
|
|
19
19
|
|
|
20
20
|
[project.scripts]
|
|
@@ -22,7 +22,7 @@ vibe-reader = "backend.main:main"
|
|
|
22
22
|
|
|
23
23
|
[project.optional-dependencies]
|
|
24
24
|
dev = [
|
|
25
|
-
"pytest==9.
|
|
25
|
+
"pytest==9.1.1",
|
|
26
26
|
"httpx2>=2.4.0",
|
|
27
27
|
]
|
|
28
28
|
|
|
@@ -154,6 +154,64 @@ def test_plain_mode_when_no_lexer(tmp_path):
|
|
|
154
154
|
assert "line-code" in payload["html"]
|
|
155
155
|
|
|
156
156
|
|
|
157
|
+
def test_large_code_file_skips_syntax_highlighting(tmp_path):
|
|
158
|
+
project_root = tmp_path / "proj"
|
|
159
|
+
project_root.mkdir()
|
|
160
|
+
code_file = project_root / "large.py"
|
|
161
|
+
code_file.write_text("def hello():\n return 'world'\n" * 20000, encoding="utf-8")
|
|
162
|
+
|
|
163
|
+
with client_with_root(project_root) as client:
|
|
164
|
+
response = client.get(
|
|
165
|
+
"/api/files/content",
|
|
166
|
+
params={"path": "large.py"},
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
payload = response.json()
|
|
170
|
+
|
|
171
|
+
assert response.status_code == 200
|
|
172
|
+
assert payload["render_mode"] == "plain"
|
|
173
|
+
assert "tok-" not in payload["html"]
|
|
174
|
+
assert payload["metadata"]["large_file"] == "true"
|
|
175
|
+
assert payload["metadata"]["render_note"] == "Large file rendered without syntax highlighting."
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_large_markdown_file_skips_markdown_rendering(tmp_path):
|
|
179
|
+
project_root = tmp_path / "proj"
|
|
180
|
+
project_root.mkdir()
|
|
181
|
+
markdown_file = project_root / "large.md"
|
|
182
|
+
markdown_file.write_text("# Heading\n\n```python\nprint('ok')\n```\n\n" * 35000, encoding="utf-8")
|
|
183
|
+
|
|
184
|
+
with client_with_root(project_root) as client:
|
|
185
|
+
response = client.get(
|
|
186
|
+
"/api/files/content",
|
|
187
|
+
params={"path": "large.md"},
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
payload = response.json()
|
|
191
|
+
|
|
192
|
+
assert response.status_code == 200
|
|
193
|
+
assert payload["render_mode"] == "plain"
|
|
194
|
+
assert 'class="md-content"' not in payload["html"]
|
|
195
|
+
assert "tok-" not in payload["html"]
|
|
196
|
+
assert payload["metadata"]["large_file"] == "true"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_file_size_limit_still_rejects_oversized_file(tmp_path):
|
|
200
|
+
project_root = tmp_path / "proj"
|
|
201
|
+
project_root.mkdir()
|
|
202
|
+
large_file = project_root / "too-large.txt"
|
|
203
|
+
large_file.write_bytes(b"x" * (10 * 1024 * 1024 + 1))
|
|
204
|
+
|
|
205
|
+
with client_with_root(project_root) as client:
|
|
206
|
+
response = client.get(
|
|
207
|
+
"/api/files/content",
|
|
208
|
+
params={"path": "too-large.txt"},
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
assert response.status_code == 400
|
|
212
|
+
assert response.json()["detail"] == "File too large (max 10MB)"
|
|
213
|
+
|
|
214
|
+
|
|
157
215
|
def test_list_files_respects_configured_root(tmp_path):
|
|
158
216
|
project_root = tmp_path / "proj"
|
|
159
217
|
project_root.mkdir()
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import subprocess
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_frontend_state_and_tmux_tree_helpers():
|
|
12
|
+
node = shutil.which("node")
|
|
13
|
+
if node is None:
|
|
14
|
+
pytest.skip("node is not available")
|
|
15
|
+
|
|
16
|
+
script = r"""
|
|
17
|
+
import assert from 'node:assert/strict';
|
|
18
|
+
import {
|
|
19
|
+
createConfigState,
|
|
20
|
+
createFilesState,
|
|
21
|
+
createMermaidState,
|
|
22
|
+
createTmuxState,
|
|
23
|
+
createViewState
|
|
24
|
+
} from './frontend/state.js';
|
|
25
|
+
import { createFilesViewController } from './frontend/files-view.js';
|
|
26
|
+
import {
|
|
27
|
+
findFirstSelectablePane,
|
|
28
|
+
getSelectedPaneKey
|
|
29
|
+
} from './frontend/tmux-tree.js';
|
|
30
|
+
|
|
31
|
+
function createDeferred() {
|
|
32
|
+
let resolve;
|
|
33
|
+
let reject;
|
|
34
|
+
const promise = new Promise((promiseResolve, promiseReject) => {
|
|
35
|
+
resolve = promiseResolve;
|
|
36
|
+
reject = promiseReject;
|
|
37
|
+
});
|
|
38
|
+
return { promise, resolve, reject };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createFileControllerHarness(apiOverrides = {}) {
|
|
42
|
+
const fileState = createFilesState();
|
|
43
|
+
const calls = {
|
|
44
|
+
inlineMessages: [],
|
|
45
|
+
renderedPayloads: [],
|
|
46
|
+
toasts: []
|
|
47
|
+
};
|
|
48
|
+
const dom = {
|
|
49
|
+
fileContentContainer: { scrollTop: 45 },
|
|
50
|
+
fileList: { querySelectorAll: () => [] },
|
|
51
|
+
pageUpFiles: { addEventListener: () => {} },
|
|
52
|
+
pageDownFiles: { addEventListener: () => {} },
|
|
53
|
+
loadFiles: { addEventListener: () => {} },
|
|
54
|
+
diffModeToggle: { addEventListener: () => {} },
|
|
55
|
+
currentPathDisplay: null
|
|
56
|
+
};
|
|
57
|
+
const contentRenderer = {
|
|
58
|
+
renderInlineMessage: (message) => calls.inlineMessages.push(message),
|
|
59
|
+
renderFileContent: (payload) => calls.renderedPayloads.push(payload),
|
|
60
|
+
renderDiffPrompt: (message) => calls.inlineMessages.push(message || 'diff prompt'),
|
|
61
|
+
resetFilePreview: () => calls.inlineMessages.push('reset')
|
|
62
|
+
};
|
|
63
|
+
const controller = createFilesViewController({
|
|
64
|
+
dom,
|
|
65
|
+
state: fileState,
|
|
66
|
+
viewState: createViewState(),
|
|
67
|
+
api: {
|
|
68
|
+
loadFileContent: () => Promise.resolve({ render_mode: 'plain', html: '', metadata: {} }),
|
|
69
|
+
loadFiles: () => Promise.resolve([]),
|
|
70
|
+
loadGitDiff: () => Promise.resolve({ text: '' }),
|
|
71
|
+
...apiOverrides
|
|
72
|
+
},
|
|
73
|
+
contentRenderer,
|
|
74
|
+
gitStatus: {
|
|
75
|
+
getSummary: () => Promise.resolve(null),
|
|
76
|
+
invalidate: () => {}
|
|
77
|
+
},
|
|
78
|
+
callbacks: {
|
|
79
|
+
markInteraction: () => {},
|
|
80
|
+
showToast: (message) => calls.toasts.push(message)
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return { calls, controller, dom, fileState };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
assert.deepEqual(createViewState(), {
|
|
87
|
+
currentView: 'tmux',
|
|
88
|
+
sidebarCollapsed: false
|
|
89
|
+
});
|
|
90
|
+
assert.equal(createTmuxState().autoRefreshEnabled, false);
|
|
91
|
+
assert.equal(createFilesState().diffSource, 'unstaged');
|
|
92
|
+
assert.equal(createFilesState().currentFileRequestId, 0);
|
|
93
|
+
assert.equal(createFilesState().currentFileAbortController, null);
|
|
94
|
+
assert.equal(createConfigState().initialized, false);
|
|
95
|
+
assert.equal(createMermaidState().diagramId, 0);
|
|
96
|
+
|
|
97
|
+
assert.equal(getSelectedPaneKey(null), null);
|
|
98
|
+
assert.equal(
|
|
99
|
+
getSelectedPaneKey({ session: 'work', window: 1, pane: 2 }),
|
|
100
|
+
'work\u00001\u00002'
|
|
101
|
+
);
|
|
102
|
+
assert.deepEqual(
|
|
103
|
+
findFirstSelectablePane([
|
|
104
|
+
{ name: 'empty', windows: [] },
|
|
105
|
+
{
|
|
106
|
+
name: 'dev',
|
|
107
|
+
windows: [
|
|
108
|
+
{ index: '0', panes: [] },
|
|
109
|
+
{ index: '1', panes: [{ index: '3' }] }
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
]),
|
|
113
|
+
{ session: 'dev', window: '1', pane: '3' }
|
|
114
|
+
);
|
|
115
|
+
assert.equal(findFirstSelectablePane([{ name: 'empty', windows: [] }]), null);
|
|
116
|
+
|
|
117
|
+
{
|
|
118
|
+
const request = createDeferred();
|
|
119
|
+
let capturedSignal = null;
|
|
120
|
+
const { calls, controller, dom } = createFileControllerHarness({
|
|
121
|
+
loadFileContent: (_path, options) => {
|
|
122
|
+
capturedSignal = options.signal;
|
|
123
|
+
return request.promise;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const pending = controller.loadFileContent('slow.py');
|
|
128
|
+
assert.equal(calls.inlineMessages.at(-1), 'Loading slow.py...');
|
|
129
|
+
assert.equal(capturedSignal instanceof AbortSignal, true);
|
|
130
|
+
|
|
131
|
+
request.resolve({
|
|
132
|
+
render_mode: 'plain',
|
|
133
|
+
html: '<div>ok</div>',
|
|
134
|
+
metadata: { render_note: 'Large file rendered without syntax highlighting.' }
|
|
135
|
+
});
|
|
136
|
+
await pending;
|
|
137
|
+
|
|
138
|
+
assert.equal(calls.renderedPayloads.length, 1);
|
|
139
|
+
assert.equal(dom.fileContentContainer.scrollTop, 0);
|
|
140
|
+
assert.deepEqual(calls.toasts, ['Large file rendered without syntax highlighting.']);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
{
|
|
144
|
+
const requests = [];
|
|
145
|
+
const { calls, controller } = createFileControllerHarness({
|
|
146
|
+
loadFileContent: (_path, options) => {
|
|
147
|
+
const request = createDeferred();
|
|
148
|
+
request.signal = options.signal;
|
|
149
|
+
requests.push(request);
|
|
150
|
+
return request.promise;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const first = controller.loadFileContent('first.py');
|
|
155
|
+
const second = controller.loadFileContent('second.py');
|
|
156
|
+
assert.equal(requests[0].signal.aborted, true);
|
|
157
|
+
|
|
158
|
+
requests[0].resolve({ render_mode: 'plain', html: 'first', metadata: {} });
|
|
159
|
+
await first;
|
|
160
|
+
assert.equal(calls.renderedPayloads.length, 0);
|
|
161
|
+
|
|
162
|
+
requests[1].resolve({ render_mode: 'plain', html: 'second', metadata: {} });
|
|
163
|
+
await second;
|
|
164
|
+
assert.equal(calls.renderedPayloads.length, 1);
|
|
165
|
+
assert.equal(calls.renderedPayloads[0].html, 'second');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
{
|
|
169
|
+
const { calls, controller } = createFileControllerHarness({
|
|
170
|
+
loadFileContent: () => Promise.reject(new DOMException('Aborted', 'AbortError'))
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await controller.loadFileContent('aborted.py');
|
|
174
|
+
|
|
175
|
+
assert.deepEqual(calls.inlineMessages, ['Loading aborted.py...']);
|
|
176
|
+
assert.equal(calls.renderedPayloads.length, 0);
|
|
177
|
+
}
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
subprocess.run(
|
|
181
|
+
[node, "--input-type=module", "-e", script],
|
|
182
|
+
cwd=PROJECT_ROOT,
|
|
183
|
+
check=True,
|
|
184
|
+
text=True,
|
|
185
|
+
)
|