Three test-only spots had baked in the machine this repository was developed
on, and the repository is publicly readable, so they shipped that machine's
shape to anyone who cloned it:
* the end-to-end embedding backend was pinned to a private LAN address;
it is now read from DSH_SLOPO_E2E_API_BASE, defaulting to Ollama's own
loopback address, so the machine running the suite supplies its own;
* the real-slopo test resolved discovery from a hardcoded root home, which
meant it could only ever run for one user — it resolves from the running
user's home now, which is also the fix for everyone else;
* the harness-slot contract tests read the global npm tree by absolute
path; they go through this repository's own node_modules symlink, which
is already relative to the checkout.
No user-facing behaviour changes and the published package never contained
the test files, so this push publishes nothing. The default configuration was
already free of machine-specific values: every address, path and key defaults
to empty and is resolved per user at runtime.
[skip release]
0.1.7-rc.2 moved four things this bundle was built on, and every one of them
failed quietly rather than loudly, so the plugin looked installed and did
nothing:
* `ctx.settings.installSection(...)` is gone. The call threw into a
`try/catch` that only logged a warning, so the settings namespace was never
registered and `configure` had nowhere to persist. An entry's exported
Config IS the form now; the namespace is the Loader entry id the bundle's
patch declares, and `ctx.settings.configure({ auto: false })` is what claims
that entry's own page.
* `settingsScope` and the `settings.plugin.item` slot are gone. The card
registered into both, so it never rendered — the tab draws the intersection
of served namespaces and registered cards, and that intersection was empty.
It now registers into `plugins.bundle.config` keyed by the bundle's package
name, over the `configForms` service, and uses the framework's own form
chrome (`SettingsFormModel` / `SettingsForm` / `SettingsValueField` /
`SettingsSecretField`) instead of the hand-rolled equivalent. A save is one
revision-fenced write.
* `ctx.shell.run(spec)` is gone. Every action that runs a command threw
`shell.run is not a function`; the seam is now
`await ctx.shell.execute(ctx.shell.resolve(request)).result()`.
* A job's `owner` is a session id, not the agent object. The wrong owner
registered a job no session could collect or cancel, and the producer's
`done` resolved `output` where the registry reads `result`, so a finished
job handed back nothing.
Two conformance gaps went with them: a Config field that is not marked
`volatile()` is invisible to every settings UI — the form projection keeps only
volatile fields, and an entry with no form is dropped from `settings.describe`
entirely — and `embeddingApiKey` was a plain field, so the key rode every
settings read back to the browser. It is `role('secret')` now: the browser is
told whether a key is stored and never receives the key.
Also declares the `@deepseek-ai/dsh-*` peerDependencies the compatibility gate
reads, so an incompatible runtime refuses this plugin at startup instead of
half-loading it.
Verified against a real 0.1.7-rc.2 boot: `settings.describe` serves the entry
with `autoGenerate: false`, the key redacted and reported only as `set: true`,
and no startup diagnostics. The host mock in the tests now has no `run` and no
`installSection`, so those seams are contract-tested rather than assumed, and
the browser tests read the installed harness's own slot catalog and package
exports — a rename fails a test instead of quietly emptying a page.
It rendered as a flat, always-open block with no header. The framework's card
chrome is a disclosure: the tab renders every card into a <ul>, each card's root
is an <li>, its header is its own <button> that discloses the controls in place,
it starts collapsed, the header marks it when it holds unsaved edits, and a save
that lands folds it back up.
Three things were wrong and only one of them was cosmetic:
* the root was a <div> inside the tab's <ul>, so the list and card styling
never reached it
* there was no header button, so no disclosure and no aria-expanded
* nothing marked unsaved edits, and nothing collapsed after a save
`PluginCard` is not importable — it lives in that package's client bundle, and
the client purity gate forbids cross-plugin value imports — so the behaviour is
reproduced rather than borrowed, with inline styles for the same reason.
The collapse-on-save originally lived in an effect watching a `saving` flag, which
is how the shipped card does it because its form state comes from a controller.
This card awaits its own writes, so it already knows the save landed; collapsing
directly in the success path is simpler and is what the tests can actually reach.
A failed save still keeps the card open with its drafts.
Tests cover the disclosure itself: the root element, collapsed-by-default, the
header's aria-expanded and label, that staged edits survive collapsing, and that
a landed save folds the card up.
The gate tests use a canned shell runner, and two of them build a real
slopo-shaped SQLite file to prove the warm-database hole is closed: the summary
line says zero new units while the database says 8,607 unembedded, and the gate
must believe the database.
Those fixtures insert in one transaction. Row-by-row they were 8,607 individual
fsyncs, which took two minutes and timed the suite out - worth knowing before
writing another one.
The probe tests use an injected fetch, so they cover the shapes that matter
without a network: a matching width, a mismatch, an HTTP error, an unreachable
host, a timeout, and the key being sent only when there is one.
The client tests evaluate the bundle the way the real module loader does —
inside `window.__ModuleLoader__.load` — with a fake `require` that answers
`react` and throws on anything else. That throw is the point: it is the check
that the card stays inside the platform's seed table, which is the constraint a
hand-written client half is most likely to break. The card is then rendered by
expanding its element tree, and its handlers are driven directly to prove that a
changed field reaches `set`, a cleared field reaches `unset`, a number field is
sent as a number, a list round-trips through comma-separated text, and a
read-only namespace disables everything.
The status test moves with the behaviour: status may now run a read-only
`--version`, so the assertion is that it executes nothing else.