fix: warn instead of erroring on blocked or cancelled sign-in popups - #14944
Conversation
Google and GitHub sign-in use signInWithPopup. When the browser blocks the popup or the user closes it, Firebase throws auth/popup-blocked, auth/popup-closed-by-user or auth/cancelled-popup-request. These are user or browser actions, not app faults, so they now surface as a warn toast with copy that names the cause and the fix, rather than a red error. auth/popup-blocked and auth/account-exists-with-different-credential were missing from the locale entirely and fell through to the generic "something went wrong" message, which told the user nothing actionable. The account-collision copy does not name the original provider because the error does not carry it: AuthError.customData exposes only appName, email and phoneNumber, so naming it would need an extra fetchSignInMethodsForEmail round-trip. The regression net derives its table from the locale keys themselves instead of a hand-maintained list, so a code without its own message fails the suite. That hand-maintained list was the reason the two missing keys went unnoticed.
🎭 Playwright: ✅ 1832 passed, 0 failed · 1 flaky📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle: 8.86 MB gzip 🔴 +157 BDetailsSummary
Category Glance App Entry Points — 3.71 kB (baseline 3.71 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.37 MB (baseline 1.37 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 2 added / 2 removed / 1 unchanged Views & Navigation — 124 kB (baseline 124 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 13 added / 13 removed / 4 unchanged Panels & Settings — 566 kB (baseline 566 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 10 added / 10 removed / 16 unchanged User & Accounts — 27.5 kB (baseline 27.5 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 6 added / 6 removed / 5 unchanged Editors & Dialogs — 125 kB (baseline 125 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 7 added / 7 removed / 1 unchanged UI Components — 67.1 kB (baseline 67.1 kB) • ⚪ 0 BReusable component library chunks
Status: 6 added / 6 removed / 8 unchanged Data & Services — 3.53 MB (baseline 3.53 MB) • 🔴 +540 BStores, services, APIs, and repositories
Status: 14 added / 14 removed / 3 unchanged Utilities & Hooks — 549 kB (baseline 549 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 21 added / 21 removed / 16 unchanged Vendor & Third-Party — 16.8 MB (baseline 16.8 MB) • ⚪ 0 BExternal libraries and shared vendor chunks Status: 18 unchanged Other — 14.2 MB (baseline 14.2 MB) • 🔴 +375 BBundles that do not match a named category
Status: 76 added / 76 removed / 210 unchanged ⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-08-19T18:51:45.917Z",
"gitSha": "d75b5c073ec526e562183d82c0b2da60bb577da0",
"branch": "fix/auth-popup-error-codes",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2067.924000000005,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.707000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 517.358,
"heapDeltaBytes": 9157044,
"heapUsedBytes": 70378880,
"domNodes": -283,
"jsHeapTotalBytes": 4186112,
"scriptDurationMs": 7.156999999999998,
"eventListeners": -181,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-idle",
"durationMs": 2027.278000000024,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.568999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 491.75,
"heapDeltaBytes": 9851340,
"heapUsedBytes": 70379044,
"domNodes": -283,
"jsHeapTotalBytes": 4186112,
"scriptDurationMs": 7.238999999999999,
"eventListeners": -181,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2382.5800000000186,
"styleRecalcs": 78,
"styleRecalcDurationMs": 49.164,
"layouts": 12,
"layoutDurationMs": 4.129,
"taskDurationMs": 1200.223,
"heapDeltaBytes": 14285292,
"heapUsedBytes": 75510324,
"domNodes": -277,
"jsHeapTotalBytes": 5234688,
"scriptDurationMs": 149.697,
"eventListeners": -181,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1819.7150000000875,
"styleRecalcs": 75,
"styleRecalcDurationMs": 39.849999999999994,
"layouts": 12,
"layoutDurationMs": 3.992,
"taskDurationMs": 875.5440000000001,
"heapDeltaBytes": -361952,
"heapUsedBytes": 60731464,
"domNodes": -281,
"jsHeapTotalBytes": 4448256,
"scriptDurationMs": 115.543,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1715.54900000001,
"styleRecalcs": 31,
"styleRecalcDurationMs": 14.852,
"layouts": 6,
"layoutDurationMs": 0.5289999999999999,
"taskDurationMs": 338.517,
"heapDeltaBytes": 2815052,
"heapUsedBytes": 63184324,
"domNodes": 75,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 8.457,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1731.318999999985,
"styleRecalcs": 30,
"styleRecalcDurationMs": 17.462,
"layouts": 6,
"layoutDurationMs": 0.5509999999999998,
"taskDurationMs": 387.23299999999995,
"heapDeltaBytes": 2524208,
"heapUsedBytes": 62594248,
"domNodes": 76,
"jsHeapTotalBytes": 4718592,
"scriptDurationMs": 12.372,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 552.8600000000097,
"styleRecalcs": 10,
"styleRecalcDurationMs": 6.725999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 360.182,
"heapDeltaBytes": 10632152,
"heapUsedBytes": 71109296,
"domNodes": 16,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 56.147000000000006,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 596.2080000000469,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.2109999999999985,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 380.97400000000005,
"heapDeltaBytes": 10384872,
"heapUsedBytes": 70906076,
"domNodes": 16,
"jsHeapTotalBytes": 4980736,
"scriptDurationMs": 58.36000000000001,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2018.6819999999557,
"styleRecalcs": 6,
"styleRecalcDurationMs": 5.6000000000000005,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 564.112,
"heapDeltaBytes": 2429024,
"heapUsedBytes": 77286288,
"domNodes": -265,
"jsHeapTotalBytes": -2625536,
"scriptDurationMs": 12.851,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2066.701999999964,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.751000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 623.599,
"heapDeltaBytes": 3004280,
"heapUsedBytes": 77462100,
"domNodes": -260,
"jsHeapTotalBytes": -2363392,
"scriptDurationMs": 16.758,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2199.3979999999738,
"styleRecalcs": 69,
"styleRecalcDurationMs": 13.979000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1198.733,
"heapDeltaBytes": 13084492,
"heapUsedBytes": 87869488,
"domNodes": -283,
"jsHeapTotalBytes": 4677632,
"scriptDurationMs": 344.348,
"eventListeners": -181,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2174.0520000000743,
"styleRecalcs": 67,
"styleRecalcDurationMs": 12.258000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1198.6729999999998,
"heapDeltaBytes": 955400,
"heapUsedBytes": 76601016,
"domNodes": -268,
"jsHeapTotalBytes": -2662400,
"scriptDurationMs": 342.382,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3196.6879999999946,
"styleRecalcs": 64,
"styleRecalcDurationMs": 13.897000000000004,
"layouts": 60,
"layoutDurationMs": 7.058,
"taskDurationMs": 1391.3310000000001,
"heapDeltaBytes": -484212,
"heapUsedBytes": 76143240,
"domNodes": -272,
"jsHeapTotalBytes": -1839104,
"scriptDurationMs": 387.078,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3331.7799999999806,
"styleRecalcs": 65,
"styleRecalcDurationMs": 15.625999999999998,
"layouts": 60,
"layoutDurationMs": 7.681,
"taskDurationMs": 1483.2610000000002,
"heapDeltaBytes": -4564908,
"heapUsedBytes": 72582724,
"domNodes": -287,
"jsHeapTotalBytes": 5238784,
"scriptDurationMs": 412.38599999999997,
"eventListeners": -183,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "legacy-node-drag",
"durationMs": 2305.7059999999865,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.974000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1422.479,
"heapDeltaBytes": 12383416,
"heapUsedBytes": 95332008,
"domNodes": 14,
"jsHeapTotalBytes": 6815744,
"scriptDurationMs": 457.46500000000003,
"eventListeners": 186,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "legacy-node-drag",
"durationMs": 2448.443999999995,
"styleRecalcs": 46,
"styleRecalcDurationMs": 9.946,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1481.7740000000001,
"heapDeltaBytes": 11745640,
"heapUsedBytes": 89750740,
"domNodes": 12,
"jsHeapTotalBytes": 7270400,
"scriptDurationMs": 479.61799999999994,
"eventListeners": 186,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2073.431000000028,
"styleRecalcs": 9,
"styleRecalcDurationMs": 11.172999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 601.9499999999999,
"heapDeltaBytes": -7086028,
"heapUsedBytes": 73850960,
"domNodes": -269,
"jsHeapTotalBytes": 4259840,
"scriptDurationMs": 13.436,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "minimap-idle",
"durationMs": 2033.9579999999842,
"styleRecalcs": 8,
"styleRecalcDurationMs": 8.443000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 695.959,
"heapDeltaBytes": -13442236,
"heapUsedBytes": 61965040,
"domNodes": -282,
"jsHeapTotalBytes": 3141632,
"scriptDurationMs": 17.244,
"eventListeners": -179,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 601.9939999999906,
"styleRecalcs": 45,
"styleRecalcDurationMs": 10.042,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 401.998,
"heapDeltaBytes": 11403148,
"heapUsedBytes": 72074400,
"domNodes": 16,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 124.052,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 610.5830000000196,
"styleRecalcs": 46,
"styleRecalcDurationMs": 10.534999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 406.845,
"heapDeltaBytes": 11372952,
"heapUsedBytes": 71987096,
"domNodes": 18,
"jsHeapTotalBytes": 4980736,
"scriptDurationMs": 117.567,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2042.9429999999797,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.4319999999999995,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 498.806,
"heapDeltaBytes": 10881828,
"heapUsedBytes": 71825924,
"domNodes": -279,
"jsHeapTotalBytes": 4186112,
"scriptDurationMs": 7.193,
"eventListeners": -181,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2011.3629999999603,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.459999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 516.235,
"heapDeltaBytes": -659648,
"heapUsedBytes": 59894036,
"domNodes": -281,
"jsHeapTotalBytes": 4710400,
"scriptDurationMs": 8.207,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1726.6759999999977,
"styleRecalcs": 75,
"styleRecalcDurationMs": 39.989999999999995,
"layouts": 16,
"layoutDurationMs": 4.694999999999999,
"taskDurationMs": 799.879,
"heapDeltaBytes": -2635640,
"heapUsedBytes": 58196140,
"domNodes": -280,
"jsHeapTotalBytes": 5496832,
"scriptDurationMs": 85.257,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1778.5770000000412,
"styleRecalcs": 77,
"styleRecalcDurationMs": 40.586999999999996,
"layouts": 16,
"layoutDurationMs": 4.962999999999999,
"taskDurationMs": 871.113,
"heapDeltaBytes": 10915624,
"heapUsedBytes": 71679324,
"domNodes": -281,
"jsHeapTotalBytes": 4972544,
"scriptDurationMs": 91.257,
"eventListeners": -183,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-transition-enter",
"durationMs": 1401.5889999999445,
"styleRecalcs": 19,
"styleRecalcDurationMs": 32.501999999999995,
"layouts": 14,
"layoutDurationMs": 14.963000000000005,
"taskDurationMs": 998.1990000000001,
"heapDeltaBytes": -444956,
"heapUsedBytes": 95886244,
"domNodes": 13673,
"jsHeapTotalBytes": 11010048,
"scriptDurationMs": 17.803999999999995,
"eventListeners": 2373,
"totalBlockingTimeMs": 141,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8252.98300000003,
"styleRecalcs": 249,
"styleRecalcDurationMs": 35.696000000000005,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4196.003,
"heapDeltaBytes": 7448544,
"heapUsedBytes": 82040636,
"domNodes": -265,
"jsHeapTotalBytes": -1351680,
"scriptDurationMs": 1056.1670000000001,
"eventListeners": -131,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8617.343000000004,
"styleRecalcs": 249,
"styleRecalcDurationMs": 38.094,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4504.839000000001,
"heapDeltaBytes": 9759248,
"heapUsedBytes": 84122516,
"domNodes": -263,
"jsHeapTotalBytes": -565248,
"scriptDurationMs": 1095.401,
"eventListeners": -131,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 16876.621999999996,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 16259.987,
"heapDeltaBytes": -43780492,
"heapUsedBytes": 176233176,
"domNodes": -8312,
"jsHeapTotalBytes": -14311424,
"scriptDurationMs": 95.91599999999998,
"eventListeners": -16389,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 18016.433000000005,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17076.286,
"heapDeltaBytes": -28779220,
"heapUsedBytes": 178231688,
"domNodes": -8312,
"jsHeapTotalBytes": -9035776,
"scriptDurationMs": 119.50800000000002,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 18.333333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 20378.004999999972,
"styleRecalcs": 164,
"styleRecalcDurationMs": 21.389999999999993,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 19814.962,
"heapDeltaBytes": -30320380,
"heapUsedBytes": 177916680,
"domNodes": -8312,
"jsHeapTotalBytes": -19668992,
"scriptDurationMs": 434.584,
"eventListeners": -16385,
"totalBlockingTimeMs": 67,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 21084.884999999987,
"styleRecalcs": 169,
"styleRecalcDurationMs": 18.681999999999977,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 20518.037,
"heapDeltaBytes": -35611372,
"heapUsedBytes": 184807124,
"domNodes": -8312,
"jsHeapTotalBytes": -11759616,
"scriptDurationMs": 415.864,
"eventListeners": -16381,
"totalBlockingTimeMs": 126,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 110.84699999992154,
"styleRecalcs": 8,
"styleRecalcDurationMs": 14.702000000000002,
"layouts": 2,
"layoutDurationMs": 0.654,
"taskDurationMs": 75.51299999999999,
"heapDeltaBytes": 3048408,
"heapUsedBytes": 63991016,
"domNodes": 111,
"jsHeapTotalBytes": 262144,
"scriptDurationMs": 5.205000000000001,
"eventListeners": 49,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 128.82200000001376,
"styleRecalcs": 7,
"styleRecalcDurationMs": 15.681000000000001,
"layouts": 3,
"layoutDurationMs": 1.3309999999999997,
"taskDurationMs": 76.00599999999999,
"heapDeltaBytes": 2939984,
"heapUsedBytes": 63263844,
"domNodes": 128,
"jsHeapTotalBytes": 0,
"scriptDurationMs": 5.235,
"eventListeners": 27,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review. 📝 WalkthroughWalkthroughThe authentication error handler reports closed, expired, and blocked popups as warnings. English translations cover popup and account-collision cases. Tests derive error coverage from locale messages and verify warning, interpolation, and ChangesAuthentication popup error handling
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to This is a localized sign-in messaging change, and no actionable merge-blocking risk remains after normal checks and review. Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 inconclusive)
✅ Passed checks (6 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/composables/auth/useAuthActions.test.ts`:
- Around line 2-7: Move authErrorMessages and its related
firebaseCodesWithOwnMessage, popupPermissionCodes, and accessErrorCodes
initialization into vi.hoisted state in the test setup. Ensure the
vi.mock('`@/i18n`', ...) factory and useAuthActions can access these values
without referencing module-level variables before initialization.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 2f82eb26-2ecd-4e5c-8e3b-910420c90973
📒 Files selected for processing (3)
src/composables/auth/useAuthActions.test.tssrc/composables/auth/useAuthActions.tssrc/locales/en/main.json
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #14944 +/- ##
==========================================
+ Coverage 79.09% 80.64% +1.54%
==========================================
Files 2210 1883 -327
Lines 120774 111640 -9134
Branches 37693 33988 -3705
==========================================
- Hits 95532 90033 -5499
+ Misses 24693 21162 -3531
+ Partials 549 445 -104
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 562 files with indirect coverage changes 🚀 New features to boost your workflow:
|
The unused-key pruner ran on an earlier commit and swept in unrelated changes: it added auth.login.signUpFreeTierPromo and dropped subscription.perYear / usdPerYear, none of which this PR is about.
|
@christian-byrne This is awaiting your review, incase missed. |
…n settles (Comfy-Org#14945) ## Summary The cloud sign-up email form was live and submittable for up to two seconds before the region check finished, so a user the China rule is meant to block could create the account inside that window. ## Changes - **What**: `userIsInChina` was a `ref(false)` resolved in a mount hook, so `false` meant both "still checking" and "not in China" and the form rendered immediately. Region state is now a three-way `pending | blocked | allowed` behind `useRegionGate()`, with a skeleton holding the space until detection settles. The form cannot render early because it sits behind `v-else`. `isInChina()` now prefers Cloudflare's edge geo-IP over the reachability heuristic. The heuristic was unsound in both directions — a VPN user in China reaches Google and is waved through, a `zh-CN` user anywhere is blocked whenever Google is briefly unreachable — and it pinged google.com and baidu.com with the user's IP from the page whose purpose is minimizing data exposure. It stays as the fallback when the edge cannot answer, so the desktop mirror picker keeps its current tolerance. Every probe leg is now bounded; two previously had no deadline at all and could hang until the browser's own network timeout. Detection always settles, so callers need no timeout of their own. `SignInContent.vue` had the identical construction and now consumes the same composable, so the race is closed on both sign-up surfaces rather than one. - **Breaking**: None. Email sign-up stays blocked in China, Google and GitHub stay allowed, login stays unguarded, and the user-facing copy is byte-identical. ## Review Focus The policy shape is unchanged; only the timing of the gate is. Previously the form was interactive during detection, which under a data-controllership rule is a real hole rather than a cosmetic flash. Two constants carry over from the old code rather than being retuned: `PROBE_TIMEOUT_MS` (2000, previously the Google leg's race) and `CHINA_LATENCY_MS` (150). No new timeout was introduced, and the composable and both views contain no timers — a caller-side deadline would decide "allowed" while a real "blocked" answer was still in flight. `getClientCountry()` reads `loc=` from `/cdn-cgi/trace`. Cloudflare documents that path only as a troubleshooting aid, not a supported API, so the durable form is a first-party endpoint echoing `CF-IPCountry`. Failure returns `undefined` and falls through to the heuristic. A client-side gate is bypassable by design. See the comment below on where enforcement has to live. ## Screenshots Simulated China conditions, same moments on each branch, after choosing **Use email instead**. **Before** — the form is live throughout detection. 300 ms <img src="https://github.com/user-attachments/assets/a829cf19-663f-4d32-9058-2f1d92177df6" width="640"> 1200 ms <img src="https://github.com/user-attachments/assets/cb2de729-3d10-4df2-82d8-c46d00af21d8" width="640"> 2800 ms — the notice finally replaces it <img src="https://github.com/user-attachments/assets/9f87673a-4934-423d-8fe7-91ca73399236" width="640"> **After** — a skeleton holds the space; no email or password field exists in the DOM. 300 ms <img src="https://github.com/user-attachments/assets/b0b6d743-d08d-4a56-91de-c8717e6fb257" width="640"> 1200 ms <img src="https://github.com/user-attachments/assets/ae73b761-f360-4f1f-81a2-e25040b6e5b2" width="640"> 2800 ms — settled to the notice <img src="https://github.com/user-attachments/assets/26ed1bf0-9753-4a50-bc49-a2cd82003e05" width="640"> ## Testing 44 tests across five files, every one mutation-checked. **43 killed, 1 survivor** (recorded below rather than papered over). `isInChina` / `getClientCountry` — 17 cases, 100% branch coverage: | Case | Mutation applied | Result | | --- | --- | --- | | Uppercases the `loc` value | drop `.toUpperCase()` | fails | | `undefined` for non-200 | delete the `response.ok` guard | fails | | `undefined` for a body with no `loc` | default the missing line to a country | fails | | `undefined` for an empty `loc` | `\|\| undefined` → `?? undefined` | fails | | `undefined` for `XX` / `T1` sentinels | return the country unfiltered; trim the sentinel set | 2 fail | | `undefined` for a network error | rethrow instead of catching | fails | | Edge never answers → gives up | bare `fetch`; deadline never rejects; timeout 2000→4000 | fails | | Prefers the edge over the heuristic | run the probe eagerly alongside the edge | fails | | Edge `CN` → blocked / other → allowed | `country === 'CN'` → `!==` | 2 fail | | Falls back to the heuristic on a sentinel | remove the sentinel filter | 2 fail | | Edge down + Google reachable → allowed | invert the google-success return | fails | | Edge down + Baidu fast → blocked | latency compare → `false`; threshold 150→0 | fails | | Neither probe answers → locale decides | inner catch → `true`; force the locale check | fails | | Every probe hangs → still settles | unbound a probe leg | fails | Region gate and both sign-up surfaces — 27 cases: | Case | Mutation applied | Result | | --- | --- | --- | | Starts pending | `ref('pending')` → `ref('allowed')` | fails | | Pending → blocked / → allowed | invert the two status branches | 2 fail | | Rejection fails open to allowed | remove `.catch`; `.catch(() => true)` | fails | | A slow real answer is never pre-empted | add a caller-side 3s deadline | fails | | Form withheld while pending (both surfaces) | pending branch → `v-if="false"` | 2 fail | | Notice replaces form in China (both) | blocked branch → `v-else-if="false"` | 2 fail | | Form renders outside China (both) | invert the blocked branch | 2 fail | | Form released when detection fails (both) | invert the blocked branch | 2 fail | | Never renders the form in China, pending or settled | disable both branches | fails | | Social sign-up unaffected in every state (4 rows) | gate the social buttons on `regionStatus === 'allowed'` | 4 fail | | Login view has no region gate | `CloudSignInForm` → `v-if="false"` | fails | | Query forwarding, free-runs copy, email toggle, webview notice | drop `query: route.query`; alter the count; force the branch | 4 fail | **One survivor, left in place.** `SignInContent > leaves sign-in ungated by region` is a real assertion — deleting `SignInForm` kills it, and it catches a gate that withholds sign-in *while the probe is pending*. What it cannot catch is a gate that revokes sign-in once the probe settles to `blocked`, which is what its name promises. Cause is timing: the assertion resolves on the first tick, before `onMounted`'s gate flips. Its `CloudLoginView` twin is genuinely killed, because that view has no gate wired at all. Also worth recording: renaming the locale key `signupBlocked` breaks no test, because `t`/`st` are mocked to echo the key. The two `signup_blocked` tests still pin real branch routing (killed three ways), but locale-key existence is not covered on this branch. `auth/popup-blocked` is covered, via the locale-derived table in Comfy-Org#14944. The `SignInContent` dialog is covered by unit assertions rather than a screenshot — driving it needs a local ComfyUI backend.
christian-byrne
left a comment
There was a problem hiding this comment.
Can you fix the merge conflicts?
…codes # Conflicts: # src/composables/auth/useAuthActions.test.ts
7de4f5e
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
christian-byrne
left a comment
There was a problem hiding this comment.
Re-approving on the new head — my earlier approval was auto-dismissed by a push, twice.
Verified since then: the change is scoped to auth error presentation. Three browser/user-caused popup outcomes (POPUP_CLOSED_BY_USER, EXPIRED_POPUP_REQUEST, POPUP_BLOCKED) move from an error toast to a warn toast, which is the right severity for something the user or their browser did rather than an app fault. Locale additions cover the two new codes plus account-exists-with-different-credential.
Nice touch in the test: deriving the known-code set from en/main.json instead of a hardcoded Set means a new locale entry can no longer drift out of sync with the test's idea of what is known.
codecov/project is a coverage-threshold check, and this PR adds test coverage rather than removing it.
Summary
A blocked or cancelled sign-in popup now shows an amber warning that says what to do, instead of a red error saying something went wrong.
Changes
What:
auth/popup-blockedwas missing fromauth.errorsentirely, so a browser blocking the popup fell through to the generic "Something went wrong while signing you in", which never mentions pop-ups. Safari blocks popups not tied to a direct user gesture by default, so this is not an edge case.A
POPUP_PERMISSION_ERROR_CODESgroup is checked ahead of the genericFirebaseErrorbranch and surfaces those codes atwarn. Closing a popup is a user action, not an app fault, and should not look like a crash.Also adds
auth/account-exists-with-different-credential, which previously fell back to generic. That is a real collision: signed up with Google, later tries GitHub on the same address.The two pre-existing popup keys both read "Sign-in was cancelled. Please try again.", which did not distinguish closing the window from having a second one already open. Both are reworded.
Breaking: None.
Review Focus
The severity split is the judgement call. The three popup codes are
warnbecause the user or their browser caused them.account-exists-with-different-credentialstayserror: it is a genuine account-state conflict, not a popup interaction.That message does not name the original provider. It cannot —
AuthError.customDatacarries only{appName, email?, phoneNumber?, tenantId?}, and naming the provider needs afetchSignInMethodsForEmailround trip, which is a behaviour change rather than copy.Worth knowing for future work in this file:
AuthErrorCodes.UNAUTHORIZED_DOMAINresolves toauth/unauthorized-continue-uri, notauth/unauthorized-domain. No SDK constant carries that value, so the hard-coded strings in the accessError branch are correct and must not be "tidied" into constants. The table test pins all three so that refactor fails loudly.Only
enis touched. Persrc/locales/CONTRIBUTING.md, CI generates translations during release PRs, and vue-i18n falls back toenat runtime.The pre-commit hook flags
auth/popup-blockedas an unused key. False positive: it resolves dynamically throughst(`auth.errors.${error.code}`), which a static scanner cannot trace.Testing
43 tests. Every one was mutation-checked: the behaviour it pins was broken in the source, the test confirmed failing, then reverted. 43 killed, 0 survivors.
detail: st(...)→t('auth.errors.generic')detail: st(...)→ genericauth/popup-blockedto the generic stringauth/popup-blockedfrom the localest(...)→ raw`auth.errors.${code}`warn(3 rows)&& false; trim the listerrorsignup_blockedsubstring match.includes(...)→===signup_blockedcase-insensitive.toLowerCase()unauthorized-domainfamily (3 rows)accessError.value = true; hardcode domain; drop a coderef(false)→ref(true)reportError; collapse the sign-up/sign-in action namesScreenshots
Captured against the running app.
Before —
auth/popup-blockedhad no key, so it fell through to the generic red error.After
auth/popup-blockedauth/popup-closed-by-userauth/cancelled-popup-requestauth/account-exists-with-different-credential— stays a red errorAny unmapped code — generic fallback, unchanged