Compare commits

1 Commits

Author SHA1 Message Date
2fa0447593 chore: ios dev files 2026-04-28 17:15:26 +02:00
343 changed files with 2168 additions and 6257 deletions

View File

@ -1,12 +1,12 @@
{ {
"last_dolt_commit": "bqh2h35ln2mtcthhm8l4918e231lh9ld", "last_dolt_commit": "mf3is3p9ve79on2q1achdjp8v50rq6s3",
"last_event_id": 0, "last_event_id": 0,
"timestamp": "2026-04-23T20:24:03.015942859Z", "timestamp": "2026-03-04T17:07:18.317614374Z",
"counts": { "counts": {
"issues": 16, "issues": 10,
"events": 41, "events": 24,
"comments": 0, "comments": 0,
"dependencies": 26, "dependencies": 21,
"labels": 0, "labels": 0,
"config": 11 "config": 11
} }

View File

@ -19,8 +19,3 @@
{"created_at":"2026-03-03T16:39:47Z","created_by":"Yandrik","depends_on_id":"abawo_bt_app-20q.1","issue_id":"abawo_bt_app-20q.8","type":"blocks"} {"created_at":"2026-03-03T16:39:47Z","created_by":"Yandrik","depends_on_id":"abawo_bt_app-20q.1","issue_id":"abawo_bt_app-20q.8","type":"blocks"}
{"created_at":"2026-03-03T16:39:28Z","created_by":"Yandrik","depends_on_id":"abawo_bt_app-20q","issue_id":"abawo_bt_app-20q.9","type":"parent-child"} {"created_at":"2026-03-03T16:39:28Z","created_by":"Yandrik","depends_on_id":"abawo_bt_app-20q","issue_id":"abawo_bt_app-20q.9","type":"parent-child"}
{"created_at":"2026-03-03T16:39:47Z","created_by":"Yandrik","depends_on_id":"abawo_bt_app-20q.7","issue_id":"abawo_bt_app-20q.9","type":"blocks"} {"created_at":"2026-03-03T16:39:47Z","created_by":"Yandrik","depends_on_id":"abawo_bt_app-20q.7","issue_id":"abawo_bt_app-20q.9","type":"blocks"}
{"created_at":"2026-04-23T21:42:57Z","created_by":"Yandrik","depends_on_id":"abawo_bt_app-8eb","issue_id":"abawo_bt_app-55i","type":"discovered-from"}
{"created_at":"2026-04-23T21:42:57Z","created_by":"Yandrik","depends_on_id":"abawo_bt_app-8eb","issue_id":"abawo_bt_app-6c0","type":"discovered-from"}
{"created_at":"2026-04-23T21:42:57Z","created_by":"Yandrik","depends_on_id":"abawo_bt_app-8eb","issue_id":"abawo_bt_app-8b8","type":"discovered-from"}
{"created_at":"2026-04-23T21:42:57Z","created_by":"Yandrik","depends_on_id":"abawo_bt_app-8eb","issue_id":"abawo_bt_app-bhs","type":"discovered-from"}
{"created_at":"2026-04-23T21:42:57Z","created_by":"Yandrik","depends_on_id":"abawo_bt_app-8eb","issue_id":"abawo_bt_app-cy2","type":"discovered-from"}

View File

@ -22,20 +22,3 @@
{"actor":"Yandrik","comment":null,"created_at":"2026-03-03T17:12:02Z","event_type":"closed","id":25,"issue_id":"abawo_bt_app-20q.9","new_value":"Added post-FINISH reset disconnect, reconnect, and reachability verification before marking update complete","old_value":""} {"actor":"Yandrik","comment":null,"created_at":"2026-03-03T17:12:02Z","event_type":"closed","id":25,"issue_id":"abawo_bt_app-20q.9","new_value":"Added post-FINISH reset disconnect, reconnect, and reachability verification before marking update complete","old_value":""}
{"actor":"Yandrik","comment":null,"created_at":"2026-03-03T17:12:17Z","event_type":"claimed","id":26,"issue_id":"abawo_bt_app-20q.3","new_value":"{\"assignee\":\"Yandrik\",\"status\":\"in_progress\"}","old_value":"{\"id\":\"abawo_bt_app-20q.3\",\"title\":\"Integrate firmware update UI into device details page\",\"description\":\"Add user-facing update controls and status presentation.\\n\\nWork:\\n- Add update card with Select Firmware, Start Update, Cancel\\n- Show phase text, progress %, bytes sent/acked, and retry status\\n- Disable conflicting actions (gear writes / connect button-to-bike) during DFU\\n- Show explicit reboot expectation after FINISH\\n- Persist/clear transient state correctly on page lifecycle changes\",\"acceptance_criteria\":\"- UI can run full update flow start-to-finish\\n- Progress/state transitions are visible and consistent\\n- Conflicting controls are disabled during active transfer\\n- Failures and cancellations are clearly shown\",\"status\":\"open\",\"priority\":1,\"issue_type\":\"feature\",\"owner\":\"me@yandrik.dev\",\"created_at\":\"2026-03-03T15:39:10Z\",\"created_by\":\"Yandrik\",\"updated_at\":\"2026-03-03T15:39:10Z\"}"} {"actor":"Yandrik","comment":null,"created_at":"2026-03-03T17:12:17Z","event_type":"claimed","id":26,"issue_id":"abawo_bt_app-20q.3","new_value":"{\"assignee\":\"Yandrik\",\"status\":\"in_progress\"}","old_value":"{\"id\":\"abawo_bt_app-20q.3\",\"title\":\"Integrate firmware update UI into device details page\",\"description\":\"Add user-facing update controls and status presentation.\\n\\nWork:\\n- Add update card with Select Firmware, Start Update, Cancel\\n- Show phase text, progress %, bytes sent/acked, and retry status\\n- Disable conflicting actions (gear writes / connect button-to-bike) during DFU\\n- Show explicit reboot expectation after FINISH\\n- Persist/clear transient state correctly on page lifecycle changes\",\"acceptance_criteria\":\"- UI can run full update flow start-to-finish\\n- Progress/state transitions are visible and consistent\\n- Conflicting controls are disabled during active transfer\\n- Failures and cancellations are clearly shown\",\"status\":\"open\",\"priority\":1,\"issue_type\":\"feature\",\"owner\":\"me@yandrik.dev\",\"created_at\":\"2026-03-03T15:39:10Z\",\"created_by\":\"Yandrik\",\"updated_at\":\"2026-03-03T15:39:10Z\"}"}
{"actor":"Yandrik","comment":null,"created_at":"2026-03-04T18:07:17Z","event_type":"closed","id":27,"issue_id":"abawo_bt_app-20q.3","new_value":"Integrated firmware file selection, update controls, progress display, and DFU state handling into device details page","old_value":""} {"actor":"Yandrik","comment":null,"created_at":"2026-03-04T18:07:17Z","event_type":"closed","id":27,"issue_id":"abawo_bt_app-20q.3","new_value":"Integrated firmware file selection, update controls, progress display, and DFU state handling into device details page","old_value":""}
{"actor":"Yandrik","comment":null,"created_at":"2026-03-04T18:07:26Z","event_type":"claimed","id":28,"issue_id":"abawo_bt_app-20q.5","new_value":"{\"assignee\":\"Yandrik\",\"status\":\"in_progress\"}","old_value":"{\"id\":\"abawo_bt_app-20q.5\",\"title\":\"Add DFU test suite for happy path, loss, stalls, and cancel\",\"description\":\"Add targeted tests for protocol and engine behavior.\\n\\nWork:\\n- Unit tests for codec + CRC + sequence helpers\\n- Engine tests with mocked BLE ack stream for:\\n - happy path\\n - dropped frame / stalled ACK and rewind\\n - timeout and bounded retry fail\\n - cancel/abort cleanup\\n- Ensure deterministic tests for wrap-around sequence scenarios\",\"acceptance_criteria\":\"- Tests cover success and critical failure/retry paths\\n- Wrap-around and ack rewind behavior is validated\\n- Regressions in sequencing/CRC are caught automatically\",\"status\":\"open\",\"priority\":1,\"issue_type\":\"task\",\"owner\":\"me@yandrik.dev\",\"created_at\":\"2026-03-03T15:39:10Z\",\"created_by\":\"Yandrik\",\"updated_at\":\"2026-03-03T15:39:10Z\"}"}
{"actor":"Yandrik","comment":null,"created_at":"2026-03-04T18:09:54Z","event_type":"closed","id":29,"issue_id":"abawo_bt_app-20q.5","new_value":"Expanded DFU tests to cover bounded retry timeout failure and deterministic sequence wrap-around behavior","old_value":""}
{"actor":"Yandrik","comment":null,"created_at":"2026-03-04T18:10:13Z","event_type":"claimed","id":30,"issue_id":"abawo_bt_app-20q.6","new_value":"{\"assignee\":\"Yandrik\",\"status\":\"in_progress\"}","old_value":"{\"id\":\"abawo_bt_app-20q.6\",\"title\":\"Document DFU v1 operator flow, troubleshooting, and constraints\",\"description\":\"Document how to use and support the new updater.\\n\\nWork:\\n- Add app-side DFU flow docs (select/start/progress/reboot/reconnect)\\n- Add troubleshooting matrix for common failures (MTU, stalled ACK, reconnect timeout, CRC mismatch)\\n- Record explicit v1 limitations and future security/version-verification roadmap\\n- Add manual QA checklist for release validation\",\"acceptance_criteria\":\"- Team can execute update and triage failures from docs\\n- v1 limitations are explicit and not ambiguous\\n- QA checklist is actionable and complete\",\"status\":\"open\",\"priority\":2,\"issue_type\":\"chore\",\"owner\":\"me@yandrik.dev\",\"created_at\":\"2026-03-03T15:39:10Z\",\"created_by\":\"Yandrik\",\"updated_at\":\"2026-03-03T15:39:10Z\"}"}
{"actor":"Yandrik","comment":null,"created_at":"2026-03-04T18:12:10Z","event_type":"closed","id":31,"issue_id":"abawo_bt_app-20q.6","new_value":"Added DFU v1 operator guide with troubleshooting matrix, limitations, and QA checklist; linked from README","old_value":""}
{"actor":"Yandrik","comment":null,"created_at":"2026-03-04T18:12:11Z","event_type":"closed","id":32,"issue_id":"abawo_bt_app-20q","new_value":"all steps complete","old_value":""}
{"actor":"Yandrik","comment":null,"created_at":"2026-04-23T21:42:23Z","event_type":"created","id":33,"issue_id":"abawo_bt_app-8eb","new_value":"","old_value":""}
{"actor":"Yandrik","comment":null,"created_at":"2026-04-23T21:42:57Z","event_type":"claimed","id":34,"issue_id":"abawo_bt_app-8eb","new_value":"{\"assignee\":\"Yandrik\",\"status\":\"in_progress\"}","old_value":"{\"id\":\"abawo_bt_app-8eb\",\"title\":\"UI overhaul from mockups\",\"description\":\"App-wide UI reskin based on the provided mockups. Scope: shared theme tokens, shell navigation with Devices and Settings, Devices tab redesign, Settings redesign with theme selection, Connect Device page restyle, Device Details reskin, gear ratio preset additions and restyle, trainer popup restyle, and per-phase verification/commits.\",\"status\":\"open\",\"priority\":1,\"issue_type\":\"epic\",\"owner\":\"me@yandrik.dev\",\"created_at\":\"2026-04-23T19:42:23Z\",\"created_by\":\"Yandrik\",\"updated_at\":\"2026-04-23T19:42:23Z\"}"}
{"actor":"Yandrik","comment":null,"created_at":"2026-04-23T21:42:57Z","event_type":"created","id":35,"issue_id":"abawo_bt_app-cy2","new_value":"","old_value":""}
{"actor":"Yandrik","comment":null,"created_at":"2026-04-23T21:42:57Z","event_type":"created","id":36,"issue_id":"abawo_bt_app-8b8","new_value":"","old_value":""}
{"actor":"Yandrik","comment":null,"created_at":"2026-04-23T21:42:57Z","event_type":"created","id":37,"issue_id":"abawo_bt_app-55i","new_value":"","old_value":""}
{"actor":"Yandrik","comment":null,"created_at":"2026-04-23T21:42:57Z","event_type":"created","id":38,"issue_id":"abawo_bt_app-bhs","new_value":"","old_value":""}
{"actor":"Yandrik","comment":null,"created_at":"2026-04-23T21:42:57Z","event_type":"created","id":39,"issue_id":"abawo_bt_app-6c0","new_value":"","old_value":""}
{"actor":"Yandrik","comment":null,"created_at":"2026-04-23T21:57:23Z","event_type":"claimed","id":40,"issue_id":"abawo_bt_app-cy2","new_value":"{\"assignee\":\"Yandrik\",\"status\":\"in_progress\"}","old_value":"{\"id\":\"abawo_bt_app-cy2\",\"title\":\"Phase 1: theme and shell navigation\",\"description\":\"Implement shared app theme tokens, support system/light/dark theme selection, replace top-level navigation with Devices and Settings shell, retire Home as a destination, and keep Connect Device / Device Details as pushed routes.\",\"status\":\"open\",\"priority\":1,\"issue_type\":\"task\",\"owner\":\"me@yandrik.dev\",\"created_at\":\"2026-04-23T19:42:57Z\",\"created_by\":\"Yandrik\",\"updated_at\":\"2026-04-23T19:42:57Z\"}"}
{"actor":"Yandrik","comment":null,"created_at":"2026-04-23T21:57:37Z","event_type":"closed","id":41,"issue_id":"abawo_bt_app-cy2","new_value":"Completed","old_value":""}
{"actor":"Yandrik","comment":null,"created_at":"2026-04-23T22:06:20Z","event_type":"claimed","id":42,"issue_id":"abawo_bt_app-8b8","new_value":"{\"assignee\":\"Yandrik\",\"status\":\"in_progress\"}","old_value":"{\"id\":\"abawo_bt_app-8b8\",\"title\":\"Phase 2: devices and settings reskin\",\"description\":\"Redesign Devices tab and Settings tab using the new design system. Devices becomes the default tab and shows saved devices and add-device entry; Settings includes theme mode controls.\",\"status\":\"open\",\"priority\":1,\"issue_type\":\"task\",\"owner\":\"me@yandrik.dev\",\"created_at\":\"2026-04-23T19:42:57Z\",\"created_by\":\"Yandrik\",\"updated_at\":\"2026-04-23T19:42:57Z\"}"}
{"actor":"Yandrik","comment":null,"created_at":"2026-04-23T22:06:32Z","event_type":"closed","id":43,"issue_id":"abawo_bt_app-8b8","new_value":"Completed","old_value":""}
{"actor":"Yandrik","comment":null,"created_at":"2026-04-23T22:24:02Z","event_type":"claimed","id":44,"issue_id":"abawo_bt_app-55i","new_value":"{\"assignee\":\"Yandrik\",\"status\":\"in_progress\"}","old_value":"{\"id\":\"abawo_bt_app-55i\",\"title\":\"Phase 3: connect flow and popup restyle\",\"description\":\"Restyle the Connect Device page and trainer assignment popup to match the new visual language while preserving the existing BLE behavior.\",\"status\":\"open\",\"priority\":1,\"issue_type\":\"task\",\"owner\":\"me@yandrik.dev\",\"created_at\":\"2026-04-23T19:42:57Z\",\"created_by\":\"Yandrik\",\"updated_at\":\"2026-04-23T19:42:57Z\"}"}

View File

@ -1,16 +1,10 @@
{"acceptance_criteria":"- User can select a firmware .bin and complete upload end-to-end\n- Upload follows protocol and handles packet loss via retransmit\n- App shows clear progress and failure states\n- Device reconnects after reboot and is reachable","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"all steps complete","closed_at":"2026-03-04T17:12:11Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c3bd8fa9b5b9d51b04cde5e11c2d8cdedaca29e049a64e8521172a69093b4ba3","created_at":"2026-03-03T15:37:50Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Implement the firmware update flow defined in universal-shifters/update-process.md for the Flutter app.\n\nScope:\n- Manual local .bin selection and upload over BLE GATT\n- START/DATA/FINISH/ABORT protocol support\n- Cumulative ACK handling with retransmit\n- Expected reboot/disconnect handling and reconnect check\n\nOut of scope for v1:\n- Hosted firmware distribution/backend\n- Cryptographic signature verification in app\n- Encrypted payload transport mode","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q","is_template":0,"issue_type":"epic","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement Universal Shifters BLE DFU v1 in app (manual .bin upload)","updated_at":"2026-03-04T17:12:11Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- User can select a firmware .bin and complete upload end-to-end\n- Upload follows protocol and handles packet loss via retransmit\n- App shows clear progress and failure states\n- Device reconnects after reboot and is reachable","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c3bd8fa9b5b9d51b04cde5e11c2d8cdedaca29e049a64e8521172a69093b4ba3","created_at":"2026-03-03T15:37:50Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Implement the firmware update flow defined in universal-shifters/update-process.md for the Flutter app.\n\nScope:\n- Manual local .bin selection and upload over BLE GATT\n- START/DATA/FINISH/ABORT protocol support\n- Cumulative ACK handling with retransmit\n- Expected reboot/disconnect handling and reconnect check\n\nOut of scope for v1:\n- Hosted firmware distribution/backend\n- Cryptographic signature verification in app\n- Encrypted payload transport mode","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q","is_template":0,"issue_type":"epic","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Implement Universal Shifters BLE DFU v1 in app (manual .bin upload)","updated_at":"2026-03-03T15:37:50Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"- All protocol constants match spec exactly\n- No duplicated literal DFU UUID/opcode values across services/UI\n- Domain models compile and are ready for transfer engine integration","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Implemented DFU constants, flags, opcodes, and typed progress models in shifter_types.dart","closed_at":"2026-03-03T15:45:20Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"d471157a1af6199f0ac0a1565b05b28d1a477e66d9166a3865edb42ea1e8c2ae","created_at":"2026-03-03T15:38:34Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Add protocol surface in app code for DFU support.\n\nWork:\n- Add UUID constants for dfu_control (...40008), dfu_data (...40009), dfu_ack (...4000a)\n- Add opcode/frame constants (START=0x01, FINISH=0x02, ABORT=0x03, frame size 64, payload size 63)\n- Add flags constants and typed update-state/progress models used by service/UI\n- Remove future magic numbers by centralizing constants","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.1","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add DFU protocol constants and domain models","updated_at":"2026-03-03T15:45:20Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- All protocol constants match spec exactly\n- No duplicated literal DFU UUID/opcode values across services/UI\n- Domain models compile and are ready for transfer engine integration","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Implemented DFU constants, flags, opcodes, and typed progress models in shifter_types.dart","closed_at":"2026-03-03T15:45:20Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"d471157a1af6199f0ac0a1565b05b28d1a477e66d9166a3865edb42ea1e8c2ae","created_at":"2026-03-03T15:38:34Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Add protocol surface in app code for DFU support.\n\nWork:\n- Add UUID constants for dfu_control (...40008), dfu_data (...40009), dfu_ack (...4000a)\n- Add opcode/frame constants (START=0x01, FINISH=0x02, ABORT=0x03, frame size 64, payload size 63)\n- Add flags constants and typed update-state/progress models used by service/UI\n- Remove future magic numbers by centralizing constants","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.1","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add DFU protocol constants and domain models","updated_at":"2026-03-03T15:45:20Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"- CRC test vector passes: \"123456789\" =\u003e 0xCBF43926\n- START/FINISH/ABORT encoders produce exact lengths/byte layout\n- Frame segmentation handles final partial payload correctly\n- Seq wrap and ack+1 rewind helpers covered by tests","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Added DFU protocol codec/CRC utilities with unit tests for payloads, frames, and sequence helpers","closed_at":"2026-03-03T15:48:57Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"9ba609e63c9e2f8a4e95de6795dace11c610aa9c13908b5dab7aa6cb83ab58d4","created_at":"2026-03-03T15:39:10Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Implement pure protocol helpers.\n\nWork:\n- Build START payload (11 bytes, LE fields)\n- Build FINISH/ABORT payloads\n- Build/segment DATA frames (seq + 63-byte payload)\n- Implement ACK/sequence helpers including wrapping behavior\n- Implement CRC32 (ISO-HDLC reflected polynomial 0xEDB88320, init/final xor FFFFFFFF)","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.2","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement DFU packet codec and CRC32 utilities with tests","updated_at":"2026-03-03T15:48:57Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- CRC test vector passes: \"123456789\" =\u003e 0xCBF43926\n- START/FINISH/ABORT encoders produce exact lengths/byte layout\n- Frame segmentation handles final partial payload correctly\n- Seq wrap and ack+1 rewind helpers covered by tests","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Added DFU protocol codec/CRC utilities with unit tests for payloads, frames, and sequence helpers","closed_at":"2026-03-03T15:48:57Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"9ba609e63c9e2f8a4e95de6795dace11c610aa9c13908b5dab7aa6cb83ab58d4","created_at":"2026-03-03T15:39:10Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Implement pure protocol helpers.\n\nWork:\n- Build START payload (11 bytes, LE fields)\n- Build FINISH/ABORT payloads\n- Build/segment DATA frames (seq + 63-byte payload)\n- Implement ACK/sequence helpers including wrapping behavior\n- Implement CRC32 (ISO-HDLC reflected polynomial 0xEDB88320, init/final xor FFFFFFFF)","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.2","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement DFU packet codec and CRC32 utilities with tests","updated_at":"2026-03-03T15:48:57Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"- UI can run full update flow start-to-finish\n- Progress/state transitions are visible and consistent\n- Conflicting controls are disabled during active transfer\n- Failures and cancellations are clearly shown","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Integrated firmware file selection, update controls, progress display, and DFU state handling into device details page","closed_at":"2026-03-04T17:07:18Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"e8d2f905536263a05de5e7e86bf8f02fa0491d7129e30f36f2c4dd09acab1882","created_at":"2026-03-03T15:39:10Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Add user-facing update controls and status presentation.\n\nWork:\n- Add update card with Select Firmware, Start Update, Cancel\n- Show phase text, progress %, bytes sent/acked, and retry status\n- Disable conflicting actions (gear writes / connect button-to-bike) during DFU\n- Show explicit reboot expectation after FINISH\n- Persist/clear transient state correctly on page lifecycle changes","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.3","is_template":0,"issue_type":"feature","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Integrate firmware update UI into device details page","updated_at":"2026-03-04T17:07:18Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- UI can run full update flow start-to-finish\n- Progress/state transitions are visible and consistent\n- Conflicting controls are disabled during active transfer\n- Failures and cancellations are clearly shown","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Integrated firmware file selection, update controls, progress display, and DFU state handling into device details page","closed_at":"2026-03-04T17:07:18Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"e8d2f905536263a05de5e7e86bf8f02fa0491d7129e30f36f2c4dd09acab1882","created_at":"2026-03-03T15:39:10Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Add user-facing update controls and status presentation.\n\nWork:\n- Add update card with Select Firmware, Start Update, Cancel\n- Show phase text, progress %, bytes sent/acked, and retry status\n- Disable conflicting actions (gear writes / connect button-to-bike) during DFU\n- Show explicit reboot expectation after FINISH\n- Persist/clear transient state correctly on page lifecycle changes","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.3","is_template":0,"issue_type":"feature","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Integrate firmware update UI into device details page","updated_at":"2026-03-04T17:07:18Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"- User can select .bin and app obtains byte payload\n- Validation errors are explicit and user-facing\n- Metadata (size/crc/session) is available to transfer engine","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Completed","closed_at":"2026-03-03T16:04:51Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"be8d8646593e4fc6c269383efb41808c40ffdfb3103e58a90acc0142e32f4711","created_at":"2026-03-03T15:39:10Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Implement local firmware artifact input for v1.\n\nWork:\n- Integrate file picker for local firmware .bin\n- Read bytes safely and validate non-empty payload\n- Guard against malformed selections and unsupported files\n- Compute total_len and crc32 from selected bytes\n- Generate per-session session_id and set flags=0x00 for v1","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.4","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add firmware file selection and binary validation flow","updated_at":"2026-03-03T16:04:51Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- User can select .bin and app obtains byte payload\n- Validation errors are explicit and user-facing\n- Metadata (size/crc/session) is available to transfer engine","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Completed","closed_at":"2026-03-03T16:04:51Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"be8d8646593e4fc6c269383efb41808c40ffdfb3103e58a90acc0142e32f4711","created_at":"2026-03-03T15:39:10Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Implement local firmware artifact input for v1.\n\nWork:\n- Integrate file picker for local firmware .bin\n- Read bytes safely and validate non-empty payload\n- Guard against malformed selections and unsupported files\n- Compute total_len and crc32 from selected bytes\n- Generate per-session session_id and set flags=0x00 for v1","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.4","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add firmware file selection and binary validation flow","updated_at":"2026-03-03T16:04:51Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"- Tests cover success and critical failure/retry paths\n- Wrap-around and ack rewind behavior is validated\n- Regressions in sequencing/CRC are caught automatically","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Expanded DFU tests to cover bounded retry timeout failure and deterministic sequence wrap-around behavior","closed_at":"2026-03-04T17:09:54Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"f784095261bfc41eed5544a567f667bb2ba4816433f1d5d6ae21ea4616c7109a","created_at":"2026-03-03T15:39:10Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Add targeted tests for protocol and engine behavior.\n\nWork:\n- Unit tests for codec + CRC + sequence helpers\n- Engine tests with mocked BLE ack stream for:\n - happy path\n - dropped frame / stalled ACK and rewind\n - timeout and bounded retry fail\n - cancel/abort cleanup\n- Ensure deterministic tests for wrap-around sequence scenarios","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.5","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add DFU test suite for happy path, loss, stalls, and cancel","updated_at":"2026-03-04T17:09:54Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- Tests cover success and critical failure/retry paths\n- Wrap-around and ack rewind behavior is validated\n- Regressions in sequencing/CRC are caught automatically","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"f784095261bfc41eed5544a567f667bb2ba4816433f1d5d6ae21ea4616c7109a","created_at":"2026-03-03T15:39:10Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Add targeted tests for protocol and engine behavior.\n\nWork:\n- Unit tests for codec + CRC + sequence helpers\n- Engine tests with mocked BLE ack stream for:\n - happy path\n - dropped frame / stalled ACK and rewind\n - timeout and bounded retry fail\n - cancel/abort cleanup\n- Ensure deterministic tests for wrap-around sequence scenarios","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.5","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Add DFU test suite for happy path, loss, stalls, and cancel","updated_at":"2026-03-03T15:39:10Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"- Team can execute update and triage failures from docs\n- v1 limitations are explicit and not ambiguous\n- QA checklist is actionable and complete","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Added DFU v1 operator guide with troubleshooting matrix, limitations, and QA checklist; linked from README","closed_at":"2026-03-04T17:12:11Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"3a5dd3fd4d901ae53a80b3c7b488b41db5e09f7cc50da0e6c185c075b8ad7e51","created_at":"2026-03-03T15:39:10Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Document how to use and support the new updater.\n\nWork:\n- Add app-side DFU flow docs (select/start/progress/reboot/reconnect)\n- Add troubleshooting matrix for common failures (MTU, stalled ACK, reconnect timeout, CRC mismatch)\n- Record explicit v1 limitations and future security/version-verification roadmap\n- Add manual QA checklist for release validation","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.6","is_template":0,"issue_type":"chore","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Document DFU v1 operator flow, troubleshooting, and constraints","updated_at":"2026-03-04T17:12:11Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- Team can execute update and triage failures from docs\n- v1 limitations are explicit and not ambiguous\n- QA checklist is actionable and complete","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"3a5dd3fd4d901ae53a80b3c7b488b41db5e09f7cc50da0e6c185c075b8ad7e51","created_at":"2026-03-03T15:39:10Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Document how to use and support the new updater.\n\nWork:\n- Add app-side DFU flow docs (select/start/progress/reboot/reconnect)\n- Add troubleshooting matrix for common failures (MTU, stalled ACK, reconnect timeout, CRC mismatch)\n- Record explicit v1 limitations and future security/version-verification roadmap\n- Add manual QA checklist for release validation","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.6","is_template":0,"issue_type":"chore","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Document DFU v1 operator flow, troubleshooting, and constraints","updated_at":"2026-03-03T15:39:10Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"- Happy path reaches done with full ACKed transfer\n- Loss/stall path retransmits and recovers correctly\n- Cancel triggers ABORT and returns to idle cleanly\n- Engine surfaces explicit error reasons for UI","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Implemented firmware transfer engine with preflight, cumulative ACK handling, retries, cancel ABORT, and unit tests","closed_at":"2026-03-03T16:00:45Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"042d39f373b05c758d2d2724c757a7e790de522c2def68f8f12e9f2fbbb70dc6","created_at":"2026-03-03T15:39:18Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Build the runtime transfer engine used by UI.\n\nWork:\n- Subscribe to dfu_ack indications before START\n- Send START and require initial ACK 0xFF\n- Stream dfu_data using write without response in windows (configurable, default 8)\n- Track cumulative ACK; on stall/timeout rewind to ack+1 (wrapping aware)\n- Handle invalid/no-progress scenarios with bounded retries\n- Send FINISH after full acked upload\n- Support ABORT for cancellation and terminal error cleanup\n- Emit state/progress stream for UI","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.7","is_template":0,"issue_type":"feature","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement BLE DFU transfer engine with cumulative ACK retransmit","updated_at":"2026-03-03T16:00:45Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- Happy path reaches done with full ACKed transfer\n- Loss/stall path retransmits and recovers correctly\n- Cancel triggers ABORT and returns to idle cleanly\n- Engine surfaces explicit error reasons for UI","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Implemented firmware transfer engine with preflight, cumulative ACK handling, retries, cancel ABORT, and unit tests","closed_at":"2026-03-03T16:00:45Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"042d39f373b05c758d2d2724c757a7e790de522c2def68f8f12e9f2fbbb70dc6","created_at":"2026-03-03T15:39:18Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Build the runtime transfer engine used by UI.\n\nWork:\n- Subscribe to dfu_ack indications before START\n- Send START and require initial ACK 0xFF\n- Stream dfu_data using write without response in windows (configurable, default 8)\n- Track cumulative ACK; on stall/timeout rewind to ack+1 (wrapping aware)\n- Handle invalid/no-progress scenarios with bounded retries\n- Send FINISH after full acked upload\n- Support ABORT for cancellation and terminal error cleanup\n- Emit state/progress stream for UI","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.7","is_template":0,"issue_type":"feature","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement BLE DFU transfer engine with cumulative ACK retransmit","updated_at":"2026-03-03T16:00:45Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"- Upload start is blocked when MTU/connection preconditions fail\n- Error messages explain what failed and next step\n- Preflight result is exposed for transfer start path","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Implemented DFU preflight checks for connection state and negotiated MTU with typed results and tests","closed_at":"2026-03-03T15:55:11Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"558fa59b42c42b122eefc43d3483d1a61bdd995464dbc36d166f763f42366e87","created_at":"2026-03-03T15:39:24Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Add runtime guards required for protocol correctness.\n\nWork:\n- Ensure active connection to target button before DFU start\n- Request elevated MTU (e.g. 128/247) before upload\n- Validate negotiated MTU supports 64-byte data writes (ATT payload requirement)\n- Fail early with actionable message when transport preconditions are not met","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.8","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add BLE DFU preflight checks (MTU and connection readiness)","updated_at":"2026-03-03T15:55:11Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- Upload start is blocked when MTU/connection preconditions fail\n- Error messages explain what failed and next step\n- Preflight result is exposed for transfer start path","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Implemented DFU preflight checks for connection state and negotiated MTU with typed results and tests","closed_at":"2026-03-03T15:55:11Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"558fa59b42c42b122eefc43d3483d1a61bdd995464dbc36d166f763f42366e87","created_at":"2026-03-03T15:39:24Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Add runtime guards required for protocol correctness.\n\nWork:\n- Ensure active connection to target button before DFU start\n- Request elevated MTU (e.g. 128/247) before upload\n- Validate negotiated MTU supports 64-byte data writes (ATT payload requirement)\n- Fail early with actionable message when transport preconditions are not met","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.8","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add BLE DFU preflight checks (MTU and connection readiness)","updated_at":"2026-03-03T15:55:11Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"- Expected reset does not appear as generic failure\n- Reconnect path is attempted and result is surfaced\n- Completion criteria are consistent with v1 definition","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Added post-FINISH reset disconnect, reconnect, and reachability verification before marking update complete","closed_at":"2026-03-03T16:12:02Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"024e8aa245b93098013fbd66102a621c9ecd58a1dda9fc276c74cd278bdd3512","created_at":"2026-03-03T15:39:28Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Implement robust completion handling around expected device reset.\n\nWork:\n- Treat disconnect after successful FINISH as expected behavior\n- Reconnect with update-specific timeout strategy\n- Verify device is reachable/readable after reconnect\n- Surface success only after reconnect verification path\n- Document limitation: no strict firmware version compare until firmware exposes version characteristic","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.9","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Handle post-FINISH disconnect/reboot and reconnect verification","updated_at":"2026-03-03T16:12:02Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- Expected reset does not appear as generic failure\n- Reconnect path is attempted and result is surfaced\n- Completion criteria are consistent with v1 definition","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Added post-FINISH reset disconnect, reconnect, and reachability verification before marking update complete","closed_at":"2026-03-03T16:12:02Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"024e8aa245b93098013fbd66102a621c9ecd58a1dda9fc276c74cd278bdd3512","created_at":"2026-03-03T15:39:28Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Implement robust completion handling around expected device reset.\n\nWork:\n- Treat disconnect after successful FINISH as expected behavior\n- Reconnect with update-specific timeout strategy\n- Verify device is reachable/readable after reconnect\n- Surface success only after reconnect verification path\n- Document limitation: no strict firmware version compare until firmware exposes version characteristic","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-20q.9","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Handle post-FINISH disconnect/reboot and reconnect verification","updated_at":"2026-03-03T16:12:02Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"239a18c9fae52747eae5d68a88cce6c1df568115003ba8b79dfe72abdd73a00d","created_at":"2026-04-23T19:42:57Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Restyle the Connect Device page and trainer assignment popup to match the new visual language while preserving the existing BLE behavior.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-55i","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"in_progress","target":"","timeout_ns":0,"title":"Phase 3: connect flow and popup restyle","updated_at":"2026-04-23T20:24:03Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"6bd716ab35225df8c9aadf4fb110a580ea3d8ac9d4987f0d52b99f661914292b","created_at":"2026-04-23T19:42:57Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Add Road, Gravel, and MTB presets, keep the current load behavior for presets, and finish shared component polish plus final analyze pass.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-6c0","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Phase 5: gear presets and polish","updated_at":"2026-04-23T19:42:57Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Completed","closed_at":"2026-04-23T20:06:32Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"ebc1040b3fc86b322ce87b736e76fc90beb04aa4cc6d81bec54d195fc9d471ce","created_at":"2026-04-23T19:42:57Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Redesign Devices tab and Settings tab using the new design system. Devices becomes the default tab and shows saved devices and add-device entry; Settings includes theme mode controls.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-8b8","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Phase 2: devices and settings reskin","updated_at":"2026-04-23T20:06:32Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c636ac111ccf498970d1002590f65a5dd910551317916545dee637887fd7803b","created_at":"2026-04-23T19:42:23Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"App-wide UI reskin based on the provided mockups. Scope: shared theme tokens, shell navigation with Devices and Settings, Devices tab redesign, Settings redesign with theme selection, Connect Device page restyle, Device Details reskin, gear ratio preset additions and restyle, trainer popup restyle, and per-phase verification/commits.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-8eb","is_template":0,"issue_type":"epic","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"in_progress","target":"","timeout_ns":0,"title":"UI overhaul from mockups","updated_at":"2026-04-23T19:42:57Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7ab3e9926f20344dfe55e09ec252d5e1cfac023357b09abb096e84d524cce310","created_at":"2026-04-23T19:42:57Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Reskin Device Details, preserving existing behavior for status, reconnect, firmware, and gear ratio editing while adapting the layout to the new mockup direction.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-bhs","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Phase 4: device details overhaul","updated_at":"2026-04-23T19:42:57Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":"Yandrik","await_id":"","await_type":"","close_reason":"Completed","closed_at":"2026-04-23T19:57:37Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"014e2491f64f1273f482c9eb4259dad0eed5ea81030ebb21e91222dee3fd854a","created_at":"2026-04-23T19:42:57Z","created_by":"Yandrik","crystallizes":0,"defer_until":null,"description":"Implement shared app theme tokens, support system/light/dark theme selection, replace top-level navigation with Devices and Settings shell, retire Home as a destination, and keep Connect Device / Device Details as pushed routes.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"abawo_bt_app-cy2","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@yandrik.dev","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Phase 1: theme and shell navigation","updated_at":"2026-04-23T19:57:37Z","waiters":"","wisp_type":"","work_type":""}

View File

@ -1 +1 @@
1720108 48179

View File

@ -0,0 +1 @@
1772550918

1
.beads/dolt-server.port Normal file
View File

@ -0,0 +1 @@
13365

150
AGENTS.md
View File

@ -0,0 +1,150 @@
# Agent Instructions
This project uses **bd** (beads) for issue tracking. Run `bd onboard` to get started.
## Quick Reference
```bash
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --claim # Claim work atomically
bd close <id> # Complete work
bd sync # Sync with git
```
## Non-Interactive Shell Commands
**ALWAYS use non-interactive flags** with file operations to avoid hanging on confirmation prompts.
Shell commands like `cp`, `mv`, and `rm` may be aliased to include `-i` (interactive) mode on some systems, causing the agent to hang indefinitely waiting for y/n input.
**Use these forms instead:**
```bash
# Force overwrite without prompting
cp -f source dest # NOT: cp source dest
mv -f source dest # NOT: mv source dest
rm -f file # NOT: rm file
# For recursive operations
rm -rf directory # NOT: rm -r directory
cp -rf source dest # NOT: cp -r source dest
```
**Other commands that may prompt:**
- `scp` - use `-o BatchMode=yes` for non-interactive
- `ssh` - use `-o BatchMode=yes` to fail instead of prompting
- `apt-get` - use `-y` flag
- `brew` - use `HOMEBREW_NO_AUTO_UPDATE=1` env var
<!-- BEGIN BEADS INTEGRATION -->
## Issue Tracking with bd (beads)
**IMPORTANT**: This project uses **bd (beads)** for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods.
### Why bd?
- Dependency-aware: Track blockers and relationships between issues
- Git-friendly: Auto-syncs to JSONL for version control
- Agent-optimized: JSON output, ready work detection, discovered-from links
- Prevents duplicate tracking systems and confusion
### Quick Start
**Check for ready work:**
```bash
bd ready --json
```
**Create new issues:**
```bash
bd create "Issue title" --description="Detailed context" -t bug|feature|task -p 0-4 --json
bd create "Issue title" --description="What this issue is about" -p 1 --deps discovered-from:bd-123 --json
```
**Claim and update:**
```bash
bd update <id> --claim --json
bd update bd-42 --priority 1 --json
```
**Complete work:**
```bash
bd close bd-42 --reason "Completed" --json
```
### Issue Types
- `bug` - Something broken
- `feature` - New functionality
- `task` - Work item (tests, docs, refactoring)
- `epic` - Large feature with subtasks
- `chore` - Maintenance (dependencies, tooling)
### Priorities
- `0` - Critical (security, data loss, broken builds)
- `1` - High (major features, important bugs)
- `2` - Medium (default, nice-to-have)
- `3` - Low (polish, optimization)
- `4` - Backlog (future ideas)
### Workflow for AI Agents
1. **Check ready work**: `bd ready` shows unblocked issues
2. **Claim your task atomically**: `bd update <id> --claim`
3. **Work on it**: Implement, test, document
4. **Discover new work?** Create linked issue:
- `bd create "Found bug" --description="Details about what was found" -p 1 --deps discovered-from:<parent-id>`
5. **Complete**: `bd close <id> --reason "Done"`
### Auto-Sync
bd automatically syncs with git:
- Exports to `.beads/issues.jsonl` after changes (5s debounce)
- Imports from JSONL when newer (e.g., after `git pull`)
- No manual export/import needed!
### Important Rules
- ✅ Use bd for ALL task tracking
- ✅ Always use `--json` flag for programmatic use
- ✅ Link discovered work with `discovered-from` dependencies
- ✅ Check `bd ready` before asking "what should I work on?"
- ❌ Do NOT create markdown TODO lists
- ❌ Do NOT use external issue trackers
- ❌ Do NOT duplicate tracking systems
For more details, see README.md and docs/QUICKSTART.md.
<!-- END BEADS INTEGRATION -->
## Landing the Plane (Session Completion)
**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.
**MANDATORY WORKFLOW:**
1. **File issues for remaining work** - Create issues for anything that needs follow-up
2. **Run quality gates** (if code changed) - Tests, linters, builds
3. **Update issue status** - Close finished work, update in-progress items
4. **PUSH TO REMOTE** - This is MANDATORY:
```bash
git pull --rebase
bd sync
git push
git status # MUST show "up to date with origin"
```
5. **Clean up** - Clear stashes, prune remote branches
6. **Verify** - All changes committed AND pushed
7. **Hand off** - Provide context for next session
**CRITICAL RULES:**
- Work is NOT complete until `git push` succeeds
- NEVER stop before pushing - that leaves work stranded locally
- NEVER say "ready to push when you are" - YOU must push
- If push fails, resolve and retry until it succeeds

View File

@ -41,6 +41,3 @@ Still mostly material design.
### Company Color Theme ### Company Color Theme
todo todo
## Code
Always use `<color>.withValues(alpha: <alpha>)` instead of `<color>.withOpacity(<alpha>)` for colors.

View File

@ -9,12 +9,6 @@
# packages, and plugins designed to encourage good coding practices. # packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
analyzer:
errors:
invalid_annotation_target: ignore
plugins:
- custom_lint
linter: linter:
# The lint rules applied to this project can be customized in the # The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml` # section below to disable rules from the `package:flutter_lints/flutter.yaml`
@ -29,5 +23,12 @@ linter:
rules: rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule # avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
errors:
invalid_annotation_target: ignore
plugins:
- custom_lint
# Additional information about this file can be found at # Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options # https://dart.dev/guides/language/analysis-options

View File

@ -42,7 +42,3 @@ android {
flutter { flutter {
source = "../.." source = "../.."
} }
dependencies {
implementation "io.reactivex.rxjava2:rxjava:2.2.21"
}

View File

@ -1,55 +1,5 @@
package com.example.abawo_bt_app package com.example.abawo_bt_app
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.reactivex.exceptions.UndeliverableException
import io.reactivex.plugins.RxJavaPlugins
class MainActivity: FlutterActivity() { class MainActivity: FlutterActivity()
private val settingsChannel = "abawo/settings"
override fun onCreate(savedInstanceState: Bundle?) {
RxJavaPlugins.setErrorHandler { throwable ->
val error = if (throwable is UndeliverableException && throwable.cause != null) {
throwable.cause!!
} else {
throwable
}
val className = error.javaClass.name
val message = error.message.orEmpty()
if (className.contains("BleGatt") || message.contains("GATT exception")) {
return@setErrorHandler
}
Thread.currentThread().uncaughtExceptionHandler
?.uncaughtException(Thread.currentThread(), error)
}
super.onCreate(savedInstanceState)
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, settingsChannel)
.setMethodCallHandler { call, result ->
when (call.method) {
"openBluetoothSettings" -> {
try {
startActivity(Intent(Settings.ACTION_BLUETOOTH_SETTINGS))
result.success(true)
} catch (_: Exception) {
try {
startActivity(Intent(Settings.ACTION_SETTINGS))
result.success(true)
} catch (_: Exception) {
result.success(false)
}
}
}
else -> result.notImplemented()
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,492 +0,0 @@
from __future__ import annotations
import argparse
import csv
import math
import shutil
import subprocess
from dataclasses import dataclass
from pathlib import Path
from tempfile import TemporaryDirectory
import numpy as np
from PIL import Image, ImageOps
@dataclass(frozen=True)
class Canvas:
width: int = 588
height: int = 495
cx: float = 304.0
cy: float = 256.0
@dataclass(frozen=True)
class Style:
background: str = "#3d3d3d"
material: str = "#eeeeee"
outline: str = "#f4f4f4"
outline_width: float = 3.0
@dataclass(frozen=True)
class Geometry:
root_radius: float = 213.0
tooth_depth: float = 17.5
reference_teeth: int = 60
pitch_radius_offset: float = 8.0
tooth_sharpness: float = 1.32
low_tooth_threshold: int = 25
low_tooth_half_width: float = 0.30
low_tooth_cusp: float = 0.55
central_hole_radius: float = 46.0
bolt_hole_radius: float = 16.5
material_margin: float = 8.0
bolt_hole_min_teeth: int = 25
decorative_cutout_min_scale: float = 0.62
samples_per_tooth: int = 28
@dataclass(frozen=True)
class SprocketSizing:
pitch_radius: float
calculated_root_radius: float
root_radius: float
tooth_depth: float
outer_radius: float
decorative_scale: float
bolt_scale: float
include_bolt_holes: bool
include_decorative_cutouts: bool
@dataclass(frozen=True)
class Cutout:
kind: str
cx: float
cy: float
rx: float
ry: float | None = None
rotation: float = 0.0
CANVAS = Canvas()
STYLE = Style()
GEOMETRY = Geometry()
def fmt(value: float) -> str:
return f"{value:.3f}".rstrip("0").rstrip(".")
def polygon_path(points: list[tuple[float, float]]) -> str:
start = points[0]
rest = points[1:]
commands = [f"M {fmt(start[0])} {fmt(start[1])}"]
commands.extend(f"L {fmt(x)} {fmt(y)}" for x, y in rest)
commands.append("Z")
return " ".join(commands)
def circle_points(cx: float, cy: float, radius: float, samples: int = 144) -> list[tuple[float, float]]:
return [
(cx + math.cos(t) * radius, cy + math.sin(t) * radius)
for t in np.linspace(0.0, 2.0 * math.pi, samples, endpoint=False)
]
def ellipse_points(
cx: float,
cy: float,
rx: float,
ry: float,
rotation_deg: float = 0.0,
samples: int = 192,
) -> list[tuple[float, float]]:
rotation = math.radians(rotation_deg)
cos_r = math.cos(rotation)
sin_r = math.sin(rotation)
points = []
for t in np.linspace(0.0, 2.0 * math.pi, samples, endpoint=False):
x = math.cos(t) * rx
y = math.sin(t) * ry
points.append((cx + x * cos_r - y * sin_r, cy + x * sin_r + y * cos_r))
return points
DECORATIVE_CUTOUT_SPECS = (
("ellipse", 0.0, -141.0, 121.0, 41.0, 0.0),
("ellipse", -109.0, 58.0, 106.0, 41.0, 63.0),
("ellipse", 109.0, 58.0, 106.0, 41.0, -63.0),
)
BOLT_HOLE_OFFSETS = (
(-134.0, -76.0),
(134.0, -76.0),
(0.0, 157.0),
)
REFERENCE_BOLT_OFFSET_RADIUS = max(math.hypot(dx, dy) for dx, dy in BOLT_HOLE_OFFSETS)
def decorative_reference_extent() -> float:
extent = 0.0
for _, dx, dy, rx, ry, rotation in DECORATIVE_CUTOUT_SPECS:
points = ellipse_points(dx, dy, rx, ry, rotation, 240)
extent = max(extent, *(math.hypot(x, y) for x, y in points))
return extent
DECORATIVE_REFERENCE_EXTENT = decorative_reference_extent()
def clamp(value: float, minimum: float, maximum: float) -> float:
return max(minimum, min(maximum, value))
def calculated_pitch_radius(teeth: int, geometry: Geometry = GEOMETRY) -> float:
reference_pitch_radius = geometry.root_radius + geometry.pitch_radius_offset
return reference_pitch_radius * math.sin(math.pi / geometry.reference_teeth) / math.sin(math.pi / teeth)
def sprocket_sizing(teeth: int, geometry: Geometry = GEOMETRY) -> SprocketSizing:
pitch_radius = calculated_pitch_radius(teeth, geometry)
calculated_root_radius = pitch_radius - geometry.pitch_radius_offset
central_min_root_radius = geometry.central_hole_radius + geometry.material_margin
include_bolt_holes = teeth >= geometry.bolt_hole_min_teeth
if include_bolt_holes:
bolt_min_radius = geometry.central_hole_radius + geometry.bolt_hole_radius + geometry.material_margin
bolt_min_root_radius = bolt_min_radius + geometry.bolt_hole_radius + geometry.material_margin
min_root_radius = max(central_min_root_radius, bolt_min_root_radius)
else:
min_root_radius = central_min_root_radius
root_radius = max(calculated_root_radius, min_root_radius)
outer_radius = root_radius + geometry.tooth_depth
max_bolt_offset = max(0.0, root_radius - geometry.bolt_hole_radius - geometry.material_margin)
if include_bolt_holes:
min_bolt_offset = geometry.central_hole_radius + geometry.bolt_hole_radius + geometry.material_margin
bolt_offset = clamp(max_bolt_offset, min_bolt_offset, REFERENCE_BOLT_OFFSET_RADIUS)
bolt_scale = bolt_offset / REFERENCE_BOLT_OFFSET_RADIUS
else:
bolt_scale = 0.0
decorative_scale = clamp((root_radius - geometry.material_margin) / DECORATIVE_REFERENCE_EXTENT, 0.0, 1.0)
include_decorative_cutouts = decorative_scale >= geometry.decorative_cutout_min_scale
return SprocketSizing(
pitch_radius=pitch_radius,
calculated_root_radius=calculated_root_radius,
root_radius=root_radius,
tooth_depth=geometry.tooth_depth,
outer_radius=outer_radius,
decorative_scale=decorative_scale,
bolt_scale=bolt_scale,
include_bolt_holes=include_bolt_holes,
include_decorative_cutouts=include_decorative_cutouts,
)
def outer_points(
teeth: int,
geometry: Geometry = GEOMETRY,
canvas: Canvas = CANVAS,
radius_delta: float = 0.0,
) -> list[tuple[float, float]]:
samples = teeth * geometry.samples_per_tooth
sizing = sprocket_sizing(teeth, geometry)
points = []
phase_offset = -math.pi / 2.0
for index in range(samples):
theta = phase_offset + 2.0 * math.pi * index / samples
tooth_phase = (index % geometry.samples_per_tooth) / geometry.samples_per_tooth
radius = sizing.root_radius + sizing.tooth_depth * tooth_profile(teeth, tooth_phase, geometry) + radius_delta
points.append((canvas.cx + math.cos(theta) * radius, canvas.cy + math.sin(theta) * radius))
return points
def tooth_profile(teeth: int, phase: float, geometry: Geometry = GEOMETRY) -> float:
if teeth < geometry.low_tooth_threshold:
distance_from_peak = abs(phase - 0.5)
if distance_from_peak >= geometry.low_tooth_half_width:
return 0.0
return 1.0 - (distance_from_peak / geometry.low_tooth_half_width) ** geometry.low_tooth_cusp
smooth_profile = 0.5 - 0.5 * math.cos(2.0 * math.pi * phase)
return smooth_profile**geometry.tooth_sharpness
def offset_cutout(
kind: str,
dx: float,
dy: float,
rx: float,
ry: float | None,
rotation: float,
center_scale: float,
radius_scale: float,
canvas: Canvas = CANVAS,
) -> Cutout:
scaled_ry = None if ry is None else ry * radius_scale
return Cutout(
kind,
canvas.cx + dx * center_scale,
canvas.cy + dy * center_scale,
rx * radius_scale,
scaled_ry,
rotation,
)
def cutouts_for_teeth(teeth: int, canvas: Canvas = CANVAS, geometry: Geometry = GEOMETRY) -> list[Cutout]:
sizing = sprocket_sizing(teeth, geometry)
return [
*(
offset_cutout(kind, dx, dy, rx, ry, rotation, sizing.decorative_scale, sizing.decorative_scale, canvas)
for kind, dx, dy, rx, ry, rotation in DECORATIVE_CUTOUT_SPECS
if sizing.include_decorative_cutouts
),
offset_cutout("circle", 0.0, 0.0, geometry.central_hole_radius, None, 0.0, 1.0, 1.0, canvas),
*(
offset_cutout("circle", dx, dy, geometry.bolt_hole_radius, None, 0.0, sizing.bolt_scale, 1.0, canvas)
for dx, dy in BOLT_HOLE_OFFSETS
if sizing.include_bolt_holes
),
]
def cutout_path(cutout: Cutout, delta: float = 0.0, reverse: bool = False) -> str:
if cutout.kind == "circle":
points = circle_points(cutout.cx, cutout.cy, cutout.rx + delta, 160)
elif cutout.kind == "ellipse":
assert cutout.ry is not None
points = ellipse_points(
cutout.cx,
cutout.cy,
cutout.rx + delta,
cutout.ry + delta,
cutout.rotation,
240,
)
else:
raise ValueError(f"Unsupported cutout kind: {cutout.kind}")
if reverse:
points = list(reversed(points))
return polygon_path(points)
def cutout_paths_for_teeth(teeth: int, canvas: Canvas = CANVAS) -> list[str]:
return [cutout_path(cutout) for cutout in cutouts_for_teeth(teeth, canvas)]
def filled_svg(teeth: int, canvas: Canvas = CANVAS, style: Style = STYLE) -> str:
outer = polygon_path(outer_points(teeth))
cutouts = " ".join(cutout_paths_for_teeth(teeth, canvas))
return f"""<svg xmlns="http://www.w3.org/2000/svg" width="{canvas.width}" height="{canvas.height}" viewBox="0 0 {canvas.width} {canvas.height}">
<path d="{outer} {cutouts}" fill="{style.material}" fill-rule="evenodd"/>
</svg>
"""
def outline_svg(teeth: int, canvas: Canvas = CANVAS, style: Style = STYLE) -> str:
half_width = style.outline_width / 2.0
outer = polygon_path(outer_points(teeth, radius_delta=half_width))
inner = polygon_path(list(reversed(outer_points(teeth, radius_delta=-half_width))))
paths = [f"{outer} {inner}"]
for cutout in cutouts_for_teeth(teeth, canvas):
paths.append(f"{cutout_path(cutout, half_width)} {cutout_path(cutout, -half_width, reverse=True)}")
path_elements = "\n ".join(
f'<path d="{path}" fill="{style.outline}" fill-rule="evenodd"/>'
for path in paths
)
return f"""<svg xmlns="http://www.w3.org/2000/svg" width="{canvas.width}" height="{canvas.height}" viewBox="0 0 {canvas.width} {canvas.height}">
{path_elements}
</svg>
"""
def render_png(svg_path: Path, png_path: Path) -> None:
magick = shutil.which("magick") or shutil.which("convert")
if magick is None:
raise RuntimeError("ImageMagick is required for SVG-to-PNG conversion, but neither 'magick' nor 'convert' is on PATH.")
png_path.parent.mkdir(parents=True, exist_ok=True)
command = [
magick,
"-background",
"none",
"-density",
"192",
str(svg_path),
"-resize",
f"{CANVAS.width}x{CANVAS.height}!",
str(png_path),
]
subprocess.run(command, check=True)
def write_asset(teeth: int, variant: str, svg_root: Path, png_root: Path, png: bool = True) -> tuple[Path, Path | None]:
if variant == "filled":
svg_text = filled_svg(teeth)
elif variant == "outline":
svg_text = outline_svg(teeth)
else:
raise ValueError(f"Unsupported variant: {variant}")
stem = f"chainring_{teeth:02d}_{variant}"
svg_path = svg_root / variant / f"{stem}.svg"
png_path = png_root / variant / f"{stem}.png"
svg_path.parent.mkdir(parents=True, exist_ok=True)
svg_path.write_text(svg_text, encoding="utf-8")
if png:
render_png(svg_path, png_path)
return svg_path, png_path
return svg_path, None
def generate_all(min_teeth: int, max_teeth: int, output_dir: Path, png: bool = True) -> list[Path]:
svg_root = output_dir / "svg"
png_root = output_dir / "png"
png_paths: list[Path] = []
for teeth in range(min_teeth, max_teeth + 1):
for variant in ("filled", "outline"):
_, png_path = write_asset(teeth, variant, svg_root, png_root, png=png)
if png_path is not None:
png_paths.append(png_path)
return png_paths
def write_radii_manifest(min_teeth: int, max_teeth: int, output_path: Path) -> None:
output_path.parent.mkdir(parents=True, exist_ok=True)
fields = [
"teeth",
"pitch_radius_px",
"calculated_root_radius_px",
"root_radius_px",
"tooth_depth_px",
"outer_radius_px",
"bolt_scale",
"decorative_scale",
"bolt_holes",
"decorative_cutouts",
]
with output_path.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(handle, fieldnames=fields)
writer.writeheader()
for teeth in range(min_teeth, max_teeth + 1):
sizing = sprocket_sizing(teeth)
writer.writerow(
{
"teeth": teeth,
"pitch_radius_px": f"{sizing.pitch_radius:.3f}",
"calculated_root_radius_px": f"{sizing.calculated_root_radius:.3f}",
"root_radius_px": f"{sizing.root_radius:.3f}",
"tooth_depth_px": f"{sizing.tooth_depth:.3f}",
"outer_radius_px": f"{sizing.outer_radius:.3f}",
"bolt_scale": f"{sizing.bolt_scale:.3f}",
"decorative_scale": f"{sizing.decorative_scale:.3f}",
"bolt_holes": int(sizing.include_bolt_holes),
"decorative_cutouts": int(sizing.include_decorative_cutouts),
}
)
def make_contact_sheet(png_paths: list[Path], output_path: Path, columns: int = 6) -> None:
if not png_paths:
return
thumbs = []
thumb_size = (196, 165)
for path in png_paths:
image = Image.open(path).convert("RGBA")
image.thumbnail(thumb_size, Image.Resampling.LANCZOS)
tile = Image.new("RGBA", thumb_size, (61, 61, 61, 255))
x = (thumb_size[0] - image.width) // 2
y = (thumb_size[1] - image.height) // 2
tile.alpha_composite(image, (x, y))
thumbs.append(tile)
rows = math.ceil(len(thumbs) / columns)
sheet = Image.new("RGBA", (columns * thumb_size[0], rows * thumb_size[1]), (36, 36, 36, 255))
for index, thumb in enumerate(thumbs):
x = (index % columns) * thumb_size[0]
y = (index // columns) * thumb_size[1]
sheet.alpha_composite(thumb, (x, y))
output_path.parent.mkdir(parents=True, exist_ok=True)
sheet.save(output_path)
def compare_to_reference(candidate_path: Path, reference_path: Path) -> dict[str, float]:
candidate = Image.open(candidate_path).convert("L")
reference = Image.open(reference_path).convert("L").resize(candidate.size, Image.Resampling.LANCZOS)
candidate_arr = np.asarray(ImageOps.autocontrast(candidate), dtype=np.float32) / 255.0
reference_arr = np.asarray(ImageOps.autocontrast(reference), dtype=np.float32) / 255.0
diff = candidate_arr - reference_arr
candidate_edges = np.abs(np.gradient(candidate_arr)[0]) + np.abs(np.gradient(candidate_arr)[1])
reference_edges = np.abs(np.gradient(reference_arr)[0]) + np.abs(np.gradient(reference_arr)[1])
edge_diff = candidate_edges - reference_edges
return {
"mse": float(np.mean(diff**2)),
"mae": float(np.mean(np.abs(diff))),
"edge_mae": float(np.mean(np.abs(edge_diff))),
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Generate chainring SVG and PNG assets with fixed reference-like inner cutouts.")
parser.add_argument("--min-teeth", type=int, default=5)
parser.add_argument("--max-teeth", type=int, default=60)
parser.add_argument("--output", type=Path, default=Path("out"))
parser.add_argument("--no-png", action="store_true")
parser.add_argument("--contact-sheet", action="store_true")
parser.add_argument("--reference", type=Path, help="Optional local reference image for reporting similarity metrics.")
parser.add_argument("--reference-teeth", type=int, default=44)
return parser.parse_args()
def main() -> None:
args = parse_args()
if args.min_teeth < 3:
raise ValueError("--min-teeth must be at least 3.")
if args.max_teeth < args.min_teeth:
raise ValueError("--max-teeth must be greater than or equal to --min-teeth.")
png_paths = generate_all(args.min_teeth, args.max_teeth, args.output, png=not args.no_png)
write_radii_manifest(args.min_teeth, args.max_teeth, args.output / "radii.csv")
if args.contact_sheet and png_paths:
filled_samples = [path for path in png_paths if path.parent.name == "filled"]
outline_samples = [path for path in png_paths if path.parent.name == "outline"]
make_contact_sheet(filled_samples, args.output / "contact_sheet_filled.png")
make_contact_sheet(outline_samples, args.output / "contact_sheet_outline.png")
if args.reference and not args.no_png:
with TemporaryDirectory() as tmp:
tmp_root = Path(tmp)
_, candidate = write_asset(args.reference_teeth, "filled", tmp_root / "svg", tmp_root / "png", png=True)
assert candidate is not None
metrics = compare_to_reference(candidate, args.reference)
print(
"reference_metrics "
+ " ".join(f"{name}={value:.6f}" for name, value in metrics.items())
)
print(f"generated teeth={args.min_teeth}..{args.max_teeth} output={args.output}")
if __name__ == "__main__":
main()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Some files were not shown because too many files have changed in this diff Show More