Skip to content

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.png and renders.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/ and logs/: private build copies and diagnostics; exclude these from website publication. Failed runs retain FAILED.json and 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/ opens main. 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.