The Tauri desktop shell's answer to the last round was that status queries alone do not help them — their purpose is to consume the market's PANEL, which they render inside their own container alongside their own chrome. That is a smaller thing than the slot inversion I declined, and it is the fallback their issue had already proposed. `market.render(props?)` returns the market's panel, error boundary included, as a React element. Same page and same React instance: this package's client bundle resolves react through the host's module table, so the element mounts anywhere in that tree. The builder moved into `src/client/market-element.ts`, taking its dependencies as arguments rather than closing over the cordis context, for two reasons: - the settings section and `render()` must not drift. They pass different `preferredSubsectionId`s and the same everything else, and a mix-up would show up as a panel opening on the wrong tab in one of the two places, on somebody's machine; - wiring that only a running host can reveal is wiring a test cannot check. The spec asserts which translate function, locale, theme store and log exporter reach the panel, and that the recovery panel's log button calls the exporter it was handed. `ctx.provide` and `ctx.reflect.provide` are the same call — cordis's Service.provide delegates to the reflect layer — so the reporter's own idiom reaches this service. Documented in UPDATE-API-V1.md. Deliberately still not done: host-fillable slots inside the market. One host asking does not yet say what the second one would need.
7.1 KiB
Public plugin update API v1
Status: beta. The shape described here may still change between releases.
GET /dsh-market/api/v1/capabilitiesreports"stability": "beta"while that is true, and"stable"once it stops moving — read that field rather than assuming from thev1in the path.Nothing here is going away; what is not yet promised is that field names and response shapes will survive untouched. If you ship against it now, say so in an issue: a shape somebody depends on is a much stronger reason not to move it, and it is how this reaches
stable.
dshmarket exposes a small, versioned, same-origin JSON API for plugin-owned
update surfaces. It lets a plugin show its own update button without spawning a
package manager, copying the Market installation algorithm, or binding to the
Market UI's private response fields.
All responses carry:
{ "schema": "dsh-market/update-api/v1" }
Clients must discover the API before enabling mutation controls:
GET /dsh-market/api/v1/capabilities
The response names the Market version, profile, runtime (web or desktop),
supported features, restart owner and endpoint paths. A client must hide its
restart button when restart.supported is false. Desktop and supervised hosts
normally delegate restart to their owning shell or operator.
Count what can be updated
GET /dsh-market/api/v1/updates/summary
{
"schema": "dsh-market/update-api/v1",
"checked": 12,
"updatable": 3,
"packages": [
{ "name": "dsh-mcp-connector", "source": "npm", "installedVersion": "1.1.0", "latestVersion": "1.2.0" }
]
}
checked is the denominator — how many installed plugins the answer looked
at. Read it, or a badge cannot tell "nothing to update" from "nothing was
looked at". The two are different answers and only one of them means the
profile is up to date.
packages carries the same objects the single-package endpoint below
returns, and lists only plugins that can be updated. A badge needs
updatable; a panel needs the rows; neither should have to filter the whole
profile itself.
GET /dsh-market/api/v1/capabilities reports this endpoint at
endpoints.updatesSummary and sets features.updatesSummary. Check that
rather than probing for the path.
Check one installed package
GET /dsh-market/api/v1/updates?name=dsh-mcp-connector&force=1
The response includes the installed version, target version, source kind and
whether the target is a forward update. Omitting force=1 allows the Market's
short update-check cache.
Start and observe an update
Mutation requests require the same-origin protection used by the Market UI.
POST /dsh-market/api/v1/updates
Content-Type: application/json
{ "packageName": "dsh-mcp-connector" }
An accepted request returns HTTP 202 immediately with an operationId.
Passing "force": true opts this one operation out of the registry release-age
wait; clients should offer it only after the normal operation reports
RELEASE_TOO_FRESH or VERSION_UNCHANGED.
Poll the operation by id:
GET /dsh-market/api/v1/operations?operationId=<id>
States are queued, running, succeeded, failed, cancelled and
rolled-back. Running operations include structured package progress when
pnpm provides it. Terminal operations include:
- the before and actually installed versions;
refreshRequiredandrestartRequiredoutcomes;- a stable failure code, bounded user-facing message and retryability;
- whether a compatibility rollback is currently available.
For npm updates, Market verifies the package version that pnpm actually placed
on disk before reporting success. This prevents pnpm's release-age policy or a
lagging registry mirror from silently turning @latest into an older or
unexpected build. A mismatch is restored from the pre-update recovery point
and reported as one of these stable failures:
DOWNGRADE_DETECTED: the resolved version is older than the version present before the operation; not retryable without a new target.RESOLVED_VERSION_MISMATCH: the resolved version is BELOW the registry target checked immediately before installation; retryable after registry or mirror convergence. A version above that target is accepted —latestcan move forward while the install is still running.
Provider clients should still compare beforeVersion, the target they showed
to the user, and installedVersion before offering restart. That independent
check protects clients connected to another compatible provider implementation.
Up to 50 operation records live in the current Host process. A boot id is embedded in every operation id, so a client never mistakes a stale browser record for a task belonging to the replacement process.
Roll back
POST /dsh-market/api/v1/rollback
Content-Type: application/json
{ "operationId": "<id>" }
Rollback is intentionally capability- and operation-scoped. It is available only when the Market's compatibility verification retained a recovery point; a later mutation may supersede it. The normalized result is written back to the same operation record.
Restart
POST /dsh-market/api/v1/restart
Content-Type: application/json
{}
This preserves the Market's stricter restart guard: direct loopback, same-origin, no forwarding headers, no package mutation in progress, and a Host whose lifecycle is not owned by Desktop or a supervisor. Clients must feature detect it; they must not invent an alternative process-control path.
Rendering the market's panel elsewhere (client-side)
A host shell that wants the market inside its own container — rather than in the settings page — reads the client service this package publishes:
const market = ctx.reflect.get('market')
const element = market.render({ preferredSubsectionId: 'installed' })
render() returns the market's own panel wrapped in its error boundary, as a
React element. Same page, same React instance: this package's client bundle
resolves react through the host's module table, so the element mounts
anywhere in that tree.
| member | |
|---|---|
version |
1 |
render(props?) |
the panel element; preferredSubsectionId is optional |
setSettingsVisible(visible) |
register or retract the market's own settings.section entry |
settingsVisible() |
whether that entry is registered right now |
ctx.provide(name, value) and ctx.reflect.provide(name, value) are the
same call — cordis's Service.provide delegates to the reflect layer — so
either idiom reaches this service.
What render() is not: a way to rearrange the market. It hands over the
whole panel, chrome included. Cutting the market into host-fillable regions
is a different design, and one host asking is not yet evidence that it fits
anyone else.
Compatibility policy
- New optional response fields may be added within v1.
- Existing v1 fields and meanings are not repurposed.
- A breaking change uses a new path and schema version.
- When discovery is unavailable, plugin UIs should fall back to opening the
Market rather than calling legacy
/dsh-market/*mutation routes directly.