Conversation
| const entry = await Promise.race([ | ||
| announcements.next(), | ||
| new Promise<null>((r) => setTimeout(() => r(null), remaining)), | ||
| ]); | ||
| if (!entry) break; // timeout | ||
| if (entry.active && entry.path.toString() === broadcastName) { | ||
| logger.info(`Broadcast '${broadcastName}' announced`); | ||
| return; | ||
| } | ||
| } |
There was a problem hiding this comment.
🚩 waitForBroadcastAnnouncement accesses entry.active and entry.path — verify against Hang SDK types
The new waitForBroadcastAnnouncement function at ui/src/stores/streamStoreHelpers.ts:128-149 calls announcements.next() and then accesses entry.active and entry.path. If conn.announced() returns a standard AsyncIterableIterator, then .next() would return { value: T, done: boolean }, meaning entry.active would be undefined and the broadcast would never be matched (always timing out). This depends entirely on the Hang SDK's API contract for announced(). If it returns a custom iterator where .next() resolves to the raw announcement object (not wrapped in IteratorResult), the code is correct. This should be verified against the SDK type definitions.
Was this helpful? React with 👍 or 👎 to provide feedback.
Debug
| clipPath: | ||
| layer.cropShape === 'circle' | ||
| ? `circle(${Math.min(layer.width, layer.height) / 2}px at 50% 50%)` | ||
| : undefined, |
There was a problem hiding this comment.
🟡 clip-path: circle(...) clips resize handles, making circle-crop layers non-resizable via drag
The change from borderRadius: '50%' to clipPath: circle(...) on the LayerBox causes all child elements — including the ResizeHandles component — to be clipped to the circle boundary. border-radius is purely cosmetic and does not clip children, but clip-path creates a hard clipping region that hides everything outside. The corner resize handles (nw, ne, se, sw) are located at distance ~√2 × radius from center, well outside the inscribed circle, so they're fully clipped. Edge handles (n, s, e, w) are right at the circle boundary and partially/fully clipped depending on rect aspect ratio. This makes it impossible to resize a crop_shape: circle layer using the mouse handles.
Affected component structure
The resize handles are children of the LayerBox that receives the clip-path:
<LayerBox style={{ clipPath: `circle(${...}px at 50% 50%)` }}>
<LayerLabel />
<LayerDimensions />
{isSelected && <ResizeHandles />} // ← clipped!
</LayerBox>A fix would be to move the clip-path to an inner wrapper element that doesn't contain the resize handles, or use a pseudo-element / separate overlay for the circle visual.
Prompt for agents
In ui/src/components/compositorCanvasLayers.tsx, the clipPath style on the LayerBox (lines 165-168) clips all child content including the ResizeHandles component, making it impossible to resize circle-crop layers via mouse handles.
The fix should separate the circle visual indicator from the interactive layer box. Two approaches:
1. Move the clipPath to an inner wrapper div that contains only the visual content (LayerLabel, LayerDimensions) but NOT the ResizeHandles. The ResizeHandles should remain as children of the outer LayerBox (which has no clipPath).
2. Alternatively, revert to using a visual-only approach like the old borderRadius but with a CSS outline/border technique that renders a circle without clipping children. For example, add a positioned pseudo-element (via a styled wrapper) with the circle clip-path and pointer-events: none, layered behind the resize handles.
The key requirement is that resize handles must remain visible and interactive outside the circle boundary.
Was this helpful? React with 👍 or 👎 to provide feedback.
Debug
| // Wait for the video encoder to produce a catalog entry before returning. | ||
| if (needsVideo) { | ||
| logger.info('Step 5b: Waiting for video catalog...'); | ||
| try { | ||
| await waitForSignalValue( | ||
| publish.video.catalog, | ||
| (v) => v !== undefined, | ||
| 10_000, | ||
| 'Video encoder failed to initialize' | ||
| ); | ||
| } catch (e) { | ||
| publish.close(); | ||
| shutdownMediaSource(camera); | ||
| shutdownMediaSource(microphone); | ||
| throw e; | ||
| } | ||
| logger.info('Step 5b: Video catalog ready'); | ||
| } |
There was a problem hiding this comment.
🚩 Publish-before-watch reordering may produce misleading error on slow connections
In performConnect (lines 519-565), the publish path is now set up BEFORE waitForSignalValue(connection.established). If needsVideo is true, setupPublishPath waits for publish.video.catalog with a 10s timeout (streamStoreHelpers.ts:393-398). If the MoQ connection takes >10s to establish, the video catalog wait would timeout first with "Video encoder failed to initialize" — masking the real issue (slow/failed connection). The subsequent 12s connection wait at line 537 would never be reached. In practice this is unlikely since connection establishment is async and typically completes in <1s, but on degraded networks the error message could be confusing.
Was this helpful? React with 👍 or 👎 to provide feedback.
Debug
* fix: align e2e tests with recent UI logic changes - Update crop shape test assertions from borderRadius to clipPath (compositor now uses clip-path: circle(...) instead of border-radius) - Add isExternalRelay flag to stream store to conditionally skip waitForBroadcastAnnouncement in gateway mode (only needed for external relay pipelines with separate pub/sub nodes) - Fix loadSamples auto-select to apply MoQ peer settings to stream store (pipelineNeedsVideo, serverUrl, etc.) so the store matches the selected template even when Radix RadioGroup doesn't fire onValueChange for an already-selected item - Add --use-fake-ui-for-media-stream to Playwright config so getUserMedia works for video capture in headless Chromium Co-Authored-By: Claudio Costa <cstcld91@gmail.com> * fix: reset isExternalRelay when switching to Direct Connect mode Direct Connect mode connects to a relay without a skit pipeline, so there is no external relay announcement to wait for. Without this reset, a stale isExternalRelay=true from a previous template selection would cause an unnecessary 15s wait in performConnect. Co-Authored-By: Claudio Costa <cstcld91@gmail.com> * fix: restore template settings on Session mode switch and fix stale serverUrl in auto-select - Re-apply selected template's MoQ settings (isExternalRelay, pipelineMediaTypes, pipelineOutputTypes) when switching back to Session mode from Direct Connect - Use useStreamStore.getState().serverUrl in loadSamples auto-select instead of stale closure-captured value that is always '' on mount Co-Authored-By: Claudio Costa <cstcld91@gmail.com> --------- Co-authored-by: StreamKit Devin <devin@streamkit.dev> Co-authored-by: Claudio Costa <cstcld91@gmail.com>
Summary
Some misc fixes to get video working properly on our demo instance.