fix: register the settings card instead of throwing while building it
Release / release (push) Successful in 7s

The card never appeared, and nothing said why: the plugin's configuration
section was missing from its card in the Plugins panel while a sibling
plugin's showed, on a deployment where the Host demonstrably served both
namespaces (`settings/describe` over the app's own RPC answered
`dsh-recall fields=32 secrets=[embeddingApiKey set]`) and the browser was
demonstrably serving this bundle.

The cause was construction order. `SettingsFormModel.bind()` is not a
subscription setter — it builds its snapshot store from one immediate
`project()` call — so the projection runs inside the constructor, and the
projection reads the describe mirror to report whether the API key is set.
`this.describe` was assigned *after* `bind`, so that first projection read
`undefined.getSnapshot()`:

  TypeError: Cannot read properties of undefined (reading 'getSnapshot')
      at RecallSettingsController.secretIsSet (client/client.js:428)
      at RecallSettingsController.projection (client/client.js:413)

It was thrown from inside the `ctx.inject(['configForms'], …)` callback while
the card was being registered, so the registration was abandoned: no ledger
entry, no section on the card, and no error in the UI — the one failure mode
this file's own header set out to prevent.

Everything the projection reads is now assigned before anything projects. The
sibling plugin was never affected because it reads the same face lazily from a
field it assigns first.

The test stub's `bind()` only *stored* the projection, which is why the suite
was green through all of this. It now projects once, as the framework does —
and with the old ordering every mounting test fails with that TypeError, so
this cannot regress silently again.
This commit is contained in:
NotExpectedYet committed 2026-09-26 07:43:31 +01:00
1 parent a350178434
commit fbf1da7452
2 files changed
+15 -2

No files matched your search

+10 -2
View File
@@ -385,6 +385,16 @@ window.__ModuleLoader__.load({
*/
constructor(ctx) {
this.ctx = ctx
// Every field the projection reads must exist BEFORE anything projects.
//
// `SettingsFormModel.bind()` is not a subscription setter: it builds the
// snapshot store from one immediate `project()` call, so the projection
// runs once, right here, inside this constructor. Assigning `describe`
// after `bind` made that first projection read `undefined.getSnapshot()`
// — a TypeError thrown while the inject callback was registering the
// card, which aborted the registration and left the plugin with no
// settings section at all and nothing in the UI to say so.
this.describe = ctx.configForms.describe()
this.form = ctx.configForms.get(SETTINGS_ENTRY)
this.model = new SettingsFormModel(
this.form,
@@ -399,8 +409,6 @@ window.__ModuleLoader__.load({
})),
)
this.store = this.model.bind(() => this.projection())
/** The describe mirror that knows whether a secret is configured. */
this.describe = ctx.configForms.describe()
}
/** Everything the page renders, rebuilt whenever the form or a draft moves. */