notmuch-browser 0.2.0rc1__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 (46) hide show
  1. notmuch_browser-0.2.0rc1/.gitignore +10 -0
  2. notmuch_browser-0.2.0rc1/LICENSE +21 -0
  3. notmuch_browser-0.2.0rc1/PKG-INFO +106 -0
  4. notmuch_browser-0.2.0rc1/README.md +85 -0
  5. notmuch_browser-0.2.0rc1/hatch_build.py +17 -0
  6. notmuch_browser-0.2.0rc1/package-lock.json +7749 -0
  7. notmuch_browser-0.2.0rc1/package.json +27 -0
  8. notmuch_browser-0.2.0rc1/pyproject.toml +66 -0
  9. notmuch_browser-0.2.0rc1/scripts/check_dist.py +40 -0
  10. notmuch_browser-0.2.0rc1/scripts/fixture_db.py +50 -0
  11. notmuch_browser-0.2.0rc1/scripts/release.sh +14 -0
  12. notmuch_browser-0.2.0rc1/scripts/shots.mjs +235 -0
  13. notmuch_browser-0.2.0rc1/src/notmuch_browser/__init__.py +3 -0
  14. notmuch_browser-0.2.0rc1/src/notmuch_browser/__main__.py +67 -0
  15. notmuch_browser-0.2.0rc1/src/notmuch_browser/app.py +320 -0
  16. notmuch_browser-0.2.0rc1/src/notmuch_browser/notmuch.py +269 -0
  17. notmuch_browser-0.2.0rc1/src/notmuch_browser/render.py +201 -0
  18. notmuch_browser-0.2.0rc1/src/notmuch_browser/static/assets/boot.72d11448.js +1 -0
  19. notmuch_browser-0.2.0rc1/src/notmuch_browser/static/assets/main.26bc01b9.css +1319 -0
  20. notmuch_browser-0.2.0rc1/src/notmuch_browser/static/assets/main.684e6e09.js +1 -0
  21. notmuch_browser-0.2.0rc1/src/notmuch_browser/static/index.html +1 -0
  22. notmuch_browser-0.2.0rc1/tests/conftest.py +160 -0
  23. notmuch_browser-0.2.0rc1/tests/test_app.py +295 -0
  24. notmuch_browser-0.2.0rc1/tests/test_notmuch.py +169 -0
  25. notmuch_browser-0.2.0rc1/tests/test_render.py +129 -0
  26. notmuch_browser-0.2.0rc1/web/src/api.js +61 -0
  27. notmuch_browser-0.2.0rc1/web/src/app.js +273 -0
  28. notmuch_browser-0.2.0rc1/web/src/boot.js +14 -0
  29. notmuch_browser-0.2.0rc1/web/src/dom.js +44 -0
  30. notmuch_browser-0.2.0rc1/web/src/format.js +113 -0
  31. notmuch_browser-0.2.0rc1/web/src/hue.js +55 -0
  32. notmuch_browser-0.2.0rc1/web/src/icons.js +59 -0
  33. notmuch_browser-0.2.0rc1/web/src/index.html +13 -0
  34. notmuch_browser-0.2.0rc1/web/src/index.js +13 -0
  35. notmuch_browser-0.2.0rc1/web/src/reader.js +281 -0
  36. notmuch_browser-0.2.0rc1/web/src/router.js +23 -0
  37. notmuch_browser-0.2.0rc1/web/src/sidebar.js +84 -0
  38. notmuch_browser-0.2.0rc1/web/src/styles/base.css +109 -0
  39. notmuch_browser-0.2.0rc1/web/src/styles/components.css +831 -0
  40. notmuch_browser-0.2.0rc1/web/src/styles/layout.css +214 -0
  41. notmuch_browser-0.2.0rc1/web/src/styles/tokens.css +161 -0
  42. notmuch_browser-0.2.0rc1/web/src/theme.js +53 -0
  43. notmuch_browser-0.2.0rc1/web/src/threadlist.js +169 -0
  44. notmuch_browser-0.2.0rc1/web/src/toast.js +45 -0
  45. notmuch_browser-0.2.0rc1/web/src/topbar.js +124 -0
  46. notmuch_browser-0.2.0rc1/webpack.config.js +35 -0
