From 22ffc0eca6db8300d1d65407d242eedc3cdf9e15 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Thu, 17 Sep 2026 16:46:31 -0300 Subject: [PATCH] refactor(panel)!: remove activity API, document provider defaults, and update README cache/configuration registration. --- .gitignore | 4 +++ CHANGELOG.md | 4 ++- README.md | 57 ++++++++++++++++++++++------------- src/Panel.php | 23 +++++++------- src/PanelView.php | 46 +++++++--------------------- tests/CacheExampleTest.php | 4 --- tests/FluentPanelViewTest.php | 16 +--------- tests/PanelViewTest.php | 4 --- 8 files changed, 67 insertions(+), 91 deletions(-) diff --git a/.gitignore b/.gitignore index 2400549..a2389e2 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ c3.php # composer composer.lock +# local notes and scaffold drafts (if present) +examples/ +internal/ + # gitHub copilot config (if present) .github/agents/** .github/copilot-instructions.md diff --git a/CHANGELOG.md b/CHANGELOG.md index a48ded9..4a9d508 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ All notable changes to this project will be documented in this file. The format is based on [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## 0.2.1 Under development +## 0.3.0 Under development + +- refactor(panel)!: remove activity API, document provider defaults, and update README cache/configuration registration. ## 0.2.0 September 15, 2026 diff --git a/README.md b/README.md index 52e3ad7..6e30d7f 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,7 @@ final class CachePanel extends Panel $view = PanelView::create() ->summary(count($operations) === 1 ? ' operation' : ' operations', count($operations)) - ->toolbar('Cache', count($operations)) - ->active($operations !== []); + ->toolbar('Cache', count($operations)); return $operations === [] ? $view->emptyState('No cache operations', 'The cache was observed, but nothing happened.') @@ -120,36 +119,53 @@ host encodes that array strictly, so omit secrets and keep values JSON-encodable ## Register it -Merge these fragments into an application whose debugger is already enabled. The collector's `id()` and the panel's -`ID` must match: that is how the host pairs a capture with its panel. +Merge these fragments into an application whose debugger is already enabled. The array key is the stable ID: it must +equal the collector's `id()` and the panel's `ID`, and both hosts reject a mismatch. `TITLE` and `ICON` are only the +defaults the panel ships with, so the host configuration may override both without touching the class. ```php // Yii2: inside the YII_DEBUG guard. Declare 'modules' => [] in the application configuration so the offset stays // typed under PHPStan level max; the guard then only fills in the debug entry. $config['modules']['debug'] = [ 'class' => DebugModule::class, - 'collectors' => ['cache-operations' => new CacheCollector()], - 'panels' => ['cache-operations' => new CachePanel()], + 'collectors' => ['cache' => $cacheCollector], + 'panels' => [ + 'cache' => ['class' => CachePanel::class, 'title' => 'Cache operations', 'icon' => 'db', 'position' => 1], + ], ]; ``` -```php -// Yii3: return the extended registry from the application's development DI factory. -$collector = new CacheCollector(); +A plain `CachePanel::class` string or `new CachePanel()` registers the panel with the provider defaults. Inject the +same `$cacheCollector` instance into the application service that calls `record()`. + +Yii3 reads the same shape from the application configuration, not from a registry object. -$registry = $registry - ->withCollector($collector) - ->withPanel(new CachePanel()); +```php +// config/web/params.php +'yii3/debug' => [ + 'collectors' => ['cache' => CacheCollector::class], + 'panels' => [ + 'cache' => ['class' => CachePanel::class, 'title' => 'Cache operations', 'icon' => 'db', 'position' => 1], + ], +], ``` -Both hosts derive the IDs from the objects themselves. `CollectorInterface` is the only collector contract the -debugger has, so the collector is registered as it is, and only the panel is adapted to the host's own panel type. -No catalog entry, icon enum, storage dispatch entry, or change to an official package is needed. Inject the same `$collector` into the application service that -calls `record()`. +The container resolves `CacheCollector::class`, so the same instance serves the application service and the capture. + +Entry options: + +- `class`: the collector or panel class, required unless the value is a class string or, in Yii2, an instance. +- `title`: the panel title, defaulting to the panel's `TITLE` constant. +- `icon`: a Debug Core icon key, defaulting to the panel's `ICON` constant. +- `enabled`: set to `false` to skip an entry without installing its package. +- `position`: order among extensions; unpositioned extensions follow alphabetically. + +`CollectorInterface` is the only collector contract the debugger has, so the collector is registered as it is, and +only the panel is adapted to the host's own panel type. No catalog entry, icon enum, storage dispatch entry, or +change to an official package is needed. A runnable version of this example, capturing through PSR-3 instead of a direct call, lives in -[tests/Support](tests/Support); `python3 tools/check-consumer.py` installs it as an independent Composer package and -replays a stored capture with no debugger host present. +[tests/Support](tests/Support). ## Presentation vocabulary @@ -170,8 +186,7 @@ PanelView::create() ->table(['Prop', 'Value'], [['auth', PanelView::value(['id' => 1])]], styles: [0 => ColumnStyle::IDENTIFIER]) ->group('Component', PanelView::create()->paragraph('Nested content')) ->emptyState('No operations', 'The cache was observed, but nothing happened.') - ->disclosure('Raw payload', $json) - ->active(true); + ->disclosure('Raw payload', $json); ``` Plain scalars and `null` become text. `PanelView::text()`, `::strong()`, `::code()`, `::preview()`, `::badge()`, and @@ -179,7 +194,7 @@ Plain scalars and `null` become text. `PanelView::text()`, `::strong()`, `::code validation reject invalid input with an `InvalidArgumentException`; arguments with incompatible declared types raise PHP's native `TypeError`. -The host reads the finished description through `summaryMetrics()`, `toolbarMetrics()`, `blocks()`, and `isActive()`. +The host reads the finished description through `summaryMetrics()`, `toolbarMetrics()`, and `blocks()`. Those accessors return `PHPForge\Debug\Presenter` value objects: `SummaryMetric`, `ToolbarMetric`, and the blocks, entries, and inline values behind them. A renderer narrows each value with `instanceof` over the sealed `Block` and `Inline` unions, which static analysis proves exhaustive, and reads its public properties. Inline text carries a diff --git a/src/Panel.php b/src/Panel.php index 57c75bb..a4fd7b6 100644 --- a/src/Panel.php +++ b/src/Panel.php @@ -9,23 +9,24 @@ /** * Describes a panel from captured data through metadata constants and a single presentation method. * - * Extensions own all titles, icons, columns, and content. The host owns their rendering. Subclasses supply non-empty - * metadata constants and build the view from the selected capture, never from live services. + * Extensions own all columns and content and declare the default title and icon; the host owns their rendering and + * may override the title and icon through its own configuration. Subclasses supply non-empty metadata constants and + * build the view from the selected capture, never from live services. */ abstract class Panel { /** - * @var string Host-interpreted icon identifier supplied by the extension. + * @var string Default host-interpreted icon identifier; the host configuration may override it. */ protected const string ICON = ''; /** - * @var string Stable identifier associating the panel with its captured data. + * @var string Stable identifier associating the panel with its captured data, validated against the registration. */ protected const string ID = ''; /** - * @var string Human-readable panel title used in navigation. + * @var string Default human-readable panel title used in navigation; the host configuration may override it. */ protected const string TITLE = ''; @@ -34,16 +35,16 @@ abstract class Panel * * @param array $data Provider-owned captured data, decoded by the integration when necessary. * - * @return PanelView Panel content, metrics, and activity described for the host frontend. + * @return PanelView Panel content and metrics described for the host frontend. */ abstract public function present(array $data): PanelView; /** - * Returns the icon identifier declared by the extension. + * Returns the default icon identifier declared by the extension. * * @throws InvalidArgumentException if the icon identifier is empty. * - * @return string Host-interpreted icon identifier. + * @return string Default icon identifier declared by the extension; the host may override it. */ final public function icon(): string { @@ -55,7 +56,7 @@ final public function icon(): string * * @throws InvalidArgumentException if the panel identifier is empty. * - * @return string Identifier used to associate the panel with captured data. + * @return string Identifier associating the panel with captured data, validated against the registration. */ final public function id(): string { @@ -63,11 +64,11 @@ final public function id(): string } /** - * Returns the navigation title declared by the extension. + * Returns the default navigation title declared by the extension. * * @throws InvalidArgumentException if the panel title is empty. * - * @return string Human-readable panel title. + * @return string Default panel title declared by the extension; the host may override it. */ final public function name(): string { diff --git a/src/PanelView.php b/src/PanelView.php index 8a5b51a..fcf41f1 100644 --- a/src/PanelView.php +++ b/src/PanelView.php @@ -22,8 +22,8 @@ * come from the static factories and are accepted wherever a scalar is accepted. * * The finished description is a tree of `PHPForge\Debug\Presenter` value objects. The host reads it through - * {@see self::summaryMetrics()}, {@see self::toolbarMetrics()}, {@see self::blocks()}, and {@see self::isActive()}, - * then narrows each value with `instanceof` over the sealed {@see Block} and {@see Inline} unions. + * {@see self::summaryMetrics()}, {@see self::toolbarMetrics()}, and {@see self::blocks()}, then narrows each value + * with `instanceof` over the sealed {@see Block} and {@see Inline} unions. */ final readonly class PanelView { @@ -31,27 +31,13 @@ * @param list $summary Summary metrics in display order. * @param list $blocks Panel content blocks in display order. * @param list $toolbar Toolbar metrics in display order, separate from the summary. - * @param bool $active Whether the panel is marked active for host navigation. */ private function __construct( private array $summary, private array $blocks, private array $toolbar, - private bool $active, ) {} - /** - * Changes the activity flag without altering content or metrics. - * - * @param bool $active Whether the panel is marked active for host navigation. - * - * @return self New view with the requested activity flag. - */ - public function active(bool $active): self - { - return new self($this->summary, $this->blocks, $this->toolbar, $active); - } - /** * Creates an inline status label with a semantic tone. * @@ -156,13 +142,13 @@ public static function column(string $title, self $content): Presenter\ColumnEnt } /** - * Creates an active view without content or metrics as the starting point for fluent composition. + * Creates a view without content or metrics as the starting point for fluent composition. * - * @return self Empty active view. + * @return self Empty view. */ public static function create(): self { - return new self([], [], [], true); + return new self([], [], []); } /** @@ -252,12 +238,12 @@ public function files(Presenter\FileEntry ...$files): self } /** - * Groups only the child's content; metrics and activity belong to the root view. + * Groups only the child's content; metrics belong to the root view. * * @param string $label Accessible group label. * @param self $content Child view contributing only its ordered content blocks. * - * @return self New view with the group appended and the current metrics and activity preserved. + * @return self New view with the group appended and the current metrics preserved. */ public function group(string $label, self $content): self { @@ -277,16 +263,6 @@ public function heading(string $title, bool $section = false): self return $this->append(new Presenter\HeadingBlock($title, $section)); } - /** - * Reports whether the panel is marked active for host navigation. - * - * @return bool Activity flag. - */ - public function isActive(): bool - { - return $this->active; - } - /** * Creates an inline navigation link the host renders as an anchor. * @@ -528,7 +504,7 @@ public function summary(string $label, string|int|float $value, bool $emphasized $emphasized ? self::strong((string) $value) : self::text((string) $value), ); - return new self([...$this->summary, $metric], $this->blocks, $this->toolbar, $this->active); + return new self([...$this->summary, $metric], $this->blocks, $this->toolbar); } /** @@ -596,7 +572,7 @@ public function toolbar(string $label, string|int|float $value): self { $metric = new Presenter\ToolbarMetric($label, (string) $value); - return new self($this->summary, $this->blocks, [...$this->toolbar, $metric], $this->active); + return new self($this->summary, $this->blocks, [...$this->toolbar, $metric]); } /** @@ -639,7 +615,7 @@ public static function value(mixed $value, bool $typeOnly = false): Presenter\Va } /** - * Appends a content block while retaining metrics and activity. + * Appends a content block while retaining metrics. * * @param Presenter\Block $block Content block placed after the existing blocks. * @@ -647,7 +623,7 @@ public static function value(mixed $value, bool $typeOnly = false): Presenter\Va */ private function append(Presenter\Block $block): self { - return new self($this->summary, [...$this->blocks, $block], $this->toolbar, $this->active); + return new self($this->summary, [...$this->blocks, $block], $this->toolbar); } /** diff --git a/tests/CacheExampleTest.php b/tests/CacheExampleTest.php index b347dcb..959d720 100644 --- a/tests/CacheExampleTest.php +++ b/tests/CacheExampleTest.php @@ -252,10 +252,6 @@ public function testRealOperationsAndTwoRequestLifecycles(): void $collector->capture(), 'An observed empty cache is still a capture.', ); - self::assertTrue( - $panel->present(['schema' => 1, 'operations' => []])->isActive(), - 'An empty capture must still open the panel.', - ); $collector->shutdown(); } diff --git a/tests/FluentPanelViewTest.php b/tests/FluentPanelViewTest.php index c734c2d..771f6e9 100644 --- a/tests/FluentPanelViewTest.php +++ b/tests/FluentPanelViewTest.php @@ -32,7 +32,6 @@ public function testDefinitionKeepsEveryContentOptionAndOrder(): void $view = PanelView::create() ->summary(' hits', 3) ->toolbar('Hits', 3) - ->active(false) ->overview(['Driver' => 'redis', 'State' => $badge, 'Null' => null, 'Ratio' => 1.5], compact: true) ->heading('Entries', section: true) ->table( @@ -88,10 +87,6 @@ public function testDefinitionKeepsEveryContentOptionAndOrder(): void $view->blocks(), 'Content, options, and ordering must survive fluent composition.', ); - self::assertFalse( - $view->isActive(), - 'Navigation visibility must remain explicit.', - ); } public function testDefinitionNeverMutatesAnEarlierView(): void @@ -99,8 +94,7 @@ public function testDefinitionNeverMutatesAnEarlierView(): void $base = PanelView::create(); $nested = PanelView::create() ->summary(' ignored', 9) - ->toolbar('Ignored', 8) - ->active(false); + ->toolbar('Ignored', 8); $view = $base ->summary(' hits', 3) @@ -117,10 +111,6 @@ public function testDefinitionNeverMutatesAnEarlierView(): void $base->summaryMetrics(), 'A reusable base view must keep no metrics.', ); - self::assertTrue( - $base->isActive(), - 'A reusable base view must keep its own activity flag.', - ); self::assertCount( 1, $view->summaryMetrics(), @@ -131,10 +121,6 @@ public function testDefinitionNeverMutatesAnEarlierView(): void $view->toolbarMetrics(), 'Nested metrics must not reach the root toolbar.', ); - self::assertTrue( - $view->isActive(), - 'Nested activity must not override the root flag.', - ); } public function testNumericOverviewKeysBecomeExplicitLabels(): void diff --git a/tests/PanelViewTest.php b/tests/PanelViewTest.php index 4dce96e..e92f73e 100644 --- a/tests/PanelViewTest.php +++ b/tests/PanelViewTest.php @@ -40,10 +40,6 @@ public function testDefaultsPreserveNonIntrusivePresentation(): void ->overview([]) ->table([], []); - self::assertTrue( - PanelView::create()->isActive(), - 'A described panel must be active by default.', - ); self::assertEquals( [ new HeadingBlock('Title', false),