This guide covers the day-to-day contributor workflow and the release checklists for maintainers.
This project is mostly vibecoded, but still requires human actions for tagging
new releases. Agents should take a look at AGENTS.md for complete JPPDOS
source code knowledge base.
All apps, except for some first-party ones, should be hosted in https://github.com/jppteam/jppdos-apps. Please refer to the Making new apps section of the repo.
master— stable. Tagged releases are cut from here.develop— integration branch. Pre-releases are cut from here.feature/<name>/fix/<name>— your work. Branch fromdevelop, open a PR back intodevelop. Mergedfeature/*/fix/*branches are swept automatically after a full release (cleanup-branches.yml).
GitHub (github.com/jppteam/jppdos) is the primary host; git.nova.tokyo
(GitLab) is a read-only mirror — never push there.
To build the firmware, you don't need a local ESP-IDF install. Use Docker instead:
docker compose run --rm build idf.py set-target esp32c6
docker compose run --rm build idf.py buildscripts/flash.sh wraps idf.py flash monitor; scripts/jpp_deploy.sh <port>
flashes via esptool and can upload built apps over JPPD-SMP in one step.
Run the host-side suite before every push — CI runs the same thing:
python3 -m pytest testsIt covers docs/contract structure, manifest validation, the keypad and HTTP
state machines (compiled from the real jpp_core sources), and the SDK ABI
pins. If you touch a pure jpp_core state machine, add a host harness for it
following the tests/keypad_harness.py / tests/httpd_harness/ pattern.
Every AGENTS.md, README.md, and everything under docs/ must be updated
in the same commit as any code or behaviour it describes — SDK surface,
broker policy, capability tiers, manifest schema, settings schema, boot
behaviour, hardware mapping, wire protocol, release milestone markers. Stale
docs are treated as a defect, not a follow-up.
Important
If you don't vibecode your change, you don't need to update the whole agent-facing documentation. Just state in your PR that those need updating.
docs/pages are MkDocs-first (!!! noteadmonitions,/// tab | Cblocks). Do not revert those to plain Markdown. Validate withmkdocs build --strict(via thejppd-docsimage) after touching docs; a new page needs anav:entry inmkdocs.yml.- A new page or a
JPP_SDK_VERSIONbump also requires updates todocs/sdk-changelog.mdand thesdk_mintable indocs/manifest.md.
- Append only. New
jpp_sdk_context_tfields go at the struct tail; new enumerators at the enum tail.jpp_sdk_native_services_tis frozen — new service callbacks go injpp_sdk_services_v2_t. Deployed app binaries read these offsets directly. - Three registrations for a new
jpp_sdk_*function, not one:s_symtabinjpp_native_symtab.c, the binding + globals-table entry injpp_mp_sdk_module.c, and aQ(name)line incomponents/micropython/qstrdefsport.h. Miss one and it fails differently (launchUNRESOLVED_SYM, PythonAttributeError, or a build error). - Keep native and MicroPython parity —
jpp_mp_sdk_module.cmirrors everyjpp_sdk_*call except the two documented structural exceptions. - Bump
JPP_SDK_VERSIONto (last released level) + 1 — not master + 1. Level 2 shipped in v1.1 and is closed; level 3 is currently open, so further backward-compatible additions land in 3. Keep changelog entries list-shaped so parallel branches merge by keeping both. Mirror the constant andALLOWED_CAPABILITIESintotests/validate_manifests.py(tests/test_sdk_abi.pyenforces this). - Update
apps/testapp_native/andapps/testapp_mp/in the same commit — they are the reference exercisers and live in-tree so a surface change breaks their build.
- Never bypass the service broker for file/network/keypad/RTC/storage access.
- Do not put firmware-layer code (SSD1306 calls, settings screen) in
jpp_core/— it belongs inmain/. - Do not auto-grant capabilities in
apply_consent(); theprompt_permission()dialog is intentional security UX. - Use the shared helpers (
jpp_string_util,jpp_file_util,jpp_nvs_util,jpp_draw_util) rather than duplicating them. jpp_corepublic APIs returnjpp_<module>_result_t/_status_t, neveresp_err_t. Headers use#pragma once.- The 5th keypad button is OK, never CENTER/CTR — fix stale terminology on
sight (except the deprecated SDK aliases and the generic
center_uvfield).
-
python3 -m pytest testspasses. -
docker compose run --rm build idf.py buildsucceeds. - Docs updated in the same commits as the behaviour.
- A
CHANGELOG.mdentry added under## Unreleased(product-facing wording — what shipped, not an API diff). - No attribution/trailer lines in commit messages.
- PR targets
develop.
Pre-releases are normally cut from develop. A tag containing rc/alpha/
beta, or any tag whose commit is reachable from develop (but not yet from
master), publishes with --prerelease and a branch/SHA banner.
- Target commit is on
developand CI is green on it. -
python3 -m pytest testspasses (the release workflow gates on it and a tagged build must not ship broken). -
## UnreleasedinCHANGELOG.mdis current and readable — adeveloptag usually predates its own## <tag>section, so release notes fall back to## Unreleased. -
JPP_SDK_VERSIONand its Python mirror agree; the open level has a section indocs/sdk-changelog.mdand a row indocs/manifest.md. -
mkdocs build --strictpasses if docs changed. -
JPPDOS_VERSIONinmain/jpp_settings_screen.h— mismatch with the tag is only a warning for a pre-release (it is routinely tagged before the header bump), so this is optional but preferred. - The IDF image cache is warm —
cache-idf-image.ymlruns on pushes tomaster; a tag cut fromdevelopstill falls back to themaster-scoped cache, so no action unlessIDF_IMAGEinrelease.ymlchanged. - Push the tag (
git push origin <tag>). After publish, verify the GitHub Release is marked Pre-release and the assets (jppdos-<tag>-esp32c6-merged.bin, the split-image zip, the apps zip,SHA256SUMS.txt) attached. -
workflow_dispatchwith the tag input re-runs a failed publish.
Full releases are cut from master (the workflow checks master reachability
first, so once develop is merged the tag is a real release).
-
developis merged intomasterand CI is green on the merge commit. -
python3 -m pytest testspasses. -
JPPDOS_VERSIONinmain/jpp_settings_screen.hexactly equals the tag — this is fatal for a stable release; Settings > About must not self-report a different version. -
CHANGELOG.mdhas a## <tag>section (e.g.## v1.1 — 2026-07-29); its body is lifted verbatim into the release notes. Move the## Unreleasedcontent into it. - SDK level: if this release closes the currently-open level, that is now
frozen — confirm
docs/sdk-changelog.mdand thesdk_mintable indocs/manifest.mddescribe it completely, and that any in-flight branch re-targets (this level) + 1 by hand. - All docs in sync with shipped behaviour;
mkdocs build --strictpasses. Thedocs.ymlworkflow publishesdocs/tohttps://jppdevice.by.m4l3vi.ch/sdk-docs/on push tomaster;site_urlinmkdocs.ymlmust match that location. - Update the snapshot/milestone markers at the top of
AGENTS.mdand any release-milestone references indocs/if this changes RTM/feature-release state. - Hardware-touching changes: verified on real hardware, not just Wokwi
(ADC, RTC, power-loss, Wi-Fi, SD). Re-check the
heap_monboot line against theESP_IDF_CONTRACT.md64 KB free-heap floor if RAM usage moved. - Manufacturing unaffected, or
scripts/(build_images.sh,prepare_device.py,lrv_manufacturing.py, record layout injpp_lrv.c) updated together. - Push the tag. Confirm the Release is marked Latest (not pre-release),
all assets present, and the J++Device Manager
> [!TIP]banner is prepended to the notes. - After publish,
cleanup-branches.ymlruns off the release — check its dry run / results if you expect mergedfeature/*branches to be swept.