Where code belongs
Put code in the most shared layer that can hold it. Logic that could run on another platform goes in crosspad-core, UI goes in crosspad-gui, and a platform repo keeps only what cannot be shared: drivers, HAL bindings and build glue. An app keeps its own screens and pad logic in its own repo. The Architecture page shows the layers, Platform parity shows where board and simulator differ, and the Manifesto says why.
The decision table
Section titled “The decision table”Go down the table and stop at the first row that matches your code. The rows run from the most specific owner to the most shared.
| If your code… | It belongs in… |
|---|---|
| Runs on the STM32 (pad scan, charger, power) | CrossPad/CrossPad_STM32_r2 |
| Calls ESP-IDF APIs (GPIO, I2S, NVS, RMT, TinyUSB) | CrossPad/platform-idf (an Esp* adapter or the BSP) |
| Calls SDL2, RtMidi, RtAudio or other desktop APIs | CrossPad/crosspad-pc (a Pc* adapter) |
| Is a feature with its own screen and pad behaviour | an app repo, listed in the app registry |
Uses LVGL widgets or shared UI patterns (launcher, settings, AppBase) | CrossPad/crosspad-gui |
| Defines something every platform must provide (a clock, a LED strip, a MIDI port) | an I* interface in CrossPad/crosspad-core |
| Uses only the C++ standard library, no chip, OS or UI | CrossPad/crosspad-core |
The table follows the dependency direction. Apps depend on gui and core, gui depends on core, and platforms implement what core and gui ask for. Code never depends on a layer above it.
The litmus test
Section titled “The litmus test”Ask one question: could this code work on another platform? If the answer is yes, it does not belong in a platform repo. Move it into core or gui, even if only one platform uses it today. A platform repo that grows logic is a platform repo that the next platform has to copy.
Ask a second question: would the same code exist in two repos? If yes, put one copy in core or gui and call it from both. Two copies drift apart, and a fix in one does not reach the other. The Manifesto puts it this way: code that exists in two places will eventually exist in one.
Adding a hardware capability
Section titled “Adding a hardware capability”Say you add a new sensor, or a new way to talk to the outside world. Start in crosspad-core: add an interface that describes what the rest of the code needs, not how the chip does it. Then write one adapter per platform: an Esp* class in platform-idf and a Pc* class in crosspad-pc.
The simulator adapter can be a stand-in. It can read the keyboard, write to a file or do nothing. What matters is that the interface exists on both platforms, so code above it builds everywhere. IImu and IVibration have a firmware adapter only, so code that uses them checks isAvailable() first.
Apps call the interface and never include ESP-IDF, SDL2 or other platform headers. That keeps the app portable, and it lets you test it in the simulator before you touch the board.
An app should depend on crosspad-core interfaces and crosspad-gui widgets only. Do not include FreeRTOS or ESP-IDF headers in an app, and do not wrap app code in #ifdef ESP_PLATFORM.
You never add your app to a platform’s list: you add it as a submodule, and the build finds it (how registration works).
If your app needs something platform-specific, the platform gets a new interface implementation and your app stays portable. Do not reach past the interface into the platform. Adding a hardware capability shows the pattern.
Start one: copy a working example from Build an app, or Start from the template.
Common mistakes
Section titled “Common mistakes”An #ifdef per platform inside an app. It works on the day you write it, then breaks when a third platform arrives. Replace it with an interface call, and put the platform-specific half in each platform’s adapter.
A widget copied into an app because only this app uses it. Keeping it in the app is fine while only one app needs it. When a second app needs it, move it to crosspad-gui instead of copying it again. Do not move it earlier: a widget with one user does not yet know what its shared shape is.
Logic in a platform repo because that is where you were working. If you add a feature while you debug the firmware, the code tends to land in platform-idf. Check it against the litmus test before you commit. If it would work on the simulator, it belongs in core, gui or an app.