fix(playground): subscribe the Pull lab to client-published messages - #557
Merged
Merged
Conversation
…sent Check 9 reported `fail` with "accepted but nothing came back" whenever no echo arrived. That sentence described the pre-3.0.0 client, where `sendMessage()` resolved before the channel lookup had answered and an unsent message was indistinguishable from an undelivered one. Since #555 it cannot be: the call returns only after the batch has been encoded by the codec under test and the connector has accepted the frame, and every earlier failure throws with a code. So the page was reporting the one outcome that can no longer happen, and `encodePathExercised` said `false` on runs where the encoder had demonstrably run. Measured on a live push-server v4 portal, which is what surfaced this: the run produced no rejection at all. `PullClient` seeds the channel cache from `config.publicChannels`, returned there by `pull.config.get` — an ordinary application call — so `pull.channel.public.list` was never requested and had nothing to refuse. The batch was encoded, the socket took it, and the server did not redeliver it. A missing echo is therefore a push-server delivery fact, not a codec one: it is now a `warn` whose text says what did run, and `encodePathExercised` is tracked from the send itself rather than inferred from the verdict. The channel-manager JSDoc and the migration note said the rejection was the permanent answer for an application; both now cover the cached-channel case as well. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F22e2ft66y7nuBJjzdThBr
Check 9's echo was unroutable by construction, so the check timed out on every portal it was ever run against and the page blamed the push server. `subscribe()` defaults `type` to `SubscriptionType.Server`, and the lab took that default. `broadcastMessage` routes on the frame's own `extra.sender.type`: a message the BACK END published — which is how checks 4-8 send, through `pull.application.event.add` — arrives as `Server`, while one a CLIENT published through `sendMessage()` arrives as `Client` and is emitted to a different subscriber set entirely. `sendMessage()` is the only way to reach the encoder, so check 9 was the one check whose reply could never arrive. The lab now subscribes to both. That also corrects the diagnosis this branch was opened with. The previous commit read "no exception, no echo" as proof that the server had declined to redeliver an accepted frame. It was not proof of anything: the lab could not have observed the echo either way. What the run does establish is narrower and still worth having — the channel lookup was skipped because the recipient was already cached, so the batch was encoded and the socket took the frame. A missing echo does not acquit the encoder: the push server drops a frame it cannot parse or address in silence, which looks identical. Also in the lab: - check 9 now skips on JSON-RPC portals. Its only gate was `isPublishingEnabled()`, which is `version > 3` and therefore also true on push-server 5+, where `sendMessage()` never reaches either codec. The check would have reported an encode that did not happen. - `encodePathExercised` resets per run, and is set on a `JSSDK_PULL_SEND_REFUSED` failure too, since that is thrown after the encoder has already run. In the SDK, one hole behind the same claim: both JSON-RPC publish paths went through `this._jsonRpcAdapter?.…`, which resolves `undefined` before `init()` has assigned the adapter — nothing encoded, no frame, no error, which is the outcome 3.0.0 exists to remove. `sendMessageBatch` was worse: the optional chain fed `JSON.stringify(undefined)` to the connector, putting the string "undefined" on the wire and resolving `true`. Both now reject with `JSSDK_PULL_SEND_REFUSED`. Documentation carried the overstatement this branch set out to fix in five more places than it corrected — `sendMessage()`'s own JSDoc, the public Pull page, the error-codes row, the skill, and the playground README, which still described one outcome for a check that now has three. The contributing guide claimed the encode half had no route to a portal from an application; it does, and the guide's exit criterion is now split into a decode half that is met and an encode half that is not, because no encoded frame has been read back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F22e2ft66y7nuBJjzdThBr
This was referenced Sep 23, 2026
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.
The bug
/pull-labcheck 9 timed out on every portal it was ever run against, and the page reported that the push server had not redelivered the frame. It had not been asked to.subscribe()defaultstypetoSubscriptionType.Server(client.ts:432), and the lab took that default.broadcastMessageroutes on the frame's ownextra.sender.type: back-end-published messages (pull.application.event.add— checks 4–8) arrive asServer; client-published ones (sendMessage()— check 9, the only route to the encoder) arrive asClientand are emitted to a different subscriber set (client.ts:1510-1533vs:1571-1580).So check 9's own echo could never reach the page. The lab now subscribes to both types.
What this corrects in the first commit
4cbdefcread "no exception, no echo" as evidence that the server had declined to redeliver an accepted frame. It was not evidence of anything — the lab could not have observed the echo either way, so that commit'swarntext and its documentation additions attributed fault to the push server on a measurement that cannot support it.What the run does establish, and this survives:
publicChannels, which carries the current user's own channel, and check 9 sends to the current user;What it does not establish: that the bytes were correct. A frame the push server cannot parse or address is dropped in silence, so a genuine encode bug produces the identical observation. The
warntext now says exactly that and nothing more.Also in the lab
isPublishingEnabled(), which isversion > 3and so also true on push-server 5+, wheresendMessage()branches into the JSON-RPC adapter and neither codec runs. The check would have reported an encode that did not happen.encodePathExercisedresets per run — it was a sticky ref reporting a previous run's fact — and is now also set on aJSSDK_PULL_SEND_REFUSEDfailure, which is thrown after the encoder has run.In the SDK
Both JSON-RPC publish paths went through
this._jsonRpcAdapter?.…, which resolvesundefinedbeforeinit()assigns the adapter: nothing encoded, no frame, no error — the outcome 3.0.0 exists to remove, surviving on the branch only push-server 5+ takes.sendMessageBatchwas worse: the optional chain fedJSON.stringify(undefined)to the connector, putting the string"undefined"on the wire and resolvingtrue. Both now reject withJSSDK_PULL_SEND_REFUSED.Documentation
The overstatement this branch opened to fix ("in an application
sendMessage()always rejects") was in five more places than the first commit corrected:sendMessage()'s JSDoc,60.pull.md, the96.error-codes.mdrow,skills/b24jssdk-helpers/SKILL.md, and the playground README — which still described one outcome for a check that now has three (skip/fail/warn). All corrected, with the guidance ("publish from the back end") left intact, since only the stated reason was wrong..github/contributing/pull-protobuf.mdclaimed the encode half has no route to a portal from an application. It does. Its exit criterion is now split:ResponseBatchdecoded by both codecs: met;The fixture's
knownGapsis corrected to match.Known gaps, stated rather than hidden
sendMessage()bypasses theisPublishingEnabled()guard, soJSSDK_PULL_PUBLISHING_DISABLEDis unreachable on push-server 5+. Left alone deliberately: making it consistent changes behaviour on a portal type not available to test here.createMessageReceiversthrows a bareErrorwith no code (client.ts:1802), reachable whengetPublicIdsresolves a partial answer. Pre-existing and out of scope for this PR.Verification
typecheck(jssdk + nuxt playground),eslinton all changed sources,pnpm lint:md,docs-lint --strict, and the Pull suites (12 files, 91 tests) all pass locally. The routing claim was verified directly againstclient.ts:432,:1510-1533and:1571-1580.🤖 Generated with Claude Code
https://claude.ai/code/session_01F22e2ft66y7nuBJjzdThBr