feat(agent): agent opt-in consent card (design prototype) - #15661
feat(agent): agent opt-in consent card (design prototype)#15661uytieu wants to merge 13 commits into
Conversation
Adds the agent opt-in consent dialog from DES-810: a two-column card with a looping video on the left and the consent copy plus Read the docs, Reject and Accept actions on the right. Title and body are props so the component carries no fixed copy; only the action labels are translated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds Storybook coverage for the consent card so it can be reviewed without being wired into the app: default, no-video, single-paragraph and long-copy states, defaulting the theme toolbar to dark. Buttons now set border-0 and an explicit background. Storybook does not apply the preflight button reset, so the user-agent 2px outset border was showing through on every action. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The card stacks to a single column below a 672px container: the video takes a 16:9 crop above the copy, padding and title size step down, and the copy area scrolls within a 90dvh cap. Sizing is driven by container queries rather than viewport breakpoints, so the card also stacks inside a narrow agent panel on a wide screen. Above the threshold it keeps the designed 1040x543 with 555/483 columns. The close button moves onto the card so it stays top-right in both layouts, picking up a scrim while it overlaps the video. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the arbitrary rounded-[14px] and rounded-[13px] with rounded-agent, the radius token the extension already uses for popovers, coach marks and message bubbles. Buttons keep rounded-lg, which is the 8px token the design calls for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Swaps the extension-local rounded-agent for the shared radius scale: the card takes the largest step and the video the next one down, so the inner corner stays tighter than the outer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Card takes rounded-2xl and the video rounded-xl, one step down, which lands within a couple of pixels of the 14/13 in the design. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Consent has to be answered, so the dismiss affordance is gone along with its close emit and translation key. Reject is the way out. The three actions now use @/components/ui/button/Button rather than hand-rolled markup: inverted for accept, secondary for reject, textonly for the docs link. Its md size reproduces the design's 57x32, 52x32 and 118x32 exactly, which the hand-rolled padding did not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Clicking Ask Comfy Agent now shows the consent card before the panel opens, and only the first time: useAgentConsent records the answer in localStorage under Comfy.AgentPanel.consentAccepted and lets later clicks straight through. Accept opens the panel, reject leaves it closed. The card is shown as a headless reka dialog with every dismissal path off, so it can only be answered, matching the removal of its close button. Closing an open panel is unaffected. agentPanelStore.open was defined but never returned; it is exported now that the consent callback needs it, so opening keeps its topbar_button telemetry instead of going through toggle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The dialog used a w-fit content box copied from the queue-clear-history precedent, whose content has an intrinsic width. This card sizes itself with w-full, so it collapsed to zero and the dialog showed nothing but its scrim. The content box now carries an explicit width, and size xl stops the default md variant capping it at 576px. Also points Read the docs at the in-app agent page and supplies the video, so the media pane no longer falls back to its placeholder. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
?agentConsent=always re-asks on every panel open and does not record the answer, so the card can be shown repeatedly without clearing localStorage by hand. Without the parameter the first-run behaviour is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The video sits on its own rounded plate and does not need the white hairline. The no-video placeholder loses it too, taking a raised fill instead so the empty state still reads as a region. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Report the entry click once it has an outcome, so declining consent is no longer counted as a panel open. - Let Escape decline. The mask stays inert, but a card that fails to render no longer traps the user behind an undismissable modal. - Load the card with defineAsyncComponent so cloud-only UI stays out of the bundles every distribution ships. - Key consent by user id, so a second account on the same browser profile is asked in its own right rather than inheriting the first user's answer. - Drop role/aria-modal from the card; reka's DialogContent already supplies both, and the pair nested a dialog inside a dialog. - Hide the docs button when no docsUrl is supplied instead of rendering a control that does nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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: ❌ 1728 passed, 51 failed · 2 flaky❌ Failed Tests📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle Size
⚡ Performance Report
Absolute values
Raw data{
"timestamp": "2026-08-23T02:04:36.525Z",
"gitSha": "64538ed3518c84be56eedb8bf56e062f0b8e837e",
"branch": "uy/agent-consent-card",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2071.9379999999887,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.786,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 474.45000000000005,
"heapDeltaBytes": 1552916,
"heapUsedBytes": 91184020,
"domNodes": 20,
"jsHeapTotalBytes": 6029312,
"scriptDurationMs": 6.567,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-idle",
"durationMs": 2012.2109999999793,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.97,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 537.8539999999999,
"heapDeltaBytes": 1387596,
"heapUsedBytes": 88927336,
"domNodes": 18,
"jsHeapTotalBytes": 5767168,
"scriptDurationMs": 9.401,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2061.063000000047,
"styleRecalcs": 79,
"styleRecalcDurationMs": 48.134,
"layouts": 14,
"layoutDurationMs": 5.0089999999999995,
"taskDurationMs": 1007.0300000000001,
"heapDeltaBytes": -4515896,
"heapUsedBytes": 82527288,
"domNodes": 65,
"jsHeapTotalBytes": 6553600,
"scriptDurationMs": 133.83499999999998,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1839.8049999999557,
"styleRecalcs": 74,
"styleRecalcDurationMs": 38.063,
"layouts": 14,
"layoutDurationMs": 5.0760000000000005,
"taskDurationMs": 849.1650000000001,
"heapDeltaBytes": -3778972,
"heapUsedBytes": 85734796,
"domNodes": 59,
"jsHeapTotalBytes": 6029312,
"scriptDurationMs": 115.40400000000001,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1721.1230000000342,
"styleRecalcs": 33,
"styleRecalcDurationMs": 17.095,
"layouts": 6,
"layoutDurationMs": 1.2049999999999996,
"taskDurationMs": 362.702,
"heapDeltaBytes": 4360304,
"heapUsedBytes": 93830108,
"domNodes": 79,
"jsHeapTotalBytes": 6291456,
"scriptDurationMs": 9.312,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1730.2899999999681,
"styleRecalcs": 30,
"styleRecalcDurationMs": 20.533,
"layouts": 6,
"layoutDurationMs": 1.3490000000000002,
"taskDurationMs": 436.56300000000005,
"heapDeltaBytes": 4530700,
"heapUsedBytes": 91758544,
"domNodes": 77,
"jsHeapTotalBytes": 5505024,
"scriptDurationMs": 13.849000000000002,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 575.4829999999629,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.232000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 353.613,
"heapDeltaBytes": 11818396,
"heapUsedBytes": 101312040,
"domNodes": 18,
"jsHeapTotalBytes": 5767168,
"scriptDurationMs": 54.964,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 596.1469999999736,
"styleRecalcs": 12,
"styleRecalcDurationMs": 10.01,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 381.901,
"heapDeltaBytes": 11556316,
"heapUsedBytes": 101070788,
"domNodes": 20,
"jsHeapTotalBytes": 5767168,
"scriptDurationMs": 57.62400000000001,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2001.2859999999932,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.639999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 553.28,
"heapDeltaBytes": -2055816,
"heapUsedBytes": 104103580,
"domNodes": 16,
"jsHeapTotalBytes": 6397952,
"scriptDurationMs": 16.080000000000002,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2029.739999999947,
"styleRecalcs": 7,
"styleRecalcDurationMs": 10.645999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 710.469,
"heapDeltaBytes": -2679076,
"heapUsedBytes": 101833072,
"domNodes": 14,
"jsHeapTotalBytes": 5341184,
"scriptDurationMs": 20.208000000000006,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666696,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2117.8110000000174,
"styleRecalcs": 68,
"styleRecalcDurationMs": 13.202000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1147.538,
"heapDeltaBytes": -4683612,
"heapUsedBytes": 102433172,
"domNodes": 14,
"jsHeapTotalBytes": 6852608,
"scriptDurationMs": 332.332,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-pan",
"durationMs": 2437.5850000000128,
"styleRecalcs": 67,
"styleRecalcDurationMs": 14.090999999999996,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1299.411,
"heapDeltaBytes": -4640132,
"heapUsedBytes": 102594900,
"domNodes": 12,
"jsHeapTotalBytes": 7376896,
"scriptDurationMs": 373.08000000000004,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.80000000000109
},
{
"name": "large-graph-zoom",
"durationMs": 3129.4690000000287,
"styleRecalcs": 65,
"styleRecalcDurationMs": 14.925,
"layouts": 60,
"layoutDurationMs": 11.937999999999999,
"taskDurationMs": 1342.943,
"heapDeltaBytes": -4631688,
"heapUsedBytes": 104021796,
"domNodes": 12,
"jsHeapTotalBytes": 6922240,
"scriptDurationMs": 388.498,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-zoom",
"durationMs": 3413.7320000000955,
"styleRecalcs": 66,
"styleRecalcDurationMs": 17.653,
"layouts": 60,
"layoutDurationMs": 13.423,
"taskDurationMs": 1498.869,
"heapDeltaBytes": -5039308,
"heapUsedBytes": 101282608,
"domNodes": 14,
"jsHeapTotalBytes": 6914048,
"scriptDurationMs": 423.703,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "legacy-node-drag",
"durationMs": 2493.134999999995,
"styleRecalcs": 46,
"styleRecalcDurationMs": 9.879999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1460.358,
"heapDeltaBytes": -17892628,
"heapUsedBytes": 89768012,
"domNodes": 12,
"jsHeapTotalBytes": 7176192,
"scriptDurationMs": 462.97200000000004,
"eventListeners": 186,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "legacy-node-drag",
"durationMs": 2770.5439999999726,
"styleRecalcs": 44,
"styleRecalcDurationMs": 9.514000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1591.364,
"heapDeltaBytes": -11061604,
"heapUsedBytes": 96935824,
"domNodes": 8,
"jsHeapTotalBytes": 8912896,
"scriptDurationMs": 499.66200000000003,
"eventListeners": 186,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333338,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "minimap-idle",
"durationMs": 1994.6379999999522,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.147,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 555.568,
"heapDeltaBytes": -1108368,
"heapUsedBytes": 106524912,
"domNodes": 16,
"jsHeapTotalBytes": 6397952,
"scriptDurationMs": 15.177000000000003,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2019.8459999999159,
"styleRecalcs": 7,
"styleRecalcDurationMs": 7.361,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 691.0239999999999,
"heapDeltaBytes": -1683864,
"heapUsedBytes": 104104148,
"domNodes": 14,
"jsHeapTotalBytes": 5603328,
"scriptDurationMs": 18.239,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 588.9680000000226,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.750999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 389.44100000000003,
"heapDeltaBytes": 12873564,
"heapUsedBytes": 102619448,
"domNodes": 20,
"jsHeapTotalBytes": 6291456,
"scriptDurationMs": 116.77000000000001,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 604.3240000000196,
"styleRecalcs": 45,
"styleRecalcDurationMs": 10.184999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 416.53999999999996,
"heapDeltaBytes": 12109856,
"heapUsedBytes": 99446020,
"domNodes": 16,
"jsHeapTotalBytes": 6029312,
"scriptDurationMs": 120.85900000000001,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 1998.6290000000508,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.66,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 437.0280000000001,
"heapDeltaBytes": 1729688,
"heapUsedBytes": 91133988,
"domNodes": 20,
"jsHeapTotalBytes": 6029312,
"scriptDurationMs": 6.435,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-idle",
"durationMs": 2037.951000000021,
"styleRecalcs": 10,
"styleRecalcDurationMs": 10.075,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 521.843,
"heapDeltaBytes": 1635588,
"heapUsedBytes": 89021264,
"domNodes": 20,
"jsHeapTotalBytes": 6291456,
"scriptDurationMs": 10.144,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1689.1219999999976,
"styleRecalcs": 78,
"styleRecalcDurationMs": 36.232,
"layouts": 18,
"layoutDurationMs": 6.341,
"taskDurationMs": 762.642,
"heapDeltaBytes": 19654796,
"heapUsedBytes": 109065372,
"domNodes": 65,
"jsHeapTotalBytes": 6291456,
"scriptDurationMs": 89.41399999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1738.0809999999656,
"styleRecalcs": 77,
"styleRecalcDurationMs": 40.137,
"layouts": 18,
"layoutDurationMs": 6.300999999999999,
"taskDurationMs": 866.514,
"heapDeltaBytes": 19648948,
"heapUsedBytes": 106781180,
"domNodes": 64,
"jsHeapTotalBytes": 5505024,
"scriptDurationMs": 94.60099999999998,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-transition-enter",
"durationMs": 1434.1699999999946,
"styleRecalcs": 18,
"styleRecalcDurationMs": 30.913999999999998,
"layouts": 14,
"layoutDurationMs": 14.154999999999998,
"taskDurationMs": 1005.8880000000001,
"heapDeltaBytes": 22544768,
"heapUsedBytes": 121257916,
"domNodes": 13673,
"jsHeapTotalBytes": 2097152,
"scriptDurationMs": 17.408999999999995,
"eventListeners": 2375,
"totalBlockingTimeMs": 146,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8330.196999999998,
"styleRecalcs": 249,
"styleRecalcDurationMs": 37.204,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4241.210000000001,
"heapDeltaBytes": -13526464,
"heapUsedBytes": 90747644,
"domNodes": -280,
"jsHeapTotalBytes": 1662976,
"scriptDurationMs": 1058.2289999999998,
"eventListeners": -173,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8915.217999999983,
"styleRecalcs": 251,
"styleRecalcDurationMs": 44.331999999999994,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4752.928,
"heapDeltaBytes": -3221908,
"heapUsedBytes": 101122376,
"domNodes": -282,
"jsHeapTotalBytes": 614400,
"scriptDurationMs": 1141.5710000000001,
"eventListeners": -141,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 17849.10500000001,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 16872.479,
"heapDeltaBytes": -22899644,
"heapUsedBytes": 199148760,
"domNodes": -8312,
"jsHeapTotalBytes": 15990784,
"scriptDurationMs": 113.896,
"eventListeners": -16369,
"totalBlockingTimeMs": 0,
"frameDurationMs": 18.333333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 18586.787999999957,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17683.587000000003,
"heapDeltaBytes": -24897840,
"heapUsedBytes": 200635056,
"domNodes": -8312,
"jsHeapTotalBytes": 18317312,
"scriptDurationMs": 122.40299999999999,
"eventListeners": -16367,
"totalBlockingTimeMs": 0,
"frameDurationMs": 18.333333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 20936.653999999977,
"styleRecalcs": 171,
"styleRecalcDurationMs": 17.246000000000038,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 20273.133,
"heapDeltaBytes": -20901828,
"heapUsedBytes": 206855880,
"domNodes": -8312,
"jsHeapTotalBytes": 12742656,
"scriptDurationMs": 395.24,
"eventListeners": -16367,
"totalBlockingTimeMs": 47,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 22830.362000000037,
"styleRecalcs": 179,
"styleRecalcDurationMs": 23.363999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 22152.169,
"heapDeltaBytes": -3761088,
"heapUsedBytes": 215258252,
"domNodes": -8312,
"jsHeapTotalBytes": 15396864,
"scriptDurationMs": 437.291,
"eventListeners": -16361,
"totalBlockingTimeMs": 173,
"frameDurationMs": 18.333333333333332,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "workflow-execution",
"durationMs": 113.21299999997336,
"styleRecalcs": 8,
"styleRecalcDurationMs": 14.956,
"layouts": 3,
"layoutDurationMs": 0.7440000000000001,
"taskDurationMs": 79.804,
"heapDeltaBytes": 3173184,
"heapUsedBytes": 89697936,
"domNodes": 114,
"jsHeapTotalBytes": 524288,
"scriptDurationMs": 6.486000000000001,
"eventListeners": 49,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 459.3199999999342,
"styleRecalcs": 16,
"styleRecalcDurationMs": 27.878000000000004,
"layouts": 4,
"layoutDurationMs": 1.704,
"taskDurationMs": 128.415,
"heapDeltaBytes": 4988144,
"heapUsedBytes": 91473036,
"domNodes": 123,
"jsHeapTotalBytes": 524288,
"scriptDurationMs": 9.441999999999998,
"eventListeners": 97,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
}
]
} |
Removed at the implementing developer's request; their workflow generates the tests for this feature. The mock and expectation changes in WorkflowTabs.test.ts stay: the consent gate calls agentPanelStore.open instead of toggle, so the pre-existing entry-button test fails without them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
Clicking Ask Comfy Agent now shows a consent card before the agent panel opens for the first time, so users agree to agent access to their workflow before it starts.
Ticket: DES-810. This PR adds a new feature. It does not fix an issue.
Important
This is a design PR, not production code. It is a working prototype that shows how the design looks and how it can behave. Review it to decide which parts to keep and which parts to rewrite. Treat every choice in it as a proposal, including the storage, the copy, the video, and the wiring.
Changes
AgentConsentCardcomponent and auseAgentConsentcomposable. The card shows a title, two paragraphs, a video, and three actions: Read the docs, Reject, and Accept. The composable stores the answer and puts the card up when there is no answer yet.WorkflowTabscalls it before it opens the panel. Accept opens the panel. Reject leaves it closed. Later clicks open the panel directly. The card has Storybook stories and unit tests.agentPanelStore.openis now exported, which is additive. Cloud users see one new dialog on first use.Review Focus
The copy is not final. The Figma frames contain lorem ipsum, so the text in
agent.consent.*is a first draft and needs a review.The video is a stand-in. It is the MCP launch film, because there is no consent video yet.
The answer is stored in the browser, under the
Comfy.AgentPanel.consentAcceptedkey in localStorage, keyed by user id. Two accounts on one browser profile each get their own answer. The answer does not follow a user to another browser or machine. A server-side record is the better long-term home.A user cannot withdraw consent after Accept. There is no setting for this yet. The copy makes no promise about withdrawal, because the feature does not exist.
Escape declines the card. The mask stays inert, so a stray click cannot dismiss it. Escape is also the only way out if the card ever fails to render, which happened once during development.
The card sizes itself with container queries, not viewport breakpoints. It stacks below a 672px container, so it also stacks inside a narrow agent panel on a wide screen.
?agentConsent=alwaysshows the card on every open and records nothing. It exists for demos. Decide whether it ships.The agent panel is cloud-only and tree-shaken out of other builds. The card loads through
defineAsyncComponentto keep it out of those bundles. Please make sure that this holds after any import changes.Screenshots (if applicable)
The
Agent/ConsentCardstories in Storybook show the card:Default,Without Video,Single Paragraph,Long Copy, andIn Narrow Panelfor the stacked layout. CI deploys a Storybook preview for this PR.Screen.Recording.2026-08-22.at.6.22.08.PM.mov