Background
After #357, data.changed emits a bare server name and the browser useDataSync matches it against the iframe's bare data-app. This restores live refresh, but data.changed remains workspace-blind — the route is scope: "global" (SSE_ROUTES in src/api/events.ts) and the match is bare-server only.
Problem
The same app installed in two workspaces (e.g. synapse-db-query in workspace A and B) has the same bare data-app. A present_result in workspace A broadcasts globally and would postMessage-match a mounted workspace-B iframe too, causing a spurious refetch there.
In practice this is mostly latent today (only the focused workspace's placements are mounted in the DOM at once), and it matches the pre-Stage-2 contract — so it was correctly deferred out of #357. But it's the real root-cause-complete fix.
Proposed fix
Grow wsId into the data.changed payload and match on (wsId, bare server):
- Producer keeps the namespaced name, extracts the
wsId via parseNamespacedSourceName, emits { wsId, server (bare), tool }.
- Flip the
data.changed route from scope: "global" to scope: "workspace" (the SSE_ROUTES comment already flags: "Revisit when payload grows wsId").
- Browser: iframe carries its
wsId as a second data attribute; useDataSync matches on both.
tool.progress (bare source, no workspace prefix) needs a way to carry wsId too — resolve from the run's workspace context at emit time.
Found during #357.
Background
After #357,
data.changedemits a bareservername and the browseruseDataSyncmatches it against the iframe's baredata-app. This restores live refresh, butdata.changedremains workspace-blind — the route isscope: "global"(SSE_ROUTESinsrc/api/events.ts) and the match is bare-server only.Problem
The same app installed in two workspaces (e.g.
synapse-db-queryin workspace A and B) has the same baredata-app. Apresent_resultin workspace A broadcasts globally and would postMessage-match a mounted workspace-B iframe too, causing a spurious refetch there.In practice this is mostly latent today (only the focused workspace's placements are mounted in the DOM at once), and it matches the pre-Stage-2 contract — so it was correctly deferred out of #357. But it's the real root-cause-complete fix.
Proposed fix
Grow
wsIdinto thedata.changedpayload and match on(wsId, bare server):wsIdviaparseNamespacedSourceName, emits{ wsId, server (bare), tool }.data.changedroute fromscope: "global"toscope: "workspace"(theSSE_ROUTEScomment already flags: "Revisit when payload grows wsId").wsIdas a second data attribute;useDataSyncmatches on both.tool.progress(baresource, no workspace prefix) needs a way to carrywsIdtoo — resolve from the run's workspace context at emit time.Found during #357.