|
| 1 | +# Config Schema V3 |
| 2 | + |
| 3 | +- Status: Current |
| 4 | +- Date: 2026-07-05 |
| 5 | +- Canonical file name: `config.json` |
| 6 | +- Canonical schema file: `schemas/config.v3.schema.json` |
| 7 | +- Base document: `docs/CONFIG-SCHEMA-V2.md` (v3 is v2 plus the delta below; all |
| 8 | + v2 rules not listed here still apply) |
| 9 | + |
| 10 | +## Why v3 |
| 11 | + |
| 12 | +Sidearm supports any programmable input device, not only the Razer Naga. The |
| 13 | +closed, Naga-specific `controlId` enum became an open token, and devices became |
| 14 | +first-class persisted entities. |
| 15 | + |
| 16 | +## Delta from v2 |
| 17 | + |
| 18 | +### `controlId` is open |
| 19 | + |
| 20 | +- v2: closed 27-value enum (`thumb_01` … `top_special_03`). |
| 21 | +- v3: any token matching `^[a-z0-9][a-z0-9_-]*$` (max 64 chars). |
| 22 | +- Referential integrity moved from the schema into application validation |
| 23 | + (`validate_config` in `src-tauri/src/config.rs`): |
| 24 | + - every `bindings[].controlId`, `bindings[].chordPartner`, |
| 25 | + `encoderMappings[].controlId`, and `devices[].hotspots[].controlId` must |
| 26 | + exist in `physicalControls` |
| 27 | + - every `physicalControls[].deviceId` must reference a declared device |
| 28 | + |
| 29 | +### New top-level entity: `devices` |
| 30 | + |
| 31 | +```json |
| 32 | +{ |
| 33 | + "id": "razer-naga", |
| 34 | + "name": "Razer Naga V2 Hyperspeed", |
| 35 | + "builtin": true, |
| 36 | + "image": "photo.png", |
| 37 | + "hotspots": [{ "controlId": "thumb_01", "x": 25.0, "y": 75.5 }] |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | +- `id`, `name` required; `builtin` defaults to false. |
| 42 | +- `image` is a **bare file name** resolved inside the app-data devices |
| 43 | + directory (path separators and `..` are rejected) — used by user devices. |
| 44 | + The built-in Naga device renders through frontend-bundled photos instead. |
| 45 | +- `hotspots` place controls on the image in percent (0..=100) of its size. |
| 46 | +- At least one device must be declared. |
| 47 | +- A config declaring the built-in Naga device (`razer-naga`) must contain all |
| 48 | + 27 Naga controls in `physicalControls` — the v2 completeness invariant, |
| 49 | + scoped to that device. |
| 50 | + |
| 51 | +### `physicalControls[].deviceId` |
| 52 | + |
| 53 | +Each control is tagged with the device it belongs to. Optional in the schema |
| 54 | +(defaults to `razer-naga` in serde) so v2 files stay valid. |
| 55 | + |
| 56 | +### Version and migration |
| 57 | + |
| 58 | +- `version` accepts `2` or `3` at the schema layer; `validate_config` requires |
| 59 | + the migrated in-memory config to be exactly `3`. |
| 60 | +- A v2 file loads unchanged: `migrate_devices` seeds the built-in Naga device, |
| 61 | + serde defaults tag legacy controls with `razer-naga`, and the version is |
| 62 | + bumped to 3 on the next save. Bindings, encoder mappings, and actions are |
| 63 | + untouched. |
| 64 | +- An app older than v3 refuses a v3 file (its v2 schema pins `version` to 2) |
| 65 | + and, per the newer-schema guard, never overwrites or "recovers" it. |
0 commit comments