fix(assets): add keyboard play/pause to compact grid video previews - #15542
fix(assets): add keyboard play/pause to compact grid video previews#15542mattmillerai wants to merge 2 commits into
Conversation
Compact grid passes show-native-video-controls=false, so the <video> never gains the controls attribute and never enters the tab order, which left play/pause reachable by mouse only (WCAG 2.1.1). Add a focusable, labelled control over the preview that shares the video's click handler and does not stop propagation, so modified clicks still reach the card's selection rules. Co-Authored-By: Christian Byrne <cbyrne@comfy.org>
|
Important Approval pendingCodeRabbit has no unresolved comments, but it has not reviewed the latest commit. Use the checkbox below to review the latest commit. CodeRabbit will approve the changes if it finds no blocking issues.
Comment |
🎨 Storybook: ✅ Built — View Storybook🎭 Playwright: ✅ 1841 passed, 0 failed📊 Browser Reports
📦 Bundle: 9.13 MB gzip 🔴 +85 BDetailsSummary
Category Glance App Entry Points — 3.71 kB (baseline 3.71 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.37 MB (baseline 1.37 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 2 added / 2 removed / 1 unchanged Views & Navigation — 124 kB (baseline 124 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 13 added / 13 removed / 4 unchanged Panels & Settings — 566 kB (baseline 566 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 10 added / 10 removed / 16 unchanged User & Accounts — 27.5 kB (baseline 27.5 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 6 added / 6 removed / 5 unchanged Editors & Dialogs — 125 kB (baseline 125 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 7 added / 7 removed / 1 unchanged UI Components — 67.1 kB (baseline 67.1 kB) • ⚪ 0 BReusable component library chunks
Status: 6 added / 6 removed / 8 unchanged Data & Services — 3.53 MB (baseline 3.53 MB) • ⚪ 0 BStores, services, APIs, and repositories
Status: 14 added / 14 removed / 3 unchanged Utilities & Hooks — 549 kB (baseline 549 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 18 added / 18 removed / 19 unchanged Vendor & Third-Party — 18.1 MB (baseline 18.1 MB) • ⚪ 0 BExternal libraries and shared vendor chunks Status: 18 unchanged Other — 14.2 MB (baseline 14.2 MB) • 🔴 +493 BBundles that do not match a named category
Status: 70 added / 70 removed / 218 unchanged ⚡ Performance Report
✅ No regressions detected. All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-08-21T08:30:59.233Z",
"gitSha": "2600ca81916b3cf3dbdb84095ec562727080b909",
"branch": "matt/fe-1712-compact-grid-video-keyboard-playback",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 1991.4630000000102,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.541,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 428.6329999999999,
"heapDeltaBytes": 3439276,
"heapUsedBytes": 64985992,
"domNodes": -282,
"jsHeapTotalBytes": 5234688,
"scriptDurationMs": 7.089999999999999,
"eventListeners": -211,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-idle",
"durationMs": 2007.1179999999913,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.910999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 422.46700000000004,
"heapDeltaBytes": 17442596,
"heapUsedBytes": 79281524,
"domNodes": -282,
"jsHeapTotalBytes": 4972544,
"scriptDurationMs": 7.508000000000001,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1918.7449999999444,
"styleRecalcs": 78,
"styleRecalcDurationMs": 40.131,
"layouts": 12,
"layoutDurationMs": 3.496,
"taskDurationMs": 818.241,
"heapDeltaBytes": 5118276,
"heapUsedBytes": 67252872,
"domNodes": -279,
"jsHeapTotalBytes": 4186112,
"scriptDurationMs": 91.943,
"eventListeners": -183,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1715.0510000000168,
"styleRecalcs": 72,
"styleRecalcDurationMs": 29.05,
"layouts": 12,
"layoutDurationMs": 3.122,
"taskDurationMs": 711.303,
"heapDeltaBytes": -5612376,
"heapUsedBytes": 56123252,
"domNodes": -281,
"jsHeapTotalBytes": 5234688,
"scriptDurationMs": 87.108,
"eventListeners": -181,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1734.2459999999846,
"styleRecalcs": 30,
"styleRecalcDurationMs": 15.138,
"layouts": 6,
"layoutDurationMs": 0.7760000000000001,
"taskDurationMs": 287.695,
"heapDeltaBytes": 2732592,
"heapUsedBytes": 64206416,
"domNodes": 76,
"jsHeapTotalBytes": 4718592,
"scriptDurationMs": 9.327000000000002,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1661.8760000000066,
"styleRecalcs": 29,
"styleRecalcDurationMs": 13.972999999999999,
"layouts": 6,
"layoutDurationMs": 0.5219999999999999,
"taskDurationMs": 307.14,
"heapDeltaBytes": 3068468,
"heapUsedBytes": 64699588,
"domNodes": 77,
"jsHeapTotalBytes": 4980736,
"scriptDurationMs": 10.502999999999998,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 525.7560000000012,
"styleRecalcs": 12,
"styleRecalcDurationMs": 9.731000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 323.519,
"heapDeltaBytes": 10086356,
"heapUsedBytes": 71501732,
"domNodes": 20,
"jsHeapTotalBytes": 4980736,
"scriptDurationMs": 46.068,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "dom-widget-clipping",
"durationMs": 496.22499999998126,
"styleRecalcs": 12,
"styleRecalcDurationMs": 7.223,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 310.317,
"heapDeltaBytes": 9969060,
"heapUsedBytes": 71515936,
"domNodes": 20,
"jsHeapTotalBytes": 4980736,
"scriptDurationMs": 45.63100000000001,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "large-graph-idle",
"durationMs": 2024.3179999999938,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.326999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 495.231,
"heapDeltaBytes": -13174316,
"heapUsedBytes": 61808628,
"domNodes": -280,
"jsHeapTotalBytes": 2879488,
"scriptDurationMs": 12.793000000000003,
"eventListeners": -177,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-idle",
"durationMs": 2007.6760000000604,
"styleRecalcs": 11,
"styleRecalcDurationMs": 10.181000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 524.2,
"heapDeltaBytes": 13263588,
"heapUsedBytes": 87979796,
"domNodes": -280,
"jsHeapTotalBytes": 3403776,
"scriptDurationMs": 14.877,
"eventListeners": -179,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2115.5749999999784,
"styleRecalcs": 70,
"styleRecalcDurationMs": 20.738,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 954.7430000000002,
"heapDeltaBytes": -6154740,
"heapUsedBytes": 70306772,
"domNodes": -268,
"jsHeapTotalBytes": -1089536,
"scriptDurationMs": 255.64600000000004,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2121.539999999982,
"styleRecalcs": 69,
"styleRecalcDurationMs": 14.311999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1030.6929999999998,
"heapDeltaBytes": -6056404,
"heapUsedBytes": 70658512,
"domNodes": -275,
"jsHeapTotalBytes": -1613824,
"scriptDurationMs": 273.029,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3184.7040000000106,
"styleRecalcs": 66,
"styleRecalcDurationMs": 14.678,
"layouts": 60,
"layoutDurationMs": 7.105,
"taskDurationMs": 1142.882,
"heapDeltaBytes": -10337064,
"heapUsedBytes": 67412228,
"domNodes": -285,
"jsHeapTotalBytes": 5238784,
"scriptDurationMs": 321.30899999999997,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-zoom",
"durationMs": 3106.9780000000264,
"styleRecalcs": 65,
"styleRecalcDurationMs": 15.382999999999997,
"layouts": 60,
"layoutDurationMs": 7.541000000000001,
"taskDurationMs": 1210.571,
"heapDeltaBytes": -14276148,
"heapUsedBytes": 62846060,
"domNodes": 14,
"jsHeapTotalBytes": 4714496,
"scriptDurationMs": 326.945,
"eventListeners": -183,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "legacy-node-drag",
"durationMs": 2136.539999999968,
"styleRecalcs": 48,
"styleRecalcDurationMs": 10.776000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1179.2910000000002,
"heapDeltaBytes": 11439632,
"heapUsedBytes": 95222764,
"domNodes": 16,
"jsHeapTotalBytes": 8318976,
"scriptDurationMs": 363.66200000000003,
"eventListeners": 184,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "legacy-node-drag",
"durationMs": 2067.5370000000157,
"styleRecalcs": 48,
"styleRecalcDurationMs": 10.145000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1224.094,
"heapDeltaBytes": -16300684,
"heapUsedBytes": 69433864,
"domNodes": 16,
"jsHeapTotalBytes": 7639040,
"scriptDurationMs": 379.33299999999997,
"eventListeners": 184,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2037.6390000000129,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.972000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 511.15500000000003,
"heapDeltaBytes": -12128288,
"heapUsedBytes": 71711788,
"domNodes": -276,
"jsHeapTotalBytes": 2617344,
"scriptDurationMs": 16.034,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2051.949000000036,
"styleRecalcs": 10,
"styleRecalcDurationMs": 13.876,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 578.1510000000001,
"heapDeltaBytes": -11907476,
"heapUsedBytes": 71850764,
"domNodes": -272,
"jsHeapTotalBytes": 2617344,
"scriptDurationMs": 16.897000000000002,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 521.1029999999823,
"styleRecalcs": 47,
"styleRecalcDurationMs": 9.077999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 317.795,
"heapDeltaBytes": 11989452,
"heapUsedBytes": 73394760,
"domNodes": 20,
"jsHeapTotalBytes": 4980736,
"scriptDurationMs": 98.388,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 525.8730000000469,
"styleRecalcs": 48,
"styleRecalcDurationMs": 10.340999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 322.405,
"heapDeltaBytes": 11906632,
"heapUsedBytes": 73620548,
"domNodes": 22,
"jsHeapTotalBytes": 5767168,
"scriptDurationMs": 95.693,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 1990.570000000048,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.585,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 416.78400000000005,
"heapDeltaBytes": -5810608,
"heapUsedBytes": 55619280,
"domNodes": -279,
"jsHeapTotalBytes": 4186112,
"scriptDurationMs": 6.813999999999999,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2021.1709999999812,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.877000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 437.30100000000004,
"heapDeltaBytes": -6641960,
"heapUsedBytes": 54785524,
"domNodes": -279,
"jsHeapTotalBytes": 4710400,
"scriptDurationMs": 7.555999999999998,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1691.7250000000195,
"styleRecalcs": 76,
"styleRecalcDurationMs": 30.913,
"layouts": 16,
"layoutDurationMs": 4.086,
"taskDurationMs": 662.011,
"heapDeltaBytes": 19904720,
"heapUsedBytes": 81444540,
"domNodes": -280,
"jsHeapTotalBytes": 5496832,
"scriptDurationMs": 73.003,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1702.413999999976,
"styleRecalcs": 74,
"styleRecalcDurationMs": 32.214,
"layouts": 16,
"layoutDurationMs": 4.199,
"taskDurationMs": 697.94,
"heapDeltaBytes": 10478452,
"heapUsedBytes": 72254240,
"domNodes": -282,
"jsHeapTotalBytes": 5234688,
"scriptDurationMs": 76.047,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-transition-enter",
"durationMs": 1301.9319999999652,
"styleRecalcs": 20,
"styleRecalcDurationMs": 25.596,
"layouts": 15,
"layoutDurationMs": 10.999000000000002,
"taskDurationMs": 796.4069999999998,
"heapDeltaBytes": 20455284,
"heapUsedBytes": 103420112,
"domNodes": 13673,
"jsHeapTotalBytes": 11272192,
"scriptDurationMs": 14.957999999999998,
"eventListeners": 2373,
"totalBlockingTimeMs": 98,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8182.584999999961,
"styleRecalcs": 253,
"styleRecalcDurationMs": 40.728,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3268.024,
"heapDeltaBytes": 5291076,
"heapUsedBytes": 80962880,
"domNodes": -232,
"jsHeapTotalBytes": -40960,
"scriptDurationMs": 831.658,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333338,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8147.328000000016,
"styleRecalcs": 251,
"styleRecalcDurationMs": 39.495000000000005,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3504.562,
"heapDeltaBytes": 15328,
"heapUsedBytes": 75645820,
"domNodes": -271,
"jsHeapTotalBytes": 221184,
"scriptDurationMs": 847.2769999999999,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 15031.383000000005,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 14493.083999999999,
"heapDeltaBytes": -31607120,
"heapUsedBytes": 190657560,
"domNodes": -8312,
"jsHeapTotalBytes": -11997184,
"scriptDurationMs": 103.92499999999998,
"eventListeners": -16391,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 15138.932000000068,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 14604.356000000002,
"heapDeltaBytes": -42266624,
"heapUsedBytes": 180118628,
"domNodes": -8312,
"jsHeapTotalBytes": -8065024,
"scriptDurationMs": 112.337,
"eventListeners": -16391,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.219999999999953,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 17697.226000000057,
"styleRecalcs": 143,
"styleRecalcDurationMs": 15.456999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17205.684999999998,
"heapDeltaBytes": -13863124,
"heapUsedBytes": 195881764,
"domNodes": -8312,
"jsHeapTotalBytes": -12328960,
"scriptDurationMs": 348.057,
"eventListeners": -16385,
"totalBlockingTimeMs": 3,
"frameDurationMs": 18.329999999999927,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-pan",
"durationMs": 17543.702000000052,
"styleRecalcs": 143,
"styleRecalcDurationMs": 14.699000000000018,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17044.863,
"heapDeltaBytes": -27878940,
"heapUsedBytes": 194359332,
"domNodes": -8312,
"jsHeapTotalBytes": -15736832,
"scriptDurationMs": 335.28799999999995,
"eventListeners": -16387,
"totalBlockingTimeMs": 76,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 82.587999999987,
"styleRecalcs": 6,
"styleRecalcDurationMs": 9.607999999999999,
"layouts": 2,
"layoutDurationMs": 0.5089999999999999,
"taskDurationMs": 55.88200000000001,
"heapDeltaBytes": 3044132,
"heapUsedBytes": 64551144,
"domNodes": 106,
"jsHeapTotalBytes": 0,
"scriptDurationMs": 4.1259999999999994,
"eventListeners": 49,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
},
{
"name": "workflow-execution",
"durationMs": 102.86899999994148,
"styleRecalcs": 8,
"styleRecalcDurationMs": 12.506,
"layouts": 2,
"layoutDurationMs": 0.8089999999999998,
"taskDurationMs": 62.53399999999999,
"heapDeltaBytes": 3047108,
"heapUsedBytes": 64905596,
"domNodes": 111,
"jsHeapTotalBytes": 0,
"scriptDurationMs": 4.247000000000001,
"eventListeners": 49,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
}
]
} |
There was a problem hiding this comment.
🔍 Cursor Review — Consolidated panel
Triggered by @mattmillerai.
Found 7 finding(s).
| Severity | Count |
|---|---|
| 🟠 High | 1 |
| 🟡 Medium | 2 |
| 🟢 Low | 2 |
| ⚪ Nit | 2 |
Panel: 8/8 reviewers contributed findings.
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #15542 +/- ##
==========================================
+ Coverage 79.80% 82.07% +2.27%
==========================================
Files 2214 1887 -327
Lines 127855 122906 -4949
Branches 40855 40124 -731
==========================================
- Hits 102036 100880 -1156
+ Misses 25214 21521 -3693
+ Partials 605 505 -100
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 340 files with indirect coverage changes 🚀 New features to boost your workflow:
|
…rols are hidden Render the play/pause button off shouldShowControls instead of the showNativeControls prop so keyboard users can start playback in the default grid path, gate it on asset.src, mark it non-draggable, add a contrasting focus ring offset, and make the preview area inert while the asset is deleting.
ELI-5
In the asset grid, the only way to play or pause a video preview was to click it with a mouse — keyboard users had no way in at all. This adds a small labelled play/pause control over the preview, so you can Tab to it and press Enter or Space. It hides itself while the video's own native controls are on screen.
Summary
Compact grid (
grid-small) passesshow-native-video-controls=false, so the<video>never gets thecontrolsattribute and never enters the tab order. The regulargridpath was no better before playback starts: native controls only attach once the video isisPlaying && isHovered, a state keyboard users cannot reach. The card root is a plain<div>with notabindex,roleor key handler, so grid videos had no keyboard path to playback — a WCAG 2.1.1 (Keyboard) failure on a primary interaction.Changes
MediaVideoTop.vuerenders a focusable,aria-labelled button centred over the preview whenever the native controls are not currently shown (!shouldShowControls) and the asset has a source. It reuses the video's existing click handler, so Enter/Space, plain clicks and modifier clicks all follow the rules already in place. The label flips betweeng.playandg.pausewith playback state; no new i18n entries were needed.!showNativeControlsto!shouldShowControlsso the defaultgridpath is keyboard-operable too; the button is gated onasset.src, markeddraggable="false"so the tile centre still starts the card's drag, and given aring-offsetso the focus ring stays visible over bright frames;MediaAssetCardmarks the preview areainertwhile the asset is deleting so the control leaves the tab order under the deletion overlay.controlsbinding itself (native controls still own playback while shown), and the card root — making the whole card a button would put every card in the tab order and still give no way to trigger playback.Review Focus
MediaAssetCard.handlePreviewClickalready returns early for unmodified video clicks, so activating play/pause does not mutate selection, and modifier clicks still bubble through to the range/toggle selection rules. The existing card tests cover both directions and pass unchanged.MediaAssetCard.test.tsasserted the old order incidentally and is updated to the new one.isPlaying && isHoveredingridmode). That transition requires a pointer hover, so a keyboard user's focus is not yanked mid-interaction; a mixed-input user hovering a playing video hands playback to the native controls, which is the intent.g.play/g.pauserather than one interpolating the asset name. Judgment call in favour of a minimal diff; the per-asset name is already carried by the adjacent selection control.Coverage
Addresses finding 3 of #15220 only. The other four findings in that issue are tracked separately and are untouched here, so this PR does not close it.
grid-small(compact): tab stopsPlay -> select -> download -> more. Fixed.grid(native controls): the same control renders until the native controls actually attach, so playback can now be started from the keyboard here too. Fixed in the review round.AssetsListItem) renders video as apointer-events-nonethumbnail with no playback at all, so it is not a playback surface and needs nothing.Provenance
pnpm test:unit src/platform/assets/components/MediaVideoTop.test.ts src/platform/assets/components/MediaAssetCard.test.ts: 2 files, 29 tests passed, 0 failed; a full localpnpm test:unitsweep passed everywhere exceptscripts/cicd/check-binary-size.test.ts, which is untouched by this PR and green in CI (local-env Node mismatch); the original run additionally confirmed the two new keyboard tests red without the control and green with it;pnpm typecheck(vue-tsc against a realpnpm installin the worktree): 0 errors; eslint on the four touched files: 0 errors;pnpm format:check: clean; pre-commit lint-staged (oxfmt, stylelint, oxlint, eslint, typecheck, knip): clean.isPlayingis exercised through theplay/pauseevents and the control's accessible-name flip rather than through real playback; the Firefox drag-from-a-form-control concern is mitigated withdraggable="false"but unexercised by automation, as is theinertgating's focus behaviour (asserted via the attribute). The Comfy Design Standards Figma file was not consulted (no access from this environment); the only visual change is a focus ring on an otherwise transparent control.