Summary
Auditing the embed postMessage API surface, most of it is sound — all 23 InboundCommandType entries have a matching case in handler.ts with no mismatch in either direction, 20 of them reach a real actuator, and GET_SCREENSHOT returns an honest NOT_IMPLEMENTED rather than pretending.
Four things do not work, and each one signals success while doing nothing. Verified on main (b3a4d30), most by execution.
1. SET_CAMERA never moves the camera
apps/viewer-embed/src/bridge/handler.ts:401-406 extracts {azimuth, elevation} and calls setCameraRotation. apps/viewer/src/store/slices/cameraSlice.ts:44 does set({cameraRotation}) — and the chain ends there.
Proven by execution: driving setCameraRotation against a store built from the real slice, with a recording proxy over cameraCallbacks, invokes zero callbacks. The only absolute-orientation actuator, applyViewpoint (apps/viewer/src/store/types.ts:339-406), takes position/target vectors and is never called from the handler. Everything azimuth/elevation-shaped on CameraCallbacks is a relative stepper (rotateLeft/rotateRight/orbit). The payload's documented zoom? field (packages/embed-protocol/src/index.ts:96) is dropped silently too.
2. CAMERA_CHANGED never fires for real navigation
cameraRotation is written only by setCameraRotation, and real camera motion deliberately bypasses store state — cameraSlice.ts:57-64, "Don't update store state during real-time updates", with the live per-frame path going through updateCameraRotationRealtime to a separate callback.
So the outbound event at EmbedViewer.tsx:241-251 can only fire by echoing back values a host just sent. User navigation — mouse drag, keyboard, ViewCube — emits nothing.
Combined with #1, a host sending SET_CAMERA receives a requestId ack and a CAMERA_CHANGED echo of its own values — every success signal — while the view never moves.
3. RESET_COLORS cannot undo SET_COLORS
SET_COLORS reaches dataSlice.ts:279 updateMeshColors, which writes pendingMeshColorUpdates and bakes colour into geometryResult.meshes[].color. RESET_COLORS reaches dataSlice.ts:309 clearPendingColorUpdates, which clears pendingColorUpdates — a different channel, used by the lens/IDS/SDK overlays.
Proven by execution: calling the real updateMeshColors then the real clearPendingColorUpdates leaves both the baked mesh colour and pendingMeshColorUpdates untouched.
4. ENTITY_HOVERED is never emitted
Declared at packages/embed-protocol/src/index.ts:130,143, with listener plumbing and tests in packages/embed-sdk. There are zero emitEvent('ENTITY_HOVERED', ...) call sites in apps/viewer-embed.
The SDK tests pass because they call harness.emit('ENTITY_HOVERED', ...) directly — they prove the SDK dispatches an event the viewer never sends.
Also: eight URL parameters are parsed and never applied
apps/viewer-embed/src/bridge/urlParams.ts parses 14 parameters; 6 are applied. Dead: controls, autoLoad, hideAxis, hideScale, select, isolate, hideTypes, camera.
autoLoad is worse than inert — the autoload effect (EmbedViewer.tsx:134-159) has no gate at all, so ?autoLoad=false loads the model anyway. urlParams.test.ts:119-127 pins the parsing with four assertions, which is why this held: the parser is tested, the application of the parsed value is not.
camera is dead for the same root cause as #1 — nothing exists to hand it to.
Status
We have fixes on a branch for everything with unambiguous semantics — the colour reset (backing up pre-override colours so reset genuinely restores), the autoLoad gate, five of the URL params, and ENTITY_HOVERED (which turned out not to need new renderer plumbing; hoverSlice was already reachable). Each has a test that fails before and passes after.
controls is not implemented — there is no orbit/pan restriction mechanism in the camera controller to hang it on.
SET_CAMERA and ?camera= are deliberately untouched. Restoring them needs a real absolute-orientation actuator on CameraCallbacks, and whether the two should share one — or route through applyViewpoint — is a design call we did not want to make on your behalf.
Happy to open a PR once the review queue has drained, or to split it if you would rather take these separately.
Summary
Auditing the embed postMessage API surface, most of it is sound — all 23
InboundCommandTypeentries have a matchingcaseinhandler.tswith no mismatch in either direction, 20 of them reach a real actuator, andGET_SCREENSHOTreturns an honestNOT_IMPLEMENTEDrather than pretending.Four things do not work, and each one signals success while doing nothing. Verified on
main(b3a4d30), most by execution.1.
SET_CAMERAnever moves the cameraapps/viewer-embed/src/bridge/handler.ts:401-406extracts{azimuth, elevation}and callssetCameraRotation.apps/viewer/src/store/slices/cameraSlice.ts:44doesset({cameraRotation})— and the chain ends there.Proven by execution: driving
setCameraRotationagainst a store built from the real slice, with a recording proxy overcameraCallbacks, invokes zero callbacks. The only absolute-orientation actuator,applyViewpoint(apps/viewer/src/store/types.ts:339-406), takes position/target vectors and is never called from the handler. Everything azimuth/elevation-shaped onCameraCallbacksis a relative stepper (rotateLeft/rotateRight/orbit). The payload's documentedzoom?field (packages/embed-protocol/src/index.ts:96) is dropped silently too.2.
CAMERA_CHANGEDnever fires for real navigationcameraRotationis written only bysetCameraRotation, and real camera motion deliberately bypasses store state —cameraSlice.ts:57-64, "Don't update store state during real-time updates", with the live per-frame path going throughupdateCameraRotationRealtimeto a separate callback.So the outbound event at
EmbedViewer.tsx:241-251can only fire by echoing back values a host just sent. User navigation — mouse drag, keyboard, ViewCube — emits nothing.Combined with #1, a host sending
SET_CAMERAreceives arequestIdack and aCAMERA_CHANGEDecho of its own values — every success signal — while the view never moves.3.
RESET_COLORScannot undoSET_COLORSSET_COLORSreachesdataSlice.ts:279updateMeshColors, which writespendingMeshColorUpdatesand bakes colour intogeometryResult.meshes[].color.RESET_COLORSreachesdataSlice.ts:309clearPendingColorUpdates, which clearspendingColorUpdates— a different channel, used by the lens/IDS/SDK overlays.Proven by execution: calling the real
updateMeshColorsthen the realclearPendingColorUpdatesleaves both the baked mesh colour andpendingMeshColorUpdatesuntouched.4.
ENTITY_HOVEREDis never emittedDeclared at
packages/embed-protocol/src/index.ts:130,143, with listener plumbing and tests inpackages/embed-sdk. There are zeroemitEvent('ENTITY_HOVERED', ...)call sites inapps/viewer-embed.The SDK tests pass because they call
harness.emit('ENTITY_HOVERED', ...)directly — they prove the SDK dispatches an event the viewer never sends.Also: eight URL parameters are parsed and never applied
apps/viewer-embed/src/bridge/urlParams.tsparses 14 parameters; 6 are applied. Dead:controls,autoLoad,hideAxis,hideScale,select,isolate,hideTypes,camera.autoLoadis worse than inert — the autoload effect (EmbedViewer.tsx:134-159) has no gate at all, so?autoLoad=falseloads the model anyway.urlParams.test.ts:119-127pins the parsing with four assertions, which is why this held: the parser is tested, the application of the parsed value is not.camerais dead for the same root cause as #1 — nothing exists to hand it to.Status
We have fixes on a branch for everything with unambiguous semantics — the colour reset (backing up pre-override colours so reset genuinely restores), the
autoLoadgate, five of the URL params, andENTITY_HOVERED(which turned out not to need new renderer plumbing;hoverSlicewas already reachable). Each has a test that fails before and passes after.controlsis not implemented — there is no orbit/pan restriction mechanism in the camera controller to hang it on.SET_CAMERAand?camera=are deliberately untouched. Restoring them needs a real absolute-orientation actuator onCameraCallbacks, and whether the two should share one — or route throughapplyViewpoint— is a design call we did not want to make on your behalf.Happy to open a PR once the review queue has drained, or to split it if you would rather take these separately.