@@ -0,0 +1,10 @@
1
+ node_modules/
2
+ .venv/
3
+ dist/
4
+ __pycache__/
5
+ *.egg-info/
6
+ .pytest_cache/
7
+ src/notmuch_browser/static/
8
+ shots/
9
+ # 21 MB launcher binary shipped with the impeccable skill, fetched on first run
10
+ .claude/skills/impeccable/scripts/bin/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Guilhem Lavaux
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,106 @@
1
+ Metadata-Version: 2.5
2
+ Name: notmuch-browser
3
+ Version: 0.2.0rc1
4
+ Summary: A small local web UI to browse and tag a notmuch mail database
5
+ Author: Guilhem Lavaux
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: email,maildir,notmuch,web
9
+ Classifier: Environment :: Web Environment
10
+ Classifier: Intended Audience :: End Users/Desktop
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Communications :: Email
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: aiohttp<4,>=3.9
19
+ Requires-Dist: nh3<0.4,>=0.3.7
20
+ Description-Content-Type: text/markdown
21
+
22
+ # notmuch-browser
23
+
24
+ A small web UI to search, read and tag the mail in a [notmuch](https://notmuchmail.org) database. It runs on your own machine and talks to the `notmuch` command line. Your mail never leaves the computer it is on.
25
+
26
+ ![Three panes: tags, threads, reader](docs/screenshot.png)
27
+
28
+ ## Install
29
+
30
+ You need Python 3.11 or newer and the `notmuch` command (0.36 or newer, developed against 0.40). You do not need Node.
31
+
32
+ ```sh
33
+ uv tool install notmuch-browser
34
+ notmuch-browser
35
+ ```
36
+
37
+ Or run it once without installing:
38
+
39
+ ```sh
40
+ uvx notmuch-browser
41
+ ```
42
+
43
+ The server listens on `http://127.0.0.1:8765/` and opens your browser. Options:
44
+
45
+ | Option | Meaning |
46
+ |---|---|
47
+ | `--port N` | listen on another port (default 8765) |
48
+ | `--notmuch-config FILE` | use a specific notmuch configuration file |
49
+ | `--no-browser` | do not open a browser window |
50
+
51
+ ## Using it
52
+
53
+ Type a notmuch query in the search field and press Enter. The help button next to the field lists the common prefixes, and `man notmuch-search-terms` has the full syntax. The left rail holds saved searches (Inbox, Unread, Flagged, All mail) and every tag, with the number of unread threads loading in the background.
54
+
55
+ | Key | Action |
56
+ |---|---|
57
+ | `/` | focus the search field |
58
+ | `j` / `k` | next and previous thread |
59
+ | `f` | flag or unflag the open thread |
60
+ | `t` | add a tag to the open thread |
61
+
62
+ Tag changes show an Undo button. Undo restores exactly the messages that the change touched, so a thread that was only partly flagged returns to its earlier state.
63
+
64
+ HTML mail is cleaned and shown in a sandboxed frame. Scripts, forms, styles and remote images are removed, so tracking pixels do not load. Inline images that belong to the message are shown. Attachments download as files.
65
+
66
+ The light and dark themes follow your system. The button in the top bar overrides it and the choice is remembered in the browser.
67
+
68
+ ## What it does to your mail
69
+
70
+ Reading changes nothing. The only write is `notmuch tag`. It never indexes, sends, composes or deletes.
71
+
72
+ Read this if you use `maildir.synchronize_flags=true` (the notmuch default): changing the `unread`, `flagged`, `replied`, `draft` or `passed` tags renames the message files in your Maildir, because notmuch keeps the Maildir flags in step. If an IMAP sync tool also works on that Maildir, avoid tagging while it runs. If another program holds the database write lock, for example `notmuch new`, a tag change fails after a few seconds with a clear "database busy" message and changes nothing.
73
+
74
+ ## Security model
75
+
76
+ - The server binds `127.0.0.1` only. There is no option to change that and there is no login.
77
+ - Requests with an unexpected `Host` header are refused, which stops DNS rebinding. Writes also need a matching `Origin` and a JSON content type, and cross-site requests are refused.
78
+ - The one cross-site request that is served is the image load of a message's inline `cid:` image from the sandboxed mail frame, because a browser labels that frame's requests cross-site. A foreign web page could use it only to learn that an image part exists and how large it is, and only if it already knew the message id.
79
+ - Attachments are always sent as downloads with a restrictive content security policy.
80
+ - On a shared machine other local users can reach the port. Do not run it there.
81
+
82
+ ## Development
83
+
84
+ ```sh
85
+ uv sync
86
+ npm ci
87
+ npm run build # compiles web/src into src/notmuch_browser/static
88
+ uv run pytest # tests use a throwaway database, never your real one
89
+ uv run notmuch-browser --notmuch-config "$(uv run python scripts/fixture_db.py /tmp/nb-fixture)"
90
+ ```
91
+
92
+ `npm run dev` rebuilds on change. The Python server serves the built files, so reload the page.
93
+
94
+ `node scripts/shots.mjs <notmuch-config>` drives the real UI with Playwright against a fixture database, checks behaviour and writes screenshots.
95
+
96
+ ### Releasing
97
+
98
+ ```sh
99
+ scripts/release.sh
100
+ ```
101
+
102
+ builds the bundle, then the wheel and sdist, and checks their contents. The bundle is not stored in git, so a wheel built without `npm run build` would have no UI. The build refuses to run in that case. The script does not upload anything. Publish with `uv publish` when you are ready.
103
+
104
+ ## License
105
+
106
+ MIT
@@ -0,0 +1,85 @@
1
+ # notmuch-browser
2
+
3
+ A small web UI to search, read and tag the mail in a [notmuch](https://notmuchmail.org) database. It runs on your own machine and talks to the `notmuch` command line. Your mail never leaves the computer it is on.
4
+
5
+ ![Three panes: tags, threads, reader](docs/screenshot.png)
6
+
7
+ ## Install
8
+
9
+ You need Python 3.11 or newer and the `notmuch` command (0.36 or newer, developed against 0.40). You do not need Node.
10
+
11
+ ```sh
12
+ uv tool install notmuch-browser
13
+ notmuch-browser
14
+ ```
15
+
16
+ Or run it once without installing:
17
+
18
+ ```sh
19
+ uvx notmuch-browser
20
+ ```
21
+
22
+ The server listens on `http://127.0.0.1:8765/` and opens your browser. Options:
23
+
24
+ | Option | Meaning |
25
+ |---|---|
26
+ | `--port N` | listen on another port (default 8765) |
27
+ | `--notmuch-config FILE` | use a specific notmuch configuration file |
28
+ | `--no-browser` | do not open a browser window |
29
+
30
+ ## Using it
31
+
32
+ Type a notmuch query in the search field and press Enter. The help button next to the field lists the common prefixes, and `man notmuch-search-terms` has the full syntax. The left rail holds saved searches (Inbox, Unread, Flagged, All mail) and every tag, with the number of unread threads loading in the background.
33
+
34
+ | Key | Action |
35
+ |---|---|
36
+ | `/` | focus the search field |
37
+ | `j` / `k` | next and previous thread |
38
+ | `f` | flag or unflag the open thread |
39
+ | `t` | add a tag to the open thread |
40
+
41
+ Tag changes show an Undo button. Undo restores exactly the messages that the change touched, so a thread that was only partly flagged returns to its earlier state.
42
+
43
+ HTML mail is cleaned and shown in a sandboxed frame. Scripts, forms, styles and remote images are removed, so tracking pixels do not load. Inline images that belong to the message are shown. Attachments download as files.
44
+
45
+ The light and dark themes follow your system. The button in the top bar overrides it and the choice is remembered in the browser.
46
+
47
+ ## What it does to your mail
48
+
49
+ Reading changes nothing. The only write is `notmuch tag`. It never indexes, sends, composes or deletes.
50
+
51
+ Read this if you use `maildir.synchronize_flags=true` (the notmuch default): changing the `unread`, `flagged`, `replied`, `draft` or `passed` tags renames the message files in your Maildir, because notmuch keeps the Maildir flags in step. If an IMAP sync tool also works on that Maildir, avoid tagging while it runs. If another program holds the database write lock, for example `notmuch new`, a tag change fails after a few seconds with a clear "database busy" message and changes nothing.
52
+
53
+ ## Security model
54
+
55
+ - The server binds `127.0.0.1` only. There is no option to change that and there is no login.
56
+ - Requests with an unexpected `Host` header are refused, which stops DNS rebinding. Writes also need a matching `Origin` and a JSON content type, and cross-site requests are refused.
57
+ - The one cross-site request that is served is the image load of a message's inline `cid:` image from the sandboxed mail frame, because a browser labels that frame's requests cross-site. A foreign web page could use it only to learn that an image part exists and how large it is, and only if it already knew the message id.
58
+ - Attachments are always sent as downloads with a restrictive content security policy.
59
+ - On a shared machine other local users can reach the port. Do not run it there.
60
+
61
+ ## Development
62
+
63
+ ```sh
64
+ uv sync
65
+ npm ci
66
+ npm run build # compiles web/src into src/notmuch_browser/static
67
+ uv run pytest # tests use a throwaway database, never your real one
68
+ uv run notmuch-browser --notmuch-config "$(uv run python scripts/fixture_db.py /tmp/nb-fixture)"
69
+ ```
70
+
71
+ `npm run dev` rebuilds on change. The Python server serves the built files, so reload the page.
72
+
73
+ `node scripts/shots.mjs <notmuch-config>` drives the real UI with Playwright against a fixture database, checks behaviour and writes screenshots.
74
+
75
+ ### Releasing
76
+
77
+ ```sh
78
+ scripts/release.sh
79
+ ```
80
+
81
+ builds the bundle, then the wheel and sdist, and checks their contents. The bundle is not stored in git, so a wheel built without `npm run build` would have no UI. The build refuses to run in that case. The script does not upload anything. Publish with `uv publish` when you are ready.
82
+
83
+ ## License
84
+
85
+ MIT
@@ -0,0 +1,17 @@
1
+ """Hatch build hook: a package without the compiled UI must never be built."""
2
+
3
+ from pathlib import Path
4
+
5
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
6
+
7
+
8
+ class StaticBundleHook(BuildHookInterface):
9
+ def initialize(self, version, build_data):
10
+ if version == "editable":
11
+ return
12
+ index = Path(self.root) / "src" / "notmuch_browser" / "static" / "index.html"
13
+ if not index.is_file():
14
+ raise RuntimeError(
15
+ f"{index} is missing. Run `npm ci && npm run build` before building "
16
+ "the package (see scripts/release.sh)."
17
+ )