Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
5222cdf
Add settings audit safety checks
cagnulein Aug 18, 2026
a1e67d4
Test settings audit safety checks
cagnulein Aug 18, 2026
64ffecc
Run settings audit in CI
cagnulein Aug 18, 2026
6bb515d
Track settings refactor phases
cagnulein Aug 18, 2026
6004b49
Limit settings audit to persistent Settings blocks
cagnulein Aug 18, 2026
b1137bb
Cover persistent Settings block parsing
cagnulein Aug 18, 2026
c982ab4
Keep settings audit checkout lightweight
cagnulein Aug 18, 2026
000bce9
Baseline existing settings catalog debt
cagnulein Aug 18, 2026
40707ac
Document catalog baseline debt
cagnulein Aug 18, 2026
ad89bbc
Mark settings refactor phase 1 complete
cagnulein Aug 18, 2026
ee6ef54
Add deterministic phase 2 catalog migration
cagnulein Aug 18, 2026
8d6c281
Run deterministic phase 2 catalog migration
cagnulein Aug 18, 2026
578e2d7
Complete settings catalog coverage for phase 2
github-actions[bot] Aug 18, 2026
7617c07
Allow phase 2 migration on pull request sync
cagnulein Aug 18, 2026
81f688f
Make phase 2 catalog migration idempotent
cagnulein Aug 18, 2026
af4a88f
Run one-shot phase 2 migration through settings audit
cagnulein Aug 18, 2026
c86f2e6
Add shared settings behavior migration
cagnulein Aug 18, 2026
ed58e35
Test shared settings behavior migration
cagnulein Aug 18, 2026
b6a91c5
Migrate generic settings writes to shared behavior controller
cagnulein Aug 18, 2026
057bc9e
Apply settings refactor phase 2 migrations
github-actions[bot] Aug 18, 2026
6adb82e
Fix settings behavior migration targeting
cagnulein Aug 18, 2026
311e418
Strengthen settings behavior controller tests
cagnulein Aug 18, 2026
68f242b
Apply settings refactor phase 2 migrations
github-actions[bot] Aug 18, 2026
8028fdd
Apply phase 2 behavior migration on push
cagnulein Aug 18, 2026
1eb4efa
Add modern settings preview migration
cagnulein Aug 18, 2026
ffbabb3
Test modern settings preview structure
cagnulein Aug 18, 2026
b909bed
Apply modern settings preview migration on push
cagnulein Aug 18, 2026
05c2669
Apply settings refactor UI migrations
github-actions[bot] Aug 18, 2026
9d411a7
Remove completed catalog migration helper
cagnulein Aug 18, 2026
ea69ca7
Remove completed behavior migration helper
cagnulein Aug 18, 2026
38507d0
Remove completed modern UI migration helper
cagnulein Aug 18, 2026
a6c364c
Remove completed settings migration workflow
cagnulein Aug 18, 2026
0ed69d5
Finalize permanent settings refactor audit
cagnulein Aug 18, 2026
2ba1af7
Update settings refactor phase status
cagnulein Aug 18, 2026
d7cc480
Add final modern settings UI compatibility fixup
cagnulein Aug 18, 2026
aaf2205
Harden modern settings preview compatibility tests
cagnulein Aug 18, 2026
9797a3e
Run final modern settings UI compatibility fixup
cagnulein Aug 18, 2026
ead76a5
Harden modern settings preview QML compatibility
github-actions[bot] Aug 18, 2026
1d3574d
Trigger final settings UI fixup
cagnulein Aug 18, 2026
6e9cd69
Remove completed settings UI fixup helper
cagnulein Aug 18, 2026
1ba610c
Remove completed settings UI fixup workflow
cagnulein Aug 18, 2026
aabcef4
Add phase 4 behavior parity migration
cagnulein Aug 18, 2026
2756af5
Add settings behavior parity tests
cagnulein Aug 18, 2026
d7b66a0
Add phase 4 settings migration workflow
cagnulein Aug 18, 2026
918e23c
Trigger phase 4 settings migration
cagnulein Aug 18, 2026
9ea5842
Trigger phase 4 parity migration
cagnulein Aug 18, 2026
a12b79a
Apply phase 4 through settings audit
cagnulein Aug 18, 2026
a78cd77
Preserve legacy settings behavior in modern UI
github-actions[bot] Aug 18, 2026
f911ce0
Allow keyed settings mutation in behavior test
cagnulein Aug 18, 2026
5197bda
Remove phase 4 migration helper
cagnulein Aug 18, 2026
4bfa0c0
Remove temporary phase 4 workflow
cagnulein Aug 18, 2026
abc09f0
Restore permanent read-only settings audit
cagnulein Aug 18, 2026
7f63008
Clean phase 4 parity test
cagnulein Aug 18, 2026
e6e1d6d
Update settings refactor phase 4 status
cagnulein Aug 18, 2026
aba7d68
Add legacy settings hierarchy generator and validation
cagnulein Aug 18, 2026
43e55ac
Add one-shot hierarchy UI migration
cagnulein Aug 18, 2026
cd97d09
Test legacy-derived modern settings hierarchy
cagnulein Aug 18, 2026
61c0cc9
Apply and validate legacy-derived settings hierarchy
cagnulein Aug 18, 2026
37e5625
Derive modern settings layout from legacy hierarchy
github-actions[bot] Aug 18, 2026
ad494c8
Make hierarchy UI migration idempotent
cagnulein Aug 18, 2026
f383aa8
Derive modern settings layout from legacy hierarchy
github-actions[bot] Aug 18, 2026
c9d9429
Remove one-shot settings hierarchy migration
cagnulein Aug 18, 2026
d2c75b3
Keep settings hierarchy validation read-only
cagnulein Aug 18, 2026
34a115b
Document legacy-derived hierarchy and master drift gate
cagnulein Aug 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/settings-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Settings audit

