joppylib 0.1.0a12__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.
@@ -0,0 +1,207 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ #dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Environments
138
+ .env
139
+ .envrc
140
+ .venv
141
+ env/
142
+ venv/
143
+ ENV/
144
+ env.bak/
145
+ venv.bak/
146
+
147
+ # Spyder project settings
148
+ .spyderproject
149
+ .spyproject
150
+
151
+ # Rope project settings
152
+ .ropeproject
153
+
154
+ # mkdocs documentation
155
+ /site
156
+
157
+ # mypy
158
+ .mypy_cache/
159
+ .dmypy.json
160
+ dmypy.json
161
+
162
+ # Pyre type checker
163
+ .pyre/
164
+
165
+ # pytype static type analyzer
166
+ .pytype/
167
+
168
+ # Cython debug symbols
169
+ cython_debug/
170
+
171
+ # PyCharm
172
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
175
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
+ #.idea/
177
+
178
+ # Abstra
179
+ # Abstra is an AI-powered process automation framework.
180
+ # Ignore directories containing user credentials, local state, and settings.
181
+ # Learn more at https://abstra.io/docs
182
+ .abstra/
183
+
184
+ # Visual Studio Code
185
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
+ # you could uncomment the following to ignore the entire vscode folder
189
+ # .vscode/
190
+
191
+ # Ruff stuff:
192
+ .ruff_cache/
193
+
194
+ # PyPI configuration file
195
+ .pypirc
196
+
197
+ # Cursor
198
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
+ # refer to https://docs.cursor.com/context/ignore-files
201
+ .cursorignore
202
+ .cursorindexingignore
203
+
204
+ # Marimo
205
+ marimo/_static/
206
+ marimo/_lsp/
207
+ __marimo__/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com),
6
+ and this project adheres to [Semantic Versioning](https://semver.org).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0a12] - 2026-04-01
11
+
12
+ ### Added
13
+ - Runtime `__version__` attribute derived from package metadata.
14
+ - CHANGELOG.md following Keep a Changelog format.
15
+ - PyPI metadata: license, keywords, classifiers, URLS.
16
+
17
+ ## [0.1.0a11] - 2026-03-31
18
+
19
+ Alpha development phase, consolidated from releases 0.1.0a1 through 0.1.0a11.
20
+
21
+ ### Added
22
+ - Note management: create, read, update, delete, search, and list notes.
23
+ - Tag management: full CRUD, add/remove tags from notes, get all tags for a note.
24
+ - Folder management: full CRUD operations.
25
+ - Support for events, resources, and revisions.
26
+ - Full-text search using Joplin's search syntax with field filtering and ordering.
27
+ - Batch listing with optional field filtering and ordering parameters.
28
+ - Flexible authentication: interactive (browser-based) and API key-based.
29
+ - Debug mode for including raw API responses.
30
+ - Custom exceptions and connection validation.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jeroen Kroesen
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,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: joppylib
3
+ Version: 0.1.0a12
4
+ Summary: A thin abstraction layer over the Joplin Data API
5
+ Project-URL: Homepage, https://github.com/jeroenkroesen/joppylib
6
+ Project-URL: Repository, https://github.com/jeroenkroesen/joppylib
7
+ Author-email: Jeroen Kroesen <jeroen@kroesen.nu>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: joplin,joplin-api,notes
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.12
17
+ Requires-Dist: pydantic-settings>=2.10.1
18
+ Requires-Dist: pydantic>=2.11.7
19
+ Requires-Dist: requests>=2.32.4
20
+ Description-Content-Type: text/markdown
21
+
22
+ # JopPyLib
23
+ Python abstraction over the Joplin data api
24
+
25
+ ==WARNING==
26
+ JopPyLib is under heavy development. I've only just started. I will update this readme when a first usable version is released.
27
+
28
+ JopPyLib is part of the [Artumis project](https://jeroenkroesen.github.io/artumis_site/). It is meant to be a thin layer to enable programmatic manipulation of Joplin notes via a higher level interface than the data API.
29
+
30
+ JopPyLib is highly opinionated because it is designed to enable a higher level library call [JopBrainLib](https://github.com/jeroenkroesen/jopbrainlib) that enables second brain functionality in Joplin.
31
+
32
+ An [example Jupyter notebook interface](https://github.com/jeroenkroesen/joppylib-notebook) is available for interacting with JopPyLib.
33
+ ***
34
+
35
+ ## Status
36
+ Current release: joppylib-0.1.0a11
37
+ Very alpha testing release.
38
+ ***
39
+
40
+
41
+ ## Roadmap
42
+ - [x] Clean up and document old personal codebase
43
+ - [ ] Complete api layer (`api_client.py`)
44
+ - [x] Generate packages on github to allow pip install when testing.
45
+ - [x] Implement PUT
46
+ - [x] Implement better tag functions
47
+ - [x] Delete tag from note
48
+ - [x] Get all tags attached to a note
49
+ - [ ] Test API functionality
50
+ - [x] Demonstrate low-level interface in [notebook](https://github.com/jeroenkroesen/joppylib-notebook)
51
+ - [x] Design higher level interface
52
+ - [x] Implement higher level interface
53
+
54
+ ***
55
+
56
+
57
+ ## Reference
58
+ [Joplin Data API documentation](https://joplinapp.org/help/api/references/rest_api/)
@@ -0,0 +1,37 @@
1
+ # JopPyLib
2
+ Python abstraction over the Joplin data api
3
+
4
+ ==WARNING==
5
+ JopPyLib is under heavy development. I've only just started. I will update this readme when a first usable version is released.
6
+
7
+ JopPyLib is part of the [Artumis project](https://jeroenkroesen.github.io/artumis_site/). It is meant to be a thin layer to enable programmatic manipulation of Joplin notes via a higher level interface than the data API.
8
+
9
+ JopPyLib is highly opinionated because it is designed to enable a higher level library call [JopBrainLib](https://github.com/jeroenkroesen/jopbrainlib) that enables second brain functionality in Joplin.
10
+
11
+ An [example Jupyter notebook interface](https://github.com/jeroenkroesen/joppylib-notebook) is available for interacting with JopPyLib.
12
+ ***
13
+
14
+ ## Status
15
+ Current release: joppylib-0.1.0a11
16
+ Very alpha testing release.
17
+ ***
18
+
19
+
20
+ ## Roadmap
21
+ - [x] Clean up and document old personal codebase
22
+ - [ ] Complete api layer (`api_client.py`)
23
+ - [x] Generate packages on github to allow pip install when testing.
24
+ - [x] Implement PUT
25
+ - [x] Implement better tag functions
26
+ - [x] Delete tag from note
27
+ - [x] Get all tags attached to a note
28
+ - [ ] Test API functionality
29
+ - [x] Demonstrate low-level interface in [notebook](https://github.com/jeroenkroesen/joppylib-notebook)
30
+ - [x] Design higher level interface
31
+ - [x] Implement higher level interface
32
+
33
+ ***
34
+
35
+
36
+ ## Reference
37
+ [Joplin Data API documentation](https://joplinapp.org/help/api/references/rest_api/)
@@ -0,0 +1,149 @@
1
+ # JopPyLib — Critical Examination Report
2
+ **Date:** 2026-03-24
3
+ **Scope:** All source code in `src/joppylib/`
4
+ **Context:** All API traffic targets the Joplin Data API running on the user's localhost. Joplin Server is out-of-scope.
5
+
6
+ ---
7
+
8
+ ## 1. Input Sanitization
9
+
10
+ ### ~~No input sanitization on `query` in `search()` — Correctness bug~~ RESOLVED
11
+ ~~`api_client.py:107` interpolates the query directly into the URL string:~~
12
+ ```python
13
+ params = f'?query={query}'
14
+ ```
15
+ ~~A query containing `&`, `=`, `#`, `+`, or spaces will break the URL or inject additional parameters. For example, searching for `C++` results in Joplin receiving `C ` (plus decoded as space), and searching for `foo&limit=1` would override the pagination limit.~~
16
+
17
+ **Fixed:** `search()` now uses a `params` dict passed to `requests.get()`, which handles URL encoding automatically. Verified end-to-end against a live Joplin instance with `C++`, `C#`, `foo&bar`, and `key=value` queries.
18
+
19
+ ---
20
+
21
+ ## 2. Bugs & Correctness Issues
22
+
23
+ ### ~~Copy-paste docstring errors~~ RESOLVED
24
+ ~~- `delete()` docstring says "Get an entity instance by ID" (`api_client.py:385`, `joplin_client.py:246`).~~
25
+ ~~- `update()` docstring says "data for the object to create" (`api_client.py:359`, `joplin_client.py:221`).~~
26
+
27
+ **Fixed:** Corrected `delete()` docstrings to say "Delete an entity instance by ID" and `update()` parameter descriptions to say "data for the object to update" in both `api_client.py` and `joplin_client.py`.
28
+
29
+ ### ~~`check_connection` ignores non-200 responses (`connection.py:30-33`)~~ RESOLVED
30
+ ```python
31
+ try:
32
+ requests.get(url_ping)
33
+ return True
34
+ except requests.exceptions.ConnectionError:
35
+ return False
36
+ ```
37
+ ~~Any HTTP response (including 500) returns `True`. Only a connection failure returns `False`. A Joplin instance that is up but malfunctioning would be reported as connected.~~
38
+
39
+ **Fixed:** `check_connection` now uses `resp.ok` (True only for 2xx) and catches the broad `RequestException` base class.
40
+
41
+ ### ~~Inconsistent return types across methods~~ ACCEPTED (by design)
42
+ - `get()`, `create()`, `update()`, `delete()` return raw `requests.Response`.
43
+ - `search()`, `get_multi()`, `get_all_tags()` return `Dict[str, Any]` with a `success`/`data`/`error` wrapper.
44
+
45
+ ~~Consumers must handle two completely different response patterns depending on which method they call.~~
46
+
47
+ **Accepted:** The split maps to a real architectural difference — single-request methods return the raw Response (full access to status, headers, body), while paginated methods aggregate multiple responses into a Dict wrapper. Unifying would either lose information (Response direction) or add unnecessary wrapping (Dict direction). Downstream consumer JopBrainLib already handles both patterns. Type hints document the contract per method.
48
+
49
+ ### ~~`name` class attribute collision in `joplin_client`~~ RESOLVED
50
+ ~~`joplin_client.Note` has class attribute `name: str = 'note'`, but its inherited `__init__` takes `name` as a parameter and overwrites it via `self.name = name`. The class attribute is dead code. Same for `Tag`.~~
51
+
52
+ **Fixed:** Removed dead `name` class attributes from `Note` and `Tag` in `joplin_client.py`. Also corrected `Tag` docstring from "Interact with items" to "Interact with tags".
53
+
54
+ ### ~~Unused `ITEM_TYPE` constant~~ RESOLVED
55
+ ~~`defaults.py` defines `ITEM_TYPE = 'note'` but it is never referenced anywhere in the codebase.~~
56
+
57
+ **Fixed:** Removed `ITEM_TYPE = 'note'` from `defaults.py`.
58
+
59
+ ### Mutable default class attributes (`api_client.py:28-29`)
60
+ ```python
61
+ fields: List[str] = []
62
+ fields_create_required: List[str] = []
63
+ ```
64
+ Class-level mutable defaults. Not currently mutated, but a latent footgun if any code ever appends to them on an instance.
65
+
66
+ ---
67
+
68
+ ## 3. Robustness Issues
69
+
70
+ ### ~~Auth polling loop has no timeout and no delay (`connection.py:77-81`)~~ RESOLVED
71
+ ~~Two problems:~~
72
+ 1. ~~If the user never responds to the Joplin auth dialog, this loops forever.~~
73
+ 2. ~~There is no `time.sleep()` between polls — it hammers the local Joplin API as fast as Python can loop, which can make the Joplin UI sluggish.~~
74
+
75
+ **Fixed:** Auth polling now has a configurable timeout and a delay between polls.
76
+
77
+ ### `get_auth_token` doesn't handle HTTP errors (`connection.py:73-74`)
78
+ ```python
79
+ resp_init = requests.post(url_init)
80
+ init_token = resp_init.json()['auth_token']
81
+ ```
82
+ If the POST returns a non-200 or non-JSON response, this crashes with an unhelpful `KeyError` or `JSONDecodeError`.
83
+
84
+ ### ~~No timeout on any HTTP request~~ RESOLVED
85
+ ~~Every `requests.get/post/put/delete` call has no `timeout` parameter. If Joplin freezes (not uncommon for an Electron app under load), the calling Python code hangs indefinitely.~~
86
+
87
+ **Fixed:** All HTTP requests now have a configurable timeout.
88
+
89
+ ### No retry on paginated operations
90
+ If page 3 of 5 fails, the entire operation fails and all data (including successfully fetched pages 1-2) is discarded.
91
+
92
+ ---
93
+
94
+ ## 4. Design Issues
95
+
96
+ ### ~~Pagination loop duplicated 3 times~~ RESOLVED
97
+ ~~The same ~20-line pagination pattern is copy-pasted in `search()` (lines 131-157), `get_multi()` (lines 230-256), and `Note.get_all_tags()` (lines 513-539). They differ only in the initial URL. This has already caused bugs historically (per commits "Fixed: reponse indexes" and "Fixed: added req_index to fix indexing").~~
98
+
99
+ **Fixed:** Extracted a shared `_paginate()` method on `Item`. All three callers now delegate to it. Also migrated `get_multi()` and `get_all_tags()` from string interpolation to `params=` dict.
100
+
101
+ ### The high-level layer is mostly boilerplate
102
+ Every method in `joplin_client.Item` is a pass-through that prepends `self._api_key` and `self.settings`. ~270 lines exist to hide two parameters. A `requests.Session`-style approach, `functools.partial`, or `__getattr__` delegation could achieve the same in a fraction of the code.
103
+
104
+ ### No connection reuse or shared client instance
105
+ Every API call opens a new TCP connection. There is no shared client instance for connection reuse, central timeout config, or auth handling.
106
+
107
+ **Recommendation:** Migrate from `requests` to `httpx`. The `httpx.Client` (sync) provides connection pooling, base URL support, and shared config out of the box. Additionally, `httpx.AsyncClient` offers an almost identical async API, enabling a future async interface without a library swap. This is low priority — the library targets localhost so the performance impact is minimal — but it removes a design issue and opens the door for async support.
108
+
109
+ ### `create()` accepts `Dict | str` but `str` bypasses validation (`api_client.py:331-338`)
110
+ When `data` is a string, required field validation is skipped entirely. The user gets a raw HTTP error with no library-level context if it fails.
111
+
112
+ ### `JoplinClient` hard-codes entity specialization (`joplin_client.py:405-410`)
113
+ `folder`, `event`, `resource`, and `revision` use the base `Item` class. This means `resource` can't handle file uploads (per the TODO at `api_client.py:729`), and there's no way for a consumer to know this without reading the source.
114
+
115
+ ### `id` shadows the Python builtin
116
+ Used as a parameter name throughout both layers. Convention is `item_id` or `id_`.
117
+
118
+ ---
119
+
120
+ ## 5. Testing & Quality
121
+
122
+ ### ~~Zero tests~~ RESOLVED
123
+ ~~This is the single biggest risk. The git history shows multiple pagination and indexing bugs that were caught manually. The triplicated pagination loop is especially prone to regressions.~~
124
+
125
+ **Fixed:** 52 tests added across all modules (`test_config.py`, `test_connection.py`, `test_api_client.py`, `test_joplin_client.py`) using `pytest` and `responses` for HTTP mocking. Covers CRUD operations, pagination, auth flows, validation, and delegation.
126
+
127
+ ### No CI/CD pipeline
128
+ No GitHub Actions, no linting, no type checking configured. The `py.typed` marker exists but `mypy`/`pyright` is not configured to run.
129
+
130
+ ### `dist/` directory is checked into git
131
+ 11 versions of wheels and tarballs bloat the repository. These should be built and published via CI or GitHub Releases.
132
+
133
+ ---
134
+
135
+ ## Summary
136
+
137
+ | Priority | Issue | Why it matters |
138
+ |----------|-------|----------------|
139
+ | ~~**High**~~ | ~~No request timeouts~~ | ~~Hangs if Joplin freezes~~ **RESOLVED** |
140
+ | ~~**High**~~ | ~~Auth polling: no timeout, no delay~~ | ~~Infinite loop, pegs CPU, makes Joplin sluggish~~ **RESOLVED** |
141
+ | ~~**High**~~ | ~~URL query not encoded~~ | ~~Searches with special characters break silently~~ **RESOLVED** |
142
+ | ~~**High**~~ | ~~No tests~~ | ~~Every change risks regressions (proven by history)~~ **RESOLVED** |
143
+ | ~~**Medium**~~ | ~~Pagination loop duplicated 3x~~ | ~~Already caused bugs; will again~~ **RESOLVED** |
144
+ | ~~**Medium**~~ | ~~Inconsistent return types (Response vs Dict)~~ | ~~Confusing API for consumers~~ **ACCEPTED (by design)** |
145
+ | ~~**Low**~~ | ~~No shared client / consider httpx migration~~ | ~~No connection reuse; httpx would also enable future async support~~ **ACCEPTED (deferred)** |
146
+ | ~~**Medium**~~ | ~~`check_connection` ignores status codes~~ | ~~False positives~~ **RESOLVED** |
147
+ | ~~**Low**~~ | ~~Copy-paste docstring errors~~ | ~~Misleading docs~~ **RESOLVED** |
148
+ | ~~**Low**~~ | ~~`dist/` in git~~ | ~~Repository bloat~~ **ACCEPTED (deferred)** |
149
+ | ~~**Low**~~ | ~~Unused `ITEM_TYPE`, dead `name` class attrs~~ | ~~Dead code~~ **RESOLVED** |