dvm-mdview 0.1.0__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.
Files changed (53) hide show
  1. dvm_mdview-0.1.0/.github/workflows/ci.yml +28 -0
  2. dvm_mdview-0.1.0/.github/workflows/publish.yml +56 -0
  3. dvm_mdview-0.1.0/.gitignore +8 -0
  4. dvm_mdview-0.1.0/AGENTS.md +43 -0
  5. dvm_mdview-0.1.0/CLAUDE.md +1 -0
  6. dvm_mdview-0.1.0/LICENSE +21 -0
  7. dvm_mdview-0.1.0/PKG-INFO +170 -0
  8. dvm_mdview-0.1.0/README.md +141 -0
  9. dvm_mdview-0.1.0/docs/dev/architecture.md +45 -0
  10. dvm_mdview-0.1.0/docs/dev/git.md +41 -0
  11. dvm_mdview-0.1.0/docs/dev/releasing.md +50 -0
  12. dvm_mdview-0.1.0/docs/dev/testing.md +35 -0
  13. dvm_mdview-0.1.0/pyproject.toml +58 -0
  14. dvm_mdview-0.1.0/src/mdview/__init__.py +4 -0
  15. dvm_mdview-0.1.0/src/mdview/__main__.py +3 -0
  16. dvm_mdview-0.1.0/src/mdview/_version.py +24 -0
  17. dvm_mdview-0.1.0/src/mdview/cli.py +64 -0
  18. dvm_mdview-0.1.0/src/mdview/gui.py +105 -0
  19. dvm_mdview-0.1.0/src/mdview/render.py +242 -0
  20. dvm_mdview-0.1.0/src/mdview/server.py +298 -0
  21. dvm_mdview-0.1.0/src/mdview/tui.py +514 -0
  22. dvm_mdview-0.1.0/src/mdview/web/app.css +172 -0
  23. dvm_mdview-0.1.0/src/mdview/web/app.js +638 -0
  24. dvm_mdview-0.1.0/src/mdview/web/index.html +64 -0
  25. dvm_mdview-0.1.0/src/mdview/web/vendor/LICENSE-katex +21 -0
  26. dvm_mdview-0.1.0/src/mdview/web/vendor/LICENSE-mermaid +21 -0
  27. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  28. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  29. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  30. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  31. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  32. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Main-Bold.woff2 +0 -0
  33. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  34. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Main-Italic.woff2 +0 -0
  35. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Main-Regular.woff2 +0 -0
  36. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  37. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Math-Italic.woff2 +0 -0
  38. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  39. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  40. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  41. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Script-Regular.woff2 +0 -0
  42. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  43. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  44. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  45. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  46. dvm_mdview-0.1.0/src/mdview/web/vendor/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  47. dvm_mdview-0.1.0/src/mdview/web/vendor/katex.min.css +1 -0
  48. dvm_mdview-0.1.0/src/mdview/web/vendor/katex.min.js +1 -0
  49. dvm_mdview-0.1.0/src/mdview/web/vendor/mermaid.min.js +4329 -0
  50. dvm_mdview-0.1.0/tests/test_render.py +90 -0
  51. dvm_mdview-0.1.0/tests/test_server.py +121 -0
  52. dvm_mdview-0.1.0/tests/test_tui.py +111 -0
  53. dvm_mdview-0.1.0/uv.lock +743 -0
