2 Commits
Author SHA1 Message Date
gitea-actions 811871e978 Release v0.1.4 [skip release] [skip actions] 2026-09-26 06:50:34 +00:00
NotExpectedYet fbf1da7452 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.
2026-09-26 07:43:31 +01:00
4 changed files with 23 additions and 3 deletions

No files matched your search

+7
View File
@@ -2,6 +2,13 @@
Every release, newest first, generated from the commits in each release. Every release, newest first, generated from the commits in each release.
## v0.1.4 — 2026-09-26
### Fixed
- register the settings card instead of throwing while building it
## v0.1.3 — 2026-09-26 ## v0.1.3 — 2026-09-26
### Fixed ### Fixed
+10 -2
View File
@@ -385,6 +385,16 @@ window.__ModuleLoader__.load({
*/ */
constructor(ctx) { constructor(ctx) {
this.ctx = 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.form = ctx.configForms.get(SETTINGS_ENTRY)
this.model = new SettingsFormModel( this.model = new SettingsFormModel(
this.form, this.form,
@@ -399,8 +409,6 @@ window.__ModuleLoader__.load({
})), })),
) )
this.store = this.model.bind(() => this.projection()) 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. */ /** Everything the page renders, rebuilt whenever the form or a draft moves. */
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "dsh-recall", "name": "dsh-recall",
"version": "0.1.3", "version": "0.1.4",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "Local-embedding code recall for DeepSeek Harness — returns ranked excerpts with line ranges instead of whole files, so the context window stops growing with every read.", "description": "Local-embedding code recall for DeepSeek Harness — returns ranked excerpts with line ranges instead of whole files, so the context window stops growing with every read.",
+5
View File
@@ -109,6 +109,11 @@ function makePrimitives(record) {
bind(project) { bind(project) {
this.project = project this.project = project
// Faithful to the framework: `bind` is not a subscription setter, it
// builds the snapshot store from ONE immediate projection. A stub that
// only stored the projection would let a controller read a field it has
// not assigned yet — which is exactly the bug this reproduces.
project()
return { getSnapshot: () => project(), subscribe: () => () => {} } return { getSnapshot: () => project(), subscribe: () => () => {} }
} }