on:
pull_request:
paths:
- 'src/settings*.qml'
- 'src/settings-catalog.json'
- 'scripts/settings_audit.py'
- 'scripts/settings_hierarchy.py'
- 'tests/test_settings_audit.py'
- 'tests/test_settings_behavior_migration.py'
- 'tests/test_modern_settings_preview.py'
- 'tests/test_settings_behavior_parity.py'
- 'tests/test_settings_hierarchy.py'
- '.github/workflows/settings-audit.yml'
- 'docs/settings-refactor-status.md'
workflow_dispatch:

permissions:
contents: read

jobs:
audit:
runs-on: ubuntu-latest
env:
PYTHONDONTWRITEBYTECODE: "1"
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Fetch latest master
run: git fetch --no-tags --depth=1 origin master:refs/remotes/origin/master
- name: Test settings refactor infrastructure
run: >-
python3 -m unittest
tests/test_settings_audit.py
tests/test_settings_behavior_migration.py
tests/test_modern_settings_preview.py
tests/test_settings_behavior_parity.py
tests/test_settings_hierarchy.py
- name: Validate generated hierarchy and latest-master coverage
run: python3 scripts/settings_hierarchy.py --master origin/master
- name: Resolve and fetch ABI base
id: base
shell: bash
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
BASE_SHA="${{ github.event.pull_request.base.sha }}"
git fetch --no-tags --depth=1 origin "$BASE_SHA"
echo "sha=$BASE_SHA" >> "$GITHUB_OUTPUT"
else
git fetch --no-tags --depth=1 origin master
echo "sha=FETCH_HEAD" >> "$GITHUB_OUTPUT"
fi
- name: Audit persistent settings ABI and catalog
run: >-
python3 scripts/settings_audit.py
--base "${{ steps.base.outputs.sha }}"
--report settings-audit.json
- name: Upload audit inventory
if: always()
uses: actions/upload-artifact@v4
with:
name: settings-audit
path: settings-audit.json
if-no-files-found: ignore
96 changes: 96 additions & 0 deletions docs/settings-refactor-status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Settings UI refactor status

This document tracks the staged refactor of the QZ settings UI. Persistent QML settings are compatibility-sensitive, especially on iOS, so every phase keeps the persistence ABI protected and the legacy renderer available until runtime smoke testing is complete.

## Invariants

- Existing persistent setting keys must not be renamed or removed.
- Existing persistent setting QML types and default expressions must not change as part of the UI refactor.
- Existing persistent declarations must keep their relative order. New persistent properties are append-only.
- `src/settings-catalog.json` coverage must never regress. Any new persistent setting must be cataloged in the same change.
- The modern category hierarchy is derived from the existing `AccordionElement` nesting in `src/settings.qml`; it is not a separately maintained taxonomy.
- A PR validation run compares against the current `master` head and fails if master contains persistent settings that are absent from the PR catalog/layout.
- Generic modern controls must not expose internal serialized storage. Persistent string state that is write-only in the legacy settings UI is treated as internal, and serialized defaults containing both `|` and `;` are rejected if visible.
- UI behavior, visibility, enabled state, side effects, and virtual-setting mappings must be preserved unless a separate behavioral change is explicitly requested.
- The legacy UI remains available as a fallback until runtime smoke testing is complete.

## Current baseline

The Phase 1 audit found **998 unique persistent setting keys** across the settings QML files. Phase 2 completed the catalog from the previous 968/998 baseline to **998/998**.

The 30 former gaps were classified before being added. User-facing examples include ANT+ Garmin, OpenBikeControl/MyWhoosh Link controls, Peloton Bike OCR, Wattbike emulator, and Zwift Play button/gear mappings. Storage-only values such as Garmin token-expiry state and Bluetooth service-changed state are cataloged with `visible: false`.

