feat(templates): migrate cloud template dialog to hub API#10675
feat(templates): migrate cloud template dialog to hub API#10675dante01yoon wants to merge 10 commits intomainfrom
Conversation
🎨 Storybook: ✅ Built — View Storybook |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis pull request integrates hub workflow templates into the cloud environment. It introduces adapter functions to transform hub workflow summaries into template format, extends the API with hub workflow fetching capabilities, and modifies store and composable logic to conditionally load hub workflows when running in cloud mode. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client/<br>useTemplateWorkflows
participant Store as workflowTemplatesStore
participant API as ComfyApi
participant Adapter as hubTemplateAdapter
participant Backend as Backend
Client->>Store: getTemplateByName(name)
alt Template found with shareId
Store-->>Client: EnhancedTemplate{shareId}
Client->>API: getHubWorkflowDetail(shareId)
API->>Backend: GET /hub/workflows/{shareId}
Backend-->>API: HubWorkflowDetail
API-->>Client: workflow_json
else No shareId or not found
Client->>API: fetchTemplateJson(sourceModule)
API-->>Client: workflow_json (default path)
end
Note over Store,Backend: First-time hub template load
Client->>Store: fetchCoreTemplates()
Store->>API: listAllHubWorkflows()
API->>Backend: GET /hub/workflows (paginated)
Backend-->>API: HubWorkflowSummary[]
API-->>Store: HubWorkflowSummary[]
Store->>Adapter: adaptHubWorkflowsToCategories(summaries)
Adapter-->>Store: WorkflowTemplates[] (hub category)
Store-->>Client: coreTemplates cached
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
🎭 Playwright: ✅ 771 passed, 0 failed · 3 flaky📊 Browser Reports
|
📦 Bundle: 5.1 MB gzip 🔴 +6.37 kBDetailsSummary
Category Glance App Entry Points — 22.3 kB (baseline 22.3 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.14 MB (baseline 1.14 MB) • 🔴 +391 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 76.6 kB (baseline 76.6 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 9 added / 9 removed / 2 unchanged Panels & Settings — 484 kB (baseline 484 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 10 added / 10 removed / 12 unchanged User & Accounts — 17.1 kB (baseline 17.1 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 5 added / 5 removed / 2 unchanged Editors & Dialogs — 109 kB (baseline 109 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 2 added / 2 removed UI Components — 60.3 kB (baseline 60.3 kB) • ⚪ 0 BReusable component library chunks
Status: 5 added / 5 removed / 8 unchanged Data & Services — 3.01 MB (baseline 2.96 MB) • 🔴 +46.9 kBStores, services, APIs, and repositories
Status: 13 added / 13 removed / 4 unchanged Utilities & Hooks — 334 kB (baseline 334 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 13 added / 13 removed / 12 unchanged Vendor & Third-Party — 9.8 MB (baseline 9.8 MB) • 🔴 +435 BExternal libraries and shared vendor chunks
Status: 1 added / 1 removed / 15 unchanged Other — 8.45 MB (baseline 8.45 MB) • ⚪ 0 BBundles that do not match a named category
Status: 56 added / 56 removed / 78 unchanged ⚡ Performance Report
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-03-29T05:34:05.078Z",
"gitSha": "9e60e858c4480373d200ced92693da0c16ccf8c2",
"branch": "feat/hub-template-api-migration",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2026.5210000000025,
"styleRecalcs": 10,
"styleRecalcDurationMs": 10.969999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 450.66,
"heapDeltaBytes": 10373836,
"heapUsedBytes": 60694464,
"domNodes": 19,
"jsHeapTotalBytes": 25165824,
"scriptDurationMs": 24.761,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.660000000000036
},
{
"name": "canvas-idle",
"durationMs": 2055.4030000000125,
"styleRecalcs": 11,
"styleRecalcDurationMs": 10.288,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 425.38399999999996,
"heapDeltaBytes": 10744160,
"heapUsedBytes": 60744332,
"domNodes": 22,
"jsHeapTotalBytes": 25165824,
"scriptDurationMs": 28.514000000000003,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "canvas-idle",
"durationMs": 2034.7850000000562,
"styleRecalcs": 10,
"styleRecalcDurationMs": 10.31,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 425.77099999999996,
"heapDeltaBytes": 10680124,
"heapUsedBytes": 60689812,
"domNodes": 20,
"jsHeapTotalBytes": 25690112,
"scriptDurationMs": 30.662000000000003,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2138.231000000019,
"styleRecalcs": 84,
"styleRecalcDurationMs": 50.281000000000006,
"layouts": 12,
"layoutDurationMs": 3.7300000000000004,
"taskDurationMs": 1143.557,
"heapDeltaBytes": 5713044,
"heapUsedBytes": 55829064,
"domNodes": 67,
"jsHeapTotalBytes": 26214400,
"scriptDurationMs": 154.88299999999998,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.659999999999947
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1805.4750000000013,
"styleRecalcs": 77,
"styleRecalcDurationMs": 37.591,
"layouts": 12,
"layoutDurationMs": 3.4070000000000005,
"taskDurationMs": 779.2869999999999,
"heapDeltaBytes": 5399604,
"heapUsedBytes": 55529792,
"domNodes": 60,
"jsHeapTotalBytes": 25952256,
"scriptDurationMs": 138.294,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2056.4729999999827,
"styleRecalcs": 84,
"styleRecalcDurationMs": 44.505,
"layouts": 12,
"layoutDurationMs": 3.384,
"taskDurationMs": 1003.6160000000001,
"heapDeltaBytes": 5770708,
"heapUsedBytes": 55563836,
"domNodes": 67,
"jsHeapTotalBytes": 26214400,
"scriptDurationMs": 146.55400000000003,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.660000000000036
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1734.6889999999462,
"styleRecalcs": 30,
"styleRecalcDurationMs": 20.552,
"layouts": 6,
"layoutDurationMs": 0.717,
"taskDurationMs": 379.91,
"heapDeltaBytes": -153484,
"heapUsedBytes": 48215936,
"domNodes": 80,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 33.912,
"eventListeners": 23,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1744.1579999999703,
"styleRecalcs": 31,
"styleRecalcDurationMs": 21.261000000000003,
"layouts": 6,
"layoutDurationMs": 0.811,
"taskDurationMs": 351.30499999999995,
"heapDeltaBytes": -178000,
"heapUsedBytes": 48238360,
"domNodes": 79,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 31.834,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.660000000000036
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1725.5790000000388,
"styleRecalcs": 31,
"styleRecalcDurationMs": 18.433999999999997,
"layouts": 6,
"layoutDurationMs": 0.5889999999999999,
"taskDurationMs": 339.998,
"heapDeltaBytes": 14552288,
"heapUsedBytes": 64486020,
"domNodes": 78,
"jsHeapTotalBytes": 19398656,
"scriptDurationMs": 28.433999999999994,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "dom-widget-clipping",
"durationMs": 696.5859999999964,
"styleRecalcs": 14,
"styleRecalcDurationMs": 11.242000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 425.705,
"heapDeltaBytes": -2998384,
"heapUsedBytes": 46961936,
"domNodes": 23,
"jsHeapTotalBytes": 15728640,
"scriptDurationMs": 76.271,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "dom-widget-clipping",
"durationMs": 586.4090000000033,
"styleRecalcs": 12,
"styleRecalcDurationMs": 10.347999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 372.439,
"heapDeltaBytes": 8045920,
"heapUsedBytes": 56525120,
"domNodes": 20,
"jsHeapTotalBytes": 13631488,
"scriptDurationMs": 69.47,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000027
},
{
"name": "dom-widget-clipping",
"durationMs": 570.6710000000612,
"styleRecalcs": 13,
"styleRecalcDurationMs": 9.833,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 353.272,
"heapDeltaBytes": 8443924,
"heapUsedBytes": 56979028,
"domNodes": 22,
"jsHeapTotalBytes": 14155776,
"scriptDurationMs": 69.063,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.65999999999999
},
{
"name": "large-graph-idle",
"durationMs": 2035.1330000000019,
"styleRecalcs": 11,
"styleRecalcDurationMs": 9.954,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 638.4590000000001,
"heapDeltaBytes": 13981128,
"heapUsedBytes": 66060752,
"domNodes": -255,
"jsHeapTotalBytes": 15462400,
"scriptDurationMs": 112.049,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.680000000000017
},
{
"name": "large-graph-idle",
"durationMs": 2030.1939999999945,
"styleRecalcs": 10,
"styleRecalcDurationMs": 10.020999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 624.91,
"heapDeltaBytes": 16960868,
"heapUsedBytes": 68630584,
"domNodes": -258,
"jsHeapTotalBytes": 14675968,
"scriptDurationMs": 114.67,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "large-graph-idle",
"durationMs": 2024.2230000000063,
"styleRecalcs": 10,
"styleRecalcDurationMs": 11.124000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 589.5129999999999,
"heapDeltaBytes": 3815172,
"heapUsedBytes": 54312624,
"domNodes": -258,
"jsHeapTotalBytes": 16453632,
"scriptDurationMs": 110.41899999999998,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000073
},
{
"name": "large-graph-pan",
"durationMs": 2183.2990000000054,
"styleRecalcs": 71,
"styleRecalcDurationMs": 18.450000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1277.107,
"heapDeltaBytes": 14512136,
"heapUsedBytes": 75547928,
"domNodes": -258,
"jsHeapTotalBytes": 18087936,
"scriptDurationMs": 473.873,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.659999999999947
},
{
"name": "large-graph-pan",
"durationMs": 2154.988000000003,
"styleRecalcs": 69,
"styleRecalcDurationMs": 16.945999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1202.2309999999998,
"heapDeltaBytes": 4025224,
"heapUsedBytes": 66916304,
"domNodes": -261,
"jsHeapTotalBytes": 15990784,
"scriptDurationMs": 428.82399999999996,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.659999999999947
},
{
"name": "large-graph-pan",
"durationMs": 2134.149999999977,
"styleRecalcs": 68,
"styleRecalcDurationMs": 15.515999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1215.215,
"heapDeltaBytes": 18445420,
"heapUsedBytes": 71819452,
"domNodes": -263,
"jsHeapTotalBytes": 19542016,
"scriptDurationMs": 438.516,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.660000000000036
},
{
"name": "large-graph-zoom",
"durationMs": 3181.0549999999866,
"styleRecalcs": 66,
"styleRecalcDurationMs": 18.155,
"layouts": 60,
"layoutDurationMs": 7.804,
"taskDurationMs": 1481.873,
"heapDeltaBytes": 7881048,
"heapUsedBytes": 62931188,
"domNodes": -266,
"jsHeapTotalBytes": 17240064,
"scriptDurationMs": 570.999,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.660000000000036
},
{
"name": "large-graph-zoom",
"durationMs": 3181.9110000000137,
"styleRecalcs": 65,
"styleRecalcDurationMs": 16.712,
"layouts": 60,
"layoutDurationMs": 7.337000000000001,
"taskDurationMs": 1392.5510000000002,
"heapDeltaBytes": 4648876,
"heapUsedBytes": 59629796,
"domNodes": -267,
"jsHeapTotalBytes": 17502208,
"scriptDurationMs": 509.271,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.659999999999947
},
{
"name": "large-graph-zoom",
"durationMs": 3231.4210000000685,
"styleRecalcs": 65,
"styleRecalcDurationMs": 15.82,
"layouts": 60,
"layoutDurationMs": 7.713,
"taskDurationMs": 1441.455,
"heapDeltaBytes": 8182360,
"heapUsedBytes": 63008796,
"domNodes": -267,
"jsHeapTotalBytes": 17764352,
"scriptDurationMs": 549.388,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.659999999999947
},
{
"name": "minimap-idle",
"durationMs": 2012.2460000000046,
"styleRecalcs": 8,
"styleRecalcDurationMs": 9.423999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 670.4570000000001,
"heapDeltaBytes": 1734804,
"heapUsedBytes": 55481184,
"domNodes": -264,
"jsHeapTotalBytes": 16191488,
"scriptDurationMs": 109.79,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.659999999999947
},
{
"name": "minimap-idle",
"durationMs": 2034.8829999999793,
"styleRecalcs": 8,
"styleRecalcDurationMs": 8.76,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 615.9989999999999,
"heapDeltaBytes": -7646720,
"heapUsedBytes": 47481452,
"domNodes": -263,
"jsHeapTotalBytes": 16248832,
"scriptDurationMs": 104.879,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.659999999999947
},
{
"name": "minimap-idle",
"durationMs": 2030.285000000049,
"styleRecalcs": 8,
"styleRecalcDurationMs": 8.357999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 612.6320000000001,
"heapDeltaBytes": 2472180,
"heapUsedBytes": 55780428,
"domNodes": -263,
"jsHeapTotalBytes": 15667200,
"scriptDurationMs": 107.496,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 564.910999999995,
"styleRecalcs": 47,
"styleRecalcDurationMs": 11.92,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 379.023,
"heapDeltaBytes": -5117808,
"heapUsedBytes": 45056384,
"domNodes": 20,
"jsHeapTotalBytes": 16515072,
"scriptDurationMs": 131.31400000000002,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.65999999999999
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 587.5679999999761,
"styleRecalcs": 48,
"styleRecalcDurationMs": 12.437999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 387.60999999999996,
"heapDeltaBytes": 8000584,
"heapUsedBytes": 56844976,
"domNodes": 21,
"jsHeapTotalBytes": 14417920,
"scriptDurationMs": 131.076,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000027
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 579.979000000094,
"styleRecalcs": 48,
"styleRecalcDurationMs": 19.461000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 383.15600000000006,
"heapDeltaBytes": -3848672,
"heapUsedBytes": 46251760,
"domNodes": 23,
"jsHeapTotalBytes": 15728640,
"scriptDurationMs": 123.668,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000027
},
{
"name": "subgraph-idle",
"durationMs": 1992.761999999999,
"styleRecalcs": 12,
"styleRecalcDurationMs": 11.262999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 447.84600000000006,
"heapDeltaBytes": 9934648,
"heapUsedBytes": 59976056,
"domNodes": 23,
"jsHeapTotalBytes": 25690112,
"scriptDurationMs": 26.841000000000005,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000073
},
{
"name": "subgraph-idle",
"durationMs": 2004.1059999999788,
"styleRecalcs": 11,
"styleRecalcDurationMs": 10.804,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 392.26199999999994,
"heapDeltaBytes": -4949852,
"heapUsedBytes": 43824968,
"domNodes": 22,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 21.941,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.659999999999947
},
{
"name": "subgraph-idle",
"durationMs": 2014.4730000000663,
"styleRecalcs": 11,
"styleRecalcDurationMs": 14.516000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 414.312,
"heapDeltaBytes": -5000912,
"heapUsedBytes": 43866244,
"domNodes": 22,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 23.107,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1705.365999999998,
"styleRecalcs": 76,
"styleRecalcDurationMs": 39.754,
"layouts": 16,
"layoutDurationMs": 4.266,
"taskDurationMs": 730.989,
"heapDeltaBytes": 14555556,
"heapUsedBytes": 63306392,
"domNodes": 63,
"jsHeapTotalBytes": 25427968,
"scriptDurationMs": 108.534,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1985.7340000000363,
"styleRecalcs": 83,
"styleRecalcDurationMs": 47.769,
"layouts": 16,
"layoutDurationMs": 4.595,
"taskDurationMs": 947.125,
"heapDeltaBytes": 13795388,
"heapUsedBytes": 62599032,
"domNodes": 72,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 106.726,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1975.6360000000086,
"styleRecalcs": 84,
"styleRecalcDurationMs": 49.719,
"layouts": 16,
"layoutDurationMs": 4.88,
"taskDurationMs": 930.8499999999999,
"heapDeltaBytes": 13845924,
"heapUsedBytes": 62712532,
"domNodes": 73,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 109.77199999999999,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "viewport-pan-sweep",
"durationMs": 8265.70399999997,
"styleRecalcs": 253,
"styleRecalcDurationMs": 47.20300000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4066.979,
"heapDeltaBytes": 7543548,
"heapUsedBytes": 67544456,
"domNodes": -256,
"jsHeapTotalBytes": 19398656,
"scriptDurationMs": 1330.179,
"eventListeners": -111,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000073
},
{
"name": "viewport-pan-sweep",
"durationMs": 8188.961000000007,
"styleRecalcs": 252,
"styleRecalcDurationMs": 44.661,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3893.228,
"heapDeltaBytes": 13673800,
"heapUsedBytes": 73435032,
"domNodes": -253,
"jsHeapTotalBytes": 19398656,
"scriptDurationMs": 1287.1809999999998,
"eventListeners": -111,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.660000000000036
},
{
"name": "viewport-pan-sweep",
"durationMs": 8162.9500000000235,
"styleRecalcs": 250,
"styleRecalcDurationMs": 44.001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3856.905,
"heapDeltaBytes": 20435924,
"heapUsedBytes": 70979240,
"domNodes": -256,
"jsHeapTotalBytes": 19542016,
"scriptDurationMs": 1252.166,
"eventListeners": -107,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999989
},
{
"name": "vue-large-graph-idle",
"durationMs": 12721.116999999993,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12706.222999999998,
"heapDeltaBytes": -44554588,
"heapUsedBytes": 161521392,
"domNodes": -8331,
"jsHeapTotalBytes": 23945216,
"scriptDurationMs": 639.0590000000001,
"eventListeners": -16460,
"totalBlockingTimeMs": 0,
"frameDurationMs": 18.329999999999927
},
{
"name": "vue-large-graph-idle",
"durationMs": 12316.63199999997,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12302.782000000001,
"heapDeltaBytes": -44175896,
"heapUsedBytes": 162507620,
"domNodes": -8331,
"jsHeapTotalBytes": 24469504,
"scriptDurationMs": 596.968,
"eventListeners": -16462,
"totalBlockingTimeMs": 0,
"frameDurationMs": 19.990000000000144
},
{
"name": "vue-large-graph-idle",
"durationMs": 12611.362999999983,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12597.179,
"heapDeltaBytes": -40938764,
"heapUsedBytes": 163411732,
"domNodes": -8331,
"jsHeapTotalBytes": 24207360,
"scriptDurationMs": 652.6110000000001,
"eventListeners": -16462,
"totalBlockingTimeMs": 0,
"frameDurationMs": 18.33000000000029
},
{
"name": "vue-large-graph-pan",
"durationMs": 14527.308000000005,
"styleRecalcs": 66,
"styleRecalcDurationMs": 14.786999999999995,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 14497.023,
"heapDeltaBytes": -23944112,
"heapUsedBytes": 173000596,
"domNodes": -8331,
"jsHeapTotalBytes": 25169920,
"scriptDurationMs": 870.171,
"eventListeners": -16458,
"totalBlockingTimeMs": 0,
"frameDurationMs": 18.33000000000029
},
{
"name": "vue-large-graph-pan",
"durationMs": 14603.364,
"styleRecalcs": 66,
"styleRecalcDurationMs": 14.790999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 14581.658999999998,
"heapDeltaBytes": -33232700,
"heapUsedBytes": 171909660,
"domNodes": -5048,
"jsHeapTotalBytes": -4714496,
"scriptDurationMs": 952.9700000000001,
"eventListeners": -16456,
"totalBlockingTimeMs": 0,
"frameDurationMs": 19.98999999999978
},
{
"name": "vue-large-graph-pan",
"durationMs": 14572.066000000064,
"styleRecalcs": 69,
"styleRecalcDurationMs": 15.245000000000008,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 14550.971000000001,
"heapDeltaBytes": -57401280,
"heapUsedBytes": 148247524,
"domNodes": -8333,
"jsHeapTotalBytes": 614400,
"scriptDurationMs": 867.629,
"eventListeners": -16490,
"totalBlockingTimeMs": 0,
"frameDurationMs": 18.329999999999927
},
{
"name": "workflow-execution",
"durationMs": 453.69300000004387,
"styleRecalcs": 22,
"styleRecalcDurationMs": 28.458000000000002,
"layouts": 5,
"layoutDurationMs": 1.408,
"taskDurationMs": 133.71699999999998,
"heapDeltaBytes": 4677148,
"heapUsedBytes": 50621428,
"domNodes": 170,
"jsHeapTotalBytes": 262144,
"scriptDurationMs": 26.429000000000002,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "workflow-execution",
"durationMs": 443.70199999991655,
"styleRecalcs": 17,
"styleRecalcDurationMs": 23.976,
"layouts": 5,
"layoutDurationMs": 1.227,
"taskDurationMs": 121.697,
"heapDeltaBytes": 4478932,
"heapUsedBytes": 53254232,
"domNodes": 158,
"jsHeapTotalBytes": 262144,
"scriptDurationMs": 29.067000000000004,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998
},
{
"name": "workflow-execution",
"durationMs": 455.5480000000216,
"styleRecalcs": 18,
"styleRecalcDurationMs": 23.056,
"layouts": 5,
"layoutDurationMs": 1.39,
"taskDurationMs": 131.01200000000003,
"heapDeltaBytes": -5212992,
"heapUsedBytes": 46209084,
"domNodes": 158,
"jsHeapTotalBytes": 1835008,
"scriptDurationMs": 30.299,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.660000000000036
}
]
} |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/platform/workflow/templates/adapters/hubTemplateAdapter.ts`:
- Around line 52-59: The adapter currently hardcodes mediaType: 'image' and
mediaSubtype: 'webp' which breaks video previews; change it to use the thumbnail
type/subtype from the incoming summary instead: set mediaType to
summary.thumbnail_type, and set mediaSubtype to summary.thumbnail_subtype if
present, otherwise default to 'mp4' when summary.thumbnail_type === 'video' and
'webp' for images; keep using mapThumbnailVariant(summary.thumbnail_type) for
thumbnailVariant and return thumbnailUrl/thumbnailComparisonUrl as-is so video
thumbnails follow the correct rendering path.
- Around line 74-80: The adapter adaptHubWorkflowsToCategories currently
hardcodes the category title 'All'; change it to accept a localized title or use
the shared i18n helper instead of English text. Update the function signature
(adaptHubWorkflowsToCategories) to take an extra parameter like allTitle: string
(or import/use the project's i18n get/t helper) and replace the literal 'All'
with that parameter/helper, and then update all call sites to pass in the
localized string (from vue-i18n or src/locales/en/main.json) so no user-facing
text is hardcoded in the adapter.
In `@src/platform/workflow/templates/composables/useTemplateUrlLoader.test.ts`:
- Around line 38-51: The tests pin isCloud to false and always return undefined
from useWorkflowTemplatesStore().getTemplateByShareId, so the new cloud/shareId
path in useTemplateUrlLoader is never exercised; update the test suite to
include at least two cloud-mode cases: one where the distribution flag (isCloud)
is set to true and useWorkflowTemplatesStore().getTemplateByShareId resolves a
valid template to verify successful cloud shareId loading, and another where
getTemplateByShareId throws/rejects to assert that a real load failure surfaces
as an error (not treated as a “not found” fallback). Implement these by making
the mocks for '@/platform/distribution/types' (isCloud) and
'@/platform/workflow/templates/repositories/workflowTemplatesStore' configurable
per test (override the mock or use vi.doMock/vi.mocked and set return values for
getTemplateByShareId) and add test cases that call useTemplateUrlLoader (the
composable under test) to assert the resolved template and the thrown error
behavior.
In `@src/platform/workflow/templates/composables/useTemplateUrlLoader.ts`:
- Around line 118-128: The code is retrying every failed load as a shareId
lookup which can repeat the same failing call because loadWorkflowTemplate
returns only false for all errors; update the logic so you only retry by shareId
when the original failure is a genuine "not found" (not any error) or when the
store mapping yields a different name than the original templateParam to avoid
re-invoking the same call: either (A) change loadWorkflowTemplate to return a
richer result/error enum (e.g., SUCCESS / NOT_FOUND / ERROR) and only call
templateWorkflows.loadWorkflowTemplate(templateByShareId.name, ...) when the
result was NOT_FOUND, or (B) at minimum guard the retry with
templateByShareId.name !== templateParam so you don’t repeat the identical call;
adjust callers in useTemplateUrlLoader, loadWorkflowTemplate, and
useWorkflowTemplatesStore/getTemplateByShareId accordingly.
In `@src/platform/workflow/templates/composables/useTemplateWorkflows.ts`:
- Around line 136-139: When mapping hub templates you currently overwrite
sourceModule ('hub' -> 'default') but lose the matched template so the workflow
name falls back to id; keep a reference to the matched template (e.g., save
matchedTemplate or template variable) when you detect sourceModule === 'hub' and
pass matchedTemplate.title into the code path that normalizes or loads the fetch
source (the call that builds the fetch source / normalizeFetchSource or the
loadWorkflowFromSource invocation) so the resulting workflow uses template.title
as its name instead of the id.
In `@src/platform/workflow/templates/repositories/workflowTemplatesStore.ts`:
- Around line 486-497: When handling the cloud branch in workflowTemplatesStore
(inside the isCloud block where api.listAllHubWorkflows() and
adaptHubWorkflowsToCategories are used), ensure hub-sourced categories are
treated as core by mapping moduleName 'hub' to the core source before setting
coreTemplates.value; e.g., after adaptHubWorkflowsToCategories(summaries)
iterate the resulting categories and replace any category.source/moduleName ===
'hub' with the core identifier used elsewhere (e.g., 'default') so
groupedTemplates and navGroupedTemplates recognize them as core and avoid
creating an Extensions > hub entry; also keep existing knownTemplateNames
handling.
In `@src/scripts/api.ts`:
- Around line 869-876: The do/while in listAllHubWorkflows() can loop forever if
the API returns a repeated next_cursor; modify listAllHubWorkflows to track seen
cursors (e.g., a Set) and/or enforce a maximum page count, and break with an
error or return when a repeated cursor or max pages is reached; reference the
variables cursor and res.next_cursor and the method listHubWorkflows to locate
where to add the guard and the early-exit logic.
🪄 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: CHILL
Plan: Pro
Run ID: 9e9ab91b-4390-4fe3-8247-5af775092716
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (10)
package.jsonsrc/platform/workflow/templates/adapters/hubTemplateAdapter.test.tssrc/platform/workflow/templates/adapters/hubTemplateAdapter.tssrc/platform/workflow/templates/composables/useTemplateUrlLoader.test.tssrc/platform/workflow/templates/composables/useTemplateUrlLoader.tssrc/platform/workflow/templates/composables/useTemplateWorkflows.test.tssrc/platform/workflow/templates/composables/useTemplateWorkflows.tssrc/platform/workflow/templates/repositories/workflowTemplatesStore.tssrc/platform/workflow/templates/types/template.tssrc/scripts/api.ts
| // Mock the workflow templates store | ||
| vi.mock( | ||
| '@/platform/workflow/templates/repositories/workflowTemplatesStore', | ||
| () => ({ | ||
| useWorkflowTemplatesStore: vi.fn(() => ({ | ||
| getTemplateByShareId: vi.fn().mockReturnValue(undefined) | ||
| })) | ||
| }) | ||
| ) | ||
|
|
||
| // Mock distribution (non-cloud for tests) | ||
| vi.mock('@/platform/distribution/types', () => ({ | ||
| isCloud: false | ||
| })) |
There was a problem hiding this comment.
This suite still never exercises the new cloud/shareId path.
isCloud is pinned to false and getTemplateByShareId() always returns undefined, so the behavior added in this PR is fully mocked out here. Please add at least one cloud-mode case that resolves a template by shareId, plus one case that proves a real load failure is not treated like a not-found fallback. As per coding guidelines, "Do not write tests that just test the mocks in Vitest; ensure tests fail when code behaves unexpectedly."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/platform/workflow/templates/composables/useTemplateUrlLoader.test.ts`
around lines 38 - 51, The tests pin isCloud to false and always return undefined
from useWorkflowTemplatesStore().getTemplateByShareId, so the new cloud/shareId
path in useTemplateUrlLoader is never exercised; update the test suite to
include at least two cloud-mode cases: one where the distribution flag (isCloud)
is set to true and useWorkflowTemplatesStore().getTemplateByShareId resolves a
valid template to verify successful cloud shareId loading, and another where
getTemplateByShareId throws/rejects to assert that a real load failure surfaces
as an error (not treated as a “not found” fallback). Implement these by making
the mocks for '@/platform/distribution/types' (isCloud) and
'@/platform/workflow/templates/repositories/workflowTemplatesStore' configurable
per test (override the mock or use vi.doMock/vi.mocked and set return values for
getTemplateByShareId) and add test cases that call useTemplateUrlLoader (the
composable under test) to assert the resolved template and the thrown error
behavior.
src/platform/workflow/templates/composables/useTemplateUrlLoader.ts
Outdated
Show resolved
Hide resolved
| // Hub templates use sourceModule 'hub' | ||
| if (sourceModule === 'hub') { | ||
| sourceModule = 'default' | ||
| } |
There was a problem hiding this comment.
Use the hub template title when this path loads a workflow.
After introducing sourceModule === 'hub', the later load still derives the workflow name from id. For hub templates that id is the share_id, so the loaded workflow gets an ID-derived name instead of the human-readable template.title. Keep the matched template around and pass its title through when you normalize the fetch source.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/platform/workflow/templates/composables/useTemplateWorkflows.ts` around
lines 136 - 139, When mapping hub templates you currently overwrite sourceModule
('hub' -> 'default') but lose the matched template so the workflow name falls
back to id; keep a reference to the matched template (e.g., save matchedTemplate
or template variable) when you detect sourceModule === 'hub' and pass
matchedTemplate.title into the code path that normalizes or loads the fetch
source (the call that builds the fetch source / normalizeFetchSource or the
loadWorkflowFromSource invocation) so the resulting workflow uses template.title
as its name instead of the id.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/scripts/api.ts (1)
865-872:⚠️ Potential issue | 🟠 MajorGuard
listAllHubWorkflows()against repeated cursors.If
/hub/workflowsever returns the samenext_cursortwice, this loop never terminates and the dialog keeps issuing requests. Track seen cursors or cap page count before advancing.🛡️ Suggested guard
async listAllHubWorkflows(): Promise<HubWorkflowSummary[]> { const all: HubWorkflowSummary[] = [] + const seenCursors = new Set<string>() let cursor: string | undefined do { + if (cursor && seenCursors.has(cursor)) { + throw new Error( + `Hub workflow pagination loop detected at cursor: ${cursor}` + ) + } + if (cursor) { + seenCursors.add(cursor) + } const page = await this.fetchHubWorkflowPage(100, cursor) all.push(...(page.workflows as HubWorkflowSummary[])) cursor = page.next_cursor || undefined } while (cursor) return all }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/scripts/api.ts` around lines 865 - 872, The loop in listAllHubWorkflows can hang if fetchHubWorkflowPage returns the same page.next_cursor repeatedly; update the function to guard against repeated cursors by tracking seen cursors (e.g., a Set) and breaking if next_cursor is already seen, or impose a hard page/iteration cap before advancing; reference the cursor variable, page.next_cursor and fetchHubWorkflowPage to locate where to add the check and exit the loop safely.
🧹 Nitpick comments (1)
src/scripts/api.ts (1)
851-853: Include request context in these thrown errors.Both branches only surface the status code, so upstream logs can’t tell which page request or share ID failed. Include
statusTextplus the request context before rethrowing.As per coding guidelines "Provide user-friendly and actionable error messages" and "Implement proper error propagation".🛠️ Suggested tweak
if (!res.ok) { - throw new Error(`Failed to list hub workflows: ${res.status}`) + throw new Error( + `Failed to list hub workflows (limit=${limit}, cursor=${ + cursor ?? 'initial' + }): ${res.status} ${res.statusText}` + ) } @@ if (!res.ok) { - throw new Error(`Failed to get hub workflow detail: ${res.status}`) + throw new Error( + `Failed to get hub workflow detail for shareId=${shareId}: ${res.status} ${res.statusText}` + ) }Also applies to: 883-885
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/scripts/api.ts` around lines 851 - 853, The thrown errors only include res.status; update the error messages where they say "Failed to list hub workflows" (and the similar throw at 883-885) to include res.statusText plus contextual request information (e.g., page, shareId, or the full request URL/endpoint/params) so logs show which page/share failed; locate the throws around the "Failed to list hub workflows" message in src/scripts/api.ts and change the Error construction to combine a descriptive message, res.status, res.statusText, and the relevant local variables (page, shareId or url) before rethrowing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/scripts/api.ts`:
- Around line 865-872: The loop in listAllHubWorkflows can hang if
fetchHubWorkflowPage returns the same page.next_cursor repeatedly; update the
function to guard against repeated cursors by tracking seen cursors (e.g., a
Set) and breaking if next_cursor is already seen, or impose a hard
page/iteration cap before advancing; reference the cursor variable,
page.next_cursor and fetchHubWorkflowPage to locate where to add the check and
exit the loop safely.
---
Nitpick comments:
In `@src/scripts/api.ts`:
- Around line 851-853: The thrown errors only include res.status; update the
error messages where they say "Failed to list hub workflows" (and the similar
throw at 883-885) to include res.statusText plus contextual request information
(e.g., page, shareId, or the full request URL/endpoint/params) so logs show
which page/share failed; locate the throws around the "Failed to list hub
workflows" message in src/scripts/api.ts and change the Error construction to
combine a descriptive message, res.status, res.statusText, and the relevant
local variables (page, shareId or url) before rethrowing.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a417a318-1991-41f4-9046-808a7e076614
📒 Files selected for processing (1)
src/scripts/api.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/composables/useTemplateFiltering.ts`:
- Around line 127-130: The watcher on debouncedSearchQuery can cause
out-of-order hub results because multiple searchHubWorkflows calls may resolve
in any order; update the logic to sequence or cancel in-flight requests before
committing results: either add an AbortController signal param to
workflowTemplatesStore.searchHubWorkflows and abort the previous controller when
starting a new watch invocation, or implement a monotonically increasing
requestId (e.g., lastHubSearchId) inside workflowTemplatesStore that
searchHubWorkflows captures and only commits results if the id matches the
latest; ensure this also coordinates with loadHubNextPage appends so that only
the current search's next-page results are applied to the same active requestId.
- Around line 126-131: The cloud path currently derives facets and counts from
the partially-populated templatesArray causing incomplete
availableModels/availableUseCases and wrong filteredCount/totalCount; update the
cloud branch (isCloud) to either (A) fetch the full hub result set before
deriving client-side filters by implementing a paginated fetchAllHubWorkflows on
workflowTemplatesStore and only computing facets/counts after that completes, or
(B) delegate facet and count computation server-side and consume server-returned
availableModels/availableUseCases/filteredCount/totalCount from
workflowTemplatesStore.searchHubWorkflows; modify the watch on
debouncedSearchQuery and any usages of templatesArray, availableModels,
availableUseCases, filteredCount, and totalCount so they use the chosen approach
(fetchAllHubWorkflows completion or server-provided values) instead of the
incremental templatesArray.
🪄 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: CHILL
Plan: Pro
Run ID: 2cf790d4-ca77-43da-a73a-4383b22af945
📒 Files selected for processing (4)
src/components/custom/widget/WorkflowTemplateSelectorDialog.vuesrc/composables/useTemplateFiltering.tssrc/platform/workflow/templates/repositories/workflowTemplatesStore.tssrc/scripts/api.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/scripts/api.ts
- src/platform/workflow/templates/repositories/workflowTemplatesStore.ts
| // On cloud, delegate search to the hub API instead of Fuse.js | ||
| if (isCloud) { | ||
| const workflowTemplatesStore = useWorkflowTemplatesStore() | ||
| watch(debouncedSearchQuery, (query) => { | ||
| void workflowTemplatesStore.searchHubWorkflows(query.trim()) | ||
| }) |
There was a problem hiding this comment.
Cloud filtering is now running on an incomplete dataset.
In cloud mode, templatesArray only contains the hub pages fetched so far, but the rest of this composable still derives facets and counts from it as if it were the full result set. That makes availableModels, availableUseCases, filteredCount/totalCount, and downstream empty-state behavior incomplete until the user scrolls through every page, so valid hub templates can be missing from filters/results. Either load the full hub result set before applying client-side filters, or move those filters/counts server-side.
Also applies to: 139-142
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/composables/useTemplateFiltering.ts` around lines 126 - 131, The cloud
path currently derives facets and counts from the partially-populated
templatesArray causing incomplete availableModels/availableUseCases and wrong
filteredCount/totalCount; update the cloud branch (isCloud) to either (A) fetch
the full hub result set before deriving client-side filters by implementing a
paginated fetchAllHubWorkflows on workflowTemplatesStore and only computing
facets/counts after that completes, or (B) delegate facet and count computation
server-side and consume server-returned
availableModels/availableUseCases/filteredCount/totalCount from
workflowTemplatesStore.searchHubWorkflows; modify the watch on
debouncedSearchQuery and any usages of templatesArray, availableModels,
availableUseCases, filteredCount, and totalCount so they use the chosen approach
(fetchAllHubWorkflows completion or server-provided values) instead of the
incremental templatesArray.
| if (isCloud) { | ||
| const workflowTemplatesStore = useWorkflowTemplatesStore() | ||
| watch(debouncedSearchQuery, (query) => { | ||
| void workflowTemplatesStore.searchHubWorkflows(query.trim()) |
There was a problem hiding this comment.
Guard hub search against out-of-order responses.
This watcher can start a new searchHubWorkflows() before the previous request finishes. Since the store replaces shared template state when each response resolves, a slower older query can overwrite a newer one, and it can also interleave with loadHubNextPage() appends from the dialog. Please add request sequencing or cancellation before committing hub search results.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/composables/useTemplateFiltering.ts` around lines 127 - 130, The watcher
on debouncedSearchQuery can cause out-of-order hub results because multiple
searchHubWorkflows calls may resolve in any order; update the logic to sequence
or cancel in-flight requests before committing results: either add an
AbortController signal param to workflowTemplatesStore.searchHubWorkflows and
abort the previous controller when starting a new watch invocation, or implement
a monotonically increasing requestId (e.g., lastHubSearchId) inside
workflowTemplatesStore that searchHubWorkflows captures and only commits results
if the id matches the latest; ensure this also coordinates with loadHubNextPage
appends so that only the current search's next-page results are applied to the
same active requestId.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/scripts/api.ts (1)
870-874:⚠️ Potential issue | 🟠 MajorGuard pagination against repeated cursors.
Line 870 loops while
cursoris truthy. If the backend repeatsnext_cursor, this can become an infinite loop. Add repeated-cursor protection (and optionally a page cap) to fail fast.🛡️ Suggested guard
async listAllHubWorkflows(): Promise<HubWorkflowSummary[]> { const all: HubWorkflowSummary[] = [] + const seenCursors = new Set<string>() let cursor: string | undefined do { + if (cursor && seenCursors.has(cursor)) { + throw new Error( + `Hub workflow pagination loop detected at cursor: ${cursor}` + ) + } + if (cursor) { + seenCursors.add(cursor) + } const page = await this.fetchHubWorkflowPage(100, cursor) all.push(...(page.workflows as HubWorkflowSummary[])) cursor = page.next_cursor || undefined } while (cursor) return all }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/scripts/api.ts` around lines 870 - 874, The pagination loop calling this.fetchHubWorkflowPage(100, cursor) can infinite-loop if the backend returns the same next_cursor repeatedly; modify the loop to track seen cursors (e.g., a Set of previous cursor values) and detect repeats before assigning cursor = page.next_cursor, and either throw an error or break when a repeat is detected; additionally add an optional maxPages counter (e.g., maxPages = 1000) and increment per iteration to fail fast if exceeded. Ensure the change is applied around the do/while loop that pushes page.workflows and references next_cursor so the guard prevents repeated-cursor progression and caps total pages.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/scripts/api.ts`:
- Around line 849-850: The default status filter currently uses
query.set('status', 'pending,approved,rejected,deprecated') which exposes
non-production workflows; change it to only 'approved' by default (replace the
value passed to query.set) and move the broader list behind a dev/test flag or
environment check (e.g., use a feature flag or NODE_ENV check) so only
development/test runs set 'pending,approved,rejected,deprecated'; ensure this
logic is implemented where query.set('status', ...) is called so production
always receives only 'approved'.
---
Duplicate comments:
In `@src/scripts/api.ts`:
- Around line 870-874: The pagination loop calling
this.fetchHubWorkflowPage(100, cursor) can infinite-loop if the backend returns
the same next_cursor repeatedly; modify the loop to track seen cursors (e.g., a
Set of previous cursor values) and detect repeats before assigning cursor =
page.next_cursor, and either throw an error or break when a repeat is detected;
additionally add an optional maxPages counter (e.g., maxPages = 1000) and
increment per iteration to fail fast if exceeded. Ensure the change is applied
around the do/while loop that pushes page.workflows and references next_cursor
so the guard prevents repeated-cursor progression and caps total pages.
🪄 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: CHILL
Plan: Pro
Run ID: b885a9ff-e3be-436e-8e81-caae02702615
📒 Files selected for processing (2)
src/platform/workflow/templates/repositories/workflowTemplatesStore.tssrc/scripts/api.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/platform/workflow/templates/repositories/workflowTemplatesStore.ts
src/scripts/api.ts
Outdated
| // TODO: Remove after production has approved data — fetch all statuses for testing | ||
| query.set('status', 'pending,approved,rejected,deprecated') |
There was a problem hiding this comment.
Default status filter includes non-approved workflows.
Line 849 currently requests pending, rejected, and deprecated in addition to approved. That can expose non-production templates in the cloud dialog if downstream filtering changes or regresses. Default this to approved and gate broader status sets behind a dev/test flag.
🔧 Suggested change
- // TODO: Remove after production has approved data — fetch all statuses for testing
- query.set('status', 'pending,approved,rejected,deprecated')
+ const includeAllStatusesForTesting =
+ getDevOverride<boolean>('hubTemplates.includeAllStatuses') === true
+ query.set(
+ 'status',
+ includeAllStatusesForTesting
+ ? 'pending,approved,rejected,deprecated'
+ : 'approved'
+ )As per coding guidelines, "Validate trusted sources before processing".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/scripts/api.ts` around lines 849 - 850, The default status filter
currently uses query.set('status', 'pending,approved,rejected,deprecated') which
exposes non-production workflows; change it to only 'approved' by default
(replace the value passed to query.set) and move the broader list behind a
dev/test flag or environment check (e.g., use a feature flag or NODE_ENV check)
so only development/test runs set 'pending,approved,rejected,deprecated'; ensure
this logic is implemented where query.set('status', ...) is called so production
always receives only 'approved'.
Replace static index.json template loading on cloud with the hub
workflows API (GET /api/hub/workflows for listing, GET
/api/hub/workflows/{share_id} for workflow JSON).
- Add listHubWorkflows, listAllHubWorkflows, getHubWorkflowDetail to api.ts
- Create adapter to convert HubWorkflowSummary to TemplateInfo
- Branch on isCloud in workflowTemplatesStore.fetchCoreTemplates()
- Update thumbnail URL resolution for absolute hub URLs
- Update workflow JSON loading to use detail API via shareId
- Add shareId-based fallback in URL template loader
…etch Replace the generic listHubWorkflows method (which accepted search/tag params) with a private fetchHubWorkflowPage that only takes limit and cursor. This prevents accidental filtering when loading all templates.
Production has no approved data yet — temporarily include all statuses (pending, approved, rejected, deprecated) so the template dialog can be tested locally.
Replace loading all hub workflows at once with cursor-based pagination. The store now loads one page at a time and fetches more on scroll via the intersection observer. Search on cloud delegates to the API search param instead of client-side Fuse.js, resetting loaded data with server-filtered results.
Revert server-side pagination and search (Phase 2) since the backend does not yet support model filter, runs-on filter, or sort params. Phase 1 approach: load all hub workflows upfront via listAllHubWorkflows, keep all existing client-side filtering/sorting/search (Fuse.js) intact. This preserves the current UX while switching data source to the hub API.
- Remove temporary status override for testing - Add 6 unit tests for workflowTemplatesStore cloud/local paths (hub loading, field adaptation, shareId lookup, error handling) - Add E2E regression tests for template dialog: open/cards, filters, search, template loading, navigation, reopen behavior
5f5cef9 to
b48ed9b
Compare
- Derive mediaType/mediaSubtype from thumbnail_type (video support) - Accept localized title in adaptHubWorkflowsToCategories instead of hardcoded 'All' - URL loader: resolve template by name or shareId before loading instead of retrying on every failure - Guard listAllHubWorkflows against cursor pagination loops
Unit tests: - Video thumbnail type → mediaType/mediaSubtype mapping - Image thumbnail type defaults - Hub absolute thumbnail URL resolution (index 1 and 2) - Static fallback when thumbnailUrl is absent E2E tests: - Remove duplicates with existing templates.spec.ts - Add sort dropdown options test - Add navigation switching test - Add thumbnail rendering verification - Add hub API mock test for cloud build validation
- Add MIT license to @comfyorg/ingest-types package.json (fixes validate-licenses CI check) - Fix search input selector: ComboboxInput renders role="combobox", not role="searchbox". Use getByPlaceholder(/search/i) instead. - Increase debounce wait from 300ms to 500ms for CI stability.
Add E2E test that confirms local (non-cloud) builds never call /api/hub/workflows and load templates from static index.json instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
| /** | ||
| * Regression tests for the template dialog hub API migration. | ||
| * | ||
| * These verify behavior that is NOT covered by the existing templates.spec.ts, | ||
| * focusing on the hub API data path and the adapter integration. | ||
| */ | ||
| test.describe( | ||
| 'Template Hub Migration — Regression', |
There was a problem hiding this comment.
I think it would still make sense to just put these in the existing template test files/modules/folders instead of distinguishing them as separate.
| await expect(comfyPage.templates.content).toBeVisible() | ||
| await comfyPage.templates.expectMinimumCardCount(1) | ||
|
|
||
| const dialog = comfyPage.page.getByRole('dialog') |
There was a problem hiding this comment.
Shouldn't the dialog and buttons be page objects like comfyPage.templates.popularBtn? Same goes for other test cases in this file
| expect(hubRequests).toHaveLength(0) | ||
| }) | ||
|
|
||
| test('hub API mock: dialog renders hub workflow data', async ({ |
There was a problem hiding this comment.
We might want to put this data in the fixtures/data and potentially the mock routing in the templates page object or helper if appropriate.
| // The hub API is only called when isCloud is true. | ||
| // This test verifies the route interception works for when the | ||
| // cloud build is running. On local builds, the template dialog | ||
| // uses static files instead, so this mock won't be hit. | ||
| // The test still validates that the mock setup and route interception | ||
| // pattern works correctly for cloud E2E testing. | ||
| await comfyPage.command.executeCommand('Comfy.BrowseTemplates') | ||
| await expect(comfyPage.templates.content).toBeVisible() | ||
| await comfyPage.templates.expectMinimumCardCount(1) |
There was a problem hiding this comment.
You can now write tests targeting cloud after #10546
Summary
index.jsontemplate loading on cloud with the hub workflows API (GET /api/hub/workflowsfor listing,GET /api/hub/workflows/{share_id}for workflow JSON)HubWorkflowSummary→TemplateInfo, preserving all existing client-side filtering/sorting/paginationChanges
src/scripts/api.tslistHubWorkflows(),listAllHubWorkflows(),getHubWorkflowDetail()with Zod validationsrc/platform/workflow/templates/adapters/hubTemplateAdapter.tsHubWorkflowSummary→TemplateInfo+ category wrappersrc/platform/workflow/templates/types/template.tsthumbnailUrl,thumbnailComparisonUrl,shareId,profiletoTemplateInfosrc/platform/workflow/templates/repositories/workflowTemplatesStore.tsisCloudinfetchCoreTemplates(), addgetTemplateByShareId()src/platform/workflow/templates/composables/useTemplateWorkflows.tssrc/platform/workflow/templates/composables/useTemplateUrlLoader.tsshareId-based fallback lookup for cloudTest plan
┆Issue is synchronized with this Notion page by Unito