From 78bc20b8acdcf8e7bbd422593b9253c4493c53e4 Mon Sep 17 00:00:00 2001 From: librelad Date: Fri, 28 Aug 2026 07:46:36 +0100 Subject: [PATCH] setup: surface the per-app storage choice in the App Center MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Choosing a drive worked from the CLI but was invisible in the WebUI, for three separate reasons, each of which hid the next: * the config editor only renders fields listed in apps-field-mappings.json, and STORAGE was not one — so no amount of correct data made it appear. Added there, in General, with its choices built from the locations registered at generate time (unlike every other select here, they are not knowable statically). * app TEMPLATES ship "[default:Primary]", and templates are what the install form reads for an app that is not installed yet — precisely the app whose form needs to show which drives exist. storageSyncAllAppComments now covers templates, and is finally called from a regen path: it was written for one and never wired in, so every option list was frozen at install time and adding a drive made it selectable nowhere. * storageLocationName resolved a name only from an in-scope CFG_STORAGE_LOC__NAME and otherwise fell back to the bare id. That name is the value CFG__STORAGE takes, so the generated dropdown offered "location-1" as both label and value — a choice that does not resolve. Read it from the location's own config when the variable is not in scope. Then the control rendered but sat blank. Config values are the raw right-hand side of "KEY=value # comment"; almost all are stored without a comment, but a field whose comment is regenerated keeps one — CFG__STORAGE records the location it currently resolves to. updateConfigForm assigned that whole string to the field, which for a gets a value matching none of its options and renders blank, as + // if nothing were set. Strip it once here rather than per field. + stripInlineComment(value) { + if (typeof value !== 'string') return value; + const cut = value.replace(/\s+#.*$/, '').trim(); + return cut.replace(/^"(.*)"$/, '$1'); + }, + updateConfigForm(appName, appConfig) { const form = document.getElementById(`app-form-${appName}`); if (!form) return; @@ -55,12 +67,20 @@ Object.assign(AppsManager.prototype, { Object.entries(appConfig).forEach(([key, value]) => { const field = form.querySelector(`[name="${key}"]`); if (!field) return; - let nextValue = value; + let nextValue = this.stripInlineComment(value); if (key.endsWith('_NETWORK')) { - nextValue = this.applyContextualDefault('NETWORK', value, appData); + nextValue = this.applyContextualDefault('NETWORK', nextValue, appData); } if (field.type === 'checkbox') { field.checked = nextValue === 'true' || nextValue === 'yes'; + } else if (field.tagName === 'SELECT') { + // Assigning a value no option carries sets selectedIndex to -1, and the + // control then renders blank — which reads as "nothing is configured" + // rather than "the stored value is unrecognised". The renderer has + // already fallen back to the field's default, so keep that instead. + if ([...field.options].some(o => o.value === String(nextValue))) { + field.value = nextValue; + } } else { field.value = nextValue; } @@ -74,6 +94,20 @@ Object.assign(AppsManager.prototype, { return; } + // Normalise once, here, instead of at each of the several places a value is + // read. Config values are the raw right-hand side of "KEY=value # comment", + // and most are stored without a comment — but any field whose comment is + // regenerated keeps one (CFG__STORAGE records the location it currently + // resolves to). A