Skip to content

M4-25 Desktop settings, own devices and sessions, step-up dialog

No Date Ruling Reason
D-M4-25-0 2026-10-17 Base int/m1-wave38 at 297ab0c, branch feat/M4-25-settings-devices in its own worktree; the branch is not merged locally and not pushed, the PR lane lands it. The /settings/<section> routes are registered in app.dart next to the other GoRoutes, because router.dart only parses locations (D-M4-18-4). The task of the build lane overrides the merge step of the brief; the router file has no route table. Sources: brief M4-25 Boundaries.
D-M4-25-1 2026-10-17 Settings defaults live on the client (PersonalSettings.resolve): an absent key or a value outside the list of 01 11 reads as the default; composer.enter_sends defaults to true on desktop, privacy.crash_reports to true only with a crash target, privacy.client_metrics to true. Choosing a device-independent default deletes the key (deleteMySetting), every other change is one putMySetting through the outbox; the local row changes at once. "Column widths: Reset" deletes layout.column_widths. 04 14 stores no defaults, because some depend on the device or the instance. Sources: 01 11, 04 14, brief M4-25 Design note 1.
D-M4-25-2 2026-10-17 The device and session screens live in packages/admin_ui/lib/src/self/ and read and write only through SelfRepository (REST over api_client, no local database); the desktop hosts them in Settings, the console (M6) and the mobile emergency actions (M5) reuse them. 15 3.1: admin_ui depends on api_client, auth, design_system, l10n, never on sync_engine or local_store (make arch). Sources: 15 3, 3.1.
D-M4-25-3 2026-10-17 StepUpDialog and StepUpDialogPrompt (the StepUpPrompt of M4-09) live in admin_ui; AppPorts installs the prompt over the router's navigator key, replacing the ConfirmingStepUpPrompt of M4-18. The dialog appears only when the server answers 401 step_up_required (device.approve, device.revoke); ending a session asks one confirmation and never for step-up. On a pairing 404 the server ends the step-up of the session (the fake server does the same), so the next attempt asks again (08 5.3). Step-up handling stays in the StepUpService of M4-09; the screen adds no step-up of its own (05). Sources: 08 5.2, 5.3, 6; brief M4-25 Design note 3.
D-M4-25-4 2026-10-17 privacy.crash_reports is shown only when the build names a crash target: BuildBranding.hasCrashTarget from the define BRANDING_CRASH_REPORTS=true until M4-31 brings the target itself; absent means hidden. privacy.client_metrics is always shown. 01 11 and 15 16: crash reports only with a configured target. Sources: brief M4-25 Design note 4.
D-M4-25-5 2026-10-17 The device list shows no hardware binding label: the Device schema of docs/api/ has no hardware or attestation field. Revoking this device or ending the current session calls OnboardingController.endLocally, which runs the wiper of M4-18 without a server logout and shows E7. Time zones come from the IANA database of the timezone package. 01 A1 names the label, but the contract does not carry it; the gap is reported, not built (P-11). Sources: 01 5.1 D8, 08 10, R-SY-14.
D-M4-25-6 2026-10-19 Port onto main from the integration squash of int/m1-wave40 on top of M4-18: the ticket's own commits only, with the doc comment fix of INT-W40/4 (ApiSelfRepository names its constructor argument as `api`); the rulings stand in their own section; the move of the early rulings to DECISIONS-EARLY.md (INT-W40/3) is not ported, because main's log is below repo.max_file_kb. The PR lane ports only the ticket and the integration fixes it needs.
D-M4-25-7 2026-10-19 The integration tests devices_test.dart and settings_sync_test.dart of this ticket get their lines in apps/client/platform-tests.json, so the platform jobs run them on macOS and Windows; the source references of D-M4-25-0 to D-M4-25-5 move into the reason cell, as the table has four columns. D-M4-18-19 asks a line for each new file; the register check refuses a row with more cells than its header.
D-M4-25-8 2026-10-19 Ending the own session from the settings ends in E7 at once: GatewayConnection.dispose no longer waits for the close of its state stream, and OnboardingController sets no state after its provider was disposed; the platform test of the theme reset taps the system choice inside settings.theme. The first platform runs of devices_test.dart hung 20 seconds in the wipe: the status line provider listens to the state stream and Riverpod pauses it while the settings cover the shell, and the close of a broadcast stream waits for paused listeners; the late state write then threw UnmountedRefException. Theme and motion both offer system, so the old finder matched two chips. Tests: session_test.dart (a paused listener does not hold dispose) and controller_test.dart (an end that outlives its container).
D-M4-25-9 2026-10-20 FakeInstance.stopSibling deletes the data folder of the second app with the retry of stop (D-M4-18-18), until its database is closed or closeTimeout ends. On the Windows runner settings_sync_test.dart failed in its teardown with PathAccessException (errno 32): the disposed container closes the database without waiting, and Windows refuses to delete an open file. Both deletes now share one helper.
D-M4-25-10 2026-10-20 L10nFormat.time puts the no-break space U+00A0 before AM and PM instead of the U+202F that intl writes; the goldens with English clock times are recorded again (settings devices, admin_ui self devices and sessions, the macOS goldens of six bothy_viz views). admin_ui installs DsGoldenFileComparator in its flutter_test_config.dart like the other members with goldens (PL/5). Atkinson Hyperlegible Next has no U+202F, so every host drew it with its own fallback font: on the Linux runner the device rows of settings_devices grew wider and wrapped, and the CI golden failed. The new goldens of admin_ui had never run on Linux and failed there without the comparator of PL/5. Both were reproduced in a Linux amd64 container with Flutter 3.47.7, where all goldens of apps/client, admin_ui and bothy_viz pass after the change. Test: formatting_test.dart.