From 040c69b57de801da0eb8ad62b6e51ade8f42c0b3 Mon Sep 17 00:00:00 2001 From: NotExpectedYet Date: Sat, 26 Sep 2026 06:37:18 +0100 Subject: [PATCH] fix!: restore the settings page against the harness 0.1.7 plugin contracts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The configuration page registered into seats that 0.1.7 removed, so it could not appear at all — and the schema carried none of the metadata the new settings document requires. Host half. `ctx.settings.installSection` no longer exists: a plugin exports a `Config` schema, the Harness keys one form per composed entry by that row's `id`, and `ctx.settings.describe` skips an entry with no `volatile()` field outright. Every field is now volatile, which is also what makes a saved edit apply to the running instance instead of remounting it — so `apply` reads the section through `settingsSnapshot`, which unwraps the live references a volatile field arrives as. Writes go through `ctx.settings.update` fenced by the revision `describe` reports. The `.min`/`.max` bounds the schema can state are stated there, so the form refuses an impossible draft in front of the person typing it; `validateSettings` keeps the cross-field rules no single field can express, and now runs on the section a call is about to act on rather than at write time, because 0.1.7 has no write-time validator hook. Browser half. `settingsScope` and the `settings.plugin.item` slot are gone. The page renders through `plugins.row.config`, keyed `#` (`dsh-recall#dsh-recall`), which is what puts a Configure control on the plugin's row in the Plugins panel. It uses the framework's own staged form — `SettingsFormModel` over `ctx.configForms`, drawn by `SettingsForm` and `SettingsValueField`/`SettingsSecretField` — instead of reproducing card chrome by hand, so the unsaved, overridden, invalid, and save states come from the same code as every other plugin's page. All 33 settings are editable now; the old card exposed 23. The right-sidebar tab also needed the stage-one tab TYPE (`ctx.sidebarRightTabs.register`) that its keyed body slot is dispatched under, so a tab can finally be opened. `--dsh-border`, defined nowhere, becomes the product's `--dsw-alias-border-l2`. Both halves are now pinned by tests: every field is volatile, the field list and the schema cannot drift, the page claims the key the patch declares, and a blank API key is never written — the secret control reports configured-and- absent instead. Verified against the harness rather than only in unit tests: `--dump-config- schema` emits the volatile and secret metadata, `apply` registers `recall` on the real tool registry, `settings.describe({redactSecrets:true})` serves `dsh-recall` with 33 fields and the key reported set-but-redacted, and a scratch profile boots the web app with the plugin composed and its bundle served byte-for-byte as this checkout has it. --- README.md | 56 ++- client/client.js | 820 +++++++++++++++++++++++++------------------- index.js | 266 ++++++++++---- package.json | 2 +- test/client.test.js | 641 +++++++++++++++++++++++----------- test/config.test.js | 99 +++++- test/plugin.test.js | 136 +++++++- 7 files changed, 1352 insertions(+), 668 deletions(-) diff --git a/README.md b/README.md index 92ecb1d..22da611 100644 --- a/README.md +++ b/README.md @@ -65,17 +65,29 @@ a fact: embeddingApiBase: http://10.50.1.2:11434 ``` -Or edit every setting from the UI. The plugin ships **two halves**, and the settings -page draws a card only when it has both: +Or edit every setting from the UI: **Plugins → dsh-recall → Configure**. The +plugin ships **two halves**, and the page appears only when both are present and +the deployment serves the namespace: -- the **host half** (`index.js`) serves a `dsh-recall` settings namespace; -- the **browser half** (`client/client.js`) claims that same namespace with a - collapsible card under **Settings → Plugins → Plugin configuration**. +- the **host half** (`index.js`) exports a `Config` schema whose every field is + `.volatile()`. That schema *is* the settings page's description — the Harness + keys one form per profile entry by that row's `id` (`dsh-recall` in + `cordis.patch.yml`), and it skips an entry with no volatile field entirely, so + a schema without those markers has no settings surface at all rather than an + empty one; +- the **browser half** (`client/client.js`) renders that form into the Plugins + panel's per-row configuration seat, `plugins.row.config`, keyed + `#` — here `dsh-recall#dsh-recall`. Registering it is + what adds the **Configure** control to the plugin's row. -A served namespace with no card renders nothing at all, and a card whose namespace -is not served is never dispatched — so both are required. A restart is needed for a -newly installed browser half to appear, because client bundle bytes are snapshotted -when the profile activates. +A served namespace with no page draws no control, and a page whose namespace is +not served is deliberately not registered — so both are required. A restart is +needed for a newly installed browser half to appear, because client bundle bytes +are snapshotted when the profile activates. + +Editing in the UI writes to the profile's own patch layer through the settings +document, so the value survives a restart and stays visible in +`cordis.patch.yml`. Every value is also checkable from the tool, without touching a file: @@ -83,9 +95,9 @@ Every value is also checkable from the tool, without touching a file: recall action=configure config={"embeddingApiBase":"http://10.50.1.2:11434"} ``` -The API key is declared `role('secret')` on the host, so it is stripped from every -settings response — the card can show that a key is set without ever receiving it, -and leaving it blank on save means "unchanged" rather than "clear". +The API key is declared `role('secret')`, so it is stripped from every settings +response: the page can show that a key is set without ever receiving it, and a +blank draft writes nothing — leaving it alone means "unchanged", never "clear". Pull the model once: @@ -102,7 +114,7 @@ ollama pull unclemusclez/jina-embeddings-v2-base-code | `search` *(default)* | answers `query`, warming a bounded slice of the index first | | `index` | builds or refreshes the whole index; **background job by default** | | `status` | index and endpoint state, touching nothing | -| `configure` | writes settings through the settings namespace | +| `configure` | writes settings through the settings document | Parameters: `query`, `action`, `limit`, `path` (restrict to a subdirectory), `run_in_background`. @@ -363,10 +375,16 @@ exactly that during development. `test/config.test.js` covers the load-time contract. Cordis rejects a `Config` before `apply` ever runs, so a broken schema cannot be reported from inside the plugin — the profile simply refuses to boot. Every other test in the suite calls -`apply` directly and would not notice. +`apply` directly and would not notice. It also pins the second half of that +contract: that every field is `volatile()`, because that marker is what makes +the Harness serve a settings page at all, and its absence is invisible from +inside the process. -`test/client.test.js` runs the browser half in Node by stubbing `window` and -`require('react')`. That is worth more than it sounds: the two failure modes most -likely to reach you silently are structural — a root element that is not an `
  • `, -and a card that does not start collapsed — and both are invisible until someone -opens Settings. It also pins that an untouched API key is never written or unset. +`test/client.test.js` runs the browser half in Node by stubbing `window`, +`require('react')`, and the settings-form primitives. That is worth more than it +sounds: the failure modes most likely to reach you silently are structural — a +page registered into a slot nobody declares (which is what the 0.1.5 seat, +`settings.plugin.item`, became), a right-sidebar body with no tab type behind it, +and a field list that has drifted from the schema — and all three are invisible +until someone opens Plugins. It also pins that an untouched API key is never +written, and that the field list and `Config` name the same settings. diff --git a/client/client.js b/client/client.js index 37c9fee..1675c8d 100644 --- a/client/client.js +++ b/client/client.js @@ -1,24 +1,37 @@ /** - * dsh-recall — browser half: the card in Settings → Plugins → Plugin configuration. + * dsh-recall — browser half. * - * The Host half registers a `dsh-recall` settings namespace. This file registers - * a card under that exact namespace key. Both are required: the tab pairs the - * namespaces the Host serves with the cards the browser claims, so a served - * namespace with no card draws nothing at all, and a card whose namespace is not - * served is never dispatched. + * Three contributions, each into a seat the Harness 0.1.7 composition declares: * - * The chrome follows the framework's `PluginCard`: the root is an `
  • ` (the tab - * renders every card into a `
      `), the header is its own `