Skip to content

Cold-eyes review 2026Q3: nine findings (S1 pass, S4 pass except the Quick Start) #16

Description

@bdbarnett

Quarterly cold-eyes review, 2026-09-03 (PyDevices/.github#25, standards S1/S4/S6). Read-only inspection by a repo-inspector agent from a fresh clone in a scratch directory; every claim below carries the command or URL that produced it. Filed by Arthur. Nothing here was changed by the review itself.

S1 (clean clone builds per its README): PASS — pip and mip paths both work from a fresh clone; the repo builds, tests (4) and docs (strict) pass. The only S1-lens gap is documentary: the README never tells a stranger how to install or test from the clone.

S4 (README to working example, first try): VERDICT: PASS for "README to one working example, first try" — FAIL for the section the README itself titles "Quick Start".

Findings (9)

[HIGH] The section titled "Quick Start" is the one thing in the README that cannot run — import board_config is undefined everywhere in the repo

README.md:70 ## Quick Start: Painting on a Display; README.md:73 import board_config; README.md:76 display_drv = board_config.display_drv.

$ ./venv1/bin/python quickstart.py # README.md:72-84, copy-pasted verbatim
ModuleNotFoundError: No module named 'board_config'

$ grep -rn "board_config" --include=".md" --include=".py" --include=".toml" --include=".yml" .
README.md:73 / README.md:76 / docs/integrations.md:58 / docs/integrations.md:61

Four uses, zero definitions and zero links. board_config is not a pip- or mip-installable module: it lives as per-board files under PyDevices/pydevices board_configs/*/board_config.py, which a user copies onto the device. Nothing in palettes says so.

This also collides with README.md:5's "zero dependencies on any other PyDevices library" — the README's only end-to-end example silently requires another PyDevices repo. docs/integrations.md repeats it and adds a second inconsistency: the heading at docs/integrations.md:53 is "Using with Display Drivers (displaydev)" but the snippet at :58 imports board_config and never mentions displaydev.

Suggested fix: Either make the Quick Start self-contained (print RGB565 values, or drive a pygraphics.FrameBuffer the way docs/integrations.md:11-28 already does successfully), or keep the display example and add one sentence naming where board_config comes from with a link to the pydevices board_configs directory — and reword README.md:5 so "zero dependencies" is not contradicted three sections later.

[MEDIUM] docs advertise four color depths that raise ValueError, and mislabel two that work

docs/color-math.md:76-86, "## 4. Depth Constants", lists 1, 2, 4, 8, 16, 24, 32. docs/palette-gallery.md:71 repeats it: "color_depth (int): Bit depth (16 for RGB565, 24 for RGB888, 32 for ARGB8888)."

$ ./venv1/bin/python -c "from palettes import get_palette; [print(d, get_palette(name='default',color_depth=d)[1]) for d in (1,2,4,8,16,24,32)]"
color_depth= 1 -> ValueError: Invalid color depth
color_depth= 2 -> ValueError: Invalid color depth
color_depth= 4 -> 128
color_depth= 8 -> 2
color_depth=16 -> 16
color_depth=24 -> 128
color_depth=32 -> ValueError: Invalid color depth

lib/palettes/init.py:169-175 only handles 24, 4, 16, 8 and raises otherwise. So 1, 2 and 32 are documented capabilities that do not exist. On top of that the table mislabels the two that do: it calls 4 "4-bit grayscale" and 8 "8-bit grayscale", where the code's own docstring (lib/palettes/init.py:97-98) says 4 (24-bit index) and 8 (RGB332), and color332() at :229 is plainly RGB332, not grayscale. The 32-bit ARGB8888 claim in particular is the one a stranger writing a desktop tool would reach for first.

Suggested fix: Cut rows 1, 2 and 32 from docs/color-math.md:80-86 or mark them explicitly unsupported, and relabel 4 and 8 to match lib/palettes/init.py:97-98. Drop 32 from docs/palette-gallery.md:71. A one-line parametrized test over the supported depth set would keep the table honest.

[MEDIUM] completeness: color_depth=4 is a documented half-surface — the named-attribute path implements indexed mode, __getitem__ does not

Published API reference (live at https://palettes.readthedocs.io/en/latest/reference/palettes/, sourced from lib/palettes/init.py:97-98 and :108-111): "color_depth (int): Output format for __getitem__: 4 (24-bit index), 8 (RGB332), 16 (RGB565), or 24 (0xRRGGBB)."

But the two halves of the surface disagree at depth 4:
lib/palettes/init.py:132 color = list(self._names.keys()).index(color) # named attrs -> a table INDEX
lib/palettes/init.py:169 if self._color_depth == 24 or self._color_depth == 4: return r << 16 | g << 8 | b # getitem -> packed RGB

$ ./venv1/bin/python -c "..."
p4.RED = 12 (index into name table)
p4[12] = 0xff0000 (packed 0xRRGGBB)
p24[12] = 0xff0000 -> depth 4 and depth 24 return identical values: True

So at depth 4 the indexed path is a duplicate of depth 24 and never returns an index, while the named path returns an index that no other API accepts. Judged along the axes this repo declares (family x color_depth x swapped x access-path): family x depth is complete and self-consistent for 8/16/24 across all four families (verified 4x4 matrix, all cells OK); swapped correctly no-ops outside depth 16, which the docstring at :99 records. Depth 4 is the one cell where a documented half of the surface has nothing implementing the other half, and no exclusion is recorded anywhere.

Suggested fix: Decide what depth 4 means and make both paths agree: either __getitem__ returns the palette index at depth 4 (matching "24-bit index" and the named attributes), or depth 4 is dropped and the docstring at :97-98 and :108-111 stops advertising it. Whichever way, record it in the class docstring so the published reference is the truth.

[MEDIUM] Published API reference renders raw Sphinx roles as literal text

$ grep -rno ":(meth|class|attr|func|mod):`[^\`]*`" lib/ | wc -l
29
lib/palettes/init.py:8 :meth:get_palette to create a palette...
lib/palettes/wheel.py:30 name (str): Prefix for :attr:~palettes.Palette.name...
lib/palettes/wheel.py:31 color_depth (int): Output format; see :class:~palettes.Palette.

mkdocs.yml sets docstring_style: google; Griffe does not resolve Sphinx roles, so they survive to the page. Scraping the live page:
$ curl -sSL https://palettes.readthedocs.io/en/latest/reference/palettes/ | (strip tags)
literal ':meth:' occurrences on the page: 6
literal ':class:' occurrences on the page: 5
literal ':attr:' occurrences on the page: 5
rendered text: "color_depth int Output format for :meth: getitem : 4 (24-bit index)..."

Org convention: /home/brad/gh/pydevices/dotgithub/docs/docstrings.md:10-11 — "Google style only" / "Do not mix Sphinx directives", written specifically so "the generated reference pages stay accurate". This is the first page a stranger lands on from README.md:104.

Suggested fix: Replace the 29 roles with mkdocstrings cross-reference syntax ([Palette][palettes.Palette]) or plain backticked names. Add .venv-docs/bin/mkdocs build 2>&1 | grep -i griffe — already the documented gate at dotgithub/docs/docstrings.md:60-68 — plus a grep for :meth:|:class:|:attr: to the tests workflow so it cannot regress.

[MEDIUM] Platform claims carry no proof tier, and two of them have no proof at all

README.md:5 claims MicroPython, CircuitPython, CPython (desktop/server) and PyScript (Web). docs/index.md:10 adds "Direct WebAssembly · Pyodide". Repo description on GitHub: "Cross-platform color palette library for MicroPython, CircuitPython, and CPython."

$ grep -rin "bench-proven|ci-proven|community-verified|proof tier|support tier" --include=".md" --include=".yml" .
(no matches, rc=1)

dotgithub/docs/platform-support-tiers.md:8-9: "Every platform claim in a PyDevices README, vision, or roadmap carries one of three labels." None here does.

What is actually proven, from my own runs: CPython is CI-proven (.github/workflows/tests.yml, ubuntu-latest, python 3.13, 4 tests) and I bench-proved it; PyScript/WASM is bench-proven (the readthedocs live demo executed and printed "Drawn 32 color wheel bands and 10 Material Design amber shades!"). MicroPython and CircuitPython have zero automated proof anywhere in the repo — and the ad-hoc run I did (see finding 1) shows they do not in fact behave identically, so the strongest claim in the README sits on the least-evidenced platforms.

Suggested fix: Label each platform in README.md:5 and docs/index.md:10 per platform-support-tiers.md. Given the unix MicroPython and CircuitPython binaries already on the bench, the cheap and honest move is to add both to the tests workflow (or a scheduled job) and claim CI-proven for all four — which also gates finding 1.

[MEDIUM] Production PyPI ships pydevices-palettes 0.0.12 and nothing in the repo tells a stranger it exists or that it is a park

README.md:95-98 gives only the TestPyPI form. AGENTS.md:20-22 says "the TestPyPI distribution is pydevices-palettes". But:

$ curl -sS https://pypi.org/pypi/pydevices-palettes/json -> 200, version 0.0.12, releases ['0.0.12']
$ ./venv3/bin/pip install pydevices-palettes # the obvious command
Successfully installed pydevices-palettes-0.0.12
installed version: 0.0.12 ; wheel[180] = 2047

So the command every Python user types first succeeds, silently, one release behind the TestPyPI 0.0.13 the README points to, with no note anywhere that the production name is a deliberate parked mirror. Reporting this under §4.2 rather than as a broken release: the mirror-park is a recorded decision, and TestPyPI-only publication is explicitly not a defect. What fails is the ledger's own visibility clause — "each is recorded and visible where a stranger would otherwise misread it" (§4.2 preamble, modernization doc lines 230-234). A stranger reading README.md:88-98 cannot tell that PyPI resolves at all, let alone that it resolves stale.

Suggested fix: One line under Installation naming the production package and its status ("pydevices-palettes is also parked on PyPI at 0.0.12; releases currently ship to TestPyPI — use the command above"), and fix AGENTS.md:21 which now understates where the distribution lives. Same edit is presumably owed by the other five parked names.

[LOW] docs/palette-gallery.md's cube table invites size= values the parameter does not accept, and nothing validates them

docs/palette-gallery.md:104-114 heads its first column "Cube Size" with values 8 / 27 / 64 / 125, then immediately below at :111-113:

size is the steps per channel: size=4 -> 4x4x4 = 64 colors

cube = get_palette("cube", color_depth=16, size=4)

The size kwarg is the edge length (2-5, per lib/palettes/cube.py:36 and :48), so the table's own column values are not valid arguments for the parameter it sits next to. There is no validation:

$ ./venv1/bin/python -c "from palettes import get_palette; [print('size',s,'-> len',len(get_palette('cube',size=s,color_depth=16))) for s in (2,3,4,5,8,27,64,125)]"
size=2 -> len 8 ; size=3 -> 27 ; size=4 -> 64 ; size=5 -> 125
size=8 -> len 512 ; size=27 -> len 19683 ; size=64 -> len 262144 ; size=125 -> len 1953125

At size>5 lib/palettes/cube.py:60-61 silently falls through to the CUBE125 name table, so color_name() is wrong too. A reader copying "27" out of the table onto an ESP32 gets a 19,683-entry palette instead of a useful error.

Suggested fix: Rename the column to "Total Colors" and add a "size= argument" column with 2/3/4/5, and raise ValueError in CubePalette.init for size outside 2-5 — the docstring at lib/palettes/cube.py:36 already states the constraint, the code just doesn't enforce it.

[LOW] AGENTS.md gives a test command that AGENTS.md itself documents as failing

AGENTS.md:11-16 ("## Tests and lint"):
python3 -m unittest discover -s tests
AGENTS.md:26-33 ("## Cursor Cloud specific instructions") then says the same command "fails with ModuleNotFoundError: No module named 'palettes'" and gives the working form.

$ cd palettes && python3 -m unittest discover -s tests
ModuleNotFoundError: No module named 'palettes'
Ran 4 tests in 0.001s
FAILED (errors=4)
$ PYTHONPATH=lib python3 -m unittest discover -s tests
Ran 4 tests in 0.012s
OK

The failing form is the one under the heading a contributor reads; the correction is 15 lines down under a vendor-specific heading, so the first command in the file is the broken one.

Suggested fix: Put PYTHONPATH=lib on the command at AGENTS.md:14 (matching .github/workflows/tests.yml, which sets env: PYTHONPATH: lib) and reduce the Cursor Cloud section to the venv note.

[LOW] README documents no way to install or test from a clone

README.md:88-98 offers only mip.install(...) and pip install -i https://test.pypi.org/simple/ ... pydevices-palettes. There is no pip install ., no pip install -e ., no test command, and no CONTRIBUTING.md in the repo (gh api repos/PyDevices/palettes/contents/CONTRIBUTING.md -> 404). docs/index.md:37-69 ("Quick Install") repeats the same four install routes and adds none from source.

All three from-source paths work — I ran them (python -m build ./palettes -> both dists; PYTHONPATH=lib python3 -m unittest discover -s tests -> OK; ruff check lib tests scripts -> All checks passed) — the README simply never says they exist. The only place they are written down is AGENTS.md:11-16, which is agent-facing and carries the broken form (previous finding). So S1's "clean clone builds per its README" passes on mechanics but has no README recipe to follow.

Suggested fix: Add a short "From source" block to README Installation: clone, pip install -e . (or PYTHONPATH=lib), python -m unittest discover -s tests, ruff check lib tests scripts — the four commands CI already runs.

Each finding is independent; tick them off here or split any that deserves its own thread.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions