Repository navigation
feat(settings): finish the Applications pane - main workstation, rename, remove, copy to shared #56
Description
Summary
The Applications settings cluster shipped in PR #25 (+ #30) covering synced settings and the device registry. /help/app-settings documents six user actions in that section; verify which of them the shipped pane actually offers and build the rest. This issue is deliberately a verify-then-build, because the pane exists and some of these may already be present.
The documented actions (/help/app-settings, re-read 2026-09-07)
| Action | Route | In DevicesView today? |
|---|---|---|
| Set as main workstation | PATCH /api/user/app-settings/{appKey}/devices/{deviceId} |
verify |
| Rename a machine | PATCH …/devices/{deviceId} |
verify |
| Remove a machine | DELETE …/devices/{deviceId} |
verify |
| View settings (shared + per-machine, with last-updated and size) | GET …/{appKey}, GET …/devices/{deviceId}/settings |
verify |
| Copy a machine's settings to shared | PUT …/{appKey} from the device payload |
verify |
| Delete shared settings | DELETE /api/user/app-settings/{appKey} |
verify |
The rules that are easy to get wrong
- The first machine registered automatically becomes the main workstation, and only one machine holds the role at a time. Promoting a machine demotes the previous holder — the UI must reflect that, not just set a flag.
- Removing the main workstation auto-promotes another machine (the most recently used), server-side. After a remove, refetch rather than assuming which machine now holds the role.
- Removing a machine deletes its per-machine settings but leaves shared settings alone. Say that in the confirmation.
- Copy to shared replaces the shared settings wholesale — it is not a merge. Confirm destructively.
- A new machine's first sign-in seeds from the main workstation's settings, then keeps its own per-machine settings thereafter.
- This section is free — no subscription needed. Do not gate it.
Standing constraints from the shipped work — do not regress
appSettingsKeyis"interlinedlist-macos"(AppEnvironment.appSettingsKey) and must stay stable. Changing it orphans every stored setting. There is no registration mechanism; the segment is a free-form namespace (proven by probe: an invented key returns200 {"devices":[]}).- 404 is the ordinary first-run state, not a failure. An app key with nothing stored 404s, and
bootstrapreturns404 {"source":"none"}for an unseen device. PR fix(app-settings): treat first-run 404s as empty, enable G17, and correct the appKey claim #30 mapped both to empty — keep it that way. - The populated payload shapes are still unverified because nothing was stored at the time. The DTOs are deliberately tolerant. Store real settings from the Mac, then re-probe and tighten — that is the main verification value in this issue.
This is also the sanctioned home for the Sync Agent's config
/help/app-settings frames per-machine settings as exactly the case the Document Sync Agent needs (a sync folder path is meaningful on one machine only). Migrating the agent's UserDefaults state here is in scope if the pane's read/write half is solid — otherwise split it out.
Division of labor
Verify first (no code)
- Walk
App/Features/Settings/DevicesView.swiftandDevicesViewModel.swiftagainst the six-action table and tick off what exists. - Write real settings from the Mac, then re-probe every
GETand record the populated shapes indocs/spikes/.
Kit — only the routes the verify pass shows are missing, plus tightened DTOs once the shapes are known.
Domain — the promote/demote and remove-then-refetch semantics; copy-to-shared as an explicit replace.
App — the missing actions in DevicesView, each with a confirmation that states its actual consequence.
Tests
- happy — rename, promote, remove, copy-to-shared each round-trip
- invalid — promote a device that no longer exists
- upstream-failure — remove succeeds but the refetch fails → the row disappears and the main-workstation badge shows unknown rather than a stale value
- boundary — exactly one registered device (removing it); first-run 404s still map to empty
Acceptance criteria
- Every action
/help/app-settingsdocuments is available in Settings ▸ Applications. - The populated payload shapes are recorded, and the DTOs are as tight as the evidence allows.
appSettingsKeyis unchanged.- Full E2E gate green.
Notes
Follows G17 (work-consolidation.md), shipped in PR #25/#30. Size S–M depending on what the verify pass finds.
Activity
Adron commentedon Sep 16, 2026
Implemented in PR #102. Work continues on the PR.
The verify-then-build framing this issue asked for was the right call, and the answer was worse than "some may already be present": three actions had UI that could not work, three had no UI at all. All six Kit route builders existed — the routes were right, the bodies and decoders were not — which is why it read as further along than it was. Nothing in production consumed the settings half, so the broken paths were never exercised.
Storing real settings and re-probing, as this issue said was its main verification value, found five wrong DTO guesses:
baseVersion— the family is compare-and-set (409 +currentwhen stale). A PUT carrying onlythemedeletedsidebarWidth: direct evidence for the "copy-to-shared replaces, it is not a merge" rule this issue flags.deviceName/isDefault, notname/isMainWorkstation— so every row would have shown a raw UUID with no Main badge, silently, because the fields were optional.PATCHstates its own contract: at least one ofdeviceNameorisDefault.POST/PATCHwrap the device in{"device":…}— decoding bare threw on success.bootstrapis one document plus asourcetag, not the shared+device pair modelled — it decoded to empty on every launch.Two findings better than assumed: the server names the promoted successor on a delete, so the "refetch, do not assume" rule is satisfied by reading the response rather than a second call; and
POST …/devicesis an upsert keyed ondeviceId, so unconditional registration would have overwritten the user's rename on every pane visit. Registration is guarded on absence.appSettingsKeyis unchanged, and first-run 404s still map to empty.Two things deliberately not built, both argued in the PR: plumbing the 409's
currentdocument (needs typed error bodies inAPIClient, which touches every endpoint's error path) and the Sync AgentUserDefaultsmigration — this issue scopes that in only if the read/write half is solid, and it was non-functional, so it earns its own issue now that this works.Merge dev into feat/app-settings-devices-g17