[backport cloud/1.49] feat(first-run-tour): resolve tour roles from pinned nodes - #14635
Conversation
> **Stack — 3 of 6.** Rebuilt from scratch after the review on #13725, split into short PRs. > Based on #13290 (coachmark engine); each PR stacks on the one before it. > > 1. #14140 — engine: canvas targets for the coachmark tour > 2. #14141 — Getting Started screen + entry gating > 3. #14142 — role pins + live drift guard ← this PR > 4. #14143 — the tour itself + e2e walk > 5. #14144 — post-tour nudge > 6. #14145 — unpinned workflows (share / template links) ## Summary Given a loaded graph, which nodes does the tour point at? For the eight templates the Getting Started grid can show, the answer is pinned by hand — three node ids each. The previous attempt inferred this with a 544-line resolver that parsed serialized JSON; this is 161 lines that read the live graph. ## Changes - `TOUR_ROLE_PINS` maps each supported template to its source / prompt / sink. The type is exhaustive over both grid id lists, so a card without pins is a compile error. - Pins carry the node type alongside the id, and resolution checks both — a renumbered template degrades to null instead of spotlighting the wrong node. - All three roles host-map through the same path, so a pin inside a subgraph resolves to the root node that hosts it. - A Playwright guard fetches every pinned template from the backend and asserts each pinned node still exists with its expected type. ## Review focus No vendored fixtures. The old PR carried 12,723 lines of copied template JSON; the drift guard replaces it by checking against what the backend actually serves, so an upstream rename fails CI instead of silently mis-targeting a spotlight. --------- Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: AustinMroz <austin@comfy.org>
📄 Knowledge reviewDosu skipped reviewing this PR because your organization has used its |
🎭 Playwright: ✅ 1776 passed, 0 failed · 4 flaky📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle Size
⚡ Performance Report
Absolute values
Raw data{
"timestamp": "2026-08-04T00:29:47.322Z",
"gitSha": "5aec7bd330dcd6797624b4c5e09789b3dd8ae608",
"branch": "backport-14142-to-cloud-1.49",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2080.2610000000072,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.322,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 556.466,
"heapDeltaBytes": 5085992,
"heapUsedBytes": 72823864,
"domNodes": 18,
"jsHeapTotalBytes": 20307968,
"scriptDurationMs": 22.652999999999995,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-idle",
"durationMs": 2073.7179999999853,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.279,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 473.161,
"heapDeltaBytes": 5195808,
"heapUsedBytes": 73171868,
"domNodes": 20,
"jsHeapTotalBytes": 20045824,
"scriptDurationMs": 16.431,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1974.5530000000144,
"styleRecalcs": 79,
"styleRecalcDurationMs": 45.227000000000004,
"layouts": 12,
"layoutDurationMs": 5.694999999999999,
"taskDurationMs": 976.6350000000001,
"heapDeltaBytes": -21200180,
"heapUsedBytes": 46756068,
"domNodes": 30,
"jsHeapTotalBytes": 21483520,
"scriptDurationMs": 124.276,
"eventListeners": -146,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1897.5469999999746,
"styleRecalcs": 75,
"styleRecalcDurationMs": 36.318000000000005,
"layouts": 12,
"layoutDurationMs": 3.636,
"taskDurationMs": 880.562,
"heapDeltaBytes": -19418668,
"heapUsedBytes": 48393172,
"domNodes": -279,
"jsHeapTotalBytes": 20697088,
"scriptDurationMs": 116.87100000000002,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1757.2959999999966,
"styleRecalcs": 32,
"styleRecalcDurationMs": 17.822000000000003,
"layouts": 6,
"layoutDurationMs": 0.7690000000000001,
"taskDurationMs": 383.463,
"heapDeltaBytes": 7871152,
"heapUsedBytes": 75842612,
"domNodes": 79,
"jsHeapTotalBytes": 20570112,
"scriptDurationMs": 19.014999999999997,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1764.440000000036,
"styleRecalcs": 29,
"styleRecalcDurationMs": 15.866999999999999,
"layouts": 6,
"layoutDurationMs": 0.6680000000000001,
"taskDurationMs": 380.90900000000005,
"heapDeltaBytes": 7894492,
"heapUsedBytes": 75854660,
"domNodes": 76,
"jsHeapTotalBytes": 20307968,
"scriptDurationMs": 18.598,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 586.9890000000169,
"styleRecalcs": 9,
"styleRecalcDurationMs": 5.912000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 366.17100000000005,
"heapDeltaBytes": -12206212,
"heapUsedBytes": 55912968,
"domNodes": 14,
"jsHeapTotalBytes": 22142976,
"scriptDurationMs": 54.90200000000001,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.669999999999998,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 577.6520000000005,
"styleRecalcs": 12,
"styleRecalcDurationMs": 7.814999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 370.59799999999996,
"heapDeltaBytes": -12183464,
"heapUsedBytes": 55759740,
"domNodes": 20,
"jsHeapTotalBytes": 21356544,
"scriptDurationMs": 55.443999999999996,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2032.623000000001,
"styleRecalcs": 7,
"styleRecalcDurationMs": 5.911,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 656.3460000000001,
"heapDeltaBytes": 8346556,
"heapUsedBytes": 68348116,
"domNodes": -284,
"jsHeapTotalBytes": 4513792,
"scriptDurationMs": 88.68799999999999,
"eventListeners": -142,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2027.8190000000222,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.388000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 680.385,
"heapDeltaBytes": 8285488,
"heapUsedBytes": 67532108,
"domNodes": -281,
"jsHeapTotalBytes": 4775936,
"scriptDurationMs": 94.476,
"eventListeners": -142,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2159.6959999999967,
"styleRecalcs": 69,
"styleRecalcDurationMs": 14.796,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1206.1680000000001,
"heapDeltaBytes": 6283300,
"heapUsedBytes": 66142280,
"domNodes": -282,
"jsHeapTotalBytes": 5001216,
"scriptDurationMs": 391.8469999999999,
"eventListeners": -142,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-pan",
"durationMs": 2202.481999999918,
"styleRecalcs": 69,
"styleRecalcDurationMs": 14.396000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1223.57,
"heapDeltaBytes": 5896672,
"heapUsedBytes": 65662520,
"domNodes": -283,
"jsHeapTotalBytes": 5525504,
"scriptDurationMs": 398.868,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-zoom",
"durationMs": 3129.871000000037,
"styleRecalcs": 65,
"styleRecalcDurationMs": 15.294000000000002,
"layouts": 60,
"layoutDurationMs": 7.726999999999999,
"taskDurationMs": 1451.457,
"heapDeltaBytes": -5524952,
"heapUsedBytes": 55912188,
"domNodes": -286,
"jsHeapTotalBytes": 7659520,
"scriptDurationMs": 500.2490000000001,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3226.3320000000704,
"styleRecalcs": 65,
"styleRecalcDurationMs": 14.494,
"layouts": 60,
"layoutDurationMs": 7.608,
"taskDurationMs": 1478.1149999999998,
"heapDeltaBytes": 1506580,
"heapUsedBytes": 62825128,
"domNodes": -286,
"jsHeapTotalBytes": 6610944,
"scriptDurationMs": 515.4029999999999,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2041.3930000000278,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.635999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 662.4870000000001,
"heapDeltaBytes": 8414540,
"heapUsedBytes": 68769736,
"domNodes": -282,
"jsHeapTotalBytes": 4513792,
"scriptDurationMs": 92.25800000000001,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2028.5350000000335,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.295000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 661.998,
"heapDeltaBytes": 7611764,
"heapUsedBytes": 68448272,
"domNodes": -283,
"jsHeapTotalBytes": 3989504,
"scriptDurationMs": 91.13700000000001,
"eventListeners": -142,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 583.1410000000119,
"styleRecalcs": 47,
"styleRecalcDurationMs": 9.957999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 388.981,
"heapDeltaBytes": -10956520,
"heapUsedBytes": 57185860,
"domNodes": 20,
"jsHeapTotalBytes": 21880832,
"scriptDurationMs": 118.146,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 589.6819999999252,
"styleRecalcs": 46,
"styleRecalcDurationMs": 9.887000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 399.94399999999996,
"heapDeltaBytes": -10966668,
"heapUsedBytes": 57222028,
"domNodes": 18,
"jsHeapTotalBytes": 21618688,
"scriptDurationMs": 121.625,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2007.682999999986,
"styleRecalcs": 11,
"styleRecalcDurationMs": 9.126000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 461.927,
"heapDeltaBytes": 4915272,
"heapUsedBytes": 72749936,
"domNodes": 22,
"jsHeapTotalBytes": 20570112,
"scriptDurationMs": 13.809999999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2020.9209999999302,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.465999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 490.743,
"heapDeltaBytes": -21328956,
"heapUsedBytes": 46678824,
"domNodes": -278,
"jsHeapTotalBytes": 19910656,
"scriptDurationMs": 12.895,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1775.4360000000133,
"styleRecalcs": 76,
"styleRecalcDurationMs": 35.07599999999999,
"layouts": 16,
"layoutDurationMs": 4.173000000000001,
"taskDurationMs": 797.859,
"heapDeltaBytes": -4597140,
"heapUsedBytes": 63521016,
"domNodes": 63,
"jsHeapTotalBytes": 20832256,
"scriptDurationMs": 104.31899999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1696.7540000000554,
"styleRecalcs": 76,
"styleRecalcDurationMs": 35.626,
"layouts": 16,
"layoutDurationMs": 3.835,
"taskDurationMs": 718.0649999999999,
"heapDeltaBytes": -4608440,
"heapUsedBytes": 63497920,
"domNodes": 65,
"jsHeapTotalBytes": 20570112,
"scriptDurationMs": 81.234,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-transition-enter",
"durationMs": 1367.8199999999947,
"styleRecalcs": 18,
"styleRecalcDurationMs": 28.872000000000003,
"layouts": 14,
"layoutDurationMs": 11.110999999999999,
"taskDurationMs": 906.7259999999997,
"heapDeltaBytes": 3728152,
"heapUsedBytes": 73343708,
"domNodes": 13673,
"jsHeapTotalBytes": 16777216,
"scriptDurationMs": 30.635999999999996,
"eventListeners": 2371,
"totalBlockingTimeMs": 122,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8234.796000000017,
"styleRecalcs": 250,
"styleRecalcDurationMs": 40.244,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4208.831,
"heapDeltaBytes": 10598696,
"heapUsedBytes": 69321152,
"domNodes": -281,
"jsHeapTotalBytes": 7622656,
"scriptDurationMs": 1261.035,
"eventListeners": -128,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8202.958999999964,
"styleRecalcs": 250,
"styleRecalcDurationMs": 40.440000000000005,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4316.046,
"heapDeltaBytes": 10377864,
"heapUsedBytes": 69589576,
"domNodes": -283,
"jsHeapTotalBytes": 6836224,
"scriptDurationMs": 1291.22,
"eventListeners": -128,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 17544.062999999995,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17517.979,
"heapDeltaBytes": -56894788,
"heapUsedBytes": 167763720,
"domNodes": -8312,
"jsHeapTotalBytes": -6754304,
"scriptDurationMs": 555.122,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666763,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 17385.381000000052,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17367.522,
"heapDeltaBytes": -44136100,
"heapUsedBytes": 166634688,
"domNodes": -8312,
"jsHeapTotalBytes": -9900032,
"scriptDurationMs": 587.712,
"eventListeners": -16389,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 22053.778999999962,
"styleRecalcs": 145,
"styleRecalcDurationMs": 18.245999999999984,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 22007.74,
"heapDeltaBytes": -49092624,
"heapUsedBytes": 177171708,
"domNodes": -8312,
"jsHeapTotalBytes": -10493952,
"scriptDurationMs": 850.7479999999999,
"eventListeners": -16385,
"totalBlockingTimeMs": 612,
"frameDurationMs": 17.780000000000047,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-pan",
"durationMs": 21015.578000000005,
"styleRecalcs": 144,
"styleRecalcDurationMs": 17.759999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 20969.583,
"heapDeltaBytes": -29720696,
"heapUsedBytes": 177606880,
"domNodes": -8312,
"jsHeapTotalBytes": -11542528,
"scriptDurationMs": 876.088,
"eventListeners": -16383,
"totalBlockingTimeMs": 309,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "workflow-execution",
"durationMs": 443.54699999996683,
"styleRecalcs": 15,
"styleRecalcDurationMs": 22.448000000000004,
"layouts": 4,
"layoutDurationMs": 1.515,
"taskDurationMs": 121.202,
"heapDeltaBytes": -15660504,
"heapUsedBytes": 51404652,
"domNodes": 132,
"jsHeapTotalBytes": 7462912,
"scriptDurationMs": 9.833,
"eventListeners": 67,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 464.38100000000304,
"styleRecalcs": 13,
"styleRecalcDurationMs": 18.357,
"layouts": 2,
"layoutDurationMs": 0.44000000000000006,
"taskDurationMs": 108.63700000000001,
"heapDeltaBytes": -16058340,
"heapUsedBytes": 51016284,
"domNodes": 119,
"jsHeapTotalBytes": 7725056,
"scriptDurationMs": 10.805,
"eventListeners": 65,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
christian-byrne
left a comment
There was a problem hiding this comment.
Approving — this is a clean cherry-pick of #14142, which I reviewed and approved on main.
Verified the backport itself rather than assuming: cherry-picking all four stack PRs onto core/1.49 and cloud/1.49 in stack order produces zero conflicts, and the resulting tour code matches main byte-for-byte. The locale merge adds 30 onboardingCoachmarks.firstRun.* keys, loses none, and correctly does not drag in main-only keys from unrelated PRs.
Worth knowing for the rest of this set: core/1.49 and cloud/1.49 are currently the same commit with zero commits not on main, so both take identical cherry-picks.
These must merge in stack order. #14637 (the #14143 backport) is currently failing 8 checks purely because its branch has none of this PR's files under it — setup fails, and lint/scan/fonts/perf cascade from that. Once this lands, #14637 needs its branch updated and its CI will be meaningful. Same story for #14144 and #14145, whose auto-backports failed for exactly this reason and should be re-triggered only after the ones below them land.
Backport of #14142 to
cloud/1.49Automatically created by backport workflow.