diff --git a/README.md b/README.md index f9cc7c0..2b6432c 100644 --- a/README.md +++ b/README.md @@ -81,9 +81,11 @@ a fact: embeddingApiBase: http://127.0.0.1:11434 # replace with your endpoint ``` -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: +Or edit every setting from the UI: **Plugins** in the sidebar → the +**dsh-recall** card → its settings are on that card, below the description. +(Not Settings → *Built-in plugins*: that section is a read-only inventory of +what the deployment ships.) The plugin ships **two halves**, and the section +appears only when both are present and the deployment serves the namespace: - the **host half** (`index.js`) exports a `Config` schema whose every field is `.volatile()`. That schema *is* the settings page's description — the Harness @@ -92,11 +94,16 @@ the deployment serves the namespace: 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. + panel's per-bundle configuration seat, `plugins.bundle.config`, keyed by the + bundle's package name. Registering it is what draws the section; without it + there is no settings UI however complete the schema is. -A served namespace with no page draws no control, and a page whose namespace is +The sibling seat `plugins.row.config` — keyed `#` — draws one +row's configuration on that row's own page. That is the right home for one row +of a bundle that declares several; this bundle declares one, so its settings +belong on the card. + +A served namespace with no page draws nothing, 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. diff --git a/client/client.js b/client/client.js index 1675c8d..32e0a2c 100644 --- a/client/client.js +++ b/client/client.js @@ -12,10 +12,11 @@ * binding `settingsScope` and registering into a `settings.plugin.item` slot. * Neither exists in 0.1.7. The generated settings document is keyed by the * plugin's own profile entry id, and a plugin that wants a page renders it as a - * contribution to the Plugins panel: `plugins.row.config`, keyed - * `#`. That key is what puts a **configure** control on - * the plugin's row — without a contribution there is no control and no page, - * however complete the schema is. + * contribution to the Plugins panel — `plugins.bundle.config`, keyed by the + * bundle's package name, which the page draws as a section on the plugin's own + * card. Without a contribution there is no section, however complete the schema + * is; the sibling seat `plugins.row.config` would instead put it one click + * deeper, on a single row's page. * * **The page is the framework's form, not a form of our own.** The card used to * reproduce `PluginCard`'s chrome by hand — the `
  • ` root, the disclosure @@ -70,13 +71,17 @@ window.__ModuleLoader__.load({ /** * The seat this plugin's configuration page renders in. * - * Keyed `#`, both as the profile's patch - * declares them. For this bundle the two coincide, which is a coincidence - * and not a rule — the constants are kept separate so that stays visible. + * `plugins.bundle.config`, keyed by the BUNDLE's package name: the Plugins + * page draws that contribution as a section on the plugin's own card, + * between its description and its rows. That is where someone opening + * "dsh-recall" expects its settings — the alternative seat, + * `plugins.row.config` (keyed `#`), draws them on a page + * one click deeper, behind the row's title button, which is the right home + * for one row of a bundle that declares several. This bundle declares one + * row, so the card is the honest place. */ const BUNDLE_PACKAGE = 'dsh-recall' - const BUNDLE_ROW = 'dsh-recall' - const ROW_CONFIG_KEY = `${BUNDLE_PACKAGE}#${BUNDLE_ROW}` + const BUNDLE_CONFIG_KEY = BUNDLE_PACKAGE const TITLE = 'Recall' const DESCRIPTION = @@ -486,10 +491,12 @@ window.__ModuleLoader__.load({ /** * The plugin's configuration page. * - * The Plugins page asks a configuration entry twice: once for a one-liner to - * stand in for a missing description (`summary`), and once for the body of - * the plugin's own page (`page`). Only the second draws a save control — - * and only one save writes, because the form discards its drafts on unmount. + * The seat asks for `page`, the form with its save control; the `summary` + * branch answers the other shape a configuration entry can be asked for — + * the one-liner standing in for a missing description — because a component + * registered under a seat that only ever asks for `page` is also the natural + * thing to reuse where that shape arrives. Only a save writes, and the form + * discards its drafts on unmount. */ function RecallSettingsCard(props) { if (props.view === 'summary') return DESCRIPTION @@ -705,11 +712,11 @@ window.__ModuleLoader__.load({ scoped.effect( () => scoped.configForms.whileServed([SETTINGS_ENTRY], () => - scoped.slots.inject('plugins.row.config', () => + scoped.slots.inject('plugins.bundle.config', () => scoped.slots.register( { - name: 'plugins.row.config', - key: ROW_CONFIG_KEY, + name: 'plugins.bundle.config', + key: BUNDLE_CONFIG_KEY, inject: () => card.inject(), }, RecallSettingsCard, diff --git a/test/client.test.js b/test/client.test.js index 58bb561..63ea60d 100644 --- a/test/client.test.js +++ b/test/client.test.js @@ -7,10 +7,13 @@ * leaving the page to be eyeballed in a running app, because the failures this * file exists to catch are structural and invisible until someone opens Plugins: * - * - the configuration page is registered into `plugins.row.config` under - * `#`. The 0.1.5 seat (`settings.plugin.item`, bound - * through `settingsScope`) does not exist in 0.1.7, so a page left in it - * registers against a slot nobody declares and simply never appears. + * - the configuration page is registered into `plugins.bundle.config` under + * the bundle's package name, which is the seat the Plugins page draws as a + * section on the plugin's own card. The 0.1.5 seat (`settings.plugin.item`, + * bound through `settingsScope`) does not exist in 0.1.7, so a page left in + * it registers against a slot nobody declares and simply never appears; the + * sibling seat `plugins.row.config` renders one click deeper instead, on a + * single row's page, which is not where someone opening the card looks. * - the right-sidebar tab needs a TYPE registered before its body means * anything: the keyed body slot is dispatched under the definition's `id`. * - the field list here and the `Config` schema in index.js must name the same @@ -322,7 +325,7 @@ function mount(options = {}) { * does, so the component under test sees the props a real render would give it. */ const card = (view) => { - const item = byName.get('plugins.row.config') + const item = byName.get('plugins.bundle.config') assert.ok(item !== undefined, 'no settings page was registered') const props = { ...item.meta.inject(), view } delete props.hooks @@ -364,13 +367,15 @@ test('the client module claims the slot registry in its inject list', () => { assert.deepEqual(module.inject, ['slots']) }) -test('the configuration page claims the 0.1.7 seat, keyed as the patch declares it', () => { +test('the configuration page claims the seat the plugin card draws', () => { const { registered } = mount() - const page = registered.find((item) => item.meta.name === 'plugins.row.config') - assert.ok(page !== undefined, 'the page must register into plugins.row.config') - // `#`: the row id is the one this bundle's own - // cordis.patch.yml declares, which is what the Plugins page dispatches by. - assert.equal(page.meta.key, 'dsh-recall#dsh-recall') + const page = registered.find((item) => item.meta.name === 'plugins.bundle.config') + assert.ok(page !== undefined, 'the page must register into plugins.bundle.config') + // Keyed by the BUNDLE's package name, which is what the Plugins page + // dispatches this seat with (`entryKey: pkg.name`). The sibling seat, + // `plugins.row.config`, would be keyed `#` and would render + // one click deeper, on the row's own page. + assert.equal(page.meta.key, 'dsh-recall') assert.equal(typeof page.meta.inject, 'function') }) @@ -385,7 +390,7 @@ test('no page is registered for a namespace the Host does not serve', () => { // onto fields nothing would accept. const { registered } = mount({ served: ['some-other-plugin'] }) assert.equal( - registered.some((item) => item.meta.name === 'plugins.row.config'), + registered.some((item) => item.meta.name === 'plugins.bundle.config'), false, ) }) @@ -431,7 +436,7 @@ test('the browser half contributes three seats, none of them the retired 0.1.5 o const { registered } = mount() assert.deepEqual(registered.map((item) => item.meta.name).sort(), [ 'conversation.session.header.corner', - 'plugins.row.config', + 'plugins.bundle.config', 'sidebar.right.pane.tab', ]) })