From ec584b5367d9cf62fa04b5537360e274d1e10e08 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 5 Nov 2026 09:59:30 -0600 Subject: [PATCH 1/2] [#64] - aligning icons and drawings Assisted-by: Claude Code --- resources/assets/debugbar.js | 72 +++++++++++++++----------- src/DebugBar/DebugBarTypes.php | 3 +- tests/JavaScript/debugbar.test.js | 86 +++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 32 deletions(-) diff --git a/resources/assets/debugbar.js b/resources/assets/debugbar.js index 85a0edf..1e6aa31 100644 --- a/resources/assets/debugbar.js +++ b/resources/assets/debugbar.js @@ -46,51 +46,60 @@ return node; } - function indicatorIcon(name) { - var svg = svgElement('svg', { - 'aria-hidden': 'true', - 'class': 'phalcon-debugbar-indicator-icon', - 'viewBox': '0 0 18 18' - }); + function gear(cx, cy, radius) { + var group = svgElement('g', {}); + group.appendChild(svgElement('circle', {cx: cx, cy: cy, r: radius})); + group.appendChild(svgElement('circle', {cx: cx, cy: cy, r: radius / 3})); + for (var index = 0; index < 8; index++) { + var angle = index * Math.PI / 4; + group.appendChild(svgElement('line', { + x1: cx + Math.cos(angle) * radius, + y1: cy + Math.sin(angle) * radius, + x2: cx + Math.cos(angle) * (radius + 1.5), + y2: cy + Math.sin(angle) * (radius + 1.5) + })); + } + return group; + } - if (name === 'clock') { + // `icon_type` in DebugBarTypes.php lists the same names, and a test compares the two. + var ICONS = { + clock: function (svg) { svg.appendChild(svgElement('circle', {cx: '9', cy: '9', r: '6.5'})); svg.appendChild(svgElement('path', {d: 'M9 5.2V9l2.8 1.7'})); - return svg; - } - - if (name === 'search') { + }, + cogs: function (svg) { + svg.appendChild(gear(6.2, 10.5, 3.1)); + svg.appendChild(gear(12.2, 6.2, 2.4)); + }, + search: function (svg) { svg.appendChild(svgElement('circle', {cx: '7.5', cy: '7.5', r: '4.5'})); svg.appendChild(svgElement('path', {d: 'M10.8 10.8l4 4'})); - return svg; - } - - function gear(cx, cy, radius) { - var group = svgElement('g', {}); - group.appendChild(svgElement('circle', {cx: cx, cy: cy, r: radius})); - group.appendChild(svgElement('circle', {cx: cx, cy: cy, r: radius / 3})); - for (var index = 0; index < 8; index++) { - var angle = index * Math.PI / 4; - group.appendChild(svgElement('line', { - x1: cx + Math.cos(angle) * radius, - y1: cy + Math.sin(angle) * radius, - x2: cx + Math.cos(angle) * (radius + 1.5), - y2: cy + Math.sin(angle) * (radius + 1.5) - })); - } - svg.appendChild(group); } + }; - gear(6.2, 10.5, 3.1); - gear(12.2, 6.2, 2.4); + function indicatorIcon(name) { + if (!Object.prototype.hasOwnProperty.call(ICONS, name)) { + return null; + } + + var svg = svgElement('svg', { + 'aria-hidden': 'true', + 'class': 'phalcon-debugbar-indicator-icon', + 'viewBox': '0 0 18 18' + }); + ICONS[name](svg); return svg; } function metricIndicator(icon, label, value) { var indicator = el('button', 'phalcon-debugbar-indicator phalcon-debugbar-metric'); + var drawing = indicatorIcon(icon); indicator.type = 'button'; indicator.title = label; - indicator.appendChild(indicatorIcon(icon)); + if (drawing) { + indicator.appendChild(drawing); + } indicator.appendChild(el('span', 'phalcon-debugbar-indicator-value', scalar(value))); return indicator; } @@ -590,6 +599,7 @@ if (typeof module !== 'undefined' && module.exports) { module.exports = { createHistoryRequestGuard: createHistoryRequestGuard, + iconNames: Object.keys(ICONS), renderHistoryBrowser: renderHistoryBrowser }; } diff --git a/src/DebugBar/DebugBarTypes.php b/src/DebugBar/DebugBarTypes.php index afb0573..1754241 100644 --- a/src/DebugBar/DebugBarTypes.php +++ b/src/DebugBar/DebugBarTypes.php @@ -27,7 +27,8 @@ * @phpstan-type exception_panel list * @phpstan-type log_row array{label: string, message: string, context: string} * @phpstan-type log_panel list - * @phpstan-type indicator array{icon: string, label: string, path: list} + * @phpstan-type icon_type 'clock'|'cogs'|'search' + * @phpstan-type indicator array{icon: icon_type, label: string, path: list} * @phpstan-type request_widget array{method: list, uri: list} * @phpstan-type widget array{ * label: string, diff --git a/tests/JavaScript/debugbar.test.js b/tests/JavaScript/debugbar.test.js index bd4d35c..0633aa9 100644 --- a/tests/JavaScript/debugbar.test.js +++ b/tests/JavaScript/debugbar.test.js @@ -42,6 +42,50 @@ function panelContract() { return JSON.parse(fs.readFileSync(require.resolve('../support/assets/DebugBar/panel-contract.json'), 'utf8')); } +function renderPayload(payload) { + var dataNode = new dom.TestElement('script'); + var mount = new dom.TestElement('div'); + dataNode.textContent = JSON.stringify(payload); + global.document = dom.createDocument({ + 'phalcon-debugbar': mount, + 'phalcon-debugbar-data': dataNode + }); + global.window = { + localStorage: { + getItem: function () { + return null; + }, + setItem: function () {} + } + }; + + delete require.cache[debugbarPath]; + require(debugbarPath); + + return mount; +} + +function renderIndicator(icon) { + return renderPayload({ + data: {time: {panel: [], badge: '12.34ms'}}, + meta: { + widgets: { + time: { + label: 'Time', + panel: 'list', + indicator: {icon: icon, label: 'Request time', path: ['badge']} + } + } + } + }); +} + +function iconShapes(icon) { + return icon.children.map(function (shape) { + return shape.tagName; + }); +} + function deferred() { var reject; var resolve; @@ -520,6 +564,48 @@ test('indicator collectors render once on the right and open their panels', func assert.equal(dom.findByClass(mount, 'phalcon-debugbar-body').style.display, 'none'); }); +test('an indicator draws the icon that it names', function () { + var clock = dom.findByClass(renderIndicator('clock'), 'phalcon-debugbar-indicator-icon'); + var cogs = dom.findByClass(renderIndicator('cogs'), 'phalcon-debugbar-indicator-icon'); + + assert.deepEqual(iconShapes(clock), ['CIRCLE', 'PATH']); + assert.equal(clock.children[0].getAttribute('r'), '6.5'); + assert.deepEqual(iconShapes(cogs), ['G', 'G']); +}); + +test('the request control draws the search icon', function () { + var mount = renderPayload({ + data: {history: {panel: {url: '/_debugbar/open'}}}, + meta: {widgets: {history: {label: 'History', panel: 'history'}}} + }); + var control = dom.findByClass(mount, 'phalcon-debugbar-request-control'); + var search = dom.findByClass(control, 'phalcon-debugbar-indicator-icon'); + + assert.deepEqual(iconShapes(search), ['CIRCLE', 'PATH']); + assert.equal(search.children[0].getAttribute('r'), '4.5'); +}); + +test('an indicator with an unknown icon name shows its value and no icon', function () { + ['clcok', 'constructor', ''].forEach(function (name) { + var mount = renderIndicator(name); + var indicator = dom.findByClass(mount, 'phalcon-debugbar-metric'); + + assert.equal(dom.findByClass(indicator, 'phalcon-debugbar-indicator-icon'), null); + assert.equal(indicator.textContent, '12.34ms'); + }); +}); + +test('the icon names in PHP match the drawings in the client', function () { + var types = fs.readFileSync(require.resolve('../../src/DebugBar/DebugBarTypes.php'), 'utf8'); + var definition = /@phpstan-type icon_type (\S+)/.exec(types); + + assert.ok(definition, 'DebugBarTypes must define icon_type'); + assert.deepEqual( + definition[1].replace(/'/g, '').split('|').sort(), + debugbar.iconNames.slice().sort() + ); +}); + test('the client renders the output of the real collectors', function () { var dataNode = new dom.TestElement('script'); var mount = new dom.TestElement('div'); From 9205b41c3431c88a89b512f0e4658be83b0e87f5 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 5 Nov 2026 09:59:44 -0600 Subject: [PATCH 2/2] [#64] - updating docs and changelog Assisted-by: Claude Code --- CHANGELOG.md | 1 + docs/index.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a30ee7..3518a33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ All notable changes to `phalcon/debugbar` are documented here. The format is bas - A collector no longer sends `widget.icon` and `AbstractCollector` has no `$icon` property, because the browser client never read them. `indicator.icon` does not change. [#62](https://github.com/phalcon/debugbar/issues/62) - `DebugBarTypes` holds the wire contract and the config shapes, and it lists the panel names, so static analysis rejects an unknown panel name. [#62](https://github.com/phalcon/debugbar/issues/62) - The debug page in ADR applications uses `Injector::isHtml()` and no longer depends on the injection rules of the bar. [#62](https://github.com/phalcon/debugbar/issues/62) +- `DebugBarTypes` lists the icon names as `icon_type`, so static analysis rejects an unknown `indicator.icon`. A test keeps the list equal to the drawings in `debugbar.js`. An indicator with an unknown icon name now shows no icon, not the gears. [#64](https://github.com/phalcon/debugbar/issues/64) ### Fixed diff --git a/docs/index.md b/docs/index.md index ea3d501..2143e09 100644 --- a/docs/index.md +++ b/docs/index.md @@ -630,6 +630,8 @@ Time and Memory appear as compact indicators on the right instead of duplicate t Their indicators remain available whether History is enabled or disabled. Each participating collector declares its own indicator and semantic value path; the History collector does not depend on the Time or Memory payload shape. +The `icon` of an indicator must be `clock`, `cogs`, or `search`. With any other name, +the indicator shows its value and no icon. The rightmost control combines a search icon with the current HTTP method and URI; it opens or closes request history instead of using a dedicated History tab. The history browser and collector