Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .stylelintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["stylelint-config-standard-scss"]
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- feat(events): add the shared execution inspector, stable chronology, grouped filters, bounded optional context and traces, and explicit lifecycle correlation.
- refactor: centralize exception messages and panel titles in typed enums while preserving diagnostics, labels, and configurable names.
- refactor: use shared `PanelIcon` enum values for built-in panel SVG keys.
- fix: improve UI contrast, focus, deep links, history alignment, shared asset sizing, and local rebuild documentation.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"require-dev": {
"infection/infection": "^0.35",
"maglnet/composer-require-checker": "^4.1",
"php-forge/baseline": "^0.2",
"php-forge/baseline-frontend": "^0.1",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"php-forge/coding-standard": "^0.3",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan-strict-rules": "^2.0.3",
Expand All @@ -57,7 +57,7 @@
},
"scaffold": {
"allowed-packages": [
"php-forge/baseline",
"php-forge/baseline-frontend",
"php-forge/coding-standard"
]
}
Expand Down
2 changes: 1 addition & 1 deletion resources/assets/dist/css/debug.min.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion resources/assets/dist/js/debug.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion resources/assets/dist/js/focus.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion resources/assets/dist/js/toolbar.min.js

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions resources/src/core/deep-links.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,13 @@ export function revealDeepLink(root, locationValue, scroll) {
: null;
}