@@ -0,0 +1,28 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ os: [ubuntu-latest, macos-latest, windows-latest]
14
+ python: ["3.10", "3.13"]
15
+ runs-on: ${{ matrix.os }}
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0 # the version is derived from git tags
20
+ - uses: astral-sh/setup-uv@v6
21
+ with:
22
+ python-version: ${{ matrix.python }}
23
+ - run: uv sync
24
+ - run: uv run pytest -q
25
+ - run: uv build
26
+ - name: Run the built wheel the way uvx does
27
+ shell: bash
28
+ run: uvx --from dist/*.whl dvm-mdview --version
@@ -0,0 +1,56 @@
1
+ name: Release
2
+
3
+ # Pushing a tag such as v1.2.3 builds version 1.2.3 (the version is derived
4
+ # from the tag), creates the GitHub release and uploads to PyPI.
5
+ #
6
+ # PyPI upload uses "trusted publishing": on pypi.org add this repository,
7
+ # workflow "publish.yml" and environment "pypi" as a trusted publisher for
8
+ # the project dvm-mdview. No API token is stored.
9
+ on:
10
+ push:
11
+ tags: ["v*"]
12
+
13
+ jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0
20
+ - uses: astral-sh/setup-uv@v6
21
+ - run: uv build
22
+ - name: Check that the built version matches the tag
23
+ run: ls "dist/dvm_mdview-${GITHUB_REF_NAME#v}-py3-none-any.whl" "dist/dvm_mdview-${GITHUB_REF_NAME#v}.tar.gz"
24
+ - uses: actions/upload-artifact@v4
25
+ with:
26
+ name: dist
27
+ path: dist/
28
+ if-no-files-found: error
29
+
30
+ github-release:
31
+ needs: build
32
+ runs-on: ubuntu-latest
33
+ permissions:
34
+ contents: write
35
+ steps:
36
+ - uses: actions/download-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+ - run: gh release create "$GITHUB_REF_NAME" dist/* --repo "$GITHUB_REPOSITORY" --title "$GITHUB_REF_NAME" --generate-notes --verify-tag
41
+ env:
42
+ GH_TOKEN: ${{ github.token }}
43
+
44
+ pypi:
45
+ needs: build
46
+ runs-on: ubuntu-latest
47
+ environment: pypi
48
+ permissions:
49
+ id-token: write
50
+ steps:
51
+ - uses: actions/download-artifact@v4
52
+ with:
53
+ name: dist
54
+ path: dist/
55
+ - uses: astral-sh/setup-uv@v6
56
+ - run: uv publish
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ src/mdview/_version.py
@@ -0,0 +1,43 @@
1
+ # mdview
2
+
3
+ Markdown viewer and editor with live preview. One Python package (`dvm-mdview` on PyPI, import
4
+ name `mdview`) with a graphical UI and a terminal UI that share one renderer.
5
+
6
+ ## Rules that always apply
7
+
8
+ 1. **No mention of Claude, Anthropic, AI or AI assistance** in commit messages (including trailers),
9
+ pull request or issue text, comments, or any file. Only `AGENTS.md`, `CLAUDE.md` and
10
+ `docs/dev/git.md` may mention it, because they state the rule.
11
+ 2. **Commit as the repository owner**, never under a tool or bot identity. Set author and committer
12
+ on every commit; the exact commands are in `docs/dev/git.md`.
13
+ 3. **Must run on macOS and Windows through `uvx`.** Pure Python only, no build step, no new
14
+ dependency without wheels for both.
15
+
16
+ ## Everyday commands
17
+
18
+ ```bash
19
+ uv sync
20
+ ```
21
+
22
+ ```bash
23
+ uv run pytest -q
24
+ ```
25
+
26
+ ```bash
27
+ uv run mdview --browser README.md
28
+ ```
29
+
30
+ ```bash
31
+ uv run mdview --tui README.md
32
+ ```
33
+
34
+ ## Read more only when the task needs it
35
+
36
+ | If you are about to... | Read |
37
+ |---|---|
38
+ | commit, push, or write a pull request | `docs/dev/git.md` |
39
+ | change rendering, the backend, or either UI | `docs/dev/architecture.md` |
40
+ | add or debug tests, or check a UI change by hand | `docs/dev/testing.md` |
41
+ | cut a release or update the bundled Mermaid / KaTeX | `docs/dev/releasing.md` |
42
+
43
+ User-facing behaviour, options and keys are documented in `README.md`; keep it in step with changes.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 divyavanmahajan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.5
2
+ Name: dvm-mdview
3
+ Version: 0.1.0
4
+ Summary: Markdown viewer and live-preview editor with a graphical UI and a terminal UI
5
+ Project-URL: Homepage, https://github.com/divyavanmahajan/mdview
6
+ Project-URL: Issues, https://github.com/divyavanmahajan/mdview/issues
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ License-File: src/mdview/web/vendor/LICENSE-katex
10
+ License-File: src/mdview/web/vendor/LICENSE-mermaid
11
+ Keywords: editor,gui,markdown,preview,tui,viewer
12
+ Classifier: Environment :: Console
13
+ Classifier: Environment :: MacOS X
14
+ Classifier: Environment :: Win32 (MS Windows)
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Topic :: Text Editors
20
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: linkify-it-py>=2.0
23
+ Requires-Dist: markdown-it-py>=3.0
24
+ Requires-Dist: mdit-py-plugins>=0.4.1
25
+ Requires-Dist: pygments>=2.17
26
+ Requires-Dist: pywebview>=5.3
27
+ Requires-Dist: textual>=3.0
28
+ Description-Content-Type: text/markdown
29
+
30
+ # mdview
31
+
32
+ A Markdown viewer and editor with live preview, for macOS, Windows and Linux.
33
+ It has two interfaces that share one renderer:
34
+
35
+ - a **graphical UI** in a native window, and
36
+ - a **terminal UI** for use in a shell or over SSH.
37
+
38
+ Published on PyPI as **`dvm-mdview`**.
39
+
40
+ ## Run it
41
+
42
+ With [uv](https://docs.astral.sh/uv/) installed, nothing else needs installing:
43
+
44
+ ```bash
45
+ uvx dvm-mdview README.md
46
+ ```
47
+
48
+ ```bash
49
+ uvx dvm-mdview --tui README.md
50
+ ```
51
+
52
+ Or install it, which gives you the shorter `mdview` command:
53
+
54
+ ```bash
55
+ uv tool install dvm-mdview
56
+ ```
57
+
58
+ ```bash
59
+ mdview notes.md
60
+ ```
61
+
62
+ | Option | Effect |
63
+ |---|---|
64
+ | *(none)* | Graphical UI. Falls back to the terminal UI when there is no display. |
65
+ | `-t`, `--tui` | Terminal UI. |
66
+ | `-b`, `--browser` | Graphical UI in your default browser instead of a native window. |
67
+ | `-e`, `--edit` | Start with editor and preview side by side. |
68
+ | `-r`, `--read` | Start with the preview only. |
69
+
70
+ A file that does not exist yet is opened empty and created when you save.
71
+ Passing a folder opens its `README.md`.
72
+
73
+ ## Features
74
+
75
+ - **Live preview.** View, Split (editor and preview side by side) and Edit layouts. The preview
76
+ updates as you type.
77
+ - **Link navigation.** Links to other Markdown files open in place, with back and forward history.
78
+ Links to headings scroll to them. Web links open in your browser. A link to a file that does not
79
+ exist starts a new document at that path.
80
+ - **Markdown.** CommonMark with GitHub tables, task lists, strikethrough and autolinks, plus
81
+ footnotes, YAML front matter and heading anchors.
82
+ - **Code highlighting** in both interfaces.
83
+ - **Mermaid diagrams and LaTeX math** rendered in the graphical UI (shown as source in the terminal).
84
+ Both work offline; the libraries are bundled.
85
+ - **Safe saving.** Files are written atomically, you are asked before unsaved changes are lost, and
86
+ a file changed by another program is reloaded (or flagged, if you have edits of your own).
87
+ - Outline sidebar, light and dark themes, and relative images in the graphical UI.
88
+
89
+ ## Keys
90
+
91
+ ### Graphical UI
92
+
93
+ `Cmd` replaces `Ctrl` on macOS.
94
+
95
+ | Key | Action |
96
+ |---|---|
97
+ | `Ctrl+E` | Cycle View / Split / Edit |
98
+ | `Ctrl+S`, `Ctrl+Shift+S` | Save, Save as |
99
+ | `Ctrl+O`, `Ctrl+N` | Open, New |
100
+ | `Alt+Left`, `Alt+Right` | Back, Forward |
101
+ | `Ctrl+B`, `Ctrl+I`, `Ctrl+K` | Bold, italic, link |
102
+
103
+ ### Terminal UI
104
+
105
+ | Key | Action |
106
+ |---|---|
107
+ | `Ctrl+E` | Cycle View / Split / Edit |
108
+ | `F6` | Switch between editor and preview |
109
+ | `Ctrl+S` | Save |
110
+ | `Ctrl+O` | Open |
111
+ | `Ctrl+L` | List the document's links and follow one |
112
+ | `Alt+Left`, `Alt+Right` | Back, Forward |
113
+ | `Ctrl+P` | Command palette (Save as, New, theme, ...) |
114
+ | `Ctrl+Q` | Quit |
115
+
116
+ Links can also be clicked with the mouse. While the preview has focus, single keys work too:
117
+ `e` edit, `l` links, `o` open, `b` back, `f` forward, `j`/`k` scroll, `q` quit.
118
+
119
+ ## How it is built
120
+
121
+ | Part | Library |
122
+ |---|---|
123
+ | Markdown parsing | [markdown-it-py](https://github.com/executablebooks/markdown-it-py) and [mdit-py-plugins](https://github.com/executablebooks/mdit-py-plugins) |
124
+ | Code highlighting | [Pygments](https://pygments.org/) |
125
+ | Native window | [pywebview](https://pywebview.flowrl.com/) (WebKit on macOS, WebView2 on Windows) |
126
+ | Terminal UI | [Textual](https://textual.textualize.io/) |
127
+ | Diagrams and math | [Mermaid](https://mermaid.js.org/) and [KaTeX](https://katex.org/), bundled |
128
+
129
+ The graphical UI is a small web app served by a local backend (`src/mdview/server.py`) that listens
130
+ only on `127.0.0.1` and requires a per-run secret token. Raw HTML in a document is displayed, but a
131
+ strict Content-Security-Policy stops any script in it from running.
132
+
133
+ On Linux a native window needs GTK or Qt bindings for pywebview; without them mdview uses the
134
+ browser automatically.
135
+
136
+ ## Development
137
+
138
+ ```bash
139
+ uv sync
140
+ ```
141
+
142
+ ```bash
143
+ uv run pytest
144
+ ```
145
+
146
+ ```bash
147
+ uv run mdview README.md
148
+ ```
149
+
150
+ ### Releasing
151
+
152
+ The version comes from the git tag, so the tag and the PyPI version always match.
153
+
154
+ 1. Once, on pypi.org, add this repository as a *trusted publisher* for `dvm-mdview`
155
+ (workflow `publish.yml`, environment `pypi`).
156
+ 2. Tag a commit on `main` and push the tag:
157
+
158
+ ```bash
159
+ git tag v1.2.3
160
+ ```
161
+
162
+ ```bash
163
+ git push origin v1.2.3
164
+ ```
165
+
166
+ The `Release` workflow builds version 1.2.3, creates the GitHub release and uploads to PyPI.
167
+
168
+ ## License
169
+
170
+ MIT. Bundled Mermaid and KaTeX are MIT licensed; their licences are in `src/mdview/web/vendor/`.
@@ -0,0 +1,141 @@
1
+ # mdview
2
+
3
+ A Markdown viewer and editor with live preview, for macOS, Windows and Linux.
4
+ It has two interfaces that share one renderer:
5
+
6
+ - a **graphical UI** in a native window, and
7
+ - a **terminal UI** for use in a shell or over SSH.
8
+
9
+ Published on PyPI as **`dvm-mdview`**.
10
+
11
+ ## Run it
12
+
13
+ With [uv](https://docs.astral.sh/uv/) installed, nothing else needs installing:
14
+
15
+ ```bash
16
+ uvx dvm-mdview README.md
17
+ ```
18
+
19
+ ```bash
20
+ uvx dvm-mdview --tui README.md
21
+ ```
22
+
23
+ Or install it, which gives you the shorter `mdview` command:
24
+
25
+ ```bash
26
+ uv tool install dvm-mdview
27
+ ```
28
+
29
+ ```bash
30
+ mdview notes.md
31
+ ```
32
+
33
+ | Option | Effect |
34
+ |---|---|
35
+ | *(none)* | Graphical UI. Falls back to the terminal UI when there is no display. |
36
+ | `-t`, `--tui` | Terminal UI. |
37
+ | `-b`, `--browser` | Graphical UI in your default browser instead of a native window. |
38
+ | `-e`, `--edit` | Start with editor and preview side by side. |
39
+ | `-r`, `--read` | Start with the preview only. |
40
+
41
+ A file that does not exist yet is opened empty and created when you save.
42
+ Passing a folder opens its `README.md`.
43
+
44
+ ## Features
45
+
46
+ - **Live preview.** View, Split (editor and preview side by side) and Edit layouts. The preview
47
+ updates as you type.
48
+ - **Link navigation.** Links to other Markdown files open in place, with back and forward history.
49
+ Links to headings scroll to them. Web links open in your browser. A link to a file that does not
50
+ exist starts a new document at that path.
51
+ - **Markdown.** CommonMark with GitHub tables, task lists, strikethrough and autolinks, plus
52
+ footnotes, YAML front matter and heading anchors.
53
+ - **Code highlighting** in both interfaces.
54
+ - **Mermaid diagrams and LaTeX math** rendered in the graphical UI (shown as source in the terminal).
55
+ Both work offline; the libraries are bundled.
56
+ - **Safe saving.** Files are written atomically, you are asked before unsaved changes are lost, and
57
+ a file changed by another program is reloaded (or flagged, if you have edits of your own).
58
+ - Outline sidebar, light and dark themes, and relative images in the graphical UI.
59
+
60
+ ## Keys
61
+
62
+ ### Graphical UI
63
+
64
+ `Cmd` replaces `Ctrl` on macOS.
65
+
66
+ | Key | Action |
67
+ |---|---|
68
+ | `Ctrl+E` | Cycle View / Split / Edit |
69
+ | `Ctrl+S`, `Ctrl+Shift+S` | Save, Save as |
70
+ | `Ctrl+O`, `Ctrl+N` | Open, New |
71
+ | `Alt+Left`, `Alt+Right` | Back, Forward |
72
+ | `Ctrl+B`, `Ctrl+I`, `Ctrl+K` | Bold, italic, link |
73
+
74
+ ### Terminal UI
75
+
76
+ | Key | Action |
77
+ |---|---|
78
+ | `Ctrl+E` | Cycle View / Split / Edit |
79
+ | `F6` | Switch between editor and preview |
80
+ | `Ctrl+S` | Save |
81
+ | `Ctrl+O` | Open |
82
+ | `Ctrl+L` | List the document's links and follow one |
83
+ | `Alt+Left`, `Alt+Right` | Back, Forward |
84
+ | `Ctrl+P` | Command palette (Save as, New, theme, ...) |
85
+ | `Ctrl+Q` | Quit |
86
+
87
+ Links can also be clicked with the mouse. While the preview has focus, single keys work too:
88
+ `e` edit, `l` links, `o` open, `b` back, `f` forward, `j`/`k` scroll, `q` quit.
89
+
90
+ ## How it is built
91
+
92
+ | Part | Library |
93
+ |---|---|
94
+ | Markdown parsing | [markdown-it-py](https://github.com/executablebooks/markdown-it-py) and [mdit-py-plugins](https://github.com/executablebooks/mdit-py-plugins) |
95
+ | Code highlighting | [Pygments](https://pygments.org/) |
96
+ | Native window | [pywebview](https://pywebview.flowrl.com/) (WebKit on macOS, WebView2 on Windows) |
97
+ | Terminal UI | [Textual](https://textual.textualize.io/) |
98
+ | Diagrams and math | [Mermaid](https://mermaid.js.org/) and [KaTeX](https://katex.org/), bundled |
99
+
100
+ The graphical UI is a small web app served by a local backend (`src/mdview/server.py`) that listens
101
+ only on `127.0.0.1` and requires a per-run secret token. Raw HTML in a document is displayed, but a
102
+ strict Content-Security-Policy stops any script in it from running.
103
+
104
+ On Linux a native window needs GTK or Qt bindings for pywebview; without them mdview uses the
105
+ browser automatically.
106
+
107
+ ## Development
108
+
109
+ ```bash
110
+ uv sync
111
+ ```
112
+
113
+ ```bash
114
+ uv run pytest
115
+ ```
116
+
117
+ ```bash
118
+ uv run mdview README.md
119
+ ```
120
+
121
+ ### Releasing
122
+
123
+ The version comes from the git tag, so the tag and the PyPI version always match.
124
+
125
+ 1. Once, on pypi.org, add this repository as a *trusted publisher* for `dvm-mdview`
126
+ (workflow `publish.yml`, environment `pypi`).
127
+ 2. Tag a commit on `main` and push the tag:
128
+
129
+ ```bash
130
+ git tag v1.2.3
131
+ ```
132
+
133
+ ```bash
134
+ git push origin v1.2.3
135
+ ```
136
+
137
+ The `Release` workflow builds version 1.2.3, creates the GitHub release and uploads to PyPI.
138
+
139
+ ## License
140
+
141
+ MIT. Bundled Mermaid and KaTeX are MIT licensed; their licences are in `src/mdview/web/vendor/`.
@@ -0,0 +1,45 @@
1
+ # Architecture
2
+
3
+ All code is in `src/mdview/`.
4
+
5
+ | File | Role |
6
+ |---|---|
7
+ | `cli.py` | Argument parsing; picks terminal UI, native window or browser. |
8
+ | `render.py` | Everything both UIs share: Markdown to HTML, link resolution, link extraction, reading and atomic writing of files. |
9
+ | `server.py` | Loopback HTTP backend for the graphical UI. |
10
+ | `gui.py` | Hosts the web app in a pywebview window, or the default browser as fallback. |
11
+ | `tui.py` | Textual app. |
12
+ | `web/` | The graphical UI: `index.html`, `app.js`, `app.css`, and `vendor/` (bundled Mermaid and KaTeX). |
13
+
14
+ ## Rendering
15
+
16
+ - `render.py` builds one markdown-it-py parser (`gfm-like` preset plus front matter, footnotes,
17
+ task lists, anchors, dollar math). Pygments highlights fenced code in Python.
18
+ - Mermaid fences become `<pre class="mermaid">` and math becomes `.math` elements; `app.js` renders
19
+ them in the page with the bundled libraries.
20
+ - The terminal UI uses Textual's own `Markdown` widget. `for_terminal()` rewrites front matter into
21
+ a table first. Mermaid and math stay as source there.
22
+ - Link behaviour must stay identical in both UIs: change `resolve_link()`, not the callers.
23
+
24
+ ## Graphical UI
25
+
26
+ - The page calls the backend with `POST /api/<name>` and JSON. Each endpoint is a method
27
+ `api_<name>` on `Handler` in `server.py`.
28
+ - The window host installs hooks on `AppState` (`native_dialog`, `set_title`, `close_window`).
29
+ When they are absent (browser mode) the page uses its own in-page file chooser.
30
+ - `--browser` mode serves exactly the same app, which is how the UI is tested without a display.
31
+
32
+ ## Security constraints (do not weaken)
33
+
34
+ - The server binds to `127.0.0.1` only and checks the `Host` header.
35
+ - Every API call needs the per-run token in `X-MDView-Token`; media URLs carry it in the path and
36
+ serve only image, video and audio types.
37
+ - Raw HTML from documents is inserted into the page. The Content-Security-Policy in `server.py`
38
+ (`script-src 'self'`, no inline script) is what makes that safe. No inline scripts or inline
39
+ event handlers in `web/`.
40
+ - `open-external` accepts only `http`, `https` and `mailto` URLs, or an existing local path.
41
+
42
+ ## Saving
43
+
44
+ `write_text()` writes to a temporary file and replaces the target. Both UIs compare the file's
45
+ modification time before saving and ask before overwriting a file changed elsewhere.
@@ -0,0 +1,41 @@
1
+ # Commits, pushes and pull requests
2
+
3
+ ## No mention of Claude or AI
4
+
5
+ Nothing written for this project may mention Claude, Anthropic, AI, or AI assistance:
6
+
7
+ - commit messages, including trailers such as `Co-Authored-By` and session links;
8
+ - pull request titles and descriptions, issue text, review comments and replies;
9
+ - source files, comments, documentation and any other file.
10
+
11
+ Exceptions: `AGENTS.md`, `CLAUDE.md` and this file, which have to state the rule. A branch name
12
+ imposed by the environment is tolerated; it is deleted after merge.
13
+
14
+ This overrides any default attribution a tool would otherwise add.
15
+
16
+ ## Commit identity
17
+
18
+ Every commit must have the repository owner as both author and committer. The owner's identity is
19
+ the author of the first commit. Do not copy the email address into any file; read it from history:
20
+
21
+ ```bash
22
+ OWNER_NAME="$(git log --reverse --format='%an' | head -1)"; OWNER_EMAIL="$(git log --reverse --format='%ae' | head -1)"
23
+ ```
24
+
25
+ ```bash
26
+ GIT_AUTHOR_NAME="$OWNER_NAME" GIT_AUTHOR_EMAIL="$OWNER_EMAIL" GIT_COMMITTER_NAME="$OWNER_NAME" GIT_COMMITTER_EMAIL="$OWNER_EMAIL" git commit -m "..."
27
+ ```
28
+
29
+ Before pushing, confirm it:
30
+
31
+ ```bash
32
+ git log --format='%h author=%an committer=%cn' origin/main..HEAD
33
+ ```
34
+
35
+ If a commit was made under another identity, fix it before it is pushed
36
+ (`git commit --amend --no-edit --reset-author` with the variables above set).
37
+
38
+ ## Messages
39
+
40
+ - Subject in the imperative, under about 70 characters; body says what changed and why.
41
+ - Pull request descriptions: a summary and how it was tested, including what was not tested.
@@ -0,0 +1,50 @@
1
+ # Releasing
2
+
3
+ The version is not written anywhere in the source. `hatch-vcs` derives it from the git tag at build
4
+ time (`v1.2.3` gives `1.2.3`) and writes `src/mdview/_version.py`, which is not committed. Between
5
+ tags, builds get a development version such as `0.2.dev3+g1a2b3c4`.
6
+
7
+ To release, tag a commit on `main` and push the tag:
8
+
9
+ ```bash
10
+ git tag v1.2.3
11
+ ```
12
+
13
+ ```bash
14
+ git push origin v1.2.3
15
+ ```
16
+
17
+ `.github/workflows/publish.yml` then builds, checks that the built version equals the tag, creates
18
+ the GitHub release with the built files attached, and uploads to PyPI through trusted publishing
19
+ (project `dvm-mdview`, workflow `publish.yml`, environment `pypi`; configured once on pypi.org).
20
+ The GitHub release and the PyPI upload are separate jobs, so one can be re-run without the other.
21
+
22
+ Do not create the release by hand in the GitHub UI; the workflow would then fail to create it.
23
+
24
+ To check a build locally:
25
+
26
+ ```bash
27
+ uv build
28
+ ```
29
+
30
+ ```bash
31
+ uvx --from dist/*.whl dvm-mdview --version
32
+ ```
33
+
34
+ Builds need git history and tags; in CI, check out with `fetch-depth: 0`.
35
+
36
+ The wheel must contain `mdview/web/` including `vendor/`; `uv build` output can be checked with
37
+ `python -m zipfile -l dist/*.whl`.
38
+
39
+ ## Updating bundled Mermaid and KaTeX
40
+
41
+ Currently Mermaid 12.3.0 and KaTeX 0.19.1. They are copied from the npm packages, not built:
42
+
43
+ ```bash
44
+ npm pack mermaid katex
45
+ ```
46
+
47
+ From the unpacked packages copy `mermaid/dist/mermaid.min.js`, `katex/dist/katex.min.js`,
48
+ `katex/dist/katex.min.css`, `katex/dist/fonts/*.woff2` and both `LICENSE` files into
49
+ `src/mdview/web/vendor/` (licences as `LICENSE-mermaid` and `LICENSE-katex`). Update the versions
50
+ above, then check a Mermaid block and a math block in the graphical UI.
@@ -0,0 +1,35 @@
1
+ # Testing
2
+
3
+ ```bash
4
+ uv run pytest -q
5
+ ```
6
+
7
+ | File | Covers |
8
+ |---|---|
9
+ | `tests/test_render.py` | Rendering features, slugs, link resolution, file reading and writing. |
10
+ | `tests/test_server.py` | Backend endpoints, token and host checks, save conflicts, media serving. |
11
+ | `tests/test_tui.py` | Terminal UI through Textual's pilot. |
12
+
13
+ ## Terminal UI tests
14
+
15
+ Actions that open a dialog run in a worker that waits for the dialog. Do not call
16
+ `app.workers.wait_for_complete()` while a dialog is open; it never returns. Use `pilot.pause()`,
17
+ then act on the dialog.
18
+
19
+ ## Checking the graphical UI
20
+
21
+ There are no automated browser tests in the repository. To check a change, run the app in browser
22
+ mode and drive it with a browser (by hand or with Playwright):
23
+
24
+ ```bash
25
+ uv run mdview --browser --no-open --port 8765 README.md
26
+ ```
27
+
28
+ Wait for `body[data-ready]` before interacting. Check at least: live preview while typing, a link to
29
+ another file and back, an anchor link, save, and a Mermaid block and a math block.
30
+
31
+ ## What cannot be tested on Linux without a display
32
+
33
+ The native pywebview window, native file dialogs, and the close prompt with unsaved changes. These
34
+ need a manual check on macOS and Windows. CI runs the test suite on all three systems but does not
35
+ open a window.