feat(video): video preview composables and metadata utils - #14121
Conversation
🎭 Playwright: ✅ 1732 passed, 0 failed · 1 flaky📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle: 8.1 MB gzip ⚪ 0 BDetailsSummary
Category Glance App Entry Points — 3.64 kB (baseline 3.64 kB) • ⚪ 0 BMain entry bundles and manifests Status: 1 unchanged Graph Workspace — 1.25 MB (baseline 1.25 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration Status: 2 unchanged Views & Navigation — 112 kB (baseline 112 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces Status: 16 unchanged Panels & Settings — 551 kB (baseline 551 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens Status: 26 unchanged User & Accounts — 29.1 kB (baseline 29.1 kB) • ⚪ 0 BAuthentication, profile, and account management bundles Status: 10 unchanged Editors & Dialogs — 121 kB (baseline 121 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors Status: 6 unchanged UI Components — 64.7 kB (baseline 64.7 kB) • ⚪ 0 BReusable component library chunks Status: 14 unchanged Data & Services — 3.37 MB (baseline 3.37 MB) • ⚪ 0 BStores, services, APIs, and repositories Status: 17 unchanged Utilities & Hooks — 357 kB (baseline 357 kB) • ⚪ 0 BHelpers, composables, and utility bundles Status: 35 unchanged Vendor & Third-Party — 15.7 MB (baseline 15.7 MB) • ⚪ 0 BExternal libraries and shared vendor chunks Status: 16 unchanged Other — 12.4 MB (baseline 12.4 MB) • ⚪ 0 BBundles that do not match a named category Status: 277 unchanged ⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-07-27T15:24:20.356Z",
"gitSha": "bf806cb020c77c24bf54a8323c5c45258dfd0904",
"branch": "feat/video-preview-composables",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2036.2890000000107,
"styleRecalcs": 10,
"styleRecalcDurationMs": 10.527999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 556.436,
"heapDeltaBytes": 3472132,
"heapUsedBytes": 71028964,
"domNodes": 20,
"jsHeapTotalBytes": 20586496,
"scriptDurationMs": 23.708999999999996,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 2020.9650000000465,
"styleRecalcs": 10,
"styleRecalcDurationMs": 11.079999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 511.44499999999994,
"heapDeltaBytes": 3491184,
"heapUsedBytes": 70998584,
"domNodes": 20,
"jsHeapTotalBytes": 20848640,
"scriptDurationMs": 18.845,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1828.0920000000265,
"styleRecalcs": 73,
"styleRecalcDurationMs": 40.419999999999995,
"layouts": 12,
"layoutDurationMs": 3.4990000000000006,
"taskDurationMs": 893.593,
"heapDeltaBytes": -1661584,
"heapUsedBytes": 65821632,
"domNodes": 57,
"jsHeapTotalBytes": 21897216,
"scriptDurationMs": 130.814,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1889.024000000063,
"styleRecalcs": 75,
"styleRecalcDurationMs": 44.318,
"layouts": 12,
"layoutDurationMs": 3.9609999999999994,
"taskDurationMs": 952.2760000000001,
"heapDeltaBytes": -14837320,
"heapUsedBytes": 52793320,
"domNodes": -267,
"jsHeapTotalBytes": 22024192,
"scriptDurationMs": 129.81599999999997,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1755.1159999999868,
"styleRecalcs": 32,
"styleRecalcDurationMs": 19.607,
"layouts": 6,
"layoutDurationMs": 0.6119999999999999,
"taskDurationMs": 479.47200000000004,
"heapDeltaBytes": 7298568,
"heapUsedBytes": 74612272,
"domNodes": 78,
"jsHeapTotalBytes": 20586496,
"scriptDurationMs": 29.961000000000002,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1715.249999999969,
"styleRecalcs": 31,
"styleRecalcDurationMs": 16.409,
"layouts": 6,
"layoutDurationMs": 0.7240000000000001,
"taskDurationMs": 375.796,
"heapDeltaBytes": 7125800,
"heapUsedBytes": 74703268,
"domNodes": 76,
"jsHeapTotalBytes": 20062208,
"scriptDurationMs": 20.339999999999996,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 732.8340000000253,
"styleRecalcs": 12,
"styleRecalcDurationMs": 9.937,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 473.83699999999993,
"heapDeltaBytes": -11875844,
"heapUsedBytes": 55775024,
"domNodes": 20,
"jsHeapTotalBytes": 21110784,
"scriptDurationMs": 71.584,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 579.7620000000734,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.684000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 375.55899999999997,
"heapDeltaBytes": -12078360,
"heapUsedBytes": 55277920,
"domNodes": 14,
"jsHeapTotalBytes": 21635072,
"scriptDurationMs": 57.406000000000006,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2015.2719999999817,
"styleRecalcs": 8,
"styleRecalcDurationMs": 8.697,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 723.195,
"heapDeltaBytes": 5675204,
"heapUsedBytes": 66998672,
"domNodes": -266,
"jsHeapTotalBytes": 4792320,
"scriptDurationMs": 116.673,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2041.2960000001021,
"styleRecalcs": 8,
"styleRecalcDurationMs": 8.042,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 693.5029999999999,
"heapDeltaBytes": 2839476,
"heapUsedBytes": 62036684,
"domNodes": -268,
"jsHeapTotalBytes": 5316608,
"scriptDurationMs": 108.186,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2251.008000000013,
"styleRecalcs": 69,
"styleRecalcDurationMs": 14.65,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1376.8580000000002,
"heapDeltaBytes": 6094044,
"heapUsedBytes": 65676080,
"domNodes": -268,
"jsHeapTotalBytes": 4997120,
"scriptDurationMs": 472.72200000000004,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2194.1790000000765,
"styleRecalcs": 67,
"styleRecalcDurationMs": 13.389999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1328.862,
"heapDeltaBytes": 2354884,
"heapUsedBytes": 62549880,
"domNodes": -270,
"jsHeapTotalBytes": 4997120,
"scriptDurationMs": 455.22200000000004,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3380.471,
"styleRecalcs": 64,
"styleRecalcDurationMs": 15.749000000000002,
"layouts": 60,
"layoutDurationMs": 8.107999999999999,
"taskDurationMs": 1581.5210000000002,
"heapDeltaBytes": 25058504,
"heapUsedBytes": 86555556,
"domNodes": 10,
"jsHeapTotalBytes": 8912896,
"scriptDurationMs": 569.685,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3296.0709999999835,
"styleRecalcs": 64,
"styleRecalcDurationMs": 14.384,
"layouts": 60,
"layoutDurationMs": 7.617,
"taskDurationMs": 1547.385,
"heapDeltaBytes": 925344,
"heapUsedBytes": 64461172,
"domNodes": -272,
"jsHeapTotalBytes": 7675904,
"scriptDurationMs": 537.393,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2030.9300000000121,
"styleRecalcs": 7,
"styleRecalcDurationMs": 8.28,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 835.4630000000001,
"heapDeltaBytes": 6230632,
"heapUsedBytes": 66670708,
"domNodes": -271,
"jsHeapTotalBytes": 4005888,
"scriptDurationMs": 136.83499999999998,
"eventListeners": -131,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "minimap-idle",
"durationMs": 2025.4170000000613,
"styleRecalcs": 8,
"styleRecalcDurationMs": 8.671999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 689.397,
"heapDeltaBytes": 6493612,
"heapUsedBytes": 66772992,
"domNodes": -269,
"jsHeapTotalBytes": 4530176,
"scriptDurationMs": 107.47599999999998,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 607.9780000000028,
"styleRecalcs": 46,
"styleRecalcDurationMs": 11.138,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 422.02400000000006,
"heapDeltaBytes": -11470632,
"heapUsedBytes": 56027704,
"domNodes": 18,
"jsHeapTotalBytes": 22421504,
"scriptDurationMs": 124.778,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 629.1549999999688,
"styleRecalcs": 47,
"styleRecalcDurationMs": 11,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 407.962,
"heapDeltaBytes": -11299376,
"heapUsedBytes": 56271972,
"domNodes": 20,
"jsHeapTotalBytes": 21110784,
"scriptDurationMs": 124.76399999999998,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2014.8170000000505,
"styleRecalcs": 8,
"styleRecalcDurationMs": 10.727,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 588.188,
"heapDeltaBytes": 3652692,
"heapUsedBytes": 71260880,
"domNodes": 16,
"jsHeapTotalBytes": 20586496,
"scriptDurationMs": 20.845,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-idle",
"durationMs": 1995.176000000015,
"styleRecalcs": 11,
"styleRecalcDurationMs": 10.508000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 499.96,
"heapDeltaBytes": 3313736,
"heapUsedBytes": 71324976,
"domNodes": 22,
"jsHeapTotalBytes": 20848640,
"scriptDurationMs": 14.13,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1755.2919999999972,
"styleRecalcs": 77,
"styleRecalcDurationMs": 42.675,
"layouts": 16,
"layoutDurationMs": 4.9910000000000005,
"taskDurationMs": 938.4140000000001,
"heapDeltaBytes": -20808664,
"heapUsedBytes": 46851436,
"domNodes": -268,
"jsHeapTotalBytes": 20451328,
"scriptDurationMs": 109.47500000000001,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1732.456999999954,
"styleRecalcs": 78,
"styleRecalcDurationMs": 39.77799999999999,
"layouts": 16,
"layoutDurationMs": 4.174,
"taskDurationMs": 838.693,
"heapDeltaBytes": -21189024,
"heapUsedBytes": 46383220,
"domNodes": -264,
"jsHeapTotalBytes": 20189184,
"scriptDurationMs": 94.602,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-transition-enter",
"durationMs": 1401.1090000000195,
"styleRecalcs": 17,
"styleRecalcDurationMs": 31.560999999999993,
"layouts": 13,
"layoutDurationMs": 13.12,
"taskDurationMs": 972.0699999999999,
"heapDeltaBytes": 31020392,
"heapUsedBytes": 97704100,
"domNodes": 13673,
"jsHeapTotalBytes": 14417920,
"scriptDurationMs": 36.50600000000001,
"eventListeners": 2371,
"totalBlockingTimeMs": 138,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8952.777000000026,
"styleRecalcs": 250,
"styleRecalcDurationMs": 41.842000000000006,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 5162.055,
"heapDeltaBytes": 9306220,
"heapUsedBytes": 67926680,
"domNodes": -268,
"jsHeapTotalBytes": 6017024,
"scriptDurationMs": 1594.8950000000002,
"eventListeners": -113,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8313.467000000059,
"styleRecalcs": 250,
"styleRecalcDurationMs": 38.644,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4525.744000000001,
"heapDeltaBytes": 1826728,
"heapUsedBytes": 60393860,
"domNodes": -267,
"jsHeapTotalBytes": 7151616,
"scriptDurationMs": 1421.344,
"eventListeners": -113,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 17782.510000000002,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17738.917999999998,
"heapDeltaBytes": -41234784,
"heapUsedBytes": 166930140,
"domNodes": -8311,
"jsHeapTotalBytes": -13570048,
"scriptDurationMs": 640.5860000000001,
"eventListeners": -16381,
"totalBlockingTimeMs": 0,
"frameDurationMs": 18.329999999999927,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 17884.893999999917,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17863.83,
"heapDeltaBytes": -58277956,
"heapUsedBytes": 165579920,
"domNodes": -8311,
"jsHeapTotalBytes": -15138816,
"scriptDurationMs": 594.6969999999999,
"eventListeners": -16382,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-pan",
"durationMs": 21939.37900000003,
"styleRecalcs": 159,
"styleRecalcDurationMs": 23.127999999999982,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 21873.237,
"heapDeltaBytes": -30279284,
"heapUsedBytes": 176714296,
"domNodes": -8311,
"jsHeapTotalBytes": -14180352,
"scriptDurationMs": 984.5390000000001,
"eventListeners": -16380,
"totalBlockingTimeMs": 422,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 21372.144000000048,
"styleRecalcs": 154,
"styleRecalcDurationMs": 18.502999999999993,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 21320.896999999997,
"heapDeltaBytes": -31079252,
"heapUsedBytes": 176896536,
"domNodes": -8311,
"jsHeapTotalBytes": -14180352,
"scriptDurationMs": 909.792,
"eventListeners": -16378,
"totalBlockingTimeMs": 354,
"frameDurationMs": 18.333333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "workflow-execution",
"durationMs": 463.7870000000248,
"styleRecalcs": 14,
"styleRecalcDurationMs": 19.897000000000002,
"layouts": 2,
"layoutDurationMs": 0.4470000000000001,
"taskDurationMs": 121.42100000000002,
"heapDeltaBytes": -15936368,
"heapUsedBytes": 50675348,
"domNodes": 132,
"jsHeapTotalBytes": 8265728,
"scriptDurationMs": 10.636,
"eventListeners": 67,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "workflow-execution",
"durationMs": 462.5859999999875,
"styleRecalcs": 15,
"styleRecalcDurationMs": 20.897,
"layouts": 2,
"layoutDurationMs": 0.39800000000000013,
"taskDurationMs": 116.55199999999999,
"heapDeltaBytes": -16156944,
"heapUsedBytes": 50634424,
"domNodes": 121,
"jsHeapTotalBytes": 8003584,
"scriptDurationMs": 10.041000000000002,
"eventListeners": 65,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthroughChangesVideo processing
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Consumer
participant useVideoSourceUrl
participant useVideoFilmstrip
participant VideoElement
participant MetadataAPI
participant Canvas
Consumer->>useVideoSourceUrl: resolve reactive videoUrl
useVideoSourceUrl-->>useVideoFilmstrip: provide videoUrl
useVideoFilmstrip->>VideoElement: load video and await metadata
useVideoFilmstrip->>MetadataAPI: fetchVideoMetadata(videoUrl)
MetadataAPI-->>useVideoFilmstrip: return validated metadata
useVideoFilmstrip->>VideoElement: seek to sample time
VideoElement-->>useVideoFilmstrip: emit seeked
useVideoFilmstrip->>Canvas: draw video frame
Canvas-->>useVideoFilmstrip: return JPEG data URL
useVideoFilmstrip-->>Consumer: update thumbnails and video properties
Possibly related PRs
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/composables/video/useVideoFilmstrip.test.ts`:
- Around line 113-198: Add focused tests around useVideoFilmstrip for failed or
timed-out metadata loading, asserting error.value and reset state after the
catch path; mock canvas getContext to return null and verify the resulting
behavior; and cover the isLoadStale race by switching videoUrl before the first
load resolves, ensuring stale results do not overwrite the second URL’s state.
Reuse existing video mocks and wait helpers while preserving the current
happy-path tests.
- Around line 168-185: The test duplicates the video and canvas creation wiring
already handled by installVideoMocks. Extend installVideoMocks with an optional
onVideoCreated callback, invoke it for each created MockVideoElement, and update
“samples the configured number of frames” to use that callback for seekCount
while retaining the existing metadata emission and canvas behavior.
In `@src/composables/video/useVideoFilmstrip.ts`:
- Around line 78-84: Update the seek wait handling in the filmstrip loop around
waitForEvent so timeout rejections continue skipping the frame, while genuine
video error-event rejections stop processing and propagate the media error
through error.value. Replace the unconditional catch-and-continue behavior
without changing successful seek handling or the alreadyAtTarget path.
- Around line 92-135: Update useVideoFilmstrip and its waitForEvent error flow
so the error ref exposes stable typed error codes or keys instead of raw English
messages or DOM event names. Replace the Canvas-unavailable and video-load
failure assignments, and map timeout/load failures to user-facing translation
keys configured in src/locales/en/main.json; let consuming components translate
and format the displayed message through vue-i18n.
- Around line 114-201: Extract the duplicated media-state assignments from the
loadVideo catch block and the falsy-url branch of the videoUrl watch callback
into a shared resetState helper. Have both paths call resetState, while
preserving their distinct error and loading behavior and keeping the existing
default FPS value.
- Around line 71-90: The filmstrip loop in useVideoFilmstrip must not rely on a
seeked event for an already-at-target currentTime, especially for the first
sample at time 0. Preserve the alreadyAtTarget bypass and add or update coverage
in src/composables/video/useVideoFilmstrip.test.ts:19-70 to verify the
first-frame path captures successfully without waiting on a seek event; no
direct change is required there beyond the requested test coverage.
In `@src/composables/video/useVideoSourceUrl.test.ts`:
- Around line 88-102: Extend the test around mountSource and the videoUrl
fallback to use reactive mocked store state, initially asserting the upstream
widget URL, then add the executed output to the mocked state and await
nextTick() before asserting videoUrl switches to the preview URL. Ensure the
test exercises the keyed subscription and reactive transition, not only initial
resolution.
In `@src/composables/video/useVideoSourceUrl.ts`:
- Around line 61-69: Update sourceFileWidgetValue to key widgetValueStore
lookups with the source node’s containing graph ID rather than
source.graph.rootGraph.id, while preserving the local widget fallback. Extend
the subgraph-related tests to cover distinct root and containing-subgraph IDs,
ensuring nested nodes read their own file widget instead of a same-ID root
widget.
- Around line 88-90: The self-source branch in useVideoSourceUrl must prioritize
the executed outputUrl before fileUrl, matching the preview-priority contract;
update src/composables/video/useVideoSourceUrl.ts lines 88-90 accordingly.
Update src/composables/video/useVideoSourceUrl.test.ts lines 104-115 to expect
already-trimmed.mp4 when both URLs exist, while preserving coverage for falling
back to the widget URL when no output exists.
In `@src/utils/videoFrameUtil.ts`:
- Around line 8-20: Require a finite positive fallback FPS before performing
fallback conversion: update both fallback checks in frameToTime and timeToFrame
in src/utils/videoFrameUtil.ts (lines 8-20) to validate with Number.isFinite and
a value greater than zero, otherwise return 0. Add negative and non-finite
fallback cases to src/utils/videoFrameUtil.test.ts (lines 12-33) and verify they
return 0.
In `@src/utils/videoMetadataUtil.ts`:
- Around line 16-33: Update viewQueryFromUrl to validate that the parsed URL’s
origin matches the configured Comfy API origin before reading its query
parameters, while preserving relative same-origin URL handling. Reject external
/view URLs by returning undefined, and add a test verifying they do not trigger
the video metadata API request.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: b9812e12-afdf-4496-840d-3c67c4c6494e
📒 Files selected for processing (8)
src/composables/video/useVideoFilmstrip.test.tssrc/composables/video/useVideoFilmstrip.tssrc/composables/video/useVideoSourceUrl.test.tssrc/composables/video/useVideoSourceUrl.tssrc/utils/videoFrameUtil.test.tssrc/utils/videoFrameUtil.tssrc/utils/videoMetadataUtil.test.tssrc/utils/videoMetadataUtil.ts
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## main #14121 +/- ##
==========================================
+ Coverage 79.53% 79.55% +0.01%
==========================================
Files 1700 1727 +27
Lines 110882 111715 +833
Branches 37719 37222 -497
==========================================
+ Hits 88190 88871 +681
- Misses 22220 22351 +131
- Partials 472 493 +21
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 43 files with indirect coverage changes 🚀 New features to boost your workflow:
|
77b9e1d to
050ba6a
Compare
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@coderabbitai approve |
✅ Action performedComments resolved and changes approved. |
dante01yoon
left a comment
There was a problem hiding this comment.
Two blocking correctness issues remain on the current head: authoritative metadata is only partially applied, and nested subgraph outputs are only unwrapped one level.
|
|
||
| if (isLoadStale(loadId, url)) return | ||
|
|
||
| fps.value = metadata?.fps ?? options.fps ?? DEFAULT_VIDEO_FPS |
There was a problem hiding this comment.
issue: The backend metadata is only used for FPS, size, and frame count; its duration, width, and height are discarded, and sampling still uses videoDuration. When loadedmetadata reports a non-finite duration (common for some fragmented/streamed MP4s), this normalizes to 0 even if /video_metadata returns a valid duration, so every filmstrip sample targets frame zero and the UI reports a zero duration. Could we derive effective duration/dimensions from the backend values when present and pass that effective duration into sampleFilmstripFrames?
| const link = current.getInputLink(slot) | ||
| if (!link) return undefined | ||
| upstream = | ||
| upstream.resolveSubgraphOutputLink(link.origin_slot)?.outputNode ?? null |
There was a problem hiding this comment.
issue: This unwraps only one subgraph boundary. If that output is itself another SubgraphNode (VideoEdit <- OuterSubgraph <- InnerSubgraph <- LoadVideo), resolution stops on the inner wrapper, so neither the actual loader output nor its file widget is found. resolveSubgraphOutputLink exposes the next link.origin_slot; could we continue traversing subgraph outputs (with a cycle guard) until reaching a concrete source node?
050ba6a to
315ec2e
Compare
This is seperate part for the big PR of video edit, crop, trim PR #14118
Summary