SCAL-336321 Navigate a shared pre-render only after its UpdateEmbedParams lands - #659
SCAL-336321 Navigate a shared pre-render only after its UpdateEmbedParams lands#659sastaachar wants to merge 4 commits into
Conversation
…ests reconcileRuntimeParams triggers UpdateRuntimeFilters after UpdateEmbedParams, so UpdateEmbedParams is no longer the last processTrigger call. Two showPreRender tests asserted it with toHaveBeenLastCalledWith and failed; they now assert the same payload with toHaveBeenCalledWith. Make the fallback branch null-safe. getPreRenderObj() reads an untyped property off the pre-render wrapper node, so it can return an object with no viewConfig; reading viewConfig.runtimeFilters off it threw a TypeError that the surrounding catch turned into a logger.error, which jest-setup escalates to a fatal error and surfaced on an unrelated liveboard.spec test. Add coverage for the three reconcile paths — filters from the new config, clearing the filters left by the previous config, and no filters at all — plus the missing-viewConfig case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ts params land `LiveboardEmbed.beforePrerenderVisible()` queued two container-ready callbacks: the base one that posts `UpdateEmbedParams`, then its own that posts `HostEvent.Navigate`. The first suspends on `await getUpdateEmbedParamsObject()` (which awaits `getAppInitData()`), so the second ran to completion first and the real order on the wire was `Navigate`, `UpdateEmbedParams`, `UpdateRuntimeFilters`. The container therefore started loading the new liveboard while still holding the previous config's `runtimeFilterParams`, and the params that arrived mid-load were dropped — the reported symptom of a filter sticking across a shared pre-render and `runtimeFilters: []` failing to reset it. `beforePrerenderVisible()` now publishes `preRenderParamsApplied`, which resolves once the params (and the `reconcileRuntimeParams` follow-up) have been posted and the container has had 200ms to apply them; the liveboard navigation awaits it. It resolves on the failure path too, so navigation is delayed but never blocked, and both callbacks are gated on the same container-ready signal, so neither can strand the other. Ordering is pinned by a test verified to fail without the change: it observed `["Navigate", "updateEmbedParams", "UpdateRuntimeFilters"]`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request addresses race conditions in pre-rendered embeds by delaying navigation until the UpdateEmbedParams payload has settled and by reconciling runtime filters on shared pre-renders. The review feedback highlights two key improvements: safely guarding the .map() call on prevRuntimeFilters with Array.isArray to prevent runtime TypeErrors, and explicitly asserting the presence of UpdateEmbedParams in test assertions to avoid false positives from indexOf returning -1.
| const prevRuntimeFilters = this.getPreRenderObj()?.viewConfig?.runtimeFilters; | ||
| if (!prevRuntimeFilters) return; |
There was a problem hiding this comment.
Since getPreRenderObj() reads untyped properties off the DOM wrapper node, prevRuntimeFilters is not guaranteed to be an array at runtime. To prevent potential runtime TypeErrors if prevRuntimeFilters is truthy but not an array, use Array.isArray to safely guard the .map() call.
| const prevRuntimeFilters = this.getPreRenderObj()?.viewConfig?.runtimeFilters; | |
| if (!prevRuntimeFilters) return; | |
| const prevRuntimeFilters = this.getPreRenderObj()?.viewConfig?.runtimeFilters; | |
| if (!Array.isArray(prevRuntimeFilters)) return; |
| expect(eventTypes.indexOf(HostEvent.UpdateEmbedParams)).toBeLessThan( | ||
| eventTypes.indexOf(HostEvent.UpdateRuntimeFilters), | ||
| ); |
There was a problem hiding this comment.
If HostEvent.UpdateEmbedParams is not triggered, indexOf returns -1. Since indexOf(HostEvent.UpdateRuntimeFilters) is >= 0, the assertion expect(...).toBeLessThan(...) will pass even if UpdateEmbedParams was never triggered. To make the test robust and prevent false positives, explicitly assert that UpdateEmbedParams is present in eventTypes.
expect(eventTypes).toContain(HostEvent.UpdateEmbedParams);
expect(eventTypes.indexOf(HostEvent.UpdateEmbedParams)).toBeLessThan(
eventTypes.indexOf(HostEvent.UpdateRuntimeFilters),
);
commit: |
Problem
On a shared pre-render, a
runtimeFiltersvalue from one liveboard sticks when the host switches to another liveboard, andruntimeFilters: []does not reset it. The container fix attempted in blink-v2 ([SCAL-333947], PR #68332) did not close it and has been reverted.Root cause — the host events go out in the wrong order
LiveboardEmbed.beforePrerenderVisible()queues two container-ready callbacks:super.beforePrerenderVisible()→ postsHostEvent.UpdateEmbedParamsHostEvent.NavigateBoth are invoked in registration order, but the first one suspends on
await this.getUpdateEmbedParamsObject()(which awaitsgetAppInitData()). It yields at thatawait, callback 2 runs to completion, and the real order on the wire is:That is observed output, not a reading of the code — it is what the new test records when the fix is removed.
So the container starts loading the new liveboard while still holding the previous config's
runtimeFilterParams, and the params that arrive mid-load are dropped. Hence the filter that "sticks", and the tile that keeps the previous filter's data.Fix
TsEmbed.beforePrerenderVisible()now publishespreRenderParamsApplied, a promise that resolves onceUpdateEmbedParams(and thereconcileRuntimeParamsfollow-up) has been posted and the container has hadUPDATE_EMBED_PARAMS_SETTLE_MS(200ms) to apply it.LiveboardEmbedawaits that before triggeringNavigate.The settle window is deliberate rather than an ack-wait: the container applies the payload through React state, so the post being delivered is not the same as the new params being in effect, and awaiting
trigger()would risk stalling navigation on the 30s trigger timeout.Two properties worth checking in review:
beforePrerenderVisible(), outsideexecuteAfterEmbedContainerLoaded, so the navigation callback has something to await whether the container is already loaded or not;finally, so a params failure delays navigation but never blocks it. Both callbacks are gated on the same container-ready signal, so neither can strand the other.Also included (previously unreviewed, on the same branch):
reconcileRuntimeParams(), which re-sends the new config's runtime filters afterUpdateEmbedParams, or — when the new config has none — re-sends the previous config's filters withvalues: [], which is what actually resets them. That is the reset the bogus-column workaround was standing in for. It only works if it lands beforeNavigate, which is what this PR makes true.Verification
await this.preRenderParamsAppliedremoved,should trigger Navigate only after UpdateEmbedParams has settledfails with the received array above. It is not a test that passes either way.tsc --noEmitclean; eslint 0 errors and no new warnings (20 on the touched files, same as baseline).AuthInittest needed 1305ms rather than 1005ms — 1000ms container-ready fallback plus the 200ms window.Notes for the reviewer
reconcileRuntimeParamshalf of this change. Close that one in favour of this.if (this.viewConfig.runtimeFilters)is truthy for[], so a caller passingruntimeFilters: []takes the first branch and sends an empty array rather than falling through to the empty-values reset. Whether blink treats an empty array as a clear decides if the customer's literalruntimeFilters: []case is closed by this PR or still needs the container-side change. Flagging rather than changing it here, since it alters behaviour rather than making the existing intent work.LiveboardEmbednavigates its pre-render on show;AppEmbeddoes not overridebeforePrerenderVisible, so it is unaffected.Refs SCAL-336321, SCAL-333947.