From d8234605a6ff4f271957f76601bd8b388bd8e7e9 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sun, 13 Sep 2026 12:11:30 -0300 Subject: [PATCH 1/2] refactor(panels)!: present Mail, Configuration, Assets, Router, Queue, and User declaratively, and fold the Request routes into its overview. --- composer.json | 6 +- resources/assets/dist/css/debug.min.css | 2 +- resources/src/styles/main.css | 2139 +++-------------- src/Helper/PhpHighlighter.php | 33 + src/Panel/Asset/AssetBundleNormalizer.php | 71 - src/Panel/Asset/AssetBundleView.php | 84 - src/Panel/Asset/AssetCardRenderer.php | 288 --- src/Panel/Asset/AssetPanel.php | 281 +++ src/Panel/Asset/AssetSectionRenderer.php | 83 - src/Panel/Asset/AssetSummary.php | 42 - src/Panel/Config/ApplicationConfig.php | 46 - src/Panel/Config/ConfigCardRenderer.php | 398 --- src/Panel/Config/ConfigDataNormalizer.php | 100 - src/Panel/Config/ConfigPanel.php | 299 +++ src/Panel/Config/ConfigSummary.php | 50 - src/Panel/Config/PhpConfig.php | 37 - src/Panel/Mail/MailCardRenderer.php | 323 --- src/Panel/Mail/MailPanel.php | 237 ++ src/Panel/PanelRenderer.php | 223 +- src/Panel/Queue/QueueGridRenderer.php | 145 -- src/Panel/Queue/QueuePanel.php | 315 +++ src/Panel/Request/RequestRenderer.php | 162 +- src/Panel/Request/RequestRoutesRenderer.php | 313 --- src/Panel/Request/RequestSectionRenderer.php | 34 +- src/Panel/Router/RouterCurrentView.php | 64 - src/Panel/Router/RouterPanel.php | 326 +++ src/Panel/Router/RouterSectionRenderer.php | 253 -- src/Panel/User/UserGuestRenderer.php | 32 - src/Panel/User/UserIdentityRenderer.php | 175 -- src/Panel/User/UserPanel.php | 303 +++ src/Panel/User/UserRbacRenderer.php | 43 - src/View/History/HistoryCellRenderer.php | 2 +- tests/Helper/PhpHighlighterTest.php | 64 + .../Panel/Asset/AssetBundleNormalizerTest.php | 455 ---- tests/Panel/Asset/AssetBundleRowTest.php | 90 + tests/Panel/Asset/AssetCardRendererTest.php | 573 ----- tests/Panel/Asset/AssetPanelTest.php | 697 ++++++ .../Panel/Asset/AssetSectionRendererTest.php | 138 -- tests/Panel/Config/ConfigCardRendererTest.php | 432 ---- .../Panel/Config/ConfigDataNormalizerTest.php | 275 --- tests/Panel/Config/ConfigPanelTest.php | 459 ++++ tests/Panel/Mail/MailCardRendererTest.php | 1161 --------- tests/Panel/Mail/MailPanelTest.php | 604 +++++ tests/Panel/PanelRendererTest.php | 248 +- tests/Panel/Queue/QueueGridRendererTest.php | 219 -- tests/Panel/Queue/QueuePanelTest.php | 472 ++++ .../RequestDiagnosticValueRendererTest.php | 195 ++ tests/Panel/Request/RequestRendererTest.php | 752 +++--- .../Request/RequestSectionRendererTest.php | 20 +- tests/Panel/Router/RouterCurrentViewTest.php | 98 - tests/Panel/Router/RouterPanelTest.php | 472 ++++ .../Router/RouterSectionRendererTest.php | 341 --- tests/Panel/User/UserGuestRendererTest.php | 36 - tests/Panel/User/UserIdentityRendererTest.php | 402 ---- tests/Panel/User/UserPanelTest.php | 357 +++ tests/Panel/User/UserRbacRendererTest.php | 47 - .../AssetBundleNormalizerProvider.php | 44 - tests/Provider/RequestRendererProvider.php | 24 + tests/Storage/PayloadTest.php | 8 + tests/Support/PanelViewAccessors.php | 263 ++ .../View/History/HistoryCellRendererTest.php | 48 +- 61 files changed, 6933 insertions(+), 8970 deletions(-) create mode 100644 src/Helper/PhpHighlighter.php delete mode 100644 src/Panel/Asset/AssetBundleNormalizer.php delete mode 100644 src/Panel/Asset/AssetBundleView.php delete mode 100644 src/Panel/Asset/AssetCardRenderer.php create mode 100644 src/Panel/Asset/AssetPanel.php delete mode 100644 src/Panel/Asset/AssetSectionRenderer.php delete mode 100644 src/Panel/Asset/AssetSummary.php delete mode 100644 src/Panel/Config/ApplicationConfig.php delete mode 100644 src/Panel/Config/ConfigCardRenderer.php delete mode 100644 src/Panel/Config/ConfigDataNormalizer.php create mode 100644 src/Panel/Config/ConfigPanel.php delete mode 100644 src/Panel/Config/ConfigSummary.php delete mode 100644 src/Panel/Config/PhpConfig.php delete mode 100644 src/Panel/Mail/MailCardRenderer.php create mode 100644 src/Panel/Mail/MailPanel.php delete mode 100644 src/Panel/Queue/QueueGridRenderer.php create mode 100644 src/Panel/Queue/QueuePanel.php delete mode 100644 src/Panel/Request/RequestRoutesRenderer.php delete mode 100644 src/Panel/Router/RouterCurrentView.php create mode 100644 src/Panel/Router/RouterPanel.php delete mode 100644 src/Panel/Router/RouterSectionRenderer.php delete mode 100644 src/Panel/User/UserGuestRenderer.php delete mode 100644 src/Panel/User/UserIdentityRenderer.php create mode 100644 src/Panel/User/UserPanel.php delete mode 100644 src/Panel/User/UserRbacRenderer.php create mode 100644 tests/Helper/PhpHighlighterTest.php delete mode 100644 tests/Panel/Asset/AssetBundleNormalizerTest.php create mode 100644 tests/Panel/Asset/AssetBundleRowTest.php delete mode 100644 tests/Panel/Asset/AssetCardRendererTest.php create mode 100644 tests/Panel/Asset/AssetPanelTest.php delete mode 100644 tests/Panel/Asset/AssetSectionRendererTest.php delete mode 100644 tests/Panel/Config/ConfigCardRendererTest.php delete mode 100644 tests/Panel/Config/ConfigDataNormalizerTest.php create mode 100644 tests/Panel/Config/ConfigPanelTest.php delete mode 100644 tests/Panel/Mail/MailCardRendererTest.php create mode 100644 tests/Panel/Mail/MailPanelTest.php delete mode 100644 tests/Panel/Queue/QueueGridRendererTest.php create mode 100644 tests/Panel/Queue/QueuePanelTest.php create mode 100644 tests/Panel/Request/RequestDiagnosticValueRendererTest.php delete mode 100644 tests/Panel/Router/RouterCurrentViewTest.php create mode 100644 tests/Panel/Router/RouterPanelTest.php delete mode 100644 tests/Panel/Router/RouterSectionRendererTest.php delete mode 100644 tests/Panel/User/UserGuestRendererTest.php delete mode 100644 tests/Panel/User/UserIdentityRendererTest.php create mode 100644 tests/Panel/User/UserPanelTest.php delete mode 100644 tests/Panel/User/UserRbacRendererTest.php delete mode 100644 tests/Provider/AssetBundleNormalizerProvider.php create mode 100644 tests/Provider/RequestRendererProvider.php create mode 100644 tests/Support/PanelViewAccessors.php diff --git a/composer.json b/composer.json index 851b0e2..5413d88 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "ui-awesome/html-interop": "^0.4", "ui-awesome/html-mixin": "^0.8.2", "ui-awesome/html-svg": "^0.6", - "php-forge/debug": "^0.1@dev" + "php-forge/debug": "^0.1" }, "require-dev": { "infection/infection": "^0.35", @@ -41,8 +41,8 @@ "phpunit/phpunit": "^12.5", "xepozz/internal-mocker": "^1.4", "yii2-extensions/scaffold": "^0.2", - "php-forge/vite": "^0.2@dev", - "php-forge/inertia": "^0.2@dev" + "php-forge/vite": "^0.2", + "php-forge/inertia": "^0.2" }, "autoload": { "psr-4": { diff --git a/resources/assets/dist/css/debug.min.css b/resources/assets/dist/css/debug.min.css index ddbeaf9..ceed151 100644 --- a/resources/assets/dist/css/debug.min.css +++ b/resources/assets/dist/css/debug.min.css @@ -1 +1 @@ -:root,:host{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--yii-debug-font-sans:"IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, blinkmacsystemfont, "Segoe UI", roboto, helvetica, arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";--yii-debug-font-mono:"JetBrains Mono", ui-monospace, sfmono-regular, menlo, monaco, consolas, "Liberation Mono", "Courier New", monospace;--yii-debug-font-display:var(--yii-debug-font-mono);--yii-debug-space-1:4px;--yii-debug-space-2:8px;--yii-debug-space-3:12px;--yii-debug-space-4:16px;--yii-debug-space-5:24px;--yii-debug-space-6:32px;--yii-debug-radius-sm:6px;--yii-debug-radius-md:10px;--yii-debug-radius-lg:14px;--yii-debug-radius-pill:999px;--yii-debug-fs-xs:.75rem;--yii-debug-fs-sm:.8125rem;--yii-debug-fs-base:.9375rem;--yii-debug-fs-lg:1.0625rem;--yii-debug-fs-xl:1.25rem;--yii-debug-fs-2xl:1.5rem;--yii-debug-control-min-size:36px;--yii-debug-panel-padding:var(--yii-debug-space-5);--yii-debug-row-padding-block:10px;--yii-debug-duration-fast:.12s;--yii-debug-duration-normal:.18s;--yii-debug-panel-bg:var(--lightningcss-light,#f2f5f1)var(--lightningcss-dark,#0c120e);--yii-debug-panel-bg-accent:var(--lightningcss-light,#0e7a551a)var(--lightningcss-dark,#2fd08c14);--yii-debug-panel-surface:var(--lightningcss-light,#fffffff0)var(--lightningcss-dark,#121a15eb);--yii-debug-panel-surface-muted:var(--lightningcss-light,#f6f8f5)var(--lightningcss-dark,#101812);--yii-debug-panel-surface-hover:var(--lightningcss-light,#e9f2ec)var(--lightningcss-dark,#1a241d);--yii-debug-panel-border:var(--lightningcss-light,#5d7a6942)var(--lightningcss-dark,#8fa39633);--yii-debug-panel-border-strong:var(--lightningcss-light,#4660515c)var(--lightningcss-dark,#8fa39652);--yii-debug-panel-text:var(--lightningcss-light,#16211a)var(--lightningcss-dark,#e4ede6);--yii-debug-panel-muted:var(--lightningcss-light,#5c6b60)var(--lightningcss-dark,#8fa396);--yii-debug-panel-link:var(--lightningcss-light,#0b6b4a)var(--lightningcss-dark,#4fd39a);--yii-debug-panel-link-hover:var(--lightningcss-light,#0d855b)var(--lightningcss-dark,#7fe4b8);--yii-debug-panel-primary:var(--lightningcss-light,#0e7a55)var(--lightningcss-dark,#2fd08c);--yii-debug-primary-bg:linear-gradient(135deg, var(--yii-debug-panel-primary-strong), var(--yii-debug-panel-primary));--yii-debug-on-primary:var(--lightningcss-light,#fff)var(--lightningcss-dark,#06281b);--yii-debug-panel-primary-strong:var(--lightningcss-light,#0a5c40)var(--lightningcss-dark,#17a673);--yii-debug-focus-ring:var(--lightningcss-light,#075c40)var(--lightningcss-dark,#7fe4b8);--yii-debug-success:var(--lightningcss-light,#0d7d47)var(--lightningcss-dark,#34c97e);--yii-debug-warning:var(--lightningcss-light,#8f5000)var(--lightningcss-dark,#e8a33d);--yii-debug-danger:var(--lightningcss-light,#c93838)var(--lightningcss-dark,#f16a6a);--yii-debug-on-success:var(--lightningcss-light,#fff)var(--lightningcss-dark,#0c120e);--yii-debug-on-warning:var(--lightningcss-light,#fff)var(--lightningcss-dark,#1c1917);--yii-debug-on-danger:var(--lightningcss-light,#fff)var(--lightningcss-dark,#1c1917);--yii-debug-info:var(--lightningcss-light,#205f9d)var(--lightningcss-dark,#5fa8e8);--yii-debug-on-info:var(--yii-debug-panel-bg);--yii-debug-panel-code-bg:var(--lightningcss-light,#ecf1ec)var(--lightningcss-dark,#17211a);--yii-debug-panel-pill:var(--lightningcss-light,#16211a0f)var(--lightningcss-dark,#8fa3962e);--yii-debug-panel-shadow:var(--lightningcss-light,0 22px 60px #16211a14)var(--lightningcss-dark,0 22px 60px #0006);--yii-debug-border:1px solid var(--yii-debug-panel-border);--yii-debug-border-strong:1px solid var(--yii-debug-panel-border-strong);--yii-debug-shadow-sm:0 1px 2px #0f172a0f;--yii-debug-shadow-md:0 10px 24px #0f172a14;--yii-debug-verb-get:var(--lightningcss-light,#0b7285)var(--lightningcss-dark,#4dc4d9);--yii-debug-verb-post:var(--lightningcss-light,#6941c6)var(--lightningcss-dark,#a78bfa);--yii-debug-verb-put:var(--lightningcss-light,#b26a00)var(--lightningcss-dark,#e8a33d);--yii-debug-verb-delete:var(--lightningcss-light,#c93838)var(--lightningcss-dark,#f16a6a);--yii-debug-verb-other:var(--yii-debug-panel-muted);--yii-debug-status-2xx:var(--yii-debug-success);--yii-debug-status-3xx:var(--yii-debug-warning);--yii-debug-status-4xx:var(--lightningcss-light,#c2410c)var(--lightningcss-dark,#fb923c);--yii-debug-status-5xx:var(--yii-debug-danger);--yii-debug-level-error:var(--yii-debug-danger);--yii-debug-level-warning:var(--yii-debug-warning);--yii-debug-level-info:var(--yii-debug-info);--yii-debug-level-trace:var(--yii-debug-panel-muted);--yii-debug-level-profile:var(--lightningcss-light,#9333ea)var(--lightningcss-dark,#b16be8);--yii-debug-level-other:var(--yii-debug-panel-muted);--yii-debug-gauge-fill:var(--yii-debug-panel-primary);--yii-debug-gauge-track:var(--lightningcss-light,#0e7a5529)var(--lightningcss-dark,#2fd08c24);--yii-debug-sql-kw:var(--lightningcss-light,#0a5c40)var(--lightningcss-dark,#4fd39a);--yii-debug-sql-str:var(--lightningcss-light,#8f4e00)var(--lightningcss-dark,#e8a33d);--yii-debug-sql-num:var(--yii-debug-info);--yii-debug-sql-comment:var(--yii-debug-panel-muted);--yii-debug-sql-param:var(--lightningcss-light,#6941c6)var(--lightningcss-dark,#a78bfa);--yii-debug-cat-app:var(--lightningcss-light,#0a6b47)var(--lightningcss-dark,#14b06e);--yii-debug-cat-db:var(--lightningcss-light,#0994b8)var(--lightningcss-dark,#0999be);--yii-debug-cat-view:var(--lightningcss-light,#6941c6)var(--lightningcss-dark,#8f6ce8);--yii-debug-cat-cache:var(--lightningcss-light,#b26a00)var(--lightningcss-dark,#c28300);--yii-debug-cat-mail:var(--lightningcss-light,#b23a67)var(--lightningcss-dark,#d95f8f);--yii-debug-cat-queue:var(--lightningcss-light,#3f4dbd)var(--lightningcss-dark,#6d7ce6);--yii-debug-cat-other:var(--yii-debug-panel-muted)}@media (prefers-color-scheme:dark){:root,:host{--lightningcss-light: ;--lightningcss-dark:initial}}:root[data-yii-debug-theme=light],:host([data-theme=light]){--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}:root[data-yii-debug-theme=dark],:host([data-theme=dark]){--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(../fonts/jetbrains-mono-latin-400-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:500;src:url(../fonts/jetbrains-mono-latin-500-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:700;src:url(../fonts/jetbrains-mono-latin-700-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(../fonts/ibm-plex-sans-latin-400-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(../fonts/ibm-plex-sans-latin-500-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(../fonts/ibm-plex-sans-latin-600-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:700;src:url(../fonts/ibm-plex-sans-latin-700-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}.yii-debug .yii-debug-event-controls{margin-block:var(--yii-debug-space-4);font-size:var(--yii-debug-fs-xs)}.yii-debug .yii-debug-event-groups{align-items:center;gap:var(--yii-debug-space-2);margin-block:var(--yii-debug-space-4);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-event-group{align-items:center;gap:var(--yii-debug-space-2);min-height:var(--yii-debug-control-min-size);padding:var(--yii-debug-space-1) var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface-muted);overflow-wrap:anywhere;display:inline-flex}.yii-debug .yii-debug-event-item>summary{cursor:pointer;list-style:none}.yii-debug .yii-debug-event-item>summary:focus-visible{outline:2px solid var(--yii-debug-panel-primary);outline-offset:4px;border-radius:var(--yii-debug-radius-sm)}.yii-debug :is(.yii-debug-event-clock,.yii-debug-event-identity){gap:var(--yii-debug-space-1);display:grid}.yii-debug .yii-debug-event-clock{min-width:105px}.yii-debug .yii-debug-event-time{white-space:nowrap;color:var(--yii-debug-panel-primary)}.yii-debug .yii-debug-event-identity{overflow-wrap:anywhere;min-width:0}.yii-debug .yii-debug-event-name:before{content:"+ ";color:var(--yii-debug-panel-primary)}.yii-debug .yii-debug-event-item[open] .yii-debug-event-name:before{content:"− "}.yii-debug .yii-debug-event-timing{font-size:var(--yii-debug-fs-xs)}.yii-debug .yii-debug-event-detail-row{display:none}.yii-debug .yii-debug-table tr:has(.yii-debug-event-item[open])+.yii-debug-event-detail-row{display:table-row}.yii-debug .yii-debug-table .yii-debug-event-detail-row>td{padding:0}.yii-debug .yii-debug-event-detail{gap:var(--yii-debug-space-4);padding:var(--yii-debug-space-4);background:var(--yii-debug-panel-surface-muted);white-space:normal;grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.yii-debug .yii-debug-event-detail>div{min-width:0}.yii-debug .yii-debug-event-detail-footer{gap:var(--yii-debug-space-2) var(--yii-debug-space-4);flex-wrap:wrap;grid-column:1/-1;display:flex}.yii-debug .yii-debug-event-metadata{gap:var(--yii-debug-space-2) var(--yii-debug-space-3);grid-template-columns:minmax(75px,.4fr) minmax(0,1fr);display:grid}.yii-debug .yii-debug-event-metadata dt{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-event-metadata dd{margin:0}.yii-debug .yii-debug-event-trace pre{white-space:pre-wrap;overflow-wrap:anywhere}.yii-debug :is(.yii-debug-event-context,.yii-debug-event-trace)>:last-child{margin-bottom:0}@media not (min-width:768px){.yii-debug .yii-debug-event-detail{grid-template-columns:minmax(0,1fr)}}.yii-debug .yii-debug-event-coverage>summary{min-height:var(--yii-debug-control-min-size);padding-block:var(--yii-debug-space-2);cursor:pointer}.yii-debug{background:radial-gradient(circle at top left, var(--yii-debug-panel-bg-accent), transparent 36rem), var(--yii-debug-panel-bg);min-height:100vh;color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-base);margin:0;padding:0;line-height:1.5}.yii-debug *,.yii-debug :before,.yii-debug :after{box-sizing:border-box}.yii-debug h1,.yii-debug h2,.yii-debug h3,.yii-debug h4,.yii-debug h5,.yii-debug h6{margin:0 0 var(--yii-debug-space-3);color:var(--yii-debug-panel-text);letter-spacing:-.01em;font-weight:700;line-height:1.25}.yii-debug h1{font-size:var(--yii-debug-fs-2xl)}.yii-debug h2{font-size:var(--yii-debug-fs-xl)}.yii-debug h3{font-size:var(--yii-debug-fs-lg)}.yii-debug h4,.yii-debug h5,.yii-debug h6{font-size:var(--yii-debug-fs-base)}.yii-debug p,.yii-debug ul,.yii-debug ol,.yii-debug dl,.yii-debug figure{margin:0 0 var(--yii-debug-space-3)}.yii-debug ul,.yii-debug ol{padding-left:var(--yii-debug-space-5)}.yii-debug strong,.yii-debug b{font-weight:600}.yii-debug a{color:var(--yii-debug-panel-link);text-decoration:none;transition:color .12s}.yii-debug a:hover,.yii-debug a:focus{color:var(--yii-debug-panel-link-hover);text-decoration:underline}.yii-debug code,.yii-debug kbd,.yii-debug samp{border-radius:var(--yii-debug-radius-sm);background:var(--yii-debug-panel-code-bg);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-variant-ligatures:none;padding:.1em .35em;font-size:.875em}.yii-debug pre{margin:0 0 var(--yii-debug-space-3);padding:var(--yii-debug-space-3);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-code-bg);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);font-variant-ligatures:none;line-height:1.4;overflow-x:auto}.yii-debug pre code{font-size:inherit;background:0 0;padding:0}.yii-debug :focus-visible{outline:3px solid var(--yii-debug-focus-ring);outline-offset:3px}.yii-debug .yii-debug-page{max-width:1680px;padding:var(--yii-debug-space-4);margin:0 auto}.yii-debug .yii-debug-layout{gap:var(--yii-debug-space-4);grid-template-columns:240px 1fr;align-items:start;display:grid}@media (max-width:959px){.yii-debug .yii-debug-layout{grid-template-columns:1fr}}@media (min-width:960px){.yii-debug .yii-debug-sidebar{top:var(--yii-debug-space-4);max-height:calc(100vh - var(--yii-debug-space-5));position:sticky;overflow-y:auto}}.yii-debug .yii-debug-main{min-width:0;container:yii-debug-main/inline-size}.yii-debug .yii-debug-main>*+:is(h2,.yii-debug-disclosure),.yii-debug .yii-debug-tab-panel>.yii-debug-table-wrap+.yii-debug-disclosure{margin-top:var(--yii-debug-space-5)}.yii-debug .yii-debug-main>.yii-debug-sr-only:first-child+:is(h2,.yii-debug-disclosure){margin-top:0}.yii-debug .yii-debug-mini-toolbar{align-items:center;gap:var(--yii-debug-space-2);margin-bottom:var(--yii-debug-space-4);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-side-section{gap:var(--yii-debug-space-2);margin:0 0 var(--yii-debug-space-3);padding:var(--yii-debug-space-3);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);flex-direction:column;display:flex}.yii-debug .yii-debug-side-section-title{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;font-weight:700}.yii-debug .yii-debug-side-section-link{color:var(--yii-debug-panel-link);font-size:var(--yii-debug-fs-sm);text-decoration:none}.yii-debug .yii-debug-side-section-link:hover,.yii-debug .yii-debug-side-section-link:focus{color:var(--yii-debug-panel-link-hover);text-decoration:underline}.yii-debug .yii-debug-request-nav-row{width:100%;margin-top:var(--yii-debug-space-2);grid-template-columns:repeat(4,1fr);gap:6px;display:grid}.yii-debug .yii-debug-btn-icon{justify-content:center;align-items:center;padding:4px 0;font-size:16px;line-height:1;display:inline-flex}.yii-debug .yii-debug-btn-icon svg{width:1em;height:1em}.yii-debug .yii-debug-request-nav .yii-debug-btn{justify-content:center;width:100%}.yii-debug .yii-debug-brand-bar{align-items:center;gap:var(--yii-debug-space-2);margin-bottom:var(--yii-debug-space-3);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:linear-gradient(180deg, color-mix(in srgb, var(--yii-debug-panel-surface) 96%, transparent), color-mix(in srgb, var(--yii-debug-panel-surface-muted) 88%, transparent));box-shadow:var(--yii-debug-shadow-sm);flex-wrap:wrap;padding:8px 12px;display:flex}.yii-debug .yii-debug-brand-chip{border:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 70%, transparent);border-radius:var(--yii-debug-radius-pill);background:color-mix(in srgb, var(--yii-debug-panel-surface), transparent 30%);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);min-height:var(--yii-debug-control-min-size);align-items:center;gap:8px;padding:6px 12px;text-decoration:none;transition:border-color .14s,transform .14s,box-shadow .14s;display:inline-flex}.yii-debug a.yii-debug-brand-chip:hover,.yii-debug a.yii-debug-brand-chip:focus{border-color:var(--yii-debug-panel-primary);box-shadow:var(--yii-debug-shadow-sm);text-decoration:none;transform:translateY(-1px)}.yii-debug .yii-debug-brand-icon{flex:none;justify-content:center;align-items:center;width:22px;height:22px;display:inline-flex}.yii-debug .yii-debug-brand-icon svg{width:100%;height:100%}.yii-debug .yii-debug-brand-chip-php .yii-debug-brand-icon{width:32px}.yii-debug .yii-debug-brand-chip-php .yii-debug-brand-icon svg{fill:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-brand-label{color:var(--yii-debug-panel-muted);letter-spacing:.06em;text-transform:uppercase;font-weight:600}.yii-debug .yii-debug-brand-value{color:var(--yii-debug-panel-text);font-weight:700}.yii-debug .yii-debug-brand-chip-mem{margin-left:auto}.yii-debug .yii-debug-brand-chip-config{border:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);text-decoration:none;transition:transform .14s,border-color .14s,color .14s,background-color .14s}.yii-debug .yii-debug-brand-chip-config .yii-debug-brand-icon{justify-content:center;align-items:center;width:14px;height:14px;transition:transform .24s;display:inline-flex}.yii-debug .yii-debug-brand-chip-config .yii-debug-brand-icon svg{width:100%;height:100%}.yii-debug .yii-debug-brand-chip-config:hover,.yii-debug .yii-debug-brand-chip-config:focus-visible{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 50%);color:var(--yii-debug-panel-primary-strong);text-decoration:none;transform:translateY(-1px)}.yii-debug .yii-debug-brand-chip-config:hover .yii-debug-brand-icon,.yii-debug .yii-debug-brand-chip-config:focus-visible .yii-debug-brand-icon{transform:rotate(45deg)}.yii-debug .yii-debug-brand-chip-config.is-disabled{opacity:.5;cursor:default;pointer-events:none}.yii-debug .yii-debug-brand-chip-config{margin-left:auto}.yii-debug .yii-debug-brand-bar:has(.yii-debug-brand-chip-mem) .yii-debug-brand-chip-config{margin-left:0}.yii-debug .yii-debug-brand-chip-theme{border:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);cursor:pointer;padding:6px 10px;transition:transform .14s,border-color .14s,color .14s,background-color .14s}.yii-debug .yii-debug-brand-chip-theme:hover,.yii-debug .yii-debug-brand-chip-theme:focus-visible{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 50%);color:var(--yii-debug-panel-primary-strong);transform:translateY(-1px)}.yii-debug .yii-debug-brand-chip-theme .yii-debug-brand-icon{justify-content:center;align-items:center;width:16px;height:16px;transition:transform .24s;display:inline-flex}.yii-debug .yii-debug-brand-chip-theme .yii-debug-brand-icon svg{width:100%;height:100%}.yii-debug .yii-debug-brand-chip-theme:hover .yii-debug-brand-icon{transform:rotate(20deg)}.yii-debug .yii-debug-snapshot-line{min-width:0;font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);align-items:baseline;gap:6px;display:flex}.yii-debug .yii-debug-snapshot-method{color:color-mix(in srgb, var(--yii-debug-hue,var(--yii-debug-panel-primary-strong)) 80%, var(--yii-debug-panel-text));flex:none;font-weight:700}.yii-debug .yii-debug-snapshot-url{min-width:0;color:var(--yii-debug-panel-text);text-overflow:ellipsis;white-space:nowrap;flex:0 auto;overflow:hidden}.yii-debug .yii-debug-snapshot-meta{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);align-items:center;gap:6px;display:flex}.yii-debug .yii-debug-snapshot-status{border-radius:var(--yii-debug-radius-pill);padding:1px 6px;font-weight:700;display:inline-block}.yii-debug .yii-debug-snapshot-tag{border:1px solid var(--yii-debug-panel-border);border-radius:var(--yii-debug-radius-pill);padding:0 6px}.yii-debug .yii-debug-request-hero{margin-bottom:var(--yii-debug-space-4);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface-muted);box-shadow:var(--yii-debug-shadow-sm)}.yii-debug .yii-debug-request-overview{margin-bottom:var(--yii-debug-space-4);border:var(--yii-debug-border);border-inline-start:4px solid color-mix(in srgb, var(--yii-debug-hue,var(--yii-debug-panel-muted)) 76%, var(--yii-debug-panel-text));border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);overflow:hidden}.yii-debug .yii-debug-request-overview-header{gap:var(--yii-debug-space-4);padding:var(--yii-debug-space-4);grid-template-columns:minmax(0,1fr) auto;align-items:center;display:grid}.yii-debug .yii-debug-request-overview-identity{min-width:0}.yii-debug .yii-debug-request-overview-label,.yii-debug .yii-debug-request-overview-status-label,.yii-debug .yii-debug-panel-heading-eyebrow{color:var(--yii-debug-panel-muted);letter-spacing:.09em;text-transform:uppercase;margin-bottom:5px;font-size:.6875rem;font-weight:700;line-height:1.2;display:block}.yii-debug .yii-debug-request-hero-line{align-items:center;gap:var(--yii-debug-space-2);min-width:0;font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-base);display:flex}.yii-debug .yii-debug-request-hero-method{border-radius:var(--yii-debug-radius-pill);letter-spacing:.04em;flex:none;padding:2px 10px;font-weight:700}.yii-debug .yii-debug-request-hero-url{min-width:0;color:var(--yii-debug-panel-text);text-overflow:ellipsis;white-space:nowrap;flex:auto;overflow:hidden}.yii-debug .yii-debug-request-hero-meta{align-items:center;gap:6px var(--yii-debug-space-3);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);flex-wrap:wrap;margin-top:6px;display:flex}.yii-debug .yii-debug-request-hero-meta:empty{display:none}.yii-debug .yii-debug-request-hero-meta-item{align-items:baseline;gap:var(--yii-debug-space-1);display:inline-flex}.yii-debug .yii-debug-request-hero-meta-label{font-family:var(--yii-debug-font-sans);letter-spacing:.07em;text-transform:uppercase;font-size:.625rem;font-weight:700}.yii-debug .yii-debug-request-hero-meta-value{color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-request-overview-status{text-align:right;min-width:72px}.yii-debug .yii-debug-request-overview-status-value{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-lg);letter-spacing:-.03em;padding:3px 9px;line-height:1.2}.yii-debug .yii-debug-request-overview-metrics{border-block:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);grid-template-columns:minmax(0,1fr) minmax(0,2fr) auto;margin:0;display:grid}.yii-debug .yii-debug-request-overview-metric{min-width:0;padding:var(--yii-debug-space-3) var(--yii-debug-space-4)}.yii-debug .yii-debug-request-overview-metric+:is(.yii-debug .yii-debug-request-overview-metric){border-inline-start:var(--yii-debug-border)}.yii-debug .yii-debug-request-overview-metric dt{color:var(--yii-debug-panel-muted);letter-spacing:.07em;text-transform:uppercase;margin-bottom:2px;font-size:.6875rem;font-weight:700}.yii-debug .yii-debug-request-overview-metric dd{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);overflow-wrap:anywhere;margin:0;font-weight:600}.yii-debug .yii-debug-request-overview-meta{align-items:center;gap:var(--yii-debug-space-2) var(--yii-debug-space-4);padding:10px var(--yii-debug-space-4);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-request-overview-meta-item{align-items:baseline;gap:6px;min-width:0;display:inline-flex}.yii-debug .yii-debug-request-overview-meta-label{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);letter-spacing:.07em;text-transform:uppercase;font-size:.625rem;font-weight:700}.yii-debug .yii-debug-request-overview-tag{max-width:34ch;color:var(--yii-debug-panel-text);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.yii-debug .yii-debug-request-overview-flag,.yii-debug .yii-debug-panel-heading-kind{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-pill);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);letter-spacing:.07em;text-transform:uppercase;padding:2px 8px;font-size:.625rem;font-weight:700;line-height:1.4}.yii-debug .yii-debug-request-routing-error{border-width:1px 0 0 4px;border-radius:0;margin:0}.yii-debug .yii-debug-request-routing-error p{margin:0}.yii-debug .yii-debug-request-tabs{min-width:0}.yii-debug .yii-debug-request-tabs>.yii-debug-tabs{scrollbar-width:thin;flex-wrap:nowrap;overflow-x:auto}.yii-debug .yii-debug-request-tabs .yii-debug-tab{flex:none}.yii-debug .yii-debug-request-tabs .yii-debug-empty-state{padding:var(--yii-debug-space-4)}.yii-debug .yii-debug-request-tabs .yii-debug-empty-state h2{font-size:var(--yii-debug-fs-base);margin:0}.yii-debug .yii-debug-diagnostic-shell{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);overflow:hidden}.yii-debug .yii-debug-diagnostic-header{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-4);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border-bottom:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);display:flex}.yii-debug .yii-debug-diagnostic-heading-copy{align-items:baseline;gap:var(--yii-debug-space-2) var(--yii-debug-space-3);flex-wrap:wrap;min-width:0;display:flex}.yii-debug .yii-debug-diagnostic-heading-copy h2{font-size:var(--yii-debug-fs-lg);margin:0}.yii-debug .yii-debug-diagnostic-counts{align-items:center;gap:var(--yii-debug-space-2);display:inline-flex}.yii-debug .yii-debug-diagnostic-count,.yii-debug .yii-debug-diagnostic-total,.yii-debug .yii-debug-header-lane-count,.yii-debug .yii-debug-server-group-count{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums}.yii-debug .yii-debug-diagnostic-count{align-items:baseline;gap:var(--yii-debug-space-1);display:inline-flex}.yii-debug .yii-debug-diagnostic-count+.yii-debug-diagnostic-count:before{width:1px;height:12px;background:var(--yii-debug-panel-border-strong);content:"";margin-inline-end:var(--yii-debug-space-1)}.yii-debug .yii-debug-diagnostic-count-value{color:var(--yii-debug-panel-text);font-weight:700}.yii-debug .yii-debug-diagnostic-filter{background:var(--yii-debug-panel-surface);width:min(280px,42%);max-width:none}.yii-debug .yii-debug-header-lanes{grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.yii-debug .yii-debug-header-lane{min-width:0;position:relative}.yii-debug .yii-debug-header-lane+.yii-debug-header-lane{border-inline-start:2px solid color-mix(in srgb, var(--yii-debug-panel-primary) 36%, var(--yii-debug-panel-border))}.yii-debug .yii-debug-header-lane+.yii-debug-header-lane:before{z-index:1;top:25px;border:2px solid var(--yii-debug-panel-surface);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-primary);content:"";width:8px;height:8px;position:absolute;inset-inline-start:-5px}.yii-debug .yii-debug-header-lane-header{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-3);min-height:58px;padding:10px var(--yii-debug-space-4);border-bottom:var(--yii-debug-border);display:flex}.yii-debug .yii-debug-header-lane-header h3{font-size:var(--yii-debug-fs-base);margin:1px 0 0}.yii-debug .yii-debug-header-direction{color:var(--yii-debug-panel-primary-strong);font-family:var(--yii-debug-font-mono);letter-spacing:.04em;font-size:.6875rem;font-weight:700;display:block}.yii-debug .yii-debug-diagnostic-ledger{margin:0}.yii-debug .yii-debug-diagnostic-row{border-top:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 58%, transparent);grid-template-columns:minmax(132px,.72fr) minmax(0,1.28fr);align-items:start;display:grid}.yii-debug .yii-debug-diagnostic-row:first-child{border-top:0}.yii-debug .yii-debug-diagnostic-row:hover{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-diagnostic-row dt,.yii-debug .yii-debug-diagnostic-row dd{min-width:0;padding:10px var(--yii-debug-space-4);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);line-height:1.5}.yii-debug .yii-debug-diagnostic-row dt{color:var(--yii-debug-panel-muted);overflow-wrap:anywhere;font-weight:600;overflow:hidden}.yii-debug .yii-debug-diagnostic-row dd{border-inline-start:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 45%, transparent);color:var(--yii-debug-panel-text);overflow-wrap:anywhere;white-space:pre-wrap;margin:0}.yii-debug .yii-debug-header-ledger .yii-debug-diagnostic-row:nth-child(2n),.yii-debug .yii-debug-server-ledger .yii-debug-diagnostic-row:nth-child(2n){background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 72%)}:is(.yii-debug .yii-debug-header-ledger .yii-debug-diagnostic-row:nth-child(2n),.yii-debug .yii-debug-server-ledger .yii-debug-diagnostic-row:nth-child(2n)):hover{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-diagnostic-values{gap:6px;display:grid}.yii-debug .yii-debug-diagnostic-value-count{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);font-size:.6875rem}.yii-debug .yii-debug-diagnostic-value-list{gap:var(--yii-debug-space-1);margin:0;padding:0;list-style:none;display:grid}.yii-debug .yii-debug-diagnostic-value-list li+li{padding-top:var(--yii-debug-space-1);border-top:1px dashed var(--yii-debug-panel-border)}.yii-debug .yii-debug-diagnostic-empty-value,.yii-debug .yii-debug-diagnostic-lane-empty,.yii-debug .yii-debug-diagnostic-empty-state,.yii-debug .yii-debug-diagnostic-filter-empty{color:var(--yii-debug-panel-muted);font-style:italic}.yii-debug .yii-debug-diagnostic-lane-empty,.yii-debug .yii-debug-diagnostic-empty-state,.yii-debug .yii-debug-diagnostic-filter-empty{padding:var(--yii-debug-space-4);margin:0}.yii-debug .yii-debug-diagnostic-filter-empty{border-top:var(--yii-debug-border);text-align:center}.yii-debug .yii-debug-server-group-body>.yii-debug-mini-toolbar{padding:var(--yii-debug-space-2) var(--yii-debug-space-4);border-bottom:var(--yii-debug-border);justify-content:flex-end;margin:0}.yii-debug .yii-debug-server-group-body>.yii-debug-mini-toolbar .yii-debug-filter-input{max-width:100%}.yii-debug .yii-debug-server-additional+.yii-debug-server-group,.yii-debug .yii-debug-server-group+.yii-debug-server-group{border-top:var(--yii-debug-border)}.yii-debug .yii-debug-server-group-summary{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-3);min-height:44px;padding:9px var(--yii-debug-space-4);border-bottom:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);display:flex}.yii-debug .yii-debug-server-group-summary .yii-debug-server-group-title{font-size:var(--yii-debug-fs-sm);margin:0;font-weight:700}.yii-debug .yii-debug-server-group-summary{cursor:pointer;list-style:none}.yii-debug .yii-debug-server-group-summary::-webkit-details-marker{display:none}.yii-debug .yii-debug-server-group-summary:hover,.yii-debug .yii-debug-server-group-summary:focus-visible{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-server-group-identity{align-items:baseline;gap:var(--yii-debug-space-2);display:inline-flex}.yii-debug .yii-debug-server-group:not([open])>.yii-debug-server-group-summary{border-bottom:0}.yii-debug .yii-debug-server-group-summary .yii-debug-disclosure-hint{margin-inline-start:auto}.yii-debug .yii-debug-diagnostic-row[hidden],.yii-debug .yii-debug-header-lane[hidden],.yii-debug .yii-debug-server-group[hidden],.yii-debug .yii-debug-diagnostic-filter-empty[hidden]{display:none}@container yii-debug-main (width<=900px){.yii-debug .yii-debug-header-lanes{grid-template-columns:minmax(0,1fr)}.yii-debug .yii-debug-header-lane+.yii-debug-header-lane{border-top:2px solid color-mix(in srgb, var(--yii-debug-panel-primary) 36%, var(--yii-debug-panel-border));border-inline-start:0}.yii-debug .yii-debug-header-lane+.yii-debug-header-lane:before{top:-5px;inset-inline-start:var(--yii-debug-space-4)}}@container yii-debug-main (width<=560px){.yii-debug .yii-debug-diagnostic-header{flex-direction:column;align-items:stretch}.yii-debug .yii-debug-diagnostic-filter{width:100%}.yii-debug .yii-debug-diagnostic-row{grid-template-columns:minmax(0,1fr)}.yii-debug .yii-debug-diagnostic-row dt{padding-bottom:var(--yii-debug-space-1)}.yii-debug .yii-debug-diagnostic-row dd{border-inline-start:0;padding-top:0}}.yii-debug .yii-debug-route-inventory-header{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-3);margin-bottom:var(--yii-debug-space-2);display:flex}.yii-debug .yii-debug-route-inventory-title{align-items:baseline;gap:var(--yii-debug-space-2);display:flex}.yii-debug .yii-debug-route-inventory-title h2{font-size:var(--yii-debug-fs-lg);margin:0}.yii-debug .yii-debug-route-inventory-count{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums}.yii-debug .yii-debug-route-inventory-context{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-3);margin-bottom:var(--yii-debug-space-3);display:flex}.yii-debug .yii-debug-route-inventory-provenance{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);margin:0}.yii-debug .yii-debug-route-inventory-badges,.yii-debug .yii-debug-route-methods{align-items:center;gap:var(--yii-debug-space-1);flex-wrap:wrap;display:inline-flex}.yii-debug .yii-debug-route-inventory-error{margin-bottom:var(--yii-debug-space-3)}.yii-debug .yii-debug-route-inventory-error p{margin:0}.yii-debug .yii-debug-route-ledger{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);overflow:hidden;container:routes/inline-size}.yii-debug .yii-debug-route-columns,.yii-debug .yii-debug-route-summary{align-items:center;gap:var(--yii-debug-space-3);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);grid-template-columns:24px 88px minmax(0,1fr) minmax(0,1fr) 132px;display:grid}.yii-debug .yii-debug-route-columns{background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);font-weight:600}.yii-debug .yii-debug-route-list{margin:0;padding:0;list-style:none}.yii-debug .yii-debug-route-list>li{border-top:var(--yii-debug-border)}.yii-debug .yii-debug-route-list>[data-yii-debug-route-match=true]{box-shadow:inset 3px 0 0 var(--yii-debug-success)}.yii-debug .yii-debug-route-summary{cursor:pointer;min-height:56px;list-style:none}.yii-debug .yii-debug-route-summary:hover{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-route-summary:focus-visible{outline:2px solid var(--yii-debug-focus-ring);outline-offset:-3px}.yii-debug .yii-debug-route-summary::-webkit-details-marker{display:none}.yii-debug .yii-debug-route-summary .yii-debug-disclosure-hint{text-align:right;font-size:.625rem}.yii-debug .yii-debug-route-order{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums}.yii-debug .yii-debug-route-method{border-radius:var(--yii-debug-radius-pill);min-height:20px;font-family:var(--yii-debug-font-mono);letter-spacing:.02em;white-space:nowrap;align-items:center;padding:1px 7px;font-size:.6875rem;font-weight:700;line-height:1;display:inline-flex}.yii-debug .yii-debug-route-methods-any{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs)}.yii-debug .yii-debug-route-pattern,.yii-debug .yii-debug-route-identity{overflow-wrap:anywhere;white-space:normal;min-width:0;font-size:var(--yii-debug-fs-xs)}:is(.yii-debug .yii-debug-route-pattern,.yii-debug .yii-debug-route-identity) code{white-space:normal}.yii-debug .yii-debug-route-pattern,.yii-debug .yii-debug-route-identity code{background:0 0;padding:0}.yii-debug .yii-debug-route-identity{align-items:center;gap:var(--yii-debug-space-2);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-route-metadata{padding:var(--yii-debug-space-4);border-top:var(--yii-debug-border);background:var(--yii-debug-panel-surface);font-size:var(--yii-debug-fs-sm);margin:0}.yii-debug .yii-debug-route-metadata>div{gap:var(--yii-debug-space-4);grid-template-columns:120px minmax(0,1fr);display:grid}.yii-debug .yii-debug-route-metadata>div+div{margin-top:var(--yii-debug-space-3)}.yii-debug .yii-debug-route-metadata dt{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-route-metadata dd{overflow-wrap:anywhere;min-width:0;font-family:var(--yii-debug-font-mono);margin:0}@container routes (width<640px){.yii-debug .yii-debug-route-columns{display:none}.yii-debug .yii-debug-route-summary{gap:var(--yii-debug-space-2);grid-template-columns:24px 72px minmax(0,1fr)}.yii-debug .yii-debug-route-identity{grid-column:3}.yii-debug .yii-debug-route-summary .yii-debug-disclosure-hint{text-align:left;grid-column:3}.yii-debug .yii-debug-route-metadata>div{gap:var(--yii-debug-space-1);grid-template-columns:minmax(0,1fr)}}.yii-debug .yii-debug-route-resolution{margin-top:var(--yii-debug-space-4)}.yii-debug .yii-debug-route-resolution-message{margin:0 0 var(--yii-debug-space-3);color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-sm)}.yii-debug .yii-debug-route-trace{min-width:560px}.yii-debug .yii-debug-panel-heading{justify-content:space-between;align-items:flex-end;gap:var(--yii-debug-space-3);margin:var(--yii-debug-space-5) 0 var(--yii-debug-space-3);padding-bottom:var(--yii-debug-space-2);border-bottom:var(--yii-debug-border);display:flex}.yii-debug .yii-debug-panel-heading-copy{min-width:0}.yii-debug .yii-debug-panel-heading-copy h2{margin:0}.yii-debug .yii-debug-panel-heading-kind{flex:none;margin-bottom:2px}.yii-debug pre.yii-debug-panel-payload{min-height:180px;padding:var(--yii-debug-space-4);border:var(--yii-debug-border);background:linear-gradient(90deg, color-mix(in srgb, var(--yii-debug-panel-primary), transparent 88%), transparent 42%), var(--yii-debug-panel-code-bg);margin-bottom:0;line-height:1.55}@media (max-width:640px){.yii-debug .yii-debug-request-overview-header{gap:var(--yii-debug-space-3);grid-template-columns:minmax(0,1fr)}.yii-debug .yii-debug-request-overview-status{align-items:center;gap:var(--yii-debug-space-2);text-align:left;display:flex}.yii-debug .yii-debug-request-overview-status-label{margin:0}.yii-debug .yii-debug-request-overview-metrics{grid-template-columns:1fr}.yii-debug .yii-debug-request-overview-metric+.yii-debug-request-overview-metric{border-block-start:var(--yii-debug-border);border-inline-start:0}.yii-debug .yii-debug-request-overview-meta-item{max-width:100%}.yii-debug .yii-debug-panel-heading{align-items:flex-start}.yii-debug .yii-debug-route-inventory-header,.yii-debug .yii-debug-route-inventory-context{flex-direction:column;align-items:flex-start}.yii-debug .yii-debug-route-filter{width:100%;max-width:none}}.yii-debug .yii-debug-section-header{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-3);margin:var(--yii-debug-space-4) 0 var(--yii-debug-space-2);display:flex}.yii-debug .yii-debug-section-header h2,.yii-debug .yii-debug-section-header h3{margin:0}.yii-debug .yii-debug-filter-input{width:240px;max-width:50%;padding:4px var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font:inherit;flex:none}.yii-debug .yii-debug-filter-input::placeholder{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-filter-input:focus{border-color:var(--yii-debug-panel-primary)}.yii-debug .yii-debug-stack>*+*{margin-top:var(--yii-debug-space-4)}.yii-debug .yii-debug-grid-2{gap:var(--yii-debug-space-4);grid-template-columns:repeat(auto-fit,minmax(280px,1fr));display:grid}.yii-debug .yii-debug-card{padding:var(--yii-debug-panel-padding);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-md)}.yii-debug .yii-debug-nav{padding:var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);margin:0}.yii-debug .yii-debug-nav ul{flex-direction:column;margin:0;padding:0;list-style:none;display:flex}.yii-debug .yii-debug-nav-group{margin-top:var(--yii-debug-space-3)}.yii-debug .yii-debug-nav-group>.yii-debug-nav{box-shadow:none;background:0 0;border:0;border-radius:0;padding:0}.yii-debug .yii-debug-nav-link{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-2);border-radius:var(--yii-debug-radius-md);color:var(--yii-debug-panel-text);padding:8px 12px;font-weight:600;text-decoration:none;transition:background .12s,color .12s;display:flex}.yii-debug .yii-debug-nav-link:after{content:"›";color:var(--yii-debug-panel-muted);font-size:1.1em;line-height:1}.yii-debug .yii-debug-nav-link:hover,.yii-debug .yii-debug-nav-link:focus{background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-text);text-decoration:none}.yii-debug .yii-debug-nav-link.is-active,.yii-debug .yii-debug-nav-link[aria-current=page]{background:var(--yii-debug-primary-bg);color:var(--yii-debug-on-primary)}:is(.yii-debug .yii-debug-nav-link.is-active,.yii-debug .yii-debug-nav-link[aria-current=page]):after{color:inherit}.yii-debug .yii-debug-btn{justify-content:center;align-items:center;gap:var(--yii-debug-space-1);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);min-height:32px;color:var(--yii-debug-panel-text);font-family:inherit;font-size:var(--yii-debug-fs-sm);letter-spacing:0;text-transform:none;cursor:pointer;padding:6px 14px;font-weight:600;line-height:1;text-decoration:none;transition:border-color .12s,background .12s,color .12s,transform .12s,box-shadow .12s;display:inline-flex}.yii-debug .yii-debug-btn:hover,.yii-debug .yii-debug-btn:focus{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 30%);background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-link-hover);text-decoration:none;transform:translateY(-1px)}.yii-debug .yii-debug-btn:active{transform:translateY(0)}.yii-debug .yii-debug-btn[disabled],.yii-debug .yii-debug-btn.is-disabled{opacity:.55;pointer-events:none;transform:none}.yii-debug .yii-debug-btn-primary{background:var(--yii-debug-primary-bg);color:var(--yii-debug-on-primary);border-color:#0000}.yii-debug .yii-debug-btn-primary:is(:hover,:focus){background:var(--yii-debug-panel-primary-strong);color:var(--yii-debug-on-primary)}.yii-debug .yii-debug-btn-ghost{border-color:var(--yii-debug-panel-border);background:0 0}.yii-debug .yii-debug-btn-subtle{background:var(--yii-debug-panel-surface-muted);border-color:#0000}.yii-debug .yii-debug-btn-sm{min-height:28px;font-size:var(--yii-debug-fs-xs);padding:4px 10px}.yii-debug .yii-debug-btn-group{gap:var(--yii-debug-space-1);flex-wrap:wrap;display:inline-flex}.yii-debug .yii-debug-badge{border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-xs);letter-spacing:.01em;align-items:center;padding:2px 10px;font-weight:600;line-height:1.4;display:inline-flex}.yii-debug .yii-debug-badge-success{background:var(--yii-debug-success);color:var(--yii-debug-on-success)}.yii-debug .yii-debug-badge-info{background:var(--yii-debug-info);color:var(--yii-debug-on-info)}.yii-debug .yii-debug-badge-warning{background:var(--yii-debug-warning);color:var(--yii-debug-on-warning)}.yii-debug .yii-debug-badge-danger{background:var(--yii-debug-danger);color:var(--yii-debug-on-danger)}.yii-debug .yii-debug-badge-muted{background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-verb-get{--yii-debug-hue:var(--yii-debug-verb-get)}.yii-debug .yii-debug-verb-post{--yii-debug-hue:var(--yii-debug-verb-post)}.yii-debug .yii-debug-verb-put{--yii-debug-hue:var(--yii-debug-verb-put)}.yii-debug .yii-debug-verb-delete{--yii-debug-hue:var(--yii-debug-verb-delete)}.yii-debug .yii-debug-verb-other{--yii-debug-hue:var(--yii-debug-verb-other)}.yii-debug .yii-debug-status-2xx{--yii-debug-hue:var(--yii-debug-status-2xx)}.yii-debug .yii-debug-status-3xx{--yii-debug-hue:var(--yii-debug-status-3xx)}.yii-debug .yii-debug-status-4xx{--yii-debug-hue:var(--yii-debug-status-4xx)}.yii-debug .yii-debug-status-5xx{--yii-debug-hue:var(--yii-debug-status-5xx)}.yii-debug .yii-debug-status-none{--yii-debug-hue:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-level-error{--yii-debug-hue:var(--yii-debug-level-error)}.yii-debug .yii-debug-level-warning{--yii-debug-hue:var(--yii-debug-level-warning)}.yii-debug .yii-debug-level-info{--yii-debug-hue:var(--yii-debug-level-info)}.yii-debug .yii-debug-level-trace{--yii-debug-hue:var(--yii-debug-level-trace)}.yii-debug .yii-debug-level-profile{--yii-debug-hue:var(--yii-debug-level-profile)}.yii-debug .yii-debug-level-other{--yii-debug-hue:var(--yii-debug-level-other)}.yii-debug .yii-debug-request-hero-method,.yii-debug .yii-debug-route-method,.yii-debug .yii-debug-snapshot-status,.yii-debug .yii-debug-level-chip,.yii-debug .yii-debug-db-type,.yii-debug .yii-debug-mail-status,.yii-debug .yii-debug-dump-type,.yii-debug .yii-debug-badge:is(.yii-debug-status-2xx,.yii-debug-status-3xx,.yii-debug-status-4xx,.yii-debug-status-5xx,.yii-debug-status-none){border:1px solid color-mix(in srgb, var(--yii-debug-hue,var(--yii-debug-panel-muted)), transparent 65%);background:color-mix(in srgb, var(--yii-debug-hue,var(--yii-debug-panel-muted)), transparent 90%);color:color-mix(in srgb, var(--yii-debug-hue,var(--yii-debug-panel-muted)) 80%, var(--yii-debug-panel-text))}.yii-debug .yii-debug-method{color:color-mix(in srgb, var(--yii-debug-hue,var(--yii-debug-panel-muted)) 80%, var(--yii-debug-panel-text));font-family:var(--yii-debug-font-mono);letter-spacing:.02em;font-weight:700}.yii-debug .yii-debug-level-chip{border-radius:var(--yii-debug-radius-pill);font-family:var(--yii-debug-font-mono);letter-spacing:.04em;text-transform:uppercase;white-space:nowrap;padding:1px 8px;font-size:.7rem;font-weight:700;display:inline-block}.yii-debug .yii-debug-callout{align-items:flex-start;gap:var(--yii-debug-space-2);margin-bottom:var(--yii-debug-space-3);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border:var(--yii-debug-border);border-left:4px solid var(--yii-debug-panel-border-strong);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-text);display:flex}.yii-debug .yii-debug-callout-info{border-left-color:var(--yii-debug-info)}.yii-debug .yii-debug-callout-success{border-left-color:var(--yii-debug-success)}.yii-debug .yii-debug-callout-warning{border-left-color:var(--yii-debug-warning)}.yii-debug .yii-debug-callout-danger{border-left-color:var(--yii-debug-danger)}.yii-debug .yii-debug-router-callout{flex-direction:column}.yii-debug .yii-debug-router-callout-message{margin:0}.yii-debug .yii-debug-router-summary{gap:4px var(--yii-debug-space-3);margin:0 0 var(--yii-debug-space-3);font-size:var(--yii-debug-fs-sm);grid-template-columns:max-content minmax(0,1fr);display:grid}.yii-debug .yii-debug-router-summary dt{color:var(--yii-debug-panel-muted);letter-spacing:.04em;text-transform:uppercase;font-weight:600;font-size:var(--yii-debug-fs-xs);align-self:center}.yii-debug .yii-debug-router-summary dd{overflow-wrap:anywhere;min-width:0;margin:0}.yii-debug .yii-debug-router-summary code{border-radius:var(--yii-debug-radius-sm);background:var(--yii-debug-panel-surface);min-width:0;color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);overflow-wrap:anywhere;padding:1px 8px;display:inline}.yii-debug .yii-debug-table-wrap{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);overflow-x:auto}.yii-debug .yii-debug-table{border-collapse:separate;border-spacing:0;width:100%;min-width:640px;color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);background:0 0;margin:0}.yii-debug .yii-debug-table thead th,.yii-debug .yii-debug-table thead td{border-bottom:var(--yii-debug-border);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 20%);color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);letter-spacing:.03em;text-align:left;text-transform:uppercase;vertical-align:middle;padding:10px 12px;font-weight:700}.yii-debug .yii-debug-table thead th a{color:var(--yii-debug-panel-link);font-weight:700;text-decoration:none}.yii-debug .yii-debug-table thead th a:hover,.yii-debug .yii-debug-table thead th a:focus{color:var(--yii-debug-panel-link-hover);text-decoration:none}.yii-debug .yii-debug-table tbody td,.yii-debug .yii-debug-table tbody th{padding:var(--yii-debug-row-padding-block) 12px;border-top:1px solid var(--yii-debug-panel-border);vertical-align:top;overflow-wrap:anywhere}.yii-debug .yii-debug-table tbody th{white-space:nowrap}.yii-debug .yii-debug-table tbody tr:first-child td,.yii-debug .yii-debug-table tbody tr:first-child th{border-top:0}.yii-debug .yii-debug-table tbody tr:nth-child(2n) td,.yii-debug .yii-debug-table tbody tr:nth-child(2n) th{background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 50%)}.yii-debug .yii-debug-table tbody tr:hover td,.yii-debug .yii-debug-table tbody tr:hover th{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-table tfoot td{border-top:var(--yii-debug-border);color:var(--yii-debug-panel-muted);padding:10px 12px}.yii-debug .yii-debug-row-success td,.yii-debug .yii-debug-row-success th{background:color-mix(in srgb, var(--yii-debug-success), var(--yii-debug-panel-surface) 86%)!important}.yii-debug .yii-debug-row-info td,.yii-debug .yii-debug-row-info th{background:color-mix(in srgb, var(--yii-debug-info), var(--yii-debug-panel-surface) 86%)!important}.yii-debug .yii-debug-row-warning td,.yii-debug .yii-debug-row-warning th{background:color-mix(in srgb, var(--yii-debug-warning), var(--yii-debug-panel-surface) 86%)!important}.yii-debug .yii-debug-row-danger td,.yii-debug .yii-debug-row-danger th{background:color-mix(in srgb, var(--yii-debug-danger), var(--yii-debug-panel-surface) 86%)!important}.yii-debug .yii-debug-grid{margin-bottom:var(--yii-debug-space-4)}.yii-debug .yii-debug-grid>.summary{margin:0 0 var(--yii-debug-space-2);padding:0 var(--yii-debug-space-1);color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-sm)}.yii-debug .yii-debug-grid>.summary b{color:var(--yii-debug-panel-text);font-weight:700}.yii-debug .yii-debug-grid>.empty{padding:var(--yii-debug-space-5);color:var(--yii-debug-panel-muted);text-align:center;font-style:italic}.yii-debug .yii-debug-grid td.yii-debug-cell-nowrap,.yii-debug .yii-debug-grid td.yii-debug-cell-numeric,.yii-debug .yii-debug-grid td.yii-debug-cell-pill,.yii-debug .yii-debug-table td.yii-debug-cell-nowrap,.yii-debug .yii-debug-table td.yii-debug-cell-numeric,.yii-debug .yii-debug-table td.yii-debug-cell-pill{white-space:nowrap}.yii-debug .yii-debug-grid td.yii-debug-cell-fqcn{overflow-wrap:normal}.yii-debug .yii-debug-disclosure{margin-bottom:var(--yii-debug-space-5);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);overflow:hidden}.yii-debug .yii-debug-disclosure[open]>.yii-debug-disclosure-summary{border-bottom:var(--yii-debug-border)}.yii-debug .yii-debug-disclosure-summary{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-3);padding:10px var(--yii-debug-space-4);cursor:pointer;list-style:none;transition:background-color .12s;display:flex}.yii-debug .yii-debug-disclosure-summary::-webkit-details-marker{display:none}.yii-debug .yii-debug-disclosure-summary:hover,.yii-debug .yii-debug-disclosure-summary:focus-visible{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-disclosure-title,.yii-debug .yii-debug-request-section-title{color:var(--yii-debug-panel-primary-strong);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.12em;text-transform:uppercase;font-weight:700}.yii-debug .yii-debug-disclosure-hint{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;transition:color .12s}.yii-debug .yii-debug-disclosure-hint [data-yii-debug-hint=expanded]{display:none}.yii-debug .yii-debug-disclosure-summary:hover .yii-debug-disclosure-hint,.yii-debug .yii-debug-disclosure-summary:focus-visible .yii-debug-disclosure-hint{color:var(--yii-debug-panel-link)}.yii-debug details[open]>summary .yii-debug-disclosure-hint [data-yii-debug-hint=collapsed]{display:none}.yii-debug details[open]>summary .yii-debug-disclosure-hint [data-yii-debug-hint=expanded]{display:inline}.yii-debug .yii-debug-disclosure-body{padding:var(--yii-debug-space-4)}.yii-debug .yii-debug-disclosure-body>pre{overflow-wrap:anywhere;color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);white-space:pre-wrap;margin:0;line-height:1.6}.yii-debug .yii-debug-cell-more-body{max-height:156px;position:relative;overflow:hidden}.yii-debug .yii-debug-cell-more-body:has(.yii-debug-table){max-height:420px}.yii-debug .yii-debug-cell-more-body:after{content:"";background:linear-gradient(to bottom, transparent, var(--yii-debug-panel-surface));pointer-events:none;height:32px;position:absolute;inset:auto 0 0}.yii-debug .yii-debug-cell-more.is-open .yii-debug-cell-more-body{max-height:none}.yii-debug .yii-debug-cell-more.is-open .yii-debug-cell-more-body:after{content:none}.yii-debug .yii-debug-cell-more-toggle{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);color:var(--yii-debug-panel-link);font-family:inherit;font-size:var(--yii-debug-fs-xs);cursor:pointer;background:0 0;align-items:center;margin-top:6px;padding:2px 10px;font-weight:600;display:inline-flex}.yii-debug .yii-debug-cell-more-toggle:before{content:"";width:1.25em;height:1.25em;margin-right:var(--yii-debug-space-1);background:linear-gradient(currentcolor, currentcolor) center / 8px 2px no-repeat, linear-gradient(currentcolor, currentcolor) center / 2px 8px no-repeat, var(--yii-debug-panel-bg-accent);border-radius:50%;display:inline-block}.yii-debug .yii-debug-cell-more.is-open .yii-debug-cell-more-toggle:before{background:linear-gradient(currentcolor, currentcolor) center / 8px 2px no-repeat, var(--yii-debug-panel-bg-accent)}.yii-debug .yii-debug-cell-more-toggle:hover,.yii-debug .yii-debug-cell-more-toggle:focus{background:var(--yii-debug-panel-bg-accent);border-color:var(--yii-debug-panel-primary);text-decoration:none}.yii-debug .yii-debug-grid td.yii-debug-cell-mono{font-family:var(--yii-debug-font-mono)}.yii-debug td.yii-debug-cell-payload{overflow-wrap:anywhere;width:100%}.yii-debug .yii-debug-grid td.yii-debug-cell-numeric{font-variant-numeric:tabular-nums;text-align:right}.yii-debug a.asc:after,.yii-debug a.desc:after,.yii-debug .yii-debug-sort-asc:after,.yii-debug .yii-debug-sort-desc:after{content:"↑";opacity:.78;margin-left:4px;font-size:.85em}.yii-debug a.desc:after,.yii-debug .yii-debug-sort-desc:after{content:"↓"}.yii-debug .yii-debug-pager{gap:var(--yii-debug-space-1);margin:var(--yii-debug-space-3) 0 0;flex-wrap:wrap;padding:0;list-style:none;display:flex}.yii-debug .yii-debug-pager-item{display:inline-flex}.yii-debug .yii-debug-pager-link,.yii-debug .yii-debug-pager>li>:is(a,span){border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);min-width:40px;min-height:40px;color:var(--yii-debug-panel-link);font-size:var(--yii-debug-fs-sm);justify-content:center;align-items:center;padding:0 10px;font-weight:600;text-decoration:none;transition:border-color .12s,background .12s,color .12s;display:inline-flex}:is(.yii-debug .yii-debug-pager-link,.yii-debug .yii-debug-pager>li>:is(a,span)):hover,:is(.yii-debug .yii-debug-pager-link,.yii-debug .yii-debug-pager>li>:is(a,span)):focus{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 30%);background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-link-hover);text-decoration:none}.yii-debug .yii-debug-pager-item.is-active .yii-debug-pager-link{background:var(--yii-debug-primary-bg);color:var(--yii-debug-on-primary);cursor:default;border-color:#0000}.yii-debug .yii-debug-pager-item.is-disabled .yii-debug-pager-link{background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);cursor:not-allowed;pointer-events:none}.yii-debug .yii-debug-field{margin-bottom:var(--yii-debug-space-3);flex-direction:column;gap:4px;display:flex}.yii-debug .yii-debug-field-grid{gap:var(--yii-debug-space-3) var(--yii-debug-space-4);margin-bottom:var(--yii-debug-space-3);grid-template-columns:repeat(auto-fit,minmax(240px,1fr));display:grid}.yii-debug .yii-debug-label{color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);font-weight:600}.yii-debug .yii-debug-input,.yii-debug .yii-debug-select,.yii-debug .yii-debug-textarea{border:1px solid var(--yii-debug-panel-border-strong);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);width:100%;min-height:32px;color:var(--yii-debug-panel-text);font-family:inherit;font-size:var(--yii-debug-fs-sm);padding:6px 10px;transition:border-color .12s,box-shadow .12s}:is(.yii-debug .yii-debug-input,.yii-debug .yii-debug-select,.yii-debug .yii-debug-textarea):hover{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), var(--yii-debug-panel-border-strong) 60%)}:is(.yii-debug .yii-debug-input,.yii-debug .yii-debug-select,.yii-debug .yii-debug-textarea):focus{border-color:var(--yii-debug-panel-primary);box-shadow:0 0 0 3px color-mix(in srgb, var(--yii-debug-panel-primary), transparent 82%)}:is(.yii-debug .yii-debug-input,.yii-debug .yii-debug-select,.yii-debug .yii-debug-textarea)::placeholder{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-textarea{resize:vertical;min-height:96px;line-height:1.45}.yii-debug .yii-debug-grid .filters td{border-bottom:var(--yii-debug-border);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 30%);padding:6px 12px}.yii-debug .yii-debug-grid .filters input,.yii-debug .yii-debug-grid .filters select,.yii-debug .yii-debug-grid .filters textarea{border:1px solid var(--yii-debug-panel-border-strong);border-radius:var(--yii-debug-radius-sm);background:var(--yii-debug-panel-surface);width:100%;min-width:0;min-height:28px;color:var(--yii-debug-panel-text);font:inherit;font-size:var(--yii-debug-fs-xs);padding:4px 8px}:is(.yii-debug .yii-debug-grid .filters input,.yii-debug .yii-debug-grid .filters select,.yii-debug .yii-debug-grid .filters textarea):focus{border-color:var(--yii-debug-panel-primary);box-shadow:0 0 0 2px color-mix(in srgb, var(--yii-debug-panel-primary), transparent 84%)}.yii-debug .yii-debug-tabs{gap:var(--yii-debug-space-1);margin:0 0 var(--yii-debug-space-3);border-bottom:var(--yii-debug-border);flex-wrap:wrap;padding:0;list-style:none;display:flex}.yii-debug .yii-debug-tab{display:inline-flex}.yii-debug .yii-debug-tab-link{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-sm);border-bottom:2px solid #0000;align-items:center;padding:8px 14px;font-weight:600;text-decoration:none;transition:color .12s,border-color .12s;display:inline-flex}.yii-debug .yii-debug-tab-link:hover,.yii-debug .yii-debug-tab-link:focus{color:var(--yii-debug-panel-text);text-decoration:none}.yii-debug .yii-debug-tab.is-active .yii-debug-tab-link,.yii-debug .yii-debug-tab-link.is-active{border-bottom-color:var(--yii-debug-panel-primary);color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-tab-panel{padding:var(--yii-debug-space-3) 0 0;display:none}.yii-debug .yii-debug-tab-panel.is-active{display:block}.yii-debug .yii-debug-collapsible{margin-top:var(--yii-debug-space-3);display:none}.yii-debug .yii-debug-collapsible.is-open{display:block}.yii-debug .yii-debug-dropdown{display:inline-block;position:relative}.yii-debug .yii-debug-dropdown-menu{z-index:100;border:var(--yii-debug-border-strong);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);min-width:220px;box-shadow:var(--yii-debug-shadow-md);margin:0;padding:4px;list-style:none;display:none;position:absolute;top:calc(100% + 4px);left:0}.yii-debug .yii-debug-dropdown.is-open .yii-debug-dropdown-menu{display:block}.yii-debug .yii-debug-dropdown-item{border-radius:var(--yii-debug-radius-sm);color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);padding:6px 10px;text-decoration:none;display:block}.yii-debug .yii-debug-dropdown-item:hover,.yii-debug .yii-debug-dropdown-item:focus{background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-link-hover);text-decoration:none}.yii-debug .yii-debug-since-previous{white-space:nowrap;align-items:center;gap:4px;display:inline-flex}.yii-debug .yii-debug-since-previous .yii-debug-since-previous-btn{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-sm);background:var(--yii-debug-panel-surface);min-width:24px;height:24px;color:var(--yii-debug-panel-link);font-size:var(--yii-debug-fs-xs);justify-content:center;align-items:center;padding:0 6px;font-weight:700;text-decoration:none;display:inline-flex}.yii-debug .yii-debug-table-mono{min-width:0}.yii-debug .yii-debug-table-mono thead th{z-index:1;background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 10%);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xs);letter-spacing:.12em;padding:8px 16px;position:sticky;top:0}.yii-debug .yii-debug-table-mono tbody tr{transition:background-color .14s}.yii-debug .yii-debug-table-mono tbody tr:hover td,.yii-debug .yii-debug-table-mono tbody tr:hover th{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-table-mono tbody tr:hover th{box-shadow:inset 3px 0 0 var(--yii-debug-panel-primary);color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-table-mono tbody th,.yii-debug .yii-debug-table-mono tbody td{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);padding:12px 16px;line-height:1.55}.yii-debug .yii-debug-table-mono tbody th{width:1%;min-width:120px;max-width:220px;color:var(--yii-debug-panel-muted);letter-spacing:.02em;white-space:nowrap;text-overflow:ellipsis;font-weight:500;transition:color .14s,box-shadow .14s;overflow:hidden}.yii-debug .yii-debug-table-mono tbody td{color:var(--yii-debug-panel-text);word-break:normal;overflow-wrap:anywhere}.yii-debug .yii-debug-table-mono tbody tr+tr td,.yii-debug .yii-debug-table-mono tbody tr+tr th{border-top:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 55%, transparent)}.yii-debug .yii-debug-table-mono tbody tr:nth-child(2n) td,.yii-debug .yii-debug-table-mono tbody tr:nth-child(2n) th{background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 70%)}.yii-debug .yii-debug-table-vite-overview tbody th,.yii-debug .yii-debug-table-overview tbody th{width:200px;min-width:160px;max-width:200px}.yii-debug .yii-debug-vite-component+.yii-debug-vite-component,.yii-debug .yii-debug-panel-group+.yii-debug-panel-group{margin-top:var(--yii-debug-space-6)}.yii-debug .yii-debug-table-wrap:has(>.yii-debug-table-mono){border-radius:var(--yii-debug-radius-md);box-shadow:var(--yii-debug-shadow-sm), inset 0 0 0 1px color-mix(in srgb, var(--yii-debug-panel-border) 60%, transparent);overflow:clip}.yii-debug .yii-debug-table-mono{min-width:0}.yii-debug .yii-debug-table-mono tbody th{overflow-wrap:anywhere;white-space:normal}.yii-debug .yii-debug-table-userswitch{width:auto;min-width:100%}.yii-debug .yii-debug-table-userswitch thead th,.yii-debug .yii-debug-table-userswitch tbody td,.yii-debug .yii-debug-table-userswitch tbody th{overflow-wrap:normal;white-space:nowrap}.yii-debug .yii-debug-table-userswitch tbody tr.filters td input{width:100%;min-width:0}.yii-debug .yii-debug-col-userswitch-id{font-variant-numeric:tabular-nums;min-width:10ch}.yii-debug .yii-debug-col-userswitch-username{min-width:14ch}.yii-debug .yii-debug-col-userswitch-email{min-width:24ch}.yii-debug .yii-debug-col-userswitch-status{text-align:center;min-width:8ch}.yii-debug .yii-debug-col-userswitch-timestamp{font-variant-numeric:tabular-nums;min-width:18ch}.yii-debug .yii-debug-table-pointer tbody tr{cursor:pointer;transition:background-color .12s}.yii-debug .yii-debug-table-pointer tbody tr:hover{background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 86%)}.yii-debug .yii-debug-timeline-duration{margin:0 0 var(--yii-debug-space-3);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);letter-spacing:.04em}.yii-debug .yii-debug-user{gap:var(--yii-debug-space-3);flex-direction:column;display:flex}.yii-debug .yii-debug-user-card{gap:var(--yii-debug-space-4);padding:var(--yii-debug-space-4) var(--yii-debug-space-5);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:radial-gradient(circle at top right, color-mix(in srgb, var(--yii-debug-panel-primary), transparent 80%), transparent 60%), var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);grid-template-columns:auto 1fr;align-items:center;display:grid;position:relative;overflow:hidden}.yii-debug .yii-debug-user-avatar{background:linear-gradient(135deg, var(--yii-debug-panel-primary), var(--yii-debug-info));width:64px;height:64px;box-shadow:0 4px 12px color-mix(in srgb, var(--yii-debug-panel-primary), transparent 50%), inset 0 1px 0 #ffffff38;color:#fff;font-family:var(--yii-debug-font-mono);letter-spacing:-.02em;border-radius:50%;flex:none;justify-content:center;align-items:center;font-size:28px;font-weight:700;display:inline-flex}.yii-debug .yii-debug-user-meta{flex-direction:column;gap:4px;min-width:0;display:flex}.yii-debug .yii-debug-user-name{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xl);letter-spacing:-.01em;margin:0;font-weight:700;line-height:1.2}.yii-debug .yii-debug-user-handle{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);margin:0}.yii-debug .yii-debug-user-tags{flex-wrap:wrap;gap:8px;margin-top:6px;display:flex}.yii-debug .yii-debug-user-status{border-radius:var(--yii-debug-radius-pill);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;align-items:center;gap:6px;padding:4px 12px 4px 8px;font-weight:700;display:inline-flex}.yii-debug .yii-debug-user-status:before{content:"";background:currentColor;border-radius:50%;width:7px;height:7px}.yii-debug .yii-debug-user-status-success{background:color-mix(in srgb, var(--yii-debug-success), transparent 84%);color:var(--yii-debug-success)}.yii-debug .yii-debug-user-status-success:before{box-shadow:0 0 0 0 color-mix(in srgb, var(--yii-debug-success), transparent 30%);animation:2.4s ease-in-out infinite yii-debug-user-pulse}.yii-debug .yii-debug-user-status-danger{background:color-mix(in srgb, var(--yii-debug-danger), transparent 84%);color:var(--yii-debug-danger)}.yii-debug .yii-debug-user-status-warning{background:color-mix(in srgb, var(--yii-debug-warning), transparent 84%);color:var(--yii-debug-warning)}.yii-debug .yii-debug-user-status-muted{background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-user-pill{border:1px solid var(--yii-debug-panel-border);border-radius:var(--yii-debug-radius-pill);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;align-items:center;padding:4px 10px;display:inline-flex}.yii-debug .yii-debug-user-section{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);overflow:hidden}.yii-debug .yii-debug-user-section>header{padding:10px var(--yii-debug-space-3);border-bottom:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;align-items:center;gap:10px;font-weight:700;display:flex}.yii-debug .yii-debug-user-section dl{margin:0;padding:0}.yii-debug .yii-debug-user-section-icon{width:18px;height:18px;color:var(--yii-debug-panel-primary);justify-content:center;align-items:center;display:inline-flex}.yii-debug .yii-debug-user-section-icon svg{width:100%;height:100%}.yii-debug .yii-debug-user-row{gap:var(--yii-debug-space-3);padding:12px var(--yii-debug-space-3);border-bottom:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 50%, transparent);grid-template-columns:minmax(140px,22%) 1fr;display:grid}.yii-debug .yii-debug-user-row:last-child{border-bottom:0}.yii-debug .yii-debug-user-row dt{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.02em;align-self:center}.yii-debug .yii-debug-user-row dd{min-width:0;margin:0}.yii-debug .yii-debug-user-value{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);word-break:break-all}.yii-debug .yii-debug-user-empty{color:color-mix(in srgb, var(--yii-debug-panel-muted), transparent 35%);font-family:var(--yii-debug-font-mono)}.yii-debug .yii-debug-user-time{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);align-items:baseline;gap:10px;display:inline-flex}.yii-debug .yii-debug-user-time .yii-debug-user-time-rel{color:var(--yii-debug-panel-text);font-weight:600}.yii-debug .yii-debug-user-time .yii-debug-user-time-abs{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs)}.yii-debug .yii-debug-user-reveal{border:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 80%, transparent);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-text);font:inherit;cursor:pointer;text-align:left;align-items:center;gap:12px;padding:6px 6px 6px 14px;transition:border-color .14s,background .14s;display:inline-flex}.yii-debug .yii-debug-user-reveal:hover,.yii-debug .yii-debug-user-reveal:focus{border-color:var(--yii-debug-panel-primary)}.yii-debug .yii-debug-user-reveal .yii-debug-user-mask,.yii-debug .yii-debug-user-reveal .yii-debug-user-real{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm)}.yii-debug .yii-debug-user-reveal .yii-debug-user-mask{letter-spacing:.1em;color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-user-reveal .yii-debug-user-real{overflow-wrap:anywhere;max-width:60ch;color:var(--yii-debug-panel-text);display:none}.yii-debug .yii-debug-user-reveal .yii-debug-user-reveal-cta{border-radius:var(--yii-debug-radius-pill);background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 80%);color:var(--yii-debug-panel-primary);font-size:var(--yii-debug-fs-xs);letter-spacing:.08em;text-transform:uppercase;flex:none;padding:3px 10px;font-weight:700}.yii-debug .yii-debug-user-reveal .yii-debug-user-reveal-cta:before{content:"Reveal"}.yii-debug .yii-debug-user-reveal.is-revealed .yii-debug-user-mask{display:none}.yii-debug .yii-debug-user-reveal.is-revealed .yii-debug-user-real{display:inline}.yii-debug .yii-debug-user-reveal.is-revealed .yii-debug-user-reveal-cta{background:color-mix(in srgb, var(--yii-debug-warning), transparent 84%);color:var(--yii-debug-warning)}.yii-debug .yii-debug-user-reveal.is-revealed .yii-debug-user-reveal-cta:before{content:"Hide"}.yii-debug .yii-debug-sr-only{clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.yii-debug .yii-debug-table-empty{padding:var(--yii-debug-space-4);border:1px dashed color-mix(in srgb, var(--yii-debug-panel-border) 70%, transparent);border-radius:var(--yii-debug-radius-md);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 40%);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;text-transform:uppercase;justify-content:center;align-items:center;gap:10px;margin:0;display:flex}.yii-debug .yii-debug-table-empty:before{content:"";opacity:.5;border:1.5px solid;border-radius:50%;width:8px;height:8px}.yii-debug .yii-debug-grid-summary{align-items:center;gap:var(--yii-debug-space-3);margin-bottom:var(--yii-debug-space-3);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);letter-spacing:.06em;text-transform:uppercase;flex-wrap:wrap;display:flex}.yii-debug .yii-debug-grid-summary strong{color:var(--yii-debug-panel-primary-strong);letter-spacing:0;text-transform:none;margin-right:.35em;font-weight:700}.yii-debug .yii-debug-grid-summary-sep{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-grid-summary-stat-warn{--yii-debug-hue:var(--yii-debug-level-warning)}.yii-debug .yii-debug-grid-summary-stat-danger{--yii-debug-hue:var(--yii-debug-level-error)}.yii-debug .yii-debug-grid-summary-stat-info{--yii-debug-hue:var(--yii-debug-level-info)}.yii-debug .yii-debug-grid-summary-stat-trace{--yii-debug-hue:var(--yii-debug-level-trace)}.yii-debug .yii-debug-grid-summary-stat-warn strong{color:var(--yii-debug-warning)}.yii-debug .yii-debug-grid-summary-stat-danger strong{color:var(--yii-debug-danger)}.yii-debug .yii-debug-grid-summary-stat-2xx{--yii-debug-hue:var(--yii-debug-status-2xx)}.yii-debug .yii-debug-grid-summary-stat-3xx{--yii-debug-hue:var(--yii-debug-status-3xx)}.yii-debug .yii-debug-grid-summary-stat-4xx{--yii-debug-hue:var(--yii-debug-status-4xx)}.yii-debug .yii-debug-grid-summary-stat-5xx{--yii-debug-hue:var(--yii-debug-status-5xx)}.yii-debug a:is(.yii-debug-grid-summary-stat-danger,.yii-debug-grid-summary-stat-warn,.yii-debug-grid-summary-stat-info,.yii-debug-grid-summary-stat-trace,.yii-debug-grid-summary-stat-2xx,.yii-debug-grid-summary-stat-3xx,.yii-debug-grid-summary-stat-4xx,.yii-debug-grid-summary-stat-5xx){border:1px solid color-mix(in srgb, var(--yii-debug-hue), transparent 65%);border-radius:var(--yii-debug-radius-pill);background:color-mix(in srgb, var(--yii-debug-hue), transparent 90%);color:color-mix(in srgb, var(--yii-debug-hue) 80%, var(--yii-debug-panel-text));align-items:center;gap:6px;padding:2px 10px;text-decoration:none;transition:transform .14s,background-color .14s;display:inline-flex}.yii-debug a:is(.yii-debug-grid-summary-stat-danger,.yii-debug-grid-summary-stat-warn,.yii-debug-grid-summary-stat-info,.yii-debug-grid-summary-stat-trace,.yii-debug-grid-summary-stat-2xx,.yii-debug-grid-summary-stat-3xx,.yii-debug-grid-summary-stat-4xx,.yii-debug-grid-summary-stat-5xx) strong{color:inherit;margin-right:0}.yii-debug a:is(.yii-debug-grid-summary-stat-danger,.yii-debug-grid-summary-stat-warn,.yii-debug-grid-summary-stat-info,.yii-debug-grid-summary-stat-trace,.yii-debug-grid-summary-stat-2xx,.yii-debug-grid-summary-stat-3xx,.yii-debug-grid-summary-stat-4xx,.yii-debug-grid-summary-stat-5xx):hover,.yii-debug a:is(.yii-debug-grid-summary-stat-danger,.yii-debug-grid-summary-stat-warn,.yii-debug-grid-summary-stat-info,.yii-debug-grid-summary-stat-trace,.yii-debug-grid-summary-stat-2xx,.yii-debug-grid-summary-stat-3xx,.yii-debug-grid-summary-stat-4xx,.yii-debug-grid-summary-stat-5xx):focus-visible{background:color-mix(in srgb, var(--yii-debug-hue), transparent 80%);text-decoration:none;transform:translateY(-1px)}.yii-debug .yii-debug-grid-summary-action{color:var(--yii-debug-panel-primary-strong);letter-spacing:0;text-transform:none;margin-left:auto;font-weight:700;text-decoration:none}.yii-debug .yii-debug-grid-summary-action:hover,.yii-debug .yii-debug-grid-summary-action:focus-visible{color:var(--yii-debug-panel-link-hover);text-decoration:underline}.yii-debug .yii-debug-grid-footer{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-3);margin-top:var(--yii-debug-space-2);color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-grid-count{margin:0}.yii-debug .yii-debug-grid-pagesize{align-items:center;gap:6px;margin-left:auto;display:inline-flex}.yii-debug .yii-debug-grid-pagesize-label{color:var(--yii-debug-panel-muted);letter-spacing:.06em;text-transform:uppercase;font-weight:600}.yii-debug .yii-debug-grid-pagesize-select{padding:3px var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-sm);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-sm);letter-spacing:0;text-transform:none;cursor:pointer}.yii-debug .yii-debug-grid-pagesize-select:focus{border-color:var(--yii-debug-panel-primary)}.yii-debug .yii-debug-db-type{border-radius:var(--yii-debug-radius-pill);min-width:5em;font-family:var(--yii-debug-font-mono);letter-spacing:.04em;text-align:center;text-transform:uppercase;white-space:nowrap;padding:2px 8px;font-size:.7rem;font-weight:700;display:inline-block}.yii-debug .yii-debug-db-sql{font-family:var(--yii-debug-font-mono);overflow-wrap:anywhere}.yii-debug .yii-debug-grid-db .yii-debug-table th:not(:last-child),.yii-debug .yii-debug-grid-db .yii-debug-table td:not(:last-child){white-space:nowrap}.yii-debug .yii-debug-grid-db .yii-debug-table th:last-child,.yii-debug .yii-debug-grid-db .yii-debug-table td:last-child{width:100%}.yii-debug .yii-debug-grid-db .filters select{min-width:max-content}.yii-debug .yii-debug-grid-db .filters select:focus{box-shadow:none}.yii-debug .yii-debug-grid-db .filters select:focus-visible{outline-offset:1px;outline-width:2px}.yii-debug .yii-debug-sql-kw{color:var(--yii-debug-sql-kw);font-weight:700}.yii-debug .yii-debug-sql-str{color:var(--yii-debug-sql-str)}.yii-debug .yii-debug-sql-num{color:var(--yii-debug-sql-num)}.yii-debug .yii-debug-sql-comment{color:var(--yii-debug-sql-comment);font-style:italic}.yii-debug .yii-debug-sql-param{color:var(--yii-debug-sql-param);font-weight:600}.yii-debug .yii-debug-db-explain,.yii-debug .yii-debug-db-trace{margin-top:var(--yii-debug-space-2)}.yii-debug .yii-debug-db-explain-toggle,.yii-debug .yii-debug-db-trace-toggle{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);color:var(--yii-debug-panel-link);font-size:var(--yii-debug-fs-xs);cursor:pointer;background:0 0;align-items:center;gap:6px;padding:2px 10px 2px 6px;font-weight:600;text-decoration:none;transition:background-color .12s,border-color .12s;display:inline-flex}.yii-debug .yii-debug-db-explain-toggle:hover,.yii-debug .yii-debug-db-explain-toggle:focus,.yii-debug .yii-debug-db-trace-toggle:hover,.yii-debug .yii-debug-db-trace-toggle:focus-visible{background:var(--yii-debug-panel-bg-accent);border-color:var(--yii-debug-panel-primary);text-decoration:none}.yii-debug .yii-debug-db-trace-toggle{list-style:none}.yii-debug .yii-debug-db-trace-toggle::-webkit-details-marker{display:none}.yii-debug .yii-debug-db-explain-chevron,.yii-debug .yii-debug-db-trace-chevron{width:10px;font-size:14px;line-height:1;transition:transform .16s;display:inline-block}.yii-debug .yii-debug-db-explain.is-open .yii-debug-db-explain-chevron,.yii-debug .yii-debug-db-trace[open]>.yii-debug-db-trace-toggle .yii-debug-db-trace-chevron{transform:rotate(90deg)}.yii-debug .yii-debug-db-explain.is-loading .yii-debug-db-explain-toggle{opacity:.55;pointer-events:none}.yii-debug .yii-debug-db-explain-text{margin-top:var(--yii-debug-space-2);display:none}.yii-debug .yii-debug-db-explain.is-open .yii-debug-db-explain-text{display:block}.yii-debug .yii-debug-db-explain-text.is-error{padding:var(--yii-debug-space-2) var(--yii-debug-space-3);border-left:3px solid var(--yii-debug-danger);border-radius:var(--yii-debug-radius-sm);background:color-mix(in srgb, var(--yii-debug-danger), transparent 92%);color:var(--yii-debug-danger);font-size:var(--yii-debug-fs-sm);font-weight:600;display:block}.yii-debug .yii-debug-db-explain-all{margin-top:var(--yii-debug-space-3);justify-content:flex-end;display:flex}.yii-debug .yii-debug-db-explain-all-toggle{white-space:nowrap;justify-content:center;min-width:104px}.yii-debug .yii-debug-explain{margin-top:var(--yii-debug-space-3)}.yii-debug .yii-debug-explain-title{margin-bottom:var(--yii-debug-space-2);font-size:var(--yii-debug-fs-lg);font-weight:700}.yii-debug .yii-debug-explain-query{margin-bottom:var(--yii-debug-space-3);padding:var(--yii-debug-space-3);background:var(--yii-debug-panel-code-bg);border-radius:var(--yii-debug-radius-md);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);white-space:pre-wrap;overflow-wrap:break-word}.yii-debug .yii-debug-explain-empty{color:var(--yii-debug-panel-muted);font-style:italic}.yii-debug .yii-debug-empty-state{margin:var(--yii-debug-space-4) 0 0;padding:var(--yii-debug-space-5) var(--yii-debug-space-5);border:1px dashed color-mix(in srgb, var(--yii-debug-panel-border) 80%, transparent);border-radius:var(--yii-debug-radius-md);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 30%)}.yii-debug .yii-debug-empty-state h2{margin:0 0 var(--yii-debug-space-3);color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-lg);font-weight:700}.yii-debug .yii-debug-empty-state p{margin:0 0 var(--yii-debug-space-3);color:var(--yii-debug-panel-muted);line-height:1.5}.yii-debug .yii-debug-empty-state p:last-child{margin-bottom:0}.yii-debug .yii-debug-empty-state code{border-radius:var(--yii-debug-radius-sm,4px);background:var(--yii-debug-panel-code-bg);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);padding:.1em .4em}.yii-debug .yii-debug-empty-state-code{margin:0 0 var(--yii-debug-space-3);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-code-bg);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);white-space:pre-wrap;overflow-wrap:break-word;line-height:1.5}.yii-debug .yii-debug-mail-list{gap:var(--yii-debug-space-3);flex-direction:column;margin:0;padding:0;list-style:none;display:flex}.yii-debug .yii-debug-mail-list-item{margin:0;padding:0;list-style:none;display:block}.yii-debug .yii-debug-mail-pager{margin-top:var(--yii-debug-space-4)}.yii-debug .yii-debug-mail-card{gap:var(--yii-debug-space-4);padding:var(--yii-debug-space-4) var(--yii-debug-space-4) var(--yii-debug-space-3);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);grid-template-columns:1fr;transition:transform .16s,border-color .16s,box-shadow .16s;display:grid;position:relative}.yii-debug .yii-debug-mail-card:hover{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 60%);box-shadow:var(--yii-debug-shadow-sm), 0 12px 32px color-mix(in srgb, var(--yii-debug-panel-primary), transparent 88%);transform:translateY(-1px)}.yii-debug .yii-debug-mail-card-head{gap:var(--yii-debug-space-3);grid-template-columns:auto 1fr auto;align-items:flex-start;display:grid}.yii-debug .yii-debug-mail-avatar{--mail-hue:210;background:linear-gradient(135deg, hsl(var(--mail-hue) 72% 52%), hsl(calc(var(--mail-hue) + 28) 65% 40%));width:44px;height:44px;box-shadow:0 2px 10px hsl(var(--mail-hue) 60% 40% / 32%), inset 0 1px 0 #ffffff38;color:#fff;font-family:var(--yii-debug-font-mono);letter-spacing:-.01em;border-radius:50%;flex:none;justify-content:center;align-items:center;font-size:16px;font-weight:800;display:inline-flex}.yii-debug .yii-debug-mail-headline{flex-direction:column;gap:4px;min-width:0;display:flex}.yii-debug .yii-debug-mail-from{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.02em;text-overflow:ellipsis;white-space:nowrap;margin:0;overflow:hidden}.yii-debug .yii-debug-mail-subject{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-lg);letter-spacing:-.01em;margin:0;font-weight:700;line-height:1.25}.yii-debug .yii-debug-mail-preview{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-sm);-webkit-line-clamp:1;line-clamp:1;-webkit-box-orient:vertical;margin:2px 0 0;line-height:1.4;display:-webkit-box;overflow:hidden}.yii-debug .yii-debug-mail-meta{flex-direction:column;flex:none;align-items:flex-end;gap:6px;display:flex}.yii-debug .yii-debug-mail-status{border-radius:var(--yii-debug-radius-pill);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;align-items:center;gap:6px;padding:3px 10px 3px 8px;font-weight:600;line-height:1;display:inline-flex}.yii-debug .yii-debug-mail-status-dot{background:var(--yii-debug-hue,var(--yii-debug-panel-muted));width:7px;height:7px;box-shadow:0 0 0 3px color-mix(in srgb, var(--yii-debug-hue,var(--yii-debug-panel-muted)), transparent 80%);border-radius:50%;flex:none}.yii-debug .yii-debug-mail-status-ok{--yii-debug-hue:var(--yii-debug-success)}.yii-debug .yii-debug-mail-status-fail{--yii-debug-hue:var(--yii-debug-danger)}.yii-debug .yii-debug-mail-time{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.02em;cursor:help}.yii-debug .yii-debug-mail-download{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface-muted);width:30px;height:30px;color:var(--yii-debug-panel-link);justify-content:center;align-items:center;text-decoration:none;transition:background-color .14s,border-color .14s,color .14s,transform .14s;display:inline-flex}.yii-debug .yii-debug-mail-download svg{width:16px;height:16px}.yii-debug .yii-debug-mail-download:hover,.yii-debug .yii-debug-mail-download:focus-visible{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 50%);background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 88%);color:var(--yii-debug-panel-primary-strong);text-decoration:none;transform:translateY(-1px)}.yii-debug .yii-debug-mail-recipients{padding:var(--yii-debug-space-3);border:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 60%, transparent);border-radius:var(--yii-debug-radius-md);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 40%);flex-direction:column;gap:8px;display:flex}.yii-debug .yii-debug-mail-recipient-group{gap:var(--yii-debug-space-3);grid-template-columns:64px 1fr;align-items:center;display:grid}.yii-debug .yii-debug-mail-recipient-label{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.14em;text-align:right;font-weight:700}.yii-debug .yii-debug-mail-recipient-label[data-role=reply]{color:var(--yii-debug-info)}.yii-debug .yii-debug-mail-recipient-label[data-role=bcc]{color:var(--yii-debug-warning)}.yii-debug .yii-debug-mail-recipient-pills{flex-wrap:wrap;gap:6px;min-width:0;display:flex}.yii-debug .yii-debug-mail-recipient-pill{border:1px solid var(--yii-debug-panel-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);max-width:100%;color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);text-overflow:ellipsis;white-space:nowrap;align-items:center;padding:3px 10px;line-height:1.4;display:inline-flex;overflow:hidden}.yii-debug .yii-debug-mail-body{padding:var(--yii-debug-space-4);border:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 60%, transparent);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);white-space:pre-wrap;overflow-wrap:break-word;line-height:1.55}.yii-debug .yii-debug-mail-body-empty{color:var(--yii-debug-panel-muted);text-align:center;font-style:italic}.yii-debug .yii-debug-mail-tech{border:1px dashed color-mix(in srgb, var(--yii-debug-panel-border) 70%, transparent);border-radius:var(--yii-debug-radius-md);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 30%);overflow:hidden}.yii-debug .yii-debug-mail-tech>summary{padding:10px var(--yii-debug-space-3);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:8px;list-style:none;display:flex}.yii-debug .yii-debug-mail-tech>summary::-webkit-details-marker{display:none}.yii-debug .yii-debug-mail-tech>summary:hover{color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-mail-tech[open]>summary{border-bottom:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 50%, transparent);color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-mail-tech[open] .yii-debug-mail-tech-chevron{transform:rotate(180deg)}.yii-debug .yii-debug-mail-tech-icon{width:14px;height:14px;color:var(--yii-debug-panel-primary);justify-content:center;align-items:center;display:inline-flex}.yii-debug .yii-debug-mail-tech-icon svg{width:100%;height:100%}.yii-debug .yii-debug-mail-tech-label{text-transform:uppercase;font-weight:700}.yii-debug .yii-debug-mail-tech-charset{border:1px solid var(--yii-debug-panel-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;text-transform:none;margin-left:4px;padding:2px 8px;font-weight:600}.yii-debug .yii-debug-mail-tech-chevron{width:14px;height:14px;color:var(--yii-debug-panel-muted);justify-content:center;align-items:center;margin-left:auto;transition:transform .2s;display:inline-flex}.yii-debug .yii-debug-mail-tech-chevron svg{width:100%;height:100%}.yii-debug .yii-debug-mail-headers{padding:var(--yii-debug-space-3) var(--yii-debug-space-4);background:var(--yii-debug-panel-code-bg);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);white-space:pre-wrap;overflow-wrap:break-word;margin:0;line-height:1.5;overflow-x:auto}@media (max-width:640px){.yii-debug .yii-debug-mail-card-head{grid-template-columns:auto 1fr}.yii-debug .yii-debug-mail-meta{flex-direction:row;grid-column:1/-1;justify-content:flex-start;align-items:center}.yii-debug .yii-debug-mail-recipient-group{grid-template-columns:1fr;gap:4px}.yii-debug .yii-debug-mail-recipient-label{text-align:left}}.yii-debug .yii-debug-queue-grid-job{flex-direction:column;gap:2px;line-height:1.2;display:flex}.yii-debug .yii-debug-queue-grid-job-link{color:var(--yii-debug-text-strong);white-space:nowrap;font-weight:600;text-decoration:none}.yii-debug .yii-debug-queue-grid-job-link:hover{text-decoration:underline}.yii-debug .yii-debug-queue-grid-job-namespace{color:var(--yii-debug-text-muted);font-family:var(--yii-debug-font-mono);white-space:nowrap;font-size:.78rem}.yii-debug .yii-debug-grid-queue .yii-debug-queue-status,.yii-debug .yii-debug-grid-queue .yii-debug-queue-driver{white-space:nowrap}.yii-debug .yii-debug-queue-job-page{gap:var(--yii-debug-space-3);flex-direction:column;display:flex}.yii-debug .yii-debug-queue-job-head{align-items:center;gap:var(--yii-debug-space-2);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-queue-card{gap:var(--yii-debug-space-2);padding:var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);flex-direction:column;display:flex}.yii-debug .yii-debug-queue-card-head{gap:var(--yii-debug-space-3);grid-template-columns:auto 1fr auto;align-items:flex-start;display:grid}.yii-debug .yii-debug-queue-avatar{--queue-hue:210;border-radius:var(--yii-debug-radius-md);background:linear-gradient(135deg, hsl(var(--queue-hue) 72% 52%), hsl(calc(var(--queue-hue) + 28) 65% 40%));width:40px;height:40px;box-shadow:0 2px 10px hsl(var(--queue-hue) 60% 40% / 32%), inset 0 1px 0 #ffffff38;color:#fff;font-family:var(--yii-debug-font-mono);letter-spacing:-.01em;flex:none;justify-content:center;align-items:center;font-size:16px;font-weight:800;display:inline-flex}.yii-debug .yii-debug-queue-headline{flex-direction:column;gap:2px;min-width:0;display:flex}.yii-debug .yii-debug-queue-class{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-lg);letter-spacing:-.01em;word-break:break-all;margin:0;font-weight:700;line-height:1.25}.yii-debug .yii-debug-queue-namespace{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.02em}.yii-debug .yii-debug-queue-meta-pills{flex-direction:column;flex:none;align-items:flex-end;gap:6px;display:flex}.yii-debug .yii-debug-queue-status{border-radius:var(--yii-debug-radius-pill);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;align-items:center;padding:3px 10px;font-weight:600;line-height:1;display:inline-flex}.yii-debug .yii-debug-queue-status-queued{color:var(--yii-debug-info);background:color-mix(in srgb, var(--yii-debug-info), transparent 86%)}.yii-debug .yii-debug-queue-status-done{color:var(--yii-debug-success);background:color-mix(in srgb, var(--yii-debug-success), transparent 86%)}.yii-debug .yii-debug-queue-status-failed{color:var(--yii-debug-danger);background:color-mix(in srgb, var(--yii-debug-danger), transparent 86%)}.yii-debug .yii-debug-queue-driver{border-radius:var(--yii-debug-radius-pill);border:var(--yii-debug-border);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.05em;text-transform:uppercase;cursor:help;align-items:center;padding:3px 10px;font-weight:600;line-height:1;display:inline-flex}.yii-debug .yii-debug-queue-driver-is-sync{color:var(--yii-debug-success);background:color-mix(in srgb, var(--yii-debug-success), transparent 92%);border-color:color-mix(in srgb, var(--yii-debug-success), transparent 70%)}.yii-debug .yii-debug-queue-driver-is-async{color:var(--yii-debug-warning);background:color-mix(in srgb, var(--yii-debug-warning), transparent 92%);border-color:color-mix(in srgb, var(--yii-debug-warning), transparent 70%)}.yii-debug .yii-debug-queue-hint{margin:var(--yii-debug-space-3) 0;padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border-radius:var(--yii-debug-radius-md);border-left:3px solid var(--yii-debug-warning);background:color-mix(in srgb, var(--yii-debug-warning), transparent 94%);color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);line-height:1.5}.yii-debug .yii-debug-queue-tree{padding:var(--yii-debug-space-3);border-radius:var(--yii-debug-radius-sm);background:color-mix(in srgb, var(--yii-debug-panel-text), transparent 96%);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);flex-direction:column;gap:4px;line-height:1.6;display:flex}.yii-debug .yii-debug-queue-tree-row{flex-wrap:wrap;align-items:baseline;gap:8px;display:flex}.yii-debug .yii-debug-queue-tree-key{color:var(--yii-debug-panel-text);font-weight:700}.yii-debug .yii-debug-queue-tree-key:after{content:":";color:var(--yii-debug-panel-muted);margin-left:1px;font-weight:400}.yii-debug .yii-debug-queue-tree-type{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);text-transform:uppercase;letter-spacing:.05em;background:color-mix(in srgb, var(--yii-debug-panel-text), transparent 92%);border-radius:3px;padding:1px 6px}.yii-debug .yii-debug-queue-tree-class{color:var(--yii-debug-info);cursor:help;font-weight:600}.yii-debug .yii-debug-queue-tree-meta{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-queue-tree-truncated{color:var(--yii-debug-warning);font-size:var(--yii-debug-fs-xs);text-transform:uppercase;letter-spacing:.05em;background:color-mix(in srgb, var(--yii-debug-warning), transparent 86%);border-radius:3px;padding:1px 6px}.yii-debug .yii-debug-queue-tree-value{word-break:break-all}.yii-debug .yii-debug-queue-tree-value-string{color:var(--yii-debug-success)}.yii-debug .yii-debug-queue-tree-value-number{color:var(--yii-debug-info)}.yii-debug .yii-debug-queue-tree-value-bool{color:var(--yii-debug-warning);font-weight:600}.yii-debug .yii-debug-queue-tree-value-null{color:var(--yii-debug-panel-muted);font-style:italic}.yii-debug .yii-debug-queue-tree-collapse{flex-direction:column;gap:4px;display:flex}.yii-debug .yii-debug-queue-tree-summary{cursor:pointer;list-style:revert;flex-wrap:wrap;align-items:baseline;gap:8px;padding:2px 0;display:inline-flex}.yii-debug .yii-debug-queue-tree-summary::-webkit-details-marker{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-queue-tree-children{margin-left:var(--yii-debug-space-4);padding-left:var(--yii-debug-space-3);border-left:1px dashed color-mix(in srgb, var(--yii-debug-panel-text), transparent 80%);flex-direction:column;gap:4px;display:flex}.yii-debug .yii-debug-queue-time{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.02em}.yii-debug .yii-debug-queue-payload{margin:0}.yii-debug .yii-debug-queue-error{padding:var(--yii-debug-space-3);border-radius:var(--yii-debug-radius-sm);border-left:3px solid var(--yii-debug-danger);background:color-mix(in srgb, var(--yii-debug-danger), transparent 92%);color:var(--yii-debug-danger);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);line-height:1.5}.yii-debug .yii-debug-queue-meta{gap:var(--yii-debug-space-2);padding-top:var(--yii-debug-space-2);border-top:var(--yii-debug-border);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-queue-meta-item{border-radius:var(--yii-debug-radius-pill);background:color-mix(in srgb, var(--yii-debug-panel-text), transparent 94%);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);align-items:center;gap:6px;padding:2px 10px;line-height:1.4;display:inline-flex}.yii-debug .yii-debug-queue-meta-label{color:var(--yii-debug-panel-muted);text-transform:uppercase;letter-spacing:.05em;font-size:var(--yii-debug-fs-xs)}.yii-debug .yii-debug-queue-meta-value{color:var(--yii-debug-panel-text);font-weight:600}.yii-debug .yii-debug-dump{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);overflow:hidden}.yii-debug .yii-debug-dump-card-head{align-items:center;gap:var(--yii-debug-space-2);padding:6px var(--yii-debug-space-3);border-bottom:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);color:var(--yii-debug-panel-muted);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-dump-index{color:var(--yii-debug-panel-primary-strong);font-weight:700}.yii-debug .yii-debug-dump-type{border-radius:var(--yii-debug-radius-pill);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;text-transform:uppercase;--yii-debug-hue:var(--yii-debug-panel-primary);padding:1px 8px;font-weight:700;display:inline-flex}.yii-debug .yii-debug-dump-type[data-type=array]{--yii-debug-hue:var(--yii-debug-info)}.yii-debug .yii-debug-dump-type[data-type=string]{--yii-debug-hue:var(--yii-debug-success)}.yii-debug .yii-debug-dump-type[data-type=number]{--yii-debug-hue:var(--yii-debug-warning)}.yii-debug .yii-debug-dump-type[data-type=bool],.yii-debug .yii-debug-dump-type[data-type=null]{--yii-debug-hue:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-dump-meta{align-items:center;gap:var(--yii-debug-space-2);margin-left:auto;display:inline-flex}.yii-debug .yii-debug-dump-time{color:var(--yii-debug-panel-muted);font-variant-numeric:tabular-nums}.yii-debug .yii-debug-dump-trace{color:var(--yii-debug-panel-link)}.yii-debug .yii-debug-dump-body{padding:var(--yii-debug-space-3);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);line-height:1.5;overflow-x:auto}.yii-debug .yii-debug-dump-body code{white-space:pre;background:0 0;padding:0;display:block}[data-yii-debug-theme=dark] .yii-debug .yii-debug-dump-body span[style*="color: #DD0000"],[data-yii-debug-theme=dark] .yii-debug .yii-debug-dump-body span[style*="color:#DD0000"]{color:#fca5a5!important}[data-yii-debug-theme=dark] .yii-debug .yii-debug-dump-body span[style*="color: #0000BB"],[data-yii-debug-theme=dark] .yii-debug .yii-debug-dump-body span[style*="color:#0000BB"]{color:#93c5fd!important}[data-yii-debug-theme=dark] .yii-debug .yii-debug-dump-body span[style*="color: #007700"],[data-yii-debug-theme=dark] .yii-debug .yii-debug-dump-body span[style*="color:#007700"]{color:#86efac!important}[data-yii-debug-theme=dark] .yii-debug .yii-debug-dump-body span[style*="color: #FF8000"],[data-yii-debug-theme=dark] .yii-debug .yii-debug-dump-body span[style*="color:#FF8000"]{color:#fdba74!important}[data-yii-debug-theme=dark] .yii-debug .yii-debug-dump-body span[style*="color: #000000"],[data-yii-debug-theme=dark] .yii-debug .yii-debug-dump-body span[style*="color:#000000"]{color:var(--yii-debug-panel-text)!important}.yii-debug .yii-debug-asset-stats{gap:var(--yii-debug-space-3);margin:0 0 var(--yii-debug-space-4);grid-template-columns:repeat(4,minmax(0,1fr));display:grid}.yii-debug .yii-debug-asset-stat{padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);align-items:center;gap:10px;display:flex}.yii-debug .yii-debug-asset-stat[data-kind=bundles] .yii-debug-asset-stat-icon{color:var(--yii-debug-panel-primary)}.yii-debug .yii-debug-asset-stat[data-kind=css] .yii-debug-asset-stat-icon{color:var(--yii-debug-info)}.yii-debug .yii-debug-asset-stat[data-kind=js] .yii-debug-asset-stat-icon{color:var(--yii-debug-warning)}.yii-debug .yii-debug-asset-stat[data-kind=deps] .yii-debug-asset-stat-icon{color:var(--yii-debug-success)}.yii-debug .yii-debug-asset-stat-icon{flex:none;justify-content:center;align-items:center;width:26px;height:26px;display:inline-flex}.yii-debug .yii-debug-asset-stat-icon svg{width:100%;height:100%}.yii-debug .yii-debug-asset-stat-value{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xl);font-weight:800;line-height:1}.yii-debug .yii-debug-asset-stat-label{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;text-transform:uppercase;margin-left:auto}.yii-debug .yii-debug-asset-list{gap:var(--yii-debug-space-3);flex-direction:column;margin:0;padding:0;list-style:none;display:flex}.yii-debug .yii-debug-asset-list-item{margin:0;padding:0;list-style:none;display:block}.yii-debug .yii-debug-asset-card{gap:var(--yii-debug-space-4);padding:var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);scroll-margin-top:var(--yii-debug-space-4);flex-direction:column;transition:transform .16s,border-color .16s,box-shadow .16s;display:flex;position:relative}.yii-debug .yii-debug-asset-card:hover{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 60%);box-shadow:var(--yii-debug-shadow-sm), 0 12px 32px color-mix(in srgb, var(--yii-debug-panel-primary), transparent 88%);transform:translateY(-1px)}.yii-debug .yii-debug-asset-card:target{border-color:var(--yii-debug-panel-primary);box-shadow:0 0 0 3px color-mix(in srgb, var(--yii-debug-panel-primary), transparent 78%), var(--yii-debug-shadow-sm)}.yii-debug .yii-debug-asset-card-head{gap:var(--yii-debug-space-3);grid-template-columns:auto 1fr auto;align-items:center;display:grid}.yii-debug .yii-debug-asset-card-icon{border-radius:var(--yii-debug-radius-md);background:linear-gradient(135deg, color-mix(in srgb, var(--yii-debug-panel-primary), transparent 78%), color-mix(in srgb, var(--yii-debug-info), transparent 78%));width:38px;height:38px;color:var(--yii-debug-panel-primary-strong);flex:none;justify-content:center;align-items:center;display:inline-flex}.yii-debug .yii-debug-asset-card-icon svg{width:22px;height:22px}.yii-debug .yii-debug-asset-card-title{flex-direction:column;gap:2px;min-width:0;display:flex}.yii-debug .yii-debug-asset-card-name{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-lg);letter-spacing:-.01em;text-overflow:ellipsis;white-space:nowrap;margin:0;font-weight:700;line-height:1.2;overflow:hidden}.yii-debug .yii-debug-asset-card-fqcn{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.01em;text-overflow:ellipsis;white-space:nowrap;margin:0;overflow:hidden}.yii-debug .yii-debug-asset-card-meta{flex-wrap:nowrap;flex:none;justify-content:flex-end;align-items:center;gap:5px;display:inline-flex}.yii-debug .yii-debug-asset-chip{border:1px solid var(--yii-debug-panel-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;text-transform:uppercase;white-space:nowrap;align-items:center;gap:4px;padding:2px 8px;font-weight:600;display:inline-flex}.yii-debug .yii-debug-asset-chip strong{color:var(--yii-debug-panel-text);font-weight:800}.yii-debug .yii-debug-asset-chip-css{border-color:color-mix(in srgb, var(--yii-debug-info), transparent 60%);background:color-mix(in srgb, var(--yii-debug-info), transparent 90%);color:var(--yii-debug-info)}.yii-debug .yii-debug-asset-chip-css strong{color:var(--yii-debug-info)}.yii-debug .yii-debug-asset-chip-js{border-color:color-mix(in srgb, var(--yii-debug-warning), transparent 60%);background:color-mix(in srgb, var(--yii-debug-warning), transparent 90%);color:var(--yii-debug-warning)}.yii-debug .yii-debug-asset-chip-js strong{color:var(--yii-debug-warning)}.yii-debug .yii-debug-asset-chip-deps{border-color:color-mix(in srgb, var(--yii-debug-success), transparent 60%);background:color-mix(in srgb, var(--yii-debug-success), transparent 90%);color:var(--yii-debug-success)}.yii-debug .yii-debug-asset-chip-deps strong{color:var(--yii-debug-success)}.yii-debug .yii-debug-asset-card-body{gap:var(--yii-debug-space-4);grid-template-columns:minmax(180px,2fr) minmax(0,3fr);display:grid}.yii-debug .yii-debug-asset-card-body[data-cols="1"]{grid-template-columns:1fr}.yii-debug .yii-debug-asset-section{gap:var(--yii-debug-space-3);flex-direction:column;min-width:0;display:flex}.yii-debug .yii-debug-asset-section-title{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xs);letter-spacing:.14em;text-transform:uppercase;margin:0;font-weight:700}.yii-debug .yii-debug-asset-files{flex-direction:column;gap:4px;display:flex}.yii-debug .yii-debug-asset-files+.yii-debug-asset-files{margin-top:4px}.yii-debug .yii-debug-asset-file{border:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 60%, transparent);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);grid-template-columns:auto 1fr;align-items:center;gap:6px;min-width:0;padding:4px 8px;display:grid}.yii-debug .yii-debug-asset-file-type{border-radius:var(--yii-debug-radius-pill);min-width:30px;font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.02em;justify-content:center;align-items:center;padding:0 5px;font-weight:700;display:inline-flex}.yii-debug .yii-debug-asset-file-type-css{background:color-mix(in srgb, var(--yii-debug-info), transparent 84%);color:var(--yii-debug-info)}.yii-debug .yii-debug-asset-file-type-js{background:color-mix(in srgb, var(--yii-debug-warning), transparent 84%);color:var(--yii-debug-warning)}.yii-debug .yii-debug-asset-file-name{color:var(--yii-debug-panel-text);text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.yii-debug .yii-debug-asset-wiring{flex-direction:column;gap:4px;margin:0;display:flex}.yii-debug .yii-debug-asset-wiring-row{border:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 60%, transparent);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface-muted);grid-template-columns:56px 1fr;align-items:baseline;gap:8px;padding:6px 10px;display:grid}.yii-debug .yii-debug-asset-wiring-label{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.12em;text-transform:uppercase;margin:0;font-weight:700}.yii-debug .yii-debug-asset-wiring-value{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);word-break:break-all;margin:0}.yii-debug .yii-debug-asset-depends{padding:10px var(--yii-debug-space-3);border:1px dashed color-mix(in srgb, var(--yii-debug-success), transparent 50%);border-radius:var(--yii-debug-radius-md);background:color-mix(in srgb, var(--yii-debug-success), transparent 92%);flex-direction:column;gap:8px;display:flex}.yii-debug .yii-debug-asset-depends-label{color:var(--yii-debug-success);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xs);letter-spacing:.14em;text-transform:uppercase;font-weight:700}.yii-debug .yii-debug-asset-depends-list{flex-wrap:wrap;gap:6px;display:flex}.yii-debug .yii-debug-asset-depend{border:1px solid color-mix(in srgb, var(--yii-debug-success), transparent 50%);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);color:var(--yii-debug-success);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);align-items:center;gap:5px;padding:3px 10px 3px 8px;font-weight:600;text-decoration:none;transition:background-color .14s,color .14s,transform .14s,border-color .14s;display:inline-flex}.yii-debug .yii-debug-asset-depend:hover,.yii-debug .yii-debug-asset-depend:focus-visible{border-color:var(--yii-debug-success);background:color-mix(in srgb, var(--yii-debug-success), transparent 86%);color:var(--yii-debug-success);text-decoration:none;transform:translateY(-1px)}.yii-debug .yii-debug-asset-depend-icon{opacity:.85;font-size:12px;line-height:1}.yii-debug .yii-debug-asset-depend-name{white-space:nowrap}@media (max-width:720px){.yii-debug .yii-debug-asset-stats{grid-template-columns:repeat(2,minmax(0,1fr))}.yii-debug .yii-debug-asset-card-body{grid-template-columns:1fr}.yii-debug .yii-debug-asset-card-head{grid-template-columns:auto 1fr}.yii-debug .yii-debug-asset-card-meta{grid-column:1/-1;justify-content:flex-start;max-width:100%}}.yii-debug .yii-debug-history-card{gap:var(--yii-debug-space-2);padding:var(--yii-debug-space-3);border:1px dashed color-mix(in srgb, var(--yii-debug-panel-border) 70%, transparent);border-radius:var(--yii-debug-radius-md);background:radial-gradient(circle at top right, color-mix(in srgb, var(--yii-debug-panel-primary), transparent 86%), transparent 60%), color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 40%);flex-direction:column;display:flex;position:relative}.yii-debug .yii-debug-nav-iconed .yii-debug-nav-link{align-items:center}.yii-debug .yii-debug-nav-iconed .yii-debug-nav-link .yii-debug-nav-link-label{margin-right:auto}.yii-debug .yii-debug-nav-link-icon{width:16px;height:16px;color:var(--yii-debug-panel-primary);flex:none;justify-content:center;align-items:center;margin-right:10px;transition:color .14s,transform .14s;display:inline-flex}.yii-debug .yii-debug-nav-link-icon svg{width:100%;height:100%}.yii-debug .yii-debug-nav-link.is-active .yii-debug-nav-link-icon{color:currentColor}.yii-debug .yii-debug-nav-link-label{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-sm);letter-spacing:.005em;flex:1;font-weight:600;overflow:hidden}.yii-debug .yii-debug-nav-link:not(.is-active){color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-nav-link:not(.is-active) .yii-debug-nav-link-icon{color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 35%)}.yii-debug .yii-debug-nav-link:not(.is-active):after{color:color-mix(in srgb, var(--yii-debug-panel-muted), transparent 40%)}.yii-debug .yii-debug-nav-link:not(.is-active):hover,.yii-debug .yii-debug-nav-link:not(.is-active):focus-visible{color:var(--yii-debug-panel-text);background:var(--yii-debug-panel-surface-hover)}:is(.yii-debug .yii-debug-nav-link:not(.is-active):hover,.yii-debug .yii-debug-nav-link:not(.is-active):focus-visible) .yii-debug-nav-link-icon{color:var(--yii-debug-panel-primary);transform:translate(1px)}:is(.yii-debug .yii-debug-nav-link:not(.is-active):hover,.yii-debug .yii-debug-nav-link:not(.is-active):focus-visible):after{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-tag-link{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);letter-spacing:-.01em;color:var(--yii-debug-panel-primary-strong);white-space:nowrap;border-bottom:1px dotted color-mix(in srgb, var(--yii-debug-panel-primary), transparent 60%);font-weight:700;text-decoration:none;display:inline-block}.yii-debug .yii-debug-tag-link:hover,.yii-debug .yii-debug-tag-link:focus-visible{color:var(--yii-debug-panel-primary);border-bottom-color:var(--yii-debug-panel-primary);text-decoration:none}.yii-debug .yii-debug-url-cell{text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;max-width:clamp(180px,26vw,460px);display:inline-block;overflow:hidden}.yii-debug .yii-debug-table tbody tr.is-cursor{background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 86%);box-shadow:inset 3px 0 0 var(--yii-debug-panel-primary)}.yii-debug .yii-debug-table tbody tr.is-cursor:hover{background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 78%)}.yii-debug .yii-debug-active-filters{margin-bottom:var(--yii-debug-space-3);padding:8px var(--yii-debug-space-3);border:1px solid color-mix(in srgb, var(--yii-debug-info), transparent 60%);border-radius:var(--yii-debug-radius-md);background:color-mix(in srgb, var(--yii-debug-info), transparent 92%);flex-wrap:wrap;align-items:center;gap:8px;display:flex}.yii-debug .yii-debug-active-filters-label{color:var(--yii-debug-info);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;font-weight:700}.yii-debug .yii-debug-active-filters-list{flex-wrap:wrap;gap:4px;display:inline-flex}.yii-debug .yii-debug-active-filter-pill{border:1px solid color-mix(in srgb, var(--yii-debug-info), transparent 50%);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);align-items:center;gap:4px;padding:2px 6px 2px 8px;text-decoration:none;transition:background-color .12s,border-color .12s;display:inline-flex}.yii-debug .yii-debug-active-filter-pill:hover,.yii-debug .yii-debug-active-filter-pill:focus-visible{border-color:var(--yii-debug-danger);background:color-mix(in srgb, var(--yii-debug-danger), transparent 88%);color:var(--yii-debug-danger);text-decoration:none}.yii-debug .yii-debug-active-filter-attr{color:var(--yii-debug-panel-muted);text-transform:lowercase}.yii-debug .yii-debug-active-filter-sep{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-active-filter-value{color:var(--yii-debug-panel-text);text-overflow:ellipsis;white-space:nowrap;max-width:220px;font-weight:700;overflow:hidden}.yii-debug .yii-debug-active-filter-x{color:var(--yii-debug-panel-muted);margin-left:2px;font-weight:700;line-height:1}.yii-debug .yii-debug-active-filter-pill:hover .yii-debug-active-filter-x,.yii-debug .yii-debug-active-filter-pill:focus-visible .yii-debug-active-filter-x{color:var(--yii-debug-danger)}.yii-debug .yii-debug-active-filters-clear{border:1px solid color-mix(in srgb, var(--yii-debug-danger), transparent 60%);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);color:var(--yii-debug-danger);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;text-transform:uppercase;margin-left:auto;padding:3px 10px;font-weight:700;text-decoration:none;transition:background-color .12s,border-color .12s,transform .12s}.yii-debug .yii-debug-active-filters-clear:hover,.yii-debug .yii-debug-active-filters-clear:focus-visible{border-color:var(--yii-debug-danger);background:color-mix(in srgb, var(--yii-debug-danger), transparent 86%);color:var(--yii-debug-danger);text-decoration:none;transform:translateY(-1px)}.yii-debug .yii-debug-col-id,.yii-debug .yii-debug-col-queue-id{white-space:nowrap;min-width:120px}.yii-debug .yii-debug-grid .filters input.yii-debug-col-id-input,.yii-debug .yii-debug-grid .filters input.yii-debug-col-queue-id-input{box-sizing:border-box;width:104px}.yii-debug .yii-debug-grid-history .yii-debug-table{--yii-debug-history-query-width:0px;--yii-debug-history-mail-width:0px;--yii-debug-history-fixed-width:calc(916px + var(--yii-debug-history-query-width) + var(--yii-debug-history-mail-width));min-width:calc(var(--yii-debug-history-fixed-width) + 180px);table-layout:fixed}.yii-debug .yii-debug-grid-history .yii-debug-table:has(thead th.yii-debug-col-num:not(:first-child,.yii-debug-col-mail)){--yii-debug-history-query-width:66px}.yii-debug .yii-debug-grid-history .yii-debug-table:has(thead th.yii-debug-col-mail){--yii-debug-history-mail-width:56px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:first-child{width:36px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:nth-child(2){width:276px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:nth-child(3){width:80px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:nth-child(4),.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:nth-child(5){width:100px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th.yii-debug-col-ip{width:112px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th.yii-debug-col-num:not(:first-child,.yii-debug-col-mail){width:66px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th.yii-debug-col-mail{width:56px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:nth-last-child(3){width:128px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:nth-last-child(2){width:84px}.yii-debug .yii-debug-grid-history .yii-debug-table tbody td,.yii-debug .yii-debug-grid-history .yii-debug-table tbody th{white-space:nowrap;padding:8px 10px}.yii-debug .yii-debug-grid-history .yii-debug-table tbody td.yii-debug-col-ip{white-space:normal;overflow-wrap:anywhere}.yii-debug .yii-debug-grid-history .yii-debug-table tbody:has(>tr:only-child>td[colspan]){caption-side:bottom;display:table-caption}.yii-debug .yii-debug-grid-history .yii-debug-table tbody:has(>tr:only-child>td[colspan]) td{display:block}.yii-debug .yii-debug-grid-history .yii-debug-table .yii-debug-tag-link,.yii-debug .yii-debug-grid-history .yii-debug-table .yii-debug-url-cell{text-overflow:ellipsis;max-width:100%;overflow:hidden}.yii-debug .yii-debug-grid-history .yii-debug-table .yii-debug-tag-link{vertical-align:middle;display:inline-block}@media (max-width:1366px){.yii-debug .yii-debug-grid-history .yii-debug-table{--yii-debug-history-fixed-width:calc(804px + var(--yii-debug-history-query-width))}.yii-debug .yii-debug-grid-history .yii-debug-col-ip,.yii-debug .yii-debug-grid-history .yii-debug-col-mail{display:none}}@media (max-width:767px){.yii-debug .yii-debug-grid-event .yii-debug-table thead th,.yii-debug .yii-debug-grid-event .yii-debug-table thead td,.yii-debug .yii-debug-grid-event .yii-debug-table tbody tr:not(.yii-debug-event-detail-row) td,.yii-debug .yii-debug-grid-event .yii-debug-table tbody th,.yii-debug .yii-debug-grid-event .yii-debug-table tfoot td{padding:8px 10px}.yii-debug .yii-debug-grid-event .yii-debug-table .filters td{padding:6px 10px}}.yii-debug th.yii-debug-col-num,.yii-debug td.yii-debug-col-num{text-align:center;white-space:nowrap;width:1%}.yii-debug td.yii-debug-col-num{font-variant-numeric:tabular-nums}.yii-debug td.yii-debug-col-num input,.yii-debug td.yii-debug-col-num select{text-align:center;width:100%;min-width:0}.yii-debug .yii-debug-explain-scroll{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);overflow-x:auto}.yii-debug .yii-debug-explain-table{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);white-space:nowrap;margin:0}.yii-debug .yii-debug-explain-table th,.yii-debug .yii-debug-explain-table td{padding:var(--yii-debug-space-2) var(--yii-debug-space-3)}.yii-debug ul.yii-debug-trace{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);white-space:normal;margin:2px 0 0;padding:0;list-style:none}.yii-debug .yii-debug-muted{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-nowrap{white-space:nowrap}.yii-debug .yii-debug-break{word-break:break-all}.yii-debug .yii-debug-not-set{color:var(--yii-debug-danger);font-style:italic}.yii-debug .yii-debug-gauge{vertical-align:middle;font-variant-numeric:tabular-nums;flex-direction:column;gap:3px;min-width:76px;display:inline-flex}.yii-debug .yii-debug-gauge-bar{border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-gauge-track);height:3px;display:block;overflow:hidden}.yii-debug .yii-debug-gauge-bar:after{content:"";width:var(--yii-debug-gauge,0%);border-radius:inherit;background:var(--yii-debug-gauge-fill);height:100%;display:block}.yii-debug .yii-debug-section{margin-bottom:var(--yii-debug-space-6)}.yii-debug .yii-debug-section:last-child{margin-bottom:0}.yii-debug .yii-debug-section-title{align-items:center;gap:var(--yii-debug-space-2);margin:0 0 var(--yii-debug-space-3);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);letter-spacing:.22em;text-transform:uppercase;font-size:.6875rem;font-weight:500;line-height:1;display:flex}.yii-debug .yii-debug-section-mark{color:var(--yii-debug-panel-primary);font-weight:700}.yii-debug .yii-debug-section-count{border-radius:var(--yii-debug-radius-pill);background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 85%);color:var(--yii-debug-panel-primary);align-items:center;margin-left:auto;padding:2px 8px;font-weight:700;display:inline-flex}.yii-debug h1.yii-debug-hero-title{margin:0 0 var(--yii-debug-space-5);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-display);letter-spacing:-.015em;text-transform:lowercase;font-size:2rem;font-weight:400;line-height:1}.yii-debug h1.yii-debug-hero-title:before{content:"// ";color:var(--yii-debug-panel-primary);font-family:var(--yii-debug-font-mono);letter-spacing:0;vertical-align:2px;margin-right:.2em;font-size:.75em;font-weight:700}.yii-debug .yii-debug-readout{gap:var(--yii-debug-space-3);margin-bottom:var(--yii-debug-space-6);grid-template-columns:repeat(auto-fit,minmax(210px,1fr));display:grid}.yii-debug .yii-debug-readout-card{gap:var(--yii-debug-space-2);min-height:132px;padding:var(--yii-debug-space-5) var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:radial-gradient(circle at 1px 1px, color-mix(in srgb, var(--yii-debug-panel-border), transparent 30%) 1px, transparent 0) 0 0 / 14px 14px, linear-gradient(150deg, color-mix(in srgb, var(--yii-debug-panel-primary), transparent 92%), transparent 55%), var(--yii-debug-panel-surface);flex-direction:column;transition:border-color .15s,transform .15s,box-shadow .15s;animation:.45s cubic-bezier(.2,.7,.3,1) both yii-debug-readout-in;display:flex;position:relative;overflow:hidden}.yii-debug .yii-debug-readout-card:hover{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 45%);box-shadow:var(--yii-debug-shadow-md);transform:translateY(-2px)}.yii-debug .yii-debug-readout-card:first-child{animation-delay:40ms}.yii-debug .yii-debug-readout-card:nth-child(2){animation-delay:80ms}.yii-debug .yii-debug-readout-card:nth-child(3){animation-delay:.12s}.yii-debug .yii-debug-readout-card:nth-child(4){animation-delay:.16s}.yii-debug .yii-debug-readout-card:nth-child(5){animation-delay:.2s}.yii-debug .yii-debug-readout-card:nth-child(6){animation-delay:.24s}.yii-debug .yii-debug-readout-corner{border:1.5px solid color-mix(in srgb, var(--yii-debug-panel-primary), transparent 40%);pointer-events:none;width:9px;height:9px;transition:border-color .15s;position:absolute}.yii-debug .yii-debug-readout-corner[data-corner=tl]{border-bottom:0;border-right:0;top:8px;left:8px}.yii-debug .yii-debug-readout-corner[data-corner=tr]{border-bottom:0;border-left:0;top:8px;right:8px}.yii-debug .yii-debug-readout-corner[data-corner=bl]{border-top:0;border-right:0;bottom:8px;left:8px}.yii-debug .yii-debug-readout-corner[data-corner=br]{border-top:0;border-left:0;bottom:8px;right:8px}.yii-debug .yii-debug-readout-card:hover .yii-debug-readout-corner{border-color:var(--yii-debug-panel-primary)}.yii-debug .yii-debug-readout-label{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);letter-spacing:.15em;text-transform:uppercase;margin:0;font-size:.6875rem;font-weight:500;line-height:1;display:block;position:relative}.yii-debug .yii-debug-readout-label:before{content:":: ";color:var(--yii-debug-panel-primary);opacity:.85;font-weight:700}.yii-debug .yii-debug-readout-value{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);letter-spacing:-.015em;overflow-wrap:break-word;margin:0;font-size:1.375rem;font-weight:700;line-height:1.15;display:block}.yii-debug .yii-debug-readout-meta{align-items:center;gap:var(--yii-debug-space-2);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);letter-spacing:.05em;text-transform:lowercase;margin-top:auto;font-size:.6875rem;line-height:1;display:inline-flex}.yii-debug .yii-debug-readout-chip{border-radius:var(--yii-debug-radius-sm);background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 80%);color:var(--yii-debug-panel-primary);font-family:var(--yii-debug-font-mono);letter-spacing:.08em;text-transform:uppercase;align-items:center;padding:2px 7px;font-size:.625rem;font-weight:700;display:inline-flex}.yii-debug .yii-debug-readout-chip-muted{background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-ext-strip{gap:var(--yii-debug-space-2);padding:var(--yii-debug-space-3);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:linear-gradient(0deg, color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 30%), var(--yii-debug-panel-surface));flex-wrap:wrap;display:flex}.yii-debug .yii-debug-ext-pill{align-items:center;gap:var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);letter-spacing:.1em;text-transform:uppercase;padding:6px 12px 6px 9px;font-size:.6875rem;font-weight:500;line-height:1;transition:border-color .15s,background .15s;display:inline-flex}.yii-debug .yii-debug-ext-pill-dot{background:color-mix(in srgb, var(--yii-debug-panel-muted), transparent 60%);border-radius:999px;width:7px;height:7px;display:inline-block}.yii-debug .yii-debug-ext-pill-label{color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-ext-pill-state{margin-left:var(--yii-debug-space-1);color:var(--yii-debug-panel-muted);letter-spacing:.12em;opacity:.7;font-size:.625rem;font-weight:700}.yii-debug .yii-debug-ext-pill.is-on{border-color:color-mix(in srgb, var(--yii-debug-success), transparent 55%);background:color-mix(in srgb, var(--yii-debug-success), var(--yii-debug-panel-surface) 90%)}.yii-debug .yii-debug-ext-pill.is-on .yii-debug-ext-pill-dot{background:var(--yii-debug-success);box-shadow:0 0 0 3px color-mix(in srgb, var(--yii-debug-success), transparent 75%);animation:2.4s ease-in-out infinite yii-debug-led-pulse}.yii-debug .yii-debug-ext-pill.is-on .yii-debug-ext-pill-state{color:var(--yii-debug-success);opacity:1}.yii-debug .yii-debug-ext-pill.is-off{opacity:.55}.yii-debug .yii-debug-dl{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);margin:0;padding:0;overflow:hidden}.yii-debug .yii-debug-dl-row{gap:var(--yii-debug-space-4);padding:10px var(--yii-debug-space-4);border-bottom:1px solid var(--yii-debug-panel-border);grid-template-columns:minmax(160px,200px) 1fr;align-items:baseline;display:grid}.yii-debug .yii-debug-dl-row:last-child{border-bottom:0}.yii-debug .yii-debug-dl-row dt{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);letter-spacing:.14em;text-transform:uppercase;margin:0;font-size:.6875rem;font-weight:500}.yii-debug .yii-debug-dl-row dd{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);overflow-wrap:break-word;margin:0;font-size:.8125rem}.yii-debug .yii-debug-package-groups{gap:var(--yii-debug-space-3);display:grid}.yii-debug .yii-debug-package-group{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);overflow:hidden}.yii-debug .yii-debug-package-group-header{justify-content:space-between;align-items:baseline;gap:var(--yii-debug-space-3);border-bottom:var(--yii-debug-border);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 18%);padding:9px 12px;display:flex}.yii-debug .yii-debug-package-vendor{color:var(--yii-debug-panel-primary);font-family:var(--yii-debug-font-mono);letter-spacing:.02em;margin:0;font-size:.75rem;font-weight:700}.yii-debug .yii-debug-package-group-count{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);letter-spacing:.08em;text-transform:uppercase;font-size:.625rem}.yii-debug .yii-debug-package-list{grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));margin:0;padding:0;display:grid}.yii-debug .yii-debug-package-row{gap:var(--yii-debug-space-2);border-right:1px solid color-mix(in srgb, var(--yii-debug-panel-border), transparent 28%);border-bottom:1px solid color-mix(in srgb, var(--yii-debug-panel-border), transparent 28%);grid-template-columns:minmax(0,1fr) auto;align-items:baseline;min-width:0;padding:7px 11px;display:grid}.yii-debug .yii-debug-package-row dt,.yii-debug .yii-debug-package-row dd{font-family:var(--yii-debug-font-mono);margin:0;font-size:.6875rem;line-height:1.35}.yii-debug .yii-debug-package-name{min-width:0;color:var(--yii-debug-panel-text);overflow-wrap:anywhere;font-weight:500}.yii-debug .yii-debug-package-version{color:var(--yii-debug-panel-primary);font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:700}.yii-debug .yii-debug-cta{align-items:center;gap:var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);letter-spacing:.02em;padding:10px 18px 10px 14px;font-size:.8125rem;font-weight:500;text-decoration:none;transition:border-color .15s,color .15s,transform .15s,box-shadow .15s;display:inline-flex}.yii-debug .yii-debug-cta:hover,.yii-debug .yii-debug-cta:focus{border-color:var(--yii-debug-panel-primary);color:var(--yii-debug-panel-primary);box-shadow:0 0 0 3px color-mix(in srgb, var(--yii-debug-panel-primary), transparent 85%);text-decoration:none;transform:translate(3px)}:is(.yii-debug .yii-debug-cta:hover,.yii-debug .yii-debug-cta:focus) .yii-debug-cta-prompt{transform:translate(2px)}.yii-debug .yii-debug-cta-prompt{color:var(--yii-debug-panel-primary);font-weight:700;transition:transform .15s}.yii-debug .yii-debug-cta-external{color:var(--yii-debug-panel-muted);margin-left:auto;font-size:.875rem}.yii-debug .yii-debug-phpinfo-shell{gap:var(--yii-debug-space-4);grid-template-columns:220px minmax(0,1fr);align-items:start;display:grid}.yii-debug .yii-debug-phpinfo-toc{top:var(--yii-debug-space-3);max-height:calc(100vh - 80px);padding:var(--yii-debug-space-3);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);scrollbar-width:thin;align-self:start;position:sticky;overflow-y:auto}.yii-debug .yii-debug-phpinfo-toc-title{margin:0 0 var(--yii-debug-space-2);color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);align-items:baseline;gap:6px;display:flex}.yii-debug .yii-debug-phpinfo-toc-title span:first-child{color:var(--yii-debug-panel-text);font-variant-numeric:tabular-nums;font-weight:700}.yii-debug .yii-debug-phpinfo-toc-title-note{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);margin-left:auto}.yii-debug .yii-debug-phpinfo-toc-overview,.yii-debug .yii-debug-phpinfo-toc-group-list{flex-direction:column;gap:2px;margin:0;padding:0;list-style:none;display:flex}.yii-debug .yii-debug-phpinfo-toc-overview{margin-bottom:var(--yii-debug-space-2);padding-bottom:var(--yii-debug-space-2);border-bottom:var(--yii-debug-border)}.yii-debug .yii-debug-phpinfo-toc-groups{flex-direction:column;gap:2px;display:flex}.yii-debug .yii-debug-phpinfo-toc-group{margin:0}.yii-debug .yii-debug-phpinfo-toc-group-summary{border-radius:var(--yii-debug-radius-sm);color:var(--yii-debug-panel-muted);cursor:pointer;font-size:var(--yii-debug-fs-xs);grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:7px;padding:7px 8px;font-weight:600;list-style:none;transition:background-color .12s,color .12s;display:grid}.yii-debug .yii-debug-phpinfo-toc-group-summary::-webkit-details-marker{display:none}.yii-debug .yii-debug-phpinfo-toc-group-summary:before{content:"›";color:var(--yii-debug-panel-muted);font-size:1.1em;line-height:1;transition:transform .12s}.yii-debug .yii-debug-phpinfo-toc-group-summary:hover,.yii-debug .yii-debug-phpinfo-toc-group-summary:focus-visible{background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-phpinfo-toc-group[open]>.yii-debug-phpinfo-toc-group-summary{color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-phpinfo-toc-group[open]>.yii-debug-phpinfo-toc-group-summary:before{transform:rotate(90deg)}.yii-debug .yii-debug-phpinfo-toc-group-count{border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface-muted);min-width:22px;color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums;text-align:center;padding:1px 6px}.yii-debug .yii-debug-phpinfo-toc-group-list{margin:2px 0 var(--yii-debug-space-2);padding-left:14px}.yii-debug .yii-debug-phpinfo-toc-link{border-radius:0 var(--yii-debug-radius-sm) var(--yii-debug-radius-sm) 0;color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-sm);border-left:2px solid #0000;padding:7px 10px 7px 12px;text-decoration:none;transition:background-color .12s,color .12s,border-color .12s;display:block}.yii-debug .yii-debug-phpinfo-toc-link:hover,.yii-debug .yii-debug-phpinfo-toc-link:focus-visible{background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-text);text-decoration:none}.yii-debug .yii-debug-phpinfo-toc-link.is-active{border-left-color:var(--yii-debug-panel-primary);background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 90%);color:var(--yii-debug-panel-primary-strong);font-weight:700}.yii-debug .yii-debug-phpinfo-main{min-width:0}.yii-debug .yii-debug-phpinfo-search{margin-bottom:var(--yii-debug-space-3);flex-direction:column;gap:6px;display:flex}.yii-debug .yii-debug-phpinfo-search-control{align-items:center;display:flex;position:relative}.yii-debug .yii-debug-phpinfo-search-icon{width:16px;height:16px;color:var(--yii-debug-panel-muted);pointer-events:none;justify-content:center;align-items:center;display:inline-flex;position:absolute;left:12px}.yii-debug .yii-debug-phpinfo-search-icon svg{width:100%;height:100%}.yii-debug .yii-debug-phpinfo-search-input{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);flex:1;padding:9px 12px 9px 36px;transition:border-color .12s,box-shadow .12s}.yii-debug .yii-debug-phpinfo-search-input::placeholder{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-phpinfo-search-input:focus{border-color:var(--yii-debug-panel-primary);box-shadow:0 0 0 3px color-mix(in srgb, var(--yii-debug-panel-primary), transparent 80%)}.yii-debug .yii-debug-phpinfo-search-clear{border-radius:var(--yii-debug-radius-sm);color:var(--yii-debug-panel-link);cursor:pointer;font:inherit;font-size:var(--yii-debug-fs-xs);background:0 0;border:0;padding:4px 8px;font-weight:600;position:absolute;right:8px}.yii-debug .yii-debug-phpinfo-search-clear:hover,.yii-debug .yii-debug-phpinfo-search-clear:focus-visible{background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-link-hover)}.yii-debug .yii-debug-phpinfo-search-control:has(.yii-debug-phpinfo-search-clear:not([hidden])) .yii-debug-phpinfo-search-input{padding-right:58px}.yii-debug .yii-debug-phpinfo-search-feedback{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-2);min-height:18px;padding-inline:2px;display:flex}.yii-debug .yii-debug-phpinfo-search-feedback:has(.yii-debug-phpinfo-search-status:empty):has(.yii-debug-phpinfo-search-empty[hidden]){display:none}.yii-debug .yii-debug-phpinfo-search-status,.yii-debug .yii-debug-phpinfo-search-empty{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs)}.yii-debug .yii-debug-phpinfo-search-empty{color:var(--yii-debug-danger)}.yii-debug .yii-debug-phpinfo-section{scroll-margin-top:var(--yii-debug-space-3)}.yii-debug .yii-debug-phpinfo-section+:is(.yii-debug .yii-debug-phpinfo-section){margin-top:var(--yii-debug-space-4)}.yii-debug .yii-debug-phpinfo-section .yii-debug-disclosure{margin-bottom:0}.yii-debug .yii-debug-phpinfo-toc-link[hidden],.yii-debug .yii-debug-phpinfo-toc-group[hidden],.yii-debug .yii-debug-phpinfo-section[hidden],.yii-debug .yii-debug-phpinfo-search-clear[hidden],.yii-debug .yii-debug-phpinfo-search-empty[hidden],.yii-debug .yii-debug-phpinfo-overview-hero-section[hidden],.yii-debug .yii-debug-disclosure[hidden],.yii-debug .yii-debug-phpinfo-extension-group[hidden],.yii-debug .yii-debug-ext-pill[hidden],.yii-debug .yii-debug-table-wrap[hidden],.yii-debug .yii-debug-phpinfo-module tr[hidden]{display:none}@media (max-width:900px){.yii-debug .yii-debug-phpinfo-shell{grid-template-columns:1fr}.yii-debug .yii-debug-phpinfo-toc{max-height:none;position:static;overflow:hidden}.yii-debug .yii-debug-phpinfo-toc-groups{gap:4px var(--yii-debug-space-2);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.yii-debug .yii-debug-phpinfo-toc-group[open]{grid-column:1/-1}.yii-debug .yii-debug-phpinfo-toc-group-list{grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:4px;padding-left:0;display:grid}.yii-debug .yii-debug-phpinfo-toc-link{border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface-muted);border-left:0;padding:4px 10px}.yii-debug .yii-debug-phpinfo-toc-link.is-active{background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 80%);border-left:0}}@media (max-width:560px){.yii-debug .yii-debug-phpinfo-toc-groups{grid-template-columns:1fr}}.yii-debug .yii-debug-phpinfo-overview-hero{gap:var(--yii-debug-space-3);margin-bottom:var(--yii-debug-space-3);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}@media (max-width:720px){.yii-debug .yii-debug-phpinfo-overview-hero{grid-template-columns:1fr}}.yii-debug .yii-debug-phpinfo-overview-hero-headline{align-items:center;gap:var(--yii-debug-space-3);grid-template-columns:1fr auto;display:grid}.yii-debug .yii-debug-phpinfo-overview-hero-version{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-display,var(--yii-debug-font-mono));letter-spacing:-.02em;font-size:clamp(1.6rem,2.4vw + 1rem,2.4rem);font-weight:800;line-height:1}.yii-debug .yii-debug-phpinfo-overview-hero-mark{border-radius:var(--yii-debug-radius-pill);border:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);min-width:64px;color:var(--yii-debug-panel-primary-strong);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-lg);letter-spacing:.08em;text-transform:lowercase;justify-content:center;align-items:center;padding:6px 14px;font-weight:700;display:inline-flex}.yii-debug .yii-debug-phpinfo-overview-hero-metrics{gap:var(--yii-debug-space-2) var(--yii-debug-space-4);grid-template-columns:repeat(auto-fit,minmax(150px,1fr));margin:0;display:grid}.yii-debug .yii-debug-phpinfo-overview-hero-section{gap:var(--yii-debug-space-3);min-width:0;padding:var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);flex-direction:column;display:flex}.yii-debug .yii-debug-phpinfo-overview-hero-section:first-child{grid-column:1/-1}.yii-debug .yii-debug-phpinfo-extensions{grid-column:1/-1}.yii-debug .yii-debug-phpinfo-extensions>.yii-debug-phpinfo-extension-groups{padding:var(--yii-debug-space-4)}.yii-debug .yii-debug-phpinfo-extension-group-count{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums;padding:1px 7px}.yii-debug .yii-debug-phpinfo-extension-groups{gap:var(--yii-debug-space-3);flex-direction:column;display:flex}.yii-debug .yii-debug-phpinfo-extension-group{gap:var(--yii-debug-space-2);flex-direction:column;display:flex}.yii-debug .yii-debug-phpinfo-extension-group-head{align-items:center;gap:var(--yii-debug-space-2);color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);font-weight:600;display:flex}.yii-debug .yii-debug-phpinfo-extension-group-head .yii-debug-phpinfo-extension-group-count{margin-left:auto}.yii-debug .yii-debug-phpinfo-extension-group .yii-debug-ext-pill{scroll-margin-top:var(--yii-debug-space-4)}.yii-debug .yii-debug-phpinfo-extension-group .yii-debug-ext-pill:target,.yii-debug .yii-debug-phpinfo-extension-group .yii-debug-ext-pill.is-search-match{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 45%);background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 90%)}.yii-debug .yii-debug-phpinfo-overview-block-head{border-bottom:var(--yii-debug-border);align-items:center;gap:10px;padding-bottom:8px;display:flex}.yii-debug .yii-debug-phpinfo-overview-block-eyebrow{color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);font-weight:600}.yii-debug .yii-debug-phpinfo-overview-hero-metric{flex-direction:column;gap:3px;padding-block:2px;display:flex}.yii-debug .yii-debug-phpinfo-overview-hero-metric dt{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);margin:0;font-weight:600;line-height:1.2}.yii-debug .yii-debug-phpinfo-overview-hero-metric dd{overflow-wrap:break-word;min-width:0;color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);margin:0;line-height:1.4}.yii-debug .yii-debug-phpinfo-overview-hero-metric dd code{color:inherit;background:0 0;padding:0}.yii-debug .yii-debug-phpinfo-overview-hero-metric.is-wide{grid-column:1/-1}.yii-debug .yii-debug-phpinfo-overview-pill{border-radius:var(--yii-debug-radius-pill);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;text-transform:uppercase;border:1px solid #0000;align-items:center;gap:6px;padding:1px 10px;font-weight:700;display:inline-flex}.yii-debug .yii-debug-phpinfo-overview-pill[data-variant=success]{color:var(--yii-debug-success);background:color-mix(in srgb, var(--yii-debug-success), transparent 86%);border-color:color-mix(in srgb, var(--yii-debug-success), transparent 65%)}.yii-debug .yii-debug-phpinfo-overview-pill[data-variant=muted]{color:var(--yii-debug-panel-muted);background:var(--yii-debug-panel-surface-muted);border-color:var(--yii-debug-panel-border)}.yii-debug .yii-debug-phpinfo-overview-token{border:1px solid var(--yii-debug-panel-border);border-radius:var(--yii-debug-radius-sm);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);padding:1px 8px}.yii-debug .yii-debug-phpinfo-overview-files{flex-wrap:wrap;gap:4px;display:flex}.yii-debug .yii-debug-phpinfo-overview-files .yii-debug-phpinfo-overview-token{cursor:help;transition:border-color .12s,background-color .12s}.yii-debug .yii-debug-phpinfo-overview-files .yii-debug-phpinfo-overview-token:hover{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 40%);background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 88%)}.yii-debug .yii-debug-phpinfo-module{gap:var(--yii-debug-space-3);flex-direction:column;margin-top:0;display:flex}.yii-debug .yii-debug-phpinfo-module-head{align-items:center;gap:10px;margin:0;padding:2px 2px 0;display:flex}.yii-debug .yii-debug-phpinfo-module-head h2{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xl);text-align:left;background:0 0;margin:0;padding:0;font-weight:600}.yii-debug .yii-debug-phpinfo-table-section{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);overflow:hidden}.yii-debug .yii-debug-phpinfo-table-section-head{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-2);padding:9px var(--yii-debug-space-3);border-bottom:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-sm);font-weight:600;display:flex}.yii-debug .yii-debug-phpinfo-variable-group>summary{cursor:pointer;list-style:none;transition:background-color .12s}.yii-debug .yii-debug-phpinfo-variable-group>summary::-webkit-details-marker{display:none}.yii-debug .yii-debug-phpinfo-variable-group>summary:hover,.yii-debug .yii-debug-phpinfo-variable-group>summary:focus-visible{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-phpinfo-variable-group>summary>span:first-child{align-items:center;gap:8px;display:inline-flex}.yii-debug .yii-debug-phpinfo-variable-group>summary>span:first-child:before{content:"›";color:var(--yii-debug-panel-muted);font-size:1.1em;line-height:1;transition:transform .12s}.yii-debug .yii-debug-phpinfo-variable-group[open]>summary>span:first-child:before{transform:rotate(90deg)}.yii-debug .yii-debug-phpinfo-table-section-count{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums;flex:none;padding:1px 7px;font-weight:500}.yii-debug .yii-debug-phpinfo-table-scroll{min-width:0;overflow-x:auto}.yii-debug .yii-debug-phpinfo-module .yii-debug-table{table-layout:fixed;min-width:560px}.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr>:first-child{width:40%}.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr>:nth-child(2):last-child{width:60%}.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr>:nth-child(2):not(:last-child),.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr>:nth-child(3){width:30%}.yii-debug .yii-debug-phpinfo-module .yii-debug-table th,.yii-debug .yii-debug-phpinfo-module .yii-debug-table td{text-align:left}.yii-debug .yii-debug-phpinfo-module .yii-debug-table th[scope=row]{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);white-space:normal;font-weight:500}.yii-debug .yii-debug-phpinfo-module .yii-debug-table td{font-family:var(--yii-debug-font-mono)}.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr.h>*{z-index:1;background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xs);letter-spacing:.03em;text-transform:uppercase;font-weight:700;position:sticky;top:0}.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr.is-search-match>*{background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 88%)!important}.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr.has-local-override>td:nth-child(2){box-shadow:inset 2px 0 0 color-mix(in srgb, var(--yii-debug-warning), transparent 25%);color:color-mix(in srgb, var(--yii-debug-warning) 72%, var(--yii-debug-panel-text))}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-phpinfo-table-scroll{overflow:visible}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts{table-layout:auto;min-width:0;display:block}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tbody{grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact{border-bottom:1px solid color-mix(in srgb, var(--yii-debug-panel-border), transparent 25%);grid-template-columns:minmax(130px,.85fr) minmax(0,1.15fr);min-width:0;display:grid}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact:nth-child(odd){border-right:1px solid color-mix(in srgb, var(--yii-debug-panel-border), transparent 25%)}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact.yii-debug-phpinfo-fact-wide{border-right:0;grid-column:1/-1}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact>*{overflow-wrap:anywhere;background:0 0;border:0;width:auto;padding:9px 12px}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact>th{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xs);font-weight:600}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact>td{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs)}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact-subheading{grid-column:1/-1;display:block}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact-subheading>th{border-block:var(--yii-debug-border);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 15%);width:auto;color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xs);letter-spacing:.02em;text-align:left;padding:8px 12px;font-weight:600;display:block}.yii-debug .yii-debug-phpinfo-status-pill,.yii-debug .yii-debug-phpinfo-redacted{border-radius:var(--yii-debug-radius-pill);width:-moz-fit-content;width:fit-content;font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.03em;border:1px solid #0000;align-items:center;padding:1px 8px;font-weight:600;display:inline-flex}.yii-debug .yii-debug-phpinfo-status-pill[data-variant=success]{border-color:color-mix(in srgb, var(--yii-debug-success), transparent 65%);background:color-mix(in srgb, var(--yii-debug-success), transparent 88%);color:var(--yii-debug-success)}.yii-debug .yii-debug-phpinfo-status-pill[data-variant=muted]{border-color:var(--yii-debug-panel-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-phpinfo-redacted{border-color:color-mix(in srgb, var(--yii-debug-warning), transparent 65%);background:color-mix(in srgb, var(--yii-debug-warning), transparent 90%);color:color-mix(in srgb, var(--yii-debug-warning) 72%, var(--yii-debug-panel-text));text-transform:uppercase}.yii-debug .yii-debug-phpinfo-table-section.is-note .yii-debug-table.is-note{table-layout:auto;min-width:0}.yii-debug .yii-debug-phpinfo-table-section.is-note .yii-debug-table.is-note td,.yii-debug .yii-debug-phpinfo-table-section.is-note .yii-debug-table.is-note th{width:auto;padding:var(--yii-debug-space-3);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);text-align:left;line-height:1.6}@media (max-width:720px){.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tbody{grid-template-columns:1fr}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact{border-right:0}}@media (max-width:959px){.yii-debug .yii-debug-sidebar{min-width:0}.yii-debug .yii-debug-nav{overscroll-behavior-x:contain;scrollbar-width:thin;overflow-x:auto}.yii-debug .yii-debug-nav ul{gap:var(--yii-debug-space-1);flex-direction:row;width:max-content;min-width:100%}.yii-debug .yii-debug-nav-link{white-space:nowrap;min-height:40px}.yii-debug .yii-debug-nav-link:after{display:none}.yii-debug .yii-debug-nav-link-icon{margin-right:var(--yii-debug-space-1)}}@media (max-width:767px){.yii-debug .yii-debug-page{padding:var(--yii-debug-space-2)}.yii-debug .yii-debug-card{padding:var(--yii-debug-space-3)}}@media (prefers-reduced-motion:reduce){.yii-debug *,.yii-debug :before,.yii-debug :after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}@keyframes yii-debug-user-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(.85)}}@keyframes yii-debug-readout-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes yii-debug-led-pulse{0%,to{box-shadow:0 0 0 3px color-mix(in srgb, var(--yii-debug-success), transparent 75%)}50%{box-shadow:0 0 0 6px color-mix(in srgb, var(--yii-debug-success), transparent 90%)}}.yii-debug-tl-filter{align-items:end;gap:var(--yii-debug-space-3);margin-bottom:var(--yii-debug-space-3);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface-muted);flex-wrap:wrap;display:flex}.yii-debug-tl-field{flex-direction:column;gap:4px;min-width:160px;display:flex}.yii-debug-tl-field label{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;font-weight:600}.yii-debug-tl-field input{padding:6px var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-sm);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font:inherit;font-family:var(--yii-debug-font-mono)}.yii-debug-tl-field input::placeholder{color:var(--yii-debug-panel-muted)}.yii-debug-tl-field input:focus{border-color:var(--yii-debug-panel-primary)}.yii-debug-tl-field-grow{flex:240px}.yii-debug-tl-hint{margin-bottom:var(--yii-debug-space-3);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border:1px dashed color-mix(in srgb, var(--yii-debug-panel-border) 70%, transparent);border-radius:var(--yii-debug-radius-md);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 40%)}.yii-debug-tl-hint-title{color:var(--yii-debug-panel-text);margin:0 0 6px;font-weight:600}.yii-debug-tl-hint-body{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-sm);margin:0;line-height:1.55}.yii-debug-tl-hint-body em{color:var(--yii-debug-panel-text);font-style:normal;font-weight:600}.yii-debug-tl{--yii-debug-tl-label-col:22%;grid-template-columns:minmax(180px, var(--yii-debug-tl-label-col)) 1fr;border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);grid-template-rows:auto 1fr auto;gap:0;display:grid;overflow:hidden}@media (max-width:1440px){.yii-debug-tl{--yii-debug-tl-label-col:32%}}.yii-debug-tl-axis{border-bottom:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);grid-column:2/3;height:32px;padding:8px 0;position:relative}.yii-debug-tl-tick{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;white-space:nowrap;pointer-events:none;position:absolute;top:8px;transform:translate(-50%)}.yii-debug-tl-tick:after{content:"";background:var(--yii-debug-panel-border);width:1px;height:8px;position:absolute;top:18px;left:50%}.yii-debug-tl-rows{grid-column:1/3;grid-template-columns:minmax(180px, var(--yii-debug-tl-label-col)) 1fr;background:linear-gradient(to right, transparent calc(var(--yii-debug-tl-label-col) - 1px), var(--yii-debug-panel-border) calc(var(--yii-debug-tl-label-col) - 1px), var(--yii-debug-panel-border) var(--yii-debug-tl-label-col), transparent var(--yii-debug-tl-label-col));display:grid}.yii-debug-tl-row{display:contents}.yii-debug-tl-row:hover .yii-debug-tl-label,.yii-debug-tl-row:hover .yii-debug-tl-track{background:var(--yii-debug-panel-surface-hover)}.yii-debug-tl-row:hover .yii-debug-tl-name{color:var(--yii-debug-panel-text)}.yii-debug-tl-label{min-width:0;padding:8px var(--yii-debug-space-3) 8px calc(var(--yii-debug-space-3) + (var(--depth,0) * 14px));border-bottom:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 50%, transparent);grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px;transition:background-color .12s;display:grid}.yii-debug-tl-dot{background:var(--yii-debug-cat-db);opacity:.85;border-radius:50%;flex:none;width:6px;height:6px}.yii-debug-tl-name{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);color:var(--yii-debug-panel-muted);text-overflow:ellipsis;white-space:nowrap;transition:color .12s;overflow:hidden}.yii-debug-tl-track{border-bottom:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 50%, transparent);transition:background-color .12s;position:relative}.yii-debug-tl-bar{background:var(--yii-debug-cat-db);border-radius:4px;min-width:2px;height:18px;position:absolute;top:50%;transform:translateY(-50%);box-shadow:inset 0 1px #ffffff1f,0 1px 2px #0000002e}.yii-debug-tl-bar-duration{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums;white-space:nowrap;justify-self:end;font-weight:500}.yii-debug-tl-memory{grid-template-columns:minmax(180px, var(--yii-debug-tl-label-col)) 1fr auto;padding:var(--yii-debug-space-2) var(--yii-debug-space-3);border-top:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);grid-column:1/3;align-items:center;display:grid}.yii-debug-tl-memory-label{text-transform:uppercase;letter-spacing:.06em}.yii-debug-tl-memory-track{border-radius:var(--yii-debug-radius-sm);background:color-mix(in srgb, var(--yii-debug-panel-surface), transparent 50%);width:100%;color:var(--yii-debug-panel-primary);position:relative;overflow:hidden}.yii-debug-tl-memory-track svg{width:100%;height:100%;display:block}.yii-debug-tl-memory-peak{margin-left:var(--yii-debug-space-2);color:var(--yii-debug-panel-text);font-weight:700}@media (max-width:640px){.yii-debug-tl,.yii-debug-tl-rows{--yii-debug-tl-label-col:52%;grid-template-columns:minmax(140px, var(--yii-debug-tl-label-col)) minmax(0, 1fr)}.yii-debug-tl-label{padding:7px var(--yii-debug-space-2);gap:6px}.yii-debug-tl-memory{grid-template-columns:minmax(140px, var(--yii-debug-tl-label-col)) minmax(0, 1fr)}.yii-debug-tl-memory-peak{text-align:right;grid-column:2;margin:4px 0 0}}.yii-debug .yii-debug-skip-link{top:var(--yii-debug-space-2);left:var(--yii-debug-space-2);z-index:1000;padding:var(--yii-debug-space-2) var(--yii-debug-space-3);border:2px solid var(--yii-debug-focus-ring);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-md);color:var(--yii-debug-panel-text);transform:translateY(calc(-100% - var(--yii-debug-space-4)));font-weight:700;position:fixed}.yii-debug .yii-debug-skip-link:focus-visible{text-decoration:none;transform:translateY(0)}.yii-debug .yii-debug-brand-chip-control{border:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);cursor:pointer;justify-content:center}.yii-debug .yii-debug-brand-chip-control:hover,.yii-debug .yii-debug-brand-chip-control:focus-visible{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 50%);color:var(--yii-debug-panel-primary-strong);transform:translateY(-1px)}.yii-debug :is(h2,h3).yii-debug-has-permalink{align-items:center;gap:var(--yii-debug-space-1);scroll-margin-block-start:var(--yii-debug-space-5);display:flex}.yii-debug .yii-debug-heading-permalink{border-radius:var(--yii-debug-radius-sm);min-width:32px;min-height:32px;color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);opacity:0;transition:color var(--yii-debug-duration-fast) ease, opacity var(--yii-debug-duration-fast) ease, background var(--yii-debug-duration-fast) ease;flex:none;justify-content:center;align-items:center;line-height:1;text-decoration:none;display:inline-flex}.yii-debug .yii-debug-heading-permalink:hover,.yii-debug .yii-debug-heading-permalink:focus-visible{background:var(--yii-debug-panel-bg-accent);color:var(--yii-debug-panel-primary-strong);opacity:1;text-decoration:none}.yii-debug :is(h2,h3).yii-debug-has-permalink:hover>.yii-debug-heading-permalink,.yii-debug :is(h2,h3).yii-debug-has-permalink:focus-within>.yii-debug-heading-permalink,.yii-debug :is(h2,h3).yii-debug-deep-link-target>.yii-debug-heading-permalink{opacity:1}.yii-debug .yii-debug-deep-link-target{outline:2px solid var(--yii-debug-focus-ring);outline-offset:4px}.yii-debug .yii-debug-db-n1-summary{gap:var(--yii-debug-space-2);margin-bottom:var(--yii-debug-space-4);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border:1px solid color-mix(in srgb, var(--yii-debug-warning), transparent 55%);border-radius:var(--yii-debug-radius-md);background:color-mix(in srgb, var(--yii-debug-warning), transparent 92%);display:grid}.yii-debug .yii-debug-db-n1-heading{justify-content:space-between;align-items:baseline;gap:var(--yii-debug-space-2);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-db-n1-list{gap:var(--yii-debug-space-2);flex-wrap:wrap;margin:0;padding:0;list-style:none;display:flex}.yii-debug .yii-debug-db-n1-link,.yii-debug .yii-debug-db-n1-row-link{align-items:center;gap:var(--yii-debug-space-2);min-height:var(--yii-debug-control-min-size);padding:var(--yii-debug-space-1) var(--yii-debug-space-2);border:1px solid color-mix(in srgb, var(--yii-debug-warning), transparent 60%);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);color:color-mix(in srgb, var(--yii-debug-warning) 72%, var(--yii-debug-panel-text));font-size:var(--yii-debug-fs-xs);font-weight:700;text-decoration:none;display:inline-flex}:is(.yii-debug .yii-debug-db-n1-link,.yii-debug .yii-debug-db-n1-row-link):hover,:is(.yii-debug .yii-debug-db-n1-link,.yii-debug .yii-debug-db-n1-row-link):focus-visible,:is(.yii-debug .yii-debug-db-n1-link,.yii-debug .yii-debug-db-n1-row-link)[aria-current=true]{background:color-mix(in srgb, var(--yii-debug-warning), transparent 84%);color:var(--yii-debug-panel-text);text-decoration:none}.yii-debug .yii-debug-db-n1-fingerprint{max-width:min(56ch,65vw);font-family:var(--yii-debug-font-mono);text-overflow:ellipsis;white-space:nowrap;font-weight:500;display:inline-block;overflow:hidden}.yii-debug .yii-debug-db-n1-row-link{margin-top:var(--yii-debug-space-3)}.yii-debug .yii-debug-grid-db .yii-debug-trace{gap:0;display:grid}.yii-debug .yii-debug-grid-db .yii-debug-trace a{align-items:center;min-height:24px;display:inline-flex}.yii-debug .yii-debug-db-n1-clear[hidden]{display:none}.yii-debug .yii-debug-compare-form{gap:var(--yii-debug-space-3);margin-bottom:var(--yii-debug-space-4);grid-template-columns:repeat(auto-fit,minmax(min(100%,16rem),1fr));align-items:end;display:grid}.yii-debug .yii-debug-compare-overview{gap:var(--yii-debug-space-3);margin-bottom:var(--yii-debug-space-4);grid-template-columns:repeat(auto-fit,minmax(min(100%,12rem),1fr));display:grid}.yii-debug .yii-debug-compare-grid{min-width:42rem}.yii-debug :is(.yii-debug-delta-up,.yii-debug-delta-down,.yii-debug-delta-neutral){padding:2px var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums;align-items:center;font-weight:700;display:inline-flex}.yii-debug .yii-debug-delta-up{border-color:color-mix(in srgb, var(--yii-debug-danger), transparent 60%);background:color-mix(in srgb, var(--yii-debug-danger), transparent 90%);color:var(--yii-debug-danger)}.yii-debug .yii-debug-delta-down{border-color:color-mix(in srgb, var(--yii-debug-success), transparent 60%);background:color-mix(in srgb, var(--yii-debug-success), transparent 90%);color:var(--yii-debug-success)}.yii-debug .yii-debug-delta-neutral{background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted)}@container yii-debug-main (width<=560px){.yii-debug .yii-debug-section-header,.yii-debug .yii-debug-panel-heading{flex-direction:column;align-items:stretch}.yii-debug .yii-debug-filter-input{width:100%;max-width:none}}@media (max-width:767px){.yii-debug .yii-debug-brand-chip-copy .yii-debug-brand-label{text-overflow:ellipsis;white-space:nowrap;max-width:5.5rem;overflow:hidden}}@media (pointer:coarse){.yii-debug .yii-debug-grid-db .yii-debug-trace a{min-width:44px;min-height:44px}.yii-debug :is(.yii-debug-btn,.yii-debug-brand-chip,.yii-debug-cell-more-toggle,.yii-debug-dropdown-item,.yii-debug-filter-input,.yii-debug-heading-permalink,.yii-debug-server-group-summary,.yii-debug-tab-link){min-height:44px}}@media (forced-colors:active){.yii-debug .yii-debug-card,.yii-debug .yii-debug-brand-bar,.yii-debug .yii-debug-brand-chip,.yii-debug .yii-debug-nav,.yii-debug .yii-debug-table-wrap{box-shadow:none;border-color:canvastext}.yii-debug .yii-debug-nav-link.is-active,.yii-debug .yii-debug-tab-link.is-active{color:highlighttext;background:highlight;border-color:highlight}.yii-debug :focus-visible{outline-color:highlight}} +:root,:host{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--yii-debug-font-sans:"IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, blinkmacsystemfont, "Segoe UI", roboto, helvetica, arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";--yii-debug-font-mono:"JetBrains Mono", ui-monospace, sfmono-regular, menlo, monaco, consolas, "Liberation Mono", "Courier New", monospace;--yii-debug-font-display:var(--yii-debug-font-mono);--yii-debug-space-1:4px;--yii-debug-space-2:8px;--yii-debug-space-3:12px;--yii-debug-space-4:16px;--yii-debug-space-5:24px;--yii-debug-space-6:32px;--yii-debug-radius-sm:6px;--yii-debug-radius-md:10px;--yii-debug-radius-lg:14px;--yii-debug-radius-pill:999px;--yii-debug-fs-xs:.75rem;--yii-debug-fs-sm:.8125rem;--yii-debug-fs-base:.9375rem;--yii-debug-fs-lg:1.0625rem;--yii-debug-fs-xl:1.25rem;--yii-debug-fs-2xl:1.5rem;--yii-debug-control-min-size:36px;--yii-debug-panel-padding:var(--yii-debug-space-5);--yii-debug-row-padding-block:10px;--yii-debug-duration-fast:.12s;--yii-debug-duration-normal:.18s;--yii-debug-panel-bg:var(--lightningcss-light,#f2f5f1)var(--lightningcss-dark,#0c120e);--yii-debug-panel-bg-accent:var(--lightningcss-light,#0e7a551a)var(--lightningcss-dark,#2fd08c14);--yii-debug-panel-surface:var(--lightningcss-light,#fffffff0)var(--lightningcss-dark,#121a15eb);--yii-debug-panel-surface-muted:var(--lightningcss-light,#f6f8f5)var(--lightningcss-dark,#101812);--yii-debug-panel-surface-hover:var(--lightningcss-light,#e9f2ec)var(--lightningcss-dark,#1a241d);--yii-debug-panel-border:var(--lightningcss-light,#5d7a6942)var(--lightningcss-dark,#8fa39633);--yii-debug-panel-border-strong:var(--lightningcss-light,#4660515c)var(--lightningcss-dark,#8fa39652);--yii-debug-panel-text:var(--lightningcss-light,#16211a)var(--lightningcss-dark,#e4ede6);--yii-debug-panel-muted:var(--lightningcss-light,#5c6b60)var(--lightningcss-dark,#8fa396);--yii-debug-panel-link:var(--lightningcss-light,#0b6b4a)var(--lightningcss-dark,#4fd39a);--yii-debug-panel-link-hover:var(--lightningcss-light,#0d855b)var(--lightningcss-dark,#7fe4b8);--yii-debug-panel-primary:var(--lightningcss-light,#0e7a55)var(--lightningcss-dark,#2fd08c);--yii-debug-primary-bg:linear-gradient(135deg, var(--yii-debug-panel-primary-strong), var(--yii-debug-panel-primary));--yii-debug-on-primary:var(--lightningcss-light,#fff)var(--lightningcss-dark,#06281b);--yii-debug-panel-primary-strong:var(--lightningcss-light,#0a5c40)var(--lightningcss-dark,#17a673);--yii-debug-focus-ring:var(--lightningcss-light,#075c40)var(--lightningcss-dark,#7fe4b8);--yii-debug-success:var(--lightningcss-light,#0d7d47)var(--lightningcss-dark,#34c97e);--yii-debug-warning:var(--lightningcss-light,#8f5000)var(--lightningcss-dark,#e8a33d);--yii-debug-danger:var(--lightningcss-light,#c93838)var(--lightningcss-dark,#f16a6a);--yii-debug-on-success:var(--lightningcss-light,#fff)var(--lightningcss-dark,#0c120e);--yii-debug-on-warning:var(--lightningcss-light,#fff)var(--lightningcss-dark,#1c1917);--yii-debug-on-danger:var(--lightningcss-light,#fff)var(--lightningcss-dark,#1c1917);--yii-debug-info:var(--lightningcss-light,#205f9d)var(--lightningcss-dark,#5fa8e8);--yii-debug-on-info:var(--yii-debug-panel-bg);--yii-debug-panel-code-bg:var(--lightningcss-light,#ecf1ec)var(--lightningcss-dark,#17211a);--yii-debug-panel-pill:var(--lightningcss-light,#16211a0f)var(--lightningcss-dark,#8fa3962e);--yii-debug-panel-shadow:var(--lightningcss-light,0 22px 60px #16211a14)var(--lightningcss-dark,0 22px 60px #0006);--yii-debug-border:1px solid var(--yii-debug-panel-border);--yii-debug-border-strong:1px solid var(--yii-debug-panel-border-strong);--yii-debug-shadow-sm:0 1px 2px #0f172a0f;--yii-debug-shadow-md:0 10px 24px #0f172a14;--yii-debug-verb-get:var(--lightningcss-light,#0b7285)var(--lightningcss-dark,#4dc4d9);--yii-debug-verb-post:var(--lightningcss-light,#6941c6)var(--lightningcss-dark,#a78bfa);--yii-debug-verb-put:var(--lightningcss-light,#b26a00)var(--lightningcss-dark,#e8a33d);--yii-debug-verb-delete:var(--lightningcss-light,#c93838)var(--lightningcss-dark,#f16a6a);--yii-debug-verb-other:var(--yii-debug-panel-muted);--yii-debug-status-2xx:var(--yii-debug-success);--yii-debug-status-3xx:var(--yii-debug-warning);--yii-debug-status-4xx:var(--lightningcss-light,#c2410c)var(--lightningcss-dark,#fb923c);--yii-debug-status-5xx:var(--yii-debug-danger);--yii-debug-level-error:var(--yii-debug-danger);--yii-debug-level-warning:var(--yii-debug-warning);--yii-debug-level-info:var(--yii-debug-info);--yii-debug-level-trace:var(--yii-debug-panel-muted);--yii-debug-level-profile:var(--lightningcss-light,#9333ea)var(--lightningcss-dark,#b16be8);--yii-debug-level-other:var(--yii-debug-panel-muted);--yii-debug-gauge-fill:var(--yii-debug-panel-primary);--yii-debug-gauge-track:var(--lightningcss-light,#0e7a5529)var(--lightningcss-dark,#2fd08c24);--yii-debug-sql-kw:var(--lightningcss-light,#0a5c40)var(--lightningcss-dark,#4fd39a);--yii-debug-sql-str:var(--lightningcss-light,#8f4e00)var(--lightningcss-dark,#e8a33d);--yii-debug-sql-num:var(--yii-debug-info);--yii-debug-sql-comment:var(--yii-debug-panel-muted);--yii-debug-sql-param:var(--lightningcss-light,#6941c6)var(--lightningcss-dark,#a78bfa);--yii-debug-cat-app:var(--lightningcss-light,#0a6b47)var(--lightningcss-dark,#14b06e);--yii-debug-cat-db:var(--lightningcss-light,#0994b8)var(--lightningcss-dark,#0999be);--yii-debug-cat-view:var(--lightningcss-light,#6941c6)var(--lightningcss-dark,#8f6ce8);--yii-debug-cat-cache:var(--lightningcss-light,#b26a00)var(--lightningcss-dark,#c28300);--yii-debug-cat-mail:var(--lightningcss-light,#b23a67)var(--lightningcss-dark,#d95f8f);--yii-debug-cat-queue:var(--lightningcss-light,#3f4dbd)var(--lightningcss-dark,#6d7ce6);--yii-debug-cat-other:var(--yii-debug-panel-muted)}@media (prefers-color-scheme:dark){:root,:host{--lightningcss-light: ;--lightningcss-dark:initial}}:root[data-yii-debug-theme=light],:host([data-theme=light]){--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}:root[data-yii-debug-theme=dark],:host([data-theme=dark]){--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(../fonts/jetbrains-mono-latin-400-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:500;src:url(../fonts/jetbrains-mono-latin-500-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:700;src:url(../fonts/jetbrains-mono-latin-700-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(../fonts/ibm-plex-sans-latin-400-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(../fonts/ibm-plex-sans-latin-500-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(../fonts/ibm-plex-sans-latin-600-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:700;src:url(../fonts/ibm-plex-sans-latin-700-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}.yii-debug .yii-debug-event-controls{margin-block:var(--yii-debug-space-4);font-size:var(--yii-debug-fs-xs)}.yii-debug .yii-debug-event-groups{align-items:center;gap:var(--yii-debug-space-2);margin-block:var(--yii-debug-space-4);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-event-group{align-items:center;gap:var(--yii-debug-space-2);min-height:var(--yii-debug-control-min-size);padding:var(--yii-debug-space-1) var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface-muted);overflow-wrap:anywhere;display:inline-flex}.yii-debug .yii-debug-event-item>summary{cursor:pointer;list-style:none}.yii-debug .yii-debug-event-item>summary:focus-visible{outline:2px solid var(--yii-debug-panel-primary);outline-offset:4px;border-radius:var(--yii-debug-radius-sm)}.yii-debug :is(.yii-debug-event-clock,.yii-debug-event-identity){gap:var(--yii-debug-space-1);display:grid}.yii-debug .yii-debug-event-clock{min-width:105px}.yii-debug .yii-debug-event-time{white-space:nowrap;color:var(--yii-debug-panel-primary)}.yii-debug .yii-debug-event-identity{overflow-wrap:anywhere;min-width:0}.yii-debug .yii-debug-event-name:before{content:"+ ";color:var(--yii-debug-panel-primary)}.yii-debug .yii-debug-event-item[open] .yii-debug-event-name:before{content:"− "}.yii-debug .yii-debug-event-timing{font-size:var(--yii-debug-fs-xs)}.yii-debug .yii-debug-event-detail-row{display:none}.yii-debug .yii-debug-table tr:has(.yii-debug-event-item[open])+.yii-debug-event-detail-row{display:table-row}.yii-debug .yii-debug-table .yii-debug-event-detail-row>td{padding:0}.yii-debug .yii-debug-event-detail{gap:var(--yii-debug-space-4);padding:var(--yii-debug-space-4);background:var(--yii-debug-panel-surface-muted);white-space:normal;grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.yii-debug .yii-debug-event-detail>div{min-width:0}.yii-debug .yii-debug-event-detail-footer{gap:var(--yii-debug-space-2) var(--yii-debug-space-4);flex-wrap:wrap;grid-column:1/-1;display:flex}.yii-debug .yii-debug-event-metadata{gap:var(--yii-debug-space-2) var(--yii-debug-space-3);grid-template-columns:minmax(75px,.4fr) minmax(0,1fr);display:grid}.yii-debug .yii-debug-event-metadata dt{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-event-metadata dd{margin:0}.yii-debug .yii-debug-event-trace pre{white-space:pre-wrap;overflow-wrap:anywhere}.yii-debug :is(.yii-debug-event-context,.yii-debug-event-trace)>:last-child{margin-bottom:0}@media not (min-width:768px){.yii-debug .yii-debug-event-detail{grid-template-columns:minmax(0,1fr)}}.yii-debug .yii-debug-event-coverage>summary{min-height:var(--yii-debug-control-min-size);padding-block:var(--yii-debug-space-2);cursor:pointer}.yii-debug{background:radial-gradient(circle at top left, var(--yii-debug-panel-bg-accent), transparent 36rem), var(--yii-debug-panel-bg);min-height:100vh;color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-base);margin:0;padding:0;line-height:1.5}.yii-debug *,.yii-debug :before,.yii-debug :after{box-sizing:border-box}.yii-debug h1,.yii-debug h2,.yii-debug h3,.yii-debug h4,.yii-debug h5,.yii-debug h6{margin:0 0 var(--yii-debug-space-3);color:var(--yii-debug-panel-text);letter-spacing:-.01em;font-weight:700;line-height:1.25}.yii-debug h1{font-size:var(--yii-debug-fs-2xl)}.yii-debug h2{font-size:var(--yii-debug-fs-xl)}.yii-debug h3{font-size:var(--yii-debug-fs-lg)}.yii-debug h4,.yii-debug h5,.yii-debug h6{font-size:var(--yii-debug-fs-base)}.yii-debug p,.yii-debug ul,.yii-debug ol,.yii-debug dl,.yii-debug figure{margin:0 0 var(--yii-debug-space-3)}.yii-debug ul,.yii-debug ol{padding-left:var(--yii-debug-space-5)}.yii-debug strong,.yii-debug b{font-weight:600}.yii-debug a{color:var(--yii-debug-panel-link);text-decoration:none;transition:color .12s}.yii-debug a:hover,.yii-debug a:focus{color:var(--yii-debug-panel-link-hover);text-decoration:underline}.yii-debug code,.yii-debug kbd,.yii-debug samp{border-radius:var(--yii-debug-radius-sm);background:var(--yii-debug-panel-code-bg);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-variant-ligatures:none;padding:.1em .35em;font-size:.875em}.yii-debug pre{margin:0 0 var(--yii-debug-space-3);padding:var(--yii-debug-space-3);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-code-bg);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);font-variant-ligatures:none;line-height:1.4;overflow-x:auto}.yii-debug pre code{font-size:inherit;background:0 0;padding:0}.yii-debug :focus-visible{outline:3px solid var(--yii-debug-focus-ring);outline-offset:3px}.yii-debug .yii-debug-page{max-width:1680px;padding:var(--yii-debug-space-4);margin:0 auto}.yii-debug .yii-debug-layout{gap:var(--yii-debug-space-4);grid-template-columns:240px 1fr;align-items:start;display:grid}@media (max-width:959px){.yii-debug .yii-debug-layout{grid-template-columns:1fr}}@media (min-width:960px){.yii-debug .yii-debug-sidebar{top:var(--yii-debug-space-4);max-height:calc(100vh - var(--yii-debug-space-5));position:sticky;overflow-y:auto}}.yii-debug .yii-debug-main{min-width:0;container:yii-debug-main/inline-size}.yii-debug .yii-debug-main>*+:is(h2,.yii-debug-disclosure),.yii-debug .yii-debug-tab-panel>.yii-debug-table-wrap+.yii-debug-disclosure{margin-top:var(--yii-debug-space-5)}.yii-debug .yii-debug-main>.yii-debug-sr-only:first-child+:is(h2,.yii-debug-disclosure){margin-top:0}.yii-debug .yii-debug-mini-toolbar{justify-content:flex-end;align-items:center;gap:var(--yii-debug-space-2);margin-bottom:var(--yii-debug-space-4);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-side-section{gap:var(--yii-debug-space-2);margin:0 0 var(--yii-debug-space-3);padding:var(--yii-debug-space-3);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);flex-direction:column;display:flex}.yii-debug .yii-debug-side-section-title{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;font-weight:700}.yii-debug .yii-debug-side-section-link{color:var(--yii-debug-panel-link);font-size:var(--yii-debug-fs-sm);text-decoration:none}.yii-debug .yii-debug-side-section-link:hover,.yii-debug .yii-debug-side-section-link:focus{color:var(--yii-debug-panel-link-hover);text-decoration:underline}.yii-debug .yii-debug-request-nav-row{width:100%;margin-top:var(--yii-debug-space-2);grid-template-columns:repeat(4,1fr);gap:6px;display:grid}.yii-debug .yii-debug-btn-icon{justify-content:center;align-items:center;padding:4px 0;font-size:16px;line-height:1;display:inline-flex}.yii-debug .yii-debug-btn-icon svg{width:1em;height:1em}.yii-debug .yii-debug-request-nav .yii-debug-btn{justify-content:center;width:100%}.yii-debug .yii-debug-brand-bar{align-items:center;gap:var(--yii-debug-space-2);margin-bottom:var(--yii-debug-space-3);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:linear-gradient(180deg, color-mix(in srgb, var(--yii-debug-panel-surface) 96%, transparent), color-mix(in srgb, var(--yii-debug-panel-surface-muted) 88%, transparent));box-shadow:var(--yii-debug-shadow-sm);flex-wrap:wrap;padding:8px 12px;display:flex}.yii-debug .yii-debug-brand-chip{border:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 70%, transparent);border-radius:var(--yii-debug-radius-pill);background:color-mix(in srgb, var(--yii-debug-panel-surface), transparent 30%);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);min-height:var(--yii-debug-control-min-size);align-items:center;gap:8px;padding:6px 12px;text-decoration:none;transition:border-color .14s,transform .14s,box-shadow .14s;display:inline-flex}.yii-debug a.yii-debug-brand-chip:hover,.yii-debug a.yii-debug-brand-chip:focus{border-color:var(--yii-debug-panel-primary);box-shadow:var(--yii-debug-shadow-sm);text-decoration:none;transform:translateY(-1px)}.yii-debug .yii-debug-brand-icon{flex:none;justify-content:center;align-items:center;width:22px;height:22px;display:inline-flex}.yii-debug .yii-debug-brand-icon svg{width:100%;height:100%}.yii-debug .yii-debug-brand-chip-php .yii-debug-brand-icon{width:32px}.yii-debug .yii-debug-brand-chip-php .yii-debug-brand-icon svg{fill:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-brand-label{color:var(--yii-debug-panel-muted);letter-spacing:.06em;text-transform:uppercase;font-weight:600}.yii-debug .yii-debug-brand-value{color:var(--yii-debug-panel-text);font-weight:700}.yii-debug .yii-debug-brand-chip-mem{margin-left:auto}.yii-debug .yii-debug-brand-chip-config{border:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);text-decoration:none;transition:transform .14s,border-color .14s,color .14s,background-color .14s}.yii-debug .yii-debug-brand-chip-config .yii-debug-brand-icon{justify-content:center;align-items:center;width:14px;height:14px;transition:transform .24s;display:inline-flex}.yii-debug .yii-debug-brand-chip-config .yii-debug-brand-icon svg{width:100%;height:100%}.yii-debug .yii-debug-brand-chip-config:hover,.yii-debug .yii-debug-brand-chip-config:focus-visible{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 50%);color:var(--yii-debug-panel-primary-strong);text-decoration:none;transform:translateY(-1px)}.yii-debug .yii-debug-brand-chip-config:hover .yii-debug-brand-icon,.yii-debug .yii-debug-brand-chip-config:focus-visible .yii-debug-brand-icon{transform:rotate(45deg)}.yii-debug .yii-debug-brand-chip-config.is-disabled{opacity:.5;cursor:default;pointer-events:none}.yii-debug .yii-debug-brand-chip-config{margin-left:auto}.yii-debug .yii-debug-brand-bar:has(.yii-debug-brand-chip-mem) .yii-debug-brand-chip-config{margin-left:0}.yii-debug .yii-debug-brand-chip-theme{border:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);cursor:pointer;padding:6px 10px;transition:transform .14s,border-color .14s,color .14s,background-color .14s}.yii-debug .yii-debug-brand-chip-theme:hover,.yii-debug .yii-debug-brand-chip-theme:focus-visible{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 50%);color:var(--yii-debug-panel-primary-strong);transform:translateY(-1px)}.yii-debug .yii-debug-brand-chip-theme .yii-debug-brand-icon{justify-content:center;align-items:center;width:16px;height:16px;transition:transform .24s;display:inline-flex}.yii-debug .yii-debug-brand-chip-theme .yii-debug-brand-icon svg{width:100%;height:100%}.yii-debug .yii-debug-brand-chip-theme:hover .yii-debug-brand-icon{transform:rotate(20deg)}.yii-debug .yii-debug-snapshot-line{min-width:0;font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);align-items:baseline;gap:6px;display:flex}.yii-debug .yii-debug-snapshot-method{color:color-mix(in srgb, var(--yii-debug-hue,var(--yii-debug-panel-primary-strong)) 80%, var(--yii-debug-panel-text));flex:none;font-weight:700}.yii-debug .yii-debug-snapshot-url{min-width:0;color:var(--yii-debug-panel-text);text-overflow:ellipsis;white-space:nowrap;flex:0 auto;overflow:hidden}.yii-debug .yii-debug-snapshot-meta{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);align-items:center;gap:6px;display:flex}.yii-debug .yii-debug-snapshot-status{border-radius:var(--yii-debug-radius-pill);padding:1px 6px;font-weight:700;display:inline-block}.yii-debug .yii-debug-snapshot-tag{border:1px solid var(--yii-debug-panel-border);border-radius:var(--yii-debug-radius-pill);padding:0 6px}.yii-debug .yii-debug-request-hero{margin-bottom:var(--yii-debug-space-4);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface-muted);box-shadow:var(--yii-debug-shadow-sm)}.yii-debug .yii-debug-request-overview{margin-bottom:var(--yii-debug-space-4);border:var(--yii-debug-border);border-inline-start:4px solid color-mix(in srgb, var(--yii-debug-hue,var(--yii-debug-panel-muted)) 76%, var(--yii-debug-panel-text));border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);overflow:hidden}.yii-debug .yii-debug-request-overview-header{gap:var(--yii-debug-space-4);padding:var(--yii-debug-space-4);grid-template-columns:minmax(0,1fr) auto;align-items:center;display:grid}.yii-debug .yii-debug-request-overview-identity{min-width:0}.yii-debug .yii-debug-request-overview-label,.yii-debug .yii-debug-request-overview-status-label,.yii-debug .yii-debug-panel-heading-eyebrow{color:var(--yii-debug-panel-muted);letter-spacing:.09em;text-transform:uppercase;margin-bottom:5px;font-size:.6875rem;font-weight:700;line-height:1.2;display:block}.yii-debug .yii-debug-request-hero-line{align-items:center;gap:var(--yii-debug-space-2);min-width:0;font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-base);display:flex}.yii-debug .yii-debug-request-hero-method{border-radius:var(--yii-debug-radius-pill);letter-spacing:.04em;flex:none;padding:2px 10px;font-weight:700}.yii-debug .yii-debug-request-hero-url{min-width:0;color:var(--yii-debug-panel-text);text-overflow:ellipsis;white-space:nowrap;flex:auto;overflow:hidden}.yii-debug .yii-debug-request-hero-meta{align-items:center;gap:6px var(--yii-debug-space-3);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);flex-wrap:wrap;margin-top:6px;display:flex}.yii-debug .yii-debug-request-hero-meta:empty{display:none}.yii-debug .yii-debug-request-hero-meta-item{align-items:baseline;gap:var(--yii-debug-space-1);display:inline-flex}.yii-debug .yii-debug-request-hero-meta-label{font-family:var(--yii-debug-font-sans);letter-spacing:.07em;text-transform:uppercase;font-size:.625rem;font-weight:700}.yii-debug .yii-debug-request-hero-meta-value{color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-request-overview-status{text-align:right;min-width:72px}.yii-debug .yii-debug-request-overview-status-value{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-lg);letter-spacing:-.03em;padding:3px 9px;line-height:1.2}.yii-debug .yii-debug-request-overview-metrics{border-block:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);grid-template-columns:minmax(0,1fr) minmax(0,2fr) auto;margin:0;display:grid}.yii-debug .yii-debug-request-overview-metric{min-width:0;padding:var(--yii-debug-space-3) var(--yii-debug-space-4)}.yii-debug .yii-debug-request-overview-metric+:is(.yii-debug .yii-debug-request-overview-metric){border-inline-start:var(--yii-debug-border)}.yii-debug .yii-debug-request-overview-metric dt{color:var(--yii-debug-panel-muted);letter-spacing:.07em;text-transform:uppercase;margin-bottom:2px;font-size:.6875rem;font-weight:700}.yii-debug .yii-debug-request-overview-metric dd{align-items:center;gap:var(--yii-debug-space-2);min-width:0;color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);overflow-wrap:anywhere;flex-wrap:wrap;margin:0;font-weight:600;display:flex}.yii-debug .yii-debug-request-overview-meta{align-items:center;gap:var(--yii-debug-space-2) var(--yii-debug-space-5);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-request-overview-config{align-items:center;gap:var(--yii-debug-space-2);flex-wrap:wrap;margin-inline-start:auto;display:inline-flex}.yii-debug .yii-debug-request-overview-meta-item{flex-direction:column;align-items:flex-start;min-width:0;display:inline-flex}.yii-debug .yii-debug-request-overview-meta-label{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);letter-spacing:.07em;text-transform:uppercase;margin-bottom:2px;font-size:.6875rem;font-weight:700}.yii-debug .yii-debug-request-overview-tag{max-width:34ch;color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.yii-debug .yii-debug-request-overview-flag,.yii-debug .yii-debug-panel-heading-kind{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-pill);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);letter-spacing:.07em;text-transform:uppercase;padding:2px 8px;font-size:.625rem;font-weight:700;line-height:1.4}.yii-debug .yii-debug-request-routing-error{border-width:1px 0 0 4px;border-radius:0;margin:0}.yii-debug .yii-debug-request-routing-error p{margin:0}.yii-debug .yii-debug-request-tabs{min-width:0}.yii-debug .yii-debug-request-tabs>.yii-debug-tabs{scrollbar-width:thin;flex-wrap:nowrap;overflow-x:auto}.yii-debug .yii-debug-request-tabs .yii-debug-tab{flex:none}.yii-debug .yii-debug-request-tabs .yii-debug-empty-state{padding:var(--yii-debug-space-4)}.yii-debug .yii-debug-request-tabs .yii-debug-empty-state h2{font-size:var(--yii-debug-fs-base);margin:0}.yii-debug .yii-debug-diagnostic-shell{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);overflow:hidden}.yii-debug .yii-debug-diagnostic-header{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-4);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border-bottom:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);display:flex}.yii-debug .yii-debug-diagnostic-heading-copy{align-items:baseline;gap:var(--yii-debug-space-2) var(--yii-debug-space-3);flex-wrap:wrap;min-width:0;display:flex}.yii-debug .yii-debug-diagnostic-heading-copy h2{margin:0}.yii-debug .yii-debug-diagnostic-counts{align-items:center;gap:var(--yii-debug-space-2);display:inline-flex}.yii-debug .yii-debug-diagnostic-count,.yii-debug .yii-debug-diagnostic-total,.yii-debug .yii-debug-header-lane-count,.yii-debug .yii-debug-server-group-count{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums}.yii-debug .yii-debug-diagnostic-count{align-items:baseline;gap:var(--yii-debug-space-1);display:inline-flex}.yii-debug .yii-debug-diagnostic-count+.yii-debug-diagnostic-count:before{width:1px;height:12px;background:var(--yii-debug-panel-border-strong);content:"";margin-inline-end:var(--yii-debug-space-1)}.yii-debug .yii-debug-diagnostic-count-value{color:var(--yii-debug-panel-text);font-weight:700}.yii-debug .yii-debug-diagnostic-filter{background:var(--yii-debug-panel-surface);width:min(280px,42%);max-width:none}.yii-debug .yii-debug-header-lanes{grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.yii-debug .yii-debug-header-lane{min-width:0;position:relative}.yii-debug .yii-debug-header-lane+.yii-debug-header-lane{border-inline-start:2px solid color-mix(in srgb, var(--yii-debug-panel-primary) 36%, var(--yii-debug-panel-border))}.yii-debug .yii-debug-header-lane+.yii-debug-header-lane:before{z-index:1;top:25px;border:2px solid var(--yii-debug-panel-surface);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-primary);content:"";width:8px;height:8px;position:absolute;inset-inline-start:-5px}.yii-debug .yii-debug-header-lane-header{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-3);min-height:58px;padding:10px var(--yii-debug-space-4);border-bottom:var(--yii-debug-border);display:flex}.yii-debug .yii-debug-header-lane-header h3{font-size:var(--yii-debug-fs-base);margin:1px 0 0}.yii-debug .yii-debug-header-direction{color:var(--yii-debug-panel-primary-strong);font-family:var(--yii-debug-font-mono);letter-spacing:.04em;font-size:.6875rem;font-weight:700;display:block}.yii-debug .yii-debug-diagnostic-ledger{margin:0}.yii-debug .yii-debug-diagnostic-row{border-top:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 58%, transparent);grid-template-columns:minmax(132px,.72fr) minmax(0,1.28fr);align-items:start;display:grid}.yii-debug .yii-debug-diagnostic-row:first-child{border-top:0}.yii-debug .yii-debug-diagnostic-row:hover{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-diagnostic-row dt,.yii-debug .yii-debug-diagnostic-row dd{min-width:0;padding:10px var(--yii-debug-space-4);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);line-height:1.5}.yii-debug .yii-debug-diagnostic-row dt{color:var(--yii-debug-panel-muted);overflow-wrap:anywhere;font-weight:600;overflow:hidden}.yii-debug .yii-debug-diagnostic-row dd{border-inline-start:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 45%, transparent);color:var(--yii-debug-panel-text);overflow-wrap:anywhere;white-space:pre-wrap;margin:0}.yii-debug .yii-debug-header-ledger .yii-debug-diagnostic-row:nth-child(2n),.yii-debug .yii-debug-server-ledger .yii-debug-diagnostic-row:nth-child(2n){background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 72%)}:is(.yii-debug .yii-debug-header-ledger .yii-debug-diagnostic-row:nth-child(2n),.yii-debug .yii-debug-server-ledger .yii-debug-diagnostic-row:nth-child(2n)):hover{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-diagnostic-values{gap:6px;display:grid}.yii-debug .yii-debug-diagnostic-value-count{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);font-size:.6875rem}.yii-debug .yii-debug-diagnostic-value-list{gap:var(--yii-debug-space-1);margin:0;padding:0;list-style:none;display:grid}.yii-debug .yii-debug-diagnostic-value-list li+li{padding-top:var(--yii-debug-space-1);border-top:1px dashed var(--yii-debug-panel-border)}.yii-debug .yii-debug-diagnostic-empty-value,.yii-debug .yii-debug-diagnostic-lane-empty,.yii-debug .yii-debug-diagnostic-empty-state,.yii-debug .yii-debug-diagnostic-filter-empty{color:var(--yii-debug-panel-muted);font-style:italic}.yii-debug .yii-debug-diagnostic-lane-empty,.yii-debug .yii-debug-diagnostic-empty-state,.yii-debug .yii-debug-diagnostic-filter-empty{padding:var(--yii-debug-space-4);margin:0}.yii-debug .yii-debug-diagnostic-filter-empty{border-top:var(--yii-debug-border);text-align:center}.yii-debug .yii-debug-server-group-body>.yii-debug-mini-toolbar{padding:var(--yii-debug-space-2) var(--yii-debug-space-4);border-bottom:var(--yii-debug-border);margin:0}.yii-debug .yii-debug-server-group-body>.yii-debug-mini-toolbar .yii-debug-filter-input{max-width:100%}.yii-debug .yii-debug-server-additional+.yii-debug-server-group,.yii-debug .yii-debug-server-group+.yii-debug-server-group{border-top:var(--yii-debug-border)}.yii-debug .yii-debug-server-group-summary{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-3);min-height:44px;padding:9px var(--yii-debug-space-4);border-bottom:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);display:flex}.yii-debug .yii-debug-server-group-summary .yii-debug-server-group-title{font-size:var(--yii-debug-fs-sm);margin:0;font-weight:700}.yii-debug .yii-debug-server-group-summary{cursor:pointer;list-style:none}.yii-debug .yii-debug-server-group-summary::-webkit-details-marker{display:none}.yii-debug .yii-debug-server-group-summary:hover,.yii-debug .yii-debug-server-group-summary:focus-visible{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-server-group-identity{align-items:baseline;gap:var(--yii-debug-space-2);display:inline-flex}.yii-debug .yii-debug-server-group:not([open])>.yii-debug-server-group-summary{border-bottom:0}.yii-debug .yii-debug-server-group-summary .yii-debug-disclosure-hint{margin-inline-start:auto}.yii-debug .yii-debug-diagnostic-row[hidden],.yii-debug .yii-debug-header-lane[hidden],.yii-debug .yii-debug-server-group[hidden],.yii-debug .yii-debug-diagnostic-filter-empty[hidden]{display:none}@container yii-debug-main (width<=900px){.yii-debug .yii-debug-header-lanes{grid-template-columns:minmax(0,1fr)}.yii-debug .yii-debug-header-lane+.yii-debug-header-lane{border-top:2px solid color-mix(in srgb, var(--yii-debug-panel-primary) 36%, var(--yii-debug-panel-border));border-inline-start:0}.yii-debug .yii-debug-header-lane+.yii-debug-header-lane:before{top:-5px;inset-inline-start:var(--yii-debug-space-4)}}@container yii-debug-main (width<=560px){.yii-debug .yii-debug-diagnostic-header{flex-direction:column;align-items:stretch}.yii-debug .yii-debug-diagnostic-filter{width:100%}.yii-debug .yii-debug-diagnostic-row{grid-template-columns:minmax(0,1fr)}.yii-debug .yii-debug-diagnostic-row dt{padding-bottom:var(--yii-debug-space-1)}.yii-debug .yii-debug-diagnostic-row dd{border-inline-start:0;padding-top:0}}.yii-debug .yii-debug-route-resolution{margin:var(--yii-debug-space-3) var(--yii-debug-space-4) var(--yii-debug-space-4)}.yii-debug .yii-debug-route-resolution-message{margin:0 0 var(--yii-debug-space-3);color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-sm)}.yii-debug .yii-debug-route-trace{min-width:560px}.yii-debug .yii-debug-table.yii-debug-route-trace thead th,.yii-debug .yii-debug-table.yii-debug-route-trace tbody td{text-align:center}.yii-debug .yii-debug-table.yii-debug-route-trace thead th:nth-child(2),.yii-debug .yii-debug-table.yii-debug-route-trace tbody td:nth-child(2){text-align:start}.yii-debug .yii-debug-table.yii-debug-route-trace thead th:first-child,.yii-debug .yii-debug-table.yii-debug-route-trace tbody td:first-child{width:56px}.yii-debug .yii-debug-table.yii-debug-route-trace thead th:last-child,.yii-debug .yii-debug-table.yii-debug-route-trace tbody td:last-child{width:160px}.yii-debug .yii-debug-panel-heading{justify-content:space-between;align-items:flex-end;gap:var(--yii-debug-space-3);margin:var(--yii-debug-space-5) 0 var(--yii-debug-space-3);padding-bottom:var(--yii-debug-space-2);border-bottom:var(--yii-debug-border);display:flex}.yii-debug .yii-debug-panel-heading-copy{min-width:0}.yii-debug .yii-debug-panel-heading-copy h2{margin:0}.yii-debug .yii-debug-panel-heading-kind{flex:none;margin-bottom:2px}.yii-debug pre.yii-debug-panel-payload{min-height:180px;padding:var(--yii-debug-space-4);border:var(--yii-debug-border);background:linear-gradient(90deg, color-mix(in srgb, var(--yii-debug-panel-primary), transparent 88%), transparent 42%), var(--yii-debug-panel-code-bg);margin-bottom:0;line-height:1.55}@media (max-width:640px){.yii-debug .yii-debug-request-overview-header{gap:var(--yii-debug-space-3);grid-template-columns:minmax(0,1fr)}.yii-debug .yii-debug-request-overview-status{align-items:center;gap:var(--yii-debug-space-2);text-align:left;display:flex}.yii-debug .yii-debug-request-overview-status-label{margin:0}.yii-debug .yii-debug-request-overview-metrics{grid-template-columns:1fr}.yii-debug .yii-debug-request-overview-metric+.yii-debug-request-overview-metric{border-block-start:var(--yii-debug-border);border-inline-start:0}.yii-debug .yii-debug-request-overview-meta-item{max-width:100%}.yii-debug .yii-debug-panel-heading{align-items:flex-start}}.yii-debug .yii-debug-section-header{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-3);margin:var(--yii-debug-space-4) 0 var(--yii-debug-space-2);display:flex}.yii-debug .yii-debug-section-header h2,.yii-debug .yii-debug-section-header h3{margin:0}.yii-debug .yii-debug-filter-input{width:240px;max-width:50%;padding:4px var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font:inherit;flex:none}.yii-debug .yii-debug-filter-input::placeholder{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-filter-input:focus{border-color:var(--yii-debug-panel-primary)}.yii-debug .yii-debug-stack>*+*{margin-top:var(--yii-debug-space-4)}.yii-debug .yii-debug-grid-2{gap:var(--yii-debug-space-4);grid-template-columns:repeat(auto-fit,minmax(280px,1fr));display:grid}.yii-debug .yii-debug-card{padding:var(--yii-debug-panel-padding);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-md)}.yii-debug .yii-debug-nav{padding:var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);margin:0}.yii-debug .yii-debug-nav ul{flex-direction:column;margin:0;padding:0;list-style:none;display:flex}.yii-debug .yii-debug-nav-group{margin-top:var(--yii-debug-space-3)}.yii-debug .yii-debug-nav-group>.yii-debug-nav{box-shadow:none;background:0 0;border:0;border-radius:0;padding:0}.yii-debug .yii-debug-nav-link{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-2);border-radius:var(--yii-debug-radius-md);color:var(--yii-debug-panel-text);padding:8px 12px;font-weight:600;text-decoration:none;transition:background .12s,color .12s;display:flex}.yii-debug .yii-debug-nav-link:after{content:"›";color:var(--yii-debug-panel-muted);font-size:1.1em;line-height:1}.yii-debug .yii-debug-nav-link:hover,.yii-debug .yii-debug-nav-link:focus{background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-text);text-decoration:none}.yii-debug .yii-debug-nav-link.is-active,.yii-debug .yii-debug-nav-link[aria-current=page]{background:var(--yii-debug-primary-bg);color:var(--yii-debug-on-primary)}:is(.yii-debug .yii-debug-nav-link.is-active,.yii-debug .yii-debug-nav-link[aria-current=page]):after{color:inherit}.yii-debug .yii-debug-btn{justify-content:center;align-items:center;gap:var(--yii-debug-space-1);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);min-height:32px;color:var(--yii-debug-panel-text);font-family:inherit;font-size:var(--yii-debug-fs-sm);letter-spacing:0;text-transform:none;cursor:pointer;padding:6px 14px;font-weight:600;line-height:1;text-decoration:none;transition:border-color .12s,background .12s,color .12s,transform .12s,box-shadow .12s;display:inline-flex}.yii-debug .yii-debug-btn:hover,.yii-debug .yii-debug-btn:focus{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 30%);background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-link-hover);text-decoration:none;transform:translateY(-1px)}.yii-debug .yii-debug-btn:active{transform:translateY(0)}.yii-debug .yii-debug-btn[disabled],.yii-debug .yii-debug-btn.is-disabled{opacity:.55;pointer-events:none;transform:none}.yii-debug .yii-debug-btn-primary{background:var(--yii-debug-primary-bg);color:var(--yii-debug-on-primary);border-color:#0000}.yii-debug .yii-debug-btn-primary:is(:hover,:focus){background:var(--yii-debug-panel-primary-strong);color:var(--yii-debug-on-primary)}.yii-debug .yii-debug-btn-ghost{border-color:var(--yii-debug-panel-border);background:0 0}.yii-debug .yii-debug-btn-subtle{background:var(--yii-debug-panel-surface-muted);border-color:#0000}.yii-debug .yii-debug-btn-sm{min-height:28px;font-size:var(--yii-debug-fs-xs);padding:4px 10px}.yii-debug .yii-debug-btn-group{gap:var(--yii-debug-space-1);flex-wrap:wrap;display:inline-flex}.yii-debug .yii-debug-badge{border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-xs);letter-spacing:.01em;align-items:center;padding:2px 10px;font-weight:600;line-height:1.4;display:inline-flex}.yii-debug .yii-debug-badge-success{background:var(--yii-debug-success);color:var(--yii-debug-on-success)}.yii-debug .yii-debug-badge-info{background:var(--yii-debug-info);color:var(--yii-debug-on-info)}.yii-debug .yii-debug-badge-warning{background:var(--yii-debug-warning);color:var(--yii-debug-on-warning)}.yii-debug .yii-debug-badge-danger{background:var(--yii-debug-danger);color:var(--yii-debug-on-danger)}.yii-debug .yii-debug-badge-muted{background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-verb-get{--yii-debug-hue:var(--yii-debug-verb-get)}.yii-debug .yii-debug-verb-post{--yii-debug-hue:var(--yii-debug-verb-post)}.yii-debug .yii-debug-verb-put{--yii-debug-hue:var(--yii-debug-verb-put)}.yii-debug .yii-debug-verb-delete{--yii-debug-hue:var(--yii-debug-verb-delete)}.yii-debug .yii-debug-verb-other{--yii-debug-hue:var(--yii-debug-verb-other)}.yii-debug .yii-debug-status-2xx{--yii-debug-hue:var(--yii-debug-status-2xx)}.yii-debug .yii-debug-status-3xx{--yii-debug-hue:var(--yii-debug-status-3xx)}.yii-debug .yii-debug-status-4xx{--yii-debug-hue:var(--yii-debug-status-4xx)}.yii-debug .yii-debug-status-5xx{--yii-debug-hue:var(--yii-debug-status-5xx)}.yii-debug .yii-debug-status-none{--yii-debug-hue:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-level-error{--yii-debug-hue:var(--yii-debug-level-error)}.yii-debug .yii-debug-level-warning{--yii-debug-hue:var(--yii-debug-level-warning)}.yii-debug .yii-debug-level-info{--yii-debug-hue:var(--yii-debug-level-info)}.yii-debug .yii-debug-level-trace{--yii-debug-hue:var(--yii-debug-level-trace)}.yii-debug .yii-debug-level-profile{--yii-debug-hue:var(--yii-debug-level-profile)}.yii-debug .yii-debug-level-other{--yii-debug-hue:var(--yii-debug-level-other)}.yii-debug .yii-debug-request-hero-method,.yii-debug .yii-debug-snapshot-status,.yii-debug .yii-debug-level-chip,.yii-debug .yii-debug-db-type,.yii-debug .yii-debug-dump-type,.yii-debug .yii-debug-badge:is(.yii-debug-status-2xx,.yii-debug-status-3xx,.yii-debug-status-4xx,.yii-debug-status-5xx,.yii-debug-status-none){border:1px solid color-mix(in srgb, var(--yii-debug-hue,var(--yii-debug-panel-muted)), transparent 65%);background:color-mix(in srgb, var(--yii-debug-hue,var(--yii-debug-panel-muted)), transparent 90%);color:color-mix(in srgb, var(--yii-debug-hue,var(--yii-debug-panel-muted)) 80%, var(--yii-debug-panel-text))}.yii-debug .yii-debug-method{color:color-mix(in srgb, var(--yii-debug-hue,var(--yii-debug-panel-muted)) 80%, var(--yii-debug-panel-text));font-family:var(--yii-debug-font-mono);letter-spacing:.02em;font-weight:700}.yii-debug .yii-debug-level-chip{border-radius:var(--yii-debug-radius-pill);font-family:var(--yii-debug-font-mono);letter-spacing:.04em;text-transform:uppercase;white-space:nowrap;padding:1px 8px;font-size:.7rem;font-weight:700;display:inline-block}.yii-debug .yii-debug-callout{align-items:flex-start;gap:var(--yii-debug-space-2);margin-bottom:var(--yii-debug-space-3);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border:var(--yii-debug-border);border-left:4px solid var(--yii-debug-panel-border-strong);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-text);display:flex}.yii-debug .yii-debug-callout-info{border-left-color:var(--yii-debug-info)}.yii-debug .yii-debug-callout-success{border-left-color:var(--yii-debug-success)}.yii-debug .yii-debug-callout-warning{border-left-color:var(--yii-debug-warning)}.yii-debug .yii-debug-callout-danger{border-left-color:var(--yii-debug-danger)}.yii-debug .yii-debug-table-wrap{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);overflow-x:auto}.yii-debug .yii-debug-table{border-collapse:separate;border-spacing:0;width:100%;min-width:640px;color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);background:0 0;margin:0}.yii-debug .yii-debug-table thead th,.yii-debug .yii-debug-table thead td{border-bottom:var(--yii-debug-border);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 20%);color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);letter-spacing:.03em;text-align:left;text-transform:uppercase;vertical-align:middle;padding:10px 12px;font-weight:700}.yii-debug .yii-debug-table thead th a{color:var(--yii-debug-panel-link);font-weight:700;text-decoration:none}.yii-debug .yii-debug-table thead th a:hover,.yii-debug .yii-debug-table thead th a:focus{color:var(--yii-debug-panel-link-hover);text-decoration:none}.yii-debug .yii-debug-table tbody td,.yii-debug .yii-debug-table tbody th{padding:var(--yii-debug-row-padding-block) 12px;border-top:1px solid var(--yii-debug-panel-border);vertical-align:top;overflow-wrap:anywhere}.yii-debug .yii-debug-table tbody th{white-space:nowrap}.yii-debug .yii-debug-table tbody tr:first-child td,.yii-debug .yii-debug-table tbody tr:first-child th{border-top:0}.yii-debug .yii-debug-table tbody tr:nth-child(2n) td,.yii-debug .yii-debug-table tbody tr:nth-child(2n) th{background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 50%)}.yii-debug .yii-debug-table tbody tr:hover td,.yii-debug .yii-debug-table tbody tr:hover th{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-table tfoot td{border-top:var(--yii-debug-border);color:var(--yii-debug-panel-muted);padding:10px 12px}.yii-debug .yii-debug-row-success td,.yii-debug .yii-debug-row-success th{background:color-mix(in srgb, var(--yii-debug-success), var(--yii-debug-panel-surface) 86%)!important}.yii-debug .yii-debug-row-info td,.yii-debug .yii-debug-row-info th{background:color-mix(in srgb, var(--yii-debug-info), var(--yii-debug-panel-surface) 86%)!important}.yii-debug .yii-debug-row-warning td,.yii-debug .yii-debug-row-warning th{background:color-mix(in srgb, var(--yii-debug-warning), var(--yii-debug-panel-surface) 86%)!important}.yii-debug .yii-debug-row-danger td,.yii-debug .yii-debug-row-danger th{background:color-mix(in srgb, var(--yii-debug-danger), var(--yii-debug-panel-surface) 86%)!important}.yii-debug .yii-debug-grid{margin-bottom:var(--yii-debug-space-4)}.yii-debug .yii-debug-grid>.summary{margin:0 0 var(--yii-debug-space-2);padding:0 var(--yii-debug-space-1);color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-sm)}.yii-debug .yii-debug-grid>.summary b{color:var(--yii-debug-panel-text);font-weight:700}.yii-debug .yii-debug-grid>.empty{padding:var(--yii-debug-space-5);color:var(--yii-debug-panel-muted);text-align:center;font-style:italic}.yii-debug .yii-debug-grid td.yii-debug-cell-nowrap,.yii-debug .yii-debug-grid td.yii-debug-cell-numeric,.yii-debug .yii-debug-grid td.yii-debug-cell-pill,.yii-debug .yii-debug-table td.yii-debug-cell-nowrap,.yii-debug .yii-debug-table td.yii-debug-cell-numeric,.yii-debug .yii-debug-table td.yii-debug-cell-pill{white-space:nowrap}.yii-debug .yii-debug-grid td.yii-debug-cell-fqcn{overflow-wrap:normal}.yii-debug .yii-debug-disclosure{margin-bottom:var(--yii-debug-space-5);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:var(--yii-debug-panel-surface);overflow:hidden}.yii-debug .yii-debug-disclosure[open]>.yii-debug-disclosure-summary{border-bottom:var(--yii-debug-border)}.yii-debug .yii-debug-disclosure-summary{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-3);padding:10px var(--yii-debug-space-4);cursor:pointer;list-style:none;transition:background-color .12s;display:flex}.yii-debug .yii-debug-disclosure-summary::-webkit-details-marker{display:none}.yii-debug .yii-debug-disclosure-summary:hover,.yii-debug .yii-debug-disclosure-summary:focus-visible{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-disclosure-title,.yii-debug .yii-debug-request-section-title,.yii-debug .yii-debug-diagnostic-heading-copy h2{color:var(--yii-debug-panel-primary-strong);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.12em;text-transform:uppercase;font-weight:700}.yii-debug .yii-debug-disclosure-hint{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;transition:color .12s}.yii-debug .yii-debug-disclosure-hint [data-yii-debug-hint=expanded]{display:none}.yii-debug .yii-debug-disclosure-summary:hover .yii-debug-disclosure-hint,.yii-debug .yii-debug-disclosure-summary:focus-visible .yii-debug-disclosure-hint{color:var(--yii-debug-panel-link)}.yii-debug details[open]>summary .yii-debug-disclosure-hint [data-yii-debug-hint=collapsed]{display:none}.yii-debug details[open]>summary .yii-debug-disclosure-hint [data-yii-debug-hint=expanded]{display:inline}.yii-debug .yii-debug-disclosure-body{padding:var(--yii-debug-space-4)}.yii-debug .yii-debug-disclosure-body>pre{overflow-wrap:anywhere;color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);white-space:pre-wrap;margin:0;line-height:1.6}.yii-debug .yii-debug-cell-more-body{max-height:156px;position:relative;overflow:hidden}.yii-debug .yii-debug-cell-more-body:has(.yii-debug-table){max-height:420px}.yii-debug .yii-debug-cell-more-body:after{content:"";background:linear-gradient(to bottom, transparent, var(--yii-debug-panel-surface));pointer-events:none;height:32px;position:absolute;inset:auto 0 0}.yii-debug .yii-debug-cell-more.is-open .yii-debug-cell-more-body{max-height:none}.yii-debug .yii-debug-cell-more.is-open .yii-debug-cell-more-body:after{content:none}.yii-debug .yii-debug-cell-more-toggle{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);color:var(--yii-debug-panel-link);font-family:inherit;font-size:var(--yii-debug-fs-xs);cursor:pointer;background:0 0;align-items:center;margin-top:6px;padding:2px 10px;font-weight:600;display:inline-flex}.yii-debug .yii-debug-cell-more-toggle:before{content:"";width:1.25em;height:1.25em;margin-right:var(--yii-debug-space-1);background:linear-gradient(currentcolor, currentcolor) center / 8px 2px no-repeat, linear-gradient(currentcolor, currentcolor) center / 2px 8px no-repeat, var(--yii-debug-panel-bg-accent);border-radius:50%;display:inline-block}.yii-debug .yii-debug-cell-more.is-open .yii-debug-cell-more-toggle:before{background:linear-gradient(currentcolor, currentcolor) center / 8px 2px no-repeat, var(--yii-debug-panel-bg-accent)}.yii-debug .yii-debug-cell-more-toggle:hover,.yii-debug .yii-debug-cell-more-toggle:focus{background:var(--yii-debug-panel-bg-accent);border-color:var(--yii-debug-panel-primary);text-decoration:none}.yii-debug .yii-debug-grid td.yii-debug-cell-mono{font-family:var(--yii-debug-font-mono)}.yii-debug td.yii-debug-cell-payload{overflow-wrap:anywhere;width:100%}.yii-debug .yii-debug-grid td.yii-debug-cell-numeric{font-variant-numeric:tabular-nums;text-align:right}.yii-debug a.asc:after,.yii-debug a.desc:after,.yii-debug .yii-debug-sort-asc:after,.yii-debug .yii-debug-sort-desc:after{content:"↑";opacity:.78;margin-left:4px;font-size:.85em}.yii-debug a.desc:after,.yii-debug .yii-debug-sort-desc:after{content:"↓"}.yii-debug .yii-debug-pager{gap:var(--yii-debug-space-1);margin:var(--yii-debug-space-3) 0 0;flex-wrap:wrap;padding:0;list-style:none;display:flex}.yii-debug .yii-debug-pager-item{display:inline-flex}.yii-debug .yii-debug-pager-link,.yii-debug .yii-debug-pager>li>:is(a,span){border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);min-width:40px;min-height:40px;color:var(--yii-debug-panel-link);font-size:var(--yii-debug-fs-sm);justify-content:center;align-items:center;padding:0 10px;font-weight:600;text-decoration:none;transition:border-color .12s,background .12s,color .12s;display:inline-flex}:is(.yii-debug .yii-debug-pager-link,.yii-debug .yii-debug-pager>li>:is(a,span)):hover,:is(.yii-debug .yii-debug-pager-link,.yii-debug .yii-debug-pager>li>:is(a,span)):focus{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 30%);background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-link-hover);text-decoration:none}.yii-debug .yii-debug-pager-item.is-active .yii-debug-pager-link{background:var(--yii-debug-primary-bg);color:var(--yii-debug-on-primary);cursor:default;border-color:#0000}.yii-debug .yii-debug-pager-item.is-disabled .yii-debug-pager-link{background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);cursor:not-allowed;pointer-events:none}.yii-debug .yii-debug-field{margin-bottom:var(--yii-debug-space-3);flex-direction:column;gap:4px;display:flex}.yii-debug .yii-debug-field-grid{gap:var(--yii-debug-space-3) var(--yii-debug-space-4);margin-bottom:var(--yii-debug-space-3);grid-template-columns:repeat(auto-fit,minmax(240px,1fr));display:grid}.yii-debug .yii-debug-label{color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);font-weight:600}.yii-debug .yii-debug-input,.yii-debug .yii-debug-select,.yii-debug .yii-debug-textarea{border:1px solid var(--yii-debug-panel-border-strong);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);width:100%;min-height:32px;color:var(--yii-debug-panel-text);font-family:inherit;font-size:var(--yii-debug-fs-sm);padding:6px 10px;transition:border-color .12s,box-shadow .12s}:is(.yii-debug .yii-debug-input,.yii-debug .yii-debug-select,.yii-debug .yii-debug-textarea):hover{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), var(--yii-debug-panel-border-strong) 60%)}:is(.yii-debug .yii-debug-input,.yii-debug .yii-debug-select,.yii-debug .yii-debug-textarea):focus{border-color:var(--yii-debug-panel-primary);box-shadow:0 0 0 3px color-mix(in srgb, var(--yii-debug-panel-primary), transparent 82%)}:is(.yii-debug .yii-debug-input,.yii-debug .yii-debug-select,.yii-debug .yii-debug-textarea)::placeholder{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-textarea{resize:vertical;min-height:96px;line-height:1.45}.yii-debug .yii-debug-grid .filters td{border-bottom:var(--yii-debug-border);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 30%);padding:6px 12px}.yii-debug .yii-debug-grid .filters input,.yii-debug .yii-debug-grid .filters select,.yii-debug .yii-debug-grid .filters textarea{border:1px solid var(--yii-debug-panel-border-strong);border-radius:var(--yii-debug-radius-sm);background:var(--yii-debug-panel-surface);width:100%;min-width:0;min-height:28px;color:var(--yii-debug-panel-text);font:inherit;font-size:var(--yii-debug-fs-xs);padding:4px 8px}:is(.yii-debug .yii-debug-grid .filters input,.yii-debug .yii-debug-grid .filters select,.yii-debug .yii-debug-grid .filters textarea):focus{border-color:var(--yii-debug-panel-primary);box-shadow:0 0 0 2px color-mix(in srgb, var(--yii-debug-panel-primary), transparent 84%)}.yii-debug .yii-debug-tabs{gap:var(--yii-debug-space-1);margin:0 0 var(--yii-debug-space-3);border-bottom:var(--yii-debug-border);flex-wrap:wrap;padding:0;list-style:none;display:flex}.yii-debug .yii-debug-tab{display:inline-flex}.yii-debug .yii-debug-tab-link{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-sm);border-bottom:2px solid #0000;align-items:center;padding:8px 14px;font-weight:600;text-decoration:none;transition:color .12s,border-color .12s;display:inline-flex}.yii-debug .yii-debug-tab-link:hover,.yii-debug .yii-debug-tab-link:focus{color:var(--yii-debug-panel-text);text-decoration:none}.yii-debug .yii-debug-tab.is-active .yii-debug-tab-link,.yii-debug .yii-debug-tab-link.is-active{border-bottom-color:var(--yii-debug-panel-primary);color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-tab-panel{padding:var(--yii-debug-space-3) 0 0;display:none}.yii-debug .yii-debug-tab-panel.is-active{display:block}.yii-debug .yii-debug-collapsible{margin-top:var(--yii-debug-space-3);display:none}.yii-debug .yii-debug-collapsible.is-open{display:block}.yii-debug .yii-debug-dropdown{display:inline-block;position:relative}.yii-debug .yii-debug-dropdown-menu{z-index:100;border:var(--yii-debug-border-strong);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);min-width:220px;box-shadow:var(--yii-debug-shadow-md);margin:0;padding:4px;list-style:none;display:none;position:absolute;top:calc(100% + 4px);left:0}.yii-debug .yii-debug-dropdown.is-open .yii-debug-dropdown-menu{display:block}.yii-debug .yii-debug-dropdown-item{border-radius:var(--yii-debug-radius-sm);color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);padding:6px 10px;text-decoration:none;display:block}.yii-debug .yii-debug-dropdown-item:hover,.yii-debug .yii-debug-dropdown-item:focus{background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-link-hover);text-decoration:none}.yii-debug .yii-debug-since-previous{white-space:nowrap;align-items:center;gap:4px;display:inline-flex}.yii-debug .yii-debug-since-previous .yii-debug-since-previous-btn{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-sm);background:var(--yii-debug-panel-surface);min-width:24px;height:24px;color:var(--yii-debug-panel-link);font-size:var(--yii-debug-fs-xs);justify-content:center;align-items:center;padding:0 6px;font-weight:700;text-decoration:none;display:inline-flex}.yii-debug .yii-debug-table-mono{min-width:0}.yii-debug .yii-debug-table-mono thead th{z-index:1;background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 10%);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xs);letter-spacing:.12em;padding:8px 16px;position:sticky;top:0}.yii-debug .yii-debug-table-mono tbody tr{transition:background-color .14s}.yii-debug .yii-debug-table-mono tbody tr:hover td,.yii-debug .yii-debug-table-mono tbody tr:hover th{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-table-mono tbody tr:hover th{box-shadow:inset 3px 0 0 var(--yii-debug-panel-primary);color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-table-mono tbody th,.yii-debug .yii-debug-table-mono tbody td{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);padding:12px 16px;line-height:1.55}.yii-debug .yii-debug-table-mono tbody th{width:1%;min-width:120px;max-width:220px;color:var(--yii-debug-panel-muted);letter-spacing:.02em;white-space:nowrap;text-overflow:ellipsis;font-weight:500;transition:color .14s,box-shadow .14s;overflow:hidden}.yii-debug .yii-debug-table-mono tbody td{color:var(--yii-debug-panel-text);word-break:normal;overflow-wrap:anywhere}.yii-debug .yii-debug-table-mono tbody tr+tr td,.yii-debug .yii-debug-table-mono tbody tr+tr th{border-top:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 55%, transparent)}.yii-debug .yii-debug-table-mono tbody tr:nth-child(2n) td,.yii-debug .yii-debug-table-mono tbody tr:nth-child(2n) th{background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 70%)}.yii-debug .yii-debug-table-vite-overview tbody th,.yii-debug .yii-debug-table-overview tbody th{width:200px;min-width:160px;max-width:200px}.yii-debug .yii-debug-vite-component+.yii-debug-vite-component,.yii-debug .yii-debug-panel-group+.yii-debug-panel-group{margin-top:var(--yii-debug-space-6)}.yii-debug .yii-debug-table-wrap:has(>.yii-debug-table-mono){border-radius:var(--yii-debug-radius-md);box-shadow:var(--yii-debug-shadow-sm), inset 0 0 0 1px color-mix(in srgb, var(--yii-debug-panel-border) 60%, transparent);overflow:clip}.yii-debug .yii-debug-table-mono{min-width:0}.yii-debug .yii-debug-table-mono tbody th{overflow-wrap:anywhere;white-space:normal}.yii-debug .yii-debug-table-userswitch{width:auto;min-width:100%}.yii-debug .yii-debug-table-userswitch thead th,.yii-debug .yii-debug-table-userswitch tbody td,.yii-debug .yii-debug-table-userswitch tbody th{overflow-wrap:normal;white-space:nowrap}.yii-debug .yii-debug-table-userswitch tbody tr.filters td input{width:100%;min-width:0}.yii-debug .yii-debug-col-userswitch-id{font-variant-numeric:tabular-nums;min-width:10ch}.yii-debug .yii-debug-col-userswitch-username{min-width:14ch}.yii-debug .yii-debug-col-userswitch-email{min-width:24ch}.yii-debug .yii-debug-col-userswitch-status{text-align:center;min-width:8ch}.yii-debug .yii-debug-col-userswitch-timestamp{font-variant-numeric:tabular-nums;min-width:18ch}.yii-debug .yii-debug-table-pointer tbody tr{cursor:pointer;transition:background-color .12s}.yii-debug .yii-debug-table-pointer tbody tr:hover{background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 86%)}.yii-debug .yii-debug-timeline-duration{margin:0 0 var(--yii-debug-space-3);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);letter-spacing:.04em}.yii-debug .yii-debug-user{gap:var(--yii-debug-space-3);flex-direction:column;display:flex}.yii-debug .yii-debug-user-card{gap:var(--yii-debug-space-4);padding:var(--yii-debug-space-4) var(--yii-debug-space-5);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-lg);background:radial-gradient(circle at top right, color-mix(in srgb, var(--yii-debug-panel-primary), transparent 80%), transparent 60%), var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);grid-template-columns:auto 1fr;align-items:center;display:grid;position:relative;overflow:hidden}.yii-debug .yii-debug-user-avatar{background:linear-gradient(135deg, var(--yii-debug-panel-primary), var(--yii-debug-info));width:64px;height:64px;box-shadow:0 4px 12px color-mix(in srgb, var(--yii-debug-panel-primary), transparent 50%), inset 0 1px 0 #ffffff38;color:#fff;font-family:var(--yii-debug-font-mono);letter-spacing:-.02em;border-radius:50%;flex:none;justify-content:center;align-items:center;font-size:28px;font-weight:700;display:inline-flex}.yii-debug .yii-debug-user-meta{flex-direction:column;gap:4px;min-width:0;display:flex}.yii-debug .yii-debug-user-name{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xl);letter-spacing:-.01em;margin:0;font-weight:700;line-height:1.2}.yii-debug .yii-debug-user-handle{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);margin:0}.yii-debug .yii-debug-user-tags{flex-wrap:wrap;gap:8px;margin-top:6px;display:flex}.yii-debug .yii-debug-user-status{border-radius:var(--yii-debug-radius-pill);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;align-items:center;gap:6px;padding:4px 12px 4px 8px;font-weight:700;display:inline-flex}.yii-debug .yii-debug-user-status:before{content:"";background:currentColor;border-radius:50%;width:7px;height:7px}.yii-debug .yii-debug-user-status-success{background:color-mix(in srgb, var(--yii-debug-success), transparent 84%);color:var(--yii-debug-success)}.yii-debug .yii-debug-user-status-success:before{box-shadow:0 0 0 0 color-mix(in srgb, var(--yii-debug-success), transparent 30%);animation:2.4s ease-in-out infinite yii-debug-user-pulse}.yii-debug .yii-debug-user-status-danger{background:color-mix(in srgb, var(--yii-debug-danger), transparent 84%);color:var(--yii-debug-danger)}.yii-debug .yii-debug-user-status-warning{background:color-mix(in srgb, var(--yii-debug-warning), transparent 84%);color:var(--yii-debug-warning)}.yii-debug .yii-debug-user-status-muted{background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-user-pill{border:1px solid var(--yii-debug-panel-border);border-radius:var(--yii-debug-radius-pill);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;align-items:center;padding:4px 10px;display:inline-flex}.yii-debug .yii-debug-user-section{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);overflow:hidden}.yii-debug .yii-debug-user-section>header{padding:10px var(--yii-debug-space-3);border-bottom:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;align-items:center;gap:10px;font-weight:700;display:flex}.yii-debug .yii-debug-user-section dl{margin:0;padding:0}.yii-debug .yii-debug-user-section-icon{width:18px;height:18px;color:var(--yii-debug-panel-primary);justify-content:center;align-items:center;display:inline-flex}.yii-debug .yii-debug-user-section-icon svg{width:100%;height:100%}.yii-debug .yii-debug-user-row{gap:var(--yii-debug-space-3);padding:12px var(--yii-debug-space-3);border-bottom:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 50%, transparent);grid-template-columns:minmax(140px,22%) 1fr;display:grid}.yii-debug .yii-debug-user-row:last-child{border-bottom:0}.yii-debug .yii-debug-user-row dt{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.02em;align-self:center}.yii-debug .yii-debug-user-row dd{min-width:0;margin:0}.yii-debug .yii-debug-user-value{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);word-break:break-all}.yii-debug .yii-debug-user-empty{color:color-mix(in srgb, var(--yii-debug-panel-muted), transparent 35%);font-family:var(--yii-debug-font-mono)}.yii-debug .yii-debug-user-time{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);align-items:baseline;gap:10px;display:inline-flex}.yii-debug .yii-debug-user-time .yii-debug-user-time-rel{color:var(--yii-debug-panel-text);font-weight:600}.yii-debug .yii-debug-user-time .yii-debug-user-time-abs{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs)}.yii-debug .yii-debug-user-reveal{border:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 80%, transparent);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-text);font:inherit;cursor:pointer;text-align:left;align-items:center;gap:12px;padding:6px 6px 6px 14px;transition:border-color .14s,background .14s;display:inline-flex}.yii-debug .yii-debug-user-reveal:hover,.yii-debug .yii-debug-user-reveal:focus{border-color:var(--yii-debug-panel-primary)}.yii-debug .yii-debug-user-reveal .yii-debug-user-mask,.yii-debug .yii-debug-user-reveal .yii-debug-user-real{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm)}.yii-debug .yii-debug-user-reveal .yii-debug-user-mask{letter-spacing:.1em;color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-user-reveal .yii-debug-user-real{overflow-wrap:anywhere;max-width:60ch;color:var(--yii-debug-panel-text);display:none}.yii-debug .yii-debug-user-reveal .yii-debug-user-reveal-cta{border-radius:var(--yii-debug-radius-pill);background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 80%);color:var(--yii-debug-panel-primary);font-size:var(--yii-debug-fs-xs);letter-spacing:.08em;text-transform:uppercase;flex:none;padding:3px 10px;font-weight:700}.yii-debug .yii-debug-user-reveal .yii-debug-user-reveal-cta:before{content:"Reveal"}.yii-debug .yii-debug-user-reveal.is-revealed .yii-debug-user-mask{display:none}.yii-debug .yii-debug-user-reveal.is-revealed .yii-debug-user-real{display:inline}.yii-debug .yii-debug-user-reveal.is-revealed .yii-debug-user-reveal-cta{background:color-mix(in srgb, var(--yii-debug-warning), transparent 84%);color:var(--yii-debug-warning)}.yii-debug .yii-debug-user-reveal.is-revealed .yii-debug-user-reveal-cta:before{content:"Hide"}.yii-debug .yii-debug-sr-only{clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.yii-debug .yii-debug-table-empty{padding:var(--yii-debug-space-4);border:1px dashed color-mix(in srgb, var(--yii-debug-panel-border) 70%, transparent);border-radius:var(--yii-debug-radius-md);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 40%);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;text-transform:uppercase;justify-content:center;align-items:center;gap:10px;margin:0;display:flex}.yii-debug .yii-debug-table-empty:before{content:"";opacity:.5;border:1.5px solid;border-radius:50%;width:8px;height:8px}.yii-debug .yii-debug-grid-summary{align-items:center;gap:var(--yii-debug-space-3);margin-bottom:var(--yii-debug-space-3);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);letter-spacing:.06em;text-transform:uppercase;flex-wrap:wrap;display:flex}.yii-debug .yii-debug-grid-summary strong{color:var(--yii-debug-panel-primary-strong);letter-spacing:0;text-transform:none;margin-right:.35em;font-weight:700}.yii-debug .yii-debug-grid-summary-sep{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-grid-summary-stat-warn{--yii-debug-hue:var(--yii-debug-level-warning)}.yii-debug .yii-debug-grid-summary-stat-danger{--yii-debug-hue:var(--yii-debug-level-error)}.yii-debug .yii-debug-grid-summary-stat-info{--yii-debug-hue:var(--yii-debug-level-info)}.yii-debug .yii-debug-grid-summary-stat-trace{--yii-debug-hue:var(--yii-debug-level-trace)}.yii-debug .yii-debug-grid-summary-stat-warn strong{color:var(--yii-debug-warning)}.yii-debug .yii-debug-grid-summary-stat-danger strong{color:var(--yii-debug-danger)}.yii-debug .yii-debug-grid-summary-stat-2xx{--yii-debug-hue:var(--yii-debug-status-2xx)}.yii-debug .yii-debug-grid-summary-stat-3xx{--yii-debug-hue:var(--yii-debug-status-3xx)}.yii-debug .yii-debug-grid-summary-stat-4xx{--yii-debug-hue:var(--yii-debug-status-4xx)}.yii-debug .yii-debug-grid-summary-stat-5xx{--yii-debug-hue:var(--yii-debug-status-5xx)}.yii-debug a:is(.yii-debug-grid-summary-stat-danger,.yii-debug-grid-summary-stat-warn,.yii-debug-grid-summary-stat-info,.yii-debug-grid-summary-stat-trace,.yii-debug-grid-summary-stat-2xx,.yii-debug-grid-summary-stat-3xx,.yii-debug-grid-summary-stat-4xx,.yii-debug-grid-summary-stat-5xx){border:1px solid color-mix(in srgb, var(--yii-debug-hue), transparent 65%);border-radius:var(--yii-debug-radius-pill);background:color-mix(in srgb, var(--yii-debug-hue), transparent 90%);color:color-mix(in srgb, var(--yii-debug-hue) 80%, var(--yii-debug-panel-text));align-items:center;gap:6px;padding:2px 10px;text-decoration:none;transition:transform .14s,background-color .14s;display:inline-flex}.yii-debug a:is(.yii-debug-grid-summary-stat-danger,.yii-debug-grid-summary-stat-warn,.yii-debug-grid-summary-stat-info,.yii-debug-grid-summary-stat-trace,.yii-debug-grid-summary-stat-2xx,.yii-debug-grid-summary-stat-3xx,.yii-debug-grid-summary-stat-4xx,.yii-debug-grid-summary-stat-5xx) strong{color:inherit;margin-right:0}.yii-debug a:is(.yii-debug-grid-summary-stat-danger,.yii-debug-grid-summary-stat-warn,.yii-debug-grid-summary-stat-info,.yii-debug-grid-summary-stat-trace,.yii-debug-grid-summary-stat-2xx,.yii-debug-grid-summary-stat-3xx,.yii-debug-grid-summary-stat-4xx,.yii-debug-grid-summary-stat-5xx):hover,.yii-debug a:is(.yii-debug-grid-summary-stat-danger,.yii-debug-grid-summary-stat-warn,.yii-debug-grid-summary-stat-info,.yii-debug-grid-summary-stat-trace,.yii-debug-grid-summary-stat-2xx,.yii-debug-grid-summary-stat-3xx,.yii-debug-grid-summary-stat-4xx,.yii-debug-grid-summary-stat-5xx):focus-visible{background:color-mix(in srgb, var(--yii-debug-hue), transparent 80%);text-decoration:none;transform:translateY(-1px)}.yii-debug .yii-debug-grid-summary-action{color:var(--yii-debug-panel-primary-strong);letter-spacing:0;text-transform:none;margin-left:auto;font-weight:700;text-decoration:none}.yii-debug .yii-debug-grid-summary-action:hover,.yii-debug .yii-debug-grid-summary-action:focus-visible{color:var(--yii-debug-panel-link-hover);text-decoration:underline}.yii-debug .yii-debug-grid-footer{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-3);margin-top:var(--yii-debug-space-2);color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-grid-count{margin:0}.yii-debug .yii-debug-grid-pagesize{align-items:center;gap:6px;margin-left:auto;display:inline-flex}.yii-debug .yii-debug-grid-pagesize-label{color:var(--yii-debug-panel-muted);letter-spacing:.06em;text-transform:uppercase;font-weight:600}.yii-debug .yii-debug-grid-pagesize-select{padding:3px var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-sm);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-sm);letter-spacing:0;text-transform:none;cursor:pointer}.yii-debug .yii-debug-grid-pagesize-select:focus{border-color:var(--yii-debug-panel-primary)}.yii-debug .yii-debug-db-type{border-radius:var(--yii-debug-radius-pill);min-width:5em;font-family:var(--yii-debug-font-mono);letter-spacing:.04em;text-align:center;text-transform:uppercase;white-space:nowrap;padding:2px 8px;font-size:.7rem;font-weight:700;display:inline-block}.yii-debug .yii-debug-db-sql{font-family:var(--yii-debug-font-mono);overflow-wrap:anywhere}.yii-debug .yii-debug-grid-db .yii-debug-table th:not(:last-child),.yii-debug .yii-debug-grid-db .yii-debug-table td:not(:last-child){white-space:nowrap}.yii-debug .yii-debug-grid-db .yii-debug-table th:last-child,.yii-debug .yii-debug-grid-db .yii-debug-table td:last-child{width:100%}.yii-debug .yii-debug-grid-db .filters select{min-width:max-content}.yii-debug .yii-debug-grid-db .filters select:focus{box-shadow:none}.yii-debug .yii-debug-grid-db .filters select:focus-visible{outline-offset:1px;outline-width:2px}.yii-debug .yii-debug-sql-kw{color:var(--yii-debug-sql-kw);font-weight:700}.yii-debug .yii-debug-sql-str{color:var(--yii-debug-sql-str)}.yii-debug .yii-debug-sql-num{color:var(--yii-debug-sql-num)}.yii-debug .yii-debug-sql-comment{color:var(--yii-debug-sql-comment);font-style:italic}.yii-debug .yii-debug-sql-param{color:var(--yii-debug-sql-param);font-weight:600}.yii-debug .yii-debug-db-explain,.yii-debug .yii-debug-db-trace{margin-top:var(--yii-debug-space-2)}.yii-debug .yii-debug-db-explain-toggle,.yii-debug .yii-debug-db-trace-toggle{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);color:var(--yii-debug-panel-link);font-size:var(--yii-debug-fs-xs);cursor:pointer;background:0 0;align-items:center;gap:6px;padding:2px 10px 2px 6px;font-weight:600;text-decoration:none;transition:background-color .12s,border-color .12s;display:inline-flex}.yii-debug .yii-debug-db-explain-toggle:hover,.yii-debug .yii-debug-db-explain-toggle:focus,.yii-debug .yii-debug-db-trace-toggle:hover,.yii-debug .yii-debug-db-trace-toggle:focus-visible{background:var(--yii-debug-panel-bg-accent);border-color:var(--yii-debug-panel-primary);text-decoration:none}.yii-debug .yii-debug-db-trace-toggle{list-style:none}.yii-debug .yii-debug-db-trace-toggle::-webkit-details-marker{display:none}.yii-debug .yii-debug-db-explain-chevron,.yii-debug .yii-debug-db-trace-chevron{width:10px;font-size:14px;line-height:1;transition:transform .16s;display:inline-block}.yii-debug .yii-debug-db-explain.is-open .yii-debug-db-explain-chevron,.yii-debug .yii-debug-db-trace[open]>.yii-debug-db-trace-toggle .yii-debug-db-trace-chevron{transform:rotate(90deg)}.yii-debug .yii-debug-db-explain.is-loading .yii-debug-db-explain-toggle{opacity:.55;pointer-events:none}.yii-debug .yii-debug-db-explain-text{margin-top:var(--yii-debug-space-2);display:none}.yii-debug .yii-debug-db-explain.is-open .yii-debug-db-explain-text{display:block}.yii-debug .yii-debug-db-explain-text.is-error{padding:var(--yii-debug-space-2) var(--yii-debug-space-3);border-left:3px solid var(--yii-debug-danger);border-radius:var(--yii-debug-radius-sm);background:color-mix(in srgb, var(--yii-debug-danger), transparent 92%);color:var(--yii-debug-danger);font-size:var(--yii-debug-fs-sm);font-weight:600;display:block}.yii-debug .yii-debug-db-explain-all{margin-top:var(--yii-debug-space-3);justify-content:flex-end;display:flex}.yii-debug .yii-debug-db-explain-all-toggle{white-space:nowrap;justify-content:center;min-width:104px}.yii-debug .yii-debug-explain{margin-top:var(--yii-debug-space-3)}.yii-debug .yii-debug-explain-title{margin-bottom:var(--yii-debug-space-2);font-size:var(--yii-debug-fs-lg);font-weight:700}.yii-debug .yii-debug-explain-query{margin-bottom:var(--yii-debug-space-3);padding:var(--yii-debug-space-3);background:var(--yii-debug-panel-code-bg);border-radius:var(--yii-debug-radius-md);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);white-space:pre-wrap;overflow-wrap:break-word}.yii-debug .yii-debug-explain-empty{color:var(--yii-debug-panel-muted);font-style:italic}.yii-debug .yii-debug-empty-state{margin:var(--yii-debug-space-4) 0 0;padding:var(--yii-debug-space-5) var(--yii-debug-space-5);border:1px dashed color-mix(in srgb, var(--yii-debug-panel-border) 80%, transparent);border-radius:var(--yii-debug-radius-md);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 30%)}.yii-debug .yii-debug-empty-state h2{margin:0 0 var(--yii-debug-space-3);color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-lg);font-weight:700}.yii-debug .yii-debug-empty-state p{margin:0 0 var(--yii-debug-space-3);color:var(--yii-debug-panel-muted);line-height:1.5}.yii-debug .yii-debug-empty-state p:last-child{margin-bottom:0}.yii-debug .yii-debug-empty-state code{border-radius:var(--yii-debug-radius-sm,4px);background:var(--yii-debug-panel-code-bg);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);padding:.1em .4em}.yii-debug .yii-debug-empty-state-code{margin:0 0 var(--yii-debug-space-3);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-code-bg);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);white-space:pre-wrap;overflow-wrap:break-word;line-height:1.5}.yii-debug .yii-debug-queue-grid-job{flex-direction:column;gap:2px;line-height:1.2;display:flex}.yii-debug .yii-debug-queue-grid-job-link{color:var(--yii-debug-text-strong);white-space:nowrap;font-weight:600;text-decoration:none}.yii-debug .yii-debug-queue-grid-job-link:hover{text-decoration:underline}.yii-debug .yii-debug-queue-grid-job-namespace{color:var(--yii-debug-text-muted);font-family:var(--yii-debug-font-mono);white-space:nowrap;font-size:.78rem}.yii-debug .yii-debug-grid-queue .yii-debug-queue-status,.yii-debug .yii-debug-grid-queue .yii-debug-queue-driver{white-space:nowrap}.yii-debug .yii-debug-queue-job-page{gap:var(--yii-debug-space-3);flex-direction:column;display:flex}.yii-debug .yii-debug-queue-job-head{align-items:center;gap:var(--yii-debug-space-2);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-queue-card{gap:var(--yii-debug-space-2);padding:var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);flex-direction:column;display:flex}.yii-debug .yii-debug-queue-card-head{gap:var(--yii-debug-space-3);grid-template-columns:auto 1fr auto;align-items:flex-start;display:grid}.yii-debug .yii-debug-queue-avatar{--queue-hue:210;border-radius:var(--yii-debug-radius-md);background:linear-gradient(135deg, hsl(var(--queue-hue) 72% 52%), hsl(calc(var(--queue-hue) + 28) 65% 40%));width:40px;height:40px;box-shadow:0 2px 10px hsl(var(--queue-hue) 60% 40% / 32%), inset 0 1px 0 #ffffff38;color:#fff;font-family:var(--yii-debug-font-mono);letter-spacing:-.01em;flex:none;justify-content:center;align-items:center;font-size:16px;font-weight:800;display:inline-flex}.yii-debug .yii-debug-queue-headline{flex-direction:column;gap:2px;min-width:0;display:flex}.yii-debug .yii-debug-queue-class{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-lg);letter-spacing:-.01em;word-break:break-all;margin:0;font-weight:700;line-height:1.25}.yii-debug .yii-debug-queue-namespace{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.02em}.yii-debug .yii-debug-queue-meta-pills{flex-direction:column;flex:none;align-items:flex-end;gap:6px;display:flex}.yii-debug .yii-debug-queue-status{border-radius:var(--yii-debug-radius-pill);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;align-items:center;padding:3px 10px;font-weight:600;line-height:1;display:inline-flex}.yii-debug .yii-debug-queue-status-queued{color:var(--yii-debug-info);background:color-mix(in srgb, var(--yii-debug-info), transparent 86%)}.yii-debug .yii-debug-queue-status-done{color:var(--yii-debug-success);background:color-mix(in srgb, var(--yii-debug-success), transparent 86%)}.yii-debug .yii-debug-queue-status-failed{color:var(--yii-debug-danger);background:color-mix(in srgb, var(--yii-debug-danger), transparent 86%)}.yii-debug .yii-debug-queue-driver{border-radius:var(--yii-debug-radius-pill);border:var(--yii-debug-border);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.05em;text-transform:uppercase;cursor:help;align-items:center;padding:3px 10px;font-weight:600;line-height:1;display:inline-flex}.yii-debug .yii-debug-queue-driver-is-sync{color:var(--yii-debug-success);background:color-mix(in srgb, var(--yii-debug-success), transparent 92%);border-color:color-mix(in srgb, var(--yii-debug-success), transparent 70%)}.yii-debug .yii-debug-queue-driver-is-async{color:var(--yii-debug-warning);background:color-mix(in srgb, var(--yii-debug-warning), transparent 92%);border-color:color-mix(in srgb, var(--yii-debug-warning), transparent 70%)}.yii-debug .yii-debug-queue-hint{margin:var(--yii-debug-space-3) 0;padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border-radius:var(--yii-debug-radius-md);border-left:3px solid var(--yii-debug-warning);background:color-mix(in srgb, var(--yii-debug-warning), transparent 94%);color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);line-height:1.5}.yii-debug .yii-debug-queue-tree{padding:var(--yii-debug-space-3);border-radius:var(--yii-debug-radius-sm);background:color-mix(in srgb, var(--yii-debug-panel-text), transparent 96%);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);flex-direction:column;gap:4px;line-height:1.6;display:flex}.yii-debug .yii-debug-queue-tree-row{flex-wrap:wrap;align-items:baseline;gap:8px;display:flex}.yii-debug .yii-debug-queue-tree-key{color:var(--yii-debug-panel-text);font-weight:700}.yii-debug .yii-debug-queue-tree-key:after{content:":";color:var(--yii-debug-panel-muted);margin-left:1px;font-weight:400}.yii-debug .yii-debug-queue-tree-type{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);text-transform:uppercase;letter-spacing:.05em;background:color-mix(in srgb, var(--yii-debug-panel-text), transparent 92%);border-radius:3px;padding:1px 6px}.yii-debug .yii-debug-queue-tree-class{color:var(--yii-debug-info);cursor:help;font-weight:600}.yii-debug .yii-debug-queue-tree-meta{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-queue-tree-truncated{color:var(--yii-debug-warning);font-size:var(--yii-debug-fs-xs);text-transform:uppercase;letter-spacing:.05em;background:color-mix(in srgb, var(--yii-debug-warning), transparent 86%);border-radius:3px;padding:1px 6px}.yii-debug .yii-debug-queue-tree-value{word-break:break-all}.yii-debug .yii-debug-queue-tree-value-string{color:var(--yii-debug-success)}.yii-debug .yii-debug-queue-tree-value-number{color:var(--yii-debug-info)}.yii-debug .yii-debug-queue-tree-value-bool{color:var(--yii-debug-warning);font-weight:600}.yii-debug .yii-debug-queue-tree-value-null{color:var(--yii-debug-panel-muted);font-style:italic}.yii-debug .yii-debug-queue-tree-collapse{flex-direction:column;gap:4px;display:flex}.yii-debug .yii-debug-queue-tree-summary{cursor:pointer;list-style:revert;flex-wrap:wrap;align-items:baseline;gap:8px;padding:2px 0;display:inline-flex}.yii-debug .yii-debug-queue-tree-summary::-webkit-details-marker{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-queue-tree-children{margin-left:var(--yii-debug-space-4);padding-left:var(--yii-debug-space-3);border-left:1px dashed color-mix(in srgb, var(--yii-debug-panel-text), transparent 80%);flex-direction:column;gap:4px;display:flex}.yii-debug .yii-debug-queue-time{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.02em}.yii-debug .yii-debug-queue-payload{margin:0}.yii-debug .yii-debug-queue-error{padding:var(--yii-debug-space-3);border-radius:var(--yii-debug-radius-sm);border-left:3px solid var(--yii-debug-danger);background:color-mix(in srgb, var(--yii-debug-danger), transparent 92%);color:var(--yii-debug-danger);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);line-height:1.5}.yii-debug .yii-debug-queue-meta{gap:var(--yii-debug-space-2);padding-top:var(--yii-debug-space-2);border-top:var(--yii-debug-border);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-queue-meta-item{border-radius:var(--yii-debug-radius-pill);background:color-mix(in srgb, var(--yii-debug-panel-text), transparent 94%);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);align-items:center;gap:6px;padding:2px 10px;line-height:1.4;display:inline-flex}.yii-debug .yii-debug-queue-meta-label{color:var(--yii-debug-panel-muted);text-transform:uppercase;letter-spacing:.05em;font-size:var(--yii-debug-fs-xs)}.yii-debug .yii-debug-queue-meta-value{color:var(--yii-debug-panel-text);font-weight:600}.yii-debug .yii-debug-dump{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);overflow:hidden}.yii-debug .yii-debug-dump-card-head{align-items:center;gap:var(--yii-debug-space-2);padding:6px var(--yii-debug-space-3);border-bottom:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);color:var(--yii-debug-panel-muted);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-dump-index{color:var(--yii-debug-panel-primary-strong);font-weight:700}.yii-debug .yii-debug-dump-type{border-radius:var(--yii-debug-radius-pill);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;text-transform:uppercase;--yii-debug-hue:var(--yii-debug-panel-primary);padding:1px 8px;font-weight:700;display:inline-flex}.yii-debug .yii-debug-dump-type[data-type=array]{--yii-debug-hue:var(--yii-debug-info)}.yii-debug .yii-debug-dump-type[data-type=string]{--yii-debug-hue:var(--yii-debug-success)}.yii-debug .yii-debug-dump-type[data-type=number]{--yii-debug-hue:var(--yii-debug-warning)}.yii-debug .yii-debug-dump-type[data-type=bool],.yii-debug .yii-debug-dump-type[data-type=null]{--yii-debug-hue:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-dump-meta{align-items:center;gap:var(--yii-debug-space-2);margin-left:auto;display:inline-flex}.yii-debug .yii-debug-dump-time{color:var(--yii-debug-panel-muted);font-variant-numeric:tabular-nums}.yii-debug .yii-debug-dump-trace{color:var(--yii-debug-panel-link)}.yii-debug .yii-debug-php-dump{text-align:start;font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);line-height:1.5;overflow-x:auto}.yii-debug .yii-debug-php-dump pre{background:0 0;margin:0}.yii-debug .yii-debug-php-dump code{white-space:pre;background:0 0;padding:0;display:block}.yii-debug .yii-debug-dump-body{padding:var(--yii-debug-space-3);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);line-height:1.5;overflow-x:auto}.yii-debug .yii-debug-dump-body code{white-space:pre;background:0 0;padding:0;display:block}[data-yii-debug-theme=dark] .yii-debug :is(.yii-debug-dump-body,.yii-debug-php-dump) span[style*="color: #DD0000"],[data-yii-debug-theme=dark] .yii-debug :is(.yii-debug-dump-body,.yii-debug-php-dump) span[style*="color:#DD0000"]{color:#e5c07b!important}[data-yii-debug-theme=dark] .yii-debug :is(.yii-debug-dump-body,.yii-debug-php-dump) span[style*="color: #0000BB"],[data-yii-debug-theme=dark] .yii-debug :is(.yii-debug-dump-body,.yii-debug-php-dump) span[style*="color:#0000BB"]{color:#93c5fd!important}[data-yii-debug-theme=dark] .yii-debug :is(.yii-debug-dump-body,.yii-debug-php-dump) span[style*="color: #007700"],[data-yii-debug-theme=dark] .yii-debug :is(.yii-debug-dump-body,.yii-debug-php-dump) span[style*="color:#007700"]{color:#86efac!important}[data-yii-debug-theme=dark] .yii-debug :is(.yii-debug-dump-body,.yii-debug-php-dump) span[style*="color: #FF8000"],[data-yii-debug-theme=dark] .yii-debug :is(.yii-debug-dump-body,.yii-debug-php-dump) span[style*="color:#FF8000"]{color:#fdba74!important}[data-yii-debug-theme=dark] .yii-debug :is(.yii-debug-dump-body,.yii-debug-php-dump) span[style*="color: #000000"],[data-yii-debug-theme=dark] .yii-debug :is(.yii-debug-dump-body,.yii-debug-php-dump) span[style*="color:#000000"]{color:var(--yii-debug-panel-text)!important}.yii-debug .yii-debug-history-card{gap:var(--yii-debug-space-2);padding:var(--yii-debug-space-3);border:1px dashed color-mix(in srgb, var(--yii-debug-panel-border) 70%, transparent);border-radius:var(--yii-debug-radius-md);background:radial-gradient(circle at top right, color-mix(in srgb, var(--yii-debug-panel-primary), transparent 86%), transparent 60%), color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 40%);flex-direction:column;display:flex;position:relative}.yii-debug .yii-debug-nav-iconed .yii-debug-nav-link{align-items:center}.yii-debug .yii-debug-nav-iconed .yii-debug-nav-link .yii-debug-nav-link-label{margin-right:auto}.yii-debug .yii-debug-nav-link-icon{width:16px;height:16px;color:var(--yii-debug-panel-primary);flex:none;justify-content:center;align-items:center;margin-right:10px;transition:color .14s,transform .14s;display:inline-flex}.yii-debug .yii-debug-nav-link-icon svg{width:100%;height:100%}.yii-debug .yii-debug-nav-link.is-active .yii-debug-nav-link-icon{color:currentColor}.yii-debug .yii-debug-nav-link-label{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-sm);letter-spacing:.005em;flex:1;font-weight:600;overflow:hidden}.yii-debug .yii-debug-nav-link:not(.is-active){color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-nav-link:not(.is-active) .yii-debug-nav-link-icon{color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 35%)}.yii-debug .yii-debug-nav-link:not(.is-active):after{color:color-mix(in srgb, var(--yii-debug-panel-muted), transparent 40%)}.yii-debug .yii-debug-nav-link:not(.is-active):hover,.yii-debug .yii-debug-nav-link:not(.is-active):focus-visible{color:var(--yii-debug-panel-text);background:var(--yii-debug-panel-surface-hover)}:is(.yii-debug .yii-debug-nav-link:not(.is-active):hover,.yii-debug .yii-debug-nav-link:not(.is-active):focus-visible) .yii-debug-nav-link-icon{color:var(--yii-debug-panel-primary);transform:translate(1px)}:is(.yii-debug .yii-debug-nav-link:not(.is-active):hover,.yii-debug .yii-debug-nav-link:not(.is-active):focus-visible):after{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-tag-link{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);letter-spacing:-.01em;color:var(--yii-debug-panel-primary-strong);white-space:nowrap;border-bottom:1px dotted color-mix(in srgb, var(--yii-debug-panel-primary), transparent 60%);font-weight:700;text-decoration:none;display:inline-block}.yii-debug .yii-debug-tag-link:hover,.yii-debug .yii-debug-tag-link:focus-visible{color:var(--yii-debug-panel-primary);border-bottom-color:var(--yii-debug-panel-primary);text-decoration:none}.yii-debug .yii-debug-url-cell{text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;max-width:clamp(180px,26vw,460px);display:inline-block;overflow:hidden}.yii-debug .yii-debug-table tbody tr.is-cursor{background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 86%);box-shadow:inset 3px 0 0 var(--yii-debug-panel-primary)}.yii-debug .yii-debug-table tbody tr.is-cursor:hover{background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 78%)}.yii-debug .yii-debug-active-filters{margin-bottom:var(--yii-debug-space-3);padding:8px var(--yii-debug-space-3);border:1px solid color-mix(in srgb, var(--yii-debug-info), transparent 60%);border-radius:var(--yii-debug-radius-md);background:color-mix(in srgb, var(--yii-debug-info), transparent 92%);flex-wrap:wrap;align-items:center;gap:8px;display:flex}.yii-debug .yii-debug-active-filters-label{color:var(--yii-debug-info);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;font-weight:700}.yii-debug .yii-debug-active-filters-list{flex-wrap:wrap;gap:4px;display:inline-flex}.yii-debug .yii-debug-active-filter-pill{border:1px solid color-mix(in srgb, var(--yii-debug-info), transparent 50%);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);align-items:center;gap:4px;padding:2px 6px 2px 8px;text-decoration:none;transition:background-color .12s,border-color .12s;display:inline-flex}.yii-debug .yii-debug-active-filter-pill:hover,.yii-debug .yii-debug-active-filter-pill:focus-visible{border-color:var(--yii-debug-danger);background:color-mix(in srgb, var(--yii-debug-danger), transparent 88%);color:var(--yii-debug-danger);text-decoration:none}.yii-debug .yii-debug-active-filter-attr{color:var(--yii-debug-panel-muted);text-transform:lowercase}.yii-debug .yii-debug-active-filter-sep{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-active-filter-value{color:var(--yii-debug-panel-text);text-overflow:ellipsis;white-space:nowrap;max-width:220px;font-weight:700;overflow:hidden}.yii-debug .yii-debug-active-filter-x{color:var(--yii-debug-panel-muted);margin-left:2px;font-weight:700;line-height:1}.yii-debug .yii-debug-active-filter-pill:hover .yii-debug-active-filter-x,.yii-debug .yii-debug-active-filter-pill:focus-visible .yii-debug-active-filter-x{color:var(--yii-debug-danger)}.yii-debug .yii-debug-active-filters-clear{border:1px solid color-mix(in srgb, var(--yii-debug-danger), transparent 60%);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);color:var(--yii-debug-danger);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;text-transform:uppercase;margin-left:auto;padding:3px 10px;font-weight:700;text-decoration:none;transition:background-color .12s,border-color .12s,transform .12s}.yii-debug .yii-debug-active-filters-clear:hover,.yii-debug .yii-debug-active-filters-clear:focus-visible{border-color:var(--yii-debug-danger);background:color-mix(in srgb, var(--yii-debug-danger), transparent 86%);color:var(--yii-debug-danger);text-decoration:none;transform:translateY(-1px)}.yii-debug .yii-debug-col-id,.yii-debug .yii-debug-col-queue-id{white-space:nowrap;min-width:120px}.yii-debug .yii-debug-grid .filters input.yii-debug-col-id-input,.yii-debug .yii-debug-grid .filters input.yii-debug-col-queue-id-input{box-sizing:border-box;width:104px}.yii-debug .yii-debug-grid-history .yii-debug-table{--yii-debug-history-query-width:0px;--yii-debug-history-mail-width:0px;--yii-debug-history-fixed-width:calc(916px + var(--yii-debug-history-query-width) + var(--yii-debug-history-mail-width));min-width:calc(var(--yii-debug-history-fixed-width) + 180px);table-layout:fixed}.yii-debug .yii-debug-grid-history .yii-debug-table:has(thead th.yii-debug-col-num:not(:first-child,.yii-debug-col-mail)){--yii-debug-history-query-width:66px}.yii-debug .yii-debug-grid-history .yii-debug-table:has(thead th.yii-debug-col-mail){--yii-debug-history-mail-width:56px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:first-child{width:36px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:nth-child(2){width:276px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:nth-child(3){width:80px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:nth-child(4),.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:nth-child(5){width:100px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th.yii-debug-col-ip{width:112px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th.yii-debug-col-num:not(:first-child,.yii-debug-col-mail){width:66px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th.yii-debug-col-mail{width:56px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:nth-last-child(3){width:128px}.yii-debug .yii-debug-grid-history .yii-debug-table thead tr:first-child>th:nth-last-child(2){width:84px}.yii-debug .yii-debug-grid-history .yii-debug-table tbody td,.yii-debug .yii-debug-grid-history .yii-debug-table tbody th{white-space:nowrap;padding:8px 10px}.yii-debug .yii-debug-grid-history .yii-debug-table tbody td.yii-debug-col-ip{white-space:normal;overflow-wrap:anywhere}.yii-debug .yii-debug-grid-history .yii-debug-table tbody:has(>tr:only-child>td[colspan]){caption-side:bottom;display:table-caption}.yii-debug .yii-debug-grid-history .yii-debug-table tbody:has(>tr:only-child>td[colspan]) td{display:block}.yii-debug .yii-debug-grid-history .yii-debug-table .yii-debug-tag-link,.yii-debug .yii-debug-grid-history .yii-debug-table .yii-debug-url-cell{text-overflow:ellipsis;max-width:100%;overflow:hidden}.yii-debug .yii-debug-grid-history .yii-debug-table .yii-debug-tag-link{vertical-align:middle;display:inline-block}@media (max-width:1366px){.yii-debug .yii-debug-grid-history .yii-debug-table{--yii-debug-history-fixed-width:calc(804px + var(--yii-debug-history-query-width))}.yii-debug .yii-debug-grid-history .yii-debug-col-ip,.yii-debug .yii-debug-grid-history .yii-debug-col-mail{display:none}}@media (max-width:767px){.yii-debug .yii-debug-grid-event .yii-debug-table thead th,.yii-debug .yii-debug-grid-event .yii-debug-table thead td,.yii-debug .yii-debug-grid-event .yii-debug-table tbody tr:not(.yii-debug-event-detail-row) td,.yii-debug .yii-debug-grid-event .yii-debug-table tbody th,.yii-debug .yii-debug-grid-event .yii-debug-table tfoot td{padding:8px 10px}.yii-debug .yii-debug-grid-event .yii-debug-table .filters td{padding:6px 10px}}.yii-debug th.yii-debug-col-num,.yii-debug td.yii-debug-col-num{text-align:center;white-space:nowrap;width:1%}.yii-debug td.yii-debug-col-num{font-variant-numeric:tabular-nums}.yii-debug td.yii-debug-col-num input,.yii-debug td.yii-debug-col-num select{text-align:center;width:100%;min-width:0}.yii-debug .yii-debug-explain-scroll{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);overflow-x:auto}.yii-debug .yii-debug-explain-table{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);white-space:nowrap;margin:0}.yii-debug .yii-debug-explain-table th,.yii-debug .yii-debug-explain-table td{padding:var(--yii-debug-space-2) var(--yii-debug-space-3)}.yii-debug ul.yii-debug-trace{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);white-space:normal;margin:2px 0 0;padding:0;list-style:none}.yii-debug .yii-debug-muted{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-nowrap{white-space:nowrap}.yii-debug .yii-debug-break{word-break:break-all}.yii-debug .yii-debug-not-set{color:var(--yii-debug-danger);font-style:italic}.yii-debug .yii-debug-gauge{vertical-align:middle;font-variant-numeric:tabular-nums;flex-direction:column;gap:3px;min-width:76px;display:inline-flex}.yii-debug .yii-debug-gauge-bar{border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-gauge-track);height:3px;display:block;overflow:hidden}.yii-debug .yii-debug-gauge-bar:after{content:"";width:var(--yii-debug-gauge,0%);border-radius:inherit;background:var(--yii-debug-gauge-fill);height:100%;display:block}.yii-debug .yii-debug-section{margin-bottom:var(--yii-debug-space-6)}.yii-debug .yii-debug-section:last-child{margin-bottom:0}.yii-debug .yii-debug-section-title{align-items:center;gap:var(--yii-debug-space-2);margin:0 0 var(--yii-debug-space-3);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);letter-spacing:.22em;text-transform:uppercase;font-size:.6875rem;font-weight:500;line-height:1;display:flex}.yii-debug .yii-debug-section-mark{color:var(--yii-debug-panel-primary);font-weight:700}.yii-debug .yii-debug-section-count{border-radius:var(--yii-debug-radius-pill);background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 85%);color:var(--yii-debug-panel-primary);align-items:center;margin-left:auto;padding:2px 8px;font-weight:700;display:inline-flex}.yii-debug h1.yii-debug-hero-title{margin:0 0 var(--yii-debug-space-5);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-display);letter-spacing:-.015em;text-transform:lowercase;font-size:2rem;font-weight:400;line-height:1}.yii-debug h1.yii-debug-hero-title:before{content:"// ";color:var(--yii-debug-panel-primary);font-family:var(--yii-debug-font-mono);letter-spacing:0;vertical-align:2px;margin-right:.2em;font-size:.75em;font-weight:700}.yii-debug .yii-debug-readout-card{gap:var(--yii-debug-space-2);min-height:132px;padding:var(--yii-debug-space-5) var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:radial-gradient(circle at 1px 1px, color-mix(in srgb, var(--yii-debug-panel-border), transparent 30%) 1px, transparent 0) 0 0 / 14px 14px, linear-gradient(150deg, color-mix(in srgb, var(--yii-debug-panel-primary), transparent 92%), transparent 55%), var(--yii-debug-panel-surface);flex-direction:column;transition:border-color .15s,transform .15s,box-shadow .15s;animation:.45s cubic-bezier(.2,.7,.3,1) both yii-debug-readout-in;display:flex;position:relative;overflow:hidden}.yii-debug .yii-debug-readout-card:hover{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 45%);box-shadow:var(--yii-debug-shadow-md);transform:translateY(-2px)}.yii-debug .yii-debug-readout-card:first-child{animation-delay:40ms}.yii-debug .yii-debug-readout-card:nth-child(2){animation-delay:80ms}.yii-debug .yii-debug-readout-card:nth-child(3){animation-delay:.12s}.yii-debug .yii-debug-readout-card:nth-child(4){animation-delay:.16s}.yii-debug .yii-debug-readout-card:nth-child(5){animation-delay:.2s}.yii-debug .yii-debug-readout-card:nth-child(6){animation-delay:.24s}.yii-debug .yii-debug-readout-label{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);letter-spacing:.15em;text-transform:uppercase;margin:0;font-size:.6875rem;font-weight:500;line-height:1;display:block;position:relative}.yii-debug .yii-debug-readout-label:before{content:":: ";color:var(--yii-debug-panel-primary);opacity:.85;font-weight:700}.yii-debug .yii-debug-readout-value{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);letter-spacing:-.015em;overflow-wrap:break-word;margin:0;font-size:1.375rem;font-weight:700;line-height:1.15;display:block}.yii-debug .yii-debug-readout-meta{align-items:center;gap:var(--yii-debug-space-2);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);letter-spacing:.05em;text-transform:lowercase;margin-top:auto;font-size:.6875rem;line-height:1;display:inline-flex}.yii-debug .yii-debug-ext-strip{gap:var(--yii-debug-space-2);padding:var(--yii-debug-space-3);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:linear-gradient(0deg, color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 30%), var(--yii-debug-panel-surface));flex-wrap:wrap;display:flex}.yii-debug .yii-debug-ext-pill{align-items:center;gap:var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);letter-spacing:.1em;text-transform:uppercase;padding:6px 12px 6px 9px;font-size:.6875rem;font-weight:500;line-height:1;transition:border-color .15s,background .15s;display:inline-flex}.yii-debug .yii-debug-ext-pill-dot{background:color-mix(in srgb, var(--yii-debug-panel-muted), transparent 60%);border-radius:999px;width:7px;height:7px;display:inline-block}.yii-debug .yii-debug-ext-pill-label{color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-ext-pill-state{margin-left:var(--yii-debug-space-1);color:var(--yii-debug-panel-muted);letter-spacing:.12em;opacity:.7;font-size:.625rem;font-weight:700}.yii-debug .yii-debug-ext-pill.is-on{border-color:color-mix(in srgb, var(--yii-debug-success), transparent 55%);background:color-mix(in srgb, var(--yii-debug-success), var(--yii-debug-panel-surface) 90%)}.yii-debug .yii-debug-ext-pill.is-on .yii-debug-ext-pill-dot{background:var(--yii-debug-success);box-shadow:0 0 0 3px color-mix(in srgb, var(--yii-debug-success), transparent 75%);animation:2.4s ease-in-out infinite yii-debug-led-pulse}.yii-debug .yii-debug-ext-pill.is-on .yii-debug-ext-pill-state{color:var(--yii-debug-success);opacity:1}.yii-debug .yii-debug-ext-pill.is-off{opacity:.55}.yii-debug .yii-debug-phpinfo-shell{gap:var(--yii-debug-space-4);grid-template-columns:220px minmax(0,1fr);align-items:start;display:grid}.yii-debug .yii-debug-phpinfo-toc{top:var(--yii-debug-space-3);max-height:calc(100vh - 80px);padding:var(--yii-debug-space-3);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);scrollbar-width:thin;align-self:start;position:sticky;overflow-y:auto}.yii-debug .yii-debug-phpinfo-toc-title{margin:0 0 var(--yii-debug-space-2);color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);align-items:baseline;gap:6px;display:flex}.yii-debug .yii-debug-phpinfo-toc-title span:first-child{color:var(--yii-debug-panel-text);font-variant-numeric:tabular-nums;font-weight:700}.yii-debug .yii-debug-phpinfo-toc-title-note{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);margin-left:auto}.yii-debug .yii-debug-phpinfo-toc-overview,.yii-debug .yii-debug-phpinfo-toc-group-list{flex-direction:column;gap:2px;margin:0;padding:0;list-style:none;display:flex}.yii-debug .yii-debug-phpinfo-toc-overview{margin-bottom:var(--yii-debug-space-2);padding-bottom:var(--yii-debug-space-2);border-bottom:var(--yii-debug-border)}.yii-debug .yii-debug-phpinfo-toc-groups{flex-direction:column;gap:2px;display:flex}.yii-debug .yii-debug-phpinfo-toc-group{margin:0}.yii-debug .yii-debug-phpinfo-toc-group-summary{border-radius:var(--yii-debug-radius-sm);color:var(--yii-debug-panel-muted);cursor:pointer;font-size:var(--yii-debug-fs-xs);grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:7px;padding:7px 8px;font-weight:600;list-style:none;transition:background-color .12s,color .12s;display:grid}.yii-debug .yii-debug-phpinfo-toc-group-summary::-webkit-details-marker{display:none}.yii-debug .yii-debug-phpinfo-toc-group-summary:before{content:"›";color:var(--yii-debug-panel-muted);font-size:1.1em;line-height:1;transition:transform .12s}.yii-debug .yii-debug-phpinfo-toc-group-summary:hover,.yii-debug .yii-debug-phpinfo-toc-group-summary:focus-visible{background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-phpinfo-toc-group[open]>.yii-debug-phpinfo-toc-group-summary{color:var(--yii-debug-panel-text)}.yii-debug .yii-debug-phpinfo-toc-group[open]>.yii-debug-phpinfo-toc-group-summary:before{transform:rotate(90deg)}.yii-debug .yii-debug-phpinfo-toc-group-count{border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface-muted);min-width:22px;color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums;text-align:center;padding:1px 6px}.yii-debug .yii-debug-phpinfo-toc-group-list{margin:2px 0 var(--yii-debug-space-2);padding-left:14px}.yii-debug .yii-debug-phpinfo-toc-link{border-radius:0 var(--yii-debug-radius-sm) var(--yii-debug-radius-sm) 0;color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-sm);border-left:2px solid #0000;padding:7px 10px 7px 12px;text-decoration:none;transition:background-color .12s,color .12s,border-color .12s;display:block}.yii-debug .yii-debug-phpinfo-toc-link:hover,.yii-debug .yii-debug-phpinfo-toc-link:focus-visible{background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-text);text-decoration:none}.yii-debug .yii-debug-phpinfo-toc-link.is-active{border-left-color:var(--yii-debug-panel-primary);background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 90%);color:var(--yii-debug-panel-primary-strong);font-weight:700}.yii-debug .yii-debug-phpinfo-main{min-width:0}.yii-debug .yii-debug-phpinfo-search{margin-bottom:var(--yii-debug-space-3);flex-direction:column;gap:6px;display:flex}.yii-debug .yii-debug-phpinfo-search-control{align-items:center;display:flex;position:relative}.yii-debug .yii-debug-phpinfo-search-icon{width:16px;height:16px;color:var(--yii-debug-panel-muted);pointer-events:none;justify-content:center;align-items:center;display:inline-flex;position:absolute;left:12px}.yii-debug .yii-debug-phpinfo-search-icon svg{width:100%;height:100%}.yii-debug .yii-debug-phpinfo-search-input{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);flex:1;padding:9px 12px 9px 36px;transition:border-color .12s,box-shadow .12s}.yii-debug .yii-debug-phpinfo-search-input::placeholder{color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-phpinfo-search-input:focus{border-color:var(--yii-debug-panel-primary);box-shadow:0 0 0 3px color-mix(in srgb, var(--yii-debug-panel-primary), transparent 80%)}.yii-debug .yii-debug-phpinfo-search-clear{border-radius:var(--yii-debug-radius-sm);color:var(--yii-debug-panel-link);cursor:pointer;font:inherit;font-size:var(--yii-debug-fs-xs);background:0 0;border:0;padding:4px 8px;font-weight:600;position:absolute;right:8px}.yii-debug .yii-debug-phpinfo-search-clear:hover,.yii-debug .yii-debug-phpinfo-search-clear:focus-visible{background:var(--yii-debug-panel-surface-hover);color:var(--yii-debug-panel-link-hover)}.yii-debug .yii-debug-phpinfo-search-control:has(.yii-debug-phpinfo-search-clear:not([hidden])) .yii-debug-phpinfo-search-input{padding-right:58px}.yii-debug .yii-debug-phpinfo-search-feedback{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-2);min-height:18px;padding-inline:2px;display:flex}.yii-debug .yii-debug-phpinfo-search-feedback:has(.yii-debug-phpinfo-search-status:empty):has(.yii-debug-phpinfo-search-empty[hidden]){display:none}.yii-debug .yii-debug-phpinfo-search-status,.yii-debug .yii-debug-phpinfo-search-empty{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs)}.yii-debug .yii-debug-phpinfo-search-empty{color:var(--yii-debug-danger)}.yii-debug .yii-debug-phpinfo-section{scroll-margin-top:var(--yii-debug-space-3)}.yii-debug .yii-debug-phpinfo-section+:is(.yii-debug .yii-debug-phpinfo-section){margin-top:var(--yii-debug-space-4)}.yii-debug .yii-debug-phpinfo-section .yii-debug-disclosure{margin-bottom:0}.yii-debug .yii-debug-phpinfo-toc-link[hidden],.yii-debug .yii-debug-phpinfo-toc-group[hidden],.yii-debug .yii-debug-phpinfo-section[hidden],.yii-debug .yii-debug-phpinfo-search-clear[hidden],.yii-debug .yii-debug-phpinfo-search-empty[hidden],.yii-debug .yii-debug-phpinfo-overview-hero-section[hidden],.yii-debug .yii-debug-disclosure[hidden],.yii-debug .yii-debug-phpinfo-extension-group[hidden],.yii-debug .yii-debug-ext-pill[hidden],.yii-debug .yii-debug-table-wrap[hidden],.yii-debug .yii-debug-phpinfo-module tr[hidden]{display:none}@media (max-width:900px){.yii-debug .yii-debug-phpinfo-shell{grid-template-columns:1fr}.yii-debug .yii-debug-phpinfo-toc{max-height:none;position:static;overflow:hidden}.yii-debug .yii-debug-phpinfo-toc-groups{gap:4px var(--yii-debug-space-2);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.yii-debug .yii-debug-phpinfo-toc-group[open]{grid-column:1/-1}.yii-debug .yii-debug-phpinfo-toc-group-list{grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:4px;padding-left:0;display:grid}.yii-debug .yii-debug-phpinfo-toc-link{border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface-muted);border-left:0;padding:4px 10px}.yii-debug .yii-debug-phpinfo-toc-link.is-active{background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 80%);border-left:0}}@media (max-width:560px){.yii-debug .yii-debug-phpinfo-toc-groups{grid-template-columns:1fr}}.yii-debug .yii-debug-phpinfo-overview-hero{gap:var(--yii-debug-space-3);margin-bottom:var(--yii-debug-space-3);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}@media (max-width:720px){.yii-debug .yii-debug-phpinfo-overview-hero{grid-template-columns:1fr}}.yii-debug .yii-debug-phpinfo-overview-hero-headline{align-items:center;gap:var(--yii-debug-space-3);grid-template-columns:1fr auto;display:grid}.yii-debug .yii-debug-phpinfo-overview-hero-version{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-display,var(--yii-debug-font-mono));letter-spacing:-.02em;font-size:clamp(1.6rem,2.4vw + 1rem,2.4rem);font-weight:800;line-height:1}.yii-debug .yii-debug-phpinfo-overview-hero-mark{border-radius:var(--yii-debug-radius-pill);border:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);min-width:64px;color:var(--yii-debug-panel-primary-strong);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-lg);letter-spacing:.08em;text-transform:lowercase;justify-content:center;align-items:center;padding:6px 14px;font-weight:700;display:inline-flex}.yii-debug .yii-debug-phpinfo-overview-hero-metrics{gap:var(--yii-debug-space-2) var(--yii-debug-space-4);grid-template-columns:repeat(auto-fit,minmax(150px,1fr));margin:0;display:grid}.yii-debug .yii-debug-phpinfo-overview-hero-section{gap:var(--yii-debug-space-3);min-width:0;padding:var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);flex-direction:column;display:flex}.yii-debug .yii-debug-phpinfo-overview-hero-section:first-child{grid-column:1/-1}.yii-debug .yii-debug-phpinfo-extensions{grid-column:1/-1}.yii-debug .yii-debug-phpinfo-extensions>.yii-debug-phpinfo-extension-groups{padding:var(--yii-debug-space-4)}.yii-debug .yii-debug-phpinfo-extension-group-count{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums;padding:1px 7px}.yii-debug .yii-debug-phpinfo-extension-groups{gap:var(--yii-debug-space-3);flex-direction:column;display:flex}.yii-debug .yii-debug-phpinfo-extension-group{gap:var(--yii-debug-space-2);flex-direction:column;display:flex}.yii-debug .yii-debug-phpinfo-extension-group-head{align-items:center;gap:var(--yii-debug-space-2);color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);font-weight:600;display:flex}.yii-debug .yii-debug-phpinfo-extension-group-head .yii-debug-phpinfo-extension-group-count{margin-left:auto}.yii-debug .yii-debug-phpinfo-extension-group .yii-debug-ext-pill{scroll-margin-top:var(--yii-debug-space-4)}.yii-debug .yii-debug-phpinfo-extension-group .yii-debug-ext-pill:target,.yii-debug .yii-debug-phpinfo-extension-group .yii-debug-ext-pill.is-search-match{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 45%);background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 90%)}.yii-debug .yii-debug-phpinfo-overview-block-head{border-bottom:var(--yii-debug-border);align-items:center;gap:10px;padding-bottom:8px;display:flex}.yii-debug .yii-debug-phpinfo-overview-block-eyebrow{color:var(--yii-debug-panel-text);font-size:var(--yii-debug-fs-sm);font-weight:600}.yii-debug .yii-debug-phpinfo-overview-hero-metric{flex-direction:column;gap:3px;padding-block:2px;display:flex}.yii-debug .yii-debug-phpinfo-overview-hero-metric dt{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);margin:0;font-weight:600;line-height:1.2}.yii-debug .yii-debug-phpinfo-overview-hero-metric dd{overflow-wrap:break-word;min-width:0;color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);margin:0;line-height:1.4}.yii-debug .yii-debug-phpinfo-overview-hero-metric dd code{color:inherit;background:0 0;padding:0}.yii-debug .yii-debug-phpinfo-overview-hero-metric.is-wide{grid-column:1/-1}.yii-debug .yii-debug-phpinfo-overview-pill{border-radius:var(--yii-debug-radius-pill);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;text-transform:uppercase;border:1px solid #0000;align-items:center;gap:6px;padding:1px 10px;font-weight:700;display:inline-flex}.yii-debug .yii-debug-phpinfo-overview-pill[data-variant=success]{color:var(--yii-debug-success);background:color-mix(in srgb, var(--yii-debug-success), transparent 86%);border-color:color-mix(in srgb, var(--yii-debug-success), transparent 65%)}.yii-debug .yii-debug-phpinfo-overview-pill[data-variant=muted]{color:var(--yii-debug-panel-muted);background:var(--yii-debug-panel-surface-muted);border-color:var(--yii-debug-panel-border)}.yii-debug .yii-debug-phpinfo-overview-token{border:1px solid var(--yii-debug-panel-border);border-radius:var(--yii-debug-radius-sm);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);padding:1px 8px}.yii-debug .yii-debug-phpinfo-overview-files{flex-wrap:wrap;gap:4px;display:flex}.yii-debug .yii-debug-phpinfo-overview-files .yii-debug-phpinfo-overview-token{cursor:help;transition:border-color .12s,background-color .12s}.yii-debug .yii-debug-phpinfo-overview-files .yii-debug-phpinfo-overview-token:hover{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 40%);background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 88%)}.yii-debug .yii-debug-phpinfo-module{gap:var(--yii-debug-space-3);flex-direction:column;margin-top:0;display:flex}.yii-debug .yii-debug-phpinfo-module-head{align-items:center;gap:10px;margin:0;padding:2px 2px 0;display:flex}.yii-debug .yii-debug-phpinfo-module-head h2{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xl);text-align:left;background:0 0;margin:0;padding:0;font-weight:600}.yii-debug .yii-debug-phpinfo-table-section{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-sm);overflow:hidden}.yii-debug .yii-debug-phpinfo-table-section-head{justify-content:space-between;align-items:center;gap:var(--yii-debug-space-2);padding:9px var(--yii-debug-space-3);border-bottom:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-sm);font-weight:600;display:flex}.yii-debug .yii-debug-phpinfo-variable-group>summary{cursor:pointer;list-style:none;transition:background-color .12s}.yii-debug .yii-debug-phpinfo-variable-group>summary::-webkit-details-marker{display:none}.yii-debug .yii-debug-phpinfo-variable-group>summary:hover,.yii-debug .yii-debug-phpinfo-variable-group>summary:focus-visible{background:var(--yii-debug-panel-surface-hover)}.yii-debug .yii-debug-phpinfo-variable-group>summary>span:first-child{align-items:center;gap:8px;display:inline-flex}.yii-debug .yii-debug-phpinfo-variable-group>summary>span:first-child:before{content:"›";color:var(--yii-debug-panel-muted);font-size:1.1em;line-height:1;transition:transform .12s}.yii-debug .yii-debug-phpinfo-variable-group[open]>summary>span:first-child:before{transform:rotate(90deg)}.yii-debug .yii-debug-phpinfo-table-section-count{border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums;flex:none;padding:1px 7px;font-weight:500}.yii-debug .yii-debug-phpinfo-table-scroll{min-width:0;overflow-x:auto}.yii-debug .yii-debug-phpinfo-module .yii-debug-table{table-layout:fixed;min-width:560px}.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr>:first-child{width:40%}.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr>:nth-child(2):last-child{width:60%}.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr>:nth-child(2):not(:last-child),.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr>:nth-child(3){width:30%}.yii-debug .yii-debug-phpinfo-module .yii-debug-table th,.yii-debug .yii-debug-phpinfo-module .yii-debug-table td{text-align:left}.yii-debug .yii-debug-phpinfo-module .yii-debug-table th[scope=row]{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);white-space:normal;font-weight:500}.yii-debug .yii-debug-phpinfo-module .yii-debug-table td{font-family:var(--yii-debug-font-mono)}.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr.h>*{z-index:1;background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xs);letter-spacing:.03em;text-transform:uppercase;font-weight:700;position:sticky;top:0}.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr.is-search-match>*{background:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 88%)!important}.yii-debug .yii-debug-phpinfo-module .yii-debug-table tr.has-local-override>td:nth-child(2){box-shadow:inset 2px 0 0 color-mix(in srgb, var(--yii-debug-warning), transparent 25%);color:color-mix(in srgb, var(--yii-debug-warning) 72%, var(--yii-debug-panel-text))}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-phpinfo-table-scroll{overflow:visible}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts{table-layout:auto;min-width:0;display:block}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tbody{grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact{border-bottom:1px solid color-mix(in srgb, var(--yii-debug-panel-border), transparent 25%);grid-template-columns:minmax(130px,.85fr) minmax(0,1.15fr);min-width:0;display:grid}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact:nth-child(odd){border-right:1px solid color-mix(in srgb, var(--yii-debug-panel-border), transparent 25%)}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact.yii-debug-phpinfo-fact-wide{border-right:0;grid-column:1/-1}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact>*{overflow-wrap:anywhere;background:0 0;border:0;width:auto;padding:9px 12px}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact>th{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xs);font-weight:600}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact>td{color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs)}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact-subheading{grid-column:1/-1;display:block}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact-subheading>th{border-block:var(--yii-debug-border);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 15%);width:auto;color:var(--yii-debug-panel-text);font-family:var(--yii-debug-font-sans);font-size:var(--yii-debug-fs-xs);letter-spacing:.02em;text-align:left;padding:8px 12px;font-weight:600;display:block}.yii-debug .yii-debug-phpinfo-status-pill,.yii-debug .yii-debug-phpinfo-redacted{border-radius:var(--yii-debug-radius-pill);width:-moz-fit-content;width:fit-content;font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.03em;border:1px solid #0000;align-items:center;padding:1px 8px;font-weight:600;display:inline-flex}.yii-debug .yii-debug-phpinfo-status-pill[data-variant=success]{border-color:color-mix(in srgb, var(--yii-debug-success), transparent 65%);background:color-mix(in srgb, var(--yii-debug-success), transparent 88%);color:var(--yii-debug-success)}.yii-debug .yii-debug-phpinfo-status-pill[data-variant=muted]{border-color:var(--yii-debug-panel-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted)}.yii-debug .yii-debug-phpinfo-redacted{border-color:color-mix(in srgb, var(--yii-debug-warning), transparent 65%);background:color-mix(in srgb, var(--yii-debug-warning), transparent 90%);color:color-mix(in srgb, var(--yii-debug-warning) 72%, var(--yii-debug-panel-text));text-transform:uppercase}.yii-debug .yii-debug-phpinfo-table-section.is-note .yii-debug-table.is-note{table-layout:auto;min-width:0}.yii-debug .yii-debug-phpinfo-table-section.is-note .yii-debug-table.is-note td,.yii-debug .yii-debug-phpinfo-table-section.is-note .yii-debug-table.is-note th{width:auto;padding:var(--yii-debug-space-3);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-sans);text-align:left;line-height:1.6}@media (max-width:720px){.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tbody{grid-template-columns:1fr}.yii-debug .yii-debug-phpinfo-table-section.is-facts .yii-debug-table.is-facts tr.yii-debug-phpinfo-fact{border-right:0}}@media (max-width:959px){.yii-debug .yii-debug-sidebar{min-width:0}.yii-debug .yii-debug-nav{overscroll-behavior-x:contain;scrollbar-width:thin;overflow-x:auto}.yii-debug .yii-debug-nav ul{gap:var(--yii-debug-space-1);flex-direction:row;width:max-content;min-width:100%}.yii-debug .yii-debug-nav-link{white-space:nowrap;min-height:40px}.yii-debug .yii-debug-nav-link:after{display:none}.yii-debug .yii-debug-nav-link-icon{margin-right:var(--yii-debug-space-1)}}@media (max-width:767px){.yii-debug .yii-debug-page{padding:var(--yii-debug-space-2)}.yii-debug .yii-debug-card{padding:var(--yii-debug-space-3)}}@media (prefers-reduced-motion:reduce){.yii-debug *,.yii-debug :before,.yii-debug :after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}@keyframes yii-debug-user-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(.85)}}@keyframes yii-debug-readout-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes yii-debug-led-pulse{0%,to{box-shadow:0 0 0 3px color-mix(in srgb, var(--yii-debug-success), transparent 75%)}50%{box-shadow:0 0 0 6px color-mix(in srgb, var(--yii-debug-success), transparent 90%)}}.yii-debug-tl-filter{align-items:end;gap:var(--yii-debug-space-3);margin-bottom:var(--yii-debug-space-3);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface-muted);flex-wrap:wrap;display:flex}.yii-debug-tl-field{flex-direction:column;gap:4px;min-width:160px;display:flex}.yii-debug-tl-field label{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-xs);letter-spacing:.06em;text-transform:uppercase;font-weight:600}.yii-debug-tl-field input{padding:6px var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-sm);background:var(--yii-debug-panel-surface);color:var(--yii-debug-panel-text);font:inherit;font-family:var(--yii-debug-font-mono)}.yii-debug-tl-field input::placeholder{color:var(--yii-debug-panel-muted)}.yii-debug-tl-field input:focus{border-color:var(--yii-debug-panel-primary)}.yii-debug-tl-field-grow{flex:240px}.yii-debug-tl-hint{margin-bottom:var(--yii-debug-space-3);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border:1px dashed color-mix(in srgb, var(--yii-debug-panel-border) 70%, transparent);border-radius:var(--yii-debug-radius-md);background:color-mix(in srgb, var(--yii-debug-panel-surface-muted), transparent 40%)}.yii-debug-tl-hint-title{color:var(--yii-debug-panel-text);margin:0 0 6px;font-weight:600}.yii-debug-tl-hint-body{color:var(--yii-debug-panel-muted);font-size:var(--yii-debug-fs-sm);margin:0;line-height:1.55}.yii-debug-tl-hint-body em{color:var(--yii-debug-panel-text);font-style:normal;font-weight:600}.yii-debug-tl{--yii-debug-tl-label-col:22%;grid-template-columns:minmax(180px, var(--yii-debug-tl-label-col)) 1fr;border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);grid-template-rows:auto 1fr auto;gap:0;display:grid;overflow:hidden}@media (max-width:1440px){.yii-debug-tl{--yii-debug-tl-label-col:32%}}.yii-debug-tl-axis{border-bottom:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);grid-column:2/3;height:32px;padding:8px 0;position:relative}.yii-debug-tl-tick{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);letter-spacing:.04em;white-space:nowrap;pointer-events:none;position:absolute;top:8px;transform:translate(-50%)}.yii-debug-tl-tick:after{content:"";background:var(--yii-debug-panel-border);width:1px;height:8px;position:absolute;top:18px;left:50%}.yii-debug-tl-rows{grid-column:1/3;grid-template-columns:minmax(180px, var(--yii-debug-tl-label-col)) 1fr;background:linear-gradient(to right, transparent calc(var(--yii-debug-tl-label-col) - 1px), var(--yii-debug-panel-border) calc(var(--yii-debug-tl-label-col) - 1px), var(--yii-debug-panel-border) var(--yii-debug-tl-label-col), transparent var(--yii-debug-tl-label-col));display:grid}.yii-debug-tl-row{display:contents}.yii-debug-tl-row:hover .yii-debug-tl-label,.yii-debug-tl-row:hover .yii-debug-tl-track{background:var(--yii-debug-panel-surface-hover)}.yii-debug-tl-row:hover .yii-debug-tl-name{color:var(--yii-debug-panel-text)}.yii-debug-tl-label{min-width:0;padding:8px var(--yii-debug-space-3) 8px calc(var(--yii-debug-space-3) + (var(--depth,0) * 14px));border-bottom:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 50%, transparent);grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px;transition:background-color .12s;display:grid}.yii-debug-tl-dot{background:var(--yii-debug-cat-db);opacity:.85;border-radius:50%;flex:none;width:6px;height:6px}.yii-debug-tl-name{font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);color:var(--yii-debug-panel-muted);text-overflow:ellipsis;white-space:nowrap;transition:color .12s;overflow:hidden}.yii-debug-tl-track{border-bottom:1px solid color-mix(in srgb, var(--yii-debug-panel-border) 50%, transparent);transition:background-color .12s;position:relative}.yii-debug-tl-bar{background:var(--yii-debug-cat-db);border-radius:4px;min-width:2px;height:18px;position:absolute;top:50%;transform:translateY(-50%);box-shadow:inset 0 1px #ffffff1f,0 1px 2px #0000002e}.yii-debug-tl-bar-duration{color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums;white-space:nowrap;justify-self:end;font-weight:500}.yii-debug-tl-memory{grid-template-columns:minmax(180px, var(--yii-debug-tl-label-col)) 1fr auto;padding:var(--yii-debug-space-2) var(--yii-debug-space-3);border-top:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);grid-column:1/3;align-items:center;display:grid}.yii-debug-tl-memory-label{text-transform:uppercase;letter-spacing:.06em}.yii-debug-tl-memory-track{border-radius:var(--yii-debug-radius-sm);background:color-mix(in srgb, var(--yii-debug-panel-surface), transparent 50%);width:100%;color:var(--yii-debug-panel-primary);position:relative;overflow:hidden}.yii-debug-tl-memory-track svg{width:100%;height:100%;display:block}.yii-debug-tl-memory-peak{margin-left:var(--yii-debug-space-2);color:var(--yii-debug-panel-text);font-weight:700}@media (max-width:640px){.yii-debug-tl,.yii-debug-tl-rows{--yii-debug-tl-label-col:52%;grid-template-columns:minmax(140px, var(--yii-debug-tl-label-col)) minmax(0, 1fr)}.yii-debug-tl-label{padding:7px var(--yii-debug-space-2);gap:6px}.yii-debug-tl-memory{grid-template-columns:minmax(140px, var(--yii-debug-tl-label-col)) minmax(0, 1fr)}.yii-debug-tl-memory-peak{text-align:right;grid-column:2;margin:4px 0 0}}.yii-debug .yii-debug-skip-link{top:var(--yii-debug-space-2);left:var(--yii-debug-space-2);z-index:1000;padding:var(--yii-debug-space-2) var(--yii-debug-space-3);border:2px solid var(--yii-debug-focus-ring);border-radius:var(--yii-debug-radius-md);background:var(--yii-debug-panel-surface);box-shadow:var(--yii-debug-shadow-md);color:var(--yii-debug-panel-text);transform:translateY(calc(-100% - var(--yii-debug-space-4)));font-weight:700;position:fixed}.yii-debug .yii-debug-skip-link:focus-visible{text-decoration:none;transform:translateY(0)}.yii-debug .yii-debug-brand-chip-control{border:var(--yii-debug-border);background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted);cursor:pointer;justify-content:center}.yii-debug .yii-debug-brand-chip-control:hover,.yii-debug .yii-debug-brand-chip-control:focus-visible{border-color:color-mix(in srgb, var(--yii-debug-panel-primary), transparent 50%);color:var(--yii-debug-panel-primary-strong);transform:translateY(-1px)}.yii-debug :is(h2,h3).yii-debug-has-permalink{align-items:center;gap:var(--yii-debug-space-1);scroll-margin-block-start:var(--yii-debug-space-5);display:flex}.yii-debug .yii-debug-heading-permalink{border-radius:var(--yii-debug-radius-sm);min-width:32px;min-height:32px;color:var(--yii-debug-panel-muted);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-sm);opacity:0;transition:color var(--yii-debug-duration-fast) ease, opacity var(--yii-debug-duration-fast) ease, background var(--yii-debug-duration-fast) ease;flex:none;justify-content:center;align-items:center;line-height:1;text-decoration:none;display:inline-flex}.yii-debug .yii-debug-heading-permalink:hover,.yii-debug .yii-debug-heading-permalink:focus-visible{background:var(--yii-debug-panel-bg-accent);color:var(--yii-debug-panel-primary-strong);opacity:1;text-decoration:none}.yii-debug :is(h2,h3).yii-debug-has-permalink:hover>.yii-debug-heading-permalink,.yii-debug :is(h2,h3).yii-debug-has-permalink:focus-within>.yii-debug-heading-permalink,.yii-debug :is(h2,h3).yii-debug-deep-link-target>.yii-debug-heading-permalink{opacity:1}.yii-debug .yii-debug-deep-link-target{outline:2px solid var(--yii-debug-focus-ring);outline-offset:4px}.yii-debug .yii-debug-db-n1-summary{gap:var(--yii-debug-space-2);margin-bottom:var(--yii-debug-space-4);padding:var(--yii-debug-space-3) var(--yii-debug-space-4);border:1px solid color-mix(in srgb, var(--yii-debug-warning), transparent 55%);border-radius:var(--yii-debug-radius-md);background:color-mix(in srgb, var(--yii-debug-warning), transparent 92%);display:grid}.yii-debug .yii-debug-db-n1-heading{justify-content:space-between;align-items:baseline;gap:var(--yii-debug-space-2);flex-wrap:wrap;display:flex}.yii-debug .yii-debug-db-n1-list{gap:var(--yii-debug-space-2);flex-wrap:wrap;margin:0;padding:0;list-style:none;display:flex}.yii-debug .yii-debug-db-n1-link,.yii-debug .yii-debug-db-n1-row-link{align-items:center;gap:var(--yii-debug-space-2);min-height:var(--yii-debug-control-min-size);padding:var(--yii-debug-space-1) var(--yii-debug-space-2);border:1px solid color-mix(in srgb, var(--yii-debug-warning), transparent 60%);border-radius:var(--yii-debug-radius-pill);background:var(--yii-debug-panel-surface);color:color-mix(in srgb, var(--yii-debug-warning) 72%, var(--yii-debug-panel-text));font-size:var(--yii-debug-fs-xs);font-weight:700;text-decoration:none;display:inline-flex}:is(.yii-debug .yii-debug-db-n1-link,.yii-debug .yii-debug-db-n1-row-link):hover,:is(.yii-debug .yii-debug-db-n1-link,.yii-debug .yii-debug-db-n1-row-link):focus-visible,:is(.yii-debug .yii-debug-db-n1-link,.yii-debug .yii-debug-db-n1-row-link)[aria-current=true]{background:color-mix(in srgb, var(--yii-debug-warning), transparent 84%);color:var(--yii-debug-panel-text);text-decoration:none}.yii-debug .yii-debug-db-n1-fingerprint{max-width:min(56ch,65vw);font-family:var(--yii-debug-font-mono);text-overflow:ellipsis;white-space:nowrap;font-weight:500;display:inline-block;overflow:hidden}.yii-debug .yii-debug-db-n1-row-link{margin-top:var(--yii-debug-space-3)}.yii-debug .yii-debug-grid-db .yii-debug-trace{gap:0;display:grid}.yii-debug .yii-debug-grid-db .yii-debug-trace a{align-items:center;min-height:24px;display:inline-flex}.yii-debug .yii-debug-db-n1-clear[hidden]{display:none}.yii-debug .yii-debug-compare-form{gap:var(--yii-debug-space-3);margin-bottom:var(--yii-debug-space-4);grid-template-columns:repeat(auto-fit,minmax(min(100%,16rem),1fr));align-items:end;display:grid}.yii-debug .yii-debug-compare-overview{gap:var(--yii-debug-space-3);margin-bottom:var(--yii-debug-space-4);grid-template-columns:repeat(auto-fit,minmax(min(100%,12rem),1fr));display:grid}.yii-debug .yii-debug-compare-grid{min-width:42rem}.yii-debug :is(.yii-debug-delta-up,.yii-debug-delta-down,.yii-debug-delta-neutral){padding:2px var(--yii-debug-space-2);border:var(--yii-debug-border);border-radius:var(--yii-debug-radius-pill);font-family:var(--yii-debug-font-mono);font-size:var(--yii-debug-fs-xs);font-variant-numeric:tabular-nums;align-items:center;font-weight:700;display:inline-flex}.yii-debug .yii-debug-delta-up{border-color:color-mix(in srgb, var(--yii-debug-danger), transparent 60%);background:color-mix(in srgb, var(--yii-debug-danger), transparent 90%);color:var(--yii-debug-danger)}.yii-debug .yii-debug-delta-down{border-color:color-mix(in srgb, var(--yii-debug-success), transparent 60%);background:color-mix(in srgb, var(--yii-debug-success), transparent 90%);color:var(--yii-debug-success)}.yii-debug .yii-debug-delta-neutral{background:var(--yii-debug-panel-surface-muted);color:var(--yii-debug-panel-muted)}@container yii-debug-main (width<=560px){.yii-debug .yii-debug-section-header,.yii-debug .yii-debug-panel-heading{flex-direction:column;align-items:stretch}.yii-debug .yii-debug-filter-input{width:100%;max-width:none}}@media (max-width:767px){.yii-debug .yii-debug-brand-chip-copy .yii-debug-brand-label{text-overflow:ellipsis;white-space:nowrap;max-width:5.5rem;overflow:hidden}}@media (pointer:coarse){.yii-debug .yii-debug-grid-db .yii-debug-trace a{min-width:44px;min-height:44px}.yii-debug :is(.yii-debug-btn,.yii-debug-brand-chip,.yii-debug-cell-more-toggle,.yii-debug-dropdown-item,.yii-debug-filter-input,.yii-debug-heading-permalink,.yii-debug-server-group-summary,.yii-debug-tab-link){min-height:44px}}@media (forced-colors:active){.yii-debug .yii-debug-card,.yii-debug .yii-debug-brand-bar,.yii-debug .yii-debug-brand-chip,.yii-debug .yii-debug-nav,.yii-debug .yii-debug-table-wrap{box-shadow:none;border-color:canvastext}.yii-debug .yii-debug-nav-link.is-active,.yii-debug .yii-debug-tab-link.is-active{color:highlighttext;background:highlight;border-color:highlight}.yii-debug :focus-visible{outline-color:highlight}} diff --git a/resources/src/styles/main.css b/resources/src/styles/main.css index 6970123..25ee489 100644 --- a/resources/src/styles/main.css +++ b/resources/src/styles/main.css @@ -218,6 +218,7 @@ display: flex; flex-wrap: wrap; align-items: center; + justify-content: flex-end; gap: var(--yii-debug-space-2); margin-bottom: var(--yii-debug-space-4); } @@ -722,7 +723,12 @@ } dd { + display: flex; + min-width: 0; + flex-wrap: wrap; + align-items: center; margin: 0; + gap: var(--yii-debug-space-2); color: var(--yii-debug-panel-text); font-family: var(--yii-debug-font-mono); font-size: var(--yii-debug-fs-sm); @@ -735,24 +741,33 @@ display: flex; flex-wrap: wrap; align-items: center; - gap: var(--yii-debug-space-2) var(--yii-debug-space-4); - padding: 10px var(--yii-debug-space-4); + gap: var(--yii-debug-space-2) var(--yii-debug-space-5); + padding: var(--yii-debug-space-3) var(--yii-debug-space-4); color: var(--yii-debug-panel-muted); font-family: var(--yii-debug-font-mono); font-size: var(--yii-debug-fs-xs); } + .yii-debug-request-overview-config { + display: inline-flex; + flex-wrap: wrap; + align-items: center; + gap: var(--yii-debug-space-2); + margin-inline-start: auto; + } + .yii-debug-request-overview-meta-item { display: inline-flex; min-width: 0; - align-items: baseline; - gap: 6px; + flex-direction: column; + align-items: flex-start; } .yii-debug-request-overview-meta-label { + margin-bottom: 2px; color: var(--yii-debug-panel-muted); font-family: var(--yii-debug-font-sans); - font-size: 0.625rem; + font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; @@ -762,6 +777,9 @@ overflow: hidden; max-width: 34ch; color: var(--yii-debug-panel-text); + font-family: var(--yii-debug-font-mono); + font-size: var(--yii-debug-fs-sm); + font-weight: 600; text-overflow: ellipsis; white-space: nowrap; } @@ -851,7 +869,6 @@ h2 { margin: 0; - font-size: var(--yii-debug-fs-lg); } } @@ -1055,7 +1072,6 @@ } .yii-debug-server-group-body > .yii-debug-mini-toolbar { - justify-content: flex-end; margin: 0; padding: var(--yii-debug-space-2) var(--yii-debug-space-4); border-bottom: var(--yii-debug-border); @@ -1167,242 +1183,42 @@ } } - .yii-debug-route-inventory-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--yii-debug-space-3); - margin-bottom: var(--yii-debug-space-2); - } - - .yii-debug-route-inventory-title { - display: flex; - align-items: baseline; - gap: var(--yii-debug-space-2); - - h2 { - margin: 0; - font-size: var(--yii-debug-fs-lg); - } - } - - .yii-debug-route-inventory-count { - color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - font-variant-numeric: tabular-nums; - } - - .yii-debug-route-inventory-context { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--yii-debug-space-3); - margin-bottom: var(--yii-debug-space-3); - } - - .yii-debug-route-inventory-provenance { - margin: 0; - color: var(--yii-debug-panel-muted); - font-size: var(--yii-debug-fs-xs); - } - - .yii-debug-route-inventory-badges, - .yii-debug-route-methods { - display: inline-flex; - flex-wrap: wrap; - align-items: center; - gap: var(--yii-debug-space-1); - } - - .yii-debug-route-inventory-error { - margin-bottom: var(--yii-debug-space-3); - - p { - margin: 0; - } - } - - .yii-debug-route-ledger { - overflow: hidden; - border: var(--yii-debug-border); - border-radius: var(--yii-debug-radius-md); - container: routes / inline-size; - } - - .yii-debug-route-columns, - .yii-debug-route-summary { - display: grid; - grid-template-columns: 24px 88px minmax(0, 1fr) minmax(0, 1fr) 132px; - align-items: center; - gap: var(--yii-debug-space-3); - padding: var(--yii-debug-space-3) var(--yii-debug-space-4); - } - - .yii-debug-route-columns { - background: var(--yii-debug-panel-surface-muted); - color: var(--yii-debug-panel-muted); - font-size: var(--yii-debug-fs-xs); - font-weight: 600; - } - - .yii-debug-route-list { - margin: 0; - padding: 0; - list-style: none; - - > li { - border-top: var(--yii-debug-border); - } - - > [data-yii-debug-route-match="true"] { - box-shadow: inset 3px 0 0 var(--yii-debug-success); - } - } - - .yii-debug-route-summary { - min-height: 56px; - cursor: pointer; - list-style: none; - - &:hover { - background: var(--yii-debug-panel-surface-hover); - } - - &:focus-visible { - outline: 2px solid var(--yii-debug-focus-ring); - outline-offset: -3px; - } - - &::-webkit-details-marker { - display: none; - } - - .yii-debug-disclosure-hint { - font-size: 0.625rem; - text-align: right; - } - } - - .yii-debug-route-order { - color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - font-variant-numeric: tabular-nums; - } - - .yii-debug-route-method { - display: inline-flex; - align-items: center; - min-height: 20px; - padding: 1px 7px; - border-radius: var(--yii-debug-radius-pill); - font-family: var(--yii-debug-font-mono); - font-size: 0.6875rem; - font-weight: 700; - letter-spacing: 0.02em; - line-height: 1; - white-space: nowrap; + .yii-debug-route-resolution { + margin: var(--yii-debug-space-3) var(--yii-debug-space-4) var(--yii-debug-space-4); } - .yii-debug-route-methods-any { + .yii-debug-route-resolution-message { + margin: 0 0 var(--yii-debug-space-3); color: var(--yii-debug-panel-muted); - font-size: var(--yii-debug-fs-xs); - } - - .yii-debug-route-pattern, - .yii-debug-route-identity { - min-width: 0; - overflow-wrap: anywhere; - white-space: normal; - font-size: var(--yii-debug-fs-xs); - - code { - white-space: normal; - } - } - - .yii-debug-route-pattern, - .yii-debug-route-identity code { - padding: 0; - background: transparent; - } - - .yii-debug-route-identity { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: var(--yii-debug-space-2); - } - - .yii-debug-route-metadata { - margin: 0; - padding: var(--yii-debug-space-4); - border-top: var(--yii-debug-border); - background: var(--yii-debug-panel-surface); font-size: var(--yii-debug-fs-sm); - - > div { - display: grid; - grid-template-columns: 120px minmax(0, 1fr); - gap: var(--yii-debug-space-4); - } - - > div + div { - margin-top: var(--yii-debug-space-3); - } - - dt { - color: var(--yii-debug-panel-muted); - } - - dd { - min-width: 0; - margin: 0; - overflow-wrap: anywhere; - font-family: var(--yii-debug-font-mono); - } } - @container routes (width < 640px) { - .yii-debug-route-columns { - display: none; - } + .yii-debug-route-trace { + min-width: 560px; + } - .yii-debug-route-summary { - grid-template-columns: 24px 72px minmax(0, 1fr); - gap: var(--yii-debug-space-2); + .yii-debug-table.yii-debug-route-trace { + thead th, + tbody td { + text-align: center; } - .yii-debug-route-identity { - grid-column: 3; + thead th:nth-child(2), + tbody td:nth-child(2) { + text-align: start; } - .yii-debug-route-summary .yii-debug-disclosure-hint { - grid-column: 3; - text-align: left; + thead th:first-child, + tbody td:first-child { + width: 56px; } - .yii-debug-route-metadata > div { - grid-template-columns: minmax(0, 1fr); - gap: var(--yii-debug-space-1); + thead th:last-child, + tbody td:last-child { + width: 160px; } } - .yii-debug-route-resolution { - margin-top: var(--yii-debug-space-4); - } - - .yii-debug-route-resolution-message { - margin: 0 0 var(--yii-debug-space-3); - color: var(--yii-debug-panel-muted); - font-size: var(--yii-debug-fs-sm); - } - - .yii-debug-route-trace { - min-width: 560px; - } - .yii-debug-panel-heading { display: flex; align-items: flex-end; @@ -1474,17 +1290,6 @@ .yii-debug-panel-heading { align-items: flex-start; } - - .yii-debug-route-inventory-header, - .yii-debug-route-inventory-context { - align-items: flex-start; - flex-direction: column; - } - - .yii-debug-route-filter { - width: 100%; - max-width: none; - } } /** @@ -1837,11 +1642,9 @@ * a soft border, and a near-transparent wash of the same hue. */ .yii-debug-request-hero-method, - .yii-debug-route-method, .yii-debug-snapshot-status, .yii-debug-level-chip, .yii-debug-db-type, - .yii-debug-mail-status, .yii-debug-dump-type, .yii-debug-badge:is( .yii-debug-status-2xx, @@ -1928,58 +1731,6 @@ border-left-color: var(--yii-debug-danger); } - /** - * Router-specific callout — surfaces the resolver explanation captured for - * the routing pass (for example the "no matching rule" trace message). - */ - .yii-debug-router-callout { - flex-direction: column; - } - - .yii-debug-router-callout-message { - margin: 0; - } - - /** - * Always-on resolved route / dispatched action readout at the top of the - * Current Route pane — keeps the pane informative even when the resolver - * left no trace messages to replay. - */ - .yii-debug-router-summary { - display: grid; - grid-template-columns: max-content minmax(0, 1fr); - gap: 4px var(--yii-debug-space-3); - margin: 0 0 var(--yii-debug-space-3); - font-size: var(--yii-debug-fs-sm); - - dt { - color: var(--yii-debug-panel-muted); - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; - font-size: var(--yii-debug-fs-xs); - align-self: center; - } - - dd { - min-width: 0; - margin: 0; - overflow-wrap: anywhere; - } - - code { - display: inline; - min-width: 0; - padding: 1px 8px; - border-radius: var(--yii-debug-radius-sm); - background: var(--yii-debug-panel-surface); - color: var(--yii-debug-panel-text); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-sm); - overflow-wrap: anywhere; - } - } - /** * ------------------------------------------------------------------- * Tables + GridView integration. @@ -2194,7 +1945,8 @@ /* Shared accent label for collapsed and populated Request sections. */ .yii-debug-disclosure-title, - .yii-debug-request-section-title { + .yii-debug-request-section-title, + .yii-debug-diagnostic-heading-copy h2 { color: var(--yii-debug-panel-primary-strong); font-family: var(--yii-debug-font-mono); font-size: var(--yii-debug-fs-xs); @@ -3717,91 +3469,93 @@ /** * ------------------------------------------------------------------- - * Mail panel — developer inbox. - * The panel opens with the shared `.yii-debug-grid-summary` strip; - * each ListView item is a self-contained "email card" with a 3-zone - * header (avatar / sender+subject+preview / status+time+download), a - * recipient pill row, a paper-card body, and a collapsible - * raw-headers
block. Avatars use a deterministic HSL hue - * derived from `crc32(email)` so the same sender always renders the - * same color. - * ------------------------------------------------------------------- - */ - - .yii-debug-mail-list { + * Queue panel — request-scoped roster of `Yii::$app->queue->push()` / + * exec / error events. The default view is a paginated GridView with + * filter row (status, driver, component, job class, job id); each row + * is a one-click jump to a dedicated detail page that renders the full + * payload tree, error block, schedule chips, and a live lifecycle pill + * (waiting / reserved / done) queried once against the originating + * queue component. Card markup (`.yii-debug-queue-card*`) lives further + * below and powers the detail page only. + * ------------------------------------------------------------------- + */ + .yii-debug-queue-grid-job { display: flex; flex-direction: column; - gap: var(--yii-debug-space-3); - margin: 0; - padding: 0; - list-style: none; + gap: 2px; + line-height: 1.2; } - .yii-debug-mail-list-item { - display: block; - margin: 0; - padding: 0; - list-style: none; + .yii-debug-queue-grid-job-link { + color: var(--yii-debug-text-strong); + font-weight: 600; + text-decoration: none; + white-space: nowrap; } - .yii-debug-mail-pager { - margin-top: var(--yii-debug-space-4); + .yii-debug-queue-grid-job-link:hover { + text-decoration: underline; } - .yii-debug-mail-card { - position: relative; - display: grid; - grid-template-columns: 1fr; - gap: var(--yii-debug-space-4); - padding: var(--yii-debug-space-4) var(--yii-debug-space-4) - var(--yii-debug-space-3); + .yii-debug-queue-grid-job-namespace { + color: var(--yii-debug-text-muted); + font-family: var(--yii-debug-font-mono); + font-size: 0.78rem; + white-space: nowrap; + } + + .yii-debug-grid-queue .yii-debug-queue-status, + .yii-debug-grid-queue .yii-debug-queue-driver { + white-space: nowrap; + } + + .yii-debug-queue-job-page { + display: flex; + flex-direction: column; + gap: var(--yii-debug-space-3); + } + + .yii-debug-queue-job-head { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: var(--yii-debug-space-2); + } + + .yii-debug-queue-card { + display: flex; + flex-direction: column; + gap: var(--yii-debug-space-2); + padding: var(--yii-debug-space-4); border: var(--yii-debug-border); border-radius: var(--yii-debug-radius-md); background: var(--yii-debug-panel-surface); - box-shadow: var(--yii-debug-shadow-sm); - transition: - transform 160ms ease, - border-color 160ms ease, - box-shadow 160ms ease; - - &:hover { - border-color: color-mix( - in srgb, - var(--yii-debug-panel-primary), - transparent 60% - ); - transform: translateY(-1px); - box-shadow: - var(--yii-debug-shadow-sm), - 0 12px 32px - color-mix(in srgb, var(--yii-debug-panel-primary), transparent 88%); - } } - .yii-debug-mail-card-head { + .yii-debug-queue-card-head { display: grid; grid-template-columns: auto 1fr auto; gap: var(--yii-debug-space-3); align-items: flex-start; } - .yii-debug-mail-avatar { - --mail-hue: 210; + .yii-debug-queue-avatar { + --queue-hue: 210; display: inline-flex; align-items: center; justify-content: center; - width: 44px; - height: 44px; + width: 40px; + height: 40px; flex: none; - border-radius: 50%; + border-radius: var(--yii-debug-radius-md); background: linear-gradient( 135deg, - hsl(var(--mail-hue) 72% 52%), - hsl(calc(var(--mail-hue) + 28) 65% 40%) + hsl(var(--queue-hue) 72% 52%), + hsl(calc(var(--queue-hue) + 28) 65% 40%) ); box-shadow: - 0 2px 10px hsl(var(--mail-hue) 60% 40% / 32%), + 0 2px 10px hsl(var(--queue-hue) 60% 40% / 32%), inset 0 1px 0 rgb(255 255 255 / 22%); color: #fff; font-family: var(--yii-debug-font-mono); @@ -3810,25 +3564,14 @@ letter-spacing: -0.01em; } - .yii-debug-mail-headline { + .yii-debug-queue-headline { min-width: 0; display: flex; flex-direction: column; - gap: 4px; - } - - .yii-debug-mail-from { - margin: 0; - color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - letter-spacing: 0.02em; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + gap: 2px; } - .yii-debug-mail-subject { + .yii-debug-queue-class { margin: 0; color: var(--yii-debug-panel-text); font-family: var(--yii-debug-font-sans); @@ -3836,22 +3579,17 @@ font-weight: 700; letter-spacing: -0.01em; line-height: 1.25; + word-break: break-all; } - .yii-debug-mail-preview { - margin: 2px 0 0; + .yii-debug-queue-namespace { color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-sans); - font-size: var(--yii-debug-fs-sm); - line-height: 1.4; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 1; - line-clamp: 1; - -webkit-box-orient: vertical; + font-family: var(--yii-debug-font-mono); + font-size: var(--yii-debug-fs-xs); + letter-spacing: 0.02em; } - .yii-debug-mail-meta { + .yii-debug-queue-meta-pills { display: flex; flex-direction: column; align-items: flex-end; @@ -3859,11 +3597,10 @@ flex: none; } - .yii-debug-mail-status { + .yii-debug-queue-status { display: inline-flex; align-items: center; - gap: 6px; - padding: 3px 10px 3px 8px; + padding: 3px 10px; border-radius: var(--yii-debug-radius-pill); font-family: var(--yii-debug-font-mono); font-size: var(--yii-debug-fs-xs); @@ -3873,1252 +3610,387 @@ line-height: 1; } - /* The "state LED": tinted by the same hue the chip formula consumes. */ - .yii-debug-mail-status-dot { - width: 7px; - height: 7px; - border-radius: 50%; - flex: none; - background: var(--yii-debug-hue, var(--yii-debug-panel-muted)); - box-shadow: 0 0 0 3px - color-mix( - in srgb, - var(--yii-debug-hue, var(--yii-debug-panel-muted)), - transparent 80% - ); + .yii-debug-queue-status-queued { + color: var(--yii-debug-info); + background: color-mix(in srgb, var(--yii-debug-info), transparent 86%); } - .yii-debug-mail-status-ok { - --yii-debug-hue: var(--yii-debug-success); + .yii-debug-queue-status-done { + color: var(--yii-debug-success); + background: color-mix(in srgb, var(--yii-debug-success), transparent 86%); } - .yii-debug-mail-status-fail { - --yii-debug-hue: var(--yii-debug-danger); + .yii-debug-queue-status-failed { + color: var(--yii-debug-danger); + background: color-mix(in srgb, var(--yii-debug-danger), transparent 86%); } - .yii-debug-mail-time { - color: var(--yii-debug-panel-muted); + .yii-debug-queue-driver { + display: inline-flex; + align-items: center; + padding: 3px 10px; + border-radius: var(--yii-debug-radius-pill); + border: var(--yii-debug-border); font-family: var(--yii-debug-font-mono); font-size: var(--yii-debug-fs-xs); - letter-spacing: 0.02em; + font-weight: 600; + letter-spacing: 0.05em; + text-transform: uppercase; + line-height: 1; cursor: help; } - .yii-debug-mail-download { - display: inline-flex; - align-items: center; - justify-content: center; - width: 30px; - height: 30px; - border: var(--yii-debug-border); - border-radius: var(--yii-debug-radius-md); - background: var(--yii-debug-panel-surface-muted); - color: var(--yii-debug-panel-link); - text-decoration: none; - transition: - background-color 140ms ease, - border-color 140ms ease, - color 140ms ease, - transform 140ms ease; + .yii-debug-queue-driver-is-sync { + color: var(--yii-debug-success); + background: color-mix(in srgb, var(--yii-debug-success), transparent 92%); + border-color: color-mix(in srgb, var(--yii-debug-success), transparent 70%); + } - svg { - width: 16px; - height: 16px; - } + .yii-debug-queue-driver-is-async { + color: var(--yii-debug-warning); + background: color-mix(in srgb, var(--yii-debug-warning), transparent 92%); + border-color: color-mix(in srgb, var(--yii-debug-warning), transparent 70%); + } - &:hover, - &:focus-visible { - border-color: color-mix( - in srgb, - var(--yii-debug-panel-primary), - transparent 50% - ); - background: color-mix( - in srgb, - var(--yii-debug-panel-primary), - transparent 88% - ); - color: var(--yii-debug-panel-primary-strong); - transform: translateY(-1px); - text-decoration: none; - } + .yii-debug-queue-hint { + margin: var(--yii-debug-space-3) 0; + padding: var(--yii-debug-space-3) var(--yii-debug-space-4); + border-radius: var(--yii-debug-radius-md); + border-left: 3px solid var(--yii-debug-warning); + background: color-mix(in srgb, var(--yii-debug-warning), transparent 94%); + color: var(--yii-debug-panel-text); + font-size: var(--yii-debug-fs-sm); + line-height: 1.5; } - .yii-debug-mail-recipients { + .yii-debug-queue-tree { display: flex; flex-direction: column; - gap: 8px; + gap: 4px; padding: var(--yii-debug-space-3); - border: 1px solid - color-mix(in srgb, var(--yii-debug-panel-border) 60%, transparent); - border-radius: var(--yii-debug-radius-md); + border-radius: var(--yii-debug-radius-sm); background: color-mix( in srgb, - var(--yii-debug-panel-surface-muted), - transparent 40% + var(--yii-debug-panel-text), + transparent 96% ); - } - - .yii-debug-mail-recipient-group { - display: grid; - grid-template-columns: 64px 1fr; - gap: var(--yii-debug-space-3); - align-items: center; - } - - .yii-debug-mail-recipient-label { - color: var(--yii-debug-panel-muted); font-family: var(--yii-debug-font-mono); font-size: var(--yii-debug-fs-xs); - font-weight: 700; - letter-spacing: 0.14em; - text-align: right; - - &[data-role="reply"] { - color: var(--yii-debug-info); - } - - &[data-role="bcc"] { - color: var(--yii-debug-warning); - } + line-height: 1.6; } - .yii-debug-mail-recipient-pills { + .yii-debug-queue-tree-row { display: flex; + align-items: baseline; + gap: 8px; flex-wrap: wrap; - gap: 6px; - min-width: 0; } - .yii-debug-mail-recipient-pill { - display: inline-flex; - align-items: center; - max-width: 100%; - padding: 3px 10px; - border: 1px solid var(--yii-debug-panel-border); - border-radius: var(--yii-debug-radius-pill); - background: var(--yii-debug-panel-surface); - color: var(--yii-debug-panel-text); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - line-height: 1.4; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .yii-debug-mail-body { - padding: var(--yii-debug-space-4); - border: 1px solid - color-mix(in srgb, var(--yii-debug-panel-border) 60%, transparent); - border-radius: var(--yii-debug-radius-md); - background: var(--yii-debug-panel-surface-muted); + .yii-debug-queue-tree-key { color: var(--yii-debug-panel-text); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-sm); - line-height: 1.55; - white-space: pre-wrap; - overflow-wrap: break-word; + font-weight: 700; } - .yii-debug-mail-body-empty { + .yii-debug-queue-tree-key::after { + content: ":"; + margin-left: 1px; color: var(--yii-debug-panel-muted); - font-style: italic; - text-align: center; + font-weight: 400; } - .yii-debug-mail-tech { - border: 1px dashed - color-mix(in srgb, var(--yii-debug-panel-border) 70%, transparent); - border-radius: var(--yii-debug-radius-md); - background: color-mix( - in srgb, - var(--yii-debug-panel-surface-muted), - transparent 30% - ); - overflow: hidden; - - > summary { - display: flex; - align-items: center; - gap: 8px; - padding: 10px var(--yii-debug-space-3); - color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - letter-spacing: 0.06em; - list-style: none; - cursor: pointer; - user-select: none; - - &::-webkit-details-marker { - display: none; - } - - &:hover { - color: var(--yii-debug-panel-text); - } - } - - &[open] > summary { - border-bottom: 1px solid - color-mix(in srgb, var(--yii-debug-panel-border) 50%, transparent); - color: var(--yii-debug-panel-text); - } - - &[open] .yii-debug-mail-tech-chevron { - transform: rotate(180deg); - } - } - - .yii-debug-mail-tech-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - color: var(--yii-debug-panel-primary); - - svg { - width: 100%; - height: 100%; - } - } - - .yii-debug-mail-tech-label { - text-transform: uppercase; - font-weight: 700; - } - - .yii-debug-mail-tech-charset { - margin-left: 4px; - padding: 2px 8px; - border: 1px solid var(--yii-debug-panel-border); - border-radius: var(--yii-debug-radius-pill); - background: var(--yii-debug-panel-surface); - color: var(--yii-debug-panel-text); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - font-weight: 600; - letter-spacing: 0.04em; - text-transform: none; - } - - .yii-debug-mail-tech-chevron { - margin-left: auto; - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - color: var(--yii-debug-panel-muted); - transition: transform 200ms ease; - - svg { - width: 100%; - height: 100%; - } - } - - .yii-debug-mail-headers { - margin: 0; - padding: var(--yii-debug-space-3) var(--yii-debug-space-4); - background: var(--yii-debug-panel-code-bg); - color: var(--yii-debug-panel-text); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - line-height: 1.5; - white-space: pre-wrap; - overflow-wrap: break-word; - overflow-x: auto; - } - - /* Compact the head row when the panel iframe gets narrow. */ - @media (width <= 640px) { - .yii-debug-mail-card-head { - grid-template-columns: auto 1fr; - } - - .yii-debug-mail-meta { - grid-column: 1 / -1; - flex-direction: row; - align-items: center; - justify-content: flex-start; - } - - .yii-debug-mail-recipient-group { - grid-template-columns: 1fr; - gap: 4px; - } - - .yii-debug-mail-recipient-label { - text-align: left; - } - } - - /** - * ------------------------------------------------------------------- - * Queue panel — request-scoped roster of `Yii::$app->queue->push()` / - * exec / error events. The default view is a paginated GridView with - * filter row (status, driver, component, job class, job id); each row - * is a one-click jump to a dedicated detail page that renders the full - * payload tree, error block, schedule chips, and a live lifecycle pill - * (waiting / reserved / done) queried once against the originating - * queue component. Card markup (`.yii-debug-queue-card*`) lives further - * below and powers the detail page only. - * ------------------------------------------------------------------- - */ - .yii-debug-queue-grid-job { - display: flex; - flex-direction: column; - gap: 2px; - line-height: 1.2; - } - - .yii-debug-queue-grid-job-link { - color: var(--yii-debug-text-strong); - font-weight: 600; - text-decoration: none; - white-space: nowrap; - } - - .yii-debug-queue-grid-job-link:hover { - text-decoration: underline; - } - - .yii-debug-queue-grid-job-namespace { - color: var(--yii-debug-text-muted); - font-family: var(--yii-debug-font-mono); - font-size: 0.78rem; - white-space: nowrap; - } - - .yii-debug-grid-queue .yii-debug-queue-status, - .yii-debug-grid-queue .yii-debug-queue-driver { - white-space: nowrap; - } - - .yii-debug-queue-job-page { - display: flex; - flex-direction: column; - gap: var(--yii-debug-space-3); - } - - .yii-debug-queue-job-head { - align-items: center; - display: flex; - flex-wrap: wrap; - gap: var(--yii-debug-space-2); - } - - .yii-debug-queue-card { - display: flex; - flex-direction: column; - gap: var(--yii-debug-space-2); - padding: var(--yii-debug-space-4); - border: var(--yii-debug-border); - border-radius: var(--yii-debug-radius-md); - background: var(--yii-debug-panel-surface); - } - - .yii-debug-queue-card-head { - display: grid; - grid-template-columns: auto 1fr auto; - gap: var(--yii-debug-space-3); - align-items: flex-start; - } - - .yii-debug-queue-avatar { - --queue-hue: 210; - - display: inline-flex; - align-items: center; - justify-content: center; - width: 40px; - height: 40px; - flex: none; - border-radius: var(--yii-debug-radius-md); - background: linear-gradient( - 135deg, - hsl(var(--queue-hue) 72% 52%), - hsl(calc(var(--queue-hue) + 28) 65% 40%) - ); - box-shadow: - 0 2px 10px hsl(var(--queue-hue) 60% 40% / 32%), - inset 0 1px 0 rgb(255 255 255 / 22%); - color: #fff; - font-family: var(--yii-debug-font-mono); - font-size: 16px; - font-weight: 800; - letter-spacing: -0.01em; - } - - .yii-debug-queue-headline { - min-width: 0; - display: flex; - flex-direction: column; - gap: 2px; - } - - .yii-debug-queue-class { - margin: 0; - color: var(--yii-debug-panel-text); - font-family: var(--yii-debug-font-sans); - font-size: var(--yii-debug-fs-lg); - font-weight: 700; - letter-spacing: -0.01em; - line-height: 1.25; - word-break: break-all; - } - - .yii-debug-queue-namespace { - color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - letter-spacing: 0.02em; - } - - .yii-debug-queue-meta-pills { - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 6px; - flex: none; - } - - .yii-debug-queue-status { - display: inline-flex; - align-items: center; - padding: 3px 10px; - border-radius: var(--yii-debug-radius-pill); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - font-weight: 600; - letter-spacing: 0.06em; - text-transform: uppercase; - line-height: 1; - } - - .yii-debug-queue-status-queued { - color: var(--yii-debug-info); - background: color-mix(in srgb, var(--yii-debug-info), transparent 86%); - } - - .yii-debug-queue-status-done { - color: var(--yii-debug-success); - background: color-mix(in srgb, var(--yii-debug-success), transparent 86%); - } - - .yii-debug-queue-status-failed { - color: var(--yii-debug-danger); - background: color-mix(in srgb, var(--yii-debug-danger), transparent 86%); - } - - .yii-debug-queue-driver { - display: inline-flex; - align-items: center; - padding: 3px 10px; - border-radius: var(--yii-debug-radius-pill); - border: var(--yii-debug-border); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - font-weight: 600; - letter-spacing: 0.05em; - text-transform: uppercase; - line-height: 1; - cursor: help; - } - - .yii-debug-queue-driver-is-sync { - color: var(--yii-debug-success); - background: color-mix(in srgb, var(--yii-debug-success), transparent 92%); - border-color: color-mix(in srgb, var(--yii-debug-success), transparent 70%); - } - - .yii-debug-queue-driver-is-async { - color: var(--yii-debug-warning); - background: color-mix(in srgb, var(--yii-debug-warning), transparent 92%); - border-color: color-mix(in srgb, var(--yii-debug-warning), transparent 70%); - } - - .yii-debug-queue-hint { - margin: var(--yii-debug-space-3) 0; - padding: var(--yii-debug-space-3) var(--yii-debug-space-4); - border-radius: var(--yii-debug-radius-md); - border-left: 3px solid var(--yii-debug-warning); - background: color-mix(in srgb, var(--yii-debug-warning), transparent 94%); - color: var(--yii-debug-panel-text); - font-size: var(--yii-debug-fs-sm); - line-height: 1.5; - } - - .yii-debug-queue-tree { - display: flex; - flex-direction: column; - gap: 4px; - padding: var(--yii-debug-space-3); - border-radius: var(--yii-debug-radius-sm); - background: color-mix( - in srgb, - var(--yii-debug-panel-text), - transparent 96% - ); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - line-height: 1.6; - } - - .yii-debug-queue-tree-row { - display: flex; - align-items: baseline; - gap: 8px; - flex-wrap: wrap; - } - - .yii-debug-queue-tree-key { - color: var(--yii-debug-panel-text); - font-weight: 700; - } - - .yii-debug-queue-tree-key::after { - content: ":"; - margin-left: 1px; - color: var(--yii-debug-panel-muted); - font-weight: 400; - } - - .yii-debug-queue-tree-type { - color: var(--yii-debug-panel-muted); - font-size: var(--yii-debug-fs-xs); - text-transform: uppercase; - letter-spacing: 0.05em; - padding: 1px 6px; - border-radius: 3px; - background: color-mix( - in srgb, - var(--yii-debug-panel-text), - transparent 92% - ); - } - - .yii-debug-queue-tree-class { - color: var(--yii-debug-info); - font-weight: 600; - cursor: help; - } - - .yii-debug-queue-tree-meta { - color: var(--yii-debug-panel-muted); - } - - .yii-debug-queue-tree-truncated { - color: var(--yii-debug-warning); - font-size: var(--yii-debug-fs-xs); - text-transform: uppercase; - letter-spacing: 0.05em; - padding: 1px 6px; - border-radius: 3px; - background: color-mix(in srgb, var(--yii-debug-warning), transparent 86%); - } - - .yii-debug-queue-tree-value { - word-break: break-all; - } - - .yii-debug-queue-tree-value-string { - color: var(--yii-debug-success); - } - - .yii-debug-queue-tree-value-number { - color: var(--yii-debug-info); - } - - .yii-debug-queue-tree-value-bool { - color: var(--yii-debug-warning); - font-weight: 600; - } - - .yii-debug-queue-tree-value-null { - color: var(--yii-debug-panel-muted); - font-style: italic; - } - - .yii-debug-queue-tree-collapse { - display: flex; - flex-direction: column; - gap: 4px; - } - - .yii-debug-queue-tree-summary { - display: inline-flex; - align-items: baseline; - gap: 8px; - flex-wrap: wrap; - cursor: pointer; - list-style: revert; - padding: 2px 0; - } - - .yii-debug-queue-tree-summary::-webkit-details-marker { - color: var(--yii-debug-panel-muted); - } - - .yii-debug-queue-tree-children { - display: flex; - flex-direction: column; - gap: 4px; - margin-left: var(--yii-debug-space-4); - padding-left: var(--yii-debug-space-3); - border-left: 1px dashed - color-mix(in srgb, var(--yii-debug-panel-text), transparent 80%); - } - - .yii-debug-queue-time { - color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - letter-spacing: 0.02em; - } - - .yii-debug-queue-payload { - margin: 0; - } - - .yii-debug-queue-error { - padding: var(--yii-debug-space-3); - border-radius: var(--yii-debug-radius-sm); - border-left: 3px solid var(--yii-debug-danger); - background: color-mix(in srgb, var(--yii-debug-danger), transparent 92%); - color: var(--yii-debug-danger); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - line-height: 1.5; - } - - .yii-debug-queue-meta { - display: flex; - flex-wrap: wrap; - gap: var(--yii-debug-space-2); - padding-top: var(--yii-debug-space-2); - border-top: var(--yii-debug-border); - } - - .yii-debug-queue-meta-item { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 2px 10px; - border-radius: var(--yii-debug-radius-pill); - background: color-mix( - in srgb, - var(--yii-debug-panel-text), - transparent 94% - ); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - line-height: 1.4; - } - - .yii-debug-queue-meta-label { - color: var(--yii-debug-panel-muted); - text-transform: uppercase; - letter-spacing: 0.05em; - font-size: var(--yii-debug-fs-xs); - } - - .yii-debug-queue-meta-value { - color: var(--yii-debug-panel-text); - font-weight: 600; - } - - /** - * ------------------------------------------------------------------- - * Dump panel — `Yii::debug()` snapshots rendered as a GridView (category - * + message). The message column wraps each value in a small "card" head - * (#index, type badge, time, trace) plus a body that hosts PHP's - * `highlight_string()` output. Dark-mode pastel overrides soften the - * hardcoded inline-style hexes that PHP emits per token. - * Light theme keeps the original highlight palette untouched (the user - * confirmed it reads fine on white surfaces); only dark theme gets the - * pastel overrides because the saturated hexes burn the eyes on dark. - * ------------------------------------------------------------------- - */ - .yii-debug-dump { - border: var(--yii-debug-border); - border-radius: var(--yii-debug-radius-md); - background: var(--yii-debug-panel-surface); - overflow: hidden; - } - - .yii-debug-dump-card-head { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: var(--yii-debug-space-2); - padding: 6px var(--yii-debug-space-3); - border-bottom: var(--yii-debug-border); - background: var(--yii-debug-panel-surface-muted); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - color: var(--yii-debug-panel-muted); - } - - .yii-debug-dump-index { - color: var(--yii-debug-panel-primary-strong); - font-weight: 700; - } - - /* Type badge: shape only — border/background/ink come from the shared chip formula over the hue. */ - .yii-debug-dump-type { - display: inline-flex; - padding: 1px 8px; - border-radius: var(--yii-debug-radius-pill); - font-size: var(--yii-debug-fs-xs); - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; - - --yii-debug-hue: var(--yii-debug-panel-primary); - - &[data-type="array"] { - --yii-debug-hue: var(--yii-debug-info); - } - - &[data-type="string"] { - --yii-debug-hue: var(--yii-debug-success); - } - - &[data-type="number"] { - --yii-debug-hue: var(--yii-debug-warning); - } - - &[data-type="bool"], - &[data-type="null"] { - --yii-debug-hue: var(--yii-debug-panel-muted); - } - } - - .yii-debug-dump-meta { - margin-left: auto; - display: inline-flex; - align-items: center; - gap: var(--yii-debug-space-2); - } - - .yii-debug-dump-time { - color: var(--yii-debug-panel-muted); - font-variant-numeric: tabular-nums; - } - - .yii-debug-dump-trace { - color: var(--yii-debug-panel-link); - } - - .yii-debug-dump-body { - padding: var(--yii-debug-space-3); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-sm); - line-height: 1.5; - overflow-x: auto; - - code { - display: block; - padding: 0; - background: transparent; - white-space: pre; - } - } - - /** - * The data-attribute lives on ``, which is an *ancestor* of `.yii-debug`, - * so we use `[…] &` to compile to `[data-yii-debug-theme="dark"] .yii-debug …` - * (selector reads outside-in, matching the actual DOM). - */ - [data-yii-debug-theme="dark"] & .yii-debug-dump-body { - span[style*="color: #DD0000"], - span[style*="color:#DD0000"] { - color: #fca5a5 !important; /* strings → pastel red */ - } - - span[style*="color: #0000BB"], - span[style*="color:#0000BB"] { - color: #93c5fd !important; /* default → pastel blue */ - } - - span[style*="color: #007700"], - span[style*="color:#007700"] { - color: #86efac !important; /* keywords → pastel green */ - } - - span[style*="color: #FF8000"], - span[style*="color:#FF8000"] { - color: #fdba74 !important; /* comments → pastel orange */ - } - - span[style*="color: #000000"], - span[style*="color:#000000"] { - color: var( - --yii-debug-panel-text - ) !important; /* black text → panel text */ - } - } - - /** - * ------------------------------------------------------------------- - * Asset Bundles panel — dependency graph + asset inventory. - * Top "stats strip" gives a build-summary feel (bundles / css / js / - * depend-edges). Each bundle renders as a card with a 3-zone header - * (icon / short class + FQCN suffix / count chips) and a 2-column - * body splitting into "Files" (CSS + JS rows tagged by filetype) and - * "Wiring" (sourcePath / basePath / baseUrl + a `Depends on N` block - * whose pills are real anchors that jump to the dependency's card). - * The card uses :target to flag the bundle that the user just jumped - * to via a depend pill. - * ------------------------------------------------------------------- - */ - - .yii-debug-asset-stats { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: var(--yii-debug-space-3); - margin: 0 0 var(--yii-debug-space-4); - } - - .yii-debug-asset-stat { - display: flex; - align-items: center; - gap: 10px; - padding: var(--yii-debug-space-3) var(--yii-debug-space-4); - border: var(--yii-debug-border); - border-radius: var(--yii-debug-radius-md); - background: var(--yii-debug-panel-surface); - box-shadow: var(--yii-debug-shadow-sm); - - &[data-kind="bundles"] .yii-debug-asset-stat-icon { - color: var(--yii-debug-panel-primary); - } - - &[data-kind="css"] .yii-debug-asset-stat-icon { - color: var(--yii-debug-info); - } - - &[data-kind="js"] .yii-debug-asset-stat-icon { - color: var(--yii-debug-warning); - } - - &[data-kind="deps"] .yii-debug-asset-stat-icon { - color: var(--yii-debug-success); - } - } - - .yii-debug-asset-stat-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 26px; - height: 26px; - flex: none; - - svg { - width: 100%; - height: 100%; - } - } - - .yii-debug-asset-stat-value { - color: var(--yii-debug-panel-text); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xl); - font-weight: 800; - line-height: 1; - } - - .yii-debug-asset-stat-label { - margin-left: auto; - color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - letter-spacing: 0.04em; - text-transform: uppercase; - } - - .yii-debug-asset-list { - display: flex; - flex-direction: column; - gap: var(--yii-debug-space-3); - margin: 0; - padding: 0; - list-style: none; - } - - .yii-debug-asset-list-item { - display: block; - margin: 0; - padding: 0; - list-style: none; - } - - .yii-debug-asset-card { - position: relative; - display: flex; - flex-direction: column; - gap: var(--yii-debug-space-4); - padding: var(--yii-debug-space-4); - border: var(--yii-debug-border); - border-radius: var(--yii-debug-radius-md); - background: var(--yii-debug-panel-surface); - box-shadow: var(--yii-debug-shadow-sm); - scroll-margin-top: var(--yii-debug-space-4); - transition: - transform 160ms ease, - border-color 160ms ease, - box-shadow 160ms ease; - - &:hover { - border-color: color-mix( - in srgb, - var(--yii-debug-panel-primary), - transparent 60% - ); - transform: translateY(-1px); - box-shadow: - var(--yii-debug-shadow-sm), - 0 12px 32px - color-mix(in srgb, var(--yii-debug-panel-primary), transparent 88%); - } - - &:target { - border-color: var(--yii-debug-panel-primary); - box-shadow: - 0 0 0 3px - color-mix(in srgb, var(--yii-debug-panel-primary), transparent 78%), - var(--yii-debug-shadow-sm); - } - } - - .yii-debug-asset-card-head { - display: grid; - grid-template-columns: auto 1fr auto; - gap: var(--yii-debug-space-3); - align-items: center; - } - - .yii-debug-asset-card-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 38px; - height: 38px; - flex: none; - border-radius: var(--yii-debug-radius-md); - background: linear-gradient( - 135deg, - color-mix(in srgb, var(--yii-debug-panel-primary), transparent 78%), - color-mix(in srgb, var(--yii-debug-info), transparent 78%) - ); - color: var(--yii-debug-panel-primary-strong); - - svg { - width: 22px; - height: 22px; - } - } - - .yii-debug-asset-card-title { - min-width: 0; - display: flex; - flex-direction: column; - gap: 2px; - } - - .yii-debug-asset-card-name { - margin: 0; - color: var(--yii-debug-panel-text); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-lg); - font-weight: 700; - letter-spacing: -0.01em; - line-height: 1.2; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .yii-debug-asset-card-fqcn { - margin: 0; - color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - letter-spacing: 0.01em; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .yii-debug-asset-card-meta { - display: inline-flex; - flex-wrap: nowrap; - justify-content: flex-end; - align-items: center; - gap: 5px; - flex: none; - } - - .yii-debug-asset-chip { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 2px 8px; - border: 1px solid var(--yii-debug-panel-border); - border-radius: var(--yii-debug-radius-pill); - background: var(--yii-debug-panel-surface-muted); + .yii-debug-queue-tree-type { color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-mono); font-size: var(--yii-debug-fs-xs); - font-weight: 600; - letter-spacing: 0.04em; text-transform: uppercase; - white-space: nowrap; - - strong { - color: var(--yii-debug-panel-text); - font-weight: 800; - } + letter-spacing: 0.05em; + padding: 1px 6px; + border-radius: 3px; + background: color-mix( + in srgb, + var(--yii-debug-panel-text), + transparent 92% + ); } - .yii-debug-asset-chip-css { - border-color: color-mix(in srgb, var(--yii-debug-info), transparent 60%); - background: color-mix(in srgb, var(--yii-debug-info), transparent 90%); + .yii-debug-queue-tree-class { color: var(--yii-debug-info); + font-weight: 600; + cursor: help; + } - strong { - color: var(--yii-debug-info); - } + .yii-debug-queue-tree-meta { + color: var(--yii-debug-panel-muted); } - .yii-debug-asset-chip-js { - border-color: color-mix(in srgb, var(--yii-debug-warning), transparent 60%); - background: color-mix(in srgb, var(--yii-debug-warning), transparent 90%); + .yii-debug-queue-tree-truncated { color: var(--yii-debug-warning); + font-size: var(--yii-debug-fs-xs); + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 1px 6px; + border-radius: 3px; + background: color-mix(in srgb, var(--yii-debug-warning), transparent 86%); + } - strong { - color: var(--yii-debug-warning); - } + .yii-debug-queue-tree-value { + word-break: break-all; } - .yii-debug-asset-chip-deps { - border-color: color-mix(in srgb, var(--yii-debug-success), transparent 60%); - background: color-mix(in srgb, var(--yii-debug-success), transparent 90%); + .yii-debug-queue-tree-value-string { color: var(--yii-debug-success); + } - strong { - color: var(--yii-debug-success); - } + .yii-debug-queue-tree-value-number { + color: var(--yii-debug-info); } - .yii-debug-asset-card-body { - display: grid; - grid-template-columns: minmax(180px, 2fr) minmax(0, 3fr); - gap: var(--yii-debug-space-4); + .yii-debug-queue-tree-value-bool { + color: var(--yii-debug-warning); + font-weight: 600; } - .yii-debug-asset-card-body[data-cols="1"] { - grid-template-columns: 1fr; + .yii-debug-queue-tree-value-null { + color: var(--yii-debug-panel-muted); + font-style: italic; } - .yii-debug-asset-section { + .yii-debug-queue-tree-collapse { display: flex; flex-direction: column; - gap: var(--yii-debug-space-3); - min-width: 0; + gap: 4px; } - .yii-debug-asset-section-title { - margin: 0; + .yii-debug-queue-tree-summary { + display: inline-flex; + align-items: baseline; + gap: 8px; + flex-wrap: wrap; + cursor: pointer; + list-style: revert; + padding: 2px 0; + } + + .yii-debug-queue-tree-summary::-webkit-details-marker { color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-sans); - font-size: var(--yii-debug-fs-xs); - font-weight: 700; - letter-spacing: 0.14em; - text-transform: uppercase; } - .yii-debug-asset-files { + .yii-debug-queue-tree-children { display: flex; flex-direction: column; gap: 4px; + margin-left: var(--yii-debug-space-4); + padding-left: var(--yii-debug-space-3); + border-left: 1px dashed + color-mix(in srgb, var(--yii-debug-panel-text), transparent 80%); + } - + .yii-debug-asset-files { - margin-top: 4px; - } + .yii-debug-queue-time { + color: var(--yii-debug-panel-muted); + font-family: var(--yii-debug-font-mono); + font-size: var(--yii-debug-fs-xs); + letter-spacing: 0.02em; } - .yii-debug-asset-file { - display: grid; - grid-template-columns: auto 1fr; - align-items: center; - gap: 6px; - padding: 4px 8px; - border: 1px solid - color-mix(in srgb, var(--yii-debug-panel-border) 60%, transparent); - border-radius: var(--yii-debug-radius-md); - background: var(--yii-debug-panel-surface-muted); + .yii-debug-queue-payload { + margin: 0; + } + + .yii-debug-queue-error { + padding: var(--yii-debug-space-3); + border-radius: var(--yii-debug-radius-sm); + border-left: 3px solid var(--yii-debug-danger); + background: color-mix(in srgb, var(--yii-debug-danger), transparent 92%); + color: var(--yii-debug-danger); font-family: var(--yii-debug-font-mono); font-size: var(--yii-debug-fs-xs); - min-width: 0; + line-height: 1.5; + } + + .yii-debug-queue-meta { + display: flex; + flex-wrap: wrap; + gap: var(--yii-debug-space-2); + padding-top: var(--yii-debug-space-2); + border-top: var(--yii-debug-border); } - .yii-debug-asset-file-type { + .yii-debug-queue-meta-item { display: inline-flex; align-items: center; - justify-content: center; - min-width: 30px; - padding: 0 5px; + gap: 6px; + padding: 2px 10px; border-radius: var(--yii-debug-radius-pill); + background: color-mix( + in srgb, + var(--yii-debug-panel-text), + transparent 94% + ); font-family: var(--yii-debug-font-mono); font-size: var(--yii-debug-fs-xs); - font-weight: 700; - letter-spacing: 0.02em; + line-height: 1.4; } - .yii-debug-asset-file-type-css { - background: color-mix(in srgb, var(--yii-debug-info), transparent 84%); - color: var(--yii-debug-info); + .yii-debug-queue-meta-label { + color: var(--yii-debug-panel-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + font-size: var(--yii-debug-fs-xs); } - .yii-debug-asset-file-type-js { - background: color-mix(in srgb, var(--yii-debug-warning), transparent 84%); - color: var(--yii-debug-warning); + .yii-debug-queue-meta-value { + color: var(--yii-debug-panel-text); + font-weight: 600; } - .yii-debug-asset-file-name { - color: var(--yii-debug-panel-text); + /** + * ------------------------------------------------------------------- + * Dump panel — `Yii::debug()` snapshots rendered as a GridView (category + * + message). The message column wraps each value in a small "card" head + * (#index, type badge, time, trace) plus a body that hosts PHP's + * `highlight_string()` output. Dark-mode pastel overrides soften the + * hardcoded inline-style hexes that PHP emits per token. + * Light theme keeps the original highlight palette untouched (the user + * confirmed it reads fine on white surfaces); only dark theme gets the + * pastel overrides because the saturated hexes burn the eyes on dark. + * ------------------------------------------------------------------- + */ + .yii-debug-dump { + border: var(--yii-debug-border); + border-radius: var(--yii-debug-radius-md); + background: var(--yii-debug-panel-surface); overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; } - .yii-debug-asset-wiring { + .yii-debug-dump-card-head { display: flex; - flex-direction: column; - gap: 4px; - margin: 0; - } - - .yii-debug-asset-wiring-row { - display: grid; - grid-template-columns: 56px 1fr; - align-items: baseline; - gap: 8px; - padding: 6px 10px; - border: 1px solid - color-mix(in srgb, var(--yii-debug-panel-border) 60%, transparent); - border-radius: var(--yii-debug-radius-md); + flex-wrap: wrap; + align-items: center; + gap: var(--yii-debug-space-2); + padding: 6px var(--yii-debug-space-3); + border-bottom: var(--yii-debug-border); background: var(--yii-debug-panel-surface-muted); - } - - .yii-debug-asset-wiring-label { - margin: 0; - color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - font-weight: 700; - letter-spacing: 0.12em; - text-transform: uppercase; - } - - .yii-debug-asset-wiring-value { - margin: 0; - color: var(--yii-debug-panel-text); font-family: var(--yii-debug-font-mono); font-size: var(--yii-debug-fs-xs); - word-break: break-all; + color: var(--yii-debug-panel-muted); } - .yii-debug-asset-depends { - display: flex; - flex-direction: column; - gap: 8px; - padding: 10px var(--yii-debug-space-3); - border: 1px dashed - color-mix(in srgb, var(--yii-debug-success), transparent 50%); - border-radius: var(--yii-debug-radius-md); - background: color-mix(in srgb, var(--yii-debug-success), transparent 92%); + .yii-debug-dump-index { + color: var(--yii-debug-panel-primary-strong); + font-weight: 700; } - .yii-debug-asset-depends-label { - color: var(--yii-debug-success); - font-family: var(--yii-debug-font-sans); + /* Type badge: shape only — border/background/ink come from the shared chip formula over the hue. */ + .yii-debug-dump-type { + display: inline-flex; + padding: 1px 8px; + border-radius: var(--yii-debug-radius-pill); font-size: var(--yii-debug-fs-xs); font-weight: 700; - letter-spacing: 0.14em; + letter-spacing: 0.04em; text-transform: uppercase; - } - .yii-debug-asset-depends-list { - display: flex; - flex-wrap: wrap; - gap: 6px; + --yii-debug-hue: var(--yii-debug-panel-primary); + + &[data-type="array"] { + --yii-debug-hue: var(--yii-debug-info); + } + + &[data-type="string"] { + --yii-debug-hue: var(--yii-debug-success); + } + + &[data-type="number"] { + --yii-debug-hue: var(--yii-debug-warning); + } + + &[data-type="bool"], + &[data-type="null"] { + --yii-debug-hue: var(--yii-debug-panel-muted); + } } - .yii-debug-asset-depend { + .yii-debug-dump-meta { + margin-left: auto; display: inline-flex; align-items: center; - gap: 5px; - padding: 3px 10px 3px 8px; - border: 1px solid - color-mix(in srgb, var(--yii-debug-success), transparent 50%); - border-radius: var(--yii-debug-radius-pill); - background: var(--yii-debug-panel-surface); - color: var(--yii-debug-success); + gap: var(--yii-debug-space-2); + } + + .yii-debug-dump-time { + color: var(--yii-debug-panel-muted); + font-variant-numeric: tabular-nums; + } + + .yii-debug-dump-trace { + color: var(--yii-debug-panel-link); + } + + .yii-debug-php-dump { + text-align: start; font-family: var(--yii-debug-font-mono); - font-size: var(--yii-debug-fs-xs); - font-weight: 600; - text-decoration: none; - transition: - background-color 140ms ease, - color 140ms ease, - transform 140ms ease, - border-color 140ms ease; + font-size: var(--yii-debug-fs-sm); + line-height: 1.5; + overflow-x: auto; - &:hover, - &:focus-visible { - border-color: var(--yii-debug-success); - background: color-mix(in srgb, var(--yii-debug-success), transparent 86%); - color: var(--yii-debug-success); - text-decoration: none; - transform: translateY(-1px); + pre { + margin: 0; + background: transparent; } - } - .yii-debug-asset-depend-icon { - font-size: 12px; - line-height: 1; - opacity: 0.85; + code { + display: block; + padding: 0; + background: transparent; + white-space: pre; + } } - .yii-debug-asset-depend-name { - white-space: nowrap; + .yii-debug-dump-body { + padding: var(--yii-debug-space-3); + font-family: var(--yii-debug-font-mono); + font-size: var(--yii-debug-fs-sm); + line-height: 1.5; + overflow-x: auto; + + code { + display: block; + padding: 0; + background: transparent; + white-space: pre; + } } - @media (width <= 720px) { - .yii-debug-asset-stats { - grid-template-columns: repeat(2, minmax(0, 1fr)); + /** + * The data-attribute lives on ``, which is an *ancestor* of `.yii-debug`, + * so we use `[…] &` to compile to `[data-yii-debug-theme="dark"] .yii-debug …` + * (selector reads outside-in, matching the actual DOM). + */ + [data-yii-debug-theme="dark"] & :is(.yii-debug-dump-body, .yii-debug-php-dump) { + span[style*="color: #DD0000"], + span[style*="color:#DD0000"] { + color: #e5c07b !important; /* strings → warm sand, calmer than red on the dark surface */ } - .yii-debug-asset-card-body { - grid-template-columns: 1fr; + span[style*="color: #0000BB"], + span[style*="color:#0000BB"] { + color: #93c5fd !important; /* default → pastel blue */ + } + + span[style*="color: #007700"], + span[style*="color:#007700"] { + color: #86efac !important; /* keywords → pastel green */ } - .yii-debug-asset-card-head { - grid-template-columns: auto 1fr; + span[style*="color: #FF8000"], + span[style*="color:#FF8000"] { + color: #fdba74 !important; /* comments → pastel orange */ } - .yii-debug-asset-card-meta { - grid-column: 1 / -1; - justify-content: flex-start; - max-width: 100%; + span[style*="color: #000000"], + span[style*="color:#000000"] { + color: var( + --yii-debug-panel-text + ) !important; /* black text → panel text */ } } @@ -5771,13 +4643,6 @@ /* -- Readout card grid (hero stats) --------------------------------- */ - .yii-debug-readout { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); - gap: var(--yii-debug-space-3); - margin-bottom: var(--yii-debug-space-6); - } - .yii-debug-readout-card { position: relative; display: flex; @@ -5843,48 +4708,6 @@ } /* Four L-shaped corner ticks per card, like gun sights on a schematic. */ - .yii-debug-readout-corner { - position: absolute; - width: 9px; - height: 9px; - border: 1.5px solid - color-mix(in srgb, var(--yii-debug-panel-primary), transparent 40%); - pointer-events: none; - transition: border-color 0.15s ease; - - &[data-corner="tl"] { - top: 8px; - left: 8px; - border-right: 0; - border-bottom: 0; - } - - &[data-corner="tr"] { - top: 8px; - right: 8px; - border-left: 0; - border-bottom: 0; - } - - &[data-corner="bl"] { - bottom: 8px; - left: 8px; - border-right: 0; - border-top: 0; - } - - &[data-corner="br"] { - bottom: 8px; - right: 8px; - border-left: 0; - border-top: 0; - } - } - - .yii-debug-readout-card:hover .yii-debug-readout-corner { - border-color: var(--yii-debug-panel-primary); - } - .yii-debug-readout-label { position: relative; display: block; @@ -5930,29 +4753,6 @@ text-transform: lowercase; } - .yii-debug-readout-chip { - display: inline-flex; - align-items: center; - padding: 2px 7px; - border-radius: var(--yii-debug-radius-sm); - background: color-mix( - in srgb, - var(--yii-debug-panel-primary), - transparent 80% - ); - color: var(--yii-debug-panel-primary); - font-family: var(--yii-debug-font-mono); - font-size: 0.625rem; - font-weight: 700; - letter-spacing: 0.08em; - text-transform: uppercase; - } - - .yii-debug-readout-chip-muted { - background: var(--yii-debug-panel-surface-muted); - color: var(--yii-debug-panel-muted); - } - /* -- Extension status strip ----------------------------------------- */ .yii-debug-ext-strip { @@ -6039,187 +4839,6 @@ opacity: 0.55; } - /* -- Definition list (application details) -------------------------- */ - - .yii-debug-dl { - margin: 0; - padding: 0; - border: var(--yii-debug-border); - border-radius: var(--yii-debug-radius-md); - background: var(--yii-debug-panel-surface); - overflow: hidden; - } - - .yii-debug-dl-row { - display: grid; - grid-template-columns: minmax(160px, 200px) 1fr; - gap: var(--yii-debug-space-4); - align-items: baseline; - padding: 10px var(--yii-debug-space-4); - border-bottom: 1px solid var(--yii-debug-panel-border); - - &:last-child { - border-bottom: 0; - } - - dt { - margin: 0; - color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-mono); - font-size: 0.6875rem; - font-weight: 500; - letter-spacing: 0.14em; - text-transform: uppercase; - } - - dd { - margin: 0; - color: var(--yii-debug-panel-text); - font-family: var(--yii-debug-font-mono); - font-size: 0.8125rem; - overflow-wrap: break-word; - } - } - - /* -- Composer namespace ledger (installed extensions) --------------- */ - - .yii-debug-package-groups { - display: grid; - gap: var(--yii-debug-space-3); - } - - .yii-debug-package-group { - border: var(--yii-debug-border); - border-radius: var(--yii-debug-radius-md); - background: var(--yii-debug-panel-surface); - overflow: hidden; - } - - .yii-debug-package-group-header { - display: flex; - align-items: baseline; - justify-content: space-between; - gap: var(--yii-debug-space-3); - padding: 9px 12px; - border-bottom: var(--yii-debug-border); - background: color-mix( - in srgb, - var(--yii-debug-panel-surface-muted), - transparent 18% - ); - } - - .yii-debug-package-vendor { - margin: 0; - color: var(--yii-debug-panel-primary); - font-family: var(--yii-debug-font-mono); - font-size: 0.75rem; - font-weight: 700; - letter-spacing: 0.02em; - } - - .yii-debug-package-group-count { - color: var(--yii-debug-panel-muted); - font-family: var(--yii-debug-font-mono); - font-size: 0.625rem; - letter-spacing: 0.08em; - text-transform: uppercase; - } - - .yii-debug-package-list { - display: grid; - grid-template-columns: repeat( - auto-fit, - minmax(min(100%, 220px), 1fr) - ); - margin: 0; - padding: 0; - } - - .yii-debug-package-row { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: var(--yii-debug-space-2); - align-items: baseline; - min-width: 0; - padding: 7px 11px; - border-right: 1px solid - color-mix(in srgb, var(--yii-debug-panel-border), transparent 28%); - border-bottom: 1px solid - color-mix(in srgb, var(--yii-debug-panel-border), transparent 28%); - - dt, - dd { - margin: 0; - font-family: var(--yii-debug-font-mono); - font-size: 0.6875rem; - line-height: 1.35; - } - } - - .yii-debug-package-name { - min-width: 0; - color: var(--yii-debug-panel-text); - font-weight: 500; - overflow-wrap: anywhere; - } - - .yii-debug-package-version { - color: var(--yii-debug-panel-primary); - font-variant-numeric: tabular-nums; - font-weight: 700; - white-space: nowrap; - } - - /* -- Terminal-prompt CTA -------------------------------------------- */ - - .yii-debug-cta { - display: inline-flex; - align-items: center; - gap: var(--yii-debug-space-2); - padding: 10px 18px 10px 14px; - border: var(--yii-debug-border); - border-radius: var(--yii-debug-radius-md); - background: var(--yii-debug-panel-surface); - color: var(--yii-debug-panel-text); - font-family: var(--yii-debug-font-mono); - font-size: 0.8125rem; - font-weight: 500; - letter-spacing: 0.02em; - text-decoration: none; - transition: - border-color 0.15s ease, - color 0.15s ease, - transform 0.15s ease, - box-shadow 0.15s ease; - - &:hover, - &:focus { - border-color: var(--yii-debug-panel-primary); - color: var(--yii-debug-panel-primary); - box-shadow: 0 0 0 3px - color-mix(in srgb, var(--yii-debug-panel-primary), transparent 85%); - text-decoration: none; - transform: translateX(3px); - - .yii-debug-cta-prompt { - transform: translateX(2px); - } - } - } - - .yii-debug-cta-prompt { - color: var(--yii-debug-panel-primary); - font-weight: 700; - transition: transform 0.15s ease; - } - - .yii-debug-cta-external { - margin-left: auto; - color: var(--yii-debug-panel-muted); - font-size: 0.875rem; - } - /** * -- phpinfo shell — left TOC + search + sectioned content ------------- * Two-column grid (sticky module selector | main column). Normal browsing diff --git a/src/Helper/PhpHighlighter.php b/src/Helper/PhpHighlighter.php new file mode 100644 index 0000000..25d7c8b --- /dev/null +++ b/src/Helper/PhpHighlighter.php @@ -0,0 +1,33 @@ +]*>]*>)<\?php\s*~', '$1', $html); + + return (string) preg_replace('~^(
]*>)]*>~', '$1', $html);
+    }
+}
diff --git a/src/Panel/Asset/AssetBundleNormalizer.php b/src/Panel/Asset/AssetBundleNormalizer.php
deleted file mode 100644
index 630db37..0000000
--- a/src/Panel/Asset/AssetBundleNormalizer.php
+++ /dev/null
@@ -1,71 +0,0 @@
- $bundles Captured bundles in registration order.
-     */
-    public function normalize(array $bundles): AssetSummary
-    {
-        $views = [];
-        $totalCss = 0;
-        $totalJs = 0;
-        $totalDeps = 0;
-
-        foreach ($bundles as $bundle) {
-            $cssCount = count($bundle->css);
-            $jsCount = count($bundle->js);
-            $depsCount = count($bundle->depends);
-
-            $hasFiles = $cssCount + $jsCount > 0;
-            $hasWiring = $bundle->sourcePath !== '' || $bundle->basePath !== '' || $bundle->baseUrl !== '';
-            $hasDepends = $depsCount > 0;
-
-            $views[] = new AssetBundleView(
-                name: $bundle->name,
-                shortName: Fqcn::shortName($bundle->name),
-                namespace: Fqcn::namespacePart($bundle->name),
-                id: Text::camel2id($bundle->name),
-                sourcePath: $bundle->sourcePath,
-                basePath: $bundle->basePath,
-                baseUrl: $bundle->baseUrl,
-                css: $bundle->css,
-                js: $bundle->js,
-                depends: $bundle->depends,
-                cssCount: $cssCount,
-                jsCount: $jsCount,
-                depsCount: $depsCount,
-                hasFiles: $hasFiles,
-                hasWiring: $hasWiring,
-                hasDepends: $hasDepends,
-                bodyCols: ($hasFiles && ($hasWiring || $hasDepends)) ? 2 : 1,
-            );
-
-            $totalCss += $cssCount;
-            $totalJs += $jsCount;
-            $totalDeps += $depsCount;
-        }
-
-        return new AssetSummary(
-            bundles: $views,
-            totalBundles: count($views),
-            totalCss: $totalCss,
-            totalJs: $totalJs,
-            totalDeps: $totalDeps,
-        );
-    }
-}
diff --git a/src/Panel/Asset/AssetBundleView.php b/src/Panel/Asset/AssetBundleView.php
deleted file mode 100644
index 7311859..0000000
--- a/src/Panel/Asset/AssetBundleView.php
+++ /dev/null
@@ -1,84 +0,0 @@
- CSS file labels, with single-element wrapper arrays already unwrapped to their first
-         * element.
-         */
-        public array $css,
-        /**
-         * @var list JS file labels, with single-element wrapper arrays already unwrapped to their first
-         * element.
-         */
-        public array $js,
-        /**
-         * @var list Fully qualified class names of dependent bundles.
-         */
-        public array $depends,
-        /**
-         * Number of CSS files in {@see $css}.
-         */
-        public int $cssCount,
-        /**
-         * Number of JS files in {@see $js}.
-         */
-        public int $jsCount,
-        /**
-         * Number of declared dependencies in {@see $depends}.
-         */
-        public int $depsCount,
-        /**
-         * `true` when the bundle declares at least one CSS or JS file.
-         */
-        public bool $hasFiles,
-        /**
-         * `true` when at least one of {@see $sourcePath}, {@see $basePath}, {@see $baseUrl} is non-empty.
-         */
-        public bool $hasWiring,
-        /**
-         * `true` when the bundle declares at least one dependency.
-         */
-        public bool $hasDepends,
-        /**
-         * Layout hint: `2` when both Files and (Wiring or Depends) sections are visible, `1` otherwise.
-         */
-        public int $bodyCols,
-    ) {}
-}
diff --git a/src/Panel/Asset/AssetCardRenderer.php b/src/Panel/Asset/AssetCardRenderer.php
deleted file mode 100644
index 137418d..0000000
--- a/src/Panel/Asset/AssetCardRenderer.php
+++ /dev/null
@@ -1,288 +0,0 @@
-` ready to drop into the detail view.
-     *
-     * @param AssetBundleView $bundle Per-bundle view-model.
-     */
-    public static function renderCard(AssetBundleView $bundle): Article
-    {
-        $head = self::renderHead($bundle);
-        $bodyChildren = self::renderBody($bundle);
-
-        $articleChildren = [$head];
-
-        if ($bodyChildren !== []) {
-            $articleChildren[] = Div::tag()
-                ->addDataAttribute('cols', (string) $bundle->bodyCols)
-                ->class('yii-debug-asset-card-body')
-                ->html(...$bodyChildren);
-        }
-
-        return Article::tag()
-            ->class('yii-debug-asset-card')
-            ->id($bundle->id)
-            ->html(...$articleChildren);
-    }
-
-    /**
-     * Builds the optional card body (Files and Wiring sections), collapsing to an empty list when neither applies.
-     *
-     * The caller drops the body wrapper entirely when the returned list is empty.
-     *
-     * @return list
Body sections in render order. - */ - private static function renderBody(AssetBundleView $bundle): array - { - $body = []; - - if ($bundle->hasFiles) { - $body[] = self::renderFilesSection($bundle); - } - - if ($bundle->hasWiring || $bundle->hasDepends) { - $body[] = self::renderWiringSection($bundle); - } - - return $body; - } - - /** - * Renders a count chip such as `3 - * css`. - * - * Chooses between singular and plural based on `$count`. When `$plural` is `''`, the singular form is used for - * every count. - */ - private static function renderChip(string $modifier, int $count, string $singular, string $plural = ''): Span - { - $label = ($plural === '' || $count === 1) ? $singular : $plural; - - return Span::tag() - ->class("yii-debug-asset-chip yii-debug-asset-chip-{$modifier}") - ->html( - Strong::tag()->content((string) $count), - " {$label}", - ); - } - - /** - * Renders one dependency link with the short name visible, the full FQCN in `title`, and the `#anchor` derived - * from the dependency name through the same {@see Text::camel2id()} rule used for bundle card ids. - */ - private static function renderDepend(string $depName): A - { - $shortName = Fqcn::shortName($depName); - - return A::tag() - ->class('yii-debug-asset-depend') - ->html( - Span::tag() - ->class('yii-debug-asset-depend-icon') - ->addAriaAttribute('hidden', 'true') - ->content('↳'), - Span::tag() - ->class('yii-debug-asset-depend-name') - ->content($shortName), - ) - ->href('#' . Text::camel2id($depName)) - ->title($depName); - } - - /** - * Renders a single file row inside a Files list. - */ - private static function renderFile(string $type, string $label): Div - { - return Div::tag() - ->class('yii-debug-asset-file') - ->html( - Span::tag() - ->class("yii-debug-asset-file-type yii-debug-asset-file-type-{$type}") - ->content(".{$type}"), - Span::tag() - ->class('yii-debug-asset-file-name') - ->title($label) - ->content($label), - ); - } - - /** - * Renders the `Files` section with separate `
` lists for CSS and JS. - */ - private static function renderFilesSection(AssetBundleView $bundle): Section - { - $fileLists = []; - - if ($bundle->css !== []) { - $fileLists[] = Div::tag() - ->class('yii-debug-asset-files') - ->html( - ...array_map( - static fn(string $f): Div => self::renderFile('css', $f), - $bundle->css, - ), - ); - } - - if ($bundle->js !== []) { - $fileLists[] = Div::tag() - ->class('yii-debug-asset-files') - ->html( - ...array_map( - static fn(string $f): Div => self::renderFile('js', $f), - $bundle->js, - ), - ); - } - - return Section::tag() - ->class('yii-debug-asset-section') - ->html( - H3::tag() - ->class('yii-debug-asset-section-title') - ->content('Files'), - ...$fileLists, - ); - } - - /** - * Renders the card header: bundle icon, short name, namespace prefix, and CSS/JS/deps chips. - */ - private static function renderHead(AssetBundleView $bundle): Header - { - $titleChildren = [ - H2::tag() - ->class('yii-debug-asset-card-name') - ->content($bundle->shortName), - ]; - - if ($bundle->namespace !== '') { - $titleChildren[] = Span::tag() - ->class('yii-debug-asset-card-fqcn') - ->content("{$bundle->namespace}\\"); - } - - $metaChildren = []; - - if ($bundle->cssCount > 0) { - $metaChildren[] = self::renderChip('css', $bundle->cssCount, 'css'); - } - - if ($bundle->jsCount > 0) { - $metaChildren[] = self::renderChip('js', $bundle->jsCount, 'js'); - } - - if ($bundle->depsCount > 0) { - $metaChildren[] = self::renderChip('deps', $bundle->depsCount, 'dep', 'deps'); - } - - return Header::tag() - ->class('yii-debug-asset-card-head') - ->html( - Span::tag() - ->class('yii-debug-asset-card-icon') - ->addAriaAttribute('hidden', 'true') - ->html(Icon::render('asset')), - Div::tag() - ->class('yii-debug-asset-card-title') - ->html(...$titleChildren), - Div::tag() - ->class('yii-debug-asset-card-meta') - ->html(...$metaChildren), - ); - } - - /** - * Renders one `
label
value
` row inside the wiring `
`. - */ - private static function renderWiringRow(string $label, string $value): Div - { - return Div::tag() - ->class('yii-debug-asset-wiring-row') - ->html( - Dt::tag() - ->class('yii-debug-asset-wiring-label') - ->content($label), - Dd::tag() - ->class('yii-debug-asset-wiring-value') - ->content($value), - ); - } - - /** - * Renders the `Wiring` section combining the `source` / `base` / `url` rows and the `Depends on N` cross-link list. - * - * The caller guarantees that at least one of `hasWiring` / `hasDepends` is `true`. - */ - private static function renderWiringSection(AssetBundleView $bundle): Section - { - $sectionChildren = [ - H3::tag() - ->class('yii-debug-asset-section-title') - ->content('Wiring'), - ]; - - if ($bundle->hasWiring) { - $rows = []; - - if ($bundle->sourcePath !== '') { - $rows[] = self::renderWiringRow('source', $bundle->sourcePath); - } - - if ($bundle->basePath !== '') { - $rows[] = self::renderWiringRow('base', $bundle->basePath); - } - - if ($bundle->baseUrl !== '') { - $rows[] = self::renderWiringRow('url', $bundle->baseUrl); - } - - $sectionChildren[] = Dl::tag() - ->class('yii-debug-asset-wiring') - ->html(...$rows); - } - - if ($bundle->hasDepends) { - $sectionChildren[] = Div::tag() - ->class('yii-debug-asset-depends') - ->html( - Span::tag() - ->class('yii-debug-asset-depends-label') - ->content("Depends on {$bundle->depsCount}"), - Div::tag() - ->class('yii-debug-asset-depends-list') - ->html( - ...array_map( - static fn(string $dep): A => self::renderDepend($dep), - $bundle->depends, - ), - ), - ); - } - - return Section::tag() - ->class('yii-debug-asset-section') - ->html(...$sectionChildren); - } -} diff --git a/src/Panel/Asset/AssetPanel.php b/src/Panel/Asset/AssetPanel.php new file mode 100644 index 0000000..5d46962 --- /dev/null +++ b/src/Panel/Asset/AssetPanel.php @@ -0,0 +1,281 @@ + $data Decoded panel payload with `bundles` and `vite` keys. + * + * @return PanelView Vite overview, bundle inventory, and per-bundle detail groups. + */ + public function present(array $data): PanelView + { + $snapshot = AssetSnapshot::fromArray( + $data, + '$.asset', + ); + + $bundles = $snapshot->bundles(); + $vite = $snapshot->vite(); + + $count = count($bundles); + + $css = 0; + $js = 0; + $depends = 0; + + foreach ($bundles as $bundle) { + $css += count($bundle->css); + $js += count($bundle->js); + $depends += count($bundle->depends); + } + + $view = PanelView::create() + ->active($count > 0 || $vite !== null) + ->summary($count === 1 ? ' bundle' : ' bundles', $count) + ->summary(' css', $css) + ->summary(' js', $js) + ->summary($depends === 1 ? ' link' : ' links', $depends) + ->toolbar('Bundles', $count); + + if ($vite !== null) { + $view = self::vite($view, $vite); + } + + if ($count === 0) { + return $view->emptyState( + 'No asset bundles loaded', + [ + 'This request did not register any ', + PanelView::code('yii\\web\\AssetBundle'), + ' via ', + PanelView::code('register()'), + ', so the inventory is empty.', + ], + [ + 'Bundles appear here when something in the request actively pulls them in, typically a layout or ', + 'view that calls a bundle\'s ', + PanelView::code('register()'), + ', or any bundle reached transitively through the ', + PanelView::code('depends'), + ' chain.', + ], + ); + } + + $view = $view + ->heading('Registered bundles', true) + ->table( + ['#', 'Bundle', 'CSS', 'JS', 'Depends'], + self::inventory($bundles), + true, + [ + 0 => ColumnStyle::NUMBER, + 1 => ColumnStyle::IDENTIFIER, + 2 => ColumnStyle::NUMBER, + 3 => ColumnStyle::NUMBER, + 4 => ColumnStyle::NUMBER, + ], + ); + + foreach ($bundles as $index => $bundle) { + $view = $view + ->heading(sprintf('%d. %s', $index + 1, Fqcn::shortName($bundle->name)), true) + ->group($bundle->name, self::detail($bundle)); + } + + return $view; + } + + /** + * Builds the detail group of one bundle: wiring overview, declared files, and dependencies. + * + * @param AssetBundleRow $bundle Captured bundle to describe. + * + * @return PanelView Child view holding only the detail blocks of the bundle. + */ + private static function detail(AssetBundleRow $bundle): PanelView + { + $namespace = Fqcn::namespacePart($bundle->name); + + $view = PanelView::create()->overview( + [ + 'Class' => PanelView::code($bundle->name), + 'Namespace' => $namespace === '' ? self::PLACEHOLDER : $namespace, + 'Source path' => self::orPlaceholder($bundle->sourcePath), + 'Base path' => self::orPlaceholder($bundle->basePath), + 'Base URL' => self::orPlaceholder($bundle->baseUrl), + ], + true, + ); + + $files = []; + + foreach ($bundle->css as $file) { + $files[] = [PanelView::badge('css', Tone::INFO), $file]; + } + + foreach ($bundle->js as $file) { + $files[] = [PanelView::badge('js', Tone::WARNING), $file]; + } + + $view = $files === [] + ? $view->paragraph('This bundle declares no CSS or JavaScript files.') + : $view->table( + ['Type', 'File'], + $files, + true, + [ + 0 => ColumnStyle::PILL, + 1 => ColumnStyle::MONOSPACE, + ], + ); + + if ($bundle->depends === []) { + return $view; + } + + $rows = []; + + foreach ($bundle->depends as $depend) { + $rows[] = [$depend]; + } + + return $view->table(['Depends on'], $rows, true, [0 => ColumnStyle::IDENTIFIER]); + } + + /** + * Builds the inventory table rows in registration order. + * + * @param list $bundles Captured bundles in registration order. + * + * @return list> One row per bundle, matching the declared column order. + */ + private static function inventory(array $bundles): array + { + $rows = []; + + foreach ($bundles as $index => $bundle) { + $rows[] = [ + $index + 1, + $bundle->name, + count($bundle->css), + count($bundle->js), + count($bundle->depends), + ]; + } + + return $rows; + } + + /** + * Returns the value, or the placeholder when the capture left it empty. + * + * @param string $value Captured value. + * + * @return string Captured value, or the placeholder when empty. + */ + private static function orPlaceholder(string $value): string + { + return $value === '' ? self::PLACEHOLDER : $value; + } + + /** + * Appends the Vite bridge overview and its build-manifest chunks. + * + * @param PanelView $view View to extend. + * @param ViteManifest $vite Captured Vite bridge snapshot. + * + * @return PanelView View completed with the Vite section. + */ + private static function vite(PanelView $view, ViteManifest $vite): PanelView + { + $server = $vite->devServerUrl; + + $mode = match (true) { + $vite->devMode && $server !== null => "Dev server ({$server})", + $vite->devMode => 'Dev server', + default => 'Build manifest', + }; + + $view = $view + ->heading('Vite', true) + ->overview( + [ + 'Mode' => $mode, + 'Base URL' => self::orPlaceholder($vite->baseUrl), + 'Manifest' => self::orPlaceholder($vite->manifestPath), + ], + true, + ); + + if ($vite->chunks === []) { + return $vite->devMode + ? $view + : $view->paragraph('The Vite manifest is missing or empty; run the front-end build to populate it.'); + } + + $rows = []; + + foreach ($vite->chunks as $index => $chunk) { + $rows[] = [ + $index + 1, + PanelView::strong($chunk->name), + self::orPlaceholder($chunk->file), + $chunk->cssCount, + $chunk->imports, + $chunk->isEntry ? PanelView::badge('entry', Tone::SUCCESS) : self::PLACEHOLDER, + ]; + } + + return $view->table( + ['#', 'Chunk', 'Output', 'CSS', 'Imports', 'Entry'], + $rows, + true, + [ + 0 => ColumnStyle::NUMBER, + 1 => ColumnStyle::MONOSPACE, + 2 => ColumnStyle::MONOSPACE, + 3 => ColumnStyle::NUMBER, + 4 => ColumnStyle::NUMBER, + 5 => ColumnStyle::PILL, + ], + ); + } +} diff --git a/src/Panel/Asset/AssetSectionRenderer.php b/src/Panel/Asset/AssetSectionRenderer.php deleted file mode 100644 index ed5d7a5..0000000 --- a/src/Panel/Asset/AssetSectionRenderer.php +++ /dev/null @@ -1,83 +0,0 @@ -totalBundles, 'bundle' . ($summary->totalBundles === 1 ? '' : 's')], - ['css', 'brand-css3', $summary->totalCss, 'css'], - ['js', 'brand-javascript', $summary->totalJs, 'js'], - ['deps', 'link', $summary->totalDeps, 'link' . ($summary->totalDeps === 1 ? '' : 's')], - ]; - $blocks = []; - - foreach ($stats as [$kind, $icon, $value, $label]) { - $blocks[] = Div::tag() - ->addDataAttribute('kind', $kind) - ->class('yii-debug-asset-stat') - ->html( - Span::tag() - ->addAriaAttribute('hidden', 'true') - ->class('yii-debug-asset-stat-icon') - ->html(Icon::render($icon)), - Strong::tag() - ->class('yii-debug-asset-stat-value') - ->content((string) $value), - Span::tag() - ->class('yii-debug-asset-stat-label') - ->content($label), - ); - } - - return H1::tag() - ->class('yii-debug-sr-only') - ->content(PanelTitle::ASSETS) - ->render() - . Header::tag() - ->class('yii-debug-asset-stats') - ->html(...$blocks) - ->render(); - } - - /** - * Renders the normalized bundle inventory as an ordered list of shared bundle cards. - */ - public static function renderInventory(AssetSummary $summary): string - { - if ($summary->isEmpty()) { - return ''; - } - - $items = []; - - foreach ($summary->bundles as $bundle) { - $items[] = Li::tag() - ->class('yii-debug-asset-list-item') - ->html(AssetCardRenderer::renderCard($bundle)); - } - - return Ol::tag() - ->class('yii-debug-asset-list') - ->html(...$items) - ->render(); - } -} diff --git a/src/Panel/Asset/AssetSummary.php b/src/Panel/Asset/AssetSummary.php deleted file mode 100644 index cab4f3e..0000000 --- a/src/Panel/Asset/AssetSummary.php +++ /dev/null @@ -1,42 +0,0 @@ - Per-bundle view-models in registration order. - */ - public array $bundles, - /** - * Number of bundles in {@see $bundles}. - */ - public int $totalBundles, - /** - * Sum of CSS files across every bundle. - */ - public int $totalCss, - /** - * Sum of JS files across every bundle. - */ - public int $totalJs, - /** - * Sum of declared dependencies across every bundle. - */ - public int $totalDeps, - ) {} - - /** - * Returns whether the summary carries no bundles. - */ - public function isEmpty(): bool - { - return $this->bundles === []; - } -} diff --git a/src/Panel/Config/ApplicationConfig.php b/src/Panel/Config/ApplicationConfig.php deleted file mode 100644 index c158733..0000000 --- a/src/Panel/Config/ApplicationConfig.php +++ /dev/null @@ -1,46 +0,0 @@ -class('yii-debug-dl') - ->html( - self::renderDlRow('Charset', $app->charset !== '' ? $app->charset : '—'), - self::renderDlRow('Current language', self::formatLanguage($app->language)), - self::renderDlRow('Source language', self::formatLanguage($app->sourceLanguage)), - ), - ); - } - - /** - * Renders the Installed extensions section, or returns `null` when the roster is empty so the caller can omit the - * wrapper entirely. - * - * @param ConfigSummary $summary Typed configuration summary. - * - * @return Section|null Installed extensions section, or `null` when the roster is empty. - */ - public static function renderInstalledExtensionsSection(ConfigSummary $summary): Section|null - { - if ($summary->hasExtensions() === false) { - return null; - } - - $groups = []; - - foreach ($summary->extensions as $name => $version) { - $parts = explode('/', $name, 2); - $vendor = $parts[0]; - $package = $parts[1] ?? $name; - - $groups[$vendor][$package] = $version; - } - - $items = []; - - foreach ($groups as $vendor => $packages) { - $items[] = self::renderPackageGroup($vendor, $packages); - } - - return self::renderSection( - '::', - [ - ' Installed extensions ', - Span::tag() - ->class('yii-debug-section-count') - ->content((string) $summary->extensionCount()), - ], - Div::tag() - ->class('yii-debug-package-groups') - ->html(...$items), - ); - } - - /** - * Renders the labeled on/off pill strip for the bundled PHP extensions (Xdebug, APCu, Memcache, Memcached). - * - * @param PhpConfig $php Typed PHP runtime section. - * - * @return Section PHP extensions section. - */ - public static function renderPhpExtensionsSection(PhpConfig $php): Section - { - $pills = [ - self::renderExtensionPill('Xdebug', $php->xdebug), - self::renderExtensionPill('APCu', $php->apcu), - self::renderExtensionPill('Memcache', $php->memcache), - self::renderExtensionPill('Memcached', $php->memcached), - ]; - - return self::renderSection( - '::', - [' PHP extensions'], - Div::tag() - ->class('yii-debug-ext-strip') - ->html(...$pills), - ); - } - - /** - * Renders the bottom call-to-action linking to the standalone phpinfo viewer. - * - * The caller resolves the destination URL (typically via `Url::to(['php-info'])`) so the renderer stays free of - * routing concerns and easy to test in isolation. - * - * @param string $href Destination URL of the standalone phpinfo viewer. - * - * @return A Call-to-action link element. - */ - public static function renderPhpInfoCta(string $href): A - { - return A::tag() - ->class('yii-debug-cta') - ->href($href) - ->target('_blank') - ->rel('noopener') - ->html( - Span::tag() - ->class('yii-debug-cta-prompt') - ->addAriaAttribute('hidden', 'true') - ->content('→'), - Span::tag()->content('View full phpinfo'), - Span::tag() - ->class('yii-debug-cta-external') - ->addAriaAttribute('hidden', 'true') - ->content('↗'), - ); - } - - /** - * Renders the four-card readout grid (`Yii`, `PHP`, `Environment`, `Application`) at the top of the detail view. - * - * @param ConfigSummary $summary Typed configuration summary. - * - * @return Div Readout grid container. - */ - public static function renderReadoutGrid(ConfigSummary $summary): Div - { - $app = $summary->application; - $php = $summary->php; - - $envMeta = $app->debug - ? Span::tag() - ->class('yii-debug-readout-chip') - ->content("debug\u{00A0}on") - : Span::tag() - ->class('yii-debug-readout-chip yii-debug-readout-chip-muted') - ->content("debug\u{00A0}off"); - - $applicationMeta = $app->version !== '' - ? Span::tag() - ->class('yii-debug-readout-chip yii-debug-readout-chip-muted') - ->content("v{$app->version}") - : 'instance'; - - return Div::tag() - ->class('yii-debug-readout') - ->html( - self::renderReadoutCard('Yii', $app->yii, 'framework'), - self::renderReadoutCard('PHP', $php->version, 'runtime'), - self::renderReadoutCard('Environment', $app->env, $envMeta), - self::renderReadoutCard('Application', $app->name !== '' ? $app->name : '—', $applicationMeta), - ); - } - - /** - * Returns a BCP-47 tag annotated with its English display name, or the em-dash placeholder when the locale is - * empty. - * - * @param string $locale BCP-47 tag to annotate. - * - * @return string Annotated tag, the bare tag when no display name resolves, or `—` for an empty locale. - */ - private static function formatLanguage(string $locale): string - { - if ($locale === '') { - return '—'; - } - - $candidates = [ - Locale::getDisplayLanguage($locale, 'en'), - Locale::getDisplayRegion($locale, 'en'), - ]; - - $parts = []; - - foreach ($candidates as $part) { - if (is_string($part) && $part !== '') { - $parts[] = $part; - } - } - - if ($parts === []) { - return $locale; - } - - return "{$locale} (" . implode(', ', $parts) . ')'; - } - - /** - * Builds the four decorative corner glyphs that frame every readout card. - * - * @return list Corner spans in `tl`, `tr`, `bl`, `br` order. - */ - private static function renderCorners(): array - { - return array_map( - static fn(string $corner): Span => Span::tag() - ->class('yii-debug-readout-corner') - ->addDataAttribute('corner', $corner) - ->addAriaAttribute('hidden', 'true'), - self::CORNERS, - ); - } - - /** - * Renders one `
term
value
` row inside the application-details description list. - * - * @param string $term Row label. - * @param string $value Row value. - * - * @return Div Description-list row. - */ - private static function renderDlRow(string $term, string $value): Div - { - return Div::tag() - ->class('yii-debug-dl-row') - ->html( - Dt::tag()->content($term), - Dd::tag()->content($value), - ); - } - - /** - * Renders one extension pill with an on/off state and a label. - * - * @param string $name Extension name shown in the pill. - * @param bool $enabled Whether the extension is loaded. - * - * @return Span Extension pill element. - */ - private static function renderExtensionPill(string $name, bool $enabled): Span - { - return ExtensionPill::render( - $name, - $enabled ? 'on' : 'off', - $enabled, - ); - } - - /** - * Renders one Composer vendor group as a compact dependency ledger. - * - * @param string $vendor Composer vendor name. - * @param array $packages Package names mapped to their installed version. - * - * @return Article Vendor group element. - */ - private static function renderPackageGroup(string $vendor, array $packages): Article - { - $items = []; - - foreach ($packages as $name => $version) { - $items[] = self::renderPackageItem($name, $version); - } - - $count = count($packages); - - return Article::tag() - ->class('yii-debug-package-group') - ->html( - Header::tag() - ->class('yii-debug-package-group-header') - ->html( - H3::tag() - ->class('yii-debug-package-vendor') - ->content("{$vendor}/"), - Span::tag() - ->class('yii-debug-package-group-count') - ->content("{$count} " . ($count === 1 ? 'package' : 'packages')), - ), - Dl::tag() - ->class('yii-debug-package-list') - ->html(...$items), - ); - } - - /** - * Renders one package name and version row inside its Composer vendor group. - * - * @param string $name Package name without its vendor prefix. - * @param string $version Installed version. - * - * @return Div Package row. - */ - private static function renderPackageItem(string $name, string $version): Div - { - return Div::tag() - ->class('yii-debug-package-row') - ->html( - Dt::tag() - ->class('yii-debug-package-name') - ->content($name), - Dd::tag() - ->class('yii-debug-package-version') - ->content("v{$version}"), - ); - } - - /** - * Builds one readout card with a label, value, and either a plain-text meta line or a chip. - * - * @param string $label Card label. - * @param string $value Card value. - * @param Span|string $meta Chip element or plain-text meta line shown under the value. - * - * @return Article Readout card element. - */ - private static function renderReadoutCard(string $label, string $value, Span|string $meta): Article - { - $metaWrap = Span::tag()->class('yii-debug-readout-meta'); - - $metaWrap = $meta instanceof Stringable - ? $metaWrap->html($meta) - : $metaWrap->content($meta); - - $children = [ - ...self::renderCorners(), - Span::tag() - ->class('yii-debug-readout-label') - ->content($label), - Span::tag() - ->class('yii-debug-readout-value') - ->content($value), - $metaWrap, - ]; - - return Article::tag() - ->class('yii-debug-readout-card') - ->html(...$children); - } - - /** - * Builds the shared section shell: a marked title followed by the section body. - * - * @param string $mark Glyph rendered inside the section mark. - * @param list $title Title nodes rendered after the mark. - * @param string|Stringable ...$body Section body nodes. - * - * @return Section Section element. - */ - private static function renderSection(string $mark, array $title, string|Stringable ...$body): Section - { - return Section::tag() - ->class('yii-debug-section') - ->html( - H2::tag() - ->class('yii-debug-section-title') - ->html( - Span::tag() - ->class('yii-debug-section-mark') - ->content($mark), - ...$title, - ), - ...$body, - ); - } -} diff --git a/src/Panel/Config/ConfigDataNormalizer.php b/src/Panel/Config/ConfigDataNormalizer.php deleted file mode 100644 index 8439868..0000000 --- a/src/Panel/Config/ConfigDataNormalizer.php +++ /dev/null @@ -1,100 +0,0 @@ - $data Narrowed configuration-panel payload (caller must guarantee `array`). - * @param array $extensions Already-typed roster from the Configuration panel extension roster. - * - * @return ConfigSummary Typed summary safe to render directly. - */ - public function normalize(array $data, array $extensions): ConfigSummary - { - $applicationRaw = is_array($data['application'] ?? null) ? $data['application'] : []; - $phpRaw = is_array($data['php'] ?? null) ? $data['php'] : []; - - return new ConfigSummary( - application: $this->buildApplication($applicationRaw), - php: $this->buildPhp($phpRaw), - extensions: $extensions, - ); - } - - /** - * Builds the application-section view-model from the `application` slice of the raw payload. - * - * @param array $raw Raw `application` slice. - * - * @return ApplicationConfig Typed application section. - */ - private function buildApplication(array $raw): ApplicationConfig - { - return new ApplicationConfig( - yii: $this->extractString($raw, 'yii'), - name: $this->extractString($raw, 'name'), - version: $this->extractString($raw, 'version'), - language: $this->extractString($raw, 'language'), - sourceLanguage: $this->extractString($raw, 'sourceLanguage'), - charset: $this->extractString($raw, 'charset'), - env: $this->extractString($raw, 'env'), - debug: $this->extractBool($raw, 'debug'), - ); - } - - /** - * Builds the PHP-runtime view-model from the `php` slice of the raw payload. - * - * @param array $raw Raw `php` slice. - * - * @return PhpConfig Typed PHP runtime section. - */ - private function buildPhp(array $raw): PhpConfig - { - return new PhpConfig( - version: $this->extractString($raw, 'version'), - xdebug: $this->extractBool($raw, 'xdebug'), - apcu: $this->extractBool($raw, 'apcu'), - memcache: $this->extractBool($raw, 'memcache'), - memcached: $this->extractBool($raw, 'memcached'), - ); - } - - /** - * Coerces the value at `$raw[$key]` to a boolean, falling back to `false` when missing. - * - * @param array $raw Slice to read from. - * @param string $key Entry to read. - * - * @return bool Coerced flag. - */ - private function extractBool(array $raw, string $key): bool - { - return (bool) ($raw[$key] ?? false); - } - - /** - * Returns the value at `$raw[$key]` when it is a string, falling back to `''` otherwise. - * - * @param array $raw Slice to read from. - * @param string $key Entry to read. - * - * @return string String value, or `''` when missing or not a string. - */ - private function extractString(array $raw, string $key): string - { - return Coerce::string($raw[$key] ?? null); - } -} diff --git a/src/Panel/Config/ConfigPanel.php b/src/Panel/Config/ConfigPanel.php new file mode 100644 index 0000000..032425f --- /dev/null +++ b/src/Panel/Config/ConfigPanel.php @@ -0,0 +1,299 @@ +phpInfoUrl('/debug/php-info') + * ->present($snapshot->jsonSerialize()); + * ``` + * + * @phpstan-import-type BadgeInline from PanelView + */ +final class ConfigPanel extends Panel +{ + /** + * @var string Icon key shared with the built-in Configuration navigation entry. + */ + protected const string ICON = 'config'; + + /** + * @var string Stable identifier associating the panel with the captured configuration payload. + */ + protected const string ID = 'config'; + + /** + * @var string Panel title used in the debugger navigation. + */ + protected const string TITLE = 'Configuration'; + + /** + * @var array Bundled PHP extensions reported as loaded or missing, keyed by payload field. + */ + private const array PHP_EXTENSIONS = [ + 'xdebug' => 'Xdebug', + 'apcu' => 'APCu', + 'memcache' => 'Memcache', + 'memcached' => 'Memcached', + ]; + + /** + * @var string Placeholder shown wherever the capture left a field empty. + */ + private const string PLACEHOLDER = '—'; + + /** + * @var string Adapter-owned phpinfo URL, or `''` when the adapter exposes no phpinfo page. + */ + private string $phpInfoUrl = ''; + + /** + * Returns a new instance with the adapter-owned phpinfo URL. + * + * @param string $url Adapter-resolved phpinfo URL, or `''` to omit the call to action. + * + * @return self New instance with the requested phpinfo URL. + */ + public function phpInfoUrl(string $url): self + { + $new = clone $this; + $new->phpInfoUrl = $url; + + return $new; + } + + /** + * Builds the panel view from the decoded configuration capture. + * + * @param array $data Decoded panel payload with a `data` key holding the tagged configuration. + * + * @return PanelView Identity overview, runtime sections, and the installed-extension roster. + */ + public function present(array $data): PanelView + { + $config = ConfigSnapshot::fromArray($data, '$.config')->data(); + + $application = self::slice($config, 'application'); + $php = self::slice($config, 'php'); + $extensions = self::extensions($config); + + $count = count($extensions); + + $yii = self::text($application, 'yii'); + $version = self::text($php, 'version'); + + $view = PanelView::create() + ->summary('', $yii === '' ? self::PLACEHOLDER : "Yii {$yii}") + ->summary('', $version === '' ? self::PLACEHOLDER : "PHP {$version}", false) + ->summary($count === 1 ? ' extension' : ' extensions', $count) + ->overview( + [ + 'Yii' => self::orPlaceholder($yii), + 'PHP' => self::orPlaceholder($version), + 'Environment' => self::orPlaceholder(self::text($application, 'env')), + 'Debug mode' => self::flag($application, 'debug', 'on', 'off'), + 'Application' => self::orPlaceholder(self::text($application, 'name')), + 'Application version' => self::orPlaceholder(self::text($application, 'version')), + ], + true, + ); + + $runtime = []; + + foreach (self::PHP_EXTENSIONS as $key => $label) { + $runtime[$label] = self::flag($php, $key, 'loaded', 'missing'); + } + + $view = $view + ->heading('PHP extensions', true) + ->overview($runtime, true) + ->heading('Application details', true) + ->overview( + [ + 'Charset' => self::orPlaceholder(self::text($application, 'charset')), + 'Current language' => self::language(self::text($application, 'language')), + 'Source language' => self::language(self::text($application, 'sourceLanguage')), + ], + true, + ) + ->heading(sprintf('Installed extensions (%d)', $count), true); + + $view = $count === 0 + ? $view->emptyState( + 'No installed extensions recorded', + 'The capture carried no Composer package roster for this request.', + ) + : $view->table( + ['Package', 'Version'], + self::rows($extensions), + true, + [ + 0 => ColumnStyle::IDENTIFIER, + 1 => ColumnStyle::MONOSPACE, + ], + ); + + return $this->phpInfoUrl === '' + ? $view + : $view->paragraph(PanelView::link('View full phpinfo', $this->phpInfoUrl, true)); + } + + /** + * Narrows the captured roster into a sorted `package => version` map. + * + * @param array $config Decoded configuration payload. + * + * @return array Installed versions keyed by package name, sorted alphabetically. + */ + private static function extensions(array $config): array + { + $roster = self::slice($config, 'extensions'); + + $packages = []; + + foreach ($roster as $entry) { + if (is_array($entry) === false) { + continue; + } + + $name = $entry['name'] ?? null; + $version = $entry['version'] ?? null; + + if (is_string($name) && is_string($version)) { + $packages[$name] = $version; + } + } + + ksort($packages); + + return $packages; + } + + /** + * Builds the badge reporting whether a captured flag was enabled. + * + * @param array $slice Decoded payload slice holding the flag. + * @param string $key Flag to read. + * @param string $enabled Badge label used when the flag is `true`. + * @param string $disabled Badge label used when the flag is `false`. + * + * @return BadgeInline Success badge when enabled, muted badge otherwise. + */ + private static function flag(array $slice, string $key, string $enabled, string $disabled): array + { + return ($slice[$key] ?? false) === true + ? PanelView::badge($enabled, Tone::SUCCESS) + : PanelView::badge($disabled, Tone::MUTED); + } + + /** + * Annotates a BCP-47 tag with its English display name. + * + * @param string $locale BCP-47 tag to annotate. + * + * A non-empty tag always resolves at least a display language, so the annotation is never empty. + * + * @return string Tag annotated with its display language and region, or the placeholder when the tag is empty. + */ + private static function language(string $locale): string + { + if ($locale === '') { + return self::PLACEHOLDER; + } + + $candidates = [ + Locale::getDisplayLanguage($locale, 'en'), + Locale::getDisplayRegion($locale, 'en'), + ]; + + $parts = []; + + foreach ($candidates as $candidate) { + if ($candidate !== '') { + $parts[] = $candidate; + } + } + + $annotation = implode(', ', $parts); + + return "{$locale} ({$annotation})"; + } + + /** + * Returns the value, or the placeholder when the capture left it empty. + * + * @param string $value Captured value. + * + * @return string Captured value, or the placeholder when empty. + */ + private static function orPlaceholder(string $value): string + { + return $value === '' ? self::PLACEHOLDER : $value; + } + + /** + * Builds the roster table rows in package order. + * + * @param array $packages Installed versions keyed by package name. + * + * @return list> One row per package, matching the declared column order. + */ + private static function rows(array $packages): array + { + $rows = []; + + foreach ($packages as $name => $version) { + $rows[] = [$name, $version]; + } + + return $rows; + } + + /** + * Returns a nested payload slice, falling back to an empty array when it was not captured. + * + * @param array $config Decoded configuration payload. + * @param string $key Slice to read. + * + * @return array Captured slice, or an empty array when missing or malformed. + */ + private static function slice(array $config, string $key): array + { + $slice = $config[$key] ?? null; + + return is_array($slice) ? $slice : []; + } + + /** + * Returns a captured scalar field as a string. + * + * @param array $slice Decoded payload slice holding the field. + * @param string $key Field to read. + * + * @return string Captured string, or `''` when missing or not a string. + */ + private static function text(array $slice, string $key): string + { + $value = $slice[$key] ?? null; + + return is_string($value) ? $value : ''; + } +} diff --git a/src/Panel/Config/ConfigSummary.php b/src/Panel/Config/ConfigSummary.php deleted file mode 100644 index bb13cf3..0000000 --- a/src/Panel/Config/ConfigSummary.php +++ /dev/null @@ -1,50 +0,0 @@ - Sorted map of installed extension `name => version`, taken verbatim from - * the Configuration panel extension roster. - */ - public array $extensions, - ) {} - - /** - * Returns the number of installed extensions in {@see $extensions}. - * - * @return int Installed extension count. - */ - public function extensionCount(): int - { - return count($this->extensions); - } - - /** - * Returns whether at least one installed extension is recorded. - * - * @return bool `true` when the roster holds at least one extension; `false` otherwise. - */ - public function hasExtensions(): bool - { - return $this->extensions !== []; - } -} diff --git a/src/Panel/Config/PhpConfig.php b/src/Panel/Config/PhpConfig.php deleted file mode 100644 index 01a38f5..0000000 --- a/src/Panel/Config/PhpConfig.php +++ /dev/null @@ -1,37 +0,0 @@ -` element. - * - * The caller supplies an URL builder (typically `static fn(string $file) => Url::to(['download-mail', 'file' => $file])`) - * so the renderer stays free of routing concerns and easy to test in isolation. - * - * @param MailMessage $message Typed mail record. - * @param callable(string): string $downloadUrlBuilder Builds the download URL for the given `.eml` file path. - */ - public static function renderItem(MailMessage $message, callable $downloadUrlBuilder): string - { - $children = [self::renderHead($message, $downloadUrlBuilder)]; - - if (self::hasRecipients($message)) { - $children[] = self::renderRecipients($message); - } - - $children[] = self::renderBody($message); - - if ($message->headers !== '' || $message->charset !== '') { - $children[] = self::renderTechDetails($message); - } - - return Article::tag() - ->class('yii-debug-mail-card') - ->html(...$children) - ->render(); - } - - /** - * Truncates the body to a single-line preview suitable for the headline area, falling back to `''` when the body - * is empty. - */ - private static function bodyPreview(string $body): string - { - $collapsed = (string) preg_replace('/\s+/', ' ', $body); - $preview = mb_substr($collapsed, 0, self::BODY_PREVIEW_LIMIT); - - return mb_strlen($collapsed) > self::BODY_PREVIEW_LIMIT - ? "{$preview}…" - : $preview; - } - - /** - * Formats a Unix timestamp into `[relative, absolute]` strings for the meta time line. - * - * The relative form follows the shared age scale ({@see Format::relativeTime()}) and falls back to the absolute - * form past 30 days. - * - * @return array{0: string, 1: string} Relative label and absolute label, in that order. - */ - private static function formatTime(int $unix): array - { - $absolute = date('M j, Y · H:i:s', $unix); - - return [Format::relativeTime(time() - $unix, $absolute), $absolute]; - } - - /** - * Returns whether the message has at least one populated recipient group. - * - * Used to decide whether the recipient block is emitted at all. - */ - private static function hasRecipients(MailMessage $message): bool - { - return $message->to !== [] || $message->cc !== [] || $message->bcc !== [] || $message->replyTo !== []; - } - - /** - * Returns the uppercased first letter of the local part of the address, falling back to `'?'` when empty. - */ - private static function initialsFor(string $email): string - { - $local = explode('@', $email)[0]; - - return Avatar::initial($local !== '' ? $local : $email); - } - - /** - * Renders the colored avatar `` with the sender's first-letter initial. - * - * The hue is derived deterministically from the address, so the same sender always gets the same color. - */ - private static function renderAvatar(MailMessage $message): Span - { - return Span::tag() - ->addAttribute('style', '--mail-hue: ' . Avatar::hueFor($message->from)) - ->addAriaAttribute('hidden', 'true') - ->class('yii-debug-mail-avatar') - ->content(self::initialsFor($message->from)); - } - - /** - * Renders the body block: the escaped plain-text body, or an empty-state placeholder when the body is empty. - */ - private static function renderBody(MailMessage $message): Div - { - if ($message->body === '') { - return Div::tag() - ->class('yii-debug-mail-body yii-debug-mail-body-empty') - ->content('(empty body)'); - } - - return Div::tag() - ->class('yii-debug-mail-body') - ->content($message->body); - } - - /** - * Renders the card head: avatar, headline, and meta line with status, time, and the optional download link. - * - * @param callable(string): string $downloadUrlBuilder Builds the download URL for the given `.eml` file path. - */ - private static function renderHead(MailMessage $message, callable $downloadUrlBuilder): Header - { - return Header::tag() - ->class('yii-debug-mail-card-head') - ->html( - self::renderAvatar($message), - self::renderHeadline($message), - self::renderMeta($message, $downloadUrlBuilder), - ); - } - - /** - * Renders the headline block: from line, subject heading, and optional body preview. - */ - private static function renderHeadline(MailMessage $message): Div - { - $children = [ - Span::tag() - ->class('yii-debug-mail-from') - ->content($message->from !== '' ? $message->from : '(no sender)'), - H2::tag() - ->class('yii-debug-mail-subject') - ->content($message->subject !== '' ? $message->subject : '(no subject)'), - ]; - - $preview = self::bodyPreview($message->body); - - if ($preview !== '') { - $children[] = Span::tag() - ->class('yii-debug-mail-preview') - ->content($preview); - } - - return Div::tag() - ->class('yii-debug-mail-headline') - ->html(...$children); - } - - /** - * Renders the meta line: status pill, relative/absolute time, and the optional `.eml` download link. - * - * @param callable(string): string $downloadUrlBuilder Builds the download URL for the given `.eml` file path. - */ - private static function renderMeta(MailMessage $message, callable $downloadUrlBuilder): Div - { - $children = [self::renderStatus($message)]; - - if ($message->time !== null) { - [$relative, $absolute] = self::formatTime($message->time); - - $children[] = Span::tag() - ->class('yii-debug-mail-time') - ->content($relative) - ->title($absolute); - } - - if ($message->file !== '') { - $children[] = A::tag() - ->addAriaAttribute('label', 'Download .eml') - ->class('yii-debug-mail-download') - ->href($downloadUrlBuilder($message->file)) - ->html(Icon::render('download')) - ->title('Download .eml'); - } - - return Div::tag() - ->class('yii-debug-mail-meta') - ->html(...$children); - } - - /** - * Renders the recipient group block: one row per non-empty list (TO, CC, BCC, REPLY-TO). - */ - private static function renderRecipients(MailMessage $message): Div - { - $rows = []; - - $groups = [ - 'to' => ['label' => 'TO', 'list' => $message->to], - 'cc' => ['label' => 'CC', 'list' => $message->cc], - 'bcc' => ['label' => 'BCC', 'list' => $message->bcc], - 'reply' => ['label' => 'REPLY-TO', 'list' => $message->replyTo], - ]; - - foreach ($groups as $key => $group) { - $list = $group['list']; - - if ($list === []) { - continue; - } - - $pills = array_map( - static fn(string $email): Span => Span::tag() - ->class('yii-debug-mail-recipient-pill') - ->content($email) - ->title($email), - $list, - ); - - $rows[] = Div::tag() - ->class('yii-debug-mail-recipient-group') - ->html( - Span::tag() - ->addDataAttribute('role', $key) - ->class('yii-debug-mail-recipient-label') - ->content($group['label']), - Span::tag() - ->class('yii-debug-mail-recipient-pills') - ->html(...$pills), - ); - } - - return Div::tag() - ->class('yii-debug-mail-recipients') - ->html(...$rows); - } - - /** - * Renders the status pill (`Sent` / `Failed` with a colored dot). - */ - private static function renderStatus(MailMessage $message): Span - { - $variant = $message->isSuccessful ? 'ok' : 'fail'; - $tooltip = $message->isSuccessful ? 'Mailer reported success' : 'Mailer reported failure'; - $label = $message->isSuccessful ? 'Sent' : 'Failed'; - - $encodedLabel = Encode::content($label); - - return Span::tag() - ->class("yii-debug-mail-status yii-debug-mail-status-{$variant}") - ->title($tooltip) - ->html( - Span::tag() - ->addAriaAttribute('hidden', 'true') - ->class('yii-debug-mail-status-dot'), - " {$encodedLabel}", - ); - } - - /** - * Renders the raw-headers `
` block, collapsed by default. - * - * Emitted only when at least one of `headers` / `charset` is populated. - */ - private static function renderTechDetails(MailMessage $message): Details - { - $summaryChildren = [ - Span::tag() - ->class('yii-debug-mail-tech-icon') - ->addAriaAttribute('hidden', 'true') - ->html(Icon::render('code')), - Span::tag() - ->class('yii-debug-mail-tech-label') - ->content('Raw headers'), - ]; - - if ($message->charset !== '') { - $summaryChildren[] = Span::tag() - ->class('yii-debug-mail-tech-charset') - ->title('Charset') - ->content($message->charset); - } - - $summaryChildren[] = Span::tag() - ->addAriaAttribute('hidden', 'true') - ->class('yii-debug-mail-tech-chevron') - ->html(Icon::render('chevron-down-thin')); - - return Details::tag() - ->class('yii-debug-mail-tech') - ->html( - Summary::tag()->html(...$summaryChildren), - Pre::tag() - ->class('yii-debug-mail-headers') - ->content($message->headers), - ); - } -} diff --git a/src/Panel/Mail/MailPanel.php b/src/Panel/Mail/MailPanel.php new file mode 100644 index 0000000..df1c2e9 --- /dev/null +++ b/src/Panel/Mail/MailPanel.php @@ -0,0 +1,237 @@ + $data Decoded panel payload with an `entries` key holding the captured messages. + * + * @return PanelView Summary table, per-message detail groups, and toolbar metric. + */ + public function present(array $data): PanelView + { + $messages = MailSnapshot::fromArray($data, '$.mail')->entries(); + + $count = count($messages); + + $view = PanelView::create()->active($count > 0); + + if ($count === 0) { + return $view->emptyState( + 'No emails sent in this request', + 'This request did not dispatch any messages through the mailer, so the inbox is empty.', + [ + PanelView::code('BaseMailer::EVENT_AFTER_SEND'), + ' is the capture hook; only requests that call ', + PanelView::code('$mailer->send()'), + ' populate this view.', + ], + ); + } + + $failed = MailMessage::failedCount($messages); + + $view = $view + ->summary($count === 1 ? ' email' : ' emails', $count) + ->toolbar('Emails', $count); + + if ($failed > 0) { + $view = $view->summary(' failed', $failed); + } + + $view = $view->table( + ['#', 'From', 'Subject', 'To', 'Status', 'Time'], + self::rows($messages), + styles: [ + 0 => ColumnStyle::NUMBER, + 4 => ColumnStyle::PILL, + 5 => ColumnStyle::IDENTIFIER, + ], + ); + + foreach ($messages as $index => $message) { + $position = $index + 1; + + $view = $view + ->heading(sprintf('%d. %s', $position, self::subject($message)), true) + ->group(sprintf('Message %d', $position), self::detail($message)); + } + + return $view; + } + + /** + * Joins a recipient group into a single line, falling back to the placeholder when the group is empty. + * + * @param list $addresses Recipient addresses in capture order. + * + * @return string Comma-separated addresses, or the placeholder when the group is empty. + */ + private static function addresses(array $addresses): string + { + return $addresses === [] ? self::PLACEHOLDER : implode(', ', $addresses); + } + + /** + * Builds the detail group of one message: envelope overview, body, and raw headers. + * + * @param MailMessage $message Captured message to describe. + * + * @return PanelView Child view holding only the detail blocks of the message. + */ + private static function detail(MailMessage $message): PanelView + { + $fields = [ + 'From' => $message->from === '' ? self::PLACEHOLDER : $message->from, + 'To' => self::addresses($message->to), + ]; + + if ($message->cc !== []) { + $fields['Cc'] = self::addresses($message->cc); + } + + if ($message->bcc !== []) { + $fields['Bcc'] = self::addresses($message->bcc); + } + + if ($message->replyTo !== []) { + $fields['Reply-To'] = self::addresses($message->replyTo); + } + + $fields['Subject'] = self::subject($message); + $fields['Status'] = self::status($message); + $fields['Sent at'] = self::timestamp($message, self::DATE_FORMAT); + + if ($message->charset !== '') { + $fields['Charset'] = $message->charset; + } + + if ($message->file !== '') { + $fields['Stored file'] = PanelView::code($message->file); + } + + $view = PanelView::create()->overview($fields, true); + + $view = $message->body === '' + ? $view->callout(Tone::MUTED, 'The mailer captured no body for this message.') + : $view->disclosure('Body', $message->body); + + return $message->headers === '' ? $view : $view->disclosure('Raw headers', $message->headers); + } + + /** + * Builds the summary table rows in capture order. + * + * @param list $messages Captured messages in send order. + * + * @return list> One row per message, matching the declared column order. + */ + private static function rows(array $messages): array + { + $rows = []; + + foreach ($messages as $index => $message) { + $rows[] = [ + $index + 1, + $message->from === '' ? self::PLACEHOLDER : $message->from, + PanelView::strong(self::subject($message)), + self::addresses($message->to), + self::status($message), + self::timestamp($message, self::TIME_FORMAT), + ]; + } + + return $rows; + } + + /** + * Builds the delivery badge reported by the mailer. + * + * @param MailMessage $message Captured message to describe. + * + * @return BadgeInline Success badge for a delivered message, danger badge for a rejected one. + */ + private static function status(MailMessage $message): array + { + return $message->isSuccessful + ? PanelView::badge('Sent', Tone::SUCCESS) + : PanelView::badge('Failed', Tone::DANGER); + } + + /** + * Returns the captured subject, falling back to an explicit placeholder when the mailer captured none. + * + * @param MailMessage $message Captured message to describe. + * + * @return string Captured subject, or the subject fallback when empty. + */ + private static function subject(MailMessage $message): string + { + return $message->subject === '' ? self::SUBJECT_FALLBACK : $message->subject; + } + + /** + * Formats the capture time, falling back to the placeholder when the payload carried no parseable time. + * + * @param MailMessage $message Captured message to describe. + * @param string $format Date format applied to the capture timestamp. + * + * @return string Formatted timestamp, or the placeholder when the message has no time. + */ + private static function timestamp(MailMessage $message, string $format): string + { + return $message->time === null ? self::PLACEHOLDER : date($format, $message->time); + } +} diff --git a/src/Panel/PanelRenderer.php b/src/Panel/PanelRenderer.php index 537e5f9..9505b89 100644 --- a/src/Panel/PanelRenderer.php +++ b/src/Panel/PanelRenderer.php @@ -5,10 +5,14 @@ namespace PHPForge\Debug\Panel; use PHPForge\Debug\{ColumnStyle, PanelView}; -use PHPForge\Debug\Helper\{Badge, CellMore, Disclosure, EmptyState, Format, Table}; +use PHPForge\Debug\Helper\{Badge, CellMore, Disclosure, EmptyState, Format, Table, Trace}; +use PHPForge\Debug\Panel\Db\SqlHighlighter; use UIAwesome\Html\Flow\{Div, P, Pre}; +use UIAwesome\Html\Form\InputSearch; use UIAwesome\Html\Heading\{H1, H2}; use UIAwesome\Html\Helper\Encode; +use UIAwesome\Html\List\{Li, Ul}; +use UIAwesome\Html\Palpable\A; use UIAwesome\Html\Phrasing\{Code, Span, Strong}; use UIAwesome\Html\Root\Header; use UIAwesome\Html\Sectioning\Section; @@ -31,49 +35,73 @@ * * @phpstan-import-type Block from PanelView * @phpstan-import-type Inline from PanelView + * @phpstan-import-type LinkInline from PanelView + * @phpstan-import-type TraceInline from PanelView * @phpstan-import-type OverviewBlock from PanelView * @phpstan-import-type ParagraphBlock from PanelView * @phpstan-import-type TableBlock from PanelView */ final class PanelRenderer { - public static function render(string $name, PanelView $view): string + /** + * @param Trace $trace Renderer applied to every captured source frame. + */ + private function __construct(private Trace $trace) {} + + /** + * Renders one panel description through the existing debugger frontend. + * + * @param string $name Panel title used as the accessible heading. + * @param PanelView $view Description to render. + * @param Trace|null $trace Adapter-configured frame renderer, or `null` to use the default source links. + * + * @return string Rendered panel markup. + */ + public static function render(string $name, PanelView $view, Trace|null $trace = null): string { - $summary = []; + return (new self($trace ?? Trace::create()))->panel($name, $view); + } - foreach ($view->summaryMetrics() as $metric) { - if ($summary !== []) { - $summary[] = Span::tag() - ->class('yii-debug-grid-summary-sep') - ->content('·'); - } + /** + * Renders an inline link, opening external targets in a new browsing context. + * + * @param LinkInline $inline Validated inline link. + * + * @return string Rendered anchor. + */ + private function anchor(array $inline): string + { + $anchor = A::tag() + ->href($inline['href']) + ->content($inline['label']); - $summary[] = Span::tag()->html(self::inline($metric['value']), Encode::content($metric['label'])); + if ($inline['external']) { + $anchor = $anchor + ->rel('noopener') + ->target('_blank'); } - return H1::tag() - ->class('yii-debug-sr-only') - ->content($name) - ->render() - . Header::tag() - ->class('yii-debug-grid-summary') - ->html(...$summary) - ->render() - . self::blocks($view->blocks()); + return $anchor->render(); } /** * @param Block $block */ - private static function block(array $block): string + private function block(array $block): string { return match ($block['kind']) { - 'disclosure' => Disclosure::render($block['title'], Pre::tag()->content($block['content'])->render()), - 'emptyState' => EmptyState::card($block['title'], ...array_map(self::paragraph(...), $block['paragraphs'])), + 'disclosure' => Disclosure::render( + $block['title'], + Pre::tag()->content($block['content'])->render(), + ), + 'emptyState' => EmptyState::card( + $block['title'], + ...array_map($this->paragraph(...), $block['paragraphs']), + ), 'group' => Section::tag() ->addAriaAttribute('label', $block['label']) ->class('yii-debug-panel-group') - ->html(self::blocks($block['content']->blocks())) + ->html($this->blocks($block['content']->blocks())) ->render(), 'heading' => $block['section'] ? Div::tag() @@ -83,47 +111,107 @@ private static function block(array $block): string : H2::tag() ->content($block['title']) ->render(), - 'overview' => self::overview($block), - 'paragraph' => self::paragraph($block), - 'table' => self::table($block), + 'overview' => $this->overview($block), + 'paragraph' => $this->paragraph($block), + 'table' => $this->table($block), }; } /** * @param list $blocks */ - private static function blocks(array $blocks): string + private function blocks(array $blocks): string { - return implode('', array_map(self::block(...), $blocks)); + return implode('', array_map($this->block(...), $blocks)); + } + + /** + * Wraps a filterable table with the in-place row filter the debugger frontend already ships. + * + * The scope element pairs the search input with its table, so no panel has to depend on DOM adjacency. + * + * @param string $label Accessible label of the table the filter narrows. + * @param string $table Rendered table markup. + * + * @return string Filter scope containing the search input and the table. + */ + private static function filterScope(string $label, string $table): string + { + $input = InputSearch::tag() + ->addAriaAttribute('label', "Filter {$label}") + ->addDataAttribute('yii-debug-filter', true) + ->class('yii-debug-filter-input') + ->placeholder('Filter…'); + + return Div::tag() + ->addDataAttribute('yii-debug-filter-scope', true) + ->html( + Header::tag() + ->class('yii-debug-section-header') + ->html($input) + ->render(), + $table, + ) + ->render(); + } + + /** + * Renders captured source frames through the adapter-configured frame renderer. + * + * @param TraceInline $inline Validated inline trace. + * + * @return string Rendered frame list. + */ + private function frames(array $inline): string + { + $items = []; + + foreach ($inline['frames'] as $frame) { + $items[] = Li::tag()->html($this->trace->render($frame)); + } + + return Ul::tag() + ->class('yii-debug-trace') + ->html(...$items) + ->render(); } /** * @param Inline $inline */ - private static function inline(array $inline): string + private function inline(array $inline): string { return match ($inline['kind']) { 'badge' => Badge::render($inline['label'], $inline['tone']->value)->render(), + 'link' => $this->anchor($inline), + 'trace' => $this->frames($inline), 'text' => match ($inline['style']) { 'code' => Code::tag() ->content($inline['value']) ->render(), 'plain' => Encode::content($inline['value']), - 'preview' => CellMore::clamp(Encode::content($inline['value']), $inline['value']), + 'preview' => CellMore::clamp( + Encode::content($inline['value']), + $inline['value'], + ), + 'sql' => CellMore::clamp( + SqlHighlighter::highlight($inline['value']), + $inline['value'], + ), 'strong' => Strong::tag() ->content($inline['value']) ->render(), }, 'value' => $inline['typeOnly'] ? Encode::content(Format::typeOf($inline['value'])) - : self::preview($inline['value']), + : $this->preview($inline['value']), }; } /** * @param OverviewBlock $block */ - private static function overview(array $block): string + private function overview(array $block): string { $rows = []; foreach ($block['fields'] as $field) { @@ -132,7 +220,7 @@ private static function overview(array $block): string Th::tag() ->scope('row') ->content($field['label']), - Td::tag()->html(self::inline($field['value'])), + Td::tag()->html($this->inline($field['value'])), ); } return Table::render( @@ -142,12 +230,49 @@ private static function overview(array $block): string ); } + /** + * Renders the accessible heading, the summary strip, and the content blocks. + * + * @param string $name Panel title used as the accessible heading. + * @param PanelView $view Description to render. + * + * @return string Rendered panel markup. + */ + private function panel(string $name, PanelView $view): string + { + $summary = []; + + foreach ($view->summaryMetrics() as $metric) { + if ($summary !== []) { + $summary[] = Span::tag() + ->class('yii-debug-grid-summary-sep') + ->content('·'); + } + + $summary[] = Span::tag()->html($this->inline($metric['value']), Encode::content($metric['label'])); + } + + $heading = H1::tag() + ->class('yii-debug-sr-only') + ->content($name) + ->render(); + + $strip = $summary === [] + ? '' + : Header::tag() + ->class('yii-debug-grid-summary') + ->html(...$summary) + ->render(); + + return $heading . $strip . $this->blocks($view->blocks()); + } + /** * @param ParagraphBlock $block */ - private static function paragraph(array $block): string + private function paragraph(array $block): string { - $paragraph = P::tag()->html(...array_map(self::inline(...), $block['content'])); + $paragraph = P::tag()->html(...array_map($this->inline(...), $block['content'])); if ($block['tone'] !== null) { $paragraph = $paragraph @@ -158,7 +283,7 @@ private static function paragraph(array $block): string return $paragraph->render(); } - private static function preview(mixed $value): string + private function preview(mixed $value): string { $json = json_encode($value, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); @@ -168,7 +293,7 @@ private static function preview(mixed $value): string /** * @param TableBlock $block */ - private static function table(array $block): string + private function table(array $block): string { $rows = []; @@ -187,7 +312,7 @@ private static function table(array $block): string ColumnStyle::PAYLOAD => 'yii-debug-cell-mono yii-debug-cell-payload', }; - $value = self::inline($inline); + $value = $this->inline($inline); if ($style === ColumnStyle::PILL && $inline['kind'] === 'text') { $value = Span::tag() @@ -203,14 +328,26 @@ private static function table(array $block): string $rows[] = Tr::tag()->html(...$cells); } - $html = Div::tag() - ->addAriaAttribute('label', implode(', ', $block['headers'])) + $label = implode(', ', $block['headers']); + + $wrap = Div::tag() + ->addAriaAttribute('label', $label) ->addAttribute('tabindex', 0) ->class('yii-debug-table-wrap') ->role('region') - ->html(Table::build($block['headers'], $rows)) - ->render(); + ->html(Table::build($block['headers'], $rows)); + + if ($block['filterable']) { + $wrap = $wrap->addDataAttribute('yii-debug-filter-target', true); + } - return $block['collapsible'] && count($rows) > CellMore::ROW_THRESHOLD ? CellMore::wrap($html) : $html; + $html = $wrap->render(); + + if ($block['collapsible'] && count($rows) > CellMore::ROW_THRESHOLD) { + $html = CellMore::wrap($html); + } + + return $block['filterable'] ? self::filterScope($label, $html) : $html; } + } diff --git a/src/Panel/Queue/QueueGridRenderer.php b/src/Panel/Queue/QueueGridRenderer.php deleted file mode 100644 index b406fd3..0000000 --- a/src/Panel/Queue/QueueGridRenderer.php +++ /dev/null @@ -1,145 +0,0 @@ -attempt === null || $record->attempt <= 0) { - return '—'; - } - - return "#{$record->attempt}"; - } - - /** - * Renders the component id cell (`'queue'` / `'queueRedis'` / ...) as plain text. - * - * The value is small enough that a pill would only add visual noise. - */ - public static function renderComponentCell(JobRecord $record): string - { - return $record->componentId; - } - - /** - * Renders the driver pill (`Sync` / `Database` / `Redis` / ...). - * - * Async drivers carry the `is-async` modifier, so the developer can spot at a glance which jobs ran in-process. - */ - public static function renderDriverCell(JobRecord $record): string - { - if ($record->driverName === '') { - return ''; - } - - return QueuePill::driver($record)->render(); - } - - /** - * Renders the duration cell as `X.X ms` for executed/errored events, falling back to an em dash (`—`) for push - * events that did not carry a duration. - */ - public static function renderDurationCell(JobRecord $record): string - { - if ($record->duration === null) { - return '—'; - } - - return Format::milliseconds($record->duration, 1); - } - - /** - * Renders the job-id cell, wrapping the id in a monospaced link-styled span (matching the History panel's tag - * column) so async-driver ids (UUIDs, hex strings, ...) read cleanly. - * - * An empty id renders as an em dash (`—`), so the column never collapses to whitespace and the dash makes it - * obvious the queue did not return an id at push time. - */ - public static function renderIdCell(JobRecord $record): string - { - if ($record->jobId === '') { - return '—'; - } - - return Span::tag() - ->class('yii-debug-tag-link') - ->content($record->jobId) - ->render(); - } - - /** - * Renders the job-class cell as a link to the dedicated detail page. - * - * The short class name shows in bold; the namespace prefix renders muted underneath; the full FQCN sits in the - * `title` attribute for hover inspection. Click drills into the typed payload tree. - * - * @param JobRecord $record Typed queue event record. - * @param string $href URL of the dedicated detail page for `$record`. - */ - public static function renderJobCell(JobRecord $record, string $href): string - { - $shortName = Fqcn::shortName($record->jobClass); - $namespace = Fqcn::namespacePart($record->jobClass); - - return Div::tag() - ->class('yii-debug-queue-grid-job') - ->html( - A::tag() - ->class('yii-debug-queue-grid-job-link') - ->href($href) - ->title($record->jobClass !== '' ? $record->jobClass : 'Mixed payload') - ->html(Strong::tag()->content($shortName === '' ? '—' : $shortName)), - $namespace !== '' - ? Span::tag() - ->class('yii-debug-queue-grid-job-namespace') - ->content("{$namespace}\\") - : '', - ) - ->render(); - } - - /** - * Renders the status pill (`Queued` / `Done` / `Failed`) for the captured event type. - */ - public static function renderStatusCell(JobRecord $record): string - { - return QueuePill::status($record)->render(); - } - - /** - * Renders the capture time as `HH:MM:SS.mmm`, derived from the captured `microtime` float. - */ - public static function renderTimeCell(JobRecord $record): string - { - return Format::timeOfDay(intval($record->time * 1000)); - } - - /** - * Renders the time-to-reserve cell as `Ns` for non-zero TTRs, falling back to an em dash (`—`) otherwise. - */ - public static function renderTtrCell(JobRecord $record): string - { - if ($record->ttr === null || $record->ttr <= 0) { - return '—'; - } - - return "{$record->ttr}s"; - } -} diff --git a/src/Panel/Queue/QueuePanel.php b/src/Panel/Queue/QueuePanel.php new file mode 100644 index 0000000..181e9eb --- /dev/null +++ b/src/Panel/Queue/QueuePanel.php @@ -0,0 +1,315 @@ + Badge describing each captured lifecycle phase. + */ + private const array STATUS = [ + JobRecord::TYPE_PUSH => ['label' => 'Queued', 'tone' => Tone::INFO], + JobRecord::TYPE_EXEC => ['label' => 'Done', 'tone' => Tone::SUCCESS], + JobRecord::TYPE_ERROR => ['label' => 'Failed', 'tone' => Tone::DANGER], + ]; + + /** + * @var array Adapter-owned per-event detail URLs, keyed by event position. + */ + private array $jobUrls = []; + + /** + * Returns a new instance with the adapter-owned per-event detail URLs. + * + * @param array $urls Detail URLs keyed by the event position in capture order. + * + * @return self New instance carrying the requested detail URLs. + */ + public function jobUrls(array $urls): self + { + $new = clone $this; + $new->jobUrls = $urls; + + return $new; + } + + /** + * Builds the panel view from the decoded queue capture. + * + * @param array $data Decoded panel payload with a `records` key holding the captured events. + * + * @return PanelView Lifecycle table, async hint, and per-event detail groups. + */ + public function present(array $data): PanelView + { + $records = QueueSnapshot::fromArray($data, '$.queue')->entries(); + $summary = QueueSummary::fromRecords($records); + + $total = $summary->totalEvents(); + $errors = $summary->totalErrors(); + + $view = PanelView::create() + ->active($total > 0) + ->summary($total === 1 ? ' event' : ' events', $total) + ->summary(' queued', $summary->totalPushed()) + ->summary(' done', $summary->totalExecuted()) + ->toolbar('Jobs', $total); + + if ($errors > 0) { + $view = $view->summary(' failed', $errors); + } + + if ($total === 0) { + return $view->emptyState( + 'No queue activity in this request', + 'This request pushed no job and ran none, so the lifecycle log is empty.', + [ + 'Events appear here when a queue component emits ', + PanelView::code('afterPush'), + ', ', + PanelView::code('afterExec'), + ', or ', + PanelView::code('afterError'), + '.', + ], + ); + } + + $async = self::asyncDrivers($records); + + if ($async !== []) { + $view = $view->callout( + Tone::INFO, + PanelView::strong('Async driver: ' . implode(', ', $async) . '.'), + ' Push events show here, but jobs run in a separate worker process; see the History sidebar for ', + PanelView::strong('CLI'), + ' debug snapshots that capture the matching exec and error events.', + ); + } + + $view = $view + ->heading('Lifecycle events', true) + ->table( + ['#', 'Status', 'Job', 'Component', 'Driver', 'Time', 'Attempt', 'Duration'], + $this->rows($records), + true, + [ + 0 => ColumnStyle::NUMBER, + 1 => ColumnStyle::PILL, + 2 => ColumnStyle::IDENTIFIER, + 3 => ColumnStyle::IDENTIFIER, + 4 => ColumnStyle::IDENTIFIER, + 5 => ColumnStyle::IDENTIFIER, + 6 => ColumnStyle::NUMBER, + 7 => ColumnStyle::NUMBER, + ], + ); + + foreach ($records as $index => $record) { + $view = $view + ->heading(sprintf('%d. %s', $index + 1, self::jobClass($record)), true) + ->group(sprintf('Event %d', $index + 1), $this->detail($record, $index)); + } + + return $view; + } + + /** + * Returns the distinct out-of-process driver names, in first-seen order. + * + * @param list $records Captured events in chronological order. + * + * @return list Async driver names, deduplicated while preserving order. + */ + private static function asyncDrivers(array $records): array + { + $drivers = []; + + foreach ($records as $record) { + if ($record->isAsync && $record->driverName !== '' && in_array($record->driverName, $drivers, true) === false) { + $drivers[] = $record->driverName; + } + } + + return $drivers; + } + + /** + * Builds the detail group of one event: envelope overview, payload, and the captured error. + * + * @param JobRecord $record Captured event to describe. + * @param int $index Event position in capture order. + * + * @return PanelView Child view holding only the detail blocks of the event. + */ + private function detail(JobRecord $record, int $index): PanelView + { + $fields = [ + 'Job' => PanelView::code(self::jobClass($record)), + 'Status' => self::status($record), + 'Component' => self::orPlaceholder($record->componentId), + 'Driver' => self::orPlaceholder($record->driverName), + 'Driver class' => $record->driverClass === '' + ? self::PLACEHOLDER + : PanelView::code($record->driverClass), + 'Execution' => $record->isAsync ? 'Worker process' : 'In process', + 'Job id' => self::orPlaceholder($record->jobId), + 'Pushed at' => date('M j, Y · H:i:s', (int) $record->time), + 'TTR' => self::seconds($record->ttr), + 'Delay' => self::seconds($record->delay), + 'Priority' => $record->priority === null ? self::PLACEHOLDER : $record->priority, + 'Attempt' => $record->attempt === null ? self::PLACEHOLDER : $record->attempt, + 'Duration' => self::duration($record->duration), + ]; + + $url = $this->jobUrls[$index] ?? null; + + if ($url !== null) { + $fields['Details'] = PanelView::link( + 'Open job detail', + $url, + ); + } + + $view = PanelView::create()->overview($fields, true); + + if ($record->error !== '') { + $view = $view->callout(Tone::DANGER, $record->error); + } + + return $record->payloadFields === [] + ? $view->paragraph('The event carried no job payload.') + : $view->overview(['Payload' => PanelView::value($record->payloadFields)]); + } + + /** + * Formats an execution time, falling back to the placeholder when the phase reports none. + * + * @param float|null $duration Execution time in seconds, or `null` when the phase reports none. + * + * @return string Formatted duration, or the placeholder. + */ + private static function duration(float|null $duration): string + { + return $duration === null ? self::PLACEHOLDER : Format::milliseconds($duration, 1); + } + + /** + * Returns the job class, falling back to the placeholder when the event carried none. + * + * @param JobRecord $record Captured event to describe. + * + * @return string Job class name, or the placeholder. + */ + private static function jobClass(JobRecord $record): string + { + return $record->jobClass === '' ? self::PLACEHOLDER : $record->jobClass; + } + + /** + * Returns the value, or the placeholder when the capture left it empty. + * + * @param string $value Captured value. + * + * @return string Captured value, or the placeholder when empty. + */ + private static function orPlaceholder(string $value): string + { + return $value === '' ? self::PLACEHOLDER : $value; + } + + /** + * Builds the lifecycle table rows in capture order. + * + * @param list $records Captured events in chronological order. + * + * @return list> One row per event, matching the declared column order. + */ + private function rows(array $records): array + { + $rows = []; + + foreach ($records as $index => $record) { + $rows[] = [ + $index + 1, + self::status($record), + self::jobClass($record), + self::orPlaceholder($record->componentId), + self::orPlaceholder($record->driverName), + date('H:i:s', (int) $record->time), + $record->attempt === null ? self::PLACEHOLDER : $record->attempt, + self::duration($record->duration), + ]; + } + + return $rows; + } + + /** + * Formats a second-based override, falling back to the placeholder when the driver default applies. + * + * @param int|null $value Override in seconds, or `null` when the driver default applies. + * + * @return string Formatted override, or the placeholder. + */ + private static function seconds(int|null $value): string + { + return $value === null ? self::PLACEHOLDER : "{$value}s"; + } + + /** + * Builds the badge describing the captured lifecycle phase. + * + * Hydration rejects any other phase, so the fallback only satisfies the type checker. + * + * @param JobRecord $record Captured event to describe. + * + * @return BadgeInline Badge carrying the phase label and tone. + */ + private static function status(JobRecord $record): array + { + $status = self::STATUS[$record->eventType] ?? self::STATUS[JobRecord::TYPE_PUSH]; + + return PanelView::badge( + $status['label'], + $status['tone'], + ); + } +} diff --git a/src/Panel/Request/RequestRenderer.php b/src/Panel/Request/RequestRenderer.php index b1af082..05b8e5a 100644 --- a/src/Panel/Request/RequestRenderer.php +++ b/src/Panel/Request/RequestRenderer.php @@ -4,33 +4,45 @@ namespace PHPForge\Debug\Panel\Request; -use PHPForge\Debug\Helper\{EmptyState, Tabs, Vocabulary}; -use PHPForge\Debug\Panel\Request\Routing\{CurrentRouteView, RequestRoutingView}; +use PHPForge\Debug\Helper\{Badge, Disclosure, EmptyState, Table, Tabs, Vocabulary}; +use PHPForge\Debug\Panel\Request\Routing\{ + CurrentRouteView, + RequestRoutingView, + RouteInventoryView, + RouteTraceRow, +}; +use PHPForge\Debug\View\Grid\RowClass; use UIAwesome\Html\Flow\{Div, P}; use UIAwesome\Html\List\{Dd, Dl, Dt}; use UIAwesome\Html\Phrasing\Span; use UIAwesome\Html\Root\Header; use UIAwesome\Html\Sectioning\Section; +use UIAwesome\Html\Table\{Td, Tr}; use function count; use function implode; +use function in_array; /** * Composes request and routing diagnostics into one framework-neutral detail view. */ final class RequestRenderer { + /** + * @var list Badge variants the shared vocabulary accepts. + */ + private const array BADGE_VARIANTS = ['danger', 'info', 'muted', 'success', 'warning']; + /** * Preserves the legacy Request presentation when no routing view is supplied. */ public static function render(RequestView $view, RequestRoutingView|null $routing = null): string { if ($routing === null) { - return RequestSectionRenderer::renderHero($view->hero) - . RequestSectionRenderer::renderTabs($view->tabs); + return RequestSectionRenderer::renderHero($view->hero) . RequestSectionRenderer::renderTabs($view->tabs); } - return self::renderOverview($view->hero, $routing->current) + return self::renderOverview($view->hero, $routing->current, $routing->inventory) . self::renderTabs($view, $routing); } @@ -99,7 +111,10 @@ private static function renderHeaders(RequestTab|null $tab): string return self::renderSections($tab->sections); } - return RequestHeadersRenderer::render($request->entries, $response->entries); + return RequestHeadersRenderer::render( + $request->entries, + $response->entries, + ); } private static function renderMetaItem(string $label, string $value): Span @@ -117,18 +132,25 @@ private static function renderMetaItem(string $label, string $value): Span ); } - private static function renderMetric(string $label, string $value): Div + private static function renderMetric(string $label, string $value, Span|null $marker = null): Div { + $value_ = Dd::tag()->title($value); + return Div::tag() ->class('yii-debug-request-overview-metric') ->html( Dt::tag()->content($label), - Dd::tag()->title($value)->content($value), + $marker === null + ? $value_->content($value) + : $value_->html(Span::tag()->content($value), $marker), ); } - private static function renderOverview(RequestHero $hero, CurrentRouteView $current): string - { + private static function renderOverview( + RequestHero $hero, + CurrentRouteView $current, + RouteInventoryView|null $inventory, + ): string { $definition = $current->getDefinition(); $route = $current->getRoute() !== '' ? $current->getRoute() : ($definition?->getName() ?? ''); $action = $current->getAction() ?? $definition?->getAction() ?? ''; @@ -185,11 +207,32 @@ private static function renderOverview(RequestHero $hero, CurrentRouteView $curr ->content($flag); } - $error = $current->getError() === null || $current->getError() === '' - ? '' - : Div::tag() - ->class('yii-debug-callout yii-debug-callout-danger yii-debug-request-routing-error') - ->html(P::tag()->content($current->getError())); + $configuration = []; + + foreach ($inventory?->getBadges() ?? [] as $badge) { + $configuration[] = Badge::render( + $badge->label, + in_array($badge->variant, self::BADGE_VARIANTS, true) ? $badge->variant : 'muted', + ); + } + + if ($configuration !== []) { + $meta[] = Span::tag() + ->class('yii-debug-request-overview-config') + ->html(...$configuration); + } + + $callouts = []; + + foreach ([$current->getError(), $inventory?->getError()] as $failure) { + if ($failure !== null && $failure !== '') { + $callouts[] = Div::tag() + ->class('yii-debug-callout yii-debug-callout-danger yii-debug-request-routing-error') + ->html(P::tag()->content($failure)); + } + } + + $callouts[] = self::renderResolution($current); return Section::tag() ->addAriaAttribute('label', 'Request overview') @@ -213,6 +256,9 @@ private static function renderOverview(RequestHero $hero, CurrentRouteView $curr self::renderMetric( 'Route', $route !== '' ? $route : 'Unresolved', + $definition === null + ? null + : Badge::render('Matched', 'success', 'yii-debug-route-match'), ), self::renderMetric( 'Action', @@ -226,11 +272,45 @@ private static function renderOverview(RequestHero $hero, CurrentRouteView $curr Div::tag() ->class('yii-debug-request-overview-meta') ->html(...$meta), - $error, + ...$callouts, ) ->render(); } + /** + * Renders the routing resolution the request went through, as a collapsed disclosure under the overview. + * + * @param CurrentRouteView $current Diagnostics for the route the request resolved to. + * + * @return string Collapsed resolution, or an empty string when nothing was captured. + */ + private static function renderResolution(CurrentRouteView $current): string + { + if (($current->getMessage() === null || $current->getMessage() === '') && $current->getTrace() === []) { + return ''; + } + + $body = $current->getMessage() === null || $current->getMessage() === '' + ? '' + : P::tag() + ->class('yii-debug-route-resolution-message') + ->content($current->getMessage()) + ->render(); + + if ($current->getTrace() !== []) { + $body .= self::renderTraceTable($current->getTrace()); + } + + $count = count($current->getTrace()); + + $title = $count === 0 ? 'Routing resolution' : "Routing resolution ({$count} rules tested)"; + + return Div::tag() + ->class('yii-debug-route-resolution') + ->html(Disclosure::render($title, $body)) + ->render(); + } + /** * @param list $sections */ @@ -251,7 +331,10 @@ private static function renderServer(RequestTab $tab, RequestView $view): string return self::renderSections($tab->sections); } - return RequestServerRenderer::renderForRequest($tab->sections[0]->entries, $view); + return RequestServerRenderer::renderForRequest( + $tab->sections[0]->entries, + $view, + ); } private static function renderTabs(RequestView $view, RequestRoutingView $routing): string @@ -302,13 +385,6 @@ private static function renderTabs(RequestView $view, RequestRoutingView $routin ]; } - if ($routing->inventory !== null) { - $tabs[] = [ - 'label' => 'Routes (' . count($routing->inventory->getRoutes()) . ')', - 'content' => RequestRoutesRenderer::render($routing->current, $routing->inventory), - ]; - } - $server = self::tab($view->tabs, 'server'); if ($server !== null) { @@ -324,6 +400,44 @@ private static function renderTabs(RequestView $view, RequestRoutingView $routin ->render(); } + /** + * Renders the rules the router tested, in the order it tested them. + * + * @param list $trace Tested rules in evaluation order. + * + * @return string Rendered trace table. + */ + private static function renderTraceTable(array $trace): string + { + $rows = []; + + foreach ($trace as $index => $entry) { + $result = $entry->matched + ? Badge::render('Matched', 'success') + : Badge::render('Not matched', 'warning'); + $row = Tr::tag() + ->html( + Td::tag()->content((string) ($index + 1)), + Td::tag()->content($entry->rule), + Td::tag()->content($entry->parent !== '' ? $entry->parent : '—'), + Td::tag()->html($result), + ); + + if ($entry->matched) { + $row = $row->attributes(RowClass::for('success')); + } + + $rows[] = $row; + } + + return Table::render( + ['#', 'Rule', 'Parent', 'Result'], + $rows, + 'yii-debug-table yii-debug-route-trace', + 'yii-debug-table-wrap yii-debug-route-trace-wrap', + ); + } + /** * @param list $tabs */ diff --git a/src/Panel/Request/RequestRoutesRenderer.php b/src/Panel/Request/RequestRoutesRenderer.php deleted file mode 100644 index 478f620..0000000 --- a/src/Panel/Request/RequestRoutesRenderer.php +++ /dev/null @@ -1,313 +0,0 @@ -getRoutes()); - - $heading = [ - Div::tag() - ->class('yii-debug-route-inventory-title') - ->html( - H2::tag()->content('Application routes'), - Span::tag() - ->class('yii-debug-route-inventory-count') - ->content((string) $routeCount), - ), - ]; - - if ($inventory->getRoutes() !== []) { - $heading[] = InputSearch::tag() - ->addAriaAttribute('label', 'Filter routes') - ->addDataAttribute('yii-debug-filter', true) - ->class('yii-debug-filter-input yii-debug-route-filter') - ->placeholder('Filter routes…'); - } - - $content = Header::tag() - ->class('yii-debug-route-inventory-header') - ->html(...$heading) - ->render(); - - $content .= self::renderInventoryContext($inventory); - - if ($inventory->getError() !== null && $inventory->getError() !== '') { - $content .= Div::tag() - ->class('yii-debug-callout yii-debug-callout-danger yii-debug-route-inventory-error') - ->html(P::tag()->content($inventory->getError())) - ->render(); - } - - $content .= $inventory->getRoutes() === [] - ? EmptyState::card('No application routes registered.') - : self::renderInventory($inventory->getRoutes(), $current); - - return $content . self::renderResolution($current); - } - - private static function renderBadge(RouteBadge $badge): Span - { - $variant = in_array($badge->variant, self::BADGE_VARIANTS, true) ? $badge->variant : 'muted'; - - return Badge::render($badge->label, $variant); - } - - private static function renderDetails(RouteDefinition $route): Dl - { - $items = []; - - $values = [ - 'Hosts' => $route->getHosts() === [] ? 'Any' : $route->getHosts(), - 'Target' => $route->getName() !== '' ? $route->getTarget() : null, - 'Action' => $route->getAction(), - 'Middleware' => $route->getMiddlewares() === [] ? 'None' : $route->getMiddlewares(), - 'Suffix' => $route->getSuffix(), - 'Mode' => $route->getMode(), - 'Type' => $route->getType(), - ]; - - foreach ($values as $label => $value) { - if ($value === null) { - continue; - } - - $items[] = Div::tag()->html( - Dt::tag()->content($label), - Dd::tag()->html(RequestDiagnosticValueRenderer::header($value)), - ); - } - - return Dl::tag() - ->class('yii-debug-route-metadata') - ->html(...$items); - } - - /** - * @param list $routes - */ - private static function renderInventory(array $routes, CurrentRouteView $current): string - { - $headings = []; - - foreach (['#', 'Methods', 'Pattern', 'Route', 'Details'] as $label) { - $headings[] = Span::tag()->content($label); - } - - $rows = []; - - foreach ($routes as $index => $route) { - $matched = $current->getRoute() !== '' && ( - $route->getName() === $current->getRoute() - || $route->getTarget() === $current->getRoute() - || $route === $current->getDefinition() - ); - - $identity = $route->getName() !== '' ? $route->getName() : ($route->getTarget() ?? '—'); - - $contents = [Code::tag()->content($identity)]; - - if ($matched) { - $contents[] = Badge::render('Matched', 'success', 'yii-debug-route-match'); - } - - $entry = Details::tag() - ->class('yii-debug-route-entry') - ->addDataAttribute('yii-debug-filter-details', true) - ->addDataAttribute('yii-debug-filter-default-open', 'false') - ->html( - Summary::tag() - ->class('yii-debug-route-summary') - ->html( - Span::tag() - ->class('yii-debug-route-order') - ->content((string) ($index + 1)), - self::renderMethodChips($route->getMethods()), - Code::tag() - ->class('yii-debug-route-pattern') - ->content($route->getPattern() !== '' ? $route->getPattern() : '—'), - Span::tag() - ->class('yii-debug-route-identity') - ->html(...$contents), - Disclosure::hint(), - ), - self::renderDetails($route), - ); - $row = Li::tag() - ->addDataAttribute('yii-debug-filter-row', true) - ->html($entry); - - if ($matched) { - $row = $row->attributes(RowClass::for('success'))->addDataAttribute('yii-debug-route-match', true); - } - - $rows[] = $row; - } - - return Div::tag() - ->class('yii-debug-route-ledger') - ->addDataAttribute('yii-debug-filter-target', true) - ->addDataAttribute('yii-debug-filter-unit', 'routes') - ->html( - Div::tag() - ->class('yii-debug-route-columns') - ->addAriaAttribute('hidden', 'true') - ->html(...$headings), - Ol::tag() - ->class('yii-debug-route-list') - ->addAriaAttribute('label', 'Application routes') - ->html(...$rows), - P::tag() - ->addDataAttribute('yii-debug-filter-empty', true) - ->addAttribute('hidden', true) - ->class('yii-debug-diagnostic-filter-empty') - ->content('No routes match this filter.'), - ) - ->render(); - } - - private static function renderInventoryContext(RouteInventoryView $inventory): string - { - $source = rtrim($inventory->getSource(), " .\t\n\r\0\x0B"); - - $message = $source === '' ? 'Configuration source unavailable.' : "Source: {$source}."; - - if ($inventory->isLive()) { - $message .= ' Live configuration may differ from this capture.'; - } - - $badges = []; - - foreach ($inventory->getBadges() as $badge) { - $badges[] = self::renderBadge($badge); - } - - return Div::tag() - ->class('yii-debug-route-inventory-context') - ->html( - P::tag() - ->class('yii-debug-route-inventory-provenance') - ->content($message), - Div::tag() - ->class('yii-debug-route-inventory-badges') - ->html(...$badges), - ) - ->render(); - } - - /** - * @param list $methods - */ - private static function renderMethodChips(array $methods): Span - { - if ($methods === []) { - return Span::tag() - ->class('yii-debug-route-methods yii-debug-route-methods-any') - ->content('Any'); - } - - $chips = []; - - foreach ($methods as $method) { - $chips[] = Span::tag() - ->class('yii-debug-route-method yii-debug-verb-' . Vocabulary::verb($method)) - ->content($method); - } - - return Span::tag() - ->class('yii-debug-route-methods') - ->html(...$chips); - } - - private static function renderResolution(CurrentRouteView $current): string - { - if (($current->getMessage() === null || $current->getMessage() === '') && $current->getTrace() === []) { - return ''; - } - - $body = $current->getMessage() === null || $current->getMessage() === '' - ? '' - : P::tag() - ->class('yii-debug-route-resolution-message') - ->content($current->getMessage()) - ->render(); - - if ($current->getTrace() !== []) { - $body .= self::renderTraceTable($current->getTrace()); - } - - $count = count($current->getTrace()); - - $title = $count === 0 ? 'Routing resolution' : "Routing resolution ({$count} rules tested)"; - - return Div::tag() - ->class('yii-debug-route-resolution') - ->html(Disclosure::render($title, $body)) - ->render(); - } - - /** - * @param list $trace - */ - private static function renderTraceTable(array $trace): string - { - $rows = []; - - foreach ($trace as $index => $entry) { - $result = $entry->matched - ? Badge::render('Matched', 'success') - : Badge::render('Not matched', 'muted'); - $row = Tr::tag() - ->html( - Td::tag()->content((string) ($index + 1)), - Td::tag()->content($entry->rule), - Td::tag()->content($entry->parent !== '' ? $entry->parent : '—'), - Td::tag()->html($result), - ); - - if ($entry->matched) { - $row = $row->attributes(RowClass::for('success')); - } - - $rows[] = $row; - } - - return Table::render( - ['#', 'Rule', 'Parent', 'Result'], - $rows, - 'yii-debug-table yii-debug-route-trace', - 'yii-debug-table-wrap yii-debug-route-trace-wrap', - ); - } -} diff --git a/src/Panel/Request/RequestSectionRenderer.php b/src/Panel/Request/RequestSectionRenderer.php index 3658ea6..aaa047f 100644 --- a/src/Panel/Request/RequestSectionRenderer.php +++ b/src/Panel/Request/RequestSectionRenderer.php @@ -4,7 +4,7 @@ namespace PHPForge\Debug\Panel\Request; -use PHPForge\Debug\Helper\{Disclosure, Dump, Table, Tabs, Vocabulary}; +use PHPForge\Debug\Helper\{Disclosure, PhpHighlighter, Table, Tabs, Vocabulary}; use UIAwesome\Html\Flow\{Div, P}; use UIAwesome\Html\Form\InputSearch; use UIAwesome\Html\Heading\H2; @@ -29,21 +29,23 @@ public static function renderDisclosureSection(RequestSection $section): string ->content('No data') ->render(); } else { - $content = ''; - $filter = self::renderFilter($section); - if ($filter !== null) { - $content .= Div::tag() + $toolbar = $filter === null + ? '' + : Div::tag() ->class('yii-debug-mini-toolbar') ->html($filter) ->render(); - } - $content .= self::renderSectionTable($section); + $content = $toolbar . self::renderSectionTable($section); } - return Disclosure::render($section->caption, $content, $section->entries !== []); + return Disclosure::render( + $section->caption, + $content, + $section->entries !== [], + ); } /** @@ -151,7 +153,11 @@ public static function renderTabs(array $tabs): string $items[] = ['label' => $tab->label, 'content' => $content]; } - return Tabs::render('request', 'Request data', $items); + return Tabs::render( + 'request', + 'Request data', + $items, + ); } private static function renderFilter(RequestSection $section): InputSearch|null @@ -174,16 +180,16 @@ private static function renderFilter(RequestSection $section): InputSearch|null */ private static function renderRow(int|string $name, mixed $value): Tr { - $valueText = Dump::asString($value); - - $escaped = RequestDiagnosticValueRenderer::escape($valueText); - return Tr::tag() ->html( Th::tag() ->scope('row') ->content((string) $name), - Td::tag()->html($escaped), + Td::tag()->html( + Div::tag() + ->class('yii-debug-php-dump') + ->html(PhpHighlighter::highlight($value)), + ), ); } diff --git a/src/Panel/Router/RouterCurrentView.php b/src/Panel/Router/RouterCurrentView.php deleted file mode 100644 index 4ef3db2..0000000 --- a/src/Panel/Router/RouterCurrentView.php +++ /dev/null @@ -1,64 +0,0 @@ - Rules inspected during routing, in inspection order. - */ - public array $logs = [], - /** - * Trace-level info message captured for the routing pass, when present. - */ - public string|null $message = null, - /** - * Resolved request route logged for the current request; empty when unresolved. - */ - public string $route = '', - ) {} - - /** - * Builds the view model from a hydrated router snapshot, or an empty one when the panel captured nothing. - */ - public static function fromSnapshot(RouterSnapshot|null $snapshot): self - { - if ($snapshot === null) { - return new self(); - } - - $logs = $snapshot->entries(); - - return new self( - action: $snapshot->action ?? '', - count: count($logs), - hasMatch: $snapshot->hasMatch(), - logs: $logs, - message: $snapshot->message, - route: $snapshot->route, - ); - } -} diff --git a/src/Panel/Router/RouterPanel.php b/src/Panel/Router/RouterPanel.php new file mode 100644 index 0000000..4305cd2 --- /dev/null +++ b/src/Panel/Router/RouterPanel.php @@ -0,0 +1,326 @@ +urlManager(true, false, '') + * ->rules($ruleRows) + * ->actionRoutes($actionRows) + * ->present($snapshot->jsonSerialize()); + * ``` + * + * @phpstan-import-type BadgeInline from PanelView + */ +final class RouterPanel extends Panel +{ + /** + * @var string Icon key shared with the built-in Router navigation entry. + */ + protected const string ICON = 'router'; + + /** + * @var string Stable identifier associating the panel with the captured routing payload. + */ + protected const string ID = 'router'; + + /** + * @var string Panel title used in the debugger navigation. + */ + protected const string TITLE = 'Router'; + + /** + * @var string Placeholder shown wherever the capture left a field empty. + */ + private const string PLACEHOLDER = '—'; + + /** + * @var list Discovered action routes in display order. + */ + private array $actionRows = []; + + /** + * @var bool Whether the URL manager generates and parses pretty URLs. + */ + private bool $prettyUrl = false; + + /** + * @var list Configured URL rules in display order. + */ + private array $ruleRows = []; + + /** + * @var bool Whether the URL manager only accepts requests matching a configured rule. + */ + private bool $strictParsing = false; + + /** + * @var string Global URL suffix, or `''` when the URL manager declares none. + */ + private string $suffix = ''; + + /** + * Returns a new instance with the discovered action routes. + * + * @param list $rows Action routes in display order. + * + * @return self New instance carrying the requested action routes. + */ + public function actionRoutes(array $rows): self + { + $new = clone $this; + $new->actionRows = $rows; + + return $new; + } + + /** + * Builds the panel view from the decoded routing capture. + * + * @param array $data Decoded panel payload with `action`, `route`, `message`, and `entries` keys. + * + * @return PanelView Route overview, rules-tested trace, URL rules, and action routes. + */ + public function present(array $data): PanelView + { + $snapshot = RouterSnapshot::fromArray($data, '$.router'); + + $entries = $snapshot->entries(); + + $tested = count($entries); + + $route = $snapshot->route; + + $view = PanelView::create() + ->summary('', $route === '' ? self::PLACEHOLDER : $route) + ->summary($tested === 1 ? ' rule tested' : ' rules tested', $tested) + ->toolbar('Route', $route === '' ? self::PLACEHOLDER : $route) + ->overview( + [ + 'Route' => $route === '' ? self::PLACEHOLDER : PanelView::code($route), + 'Action' => self::action($snapshot), + 'Pretty URL' => self::flag($this->prettyUrl), + 'Strict parsing' => self::flag($this->strictParsing), + 'Global suffix' => $this->suffix === '' ? self::PLACEHOLDER : PanelView::code($this->suffix), + ], + true, + ); + + if ($snapshot->message !== null) { + $view = $view->callout(Tone::INFO, $snapshot->message); + } + + $view = $view->heading(self::testedHeading($tested, $snapshot->hasMatch()), true); + + if ($entries === []) { + $view = $view->paragraph('The router captured no rule trace for this request.'); + } else { + $rows = []; + + foreach ($entries as $index => $entry) { + $rows[] = [ + $index + 1, + $entry->rule, + $entry->parent === '' ? self::PLACEHOLDER : $entry->parent, + $entry->match + ? PanelView::badge('match', Tone::SUCCESS) + : PanelView::badge('no match', Tone::MUTED), + ]; + } + + $view = $view->table( + ['#', 'Rule', 'Parent', 'Result'], + $rows, + true, + [ + 0 => ColumnStyle::NUMBER, + 1 => ColumnStyle::IDENTIFIER, + 2 => ColumnStyle::IDENTIFIER, + 3 => ColumnStyle::PILL, + ], + ); + } + + $view = $view->heading(sprintf('URL rules (%d)', count($this->ruleRows)), true); + + $view = $this->ruleRows === [] + ? $view->paragraph('The URL manager declares no rules.') + : $view->table( + ['#', 'Name', 'Route', 'Verb', 'Suffix', 'Mode', 'Type'], + self::ruleRows($this->ruleRows), + true, + [ + 0 => ColumnStyle::NUMBER, + 1 => ColumnStyle::MONOSPACE, + 2 => ColumnStyle::MONOSPACE, + 5 => ColumnStyle::IDENTIFIER, + 6 => ColumnStyle::IDENTIFIER, + ], + ); + + $view = $view->heading(sprintf('Action routes (%d)', count($this->actionRows)), true); + + return $this->actionRows === [] + ? $view->paragraph('No actions are configured.') + : $view->table( + ['#', 'Action', 'Route', 'First matching rule', 'Rules tested'], + self::actions($this->actionRows), + true, + [ + 0 => ColumnStyle::NUMBER, + 1 => ColumnStyle::IDENTIFIER, + 2 => ColumnStyle::MONOSPACE, + 3 => ColumnStyle::IDENTIFIER, + 4 => ColumnStyle::NUMBER, + ], + ); + } + + /** + * Returns a new instance with the configured URL rules. + * + * @param list $rows URL rules in display order. + * + * @return self New instance carrying the requested URL rules. + */ + public function rules(array $rows): self + { + $new = clone $this; + $new->ruleRows = $rows; + + return $new; + } + + /** + * Returns a new instance with the URL manager configuration. + * + * @param bool $prettyUrl Whether the URL manager generates and parses pretty URLs. + * @param bool $strictParsing Whether the URL manager only accepts requests matching a configured rule. + * @param string $suffix Global URL suffix, or `''` when the URL manager declares none. + * + * @return self New instance carrying the requested configuration. + */ + public function urlManager(bool $prettyUrl, bool $strictParsing, string $suffix): self + { + $new = clone $this; + $new->prettyUrl = $prettyUrl; + $new->strictParsing = $strictParsing; + $new->suffix = $suffix; + + return $new; + } + + /** + * Returns the dispatched action, or the placeholder when routing resolved none. + * + * @param RouterSnapshot $snapshot Captured routing trace. + * + * @return string Dispatched action descriptor, or the placeholder. + */ + private static function action(RouterSnapshot $snapshot): string + { + $action = $snapshot->action; + + return $action === null || $action === '' ? self::PLACEHOLDER : $action; + } + + /** + * Builds the action-routes table rows in discovery order. + * + * @param list $rows Discovered action routes in display order. + * + * @return list> One row per action, matching the declared column order. + */ + private static function actions(array $rows): array + { + $result = []; + + foreach ($rows as $index => $row) { + $result[] = [ + $index + 1, + $row->action, + $row->route === '' ? self::PLACEHOLDER : $row->route, + $row->rule === '' ? self::PLACEHOLDER : $row->rule, + $row->count, + ]; + } + + return $result; + } + + /** + * Builds the badge reporting whether a URL manager flag is enabled. + * + * @param bool $enabled Whether the flag is enabled. + * + * @return BadgeInline Success badge when enabled, muted badge otherwise. + */ + private static function flag(bool $enabled): array + { + return $enabled + ? PanelView::badge('enabled', Tone::SUCCESS) + : PanelView::badge('disabled', Tone::MUTED); + } + + /** + * Builds the URL-rules table rows in declaration order. + * + * @param list $rows Configured URL rules in display order. + * + * @return list> One row per rule, matching the declared column order. + */ + private static function ruleRows(array $rows): array + { + $result = []; + + foreach ($rows as $index => $row) { + $result[] = [ + $index + 1, + $row->name, + $row->route === '' ? self::PLACEHOLDER : $row->route, + $row->verb === '' ? self::PLACEHOLDER : $row->verb, + $row->suffix === '' ? self::PLACEHOLDER : $row->suffix, + $row->mode === '' ? self::PLACEHOLDER : $row->mode, + $row->type === '' ? self::PLACEHOLDER : $row->type, + ]; + } + + return $result; + } + + /** + * Builds the heading of the rules-tested trace. + * + * @param int $tested Number of rules inspected during routing. + * @param bool $matched Whether any inspected rule reported a successful match. + * + * @return string Heading describing the routing pass. + */ + private static function testedHeading(int $tested, bool $matched): string + { + if ($tested === 0) { + return 'Rules tested'; + } + + return sprintf( + 'Tested %d %s%s', + $tested, + $tested === 1 ? 'rule' : 'rules', + $matched ? ' before match' : '', + ); + } +} diff --git a/src/Panel/Router/RouterSectionRenderer.php b/src/Panel/Router/RouterSectionRenderer.php deleted file mode 100644 index 41eff13..0000000 --- a/src/Panel/Router/RouterSectionRenderer.php +++ /dev/null @@ -1,253 +0,0 @@ - $ruleRows Router rules in display order. - * @param list $actionRows Discovered action routes in display order. - * @param list $badges Router-wide flag badges for the strip. - */ - public static function renderTabs( - RouterCurrentView $current, - array $ruleRows, - array $actionRows, - array $badges, - ): string { - $flags = self::renderFlagsStrip($badges); - - $tabs = Tabs::render( - 'router', - 'Router data', - [ - ['label' => 'Current Route', 'content' => self::renderCurrentRoutePanel($current)], - ['label' => 'Router Rules', 'content' => self::renderRouterRulesPanel($ruleRows)], - ['label' => 'Action Routes', 'content' => self::renderActionRoutesPanel($actionRows)], - ], - ); - - return "{$flags}{$tabs}"; - } - - /** - * Renders the Action Routes section as a `` of action → route, first matching rule, and rules tested. - * - * @param list $actionRows Discovered action routes in display order. - */ - private static function renderActionRoutesPanel(array $actionRows): string - { - if ($actionRows === []) { - return H2::tag()->content('No actions configured.')->render(); - } - - $rows = []; - - foreach ($actionRows as $i => $row) { - $rows[] = Tr::tag() - ->html( - Td::tag()->content((string) ($i + 1)), - Td::tag()->content($row->action), - Td::tag()->content($row->route), - Td::tag()->content($row->rule), - Td::tag()->content((string) $row->count), - ); - } - - return Table::render(['#', 'Action', 'Route', 'First Matching Rule', 'Rules Tested'], $rows); - } - - /** - * Renders the callout block surfaced by {@see RouterCurrentView::$message}. - */ - private static function renderCalloutBlock(RouterCurrentView $current): string - { - if ($current->message === null) { - return ''; - } - - return Div::tag() - ->class('yii-debug-callout yii-debug-callout-info yii-debug-router-callout') - ->html( - P::tag() - ->class('yii-debug-router-callout-message') - ->content($current->message), - ) - ->render(); - } - - /** - * Renders the Current Route section: route summary, heading, callout block, and rules-tested log table. - * - * Falls back to a dedicated empty-state heading when no route resolution data was captured for the request. - */ - private static function renderCurrentRoutePanel(RouterCurrentView $current): string - { - $heading = $current->count === 0 - ? '' - : H2::tag() - ->content( - sprintf( - 'Tested %d %s%s.', - $current->count, - $current->count === 1 ? 'rule' : 'rules', - $current->hasMatch ? ' before match' : '', - ), - ) - ->render(); - - $summary = self::renderRouteSummary($current); - $callout = self::renderCalloutBlock($current); - $logs = self::renderLogsTable($current); - - $body = "{$summary}{$heading}{$callout}{$logs}"; - - return $body === '' - ? H2::tag()->content('No route resolution captured.')->render() - : $body; - } - - /** - * Renders the router-wide flags strip from adapter-supplied badges. - * - * Reuses the shared `yii-debug-grid-summary` strip so the read-only flags sit above the tab row instead of - * competing with the navigable tabs. - * - * @param list $badges Router-wide flag badges. - */ - private static function renderFlagsStrip(array $badges): string - { - if ($badges === []) { - return ''; - } - - $chips = []; - - foreach ($badges as $badge) { - $chips[] = Badge::render($badge['label'], $badge['variant']); - } - - return Header::tag() - ->class('yii-debug-grid-summary') - ->html(...$chips) - ->render(); - } - - /** - * Renders the rules-tested log table beneath the Current Route callout. - * - * Returns `''` when there are no captured logs, since the heading already conveys the `Tested 0 rules` state. - */ - private static function renderLogsTable(RouterCurrentView $current): string - { - if (count($current->logs) === 0) { - return ''; - } - - $rows = []; - - foreach ($current->logs as $i => $row) { - $result = $row->match - ? Badge::render('Matched', 'success') - : Badge::render('Not matched', 'muted'); - - $tr = Tr::tag() - ->html( - Td::tag()->content((string) ($i + 1)), - Td::tag()->content($row->rule), - Td::tag()->content($row->parent), - Td::tag()->html($result), - ); - - if ($row->match) { - $tr = $tr->attributes(RowClass::for('success')); - } - - $rows[] = $tr; - } - - return Table::render(['#', 'Rule', 'Parent', 'Result'], $rows); - } - - /** - * Renders the Router Rules section as a `
` of rule → target, verb, suffix, mode, and type. - * - * @param list $ruleRows Router rules in display order. - */ - private static function renderRouterRulesPanel(array $ruleRows): string - { - if (count($ruleRows) === 0) { - return H2::tag() - ->content('No routing rules configured.') - ->render(); - } - - $rows = []; - - foreach ($ruleRows as $i => $row) { - $rows[] = Tr::tag() - ->html( - Td::tag()->content((string) ($i + 1)), - Td::tag()->content($row->name), - Td::tag()->content($row->route), - Td::tag()->content($row->verb), - Td::tag()->content($row->suffix), - Td::tag()->content($row->mode), - Td::tag()->content($row->type), - ); - } - - return Table::render(['#', 'Rule', 'Target', 'Verb', 'Suffix', 'Mode', 'Type'], $rows); - } - - /** - * Renders the resolved-route / dispatched-action summary `
` for the Current Route section. - * - * Surfaces the captured route and action unconditionally, so the pane stays informative when the resolver left no - * trace messages to replay. - */ - private static function renderRouteSummary(RouterCurrentView $current): string - { - if ($current->route === '' && $current->action === '') { - return ''; - } - - $items = []; - - if ($current->route !== '') { - $items[] = Dt::tag()->content('Resolved route'); - $items[] = Dd::tag()->html(Code::tag()->content($current->route)); - } - - if ($current->action !== '') { - $items[] = Dt::tag()->content('Dispatched action'); - $items[] = Dd::tag()->html(Code::tag()->content($current->action)); - } - - return Dl::tag() - ->class('yii-debug-router-summary') - ->html(...$items) - ->render(); - } -} diff --git a/src/Panel/User/UserGuestRenderer.php b/src/Panel/User/UserGuestRenderer.php deleted file mode 100644 index eb5875d..0000000 --- a/src/Panel/User/UserGuestRenderer.php +++ /dev/null @@ -1,32 +0,0 @@ -content( - 'The request was served to a guest, so there are no identity attributes, roles, or permissions to inspect.', - ), - P::tag() - ->content( - 'Sign in and reload to inspect the identity. User switching remains unavailable to guests.', - ), - ); - } -} diff --git a/src/Panel/User/UserIdentityRenderer.php b/src/Panel/User/UserIdentityRenderer.php deleted file mode 100644 index 71464e7..0000000 --- a/src/Panel/User/UserIdentityRenderer.php +++ /dev/null @@ -1,175 +0,0 @@ -` containing the hero header and every - * non-empty section. - */ - public static function render(UserIdentityView $view): string - { - $children = [self::renderHero($view->hero)]; - - foreach ($view->sections as $section) { - $children[] = self::renderSection($section); - } - - return Section::tag() - ->class('yii-debug-user') - ->html(...$children)->render(); - } - - /** - * Renders one attribute row as a `
` carrying the `
` label and the kind-specific - * `
` value. - */ - private static function renderAttribute(UserAttribute $attribute): Div - { - return Div::tag() - ->class('yii-debug-user-row') - ->html( - Dt::tag()->content($attribute->label), - Dd::tag()->html(self::renderValue($attribute)), - ); - } - - /** - * Renders the hero card header: avatar monogram, name, email, status pill, and id pill. - */ - private static function renderHero(UserIdentityHero $hero): Header - { - $tags = []; - - if ($hero->statusLabel !== '') { - $tags[] = Span::tag() - ->class('yii-debug-user-status yii-debug-user-status-' . $hero->statusVariant) - ->content($hero->statusLabel); - } - - if ($hero->idValue !== '') { - $tags[] = Span::tag() - ->class('yii-debug-user-pill') - ->content("ID #{$hero->idValue}"); - } - - $metaChildren = [ - H2::tag() - ->class('yii-debug-user-name') - ->content($hero->username), - ]; - - if ($hero->email !== '') { - $metaChildren[] = P::tag() - ->class('yii-debug-user-handle') - ->content($hero->email); - } - - $metaChildren[] = Div::tag() - ->class('yii-debug-user-tags') - ->html(...$tags); - - return Header::tag() - ->class('yii-debug-user-card') - ->html( - Span::tag() - ->addAttribute('aria-hidden', 'true') - ->class('yii-debug-user-avatar') - ->content($hero->monogram), - Div::tag() - ->class('yii-debug-user-meta') - ->html(...$metaChildren), - ); - } - - /** - * Renders one section as an `
` with the header chip and the `
` of attribute rows. - */ - private static function renderSection(UserIdentitySection $section): Article - { - $rows = []; - - foreach ($section->attributes as $attribute) { - $rows[] = self::renderAttribute($attribute); - } - - return Article::tag() - ->class('yii-debug-user-section') - ->html( - Header::tag() - ->html( - Span::tag() - ->addAttribute('aria-hidden', 'true') - ->class('yii-debug-user-section-icon') - ->html($section->icon), - Span::tag()->content($section->label), - ), - Dl::tag()->html(...$rows), - ); - } - - /** - * Renders the right-hand `
` content for one attribute, branching on `$attribute->kind`. - */ - private static function renderValue(UserAttribute $attribute): Span|Button - { - if ($attribute->kind === UserAttribute::KIND_EMPTY) { - return Span::tag() - ->class('yii-debug-user-empty') - ->content('—'); - } - - if ($attribute->kind === UserAttribute::KIND_SECURITY) { - return Button::tag() - ->addAriaAttribute('label', 'Reveal ' . $attribute->label) - ->addAriaAttribute('pressed', 'false') - ->addDataAttribute('yii-debug-reveal', true) - ->addDataAttribute('yii-debug-reveal-label', $attribute->label) - ->class('yii-debug-user-reveal') - ->html( - Span::tag() - ->class('yii-debug-user-mask') - ->content('••••••••••••'), - Span::tag() - ->class('yii-debug-user-real') - ->content($attribute->displayValue), - Span::tag() - ->class('yii-debug-user-reveal-cta') - ->addAttribute('aria-hidden', 'true'), - ) - ->type('button'); - } - - if ($attribute->kind === UserAttribute::KIND_TIMESTAMP) { - return Span::tag() - ->class('yii-debug-user-time') - ->title($attribute->displayValue) - ->html( - Span::tag() - ->class('yii-debug-user-time-rel') - ->content($attribute->timestampRel), - Span::tag() - ->class('yii-debug-user-time-abs') - ->content($attribute->timestampAbs), - ); - } - - return Span::tag() - ->class('yii-debug-user-value') - ->content($attribute->displayValue); - } -} diff --git a/src/Panel/User/UserPanel.php b/src/Panel/User/UserPanel.php new file mode 100644 index 0000000..3cdeaf8 --- /dev/null +++ b/src/Panel/User/UserPanel.php @@ -0,0 +1,303 @@ + Tone applied to each status variant the normalizer resolves. + */ + private const array STATUS_TONES = [ + 'success' => Tone::SUCCESS, + 'danger' => Tone::DANGER, + 'muted' => Tone::MUTED, + ]; + + /** + * Builds the panel view from the decoded identity capture. + * + * @param array $data Decoded panel payload with a `data` key holding the tagged identity. + * + * @return PanelView Identity overview, attribute sections, and the RBAC tables. + */ + public function present(array $data): PanelView + { + $payload = UserSnapshot::fromArray($data, '$.user')->data(); + + $identity = $payload['identity'] ?? null; + + if (is_array($identity) === false || $identity === []) { + return PanelView::create() + ->active(false) + ->emptyState( + 'No authenticated user', + 'This request ran as a guest, so the debugger captured no identity to inspect.', + [ + 'Sign in and reload the page; the identity appears here as soon as ', + PanelView::code('Yii::$app->user->identity'), + ' resolves.', + ], + ); + } + + $view = self::identity(self::strings($identity), $payload['attributes'] ?? null); + $view = self::rbac($view, 'Roles', $payload['roles'] ?? null); + + return self::rbac($view, 'Permissions', $payload['permissions'] ?? null); + } + + /** + * Renders one attribute row according to the kind the normalizer resolved. + * + * @param UserAttribute $attribute Typed attribute row. + * + * @return mixed Inline value describing the attribute. + */ + private static function attribute(UserAttribute $attribute): mixed + { + return match ($attribute->kind) { + UserAttribute::KIND_EMPTY => self::PLACEHOLDER, + UserAttribute::KIND_SECURITY => PanelView::preview($attribute->displayValue), + UserAttribute::KIND_TIMESTAMP => self::timestampLabel($attribute), + default => $attribute->displayValue, + }; + } + + /** + * Builds the identity overview and one section per attribute bucket. + * + * @param array $identity Captured identity attributes. + * @param mixed $attributes Optional label map captured alongside the identity. + * + * @return PanelView View carrying the identity overview and its attribute sections. + */ + private static function identity(array $identity, mixed $attributes): PanelView + { + $labels = is_array($attributes) ? self::labels($attributes) : null; + + $identityView = UserDataNormalizer::fromIdentity( + $identity, + $labels, + ); + + $hero = $identityView->hero; + + $view = PanelView::create() + ->summary('', $hero->username) + ->toolbar('User', $hero->username) + ->overview( + [ + 'User' => $hero->username, + 'Email' => $hero->email === '' ? self::PLACEHOLDER : $hero->email, + 'User ID' => $hero->idValue === '' ? self::PLACEHOLDER : $hero->idValue, + 'Status' => self::status($hero), + ], + true, + ); + + foreach ($identityView->sections as $section) { + $fields = []; + + foreach ($section->attributes as $attribute) { + $fields[$attribute->label] = self::attribute($attribute); + } + + $view = $view + ->heading($section->label, true) + ->overview($fields, true); + } + + return $view; + } + + /** + * Narrows the captured label map into the shape the normalizer accepts. + * + * @param array $attributes Captured label map. + * + * @return list Label definitions in capture order. + */ + private static function labels(array $attributes): array + { + $labels = []; + + foreach ($attributes as $entry) { + if (is_array($entry) === false) { + continue; + } + + $attribute = $entry['attribute'] ?? null; + $label = $entry['label'] ?? null; + + if (is_string($attribute) && is_string($label)) { + $labels[] = ['attribute' => $attribute, 'label' => $label]; + } + } + + return $labels; + } + + /** + * Appends one RBAC section, explaining the absence when the auth manager exposed no item. + * + * @param PanelView $view View to extend. + * @param string $label Section label, either roles or permissions. + * @param mixed $rows Captured RBAC rows, or `null` when the auth manager exposed none. + * + * @return PanelView View completed with the RBAC section. + */ + private static function rbac(PanelView $view, string $label, mixed $rows): PanelView + { + $items = []; + + if (is_array($rows)) { + foreach ($rows as $row) { + $items[] = UserRbacRow::fromArray(is_array($row) ? $row : []); + } + } + + $view = $view->heading(sprintf('%s (%d)', $label, count($items)), true); + + if ($items === []) { + return $view->paragraph('The auth manager granted no ' . strtolower($label) . ' to this identity.'); + } + + $table = []; + + foreach ($items as $index => $item) { + $table[] = [ + $index + 1, + $item->name === '' ? self::PLACEHOLDER : $item->name, + $item->description === '' ? self::PLACEHOLDER : $item->description, + $item->ruleName === '' ? self::PLACEHOLDER : $item->ruleName, + $item->data === '' ? self::PLACEHOLDER : $item->data, + self::timestamp($item->createdAt), + self::timestamp($item->updatedAt), + ]; + } + + return $view->table( + ['#', 'Name', 'Description', 'Rule', 'Data', 'Created', 'Updated'], + $table, + true, + [ + 0 => ColumnStyle::NUMBER, + 1 => ColumnStyle::IDENTIFIER, + 3 => ColumnStyle::IDENTIFIER, + 4 => ColumnStyle::MONOSPACE, + 5 => ColumnStyle::IDENTIFIER, + 6 => ColumnStyle::IDENTIFIER, + ], + ); + } + + /** + * Builds the badge describing the captured account status. + * + * The normalizer only resolves the three variants listed, so the fallback only satisfies the type checker. + * + * @param UserIdentityHero $hero Typed identity header. + * + * @return BadgeInline Badge carrying the status label and tone. + */ + private static function status(UserIdentityHero $hero): array + { + $label = $hero->statusLabel === '' ? 'Unknown' : $hero->statusLabel; + + return PanelView::badge( + $label, + self::STATUS_TONES[$hero->statusVariant] ?? Tone::MUTED, + ); + } + + /** + * Narrows the captured identity map to string values. + * + * @param array $identity Captured identity attributes. + * + * @return array Identity attributes narrowed to strings. + */ + private static function strings(array $identity): array + { + $values = []; + + foreach ($identity as $key => $value) { + if (is_string($key) && is_string($value)) { + $values[$key] = $value; + } + } + + return $values; + } + + /** + * Formats an RBAC timestamp, falling back to the placeholder when the item recorded none. + * + * @param int|null $timestamp Unix-epoch second, or `null` when not recorded. + * + * @return string Formatted timestamp, or the placeholder. + */ + private static function timestamp(int|null $timestamp): string + { + return $timestamp === null ? self::PLACEHOLDER : date(self::DATE_FORMAT, $timestamp); + } + + /** + * Joins the absolute and relative forms of a captured timestamp. + * + * Past the relative scale the normalizer already falls back to the absolute form, so the two forms are joined + * only while they differ. + * + * @param UserAttribute $attribute Typed timestamp attribute. + * + * @return string Absolute form, optionally followed by the relative one. + */ + private static function timestampLabel(UserAttribute $attribute): string + { + $absolute = $attribute->timestampAbs; + $relative = $attribute->timestampRel; + + return $relative === $absolute ? $absolute : "{$absolute} · {$relative}"; + } +} diff --git a/src/Panel/User/UserRbacRenderer.php b/src/Panel/User/UserRbacRenderer.php deleted file mode 100644 index 4b96945..0000000 --- a/src/Panel/User/UserRbacRenderer.php +++ /dev/null @@ -1,43 +0,0 @@ -content('Roles') - ->render() . $rolesGrid; - } - - if ($permissionsGrid !== null) { - $sections[] = H2::tag() - ->content('Permissions') - ->render() . $permissionsGrid; - } - - return implode('', $sections); - } -} diff --git a/src/View/History/HistoryCellRenderer.php b/src/View/History/HistoryCellRenderer.php index 6c40d31..c10e59e 100644 --- a/src/View/History/HistoryCellRenderer.php +++ b/src/View/History/HistoryCellRenderer.php @@ -139,7 +139,7 @@ public static function renderSqlCountCell( $warningParts[] = "{$row->excessiveCallersCount} {$callerLabel} making too many calls."; } - $warning = implode(' ', $warningParts); + $warning = implode("\n", $warningParts); $content = (string) $row->sqlCount; diff --git a/tests/Helper/PhpHighlighterTest.php b/tests/Helper/PhpHighlighterTest.php new file mode 100644 index 0000000..188f9bb --- /dev/null +++ b/tests/Helper/PhpHighlighterTest.php @@ -0,0 +1,64 @@ +alert(1)"); + + self::assertStringNotContainsString( + ''), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

<script>alert(1)</script> -
- Sent -
-
- <script>alert(1)</script> -
-
- HTML, - $html, - 'Body must be HTML-escaped.', - ); - } - - public function testRenderItemFormatsRelativeTimeForDaysAgoDelta(): void - { - self::freezeTime(); - - $html = MailCardRenderer::renderItem( - self::makeMessage(time: self::NOW - (3 * 86400)), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent3 d ago -
-
- Test body -
- - HTML, - $html, - "Days delta must read 'X d ago'.", - ); - } - - public function testRenderItemFormatsRelativeTimeForHoursAgoDelta(): void - { - self::freezeTime(); - - $html = MailCardRenderer::renderItem( - self::makeMessage(time: self::NOW - 7200), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent2 h ago -
-
- Test body -
- - HTML, - $html, - "Hours delta must read 'X h ago'.", - ); - } - - public function testRenderItemFormatsRelativeTimeForJustNowDelta(): void - { - self::freezeTime(); - - $html = MailCardRenderer::renderItem( - self::makeMessage(time: self::NOW), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sentjust now -
-
- Test body -
- - HTML, - $html, - "Under-minute delta must read 'just now'.", - ); - } - - public function testRenderItemFormatsRelativeTimeForMinutesAgoDelta(): void - { - self::freezeTime(); - - $html = MailCardRenderer::renderItem( - self::makeMessage(time: self::NOW - 600), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent10 min ago -
-
- Test body -
- - HTML, - $html, - "Minutes delta must read 'X min ago'.", - ); - } - - public function testRenderItemOmitsBodyPreviewWhenBodyIsEmpty(): void - { - self::assertSame( - << -
-
- (no sender)

- Test subject -

-
- Sent -
-
- (empty body) -
- - HTML, - MailCardRenderer::renderItem(self::makeMessage(body: ''), self::makeUrlBuilder()), - 'Empty body must omit the preview span.', - ); - } - - public function testRenderItemOmitsBodyPreviewWhenWhitespaceNormalizationFails(): void - { - MockerState::addCondition( - 'PHPForge\\Debug\\Panel\\Mail', - 'preg_replace', - ['/\s+/', ' ', 'Test body'], - null, - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

-
- Sent -
-
- Test body -
- - HTML, - MailCardRenderer::renderItem( - self::makeMessage(), - self::makeUrlBuilder(), - ), - 'A PCRE failure must omit the preview without dropping the mail card.', - ); - } - - public function testRenderItemOmitsDownloadLinkWhenFileIsEmpty(): void - { - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent -
-
- Test body -
- - HTML, - MailCardRenderer::renderItem(self::makeMessage(file: ''), self::makeUrlBuilder()), - 'Empty file must omit the download link.', - ); - } - - public function testRenderItemOmitsRecipientBlockWhenAllListsAreEmpty(): void - { - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent -
-
- Test body -
- - HTML, - MailCardRenderer::renderItem(self::makeMessage(), self::makeUrlBuilder()), - 'Empty recipient lists must omit the block.', - ); - } - - public function testRenderItemOmitsTechDetailsWhenBothHeadersAndCharsetAreEmpty(): void - { - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent -
-
- Test body -
- - HTML, - MailCardRenderer::renderItem(self::makeMessage(headers: '', charset: ''), self::makeUrlBuilder()), - 'Empty headers and charset must omit the tech details.', - ); - } - - public function testRenderItemOmitsTimeWhenNull(): void - { - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent -
-
- Test body -
- - HTML, - MailCardRenderer::renderItem(self::makeMessage(time: null), self::makeUrlBuilder()), - "'null' time must omit the time span.", - ); - } - - public function testRenderItemRendersAvatarHueDeterministicallyFromSender(): void - { - $first = MailCardRenderer::renderItem( - self::makeMessage(from: 'a@example.com'), - self::makeUrlBuilder(), - ); - $second = MailCardRenderer::renderItem( - self::makeMessage(from: 'a@example.com'), - self::makeUrlBuilder(), - ); - $third = MailCardRenderer::renderItem( - self::makeMessage(from: 'b@example.com'), - self::makeUrlBuilder(), - ); - - self::assertMatchesRegularExpression( - '/--mail-hue: \d+/', - $first, - 'Avatar must carry an inline hue style.', - ); - self::assertSame( - self::extractHue($first), - self::extractHue($second), - 'Same sender must produce the same hue.', - ); - self::assertNotSame( - self::extractHue($first), - self::extractHue($third), - 'Different senders must produce different hues.', - ); - } - - public function testRenderItemRendersBodyPreviewTruncatedAt140Characters(): void - { - $longBody = str_repeat('Lorem ipsum dolor sit amet, ', 20); - - $html = MailCardRenderer::renderItem( - self::makeMessage(body: $longBody), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Lorem ipsum dolor sit amet, Lorem ipsum dolor sit amet, Lorem ipsum dolor sit amet, Lorem ipsum dolor sit amet, Lorem ipsum dolor sit amet, … -
- Sent -
-
- {$longBody} -
- - HTML, - $html, - 'Preview span must be present when body is non-empty.', - ); - - } - - public function testRenderItemRendersDownloadLinkWhenFileIsSet(): void - { - $html = MailCardRenderer::renderItem( - self::makeMessage(file: '/tmp/mail.eml'), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent -
-
- Test body -
- - HTML, - $html, - 'Download link must carry the dedicated class.', - ); - } - - public function testRenderItemRendersEachRecipientGroupWhenItIsTheOnlyPopulatedList(): void - { - $cases = [ - 'to' => [ - ['only-to@example.com'], - [], - [], - [], - 'to', - 'TO', - 'only-to@example.com', - ], - 'cc' => [ - [], - ['only-cc@example.com'], - [], - [], - 'cc', - 'CC', - 'only-cc@example.com', - ], - 'bcc' => [ - [], - [], - ['only-bcc@example.com'], - [], - 'bcc', - 'BCC', - 'only-bcc@example.com', - ], - 'reply' => [ - [], - [], - [], - ['only-reply@example.com'], - 'reply', - 'REPLY-TO', - 'only-reply@example.com', - ], - ]; - - foreach ($cases as $name => [$to, $cc, $bcc, $replyTo, $role, $label, $address]) { - $html = MailCardRenderer::renderItem( - self::makeMessage(to: $to, cc: $cc, bcc: $bcc, replyTo: $replyTo), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent -
-
-
- {$label}{$address} -
-
- Test body -
- - HTML, - $html, - "{$name} alone must render the exact recipient card markup.", - ); - } - } - - public function testRenderItemRendersEmptyBodyPlaceholderWhenBodyIsEmpty(): void - { - $html = MailCardRenderer::renderItem( - self::makeMessage(body: ''), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

-
- Sent -
-
- (empty body) -
- - HTML, - $html, - 'Empty body must use the empty-body modifier.', - ); - - } - - public function testRenderItemRendersFallbackPlaceholdersWhenFromOrSubjectAreEmpty(): void - { - $html = MailCardRenderer::renderItem( - self::makeMessage(from: '', subject: ''), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- (no subject) -

Test body -
- Sent -
-
- Test body -
- - HTML, - $html, - "Empty from must fall back to '(no sender)'.", - ); - } - - public function testRenderItemRendersFromAndSubject(): void - { - $html = MailCardRenderer::renderItem( - self::makeMessage(from: 'sender@example.com', subject: 'Welcome'), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- sender@example.com

- Welcome -

Test body -
- Sent -
-
- Test body -
- - HTML, - $html, - 'Sender address must be visible.', - ); - } - - public function testRenderItemRendersRecipientGroupsWithLabelsAndPills(): void - { - $html = MailCardRenderer::renderItem( - self::makeMessage( - to: [ - 'a@example.com', - 'b@example.com', - ], - cc: ['carbon@example.com'], - bcc: ['bcc@example.com'], - replyTo: ['reply@example.com'], - ), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent -
-
-
- TOa@example.comb@example.com -
- CCcarbon@example.com -
- BCCbcc@example.com -
- REPLY-TOreply@example.com -
-
- Test body -
- - HTML, - $html, - 'Recipients wrapper must be present.', - ); - self::assertSame( - 5, - substr_count($html, 'class="yii-debug-mail-recipient-pill"'), - 'Every declared recipient must be wrapped in its own pill.', - ); - } - - public function testRenderItemRendersStatusFailWhenIsSuccessfulIsFalse(): void - { - $html = MailCardRenderer::renderItem( - self::makeMessage(isSuccessful: false), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Failed -
-
- Test body -
- - HTML, - $html, - "Failed messages must use the 'fail' variant.", - ); - } - - public function testRenderItemRendersStatusOkWhenIsSuccessfulIsTrue(): void - { - $html = MailCardRenderer::renderItem( - self::makeMessage(isSuccessful: true), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent -
-
- Test body -
- - HTML, - $html, - "Successful messages must use the 'ok' variant.", - ); - } - - public function testRenderItemRendersTechDetailsWhenHeadersOrCharsetSet(): void - { - $html = MailCardRenderer::renderItem( - self::makeMessage(headers: 'X-Foo: bar', charset: 'UTF-8'), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent -
-
- Test body -
- - Raw headersUTF-8 -
-            X-Foo: bar
-            
-
- - HTML, - $html, - 'Tech details wrapper must be present.', - ); - } - - public function testRenderItemRendersTechDetailsWhenOnlyOneTechnicalFieldIsSet(): void - { - $headersOnly = MailCardRenderer::renderItem( - self::makeMessage(headers: 'X-Only: header', charset: ''), - self::makeUrlBuilder(), - ); - $charsetOnly = MailCardRenderer::renderItem( - self::makeMessage(headers: '', charset: 'UTF-16'), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent -
-
- Test body -
- - Raw headers -
-            X-Only: header
-            
-
- - HTML, - $headersOnly, - 'Headers alone must render details.', - ); - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent -
-
- Test body -
- - Raw headersUTF-16 -
-            
-
- - HTML, - $charsetOnly, - 'Charset alone must render details.', - ); - } - - public function testRenderItemRendersTimeWhenSet(): void - { - $html = MailCardRenderer::renderItem( - self::makeMessage(time: 1_700_000_000), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- SentNov 14, 2023 · 22:13:20 -
-
- Test body -
- - HTML, - $html, - 'Time span must carry the dedicated class.', - ); - self::assertMatchesRegularExpression( - '/title="[A-Z][a-z]{2} \d/', - $html, - 'Time tooltip must contain the formatted absolute date.' - ); - } - - public function testRenderItemRendersUnicodeAndEmptyLocalPartInitials(): void - { - $unicode = MailCardRenderer::renderItem( - self::makeMessage(from: 'élise@example.com'), - self::makeUrlBuilder(), - ); - $emptyLocal = MailCardRenderer::renderItem( - self::makeMessage(from: '@example.com'), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- élise@example.com

- Test subject -

Test body -
- Sent -
-
- Test body -
- - HTML, - $unicode, - 'Unicode initials must be sliced and uppercased safely.', - ); - self::assertSame( - << -
-
- @example.com

- Test subject -

Test body -
- Sent -
-
- Test body -
- - HTML, - $emptyLocal, - 'An empty local part must fall back to the full address.', - ); - } - - public function testRenderItemRendersUppercasedFirstLetterOfLocalPartAsInitial(): void - { - $html = MailCardRenderer::renderItem( - self::makeMessage(from: 'wilmer@example.com'), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- wilmer@example.com

- Test subject -

Test body -
- Sent -
-
- Test body -
- - HTML, - $html, - 'Initial must be the uppercased first letter of the local part.', - ); - } - - public function testRenderItemSkipsRecipientGroupsThatAreEmpty(): void - { - $html = MailCardRenderer::renderItem( - self::makeMessage(to: ['user@example.com'], cc: [], bcc: [], replyTo: []), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent -
-
-
- TOuser@example.com -
-
- Test body -
- - HTML, - $html, - "Populated 'TO' group must be rendered.", - ); - - } - - public function testRenderItemUsesExactRelativeTimeBoundariesAndUnits(): void - { - self::freezeTime(); - - $cases = [ - ['1 min ago', 60, 'the minute boundary'], - ['1 h ago', 3600, 'the hour boundary'], - ['1 d ago', 86400, 'the day boundary'], - ['1 min ago', 118, 'a non-divisible minute delta'], - ['1 h ago', 7198, 'a non-divisible hour delta'], - ['1 d ago', 172798, 'a non-divisible day delta'], - ]; - - foreach ($cases as [$expected, $diff, $description]) { - $time = self::NOW - $diff; - - $absoluteTime = date('M j, Y · H:i:s', $time); - - $html = MailCardRenderer::renderItem( - self::makeMessage(time: $time), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent{$expected} -
-
- Test body -
- - HTML, - $html, - "Relative time must render the exact card markup for {$description}.", - ); - } - - $html = MailCardRenderer::renderItem( - self::makeMessage(time: self::NOW - 2_592_000), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- SentOct 15, 2023 · 22:13:20 -
-
- Test body -
- - HTML, - $html, - 'Thirty days must switch to the absolute label.', - ); - } - - public function testRenderItemWrapsContentInArticleWithMailCardClass(): void - { - $html = MailCardRenderer::renderItem( - self::makeMessage(), - self::makeUrlBuilder(), - ); - - self::assertSame( - << -
-
- (no sender)

- Test subject -

Test body -
- Sent -
-
- Test body -
- - HTML, - $html, - 'Outer wrapper class must be present.', - ); - } - - /** - * Extracts the avatar hue value from rendered HTML for hue-stability assertions. - */ - private static function extractHue(string $html): int - { - if (preg_match('/--mail-hue: (\d+)/', $html, $m) === 1) { - return (int) $m[1]; - } - - self::fail('No avatar hue found in rendered HTML.'); - } - - private static function freezeTime(): void - { - MockerState::addCondition( - 'PHPForge\\Debug\\Panel\\Mail', - 'time', - [], - self::NOW, - true, - ); - } - - /** - * @param list $to - * @param list $cc - * @param list $bcc - * @param list $replyTo - */ - private static function makeMessage( - string $from = '', - array $to = [], - array $cc = [], - array $bcc = [], - array $replyTo = [], - string $subject = 'Test subject', - string $body = 'Test body', - string $headers = '', - string $charset = '', - string $file = '', - bool $isSuccessful = true, - int|null $time = null, - ): MailMessage { - return new MailMessage( - from: $from, - to: $to, - cc: $cc, - bcc: $bcc, - replyTo: $replyTo, - subject: $subject, - body: $body, - headers: $headers, - charset: $charset, - file: $file, - isSuccessful: $isSuccessful, - time: $time, - ); - } - - /** - * Builds a deterministic download-URL builder so tests can assert the rendered href without needing an active - * controller context. - * - * @return callable(string): string - */ - private static function makeUrlBuilder(): callable - { - return static fn(string $file): string => "/debug/download-mail?file={$file}"; - } -} diff --git a/tests/Panel/Mail/MailPanelTest.php b/tests/Panel/Mail/MailPanelTest.php new file mode 100644 index 0000000..e47a8ed --- /dev/null +++ b/tests/Panel/Mail/MailPanelTest.php @@ -0,0 +1,604 @@ +isActive(), + 'An empty inbox must not activate navigation.', + ); + self::assertSame( + [], + $view->summaryMetrics(), + 'An empty inbox must carry no summary metric.', + ); + self::assertSame( + [], + $view->toolbarMetrics(), + 'An empty inbox must carry no toolbar metric.', + ); + + $state = self::emptyState(self::blockAt($view, 0)); + + self::assertCount( + 1, + $view->blocks(), + 'The empty state must replace the table and the groups.', + ); + self::assertSame( + 'No emails sent in this request', + $state['title'], + 'The empty state must keep its heading.', + ); + self::assertSame( + ['This request did not dispatch any messages through the mailer, so the inbox is empty.'], + self::inlineValues($state['paragraphs'][0] ?? self::fail('The empty state must explain itself.')), + 'The first paragraph must describe the empty inbox.', + ); + self::assertSame( + [ + 'BaseMailer::EVENT_AFTER_SEND', + ' is the capture hook; only requests that call ', + '$mailer->send()', + ' populate this view.', + ], + self::inlineValues($state['paragraphs'][1] ?? self::fail('The empty state must name the capture hook.')), + 'The capture hook explanation must stay complete and ordered.', + ); + } + + public function testEmptyEnvelopeFieldsFallBackToPlaceholders(): void + { + $view = self::present( + [ + 'from' => '', + 'to' => '', + 'subject' => '', + 'body' => '', + 'headers' => '', + 'isSuccessful' => true, + 'time' => null, + ], + ); + + $row = self::table(self::blockAt($view, 0))['rows'][0] ?? self::fail('The table must describe every message.'); + + self::assertSame( + '—', + self::textValue($row[1] ?? self::fail('The sender column must exist.')), + 'A missing sender must show the placeholder.', + ); + self::assertSame( + '(no subject)', + self::textValue($row[2] ?? self::fail('The subject column must exist.')), + 'A missing subject must show its own fallback.', + ); + self::assertSame( + '—', + self::textValue($row[3] ?? self::fail('The recipient column must exist.')), + 'An empty recipient group must show the placeholder.', + ); + self::assertSame( + '—', + self::textValue($row[5] ?? self::fail('The time column must exist.')), + 'A message without time must show the placeholder.', + ); + self::assertSame( + '1. (no subject)', + self::heading(self::blockAt($view, 1))['title'], + 'The heading must reuse the subject fallback.', + ); + + $fields = self::fields(self::overview(self::childBlockAt(self::group(self::blockAt($view, 2)), 0))); + + self::assertSame( + '—', + self::textValue($fields['From'] ?? self::fail('The envelope must keep the sender row.')), + 'A missing sender must show the placeholder.', + ); + self::assertSame( + '—', + self::textValue($fields['Sent at'] ?? self::fail('The envelope must keep the time row.')), + 'A message without time must show the placeholder.', + ); + } + + public function testFailedDeliveryIsCountedInTheSummaryAndBadgedAsDanger(): void + { + $view = self::present(self::message(['isSuccessful' => false]), self::message()); + + self::assertSame( + [' emails', ' failed'], + self::metricLabels($view->summaryMetrics()), + 'The failed count must follow the total.', + ); + self::assertSame( + '1', + self::metricValue($view->summaryMetrics(), 1), + 'Only the rejected message must be counted as failed.', + ); + + $rows = self::table(self::blockAt($view, 0))['rows']; + $failed = self::badge($rows[0][4] ?? self::fail('The status column must exist.')); + + self::assertSame( + 'Failed', + $failed['label'], + 'A rejected message must be labeled as failed.', + ); + self::assertSame( + Tone::DANGER, + $failed['tone'], + 'A rejected message must use the danger tone.', + ); + + $sent = self::badge($rows[1][4] ?? self::fail('The status column must exist.')); + + self::assertSame( + 'Sent', + $sent['label'], + 'A delivered message must be labeled as sent.', + ); + self::assertSame( + Tone::SUCCESS, + $sent['tone'], + 'A delivered message must use the success tone.', + ); + } + + public function testMetadataMatchesTheBuiltInMailPanel(): void + { + $panel = new MailPanel(); + + self::assertSame( + 'mail', + $panel->id(), + 'The persisted panel identifier must stay stable.', + ); + self::assertSame( + 'Mail', + $panel->name(), + 'The navigation title must stay stable.', + ); + self::assertSame( + 'mail', + $panel->icon(), + 'The panel must reuse the existing icon.', + ); + } + + public function testMultipleMessagesUseThePluralSummaryAndOneGroupPerMessage(): void + { + $view = self::present(self::message(), self::message(['subject' => 'Second notice'])); + + self::assertSame( + [' emails'], + self::metricLabels($view->summaryMetrics()), + 'A delivered inbox must carry only the total.', + ); + self::assertSame( + '2', + self::metricValue($view->toolbarMetrics(), 0), + 'The toolbar must report the captured count.', + ); + self::assertCount( + 5, + $view->blocks(), + 'Each message must add its own heading and group.', + ); + self::assertSame( + '2. Second notice', + self::heading(self::blockAt($view, 3))['title'], + 'Headings must be numbered in capture order.', + ); + self::assertSame( + 'Message 2', + self::group(self::blockAt($view, 4))['label'], + 'Groups must be numbered in capture order.', + ); + } + + public function testOptionalEnvelopeFieldsAreOmittedWhenTheCaptureLacksThem(): void + { + $view = self::present( + [ + 'from' => 'alice@example.com', + 'to' => 'bob@example.com', + 'subject' => 'Welcome aboard', + 'headers' => '', + 'isSuccessful' => true, + 'time' => self::TIME, + ], + ); + $content = self::group(self::blockAt($view, 2)); + + self::assertSame( + ['From', 'To', 'Subject', 'Status', 'Sent at'], + array_keys(self::fields(self::overview(self::childBlockAt($content, 0)))), + 'Uncaptured envelope rows must be dropped, not filled with placeholders.', + ); + self::assertSame( + 'paragraph', + self::childBlockAt($content, 1)['kind'], + 'A message without body must state it instead of opening a disclosure.', + ); + self::assertCount( + 2, + $content['content']->blocks(), + 'Uncaptured headers must not open a disclosure.', + ); + } + + public function testSingleMessageProducesTheSummaryTableAndItsDetailGroup(): void + { + $view = self::present(self::message()); + + self::assertTrue( + $view->isActive(), + 'A captured message must activate navigation.', + ); + self::assertSame( + [' email'], + self::metricLabels($view->summaryMetrics()), + 'A single message must use the singular label.', + ); + + $table = self::table(self::blockAt($view, 0)); + + self::assertSame( + ['#', 'From', 'Subject', 'To', 'Status', 'Time'], + $table['headers'], + 'The column order must stay stable.', + ); + self::assertSame( + [ + 0 => ColumnStyle::NUMBER, + 4 => ColumnStyle::PILL, + 5 => ColumnStyle::IDENTIFIER, + ], + $table['styles'], + 'Each style must stay attached to the column it formats.', + ); + + $row = $table['rows'][0] ?? self::fail('The table must describe every message.'); + + self::assertSame( + '1', + self::textValue($row[0] ?? self::fail('The position column must exist.')), + 'Rows must be numbered from one.', + ); + self::assertSame( + 'bob@example.com, carol@example.com', + self::textValue($row[3] ?? self::fail('The recipient column must exist.')), + 'Every primary recipient must stay visible.', + ); + self::assertSame( + date('H:i:s', self::TIME), + self::textValue($row[5] ?? self::fail('The time column must exist.')), + 'The table must show the clock-only timestamp.', + ); + + $heading = self::heading(self::blockAt($view, 1)); + + self::assertSame( + '1. Welcome aboard', + $heading['title'], + 'The heading must number the message and repeat its subject.', + ); + self::assertTrue( + $heading['section'], + 'Each message must open a section-level heading.', + ); + + $content = self::group(self::blockAt($view, 2)); + + self::assertSame( + 'Message 1', + $content['label'], + 'The group must identify the message it describes.', + ); + + $overview = self::overview(self::childBlockAt($content, 0)); + + self::assertTrue( + $overview['compact'], + 'The envelope must use the compact presentation.', + ); + + $fields = self::fields($overview); + + self::assertSame( + ['From', 'To', 'Cc', 'Bcc', 'Reply-To', 'Subject', 'Status', 'Sent at', 'Charset', 'Stored file'], + array_keys($fields), + 'The envelope must keep every captured recipient group.', + ); + self::assertSame( + 'dave@example.com', + self::textValue($fields['Cc'] ?? self::fail('The envelope must keep the copy row.')), + 'Carbon copies must survive the migration.', + ); + self::assertSame( + 'erin@example.com', + self::textValue($fields['Bcc'] ?? self::fail('The envelope must keep the blind copy row.')), + 'Blind carbon copies must survive the migration.', + ); + self::assertSame( + 'noreply@example.com', + self::textValue($fields['Reply-To'] ?? self::fail('The envelope must keep the reply row.')), + 'Reply addresses must survive the migration.', + ); + self::assertSame( + 'UTF-8', + self::textValue($fields['Charset'] ?? self::fail('The envelope must keep the charset row.')), + 'The declared charset must survive the migration.', + ); + self::assertSame( + '2026-09-12-welcome.eml', + self::textValue($fields['Stored file'] ?? self::fail('The envelope must keep the file row.')), + 'The persisted file must survive the migration.', + ); + self::assertSame( + date('M j, Y · H:i:s', self::TIME), + self::textValue($fields['Sent at'] ?? self::fail('The envelope must keep the time row.')), + 'The envelope must show the absolute timestamp.', + ); + self::assertSame( + 'disclosure', + self::childBlockAt($content, 1)['kind'], + 'A captured body must open a disclosure.', + ); + self::assertSame( + 'disclosure', + self::childBlockAt($content, 2)['kind'], + 'Captured headers must open their own disclosure.', + ); + } + + /** + * @param Inline $inline Cell or field value to narrow. + * + * @return BadgeInline Narrowed status badge. + */ + private static function badge(array $inline): array + { + return match ($inline['kind']) { + 'badge' => $inline, + default => self::fail('The delivery status must be a badge.'), + }; + } + + /** + * @param PanelView $view View to read. + * @param int $index Position of the block in display order. + * + * @return Block Block declared at the requested position. + */ + private static function blockAt(PanelView $view, int $index): array + { + return $view->blocks()[$index] ?? self::fail('The declared presentation structure must be complete.'); + } + + /** + * @param GroupBlock $block Group whose child view is read. + * @param int $index Position of the block inside the group. + * + * @return Block Block declared at the requested position. + */ + private static function childBlockAt(array $block, int $index): array + { + return $block['content']->blocks()[$index] + ?? self::fail('The declared presentation structure must be complete.'); + } + + /** + * @param Block $block Block to narrow. + * + * @return EmptyStateBlock Narrowed empty state. + */ + private static function emptyState(array $block): array + { + return match ($block['kind']) { + 'emptyState' => $block, + default => self::fail('An empty inbox must be explained by an empty state.'), + }; + } + + /** + * @param OverviewBlock $block Overview whose fields are indexed. + * + * @return array Field values keyed by their label, in display order. + */ + private static function fields(array $block): array + { + $fields = []; + + foreach ($block['fields'] as $field) { + $fields[$field['label']] = $field['value']; + } + + return $fields; + } + + /** + * @param Block $block Block to narrow. + * + * @return GroupBlock Narrowed message group. + */ + private static function group(array $block): array + { + return match ($block['kind']) { + 'group' => $block, + default => self::fail('Each message must have an accessible group.'), + }; + } + + /** + * @param Block $block Block to narrow. + * + * @return array{kind: 'heading', title: string, section: bool} Narrowed message heading. + */ + private static function heading(array $block): array + { + return match ($block['kind']) { + 'heading' => $block, + default => self::fail('Each message must have a visible heading.'), + }; + } + + /** + * @param ParagraphBlock $block Paragraph whose inline content is read. + * + * @return list Text carried by each inline value, in display order. + */ + private static function inlineValues(array $block): array + { + $values = []; + + foreach ($block['content'] as $inline) { + $values[] = self::textValue($inline); + } + + return $values; + } + + /** + * Builds a fully populated capture payload, overriding the requested fields. + * + * @param array $overrides Capture fields replacing the defaults. + * + * @return array Capture payload accepted by {@see MailSnapshot::capture()}. + */ + private static function message(array $overrides = []): array + { + return [ + 'from' => 'alice@example.com', + 'to' => 'bob@example.com, carol@example.com', + 'cc' => 'dave@example.com', + 'bcc' => 'erin@example.com', + 'reply' => 'noreply@example.com', + 'subject' => 'Welcome aboard', + 'body' => 'Hello Bob, welcome aboard.', + 'headers' => "Content-Type: text/plain\nX-Mailer: test", + 'charset' => 'UTF-8', + 'file' => '2026-09-12-welcome.eml', + 'isSuccessful' => true, + 'time' => self::TIME, + ...$overrides, + ]; + } + + /** + * @param list $metrics Metrics in display order. + * + * @return list Metric labels in display order. + */ + private static function metricLabels(array $metrics): array + { + $labels = []; + + foreach ($metrics as $metric) { + $labels[] = $metric['label']; + } + + return $labels; + } + + /** + * @param list $metrics Metrics in display order. + * @param int $index Position of the metric in display order. + * + * @return string Metric value as plain text. + */ + private static function metricValue(array $metrics, int $index): string + { + $metric = $metrics[$index] ?? self::fail('The declared presentation structure must be complete.'); + + return self::textValue($metric['value']); + } + + /** + * @param Block $block Block to narrow. + * + * @return OverviewBlock Narrowed envelope overview. + */ + private static function overview(array $block): array + { + return match ($block['kind']) { + 'overview' => $block, + default => self::fail('Each message must keep an inspectable envelope.'), + }; + } + + /** + * Presents the given capture payloads through the panel under test. + * + * @param array ...$messages Capture payloads in send order. + * + * @return PanelView Description built by the panel. + */ + private static function present(array ...$messages): PanelView + { + return (new MailPanel())->present(MailSnapshot::capture($messages)->jsonSerialize()); + } + + /** + * @param Block $block Block to narrow. + * + * @return TableBlock Narrowed summary table. + */ + private static function table(array $block): array + { + return match ($block['kind']) { + 'table' => $block, + default => self::fail('The summary must use the shared table contract.'), + }; + } + + /** + * @param Inline $inline Cell or field value to read. + * + * @return string Text carried by the value. + */ + private static function textValue(array $inline): string + { + return match ($inline['kind']) { + 'text' => $inline['value'], + default => self::fail('The value must be plain text.'), + }; + } +} diff --git a/tests/Panel/PanelRendererTest.php b/tests/Panel/PanelRendererTest.php index ec7dfd2..950a819 100644 --- a/tests/Panel/PanelRendererTest.php +++ b/tests/Panel/PanelRendererTest.php @@ -6,7 +6,7 @@ use JsonException; use PHPForge\Debug\{ColumnStyle, PanelView, Tone}; -use PHPForge\Debug\Helper\CellMore; +use PHPForge\Debug\Helper\{CellMore, Trace}; use PHPForge\Debug\Panel\PanelRenderer; use PHPForge\Debug\Tests\Provider\PanelRendererProvider; use PHPUnit\Framework\Attributes\DataProviderExternal; @@ -19,12 +19,42 @@ */ final class PanelRendererTest extends TestCase { + public function testCapturedFramesRenderThroughTheAdapterFrameRenderer(): void + { + $view = PanelView::create()->paragraph( + PanelView::trace([['file' => '/app/src/Site.php', 'line' => 42], ['internal' => 'call_user_func']]), + ); + + self::assertStringContainsString( + '
    ', + PanelRenderer::render('Custom', $view), + 'Frames must reuse the existing trace list markup.', + ); + self::assertStringContainsString( + 'ide://open?url=file:///app/src/Site.php&line=42', + PanelRenderer::render('Custom', $view), + 'The default frame renderer must keep its editor deep link.', + ); + self::assertStringContainsString( + '
  • ' . "\n" . '/app/src/Site.php:42' . "\n" . '
  • ', + PanelRenderer::render('Custom', $view, Trace::create()->withTemplate(false)), + 'An adapter-configured frame renderer must replace the default one.', + ); + self::assertStringContainsString( + 'internal', + PanelRenderer::render('Custom', $view), + 'A frame without file or line must still be inspectable.', + ); + } #[DataProviderExternal(PanelRendererProvider::class, 'nonCollapsingTables')] public function testCollapseRequiresOptInAndMoreRowsThanThreshold(int $count, bool $collapsible): void { $view = PanelView::create()->table(['Value'], array_fill(0, $count, ['short']), $collapsible); - $html = PanelRenderer::render('Custom', $view); + $html = PanelRenderer::render( + 'Custom', + $view, + ); self::assertStringNotContainsString( 'class="yii-debug-cell-more"', @@ -33,6 +63,127 @@ public function testCollapseRequiresOptInAndMoreRowsThanThreshold(int $count, bo ); } + public function testFilterableTablesReuseTheExistingRowFilter(): void + { + $html = PanelRenderer::render( + 'Custom', + PanelView::create()->table(['Name', 'Value'], [['HTTP_HOST', 'example.test']], filterable: true), + ); + + self::assertStringContainsString( + 'data-yii-debug-filter-scope="true"', + $html, + 'The filter must be paired with its table through an explicit scope.', + ); + self::assertStringContainsString( + '', + $html, + 'The filter input must carry the hook the shared row filter listens to.', + ); + self::assertStringContainsString( + 'data-yii-debug-filter-target="true"', + $html, + 'The table must declare itself as the filter target.', + ); + self::assertStringNotContainsString( + 'yii-debug-filter-scope', + PanelRenderer::render('Custom', PanelView::create()->table(['Name'], [['x']])), + 'An ordinary table must not request the row filter.', + ); + } + + public function testFilterableTablesStayCollapsibleAboveTheRowThreshold(): void + { + $rows = array_fill(0, CellMore::ROW_THRESHOLD + 1, ['x']); + + $html = PanelRenderer::render( + 'Custom', + PanelView::create()->table(['Name'], $rows, collapsible: true, filterable: true), + ); + + self::assertStringContainsString( + 'yii-debug-cell-more-toggle', + $html, + 'Filtering must not remove the collapse control.', + ); + self::assertStringContainsString( + 'data-yii-debug-filter-scope="true"', + $html, + 'Collapsing must not remove the filter scope.', + ); + } + + public function testFrontendContractPinsOrderSeparatorsAndColumnTreatments(): void + { + $html = PanelRenderer::render( + 'Custom', + PanelView::create() + ->summary(' first', 1) + ->summary(' second', 2) + ->heading('Section', true) + ->heading('Plain') + ->table( + ['Pill', 'Badge', 'Plain'], + [['queued', PanelView::badge('done', Tone::SUCCESS), 'text']], + styles: [0 => ColumnStyle::PILL, 1 => ColumnStyle::PILL], + ), + ); + + self::assertStringContainsString( + '1 first' + . '·' + . '2 second', + $html, + 'Exactly one separator must sit between the two metrics, never before the first.', + ); + self::assertStringStartsWith( + '

    ' . "\n" . 'Custom' . "\n" . '

    ', + $html, + 'Order: accessible heading, summary strip, then content.', + ); + self::assertStringContainsString( + '
    ' . "\n" . '

    ' . "\n" . 'Section' . "\n" . '

    ', + $html, + 'Only the section heading may use the section wrapper.', + ); + self::assertStringContainsString( + '

    ' . "\n" . 'Plain' . "\n" . '

    ', + $html, + 'An ordinary heading must stay a bare level-two heading.', + ); + self::assertStringContainsString( + '
', + PanelRenderer::render('Custom', PanelView::create()->overview(['Key' => 'value'], true)), + 'The compact overview must request the overview table treatment.', + ); + self::assertStringNotContainsString( + 'yii-debug-table-overview', + PanelRenderer::render('Custom', PanelView::create()->overview(['Key' => 'value'])), + 'An ordinary overview must not request the compact treatment.', + ); + self::assertStringContainsString( + '
' . "\n" . 'queued', + $html, + 'Plain text in a pill column must be wrapped for the reader.', + ); + self::assertStringContainsString( + '' . "\n" . 'paragraph( + PanelView::link('', '/debug?q=">'), + ), + ); + + self::assertStringNotContainsString( + ''; @@ -141,6 +366,25 @@ public function testRenderUsesExistingFrontendAndEscapesEveryTextPosition(): voi ); } + public function testSqlStatementsAreHighlightedAndEscaped(): void + { + $html = PanelRenderer::render( + 'Custom', + PanelView::create()->paragraph(PanelView::sql("SELECT * FROM t WHERE a = ''")), + ); + + self::assertStringContainsString( + 'SELECT', + $html, + 'Statements must reuse the shared SQL token spans.', + ); + self::assertStringNotContainsString( + "''", + $html, + 'A statement literal must never inject HTML.', + ); + } + public function testThrowJsonExceptionForUnencodableValue(): void { $this->expectException(JsonException::class); diff --git a/tests/Panel/Queue/QueueGridRendererTest.php b/tests/Panel/Queue/QueueGridRendererTest.php deleted file mode 100644 index a056654..0000000 --- a/tests/Panel/Queue/QueueGridRendererTest.php +++ /dev/null @@ -1,219 +0,0 @@ -Redis - HTML, - QueueGridRenderer::renderDriverCell(JobRecordFixture::create(driverName: 'Redis', isAsync: true)), - "Async drivers must carry the 'is-async' modifier.", - ); - - - } - - public function testRenderDriverCellAddsSyncModifierWhenInProcess(): void - { - self::assertSame( - <<Sync - HTML, - QueueGridRenderer::renderDriverCell(JobRecordFixture::create(driverName: 'Sync', isAsync: false)), - "Sync drivers must carry the 'is-sync' modifier.", - ); - } - - public function testRenderDriverCellReturnsEmptyWhenDriverNameIsMissing(): void - { - self::assertSame( - '', - QueueGridRenderer::renderDriverCell(JobRecordFixture::create(driverName: '')), - 'Empty driver name must yield an empty cell.', - ); - } - - public function testRenderDriverCellUsesFallbackTooltipWhenDriverClassIsMissing(): void - { - self::assertSame( - <<Custom - HTML, - QueueGridRenderer::renderDriverCell(JobRecordFixture::create(driverName: 'Custom', driverClass: '')), - 'Missing driver class must use the explicit fallback tooltip.', - ); - } - - public function testRenderDurationCellFormatsMilliseconds(): void - { - self::assertSame( - '12.3 ms', - QueueGridRenderer::renderDurationCell(JobRecordFixture::create(duration: 0.0123)), - "Seconds must be formatted as 'XX.X ms'.", - ); - self::assertSame( - '1,000.0 ms', - QueueGridRenderer::renderDurationCell(JobRecordFixture::create(duration: 1.0)), - 'One second must convert using exactly one thousand milliseconds.', - ); - } - - public function testRenderDurationCellReturnsDashWhenDurationIsNull(): void - { - self::assertSame( - '—', - QueueGridRenderer::renderDurationCell(JobRecordFixture::create(duration: null)), - "Missing duration must yield '—'.", - ); - } - - public function testRenderIdCellReturnsDashWhenJobIdIsEmpty(): void - { - self::assertSame( - '—', - QueueGridRenderer::renderIdCell(JobRecordFixture::create(jobId: '')), - "Empty job id must yield '—' to keep the column readable.", - ); - } - - public function testRenderIdCellWrapsJobIdInTagLinkSpan(): void - { - self::assertSame( - <<69ffbbf2a6830 - HTML, - QueueGridRenderer::renderIdCell(JobRecordFixture::create(jobId: '69ffbbf2a6830')), - 'Id must reuse the History tag-link styling.', - ); - - } - - public function testRenderJobCellSplitsFqcnAndWiresHref(): void - { - self::assertSame( - << - HelloJobapp\jobs\ - - HTML, - QueueGridRenderer::renderJobCell( - JobRecordFixture::create(jobClass: 'app\\jobs\\HelloJob'), - '/debug/queue?seq=2', - ), - 'Short class name must render in bold inside the link.', - ); - } - - public function testRenderStatusCellRendersFailedVariantForErrorEvents(): void - { - self::assertSame( - <<Failed - HTML, - QueueGridRenderer::renderStatusCell(JobRecordFixture::create(eventType: 'error')), - "Error events must produce the 'failed' modifier.", - ); - } - - public function testRenderStatusCellRendersQueuedVariantForPushEvents(): void - { - self::assertSame( - <<<'HTML' - Queued - HTML, - QueueGridRenderer::renderStatusCell(JobRecordFixture::create(eventType: 'push')), - "Push events must produce the 'queued' modifier.", - ); - - } - - public function testRenderTimeCellFormatsMicrotimeAsHmsWithMilliseconds(): void - { - self::assertSame( - date('H:i:s', 1_704_112_496) . '.789', - QueueGridRenderer::renderTimeCell(JobRecordFixture::create(time: 1_704_112_496.789)), - "Time cell must preserve the exact 'HH:MM:SS.mmm' value.", - ); - } - - public function testRenderTimeCellTruncatesSubMillisecondPrecision(): void - { - self::assertSame( - date('H:i:s', 1_704_112_496) . '.123', - QueueGridRenderer::renderTimeCell(JobRecordFixture::create(time: 1_704_112_496.1239)), - 'Sub-millisecond precision must truncate after three digits.', - ); - } - - public function testRenderTtrCellAppendsSecondsSuffix(): void - { - self::assertSame( - '300s', - QueueGridRenderer::renderTtrCell(JobRecordFixture::create(ttr: 300)), - "Non-zero TTR must render as 'Ns'.", - ); - } - - public function testRenderTtrCellReturnsDashWhenTtrIsNullOrZero(): void - { - self::assertSame( - '—', - QueueGridRenderer::renderTtrCell(JobRecordFixture::create(ttr: null)), - "Null TTR must yield '—'.", - ); - self::assertSame( - '—', - QueueGridRenderer::renderTtrCell(JobRecordFixture::create(ttr: 0)), - "Zero TTR must yield '—'.", - ); - } -} diff --git a/tests/Panel/Queue/QueuePanelTest.php b/tests/Panel/Queue/QueuePanelTest.php new file mode 100644 index 0000000..23a3433 --- /dev/null +++ b/tests/Panel/Queue/QueuePanelTest.php @@ -0,0 +1,472 @@ +isActive(), + 'An empty lifecycle log must not activate navigation.', + ); + self::assertSame( + [' events', ' queued', ' done'], + self::metricLabels($view->summaryMetrics()), + 'A capture without failures must not add the failed counter.', + ); + + $state = self::emptyState(self::blockAt($view, 0)); + + self::assertCount( + 1, + $view->blocks(), + 'An empty lifecycle log must replace the table and the groups.', + ); + self::assertSame( + 'No queue activity in this request', + $state['title'], + 'The empty state must keep its heading.', + ); + self::assertSame( + ['This request pushed no job and ran none, so the lifecycle log is empty.'], + self::inlineValues($state['paragraphs'][0] ?? self::fail('The empty state must explain itself.')), + 'The first paragraph must describe the empty log.', + ); + self::assertSame( + ['Events appear here when a queue component emits ', 'afterPush', ', ', 'afterExec', ', or ', 'afterError', '.'], + self::inlineValues($state['paragraphs'][1] ?? self::fail('The empty state must name the hooks.')), + 'The capture hooks must stay complete and ordered.', + ); + } + + public function testEventWithoutPayloadStatesItInstead(): void + { + $view = self::present([JobRecordFixture::create(time: self::TIME)]); + $content = self::group(self::blockAt($view, 3)); + + self::assertCount( + 2, + $content['content']->blocks(), + 'An event without payload must keep only its overview and the explanation.', + ); + self::assertSame( + ['The event carried no job payload.'], + self::inlineValues(self::paragraph(self::childBlockAt($content, 1))), + 'An event without payload must state it instead of rendering an empty overview.', + ); + } + + public function testFailedEventIsCountedBadgedAndCarriesItsError(): void + { + $view = self::present( + [ + JobRecordFixture::create( + eventType: JobRecord::TYPE_ERROR, + attempt: 2, + duration: 0.125, + error: 'Division by zero', + time: self::TIME, + ), + ], + ); + + self::assertSame( + [' event', ' queued', ' done', ' failed'], + self::metricLabels($view->summaryMetrics()), + 'A failure must add the failed counter after the others.', + ); + self::assertSame( + ['1', '0', '0', '1'], + self::metricValues($view->summaryMetrics()), + 'Each lifecycle phase must be counted independently.', + ); + + $table = self::table(self::blockAt($view, 1)); + + self::assertSame( + 'Failed', + self::badge(self::row($table, 0)[1] ?? self::fail('Every event must report its phase.'))['label'], + 'A failed event must be labeled as failed.', + ); + self::assertSame( + Tone::DANGER, + self::badge(self::row($table, 0)[1] ?? self::fail('Every event must report its phase.'))['tone'], + 'A failed event must use the danger tone.', + ); + self::assertSame( + ['1', 'app\\jobs\\HelloJob', 'queue', 'Sync', date('H:i:s', (int) self::TIME), '2', '125.0 ms'], + self::textValues( + [ + self::row($table, 0)[0] ?? self::fail('Every event must be listed.'), + self::row($table, 0)[2] ?? self::fail('Every event must be listed.'), + self::row($table, 0)[3] ?? self::fail('Every event must be listed.'), + self::row($table, 0)[4] ?? self::fail('Every event must be listed.'), + self::row($table, 0)[5] ?? self::fail('Every event must be listed.'), + self::row($table, 0)[6] ?? self::fail('Every event must be listed.'), + self::row($table, 0)[7] ?? self::fail('Every event must be listed.'), + ], + ), + 'Every lifecycle column must survive the migration.', + ); + + $content = self::group(self::blockAt($view, 3)); + + self::assertSame( + '2', + self::textValue( + self::fields(self::overview(self::childBlockAt($content, 0)))['Attempt'] + ?? self::fail('The envelope must keep the attempt row.'), + ), + 'A retried event must report its attempt number.', + ); + self::assertSame( + ['Division by zero'], + self::inlineValues(self::paragraph(self::childBlockAt($content, 1))), + 'The captured error must stay visible.', + ); + self::assertSame( + Tone::DANGER, + self::paragraph(self::childBlockAt($content, 1))['tone'], + 'The captured error must read as a failure callout.', + ); + } + + public function testJobDetailUrlsComeFromTheAdapter(): void + { + $panel = new QueuePanel(); + + self::assertNotSame( + $panel, + $panel->jobUrls([]), + 'New instance must be returned (immutability).', + ); + + $view = $panel + ->jobUrls([1 => '/debug/queue-job?seq=1']) + ->present( + new QueueSnapshot( + [ + JobRecordFixture::create(time: self::TIME), + JobRecordFixture::create(time: self::TIME), + ], + )->jsonSerialize(), + ); + + self::assertCount( + 2, + self::table(self::blockAt($view, 1))['rows'], + 'The lifecycle table must list every captured event.', + ); + self::assertArrayNotHasKey( + 'Details', + self::fields(self::overview(self::childBlockAt(self::group(self::blockAt($view, 3)), 0))), + 'An event without detail route must not offer a link.', + ); + self::assertSame( + ['kind' => 'link', 'label' => 'Open job detail', 'href' => '/debug/queue-job?seq=1', 'external' => false], + self::fields(self::overview(self::childBlockAt(self::group(self::blockAt($view, 5)), 0)))['Details'] + ?? self::fail('The detail route must reach the envelope.'), + 'The adapter-resolved detail route must stay in the same browsing context.', + ); + } + + public function testMetadataMatchesTheBuiltInQueuePanel(): void + { + $panel = new QueuePanel(); + + self::assertSame( + 'queue', + $panel->id(), + 'The persisted panel identifier must stay stable.', + ); + self::assertSame( + 'Queue', + $panel->name(), + 'The navigation title must stay stable.', + ); + self::assertSame( + 'queue', + $panel->icon(), + 'The panel must reuse the existing icon.', + ); + } + + public function testPushedEventDescribesItsEnvelopeAndPayload(): void + { + $view = self::present( + [ + JobRecordFixture::create( + eventType: JobRecord::TYPE_PUSH, + componentId: 'queueEmail', + driverName: 'Database', + driverClass: 'yii\\queue\\db\\Queue', + isAsync: true, + payloadFields: ['to' => 'admin@example.com'], + time: self::TIME, + jobId: '42', + ttr: 300, + delay: 5, + priority: 1024, + ), + ], + ); + + self::assertTrue( + $view->isActive(), + 'A captured event must activate navigation.', + ); + self::assertSame( + [['label' => 'Jobs', 'value' => ['kind' => 'text', 'value' => '1', 'style' => 'plain']]], + $view->toolbarMetrics(), + 'The toolbar must report the event count.', + ); + + $heading = self::heading(self::blockAt($view, 1)); + + self::assertSame( + 'Lifecycle events', + $heading['title'], + 'The lifecycle table must keep its heading.', + ); + self::assertTrue( + $heading['section'], + 'The lifecycle table must open a section-level heading.', + ); + + $table = self::table(self::blockAt($view, 2)); + + self::assertSame( + ['#', 'Status', 'Job', 'Component', 'Driver', 'Time', 'Attempt', 'Duration'], + $table['headers'], + 'The lifecycle column order must stay stable.', + ); + self::assertSame( + [ + 0 => ColumnStyle::NUMBER, + 1 => ColumnStyle::PILL, + 2 => ColumnStyle::IDENTIFIER, + 3 => ColumnStyle::IDENTIFIER, + 4 => ColumnStyle::IDENTIFIER, + 5 => ColumnStyle::IDENTIFIER, + 6 => ColumnStyle::NUMBER, + 7 => ColumnStyle::NUMBER, + ], + $table['styles'], + 'Each style must stay attached to the column it formats.', + ); + self::assertTrue( + $table['collapsible'], + 'A long lifecycle log must stay collapsible.', + ); + self::assertSame( + 'Queued', + self::badge(self::row($table, 0)[1] ?? self::fail('Every event must report its phase.'))['label'], + 'A pushed event must be labeled as queued.', + ); + self::assertSame( + Tone::INFO, + self::badge(self::row($table, 0)[1] ?? self::fail('Every event must report its phase.'))['tone'], + 'A pushed event must stay neutral.', + ); + + $eventHeading = self::heading(self::blockAt($view, 3)); + + self::assertSame( + '1. app\\jobs\\HelloJob', + $eventHeading['title'], + 'Headings must number the events and name the job.', + ); + self::assertTrue( + $eventHeading['section'], + 'Each event must open a section-level heading.', + ); + + $content = self::group(self::blockAt($view, 4)); + + self::assertSame( + 'Event 1', + $content['label'], + 'The group must identify the event it describes.', + ); + + $overview = self::overview(self::childBlockAt($content, 0)); + + self::assertTrue( + $overview['compact'], + 'The envelope must use the compact presentation.', + ); + self::assertSame( + [ + 'Job', + 'Status', + 'Component', + 'Driver', + 'Driver class', + 'Execution', + 'Job id', + 'Pushed at', + 'TTR', + 'Delay', + 'Priority', + 'Attempt', + 'Duration', + ], + array_keys(self::fields($overview)), + 'The envelope row order must stay stable.', + ); + + $fields = self::fields($overview); + + self::assertSame( + ['kind' => 'text', 'value' => 'yii\\queue\\db\\Queue', 'style' => 'code'], + $fields['Driver class'] ?? self::fail('The envelope must keep the driver class row.'), + 'A captured driver class must read as source code.', + ); + self::assertSame( + 'Worker process', + self::textValue($fields['Execution'] ?? self::fail('The envelope must keep the execution row.')), + 'An async driver must say the job runs in a worker.', + ); + self::assertSame( + '300s', + self::textValue($fields['TTR'] ?? self::fail('The envelope must keep the time-to-run row.')), + 'A declared time-to-run must survive the migration.', + ); + self::assertSame( + '5s', + self::textValue($fields['Delay'] ?? self::fail('The envelope must keep the delay row.')), + 'A declared delay must survive the migration.', + ); + self::assertSame( + '1024', + self::textValue($fields['Priority'] ?? self::fail('The envelope must keep the priority row.')), + 'A declared priority must survive the migration.', + ); + self::assertSame( + '—', + self::textValue($fields['Attempt'] ?? self::fail('The envelope must keep the attempt row.')), + 'A pushed event must report no attempt.', + ); + self::assertSame( + date('M j, Y · H:i:s', (int) self::TIME), + self::textValue($fields['Pushed at'] ?? self::fail('The envelope must keep the time row.')), + 'The envelope must show the absolute timestamp.', + ); + self::assertSame( + ['kind' => 'value', 'value' => ['to' => 'admin@example.com'], 'typeOnly' => false], + self::fields(self::overview(self::childBlockAt($content, 1)))['Payload'] + ?? self::fail('The captured payload must stay inspectable.'), + 'The captured payload must survive the migration unflattened.', + ); + } + + public function testSynchronousDriverWithoutOverridesFallsBackToPlaceholders(): void + { + $view = self::present([JobRecordFixture::create(componentId: '', driverName: '', driverClass: '', jobClass: '', time: self::TIME)]); + + self::assertSame( + 'Lifecycle events', + self::heading(self::blockAt($view, 0))['title'], + 'A synchronous driver must not add the async hint.', + ); + + $fields = self::fields(self::overview(self::childBlockAt(self::group(self::blockAt($view, 3)), 0))); + + self::assertSame( + ['—', 'In process', '—', '—', '—', '—'], + [ + self::textValue($fields['Component'] ?? self::fail('The envelope must keep the component row.')), + self::textValue($fields['Execution'] ?? self::fail('The envelope must keep the execution row.')), + self::textValue($fields['Job id'] ?? self::fail('The envelope must keep the job id row.')), + self::textValue($fields['TTR'] ?? self::fail('The envelope must keep the time-to-run row.')), + self::textValue($fields['Delay'] ?? self::fail('The envelope must keep the delay row.')), + self::textValue($fields['Priority'] ?? self::fail('The envelope must keep the priority row.')), + ], + 'Missing envelope fields must show the placeholder.', + ); + } + + /** + * @param list $metrics Metrics in display order. + * + * @return list Metric labels in display order. + */ + private static function metricLabels(array $metrics): array + { + $labels = []; + + foreach ($metrics as $metric) { + $labels[] = $metric['label']; + } + + return $labels; + } + + /** + * @param list $records Captured events in chronological order. + * + * @return PanelView Description built by the panel. + */ + private static function present(array $records): PanelView + { + return (new QueuePanel())->present((new QueueSnapshot($records))->jsonSerialize()); + } +} diff --git a/tests/Panel/Request/RequestDiagnosticValueRendererTest.php b/tests/Panel/Request/RequestDiagnosticValueRendererTest.php new file mode 100644 index 0000000..e9485bc --- /dev/null +++ b/tests/Panel/Request/RequestDiagnosticValueRendererTest.php @@ -0,0 +1,195 @@ +&'), + 'Entities must be encoded again, never left raw.', + ); + self::assertSame( + ''quoted'', + RequestDiagnosticValueRenderer::escape("'quoted'"), + 'Single quotes must be encoded.', + ); + } + + public function testEscapeSubstitutesMalformedUtf8Bytes(): void + { + self::assertSame( + "caf\u{FFFD} bar", + RequestDiagnosticValueRenderer::escape("caf\xE9 bar"), + 'Invalid bytes must become the replacement character.', + ); + } + + public function testHeaderClampsRepeatedLinesWhenJoinedSourceExceedsThreshold(): void + { + $line = str_repeat('a', CellMore::THRESHOLD + 1); + + self::assertSame( + << +
+
+ 1 value
    +
  • + {$line} +
  • +
+
+
+ + HTML, + RequestDiagnosticValueRenderer::header([$line]), + 'Clamp must measure the joined lines, not the markup.', + ); + } + + public function testHeaderFallsBackToValueForNonStringLists(): void + { + self::assertSame( + 'text/html', + RequestDiagnosticValueRenderer::header('text/html'), + 'A scalar header must render as a plain value.', + ); + self::assertSame( + '[]', + RequestDiagnosticValueRenderer::header([]), + 'An empty array must dump, not list.', + ); + self::assertSame( + << 'b']), + 'A map must dump, not list.', + ); + self::assertSame( + << + 2 values
    +
  • + gzip +
  • + deflate +
  • +
+ + HTML, + RequestDiagnosticValueRenderer::header(['gzip', 'deflate']), + 'Every repeated line must survive as its own item.', + ); + self::assertSame( + << + 1 value
    +
  • + gzip +
  • +
+ + HTML, + RequestDiagnosticValueRenderer::header(['gzip']), + 'A single line must read `1 value`.', + ); + } + + public function testHeaderRendersRepeatedLinesWithEmptyPlaceholders(): void + { + self::assertSame( + << + 2 values
    +
  • + Empty value +
  • + <b> +
  • +
+ + HTML, + RequestDiagnosticValueRenderer::header(['', '']), + 'Blank lines must keep their slot as a placeholder.', + ); + } + + public function testValueClampsOnlySourcesBeyondTheThreshold(): void + { + $source = str_repeat('a', CellMore::THRESHOLD); + + self::assertSame( + $source, + RequestDiagnosticValueRenderer::value($source), + 'Threshold-length source must stay unclamped.', + ); + self::assertSame( + << +
+ {$source}a +
+ + HTML, + RequestDiagnosticValueRenderer::value($source . 'a'), + 'Source beyond the threshold must be clamped.', + ); + } + + public function testValueDumpsStructuredValuesAndFlagsEmptyStrings(): void + { + self::assertSame( + 'Empty value', + RequestDiagnosticValueRenderer::value(''), + 'An empty string must render as a placeholder.', + ); + self::assertSame( + 'plain <b>', + RequestDiagnosticValueRenderer::value('plain '), + 'A non-empty string must be escaped verbatim.', + ); + self::assertSame( + << 1]), + 'A structured value must be dumped, then escaped.', + ); + } +} diff --git a/tests/Panel/Request/RequestRendererTest.php b/tests/Panel/Request/RequestRendererTest.php index 004f50d..b3ac425 100644 --- a/tests/Panel/Request/RequestRendererTest.php +++ b/tests/Panel/Request/RequestRendererTest.php @@ -21,10 +21,12 @@ RouteInventoryView, RouteTraceRow, }; -use PHPUnit\Framework\Attributes\Group; +use PHPForge\Debug\Tests\Provider\RequestRendererProvider; +use PHPUnit\Framework\Attributes\{DataProviderExternal, Group}; use PHPUnit\Framework\TestCase; use function strpos; +use function substr_count; /** * Unit tests for the composed Request and routing presentation. @@ -59,7 +61,7 @@ public function testRenderBuildsOneOverviewAndCanonicalTabOrder(): void 'The three primary metrics must follow route, action, duration order.', ); - $labels = ['Input', 'Headers', 'Session', 'Routes (2)', 'Server']; + $labels = ['Input', 'Headers', 'Session', 'Server']; $offset = -1; foreach ($labels as $label) { @@ -78,6 +80,72 @@ public function testRenderBuildsOneOverviewAndCanonicalTabOrder(): void $offset = $position; } } + public function testRenderCarriesTheRoutingConstraintsOnTheContextStrip(): void + { + $html = RequestRenderer::render( + self::requestView(), + new RequestRoutingView( + CurrentRouteView::create(route: 'orders/view')->withDefinition( + RouteDefinition::create(name: 'orders/view', pattern: '/orders/') + ->withMethods(['GET', 'HEAD']) + ->withHosts(['api.example.test']) + ->withMiddlewares(['Auth']), + ), + ), + ); + + $chips = [ + 'Pattern' => '/orders/<id>', + 'Methods' => 'GET, HEAD', + 'Hosts' => 'api.example.test', + 'Middleware' => 'Auth', + ]; + + foreach ($chips as $label => $value) { + self::assertStringContainsString( + "{$label}", + $html, + "The context strip must label the {$label} chip.", + ); + self::assertStringContainsString( + ">{$value}", + $html, + "The {$label} chip must carry its captured value.", + ); + } + + $wildcards = RequestRenderer::render( + self::requestView(), + new RequestRoutingView( + CurrentRouteView::create(route: 'orders/view')->withDefinition( + RouteDefinition::create(name: 'orders/view', pattern: '/orders')->withMiddlewares([]), + ), + ), + ); + + self::assertStringContainsString( + '>Any', + $wildcards, + 'A definition without methods or hosts must read as accepting any.', + ); + self::assertStringContainsString( + '>None', + $wildcards, + 'An empty middleware stack must read as none.', + ); + self::assertStringNotContainsString( + 'Middleware', + RequestRenderer::render( + self::requestView(), + new RequestRoutingView( + CurrentRouteView::create(route: 'orders/view')->withDefinition( + RouteDefinition::create(name: 'orders/view', pattern: '/orders'), + ), + ), + ), + 'A framework that reports no middleware must not show an empty chip.', + ); + } public function testRenderDelegatesToLegacyRendererWithoutRoutingView(): void { @@ -99,7 +167,6 @@ public function testRenderEscapesCurrentAndInventoryRouteValues(): void ->withMethods(['G