From 147247a5aa30cae53c1152c4cc15226338def2f6 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Fri, 16 Oct 2026 10:03:17 +0200 Subject: fix(user): the page retries partial saves and survives leaving mid-save A key whose write half-failed (AccountsService took it, git did not) reloads as unchanged and Save went dead. A key with an error now counts as changed, and editing a field clears its error. Processes were parented to the page, so leaving it mid-save killed userctl between stores. They hang off the module now, passed in as owner (not the module's id, which would bind to itself). busy now clears when the reload lands, so Save cannot be clicked again before saved is fresh, and the fields and avatar picker are disabled while busy. Avatar paths with # or % broke the preview under a bare file:// prefix; each segment is now URI-encoded for the Image source (page and row), while draft.avatar stays a plain path for userctl. Dropped a redundant saved clear in load. Co-Authored-By: Claude Sonnet 5.5 --- desktop/modules/user/UserModule.qml | 3 ++- desktop/modules/user/UserPage.qml | 17 ++++++++++++----- desktop/modules/user/UserRow.qml | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/desktop/modules/user/UserModule.qml b/desktop/modules/user/UserModule.qml index ef49829..9e5210c 100644 --- a/desktop/modules/user/UserModule.qml +++ b/desktop/modules/user/UserModule.qml @@ -14,6 +14,7 @@ import "../.." // The user's details. No tile: the drawer's greeting row opens the page. Module { + id: um name: "user" label: "User" // nf-fa-user, present in Inconsolata Nerd Font. @@ -23,7 +24,7 @@ Module { page: Component { Page { title: "User" - UserPage { width: parent.width } + UserPage { width: parent.width; owner: um } } } } diff --git a/desktop/modules/user/UserPage.qml b/desktop/modules/user/UserPage.qml index 024a63b..f559538 100644 --- a/desktop/modules/user/UserPage.qml +++ b/desktop/modules/user/UserPage.qml @@ -43,13 +43,19 @@ Column { property var keys: [] property var locales: [] property bool busy: false + // The module, which outlives the page: a save in flight finishes even if + // the page is closed. Not named `um`, the module's id (it would bind to itself). + required property QtObject owner property string loadError: "" - readonly property var changed: Object.keys(draft).filter(k => draft[k] !== saved[k]) + readonly property var changed: Object.keys(draft).filter(k => draft[k] !== saved[k] || !!errors[k]) function set(key, value) { const d = Object.assign({}, form.draft); d[key] = value; form.draft = d; + const e = Object.assign({}, form.errors); + delete e[key]; + form.errors = e; } // ComboBox entries per kind. The current language is kept even if @@ -67,7 +73,7 @@ Column { // One Process per call, so reads and writes never share one (a running // Process ignores a second start, AGENTS.md). cb gets stdout. function run(cmd, cb) { - op.createObject(form, { command: cmd, cb: cb }).running = true; + op.createObject(form.owner, { command: cmd, cb: cb }).running = true; } Component { id: op @@ -87,12 +93,12 @@ Column { function load(keep) { form.run([form.ctl, "get"], out => { const s = U.parse(out); + form.busy = false; if (s.error) { form.loadError = s.error; return; } form.loadError = ""; - form.saved = ({}); form.saved = s; form.draft = Object.assign({}, s, keep); }); @@ -106,7 +112,6 @@ Column { } function step(queue, failed) { if (queue.length === 0) { - form.busy = false; form.load(failed); return; } @@ -147,7 +152,7 @@ Column { Image { id: pic anchors.fill: parent - source: form.draft.avatar ? "file://" + form.draft.avatar : "" + source: form.draft.avatar ? "file://" + form.draft.avatar.split("/").map(encodeURIComponent).join("/") : "" fillMode: Image.PreserveAspectCrop sourceSize: Qt.size(192, 192) cache: false @@ -161,6 +166,7 @@ Column { } MouseArea { anchors.fill: parent + enabled: !form.busy cursorShape: Qt.PointingHandCursor onClicked: picker.open() } @@ -188,6 +194,7 @@ Column { delegate: Column { id: field + enabled: !form.busy required property var modelData width: form.width spacing: 4 diff --git a/desktop/modules/user/UserRow.qml b/desktop/modules/user/UserRow.qml index 106a490..bd1fe0b 100644 --- a/desktop/modules/user/UserRow.qml +++ b/desktop/modules/user/UserRow.qml @@ -59,7 +59,7 @@ Rectangle { Image { id: pic anchors.fill: parent - source: row.info.avatar ? "file://" + row.info.avatar : "" + source: row.info.avatar ? "file://" + row.info.avatar.split("/").map(encodeURIComponent).join("/") : "" fillMode: Image.PreserveAspectCrop sourceSize: Qt.size(88, 88) cache: false -- cgit v1.2.3