feat(video): crop box editor composables and overlay component - #14122
Conversation
🎭 Playwright: ✅ 1726 passed, 0 failed · 4 flaky📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle: 8.1 MB gzip 🟢 -67 BDetailsSummary
Category Glance App Entry Points — 3.64 kB (baseline 3.64 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.25 MB (baseline 1.25 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed / 1 unchanged Views & Navigation — 112 kB (baseline 112 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 12 added / 12 removed / 4 unchanged Panels & Settings — 551 kB (baseline 551 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 11 added / 11 removed / 15 unchanged User & Accounts — 29.1 kB (baseline 29.1 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 7 added / 7 removed / 3 unchanged Editors & Dialogs — 121 kB (baseline 121 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 5 added / 5 removed / 1 unchanged UI Components — 64.7 kB (baseline 64.7 kB) • ⚪ 0 BReusable component library chunks
Status: 6 added / 6 removed / 8 unchanged Data & Services — 3.37 MB (baseline 3.37 MB) • ⚪ 0 BStores, services, APIs, and repositories
Status: 14 added / 14 removed / 3 unchanged Utilities & Hooks — 357 kB (baseline 357 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 18 added / 18 removed / 17 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) • 🔴 +113 BBundles that do not match a named category
Status: 78 added / 78 removed / 199 unchanged ⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-07-27T15:45:21.458Z",
"gitSha": "23e813805238dda42a9b59520f2a5ba633ff165d",
"branch": "feat/video-crop-overlay",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2066.522999999989,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.319999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 538.11,
"heapDeltaBytes": 3435748,
"heapUsedBytes": 71073840,
"domNodes": 18,
"jsHeapTotalBytes": 21106688,
"scriptDurationMs": 17.250999999999998,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 2064.21499999999,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.313,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 513.2379999999999,
"heapDeltaBytes": 3626668,
"heapUsedBytes": 71090104,
"domNodes": 20,
"jsHeapTotalBytes": 21106688,
"scriptDurationMs": 19.365000000000002,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1872.7220000000102,
"styleRecalcs": 72,
"styleRecalcDurationMs": 36.697,
"layouts": 12,
"layoutDurationMs": 3.631,
"taskDurationMs": 912.636,
"heapDeltaBytes": -18225556,
"heapUsedBytes": 49352016,
"domNodes": -267,
"jsHeapTotalBytes": 20971520,
"scriptDurationMs": 127.775,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1840.4319999999643,
"styleRecalcs": 75,
"styleRecalcDurationMs": 37.98,
"layouts": 12,
"layoutDurationMs": 3.9310000000000005,
"taskDurationMs": 891.337,
"heapDeltaBytes": -16094724,
"heapUsedBytes": 51222112,
"domNodes": -265,
"jsHeapTotalBytes": 21495808,
"scriptDurationMs": 124.13000000000001,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1678.923999999995,
"styleRecalcs": 31,
"styleRecalcDurationMs": 17.522000000000002,
"layouts": 6,
"layoutDurationMs": 0.543,
"taskDurationMs": 409.013,
"heapDeltaBytes": 7102948,
"heapUsedBytes": 74737020,
"domNodes": 78,
"jsHeapTotalBytes": 20582400,
"scriptDurationMs": 22.032,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1722.3199999999679,
"styleRecalcs": 32,
"styleRecalcDurationMs": 16.663999999999998,
"layouts": 6,
"layoutDurationMs": 0.631,
"taskDurationMs": 371.43,
"heapDeltaBytes": 7149412,
"heapUsedBytes": 74740444,
"domNodes": 76,
"jsHeapTotalBytes": 20058112,
"scriptDurationMs": 18.086,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 590.7129999999938,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.440000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 397.12799999999993,
"heapDeltaBytes": -12304248,
"heapUsedBytes": 55254172,
"domNodes": 18,
"jsHeapTotalBytes": 21630976,
"scriptDurationMs": 64.49000000000001,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 592.7249999999731,
"styleRecalcs": 11,
"styleRecalcDurationMs": 7.663999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 373.258,
"heapDeltaBytes": -11774732,
"heapUsedBytes": 55653028,
"domNodes": 18,
"jsHeapTotalBytes": 20582400,
"scriptDurationMs": 59.099999999999994,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2056.7219999999793,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.991,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 693.9100000000001,
"heapDeltaBytes": 6906920,
"heapUsedBytes": 65415688,
"domNodes": -268,
"jsHeapTotalBytes": 4526080,
"scriptDurationMs": 112.41700000000002,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2052.8490000000374,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.898999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 680.0260000000002,
"heapDeltaBytes": 6288628,
"heapUsedBytes": 65386636,
"domNodes": -269,
"jsHeapTotalBytes": 5050368,
"scriptDurationMs": 112.49800000000002,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-pan",
"durationMs": 2185.155000000009,
"styleRecalcs": 69,
"styleRecalcDurationMs": 14.525,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1345.9669999999999,
"heapDeltaBytes": -4228548,
"heapUsedBytes": 55622496,
"domNodes": -269,
"jsHeapTotalBytes": 4788224,
"scriptDurationMs": 463.86899999999997,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2154.9589999999625,
"styleRecalcs": 68,
"styleRecalcDurationMs": 14.725000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1314.771,
"heapDeltaBytes": 2369096,
"heapUsedBytes": 62233344,
"domNodes": -268,
"jsHeapTotalBytes": 5779456,
"scriptDurationMs": 443.94100000000003,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-zoom",
"durationMs": 3217.143999999962,
"styleRecalcs": 64,
"styleRecalcDurationMs": 14.525000000000004,
"layouts": 60,
"layoutDurationMs": 7.683999999999999,
"taskDurationMs": 1513.7099999999998,
"heapDeltaBytes": 23485604,
"heapUsedBytes": 85237264,
"domNodes": 10,
"jsHeapTotalBytes": 8388608,
"scriptDurationMs": 542.501,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-zoom",
"durationMs": 3247.781000000032,
"styleRecalcs": 65,
"styleRecalcDurationMs": 14.184,
"layouts": 60,
"layoutDurationMs": 7.719,
"taskDurationMs": 1578.195,
"heapDeltaBytes": 1806016,
"heapUsedBytes": 63193324,
"domNodes": -272,
"jsHeapTotalBytes": 8458240,
"scriptDurationMs": 580.5290000000001,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2041.3039999999683,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.594999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 697.2289999999999,
"heapDeltaBytes": 7281968,
"heapUsedBytes": 68986432,
"domNodes": -267,
"jsHeapTotalBytes": 5574656,
"scriptDurationMs": 112.66400000000002,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2035.2809999999408,
"styleRecalcs": 7,
"styleRecalcDurationMs": 7.452,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 692.953,
"heapDeltaBytes": 6958700,
"heapUsedBytes": 67004892,
"domNodes": -270,
"jsHeapTotalBytes": 4526080,
"scriptDurationMs": 116.30399999999999,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 619.5439999999621,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.792000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 418.16400000000004,
"heapDeltaBytes": -11899268,
"heapUsedBytes": 55640704,
"domNodes": 20,
"jsHeapTotalBytes": 22417408,
"scriptDurationMs": 119.789,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 595.2279999999064,
"styleRecalcs": 47,
"styleRecalcDurationMs": 11.945,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 421.24799999999993,
"heapDeltaBytes": -11825348,
"heapUsedBytes": 55740972,
"domNodes": 20,
"jsHeapTotalBytes": 21106688,
"scriptDurationMs": 125.056,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2024.1190000000415,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 491.19,
"heapDeltaBytes": 3308832,
"heapUsedBytes": 70881448,
"domNodes": 20,
"jsHeapTotalBytes": 20844544,
"scriptDurationMs": 17.506,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-idle",
"durationMs": 2038.644999999974,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.905000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 521.828,
"heapDeltaBytes": -20163864,
"heapUsedBytes": 47578996,
"domNodes": -264,
"jsHeapTotalBytes": 20185088,
"scriptDurationMs": 15.066,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1752.2180000000276,
"styleRecalcs": 77,
"styleRecalcDurationMs": 40.038,
"layouts": 16,
"layoutDurationMs": 4.654999999999999,
"taskDurationMs": 833.5210000000001,
"heapDeltaBytes": -5652808,
"heapUsedBytes": 61883104,
"domNodes": 64,
"jsHeapTotalBytes": 21630976,
"scriptDurationMs": 99.46500000000002,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1733.6050000000114,
"styleRecalcs": 77,
"styleRecalcDurationMs": 37.023,
"layouts": 16,
"layoutDurationMs": 4.115,
"taskDurationMs": 707.5969999999999,
"heapDeltaBytes": -21702180,
"heapUsedBytes": 45748580,
"domNodes": 61,
"jsHeapTotalBytes": 20971520,
"scriptDurationMs": 91.192,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-transition-enter",
"durationMs": 1417.4919999999815,
"styleRecalcs": 19,
"styleRecalcDurationMs": 30.710999999999995,
"layouts": 15,
"layoutDurationMs": 12.767000000000005,
"taskDurationMs": 966.2529999999998,
"heapDeltaBytes": 2024544,
"heapUsedBytes": 72775636,
"domNodes": 13673,
"jsHeapTotalBytes": 15728640,
"scriptDurationMs": 36.082,
"eventListeners": 2371,
"totalBlockingTimeMs": 144,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8476.606000000003,
"styleRecalcs": 249,
"styleRecalcDurationMs": 37.733000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4862.351000000001,
"heapDeltaBytes": 10354960,
"heapUsedBytes": 68965240,
"domNodes": -269,
"jsHeapTotalBytes": 7847936,
"scriptDurationMs": 1554.1889999999999,
"eventListeners": -113,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.80000000000109
},
{
"name": "viewport-pan-sweep",
"durationMs": 8266.631000000074,
"styleRecalcs": 251,
"styleRecalcDurationMs": 38.754,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4531.7300000000005,
"heapDeltaBytes": 10206900,
"heapUsedBytes": 68125772,
"domNodes": -265,
"jsHeapTotalBytes": 4964352,
"scriptDurationMs": 1429.552,
"eventListeners": -113,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.669999999999952,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 16879.533999999978,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 16854.749,
"heapDeltaBytes": -61295048,
"heapUsedBytes": 165521812,
"domNodes": -8311,
"jsHeapTotalBytes": -14090240,
"scriptDurationMs": 606.5,
"eventListeners": -16382,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 17541.042999999947,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17517.27,
"heapDeltaBytes": -55338524,
"heapUsedBytes": 166995324,
"domNodes": -8311,
"jsHeapTotalBytes": -15667200,
"scriptDurationMs": 605.248,
"eventListeners": -16386,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.779999999999927,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 20651.324999999986,
"styleRecalcs": 145,
"styleRecalcDurationMs": 17.32,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 20600.092,
"heapDeltaBytes": -40031780,
"heapUsedBytes": 168376156,
"domNodes": -8311,
"jsHeapTotalBytes": -15142912,
"scriptDurationMs": 888.4879999999999,
"eventListeners": -16378,
"totalBlockingTimeMs": 99,
"frameDurationMs": 17.779999999999927,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 20979.95400000002,
"styleRecalcs": 146,
"styleRecalcDurationMs": 17.992000000000008,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 20901.593,
"heapDeltaBytes": -55301868,
"heapUsedBytes": 153627812,
"domNodes": -8311,
"jsHeapTotalBytes": -16715776,
"scriptDurationMs": 883.113,
"eventListeners": -16378,
"totalBlockingTimeMs": 301,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 458.204999999964,
"styleRecalcs": 15,
"styleRecalcDurationMs": 20.794,
"layouts": 2,
"layoutDurationMs": 0.38600000000000007,
"taskDurationMs": 115.021,
"heapDeltaBytes": -15819312,
"heapUsedBytes": 50891012,
"domNodes": 132,
"jsHeapTotalBytes": 7999488,
"scriptDurationMs": 9.994,
"eventListeners": 67,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "workflow-execution",
"durationMs": 466.6789999999992,
"styleRecalcs": 15,
"styleRecalcDurationMs": 20.017,
"layouts": 3,
"layoutDurationMs": 0.547,
"taskDurationMs": 107.56699999999998,
"heapDeltaBytes": -16238216,
"heapUsedBytes": 50407356,
"domNodes": 119,
"jsHeapTotalBytes": 8785920,
"scriptDurationMs": 9.354,
"eventListeners": 65,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
📝 WalkthroughWalkthroughAdds a video crop overlay with draggable and resizable bounds, source-relative percentage positioning, optional aspect-ratio locking, disabled interaction handling, localization, and comprehensive composable/component tests. ChangesVideo crop editing
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant VideoCropOverlay
participant useCropBoxEditor
participant Bounds
User->>VideoCropOverlay: pointerdown on crop box or handle
VideoCropOverlay->>useCropBoxEditor: startDrag(mode)
useCropBoxEditor->>Bounds: update source-space bounds
Bounds-->>VideoCropOverlay: reactive bounds change
VideoCropOverlay-->>User: render updated crop position and size
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 3❌ Failed checks (1 warning, 2 inconclusive)
✅ Passed checks (3 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: 6
🤖 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/components/videoEdit/VideoCropOverlay.test.ts`:
- Around line 65-70: Replace each getByTestId('crop-box') query in the
VideoCropOverlay tests, including the call sites around the existing style
assertions, with getByLabelText('Adjust crop region'). Keep data-testid queries
for the resize handles, which do not expose an accessible name.
In `@src/components/videoEdit/VideoCropOverlay.vue`:
- Line 38: Remove the unused useI18n import and t setup from VideoCropOverlay,
and update the template aria-label to call the globally injected $t directly.
Preserve the existing translation key and rendered label behavior.
In `@src/composables/video/useCropRatioLock.test.ts`:
- Around line 80-91: Update the “maps a locked preset value back to its key”
test to set the underlying lockedRatio value to 16 / 9 directly, or initialize
the lock with 16:9 bounds, instead of assigning selectedRatio. Then assert
selectedRatio.value resolves to “16:9”, exercising the getter’s numeric-to-key
mapping through observable behavior.
- Around line 52-64: Add a regression test alongside the existing “clamps the
reshaped bounds to the source frame” case using a crop positioned near the right
and bottom edges, then switch selectedRatio to '1:1'. Assert the resulting
bounds remain within the source frame, both dimensions meet MIN_CROP_SIZE, and
width equals height to verify ratio preservation during minimum-size clamping.
- Around line 26-36: Replace the standalone “defaults to custom with no locked
ratio” test with behavioral coverage in the createLock tests. Fold its
initial-state assertions into a transition scenario that selects a preset ratio
and then disables the lock, verifying the resulting selectedRatio and
isLockEnabled values while preserving coverage of the default state through that
workflow.
In `@src/composables/video/useCropRatioLock.ts`:
- Around line 30-44: Update the bounds calculation in the crop-ratio locking
logic to derive a single ratio-preserving width and height after frame clamping,
rather than applying Math.max independently. Ensure the fitted rectangle remains
within sourceWidth and sourceHeight while meeting MIN_CROP_SIZE whenever
possible; when the minimum cannot fit at the current x/y position, use a
documented fallback that preserves bounds and the locked ratio.
🪄 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: 7578177b-b9c9-482b-8caf-7b265df9604e
📒 Files selected for processing (7)
src/components/videoEdit/VideoCropOverlay.test.tssrc/components/videoEdit/VideoCropOverlay.vuesrc/composables/video/useCropBoxEditor.test.tssrc/composables/video/useCropBoxEditor.tssrc/composables/video/useCropRatioLock.test.tssrc/composables/video/useCropRatioLock.tssrc/locales/en/main.json
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## main #14122 +/- ##
==========================================
+ Coverage 79.53% 79.54% +0.01%
==========================================
Files 1700 1726 +26
Lines 110882 111671 +789
Branches 37719 37224 -495
==========================================
+ Hits 88190 88831 +641
- Misses 22220 22347 +127
- Partials 472 493 +21
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 44 files with indirect coverage changes 🚀 New features to boost your workflow:
|
e98d014 to
cc5e87a
Compare
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
dante01yoon
left a comment
There was a problem hiding this comment.
Two blocking crop-editor edge cases remain on the current head: ratio actions can corrupt pre-metadata bounds, and locked corner handles do not honor vertical movement.
| Math.max(sourceW - x, 0) / width, | ||
| Math.max(sourceH - y, 0) / height | ||
| ) | ||
| width = Math.max(width * scale, MIN_CROP_SIZE, MIN_CROP_SIZE * ratio) |
There was a problem hiding this comment.
issue: This calculation can persist NaN bounds when ratio controls are used before source metadata is available. With the initial { width: 0, height: 0 } and source dimensions 0x0, selecting 1:1 makes both available-size divisions 0 / 0, after which Math.max(NaN, ...) and the rounded fields remain NaN. The linked editor currently exposes these ratio controls while crop is enabled even during loading. Could the composable reject preset/lock operations until source dimensions and bounds are finite and positive (and the consumer disable the controls while unavailable)?
| const free = freeResize(mode, start, dx, dy) | ||
|
|
||
| const heightDriven = mode === 'n' || mode === 's' | ||
| let width = heightDriven ? free.height * ratio : free.width |
There was a problem hiding this comment.
issue: Every locked corner handle is width-driven, so dy is discarded. For example, dragging se straight downward changes free.height, but width remains unchanged and height is immediately recomputed from that unchanged width, so the crop does not resize. Diagonal drags likewise respond only to horizontal movement. Could corner resizing choose/project the controlling dimension from both pointer deltas while keeping the opposite corner anchored?
cc5e87a to
6f9b442
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/components/videoEdit/VideoCropOverlay.test.ts`:
- Around line 23-59: Add a component-level test in the VideoCropOverlay test
suite that calls renderOverlay with a non-null lockedRatio, simulates
pointer-driven resizing through the crop handle, and asserts the updated model
retains the locked aspect ratio. Cover the prop-to-overlay behavior rather than
testing the underlying composable directly.
In `@src/components/videoEdit/VideoCropOverlay.vue`:
- Around line 7-32: Add the `touch-none` utility to both draggable targets in
the `VideoCropOverlay` template: the crop box using `startDrag('move', $event)`
and each handle using `startDrag(handle.dir, $event)`. Preserve the existing
classes and disabled-state behavior.
In `@src/composables/video/useCropBoxEditor.test.ts`:
- Around line 10-196: Add a test in the useCropBoxEditor suite covering
startDrag when sourceWidth and sourceHeight are zero or otherwise invalid. Reuse
createEditor where possible, perform a drag, and assert bounds remain unchanged,
verifying the early guard prevents updates while dimensions are unavailable.
🪄 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: a36b9bcc-6748-4c23-b72b-e622366bd359
📒 Files selected for processing (7)
src/components/videoEdit/VideoCropOverlay.test.tssrc/components/videoEdit/VideoCropOverlay.vuesrc/composables/video/useCropBoxEditor.test.tssrc/composables/video/useCropBoxEditor.tssrc/composables/video/useCropRatioLock.test.tssrc/composables/video/useCropRatioLock.tssrc/locales/en/main.json
This is seperate part for the big PR of video edit, crop, trim PR #14118
Summary