// Tabs already indicate selection; highlighting the whole panel looks like stuck focus.
if (!target.getAttribute || target.getAttribute("role") !== "tabpanel") {
// Selected tabs and expanded events already expose their state without a persistent focus-like outline.
var isTabPanel =
target.getAttribute && target.getAttribute("role") === "tabpanel";
var isEvent =
target.matches && target.matches("details.yii-debug-event-item");

if (!isTabPanel && !isEvent) {
target.classList.add("yii-debug-deep-link-target");
}

Expand Down
54 changes: 54 additions & 0 deletions resources/src/core/history-capture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/** Keeps capture-specific shell metadata and links aligned with the History cursor. */
export function updateHistoryCapture(root, snapshot, locationValue) {
if (!snapshot.tag) {
return;
}

var links = root.querySelectorAll(
".yii-debug-nav-link[href], a.yii-debug-brand-chip-config[href]",
);

for (var i = 0; i < links.length; i++) {
var target = new URL(links[i].getAttribute("href"), locationValue);
if (
target.origin !== new URL(locationValue).origin ||
!target.searchParams.has("tag")
) {
continue;
}
target.searchParams.set("tag", snapshot.tag);
links[i].setAttribute(
"href",
target.pathname + target.search + target.hash,
);
}

var header = root.querySelector(".yii-debug-brand-bar");
if (!header) {
return;
}

var chip = header.querySelector(".yii-debug-brand-chip-mem");
if (!snapshot.memory) {
if (chip) {
chip.remove();
}
return;
}

if (!chip) {
chip = root.createElement("span");
chip.className = "yii-debug-brand-chip yii-debug-brand-chip-mem";
var label = root.createElement("span");
label.className = "yii-debug-brand-label";
label.textContent = "Memory";
var value = root.createElement("span");
value.className = "yii-debug-brand-value";
chip.append(label, value);
header.insertBefore(
chip,
header.querySelector(".yii-debug-brand-chip-config"),
);
}
chip.querySelector(".yii-debug-brand-value").textContent = snapshot.memory;
}
36 changes: 8 additions & 28 deletions resources/src/core/history-cursor.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { updateHistoryCapture } from "./history-capture.js";

/**
* Index-page client behavior:
* History cursor — peek at requests one by one without leaving the page.
Expand Down Expand Up @@ -72,6 +74,7 @@
status: status,
time: row.getAttribute("data-yii-debug-time") || "",
ajax: row.getAttribute("data-yii-debug-ajax") === "1",
memory: row.getAttribute("data-yii-debug-memory") || "",
};
}

Expand All @@ -92,33 +95,6 @@
return "other";
}

/**
* Keep primary Request links aligned with the row represented by the
* client-side history cursor. The `auto` variant preserves compatibility
* with captures created before the Request panel was available.
*/
function updateRequestLinks(tag) {
if (!tag) {
return;
}

var links = document.querySelectorAll(
'.yii-debug-nav-link[href*="panel=request"], .yii-debug-nav-link[href*="panel=auto"]',
);

for (var li = 0; li < links.length; li++) {
var target = new URL(
links[li].getAttribute("href"),
window.location.href,
);
target.searchParams.set("tag", tag);
links[li].setAttribute(
"href",
target.pathname + target.search + target.hash,
);
}
}

function update() {
rows.forEach(function (r, i) {
r.classList.toggle("is-cursor", i === cursor);
Expand Down Expand Up @@ -161,7 +137,11 @@
);
}

updateRequestLinks(snap.tag);
updateHistoryCapture(
document,
snap,
window.location && window.location.href,
);

var newestBtn = section.querySelector('[data-yii-debug-cursor="newest"]');
var newerBtn = section.querySelector('[data-yii-debug-cursor="newer"]');
Expand Down
37 changes: 10 additions & 27 deletions resources/src/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -1614,15 +1614,11 @@

&.is-active,
&[aria-current="page"] {
background: linear-gradient(
135deg,
var(--yii-debug-panel-primary-strong),
var(--yii-debug-panel-primary)
);
color: #fff;
background: var(--yii-debug-primary-bg);
color: var(--yii-debug-on-primary);

&::after {
color: rgb(255 255 255 / 85%);
color: inherit;
}
}
}
Expand Down Expand Up @@ -1686,21 +1682,12 @@

.yii-debug-btn-primary {
border-color: transparent;
background: linear-gradient(
135deg,
var(--yii-debug-panel-primary-strong),
var(--yii-debug-panel-primary)
);
color: #fff;
background: var(--yii-debug-primary-bg);
color: var(--yii-debug-on-primary);

&:hover,
&:focus {
background: linear-gradient(
135deg,
var(--yii-debug-panel-primary-strong),
var(--yii-debug-panel-primary-strong)
);
color: #fff;
&:is(:hover, :focus) {
background: var(--yii-debug-panel-primary-strong);
color: var(--yii-debug-on-primary);
}
}

Expand Down Expand Up @@ -2431,12 +2418,8 @@

.yii-debug-pager-item.is-active .yii-debug-pager-link {
border-color: transparent;
background: linear-gradient(
135deg,
var(--yii-debug-panel-primary-strong),
var(--yii-debug-panel-primary)
);
color: #fff;
background: var(--yii-debug-primary-bg);
color: var(--yii-debug-on-primary);
cursor: default;
}

Expand Down
2 changes: 2 additions & 0 deletions resources/src/styles/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
--yii-debug-panel-link: light-dark(#0b6b4a, #4fd39a);
--yii-debug-panel-link-hover: light-dark(#0d855b, #7fe4b8);
--yii-debug-panel-primary: light-dark(#0e7a55, #2fd08c);
--yii-debug-primary-bg: linear-gradient(135deg, var(--yii-debug-panel-primary-strong), var(--yii-debug-panel-primary));
--yii-debug-on-primary: light-dark(#fff, #06281b);
--yii-debug-panel-primary-strong: light-dark(#0a5c40, #17a673);
--yii-debug-focus-ring: light-dark(#075c40, #7fe4b8);
--yii-debug-success: light-dark(#0d7d47, #34c97e);
Expand Down
10 changes: 6 additions & 4 deletions resources/src/toolbar/focus.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ export function focusToolbarElement(root, selector) {

var element = root.querySelector(selector);

if (!element || typeof element.focus !== "function") {
if (typeof element?.focus !== "function") {
return false;
}

element.focus();

return true;
return root.activeElement === element;
}

export function focusToolbarTrigger(root, url) {
Expand All @@ -23,9 +23,11 @@ export function focusToolbarTrigger(root, url) {

for (var i = 0; i < triggers.length; i++) {
if (triggers[i].getAttribute("data-debug-url") === url) {
triggers[i].focus();
triggers[i].focus?.();

return true;
if (root.activeElement === triggers[i]) {
return true;
}
}
}

Expand Down
28 changes: 28 additions & 0 deletions resources/tests/deep-links.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,31 @@ test("tab fragments restore without a persistent panel highlight", () => {
assert.deepEqual(target.scrollOptions, { block: "start" });
}
});

test("event fragments reopen diagnostics on load without a persistent focus-like outline", () => {
var classes = new Set();
var target = {
open: false,
parentElement: null,
classList: { add: (name) => classes.add(name) },
getAttribute: () => null,
matches: (selector) => selector === "details.yii-debug-event-item",
closest: () => target,
scrollIntoView(options) {
this.scrollOptions = options;
},
focus() {
assert.fail("Restoring an event fragment must not move keyboard focus.");
},
};
var root = {
getElementById: () => target,
querySelectorAll: () => [],
};

revealDeepLink(root, { hash: "#event-1" }, true);

assert.equal(target.open, true);
assert.equal(classes.has("yii-debug-deep-link-target"), false);
assert.deepEqual(target.scrollOptions, { block: "start" });
});
101 changes: 101 additions & 0 deletions resources/tests/history-capture.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import assert from "node:assert/strict";
import { test } from "vitest";
import { updateHistoryCapture } from "../src/core/history-capture.js";

function link(href) {
return {
href,
getAttribute() {
return this.href;
},
setAttribute(name, value) {
assert.equal(name, "href");
this.href = value;
},
};
}

function shell() {
var header = {
chip: null,
querySelector(selector) {
return selector === ".yii-debug-brand-chip-mem" ? this.chip : null;
},
insertBefore(chip) {
this.chip = chip;
},
};
return {
header,
links: [
link("/debug/view?tag=latest&panel=request"),
link("/debug/view?tag=latest&panel=event&yii_debug_theme=dark"),
link("/debug/view?tag=latest&panel=log"),
link("/debug/view?tag=latest&panel=config"),
link("/debug"),
link("https://external.test/?tag=latest"),
],
querySelector: () => header,
querySelectorAll() {
return this.links;
},
createElement() {
return {
children: [],
append(...nodes) {
this.children.push(...nodes);
},
querySelector() {
return this.children[1];
},
remove() {
header.chip = null;
},
};
},
};
}

test("cursor updates every capture link and memory without changing History or external links", () => {
var root = shell();
updateHistoryCapture(
root,
{ tag: "older", memory: "6.00 MB" },
"https://example.test/debug",
);

for (var item of root.links.slice(0, 4)) {
assert.equal(
new URL(item.href, "https://example.test").searchParams.get("tag"),
"older",
);
}
assert.ok(root.links[1].href.includes("panel=event&yii_debug_theme=dark"));
assert.equal(root.links[4].href, "/debug");
assert.equal(root.links[5].href, "https://external.test/?tag=latest");
assert.equal(root.header.chip.children[1].textContent, "6.00 MB");
assert.equal(root.header.chip.children[0].textContent, "Memory");

var existingChip = root.header.chip;
updateHistoryCapture(
root,
{ tag: "other", memory: "2.00 MB" },
"https://example.test/debug",
);
assert.equal(root.header.chip, existingChip);
assert.equal(root.header.chip.children[1].textContent, "2.00 MB");
});

test("missing memory removes stale metrics and a later captured value recreates the chip", () => {
var root = shell();
var base = "https://example.test/debug";
updateHistoryCapture(root, { tag: "old", memory: "" }, base);
assert.equal(root.header.chip, null);
updateHistoryCapture(root, { tag: "old", memory: "7.00 MB" }, base);
updateHistoryCapture(root, { tag: "missing", memory: "" }, base);
assert.equal(root.header.chip, null);
updateHistoryCapture(root, { tag: "later", memory: "3.00 MB" }, base);
assert.equal(root.header.chip.children[1].textContent, "3.00 MB");
updateHistoryCapture(root, { tag: "", memory: "" }, base);
assert.equal(root.header.chip.children[1].textContent, "3.00 MB");
});
Loading
Loading