Restore gated Hugging Face missing model download handling#13331
Restore gated Hugging Face missing model download handling#13331jaeone94 wants to merge 5 commits into
Conversation
📝 WalkthroughWalkthroughThis PR adds gated HuggingFace repository handling across missing-model metadata, storage, pipeline propagation, and download routing. It introduces a composable and new store state so components can prefetch metadata, cache gated repo URLs, and open gated pages instead of downloading directly when needed. ChangesGated Model Download Handling
Estimated code review effort: 3 (Moderate) | ~25 minutes Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 6✅ Passed checks (6 passed)
✨ Finishing Touches🧪 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 |
🎨 Storybook: ✅ Built — View Storybook🎭 Playwright: ✅ 1687 passed, 0 failed · 3 flaky📊 Browser Reports
📦 Bundle: 7.77 MB gzip 🔴 +392 BDetailsSummary
Category Glance App Entry Points — 47.4 kB (baseline 47.4 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.25 MB (baseline 1.25 MB) • 🔴 +1.35 kBGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 97.7 kB (baseline 97.7 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 9 added / 9 removed / 3 unchanged Panels & Settings — 546 kB (baseline 546 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 11 added / 11 removed / 16 unchanged User & Accounts — 26.9 kB (baseline 26.9 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 7 added / 7 removed / 3 unchanged Editors & Dialogs — 117 kB (baseline 117 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 4 added / 4 removed / 1 unchanged UI Components — 57.2 kB (baseline 57.2 kB) • ⚪ 0 BReusable component library chunks
Status: 5 added / 5 removed / 8 unchanged Data & Services — 270 kB (baseline 270 kB) • ⚪ 0 BStores, services, APIs, and repositories
Status: 13 added / 13 removed / 3 unchanged Utilities & Hooks — 3.37 MB (baseline 3.37 MB) • 🔴 +488 BHelpers, composables, and utility bundles
Status: 16 added / 16 removed / 17 unchanged Vendor & Third-Party — 15.3 MB (baseline 15.3 MB) • ⚪ 0 BExternal libraries and shared vendor chunks Status: 16 unchanged Other — 11.7 MB (baseline 11.7 MB) • 🔴 +340 BBundles that do not match a named category
Status: 66 added / 66 removed / 99 unchanged ⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-07-01T05:41:06.384Z",
"gitSha": "92583ec93e323ee7620a91e430070b0e1ab8db20",
"branch": "jaeone/fe-1173-restore-gated-hugging-face-model-download-handling",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2040.8070000000862,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.895000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 393.86000000000007,
"heapDeltaBytes": -1795136,
"heapUsedBytes": 50753656,
"domNodes": -291,
"jsHeapTotalBytes": 17690624,
"scriptDurationMs": 15.570999999999998,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 2071.2639999999283,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.372,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 379.11499999999995,
"heapDeltaBytes": 13361260,
"heapUsedBytes": 65368540,
"domNodes": -275,
"jsHeapTotalBytes": 16117760,
"scriptDurationMs": 15.495,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1774.9000000000024,
"styleRecalcs": 73,
"styleRecalcDurationMs": 37.672000000000004,
"layouts": 12,
"layoutDurationMs": 3.565,
"taskDurationMs": 757.36,
"heapDeltaBytes": -12982932,
"heapUsedBytes": 56340740,
"domNodes": -240,
"jsHeapTotalBytes": 19820544,
"scriptDurationMs": 107.11999999999999,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1775.822000000062,
"styleRecalcs": 72,
"styleRecalcDurationMs": 36.098,
"layouts": 12,
"layoutDurationMs": 3.39,
"taskDurationMs": 744.357,
"heapDeltaBytes": -13099848,
"heapUsedBytes": 56108860,
"domNodes": -241,
"jsHeapTotalBytes": 19820544,
"scriptDurationMs": 106.00999999999999,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1714.4610000000284,
"styleRecalcs": 30,
"styleRecalcDurationMs": 15.014000000000001,
"layouts": 6,
"layoutDurationMs": 0.654,
"taskDurationMs": 290.85,
"heapDeltaBytes": 2000056,
"heapUsedBytes": 60958292,
"domNodes": 77,
"jsHeapTotalBytes": 25165824,
"scriptDurationMs": 16.944999999999997,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1774.2769999999837,
"styleRecalcs": 29,
"styleRecalcDurationMs": 19.563,
"layouts": 6,
"layoutDurationMs": 0.5730000000000002,
"taskDurationMs": 358.336,
"heapDeltaBytes": 6690048,
"heapUsedBytes": 58640756,
"domNodes": -241,
"jsHeapTotalBytes": 3534848,
"scriptDurationMs": 17.062,
"eventListeners": -184,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 598.7999999999829,
"styleRecalcs": 12,
"styleRecalcDurationMs": 10.369,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 379.072,
"heapDeltaBytes": 2713280,
"heapUsedBytes": 72038276,
"domNodes": -280,
"jsHeapTotalBytes": 5402624,
"scriptDurationMs": 56.836999999999996,
"eventListeners": -201,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66666666666665,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 550.809000000072,
"styleRecalcs": 9,
"styleRecalcDurationMs": 5.533999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 359.43600000000004,
"heapDeltaBytes": 9229948,
"heapUsedBytes": 61174564,
"domNodes": -291,
"jsHeapTotalBytes": 7467008,
"scriptDurationMs": 50.889,
"eventListeners": -203,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2069.419000000039,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.105999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 523.4929999999999,
"heapDeltaBytes": -8271764,
"heapUsedBytes": 58664596,
"domNodes": -271,
"jsHeapTotalBytes": -1310720,
"scriptDurationMs": 81.244,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2045.9140000000389,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.931,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 518.6809999999999,
"heapDeltaBytes": -1029372,
"heapUsedBytes": 58941976,
"domNodes": -273,
"jsHeapTotalBytes": -1081344,
"scriptDurationMs": 80.762,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2065.6420000000253,
"styleRecalcs": 69,
"styleRecalcDurationMs": 19.744,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 976.95,
"heapDeltaBytes": 10162712,
"heapUsedBytes": 83098328,
"domNodes": 20,
"jsHeapTotalBytes": 11067392,
"scriptDurationMs": 348.514,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-pan",
"durationMs": 2076.887999999826,
"styleRecalcs": 67,
"styleRecalcDurationMs": 15.714000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 946.998,
"heapDeltaBytes": 27878776,
"heapUsedBytes": 82466860,
"domNodes": 10,
"jsHeapTotalBytes": 204800,
"scriptDurationMs": 350.843,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3133.816999999908,
"styleRecalcs": 66,
"styleRecalcDurationMs": 20.377,
"layouts": 60,
"layoutDurationMs": 8.098999999999998,
"taskDurationMs": 1257.439,
"heapDeltaBytes": -5166932,
"heapUsedBytes": 69330800,
"domNodes": 14,
"jsHeapTotalBytes": 10891264,
"scriptDurationMs": 479.981,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3074.9819999998635,
"styleRecalcs": 63,
"styleRecalcDurationMs": 17.166999999999998,
"layouts": 60,
"layoutDurationMs": 7.707000000000001,
"taskDurationMs": 1222.661,
"heapDeltaBytes": 12109852,
"heapUsedBytes": 65674072,
"domNodes": -285,
"jsHeapTotalBytes": 0,
"scriptDurationMs": 457.45,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "minimap-idle",
"durationMs": 2045.5669999998918,
"styleRecalcs": 7,
"styleRecalcDurationMs": 5.536,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 485.11899999999997,
"heapDeltaBytes": -2480408,
"heapUsedBytes": 58989540,
"domNodes": -275,
"jsHeapTotalBytes": -557056,
"scriptDurationMs": 76.17999999999999,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "minimap-idle",
"durationMs": 2020.9409999999934,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.094000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 480.58200000000005,
"heapDeltaBytes": -9260260,
"heapUsedBytes": 63907228,
"domNodes": 18,
"jsHeapTotalBytes": 8007680,
"scriptDurationMs": 76.92699999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 604.45100000004,
"styleRecalcs": 47,
"styleRecalcDurationMs": 11.638,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 385.892,
"heapDeltaBytes": -21999212,
"heapUsedBytes": 47482496,
"domNodes": -277,
"jsHeapTotalBytes": 5402624,
"scriptDurationMs": 116.178,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 586.0310000000482,
"styleRecalcs": 49,
"styleRecalcDurationMs": 13.919999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 398.949,
"heapDeltaBytes": -22728480,
"heapUsedBytes": 46712736,
"domNodes": -271,
"jsHeapTotalBytes": 6451200,
"scriptDurationMs": 124.242,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666682,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2080.872999999997,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.326,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 418.43199999999996,
"heapDeltaBytes": -8191688,
"heapUsedBytes": 61252220,
"domNodes": -275,
"jsHeapTotalBytes": 19820544,
"scriptDurationMs": 13.745000000000001,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-idle",
"durationMs": 2030.3639999999632,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.511000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 402.81300000000005,
"heapDeltaBytes": -8094308,
"heapUsedBytes": 61256280,
"domNodes": -274,
"jsHeapTotalBytes": 19296256,
"scriptDurationMs": 15.225000000000001,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1727.7859999999237,
"styleRecalcs": 75,
"styleRecalcDurationMs": 36.033,
"layouts": 16,
"layoutDurationMs": 4.5,
"taskDurationMs": 676.524,
"heapDeltaBytes": -17002488,
"heapUsedBytes": 52474204,
"domNodes": -237,
"jsHeapTotalBytes": 19820544,
"scriptDurationMs": 84.351,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1694.6290000000772,
"styleRecalcs": 75,
"styleRecalcDurationMs": 34.708000000000006,
"layouts": 16,
"layoutDurationMs": 4.136,
"taskDurationMs": 641.494,
"heapDeltaBytes": -10489000,
"heapUsedBytes": 48350904,
"domNodes": 61,
"jsHeapTotalBytes": 25165824,
"scriptDurationMs": 85.48700000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-transition-enter",
"durationMs": 999.3259999998827,
"styleRecalcs": 16,
"styleRecalcDurationMs": 27.253000000000007,
"layouts": 5,
"layoutDurationMs": 12.656,
"taskDurationMs": 791.9359999999999,
"heapDeltaBytes": -10015556,
"heapUsedBytes": 78775476,
"domNodes": 12388,
"jsHeapTotalBytes": 3694592,
"scriptDurationMs": 29.877000000000002,
"eventListeners": 844,
"totalBlockingTimeMs": 141,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8204.325999999925,
"styleRecalcs": 250,
"styleRecalcDurationMs": 59.69199999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3511.207,
"heapDeltaBytes": 20063432,
"heapUsedBytes": 77452836,
"domNodes": -265,
"jsHeapTotalBytes": 5152768,
"scriptDurationMs": 1123.945,
"eventListeners": -130,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8153.656000000183,
"styleRecalcs": 252,
"styleRecalcDurationMs": 56.813,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3493.422,
"heapDeltaBytes": 946500,
"heapUsedBytes": 67289504,
"domNodes": -258,
"jsHeapTotalBytes": 1253376,
"scriptDurationMs": 1133.217,
"eventListeners": -183,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 12638.909000000012,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12627.746000000001,
"heapDeltaBytes": -42369832,
"heapUsedBytes": 170516228,
"domNodes": -3300,
"jsHeapTotalBytes": -1601536,
"scriptDurationMs": 512.078,
"eventListeners": -16376,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 12648.713000000043,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12630.716,
"heapDeltaBytes": -44449200,
"heapUsedBytes": 158901368,
"domNodes": -3300,
"jsHeapTotalBytes": 17563648,
"scriptDurationMs": 501.973,
"eventListeners": -16366,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.220000000000073,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 14831.867000000102,
"styleRecalcs": 65,
"styleRecalcDurationMs": 18.581999999999987,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 14810.533,
"heapDeltaBytes": -23527768,
"heapUsedBytes": 179960740,
"domNodes": -3300,
"jsHeapTotalBytes": 5943296,
"scriptDurationMs": 872.238,
"eventListeners": -16368,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.22666666666664,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-pan",
"durationMs": 14650.413999999955,
"styleRecalcs": 68,
"styleRecalcDurationMs": 18.498999999999988,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 14629.298999999999,
"heapDeltaBytes": -54531640,
"heapUsedBytes": 158526468,
"domNodes": -3300,
"jsHeapTotalBytes": 18350080,
"scriptDurationMs": 815.238,
"eventListeners": -16366,
"totalBlockingTimeMs": 61,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 485.4630000002089,
"styleRecalcs": 16,
"styleRecalcDurationMs": 29.783000000000005,
"layouts": 5,
"layoutDurationMs": 1.313,
"taskDurationMs": 215.249,
"heapDeltaBytes": -21935804,
"heapUsedBytes": 48658272,
"domNodes": -167,
"jsHeapTotalBytes": 4616192,
"scriptDurationMs": 16.456,
"eventListeners": -134,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 473.6120000000028,
"styleRecalcs": 18,
"styleRecalcDurationMs": 27.110000000000003,
"layouts": 4,
"layoutDurationMs": 1.0690000000000002,
"taskDurationMs": 206.16099999999997,
"heapDeltaBytes": -22218600,
"heapUsedBytes": 48432892,
"domNodes": -176,
"jsHeapTotalBytes": 4354048,
"scriptDurationMs": 15.508999999999999,
"eventListeners": -132,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## main #13331 +/- ##
==========================================
+ Coverage 77.08% 77.57% +0.49%
==========================================
Files 1636 1637 +1
Lines 98258 97087 -1171
Branches 33107 33409 +302
==========================================
- Hits 75738 75316 -422
+ Misses 21812 21082 -730
+ Partials 708 689 -19
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 25 files with indirect coverage changes 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/platform/missingModel/components/MissingModelRow.vue (1)
349-356: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winGate mount-time metadata prefetch
onMounted()still fetchesprefetchModelMetadata(url)for every non-cloud row with a URL. Reusedownloadable.valuehere so only allowlisted, directory-backed rows probe remote hosts;makeModel()already setsrepresentative.directory, so the gated HuggingFace test still covers this path.🔒 Proposed fix
onMounted(() => { if (isCloud) return const url = model.representative.url - if (url) { + if (url && downloadable.value) { void prefetchModelMetadata(url) } })🤖 Prompt for 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. In `@src/platform/missingModel/components/MissingModelRow.vue` around lines 349 - 356, The mount-time metadata prefetch in MissingModelRow.vue is too broad because it runs for every non-cloud row with a URL. Update the onMounted() logic to reuse downloadable.value (which is already derived from makeModel() and representative.directory) so prefetchModelMetadata(url) only runs for allowlisted, directory-backed rows; keep the existing isCloud early return and preserve the HuggingFace coverage through that gated path.Source: Coding guidelines
🤖 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.
Outside diff comments:
In `@src/platform/missingModel/components/MissingModelRow.vue`:
- Around line 349-356: The mount-time metadata prefetch in MissingModelRow.vue
is too broad because it runs for every non-cloud row with a URL. Update the
onMounted() logic to reuse downloadable.value (which is already derived from
makeModel() and representative.directory) so prefetchModelMetadata(url) only
runs for allowlisted, directory-backed rows; keep the existing isCloud early
return and preserve the HuggingFace coverage through that gated path.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 40933679-c932-4a31-a52f-410644515b21
📒 Files selected for processing (12)
src/platform/missingModel/components/MissingModelCard.test.tssrc/platform/missingModel/components/MissingModelCard.vuesrc/platform/missingModel/components/MissingModelRow.test.tssrc/platform/missingModel/components/MissingModelRow.vuesrc/platform/missingModel/composables/useMissingModelDownload.test.tssrc/platform/missingModel/composables/useMissingModelDownload.tssrc/platform/missingModel/missingModelDownload.test.tssrc/platform/missingModel/missingModelDownload.tssrc/platform/missingModel/missingModelPipeline.test.tssrc/platform/missingModel/missingModelPipeline.tssrc/platform/missingModel/missingModelStore.test.tssrc/platform/missingModel/missingModelStore.ts
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/platform/missingModel/missingModelPipeline.ts (1)
207-219: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick winAvoid re-fetching metadata for already-known gated URLs
src/platform/missingModel/missingModelPipeline.tsstill callsfetchModelMetadata(c.url)for every downloadable candidate on each run, and gated responses are never cached infetchModelMetadata. Already-resolved gated URLs keep hitting the remote host instead of reusing store state. Mirror theprefetchModelMetadataguard here, or expose the cached maps to this pipeline before fetching.🤖 Prompt for 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. In `@src/platform/missingModel/missingModelPipeline.ts` around lines 207 - 219, The missing model pipeline still re-fetches metadata for URLs that are already known to be gated, so update the candidate handling in missingModelPipeline.ts to consult existing store/cache state before calling fetchModelMetadata. Mirror the prefetchModelMetadata guard or reuse the cached gated/file-size maps via missingModelStore, and skip the network fetch when the URL has already been resolved as gated.
🤖 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/platform/missingModel/components/MissingModelCard.vue`:
- Around line 137-141: The bulk download flow in downloadAllModels() is opening
a separate gated-repo tab for every gated model, which can spam multiple browser
tabs at once. Update downloadAllModels() and the gated model path in
downloadMissingModel/openGatedRepoPage to batch or dedupe gated-repo launches so
only one tab per host/repo (or one prompt) is opened when multiple gated models
are selected.
---
Outside diff comments:
In `@src/platform/missingModel/missingModelPipeline.ts`:
- Around line 207-219: The missing model pipeline still re-fetches metadata for
URLs that are already known to be gated, so update the candidate handling in
missingModelPipeline.ts to consult existing store/cache state before calling
fetchModelMetadata. Mirror the prefetchModelMetadata guard or reuse the cached
gated/file-size maps via missingModelStore, and skip the network fetch when the
URL has already been resolved as gated.
🪄 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: 37ab1e40-aca5-427e-a99b-f4ee885bef85
📒 Files selected for processing (11)
src/platform/missingModel/components/MissingModelCard.test.tssrc/platform/missingModel/components/MissingModelCard.vuesrc/platform/missingModel/components/MissingModelRow.test.tssrc/platform/missingModel/components/MissingModelRow.vuesrc/platform/missingModel/composables/useMissingModelDownload.test.tssrc/platform/missingModel/composables/useMissingModelDownload.tssrc/platform/missingModel/missingModelDownload.test.tssrc/platform/missingModel/missingModelDownload.tssrc/platform/missingModel/missingModelPipeline.tssrc/platform/missingModel/missingModelStore.test.tssrc/platform/missingModel/missingModelStore.ts
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/platform/missingModel/components/MissingModelRow.vue (1)
349-369: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRace window: click-to-download can bypass gated redirect if it fires before mount-time prefetch resolves.
onMountedfiresprefetchModelMetadata(url)fire-and-forget (Line 354).handleDownloadnow relies entirely ondownloadMissingModel, which only routes to the gated repo page whenstore.gatedRepoUrls[model.url]is already populated (per the composable'sdownloadMissingModel, it falls through todownloadModel(...)directly whenever that cache entry isn't set yet). Previously,handleDownloaddid its own freshfetchModelMetadatacall before downloading, so a click always got current gating info. Now, if a user clicks Download before the mount prefetch promise resolves (e.g., slow network, or a very fast click right after the row renders), a gated model will be downloaded directly instead of opening the HF page — reintroducing the exact failure mode this PR is meant to fix.Track the in-flight prefetch and await it before deciding whether to download, so click-time behavior can't race ahead of the mount-time metadata fetch.
🏁 Proposed fix to close the race window
+const pendingPrefetch = ref<Promise<void>>() + onMounted(() => { if (isCloud) return const url = model.representative.url if (url && downloadable.value) { - void prefetchModelMetadata(url) + pendingPrefetch.value = prefetchModelMetadata(url) } }) -function handleDownload() { +async function handleDownload() { const rep = model.representative - if (rep.url && rep.directory) { - void downloadMissingModel({ - name: rep.name, - url: rep.url, - directory: rep.directory - }) - } else { + if (!rep.url || !rep.directory) { console.warn('[MissingModelRow] Cannot download: missing url or directory') + return } + if (pendingPrefetch.value) await pendingPrefetch.value + await downloadMissingModel({ + name: rep.name, + url: rep.url, + directory: rep.directory + }) }Note:
refis already imported destructively elsewhere in the codebase's<script setup>pattern; add it to the existingvueimport.🤖 Prompt for 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. In `@src/platform/missingModel/components/MissingModelRow.vue` around lines 349 - 369, The click-to-download path in MissingModelRow.vue can race ahead of the mount-time metadata prefetch, causing gated models to bypass the gated redirect. Track the in-flight prefetch started in onMounted (the prefetchModelMetadata(url) call) and await that promise inside handleDownload before calling downloadMissingModel, so click-time behavior always uses up-to-date gating info. Use the existing model.representative, prefetchModelMetadata, and handleDownload symbols to wire this up without relying on the cache being populated first.
🤖 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.
Outside diff comments:
In `@src/platform/missingModel/components/MissingModelRow.vue`:
- Around line 349-369: The click-to-download path in MissingModelRow.vue can
race ahead of the mount-time metadata prefetch, causing gated models to bypass
the gated redirect. Track the in-flight prefetch started in onMounted (the
prefetchModelMetadata(url) call) and await that promise inside handleDownload
before calling downloadMissingModel, so click-time behavior always uses
up-to-date gating info. Use the existing model.representative,
prefetchModelMetadata, and handleDownload symbols to wire this up without
relying on the cache being populated first.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: df819392-04a7-479f-9fa2-10b44e923a78
📒 Files selected for processing (4)
src/platform/missingModel/components/MissingModelRow.test.tssrc/platform/missingModel/components/MissingModelRow.vuesrc/platform/missingModel/missingModelPipeline.test.tssrc/platform/missingModel/missingModelPipeline.ts
Summary
This restores the gated Hugging Face missing-model download behavior that was originally added in #3004 and later dropped during the changes in #9921. The goal is to bring the OSS/core behavior back while making the flow a little harder to accidentally remove again.
The previous behavior was important for gated HF models: when the metadata request tells us the model is gated, the UI should not try to download the raw file URL. Instead, it should open the Hugging Face repo page so the user can sign in, accept the model terms, or otherwise resolve access on Hugging Face.
Review Point: Important Browser Download Trade-Off
Please review this trade-off carefully before merging. In a normal browser, if the user is already signed in to Hugging Face and already has access to the gated model, the current
mainbehavior can still succeed because it always hands the original download URL to a browser<a download>click. The browser can then use the user’s existing Hugging Face session and the file may download successfully.With this PR, once a model URL is classified as gated, the download button opens the Hugging Face repo page instead of handing the original file URL to the browser. That restores the intended behavior for users who cannot download yet, because they are sent to the right Hugging Face page to sign in or accept terms. However, it can also take away one-click download from users whose browser session already has access, because the gated classification is based on metadata probing rather than a guaranteed answer about whether the current browser session can actually download the file.
In other words: this PR centralizes and stabilizes gated-model detection so it can be reused later, and it fixes the broken path for users who truly need to visit the gated repo page. But reviewers should consider whether the download button should always open the repo page for known gated models, or whether we should preserve the browser
<a download>attempt and expose the gated repo page through a separate hint/action instead. That alternative would probably need a Desktop contract change too, because Desktop has a separate download path and cannot rely on the browser<a download>behavior in the same way.What Changed
This PR preserves the gated HF repo URL instead of dropping it after metadata fetches. That gated repo URL is now stored alongside the missing-model metadata and checked before starting the normal model download path. If a missing model is known to be gated, clicking download opens the repo page instead of trying to download the model file directly.
Stored gated metadata is also revalidated on later download clicks. If a later metadata request can read the file size, the stale gated marker is cleared and the existing download path runs normally. Gated metadata itself is not cached in the shared metadata cache, so a transient gated result does not poison that cache for the rest of the session.
I also pulled the missing-model download decision into a small platform-level composable,
useMissingModelDownload().MissingModelRowandMissingModelCardnow go through that composable instead of each wiring metadata/download behavior by hand. This keeps the row action and the bulk “Download all” action on the same path, and gives the gated behavior one place to live.A small follow-up guard keeps metadata probing bounded: mounted rows only prefetch metadata when they are actually downloadable, and the missing-model pipeline skips URLs that already have stored file size or gated repo metadata.
The PR also adds regression coverage for the cases that matter here:
huggingface.coappears in the pathCommit Breakdown
fix(missing-model): harden HuggingFace gated metadata detectionhuggingface.cohost.feat(missing-model): persist gated repo metadatagatedRepoUrlsto the missing-model store.refactor(missing-model): centralize download handlinguseMissingModelDownload()for metadata prefetch and download decisions.fix(missing-model): revalidate stale gated metadatafix(missing-model): skip redundant metadata probesNot In This PR
This PR intentionally keeps the scope small. It does not change the desktop download implementation, and it does not redesign
downloadModel()into a larger action-union API. Those may be good future directions, but mixing them into this fix would make the review harder and increase the risk of changing unrelated behavior.This PR also does not introduce a new gated-model UI flow, such as separate “open Hugging Face repo” and “try download” actions. That might be worth revisiting later, but there is related core model-download work in progress around Hugging Face authentication, so it seems better to avoid adding a parallel UI model here.
Desktop Follow-Up
Desktop still has a separate model-download path, so this PR should be treated as the first step: restore the core/OSS behavior and centralize the FE-side decision point. It does not make the Desktop download manager gated-aware by itself.
A possible Desktop follow-up would be to extend the Desktop2 bridge payload with an optional gated/access-page hint, for example
gatedRepoUrl, and let the Desktop download manager handle the rest of the flow. That manager could still try the real download first, then surface an auth-required state and open the Hugging Face repo page in a same-session Electron window if the download fails due to access. A retry could then reuse the same session after the user signs in or accepts the model terms.The better long-term direction may be to hand this responsibility to the upcoming core model-download flow with Hugging Face authentication, rather than building a parallel Desktop-specific auth model here. This PR leaves that path open by keeping the gated handling centralized in the FE missing-model download layer without changing Desktop contracts yet.
E2E Coverage
I did not add an E2E test for this flow. The key behavior depends on Hugging Face gated-model access, which requires an external authenticated session and model-specific access state. That makes a reliable E2E test hard to run in CI without either real credentials, a controlled HF fixture, or a mocked browser/network layer that would mostly duplicate the unit coverage here.
For this PR, the behavior is better covered at the unit/component level: the tests pin the metadata classification, store persistence, row action, bulk action, and click-time revalidation behavior without relying on external Hugging Face state. A future E2E would make more sense if we introduce a stable test fixture or a dedicated mocked integration harness for gated model downloads.
Follow-Ups
A lightweight architecture guard could prevent missing-model UI components from calling low-level download helpers directly. I did not include that here because the repo’s existing restricted-import rules are mostly cross-cutting architecture rules, and a domain-specific lint rule should probably be agreed on separately.
Validation
pnpm formatpnpm lintpnpm typecheckpnpm test:unitpnpm knipScreenshot
2026-07-01.2.55.52.mov