Hardware documentation and board exports¶
just web builds the MkDocs Material site at build/web/index.html, using
KiBot for documentation exports from disposable source snapshots. The catalogue
in web/boards.json includes the canonical left ear,
joined WROOM headband, detachable microphone flex, AW93x05 breakout,
LSM6DSV16BX breakout and original MD-DRA371-H10 microphone breakout.
Each board page includes front/rear previews, a hierarchical schematic PDF, component CSV BOM, interactive HTML BOM with board/net inspection, and the exact saved PCB snapshot. These are development documentation exports. The CSV BOM is not a JLC upload file. Manufacturing outputs, frozen order inputs and physical qualification remain owned by their existing workflows.
The shared model pool supplies the referenced STEP bodies without network downloads or machine-specific library paths. Its migration manifest is hash-checked before generation.
The site stages an explicit Markdown allowlist from
web/docs.json. Maintained guides remain in their existing
repository locations; no second copy needs editing. Links between selected pages
stay local. Other evidence links point to the recorded Git revision on GitHub.
Links into local build/ output become labelled local-artifact references.
Archives, frozen records and artwork are not copied wholesale into the site.
Local generation¶
The easiest full build uses the same digest-pinned Linux image as Actions:
# Start Docker Desktop first. The wrapper discovers its macOS CLI if needed.
just web-container
# Prose/navigation preview without Docker or KiCad:
just web-docs
# Include the native raytraced galleries explicitly:
just web-container --raytrace
just test-web
python3 -m http.server 8000 --bind 127.0.0.1 --directory build/web
Open http://127.0.0.1:8000/. Container generation mounts the checkout read-only
and only build/ writable. The first run pulls the KiCad/KiBot image and installs
workflow tools. Linked Git worktrees are supported through separately mounted,
read-only Git metadata; optional index refreshes are disabled in the container.
Native just web requires KiCad 10, the pinned KiBot revision, KiAuto, iBOM,
PcbDraw dependencies and a usable display; Linux needs a virtual display. Actions and the container wrapper use
.github/with-display.sh, which waits for Xvfb without the SIGUSR1 handshake
that can hang under emulation.
The shared uv run --script scripts/hw.py driver pins MkDocs 1.6.1,
Material 9.7.0 and mike 2.2.0. just web-docs explicitly marks each board page as a preview
without CAD exports; CI always builds full exports.
web/toolchain.json and the workflow pin the same KiCad 10 base-image digest.
.github/requirements-kibot.txt additionally pins KiBot 1.9.2 development commit
bbe7ab715a71c2822c27f6c357ef5e1c9f427cee, including its archive SHA-256.
KiBot 1.9.1's positional wire parser rejected omitted default strokes in older
generated schematics; the upstream revision handles those fields. The microphone
projects have also been opened and resaved in native KiCad 10.
This is a deliberate source pin, not a floating development image.
On Apple Silicon, the local wrapper uses checksum-pinned ARM64 uv 0.13.1 inside
the Linux container; some QEMU versions segfault on the x86 uv binary. KiCad,
KiBot and Python still use the same x86 container environment as Actions.
KiBot owns schematic/BOM/iBOM/2D output through web/kibot.yaml. The renderer
wrapper preserves the exact cubic/quadratic outline adaptation for KiBot's
vendored PcbDraw. Styles are per-board, including the ear's opaque white-coverlay
palette, hidden covered copper and ENIG pads. Component artwork is illustrative.
Cmts.User annotations are never presented as V-cuts.
To add galleries locally without repeating KiBot exports, retain a complete
site outside build/web/ and build/work/web/, then reuse its checked exports:
cp -R build/web build/website-export-cache
just web-release --exports-from build/website-export-cache
This permits native macOS rendering after a Linux container export. Reuse requires
a completed full site, identical current input hashes and exact hashes for every
copied export. It fails on changed source/docs/tooling or modified cached files.
The current source snapshot is still copied and checked on success/failure.
Native renderer versions and model coverage are published separately in each
boards/renders/provenance.json; raw work manifests and absolute paths stay private.
Raytraced render sets¶
The standalone renderer saves the multi-angle presentation views without needing KiKit's website builder or PcbDraw. It uses KiCad 10's native raytracer and the same public hardware CLI:
# First generate a fresh AW build after any source design edits:
just aw-ear-fab /path/to/round-tracks-plugin build/ears/aw-fab-next
just ear-renders build/ears/aw-fab-next build/renders/ears-next
# Quicker, quarter-size previews (same high-quality raytracer):
just ear-renders build/ears/aw-fab-next build/renders/ears-preview 0.25 high
# One saved PCB, using its stackup colours by default:
just pcb-renders espurr/espurr-ears/espurr-ears.kicad_pcb build/renders/left-next
# Explicit appearance, selected views and up to two parallel render processes:
uv run --script scripts/hw.py render espurr/espurr-ears/espurr-ears.kicad_pcb \
--output build/renders/left-white-next --appearance white-flex --view front --view rear
uv run --script scripts/hw.py ear render build/ears/aw-fab-next \
--output build/renders/ears-web-next --scale 0.5 --jobs 2 --view pair-front --view pair-rear
just test-renders
Every output must be new/empty and under build/. A nonempty output is refused
without deleting its previous results. KICAD_CLI and KICAD_PYTHON overrides
work as usual; KiKit/PcbDraw are not prerequisites for the render command itself.
Linux still needs a usable display for the native wx/KiCad worker, e.g. Xvfb.
Ear rendering consumes an explicit completed aw-ear-fab directory with status
AW_RAIL_FREE_CAM_VERIFIED, verifies all declared artifact hashes, and uses the
hash-bound finished L/R boards and their placement plans. Historical absolute
paths inside those plans are not followed. It never derives a new right ear
from an old generated board: the AW build already owns generation from its
captured left master and connectivity checks. After geometry changes, generate
a new AW build and point the renderer at that directory. An explicitly selected
older build remains an illustration of that older snapshot; rendering does not
establish order submission, supplier acceptance or fabrication readiness.
The checked-in render settings own cameras,
lighting, resolution, palette and the band presentation poses: wearer's right
on the viewer's left, with both short edges aligned across the top. Use
--settings <file> for another configuration. The default eight ear views have
3840 × 2560 pair canvases and 3200 × 3200 individual canvases; --scale scales
those dimensions. Generic single-board mode has four 3200 × 3200 views and
retains its saved board orientation. --quality basic is available for faster
previews. On macOS/Linux, the KiCad raytracing child runs through nice -n 15
by default so interactive work takes priority; the render manifest records this
increment. This changes scheduling priority, not image quality or a CPU-usage cap. Each render gets an isolated KiCad configuration; the user's GUI
preferences are neither consulted nor changed.
The output contains:
images/*.png: neutral dark background and a soft presentation shadow.transparent/*.png: cropped alpha versions, useful on website backgrounds.contact-sheet.pngandrenders.zip: review sheet and an image-only sharing package, including captions/provenance notes and attribution.gallery.json: versioned relative image paths, dimensions, captions and attribution for the MkDocs website and other consumers.manifest.json: completion marker, exact input/settings/tooling hashes, KiCad version, model coverage and native scene verification evidence.work/andlogs/: private build copies and diagnostics; exclude these from website publication. Failed runs retainFAILED.jsonand diagnostics, with no complete manifest, gallery or sharing ZIP.
The input PCB bytes and model assets are copied before native operations. Numbered pads/nets, component poses and sides, copper primitives (including arc bulges), board outlines and filled-zone boundaries are checked through saved readback after the display transforms. Native quantisation is bounded to 5 nm; redundant fill vertices can disappear on save without changing their boundaries. Input hashes are checked after preparation, rendering and packaging, including failure paths. Concurrent changes are reported and never restored over.
The ear palette models opaque white coverlay, black silk and ENIG. Missing
AW96105 bodies receive an explicitly reported illustrative 1.8 × 2.1 × 0.55 mm
envelope; an existing authored model takes precedence. The known FH34SRJ model's
cyan/magenta placeholder colours are replaced on a copy, with all non-colour
STEP data preserved. Saved stack thickness and manual rear connectors remain as
modelled; adhesive is not modelled. Missing unreferenced bodies are listed in
the manifest. Unresolved referenced models and unsupported scene objects fail
instead of silently producing incomplete geometry. For generic boards,
--models <directory> selects the flat asset directory used to resolve model
basenames; the default is espurr/packages3D.
Tagged website builds reuse this renderer for front/rear, half-scale,
high-quality galleries on each source-board page. The website requires
RENDER_SET_COMPLETE, consumes the relative paths in gallery.json, and copies
only the listed images, image-only ZIP, contact sheet and attribution. Internal
work boards, models and absolute-path native manifests stay private under build/.
The standalone ear-renders command still consumes a validated AW pair when a
finished L/R presentation is needed; website generation never invents a right ear.
Actions and GitHub Pages¶
The Hardware docs workflow builds relevant
pull requests and pushes to main, including Markdown/configuration changes.
Tags matching v* also build; manual dispatch accepts a raytrace checkbox.
Normal pushes and PRs do not raytrace. Tagged builds and explicit manual requests include front/rear high-quality galleries at half the configured resolution. The 60-minute timeout allows headroom for hosted CPU rendering; it is not a measured GitHub-runner timing guarantee. A tag site includes its own revision's galleries. Later main updates preserve those tagged galleries at their versioned URLs; main keeps its current 2D previews. No stale release imagery is silently mixed with newer board geometry.
Build jobs have contents: read. A separate publication/deploy job has
contents: write, pages: write and id-token: write, and runs only after a
successful main/tag build. PRs and manual builds on other branches upload
previews without deployment. Preview and report
artifacts are named hardware-docs-preview and hardware-docs-reports.
Preview sites are retained for 14 days, or 90 days for v* tags; reports are
retained for 14 days. Failed builds upload diagnostics and cannot deploy.
In Settings → Pages → Build and deployment, select GitHub Actions.
Allow main and intended release tags in the github-pages environment's
protection rules. Repository settings are separate from the workflow file.
No deploy key or PAT is required. gh-pages is generated version storage;
Pages itself remains in GitHub Actions mode.
Main/release selector¶
The Material header selector lists main and future v* release tags:
/espurr-hw/main/follows current development./espurr-hw/v0.1.0/is an example release URL; each release keeps its own source snapshot and raytraced galleries./espurr-hw/opensmain. Historical ear checkpoint tags are not backfilled.- The Firmware tab and home-page link use the domain-relative
/espurr/, so they work from every nested version without hard-coding a hostname.
just web --version main and just web-release --version v0.1.0 select the
canonical URL and manifest version. just web-publish --version main assembles
the retained versions at build/pages/ without pushing. Add --push only when
the generated gh-pages history should be sent to origin; CI does this after
downloading the successful build's exact site and hash report.
Publication uses mike's pinned prebuilt-site API. It does not rerun KiBot,
raytracing or MkDocs. It requires a complete full site with matching revision,
version, source hashes and output hashes; prose-only previews cannot publish.
Every stored version has a publication.json with its complete site inventory.
The combined artifact is rechecked against all retained versions, including
links and hashes, before Pages deployment. Other versions' Git trees must stay
identical; an existing release cannot be replaced with different source inputs.
Only the assembled build/pages/ is packaged, excluding Git metadata and work
files. The official Pages v5 uploader explicitly includes hidden files, keeping
empty .nojekyll markers in the delivered inventory. Its matching v5 deployer
handles the Pages artifact contract.
Publication and deployment share a queued concurrency group, so main/tag updates cannot overwrite each other's version catalogue. The queue retains pending releases; source builds still cancel superseded builds of the same ref. Builds on other branches remain downloadable previews rather than additional site versions.
The build explicitly trusts only its mounted GITHUB_WORKSPACE for Git commands.
This fixes the observed container detected dubious ownership failure; it does
not disable Git's ownership checking globally. The shared bootstrap installs
missing pip/Xauth support in the upstream image before installing pinned just/uv.
KiBot is installed with --no-compile so its macro importer sees source modules.
Source and publication guards¶
- CAD, shared libraries, worksheets and model inputs are hashed, together with
selected docs, styles, configs and the explicit website/render-worker dependencies.
Unrelated generator scripts do not invalidate the website snapshot. Inputs are copied under
build/work/web/inputs/and checked during the snapshot. - KiBot runs only on those copies, with no source-updating preflights and an isolated configuration home. The custom right-ear, pair, panel, CAM and manufacturing recipes remain unchanged.
- Source hashes are checked again on success and failure. Concurrent edits fail generation; they are never restored over. Saved input PCB bytes are compared with the published download independently.
- The KiBot output inventory is exact: two PNGs, one schematic PDF, one CSV BOM, one iBOM HTML and the selected PCB. Extra files, including Gerber archives, fail publication. Gallery publication has its own explicit inventory.
- MkDocs receives only selected Markdown, generated pages and approved outputs. Its file inventory is derived before generation from those inputs and the installed theme. Unexpected/missing output, empty files, symlinks and broken local links fail the build. The previous public output is invalidated first; completion manifests are written only after checks succeed.
- MiniStudioo attribution and both shop/pattern links remain on the home and ear pages. Commercial originals, private correspondence, render work files and model sources are excluded.
build/web/manifest.json records the revision, dirty-checkout flag, selected
boards, tool versions, exact source hashes and publication input hashes.
build/reports/web/build.json records completion/source preservation, with
per-command logs and site-hashes.json alongside it. The manifest describes
saved files, including local uncommitted work; unsaved editor state is not included.
A successful website build does not run or replace manufacturing ERC/DRC/CAM
qualification and is not a fabrication release.
KiBot PDFs and interactive HTML can contain tool-generated metadata; the new
pipeline validates exact inputs, publication inventories and downloadable PCB
bytes rather than promising byte-identical exports across runs. The retained
web compare command supports inspection of comparable site snapshots; native
raytracer PNGs still require bounded sampling-noise comparisons.
Adding a board or guide¶
Add an explicit PCB/schematic pair, unique slug, description and palette to
web/boards.json; add the description under web/. Add maintained Markdown
paths to web/docs.json and navigation in the website module when appropriate.
Never select an old generated build/ board as a canonical source. Generated
right ears and panels must continue to come from their verified generators.
References: KiBot outputs, MkDocs configuration, Material versioning, mike, GitHub Pages workflows.