Contributing¶
How to work on Sextile. The authority is CLAUDE.md at the repository root; this
page gathers the parts a human contributor needs and points there for the rest.
The gate¶
Four commands must pass over the whole workspace, at every commit — the last builds the documentation with warnings treated as errors, so a docstring autodoc cannot render stays broken no longer than the commit that breaks it:
uv run pytest
uv run ruff check .
uv run mypy
uv run --group docs sphinx-build -n -W --keep-going -b html docs docs/_build/html
The documentation builds on Python 3.13 — the development default, set in
.python-version — because the show-a-photograph how-to converts an image
live with sextants, which needs 3.13. The package itself supports 3.12, and the
CI matrix tests both; only the docs build is pinned to 3.13.
The two invariants¶
Nothing in
packages/sextile/may know about any particular service — not a forum, a calendar or the weather.calendar-viewdataandstardot-viewdatakeep this honest in-tree; a deployed service such asweather-viewdatalives in its own repository against the published framework, exercising it as a consumer would.Nothing in an application may reach past the framework’s public surface. The surface is the set of modules and names in Public surface, and
test_public_surface.pyfails if an application imports past it.
The method¶
Test-first, in small increments: name the next behaviour, write the failing
test, make it pass, tidy, commit. Retrocomputing facts are settled by driving the
real emulator, not by reading the documentation. When code goes, its tests are
sorted — repointed or deleted — not reimplemented to keep the suite green. The
full method and the conventions are in CLAUDE.md.
Building the documentation¶
uv run --group docs sphinx-build -n -W --keep-going -b html docs docs/_build/html
The output is written to docs/_build/html, which is git-ignored.
Documentation conventions¶
MyST Markdown for every page.
Cross-reference the API with the
{py:class},{py:func}and{py:mod}roles; cross-reference a page with{doc}.Code fences carry their language.
A heading is a task or a noun, never a claim.
Line one of a page declares its genre.
API names are backticked and spelled exactly as the surface spells them.
No bold sentences. The full docstring and document rules are in
CLAUDE.md.
Showing a frame¶
A doc that shows a Viewdata frame draws it with the sextile-frame directive, so
the frame is the one the code produces at build time, not a screenshot that goes
stale. A frame that stops rendering stops the build. Two forms:
Fetch a page from a service by its module:name:
```{sextile-frame}
:app: calendar_viewdata:app
:page: "3"
```
Or run a snippet that leaves a frame (a Frame) or a page (a Page), with
fetch(app, number) in scope — the form for anything that needs a fixed clock or
a hand-built frame. :frame: picks a later frame, :keys: presses keys from the
page first, and :show-code: shows the snippet:
```{sextile-frame}
:show-code:
from datetime import UTC, datetime
from calendar_viewdata import build_application
app = build_application(now=lambda: datetime(2026, 8, 1, tzinfo=UTC))
frame = fetch(app, "3")
```
The directive is docs/_ext/sextile_frames.py; it draws the frame as
render --form html does, and registers the stylesheet and the Bedstead font
from the package.