The catalog, persistent ABI, generated hierarchy, and latest-master setting coverage are continuously checked by the read-only `Settings audit` workflow.

## Phase 1: safety infrastructure

Status: **complete**

- [x] Parse only persistent `Settings {}` blocks in all settings QML files.
- [x] Compare each pull request against its actual base commit.
- [x] Reject removal, reorder, rename, QML type changes, and default-expression changes for existing persistent properties.
- [x] Allow new persistent properties only by appending after existing declarations.
- [x] Validate catalog coverage, `settingCount`, and catalog `qmlType` values.
- [x] Produce a machine-readable audit artifact with references, direct writes, and simple `visible`/`enabled` references.
- [x] Add unit tests and a dedicated GitHub Actions workflow.

## Phase 2: catalog completion and shared settings behavior layer

Status: **complete**

- [x] Classify and add all 30 previously uncataloged persistent keys.
- [x] Reach 998/998 catalog coverage.
- [x] Add a shared `settingsBehavior` controller for catalog/search/new-UI mutations.
- [x] Route generic persistent writes through the shared controller.
- [x] Route virtual multi-setting selections through the shared controller.
- [x] Keep hand-written legacy controls intact while the behavior layer is introduced.

## Phase 3: modern iOS-style settings UI

Status: **complete, modern renderer is now the default**

- [x] Add the modern hierarchical settings renderer.
- [x] Add in-UI search across catalog-visible settings, virtual settings, and pages.
- [x] Add rows for booleans, free-form values, catalog choices, virtual choices, and page navigation.
- [x] Reuse the same catalog helpers and shared mutation path.
- [x] Promote the modern renderer to open automatically when the settings page is entered.
- [x] Keep the complete legacy renderer available behind the modern Drawer as a fallback during final validation.

## Phase 4: behavior parity hardening

Status: **repository parity complete; runtime smoke testing pending**

- [x] Persistent ABI compatibility remains unchanged.
- [x] Catalog coverage remains 998/998.
- [x] Add explicit `restartRequired` metadata to every persistent catalog setting and every virtual setting.
- [x] Derive restart policy from the legacy handlers instead of assuming every generic edit needs a restart.
- [x] Preserve metric/imperial display and storage conversions for weight, bike weight, height, autolap distance, treadmill speeds, and Peloton treadmill speed thresholds.
- [x] Preserve race-pace total-time formatting/storage for 1 mile, 5 km, 10 km, half marathon, and marathon targets.
- [x] Preserve Domyos profile and Kingsmith encryption mutual exclusion.
- [x] Preserve Peloton auto-start mutual exclusion.
- [x] Preserve Zwift OCR mutual exclusion and Android notification side effect.
- [x] Preserve Zwift Play / Wattbike emulator conflict handling, including the existing confirmation dialog behavior.
- [x] Preserve calibration cache invalidation for watt offset, watt gain, and power sensor changes.
- [x] Add permanent tests for the above behavior layer and explicit restart metadata.

## Phase 5: legacy-derived information architecture and drift protection

Status: **implemented; runtime validation pending**

- [x] Derive root categories and subcategories from the actual nested `AccordionElement` structure in `src/settings.qml`.
- [x] Store the generated hierarchy and setting/page/virtual-setting node mappings in the catalog so the runtime does not maintain a second manual taxonomy.
- [x] Navigate nested legacy categories in the modern UI instead of promoting every catalog `parent` to root.
- [x] Add UTF-8/Unicode-only icons to root categories; no external graphical assets are required.
- [x] Hide legacy write-only persistent string storage from generic modern TextFields.
- [x] Explicitly prevent serialized string defaults containing both `|` and `;` from being exposed as normal visible settings.
- [x] `ergDataPoints` and `treadmillDataPoints` are now internal and no longer appear as duplicate `Watt Offset` fields.
- [x] Fetch current `master` during Settings audit and fail if master has added persistent settings missing from the PR/catalog/layout.
- [x] Keep the hierarchy generator/check permanent and the workflow read-only.
- [x] Unit tests and hierarchy/latest-master gate green after migration.
- [ ] Full multiplatform build green on the final cleaned-up head.
- [ ] Manual smoke test of root categories/subcategory navigation on iOS.
- [ ] Manual smoke test on at least one non-iOS platform.

### Current stop condition

The modern renderer is the default and now mirrors the legacy category hierarchy rather than the catalog's flat parent list. The remaining blocker for deleting the legacy renderer is runtime visual/interaction validation on iOS and at least one non-iOS platform, plus any additional dynamic visibility/enabled behavior discovered during that smoke test.
Loading
Loading