fix(client): register the browser half with the module loader - #14
Open
womeimingzi11 wants to merge 2 commits into
Open
womeimingzi11 wants to merge 2 commits into
womeimingzi11 wants to merge 2 commits into
Conversation
`lib/client.js` shipped as verbatim `tsc` ESM output: top-level `import` /
`export` bindings and no `window.__ModuleLoader__.load({ id, factory })` call.
The host's client module system (`@deepseek-ai/dsh-client-modules`) is a lazy
CJS table whose only acceptance test is that registration, so the bundle
executed, registered nothing, and the Plugins page reported
client-modules: could not load "@perrylink/dsh-github": ... :
loaded without registering "@perrylink/dsh-github" via __ModuleLoader__.load
The host half was unaffected and kept its tools registered throughout.
- `scripts/build-client.mjs` rewrites the compiled bundle into the loader's
factory: `import ... from 'm'` becomes `require('m')`, each top-level `export`
declaration loses its keyword and gains an `exports.<name>` line, and the body
is wrapped. It fails loud on any shape it cannot transform and is a no-op on an
already-wrapped bundle, so both `build` and `prepare` call it. Original line
order is preserved and the shipped source map is shifted by the wrapper's line
count, so it stays aligned.
- `dsh.client.inject` gains the two modules the bundle actually requires
(`react`, `@deepseek-ai/dsh-client-ui-primitives`), neither of which was
declared.
- `scripts/verify-artifacts.mjs` executed only the host face; it now also runs
the shipped client bundle against a stub `__ModuleLoader__` and asserts
registration, materialization and the absence of top-level ESM.
- `test/client-bundle-contract.test.ts` asserts the same contract as a unit test.
Declares and tests the DSH 0.2 host line in the same change, because this fix is
what makes the browser half work there: the `@deepseek-ai/dsh-*` peer union and
`engines.dsh` gain `|| >=0.2.0-rc.1 <0.3.0-0`, `dshWorkshop.compatibility.
dshVersions` records `0.2.0-rc.2`, the five READMEs follow, and the Compat
workflow's profile job now runs both ends of the band instead of one pin.
Verified on `0.2.0-rc.2`: the batch bundle the Plugins page requests
(`/plugins/??...,@perrylink/dsh-github/client.js,...`) serves and executes to
seven registered modules with this row among them, the single-row fallback URL
registers the same row, and `pnpm test` reports 189 passed / 3 skipped.
The browser half took the page's `plugins.item` seat. That list is what the
Plugins page renders through `renderGroup("official", …)` — the Official group,
where the host's own settings plugins (agent-loop, shell, subagent, web-search)
mount their cards — so this community bundle was presented as an official plugin
instead of an installed one, while its bundle row sat in the Installed group
with no configuration of its own.
The card now takes this bundle row's seat, `plugins.row.config`, keyed
`<package>#<rowId>` — the key the page's own `rowConfigKey` helper defines. Both
seats hand the card the same props (`{ view, form }`; `submit` and the snapshot
hook come from the card's own `inject`), so the component is unchanged.
`plugins.bundle.config` is not the alternative: that seat is rendered with
`{ view }` only — no `form` — and this card reads `tokenRef` from the form.
- `PACKAGE_NAME` / `ROW_ID` / `ROW_CONFIG_KEY` are stated once so the seat key
and `cordis.patch.yml` cannot drift apart.
- `test/client-card-seat.test.ts` runs `apply` against a recording stub context
and asserts the bundle registers `plugins.row.config` under the key derived
from the patch, and never touches `plugins.item`.
Verified against the real host on `0.2.0-rc.2`: the bundle the server returns for
this row registers exactly `["plugins.row.config"]` with key
`@perrylink/dsh-github#dsh-github`, and carries no `plugins.item` at all.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptoms
Two defects in the browser half, both visible on a DSH
0.2.0-rc.2desktop profile.1. The Plugins page reported the plugin as failed to sync.
2. The configuration card appeared in the page's Official group, presented as an
official plugin rather than an installed one.
The host half was unaffected throughout: the bundle row mounts,
gh_*/pr_*/issue_*stay on the roster, and real GitHub reads answer.Root causes
1. The bundle never registered.
lib/client.jsis verbatimtscoutput — aplain ESM module with top-level
import/exportbindings and no registrationcall. The host's client module system (
@deepseek-ai/dsh-client-modules) is alazy CJS table:
So the bundle executes, registers nothing, and the loader rejects the row. The
build never produced the contract:
buildwastsc … && node scripts/fix-dts.mjswith no wrapping step, and
verify-artifacts.mjsonly ever executed the host face.lib/client.jsis identical to the committed artifact in this repository and tothe published tarball, so neither a reinstall nor a restart changes anything.
2. The card took the wrong configuration seat. It registered on
plugins.item, which the page renders throughrenderGroup("official", …)— the Official group, where the host's own settingsplugins (agent-loop, shell, subagent, web-search) mount their cards. The row
configuration seat is
plugins.row.config, keyed<package>#<rowId>.Both seats hand the card the same props (
{ view, form };submitand thesnapshot hook come from the card's own
inject), so this is a change of seat, notof card.
plugins.bundle.configis not the alternative: it is rendered with{ view }only — noform— and this card readstokenReffrom the form.Changes
scripts/build-client.mjs(new) rewrites the compiled bundle into theloader's factory:
import … from 'm'→require('m'), each top-levelexportdeclaration loses its keyword and gains an
exports.<name> = <name>line, andthe body is wrapped. It fails loud on any shape it cannot transform and is a
no-op on an already-wrapped bundle, so both
buildandpreparecall it.Original line order is preserved and
lib/client.js.mapis shifted by thewrapper's line count, so the shipped map stays aligned.
plugins.row.config, keyed by this bundle's own row.PACKAGE_NAME/ROW_ID/ROW_CONFIG_KEYare stated once so the seat key andcordis.patch.ymlcannot drift apart.dsh.client.injectgains the two modules the bundle actually requires(
react,@deepseek-ai/dsh-client-ui-primitives); neither was declared, sothe module graph did not guarantee they arrive first.
scripts/verify-artifacts.mjsnow also runs the shipped client bundleagainst a stub
__ModuleLoader__, asserts it registers exactly once under thepackage name, materializes its factory, and rejects top-level ESM.
test/client-bundle-contract.test.ts(new) asserts the module-loadercontract as a unit test;
test/client-card-seat.test.ts(new) runsapplyagainst a recording stub context and pins the seat and its key, deriving the row
id from
cordis.patch.ymlrather than restating it.0.2is declared and exercised, since these fixes are what make thebrowser half work there:
engines.dshand the@deepseek-ai/dsh-*peer uniongain
|| >=0.2.0-rc.1 <0.3.0-0,dshWorkshop.compatibility.dshVersionsrecords
0.2.0-rc.2, the five READMEs follow, and the Compat workflow'sprofilejob runs both ends of the band (0.1.7-rc.2,0.2.0-rc.2) insteadof a single pin.
Verification
0.2.0-rc.2— the strongest check, since it runs thebytes the browser receives. The batch bundle the Plugins page actually requests
(
/plugins/??…,@perrylink/dsh-github/client.js,…, the URL in the reportederror) serves and executes to seven registered modules with
@perrylink/dsh-githubamong them, materializingapplyandinject; thesingle-row fallback URL registers the same row, and running its
applyagainsta recording context registers exactly
["plugins.row.config"]with key@perrylink/dsh-github#dsh-githuband carries noplugins.itemat all.0.2.0-rc.2—/api/pluginInventory/listreports the rowfiberPhase: "active"with no failure diagnostic, and a realgh_repocallreturns repository data.
pnpm test: 21 files, 192 passed / 3 skipped.pnpm run build,verify:artifacts,check:readmes,lint,typecheck, andpnpm install --frozen-lockfileall green.Notes for review
lib/client.jsis a generated artifact; its diff is dominated by the factoryindentation applied to every line.
scripts/build-client.mjs,test/client-bundle-contract.test.tsandtest/client-card-seat.test.tsarethe readable part.
@deepseek-ai/[email protected], so the registration defect is inhow the artifact is built rather than a
0.2-only regression. It may be worthconfirming whether the committed bundle ever registered on the older line.
>=0.2.0-rc.1 <0.3.0-0, matching theshape the ecosystem already uses;
0.3stays out.