Skip to content

Commit b27b768

Browse files
refactor(panel)!: remove activity API, document provider defaults, and update README cache/configuration registration. (#6)
1 parent 644b6ce commit b27b768

8 files changed

Lines changed: 67 additions & 91 deletions

File tree

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ c3.php
1111
# composer
1212
composer.lock
1313

14+
# local notes and scaffold drafts (if present)
15+
examples/
16+
internal/
17+
1418
# gitHub copilot config (if present)
1519
.github/agents/**
1620
.github/copilot-instructions.md

‎CHANGELOG.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## 0.2.1 Under development
8+
## 0.3.0 Under development
9+
10+
- refactor(panel)!: remove activity API, document provider defaults, and update README cache/configuration registration.
911

1012
## 0.2.0 September 15, 2026
1113

‎README.md‎

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,7 @@ final class CachePanel extends Panel
9999

100100
$view = PanelView::create()
101101
->summary(count($operations) === 1 ? ' operation' : ' operations', count($operations))
102-
->toolbar('Cache', count($operations))
103-
->active($operations !== []);
102+
->toolbar('Cache', count($operations));
104103

105104
return $operations === []
106105
? $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
120119

121120
## Register it
122121

123-
Merge these fragments into an application whose debugger is already enabled. The collector's `id()` and the panel's
124-
`ID` must match: that is how the host pairs a capture with its panel.
122+
Merge these fragments into an application whose debugger is already enabled. The array key is the stable ID: it must
123+
equal the collector's `id()` and the panel's `ID`, and both hosts reject a mismatch. `TITLE` and `ICON` are only the
124+
defaults the panel ships with, so the host configuration may override both without touching the class.
125125

126126
```php
127127
// Yii2: inside the YII_DEBUG guard. Declare 'modules' => [] in the application configuration so the offset stays
128128
// typed under PHPStan level max; the guard then only fills in the debug entry.
129129
$config['modules']['debug'] = [
130130
'class' => DebugModule::class,
131-
'collectors' => ['cache-operations' => new CacheCollector()],
132-
'panels' => ['cache-operations' => new CachePanel()],
131+
'collectors' => ['cache' => $cacheCollector],
132+
'panels' => [
133+
'cache' => ['class' => CachePanel::class, 'title' => 'Cache operations', 'icon' => 'db', 'position' => 1],
134+
],
133135
];
134136
```
135137

136-
```php
137-
// Yii3: return the extended registry from the application's development DI factory.
138-
$collector = new CacheCollector();
138+
A plain `CachePanel::class` string or `new CachePanel()` registers the panel with the provider defaults. Inject the
139+
same `$cacheCollector` instance into the application service that calls `record()`.
140+
141+
Yii3 reads the same shape from the application configuration, not from a registry object.
139142

140-
$registry = $registry
141-
->withCollector($collector)
142-
->withPanel(new CachePanel());
143+
```php
144+
// config/web/params.php
145+
'yii3/debug' => [
146+
'collectors' => ['cache' => CacheCollector::class],
147+
'panels' => [
148+
'cache' => ['class' => CachePanel::class, 'title' => 'Cache operations', 'icon' => 'db', 'position' => 1],
149+
],
150+
],
143151
```
144152

145-
Both hosts derive the IDs from the objects themselves. `CollectorInterface` is the only collector contract the
146-
debugger has, so the collector is registered as it is, and only the panel is adapted to the host's own panel type.
147-
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
148-
calls `record()`.
153+
The container resolves `CacheCollector::class`, so the same instance serves the application service and the capture.
154+
155+
Entry options:
156+
157+
- `class`: the collector or panel class, required unless the value is a class string or, in Yii2, an instance.
158+
- `title`: the panel title, defaulting to the panel's `TITLE` constant.
159+
- `icon`: a Debug Core icon key, defaulting to the panel's `ICON` constant.
160+
- `enabled`: set to `false` to skip an entry without installing its package.
161+
- `position`: order among extensions; unpositioned extensions follow alphabetically.
162+
163+
`CollectorInterface` is the only collector contract the debugger has, so the collector is registered as it is, and
164+
only the panel is adapted to the host's own panel type. No catalog entry, icon enum, storage dispatch entry, or
165+
change to an official package is needed.
149166

150167
A runnable version of this example, capturing through PSR-3 instead of a direct call, lives in
151-
[tests/Support](tests/Support); `python3 tools/check-consumer.py` installs it as an independent Composer package and
152-
replays a stored capture with no debugger host present.
168+
[tests/Support](tests/Support).
153169

154170
## Presentation vocabulary
155171

@@ -170,16 +186,15 @@ PanelView::create()
170186
->table(['Prop', 'Value'], [['auth', PanelView::value(['id' => 1])]], styles: [0 => ColumnStyle::IDENTIFIER])
171187
->group('Component', PanelView::create()->paragraph('Nested content'))
172188
->emptyState('No operations', 'The cache was observed, but nothing happened.')
173-
->disclosure('Raw payload', $json)
174-
->active(true);
189+
->disclosure('Raw payload', $json);
175190
```
176191

177192
Plain scalars and `null` become text. `PanelView::text()`, `::strong()`, `::code()`, `::preview()`, `::badge()`, and
178193
`::value()` produce validated inline values accepted wherever a scalar is accepted. Methods with explicit value
179194
validation reject invalid input with an `InvalidArgumentException`; arguments with incompatible declared types raise
180195
PHP's native `TypeError`.
181196

182-
The host reads the finished description through `summaryMetrics()`, `toolbarMetrics()`, `blocks()`, and `isActive()`.
197+
The host reads the finished description through `summaryMetrics()`, `toolbarMetrics()`, and `blocks()`.
183198
Those accessors return `PHPForge\Debug\Presenter` value objects: `SummaryMetric`, `ToolbarMetric`, and the blocks,
184199
entries, and inline values behind them. A renderer narrows each value with `instanceof` over the sealed `Block` and
185200
`Inline` unions, which static analysis proves exhaustive, and reads its public properties. Inline text carries a

‎src/Panel.php‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,24 @@
99
/**
1010
* Describes a panel from captured data through metadata constants and a single presentation method.
1111
*
12-
* Extensions own all titles, icons, columns, and content. The host owns their rendering. Subclasses supply non-empty
13-
* metadata constants and build the view from the selected capture, never from live services.
12+
* Extensions own all columns and content and declare the default title and icon; the host owns their rendering and
13+
* may override the title and icon through its own configuration. Subclasses supply non-empty metadata constants and
14+
* build the view from the selected capture, never from live services.
1415
*/
1516
abstract class Panel
1617
{
1718
/**
18-
* @var string Host-interpreted icon identifier supplied by the extension.
19+
* @var string Default host-interpreted icon identifier; the host configuration may override it.
1920
*/
2021
protected const string ICON = '';
2122

2223
/**
23-
* @var string Stable identifier associating the panel with its captured data.
24+
* @var string Stable identifier associating the panel with its captured data, validated against the registration.
2425
*/
2526
protected const string ID = '';
2627

2728
/**
28-
* @var string Human-readable panel title used in navigation.
29+
* @var string Default human-readable panel title used in navigation; the host configuration may override it.
2930
*/
3031
protected const string TITLE = '';
3132

@@ -34,16 +35,16 @@ abstract class Panel
3435
*
3536
* @param array<string, mixed> $data Provider-owned captured data, decoded by the integration when necessary.
3637
*
37-
* @return PanelView Panel content, metrics, and activity described for the host frontend.
38+
* @return PanelView Panel content and metrics described for the host frontend.
3839
*/
3940
abstract public function present(array $data): PanelView;
4041

4142
/**
42-
* Returns the icon identifier declared by the extension.
43+
* Returns the default icon identifier declared by the extension.
4344
*
4445
* @throws InvalidArgumentException if the icon identifier is empty.
4546
*
46-
* @return string Host-interpreted icon identifier.
47+
* @return string Default icon identifier declared by the extension; the host may override it.
4748
*/
4849
final public function icon(): string
4950
{
@@ -55,19 +56,19 @@ final public function icon(): string
5556
*
5657
* @throws InvalidArgumentException if the panel identifier is empty.
5758
*
58-
* @return string Identifier used to associate the panel with captured data.
59+
* @return string Identifier associating the panel with captured data, validated against the registration.
5960
*/
6061
final public function id(): string
6162
{
6263
return self::required(static::ID, 'ID');
6364
}
6465

6566
/**
66-
* Returns the navigation title declared by the extension.
67+
* Returns the default navigation title declared by the extension.
6768
*
6869
* @throws InvalidArgumentException if the panel title is empty.
6970
*
70-
* @return string Human-readable panel title.
71+
* @return string Default panel title declared by the extension; the host may override it.
7172
*/
7273
final public function name(): string
7374
{

‎src/PanelView.php‎

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,36 +22,22 @@
2222
* come from the static factories and are accepted wherever a scalar is accepted.
2323
*
2424
* The finished description is a tree of `PHPForge\Debug\Presenter` value objects. The host reads it through
25-
* {@see self::summaryMetrics()}, {@see self::toolbarMetrics()}, {@see self::blocks()}, and {@see self::isActive()},
26-
* then narrows each value with `instanceof` over the sealed {@see Block} and {@see Inline} unions.
25+
* {@see self::summaryMetrics()}, {@see self::toolbarMetrics()}, and {@see self::blocks()}, then narrows each value
26+
* with `instanceof` over the sealed {@see Block} and {@see Inline} unions.
2727
*/
2828
final readonly class PanelView
2929
{
3030
/**
3131
* @param list<Presenter\SummaryMetric> $summary Summary metrics in display order.
3232
* @param list<Presenter\Block> $blocks Panel content blocks in display order.
3333
* @param list<Presenter\ToolbarMetric> $toolbar Toolbar metrics in display order, separate from the summary.
34-
* @param bool $active Whether the panel is marked active for host navigation.
3534
*/
3635
private function __construct(
3736
private array $summary,
3837
private array $blocks,
3938
private array $toolbar,
40-
private bool $active,
4139
) {}
4240

43-
/**
44-
* Changes the activity flag without altering content or metrics.
45-
*
46-
* @param bool $active Whether the panel is marked active for host navigation.
47-
*
48-
* @return self New view with the requested activity flag.
49-
*/
50-
public function active(bool $active): self
51-
{
52-
return new self($this->summary, $this->blocks, $this->toolbar, $active);
53-
}
54-
5541
/**
5642
* Creates an inline status label with a semantic tone.
5743
*
@@ -156,13 +142,13 @@ public static function column(string $title, self $content): Presenter\ColumnEnt
156142
}
157143

158144
/**
159-
* Creates an active view without content or metrics as the starting point for fluent composition.
145+
* Creates a view without content or metrics as the starting point for fluent composition.
160146
*
161-
* @return self Empty active view.
147+
* @return self Empty view.
162148
*/
163149
public static function create(): self
164150
{
165-
return new self([], [], [], true);
151+
return new self([], [], []);
166152
}
167153

168154
/**
@@ -252,12 +238,12 @@ public function files(Presenter\FileEntry ...$files): self
252238
}
253239

254240
/**
255-
* Groups only the child's content; metrics and activity belong to the root view.
241+
* Groups only the child's content; metrics belong to the root view.
256242
*
257243
* @param string $label Accessible group label.
258244
* @param self $content Child view contributing only its ordered content blocks.
259245
*
260-
* @return self New view with the group appended and the current metrics and activity preserved.
246+
* @return self New view with the group appended and the current metrics preserved.
261247
*/
262248
public function group(string $label, self $content): self
263249
{
@@ -277,16 +263,6 @@ public function heading(string $title, bool $section = false): self
277263
return $this->append(new Presenter\HeadingBlock($title, $section));
278264
}
279265

280-
/**
281-
* Reports whether the panel is marked active for host navigation.
282-
*
283-
* @return bool Activity flag.
284-
*/
285-
public function isActive(): bool
286-
{
287-
return $this->active;
288-
}
289-
290266
/**
291267
* Creates an inline navigation link the host renders as an anchor.
292268
*
@@ -528,7 +504,7 @@ public function summary(string $label, string|int|float $value, bool $emphasized
528504
$emphasized ? self::strong((string) $value) : self::text((string) $value),
529505
);
530506

531-
return new self([...$this->summary, $metric], $this->blocks, $this->toolbar, $this->active);
507+
return new self([...$this->summary, $metric], $this->blocks, $this->toolbar);
532508
}
533509

534510
/**
@@ -596,7 +572,7 @@ public function toolbar(string $label, string|int|float $value): self
596572
{
597573
$metric = new Presenter\ToolbarMetric($label, (string) $value);
598574

599-
return new self($this->summary, $this->blocks, [...$this->toolbar, $metric], $this->active);
575+
return new self($this->summary, $this->blocks, [...$this->toolbar, $metric]);
600576
}
601577

602578
/**
@@ -639,15 +615,15 @@ public static function value(mixed $value, bool $typeOnly = false): Presenter\Va
639615
}
640616

641617
/**
642-
* Appends a content block while retaining metrics and activity.
618+
* Appends a content block while retaining metrics.
643619
*
644620
* @param Presenter\Block $block Content block placed after the existing blocks.
645621
*
646622
* @return self New view containing the additional block.
647623
*/
648624
private function append(Presenter\Block $block): self
649625
{
650-
return new self($this->summary, [...$this->blocks, $block], $this->toolbar, $this->active);
626+
return new self($this->summary, [...$this->blocks, $block], $this->toolbar);
651627
}
652628

653629
/**

‎tests/CacheExampleTest.php‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,6 @@ public function testRealOperationsAndTwoRequestLifecycles(): void
252252
$collector->capture(),
253253
'An observed empty cache is still a capture.',
254254
);
255-
self::assertTrue(
256-
$panel->present(['schema' => 1, 'operations' => []])->isActive(),
257-
'An empty capture must still open the panel.',
258-
);
259255

260256
$collector->shutdown();
261257
}

‎tests/FluentPanelViewTest.php‎

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ public function testDefinitionKeepsEveryContentOptionAndOrder(): void
3232
$view = PanelView::create()
3333
->summary(' hits', 3)
3434
->toolbar('Hits', 3)
35-
->active(false)
3635
->overview(['Driver' => 'redis', 'State' => $badge, 'Null' => null, 'Ratio' => 1.5], compact: true)
3736
->heading('Entries', section: true)
3837
->table(
@@ -88,19 +87,14 @@ public function testDefinitionKeepsEveryContentOptionAndOrder(): void
8887
$view->blocks(),
8988
'Content, options, and ordering must survive fluent composition.',
9089
);
91-
self::assertFalse(
92-
$view->isActive(),
93-
'Navigation visibility must remain explicit.',
94-
);
9590
}
9691

9792
public function testDefinitionNeverMutatesAnEarlierView(): void
9893
{
9994
$base = PanelView::create();
10095
$nested = PanelView::create()
10196
->summary(' ignored', 9)
102-
->toolbar('Ignored', 8)
103-
->active(false);
97+
->toolbar('Ignored', 8);
10498

10599
$view = $base
106100
->summary(' hits', 3)
@@ -117,10 +111,6 @@ public function testDefinitionNeverMutatesAnEarlierView(): void
117111
$base->summaryMetrics(),
118112
'A reusable base view must keep no metrics.',
119113
);
120-
self::assertTrue(
121-
$base->isActive(),
122-
'A reusable base view must keep its own activity flag.',
123-
);
124114
self::assertCount(
125115
1,
126116
$view->summaryMetrics(),
@@ -131,10 +121,6 @@ public function testDefinitionNeverMutatesAnEarlierView(): void
131121
$view->toolbarMetrics(),
132122
'Nested metrics must not reach the root toolbar.',
133123
);
134-
self::assertTrue(
135-
$view->isActive(),
136-
'Nested activity must not override the root flag.',
137-
);
138124
}
139125

140126
public function testNumericOverviewKeysBecomeExplicitLabels(): void

‎tests/PanelViewTest.php‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ public function testDefaultsPreserveNonIntrusivePresentation(): void
4040
->overview([])
4141
->table([], []);
4242

43-
self::assertTrue(
44-
PanelView::create()->isActive(),
45-
'A described panel must be active by default.',
46-
);
4743
self::assertEquals(
4844
[
4945
new HeadingBlock('Title', false),

0 commit comments

Comments
 (0)