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
2 changes: 1 addition & 1 deletion resources/assets/dist/css/debug.min.css

Large diffs are not rendered by default.

157 changes: 157 additions & 0 deletions resources/src/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,7 @@
*/
.yii-debug-request-hero-method,
.yii-debug-snapshot-status,
.yii-debug-hero-status > .yii-debug-badge,
.yii-debug-level-chip,
.yii-debug-db-type,
.yii-debug-dump-type,
Expand Down Expand Up @@ -3742,6 +3743,162 @@
white-space: nowrap;
}

/**
* ===================================================================
* Hero — the header of a panel about one subject.
* ===================================================================
* The Request overview rail, generalized: an accent rail, a mark tile,
* the title and subtitle, a status chip, and one metric row. The status
* tone sets `--yii-debug-hue`, so the rail, the mark, and the chip share
* one hue; the muted variant keeps the neutral fallback.
*/
.yii-debug-hero {
overflow: hidden;
margin-bottom: var(--yii-debug-space-5);
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);
}

.yii-debug-hero-danger {
--yii-debug-hue: var(--yii-debug-danger);
}

.yii-debug-hero-info {
--yii-debug-hue: var(--yii-debug-info);
}

.yii-debug-hero-success {
--yii-debug-hue: var(--yii-debug-success);
}

.yii-debug-hero-warning {
--yii-debug-hue: var(--yii-debug-warning);
}

.yii-debug-hero-header {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: var(--yii-debug-space-4);
align-items: center;
padding: var(--yii-debug-space-4);
}

.yii-debug-hero-identity {
display: flex;
min-width: 0;
align-items: center;
gap: var(--yii-debug-space-3);
}

.yii-debug-hero-mark {
display: inline-flex;
width: 40px;
height: 40px;
flex: none;
align-items: center;
justify-content: center;
border: 1px solid
color-mix(
in srgb,
var(--yii-debug-hue, var(--yii-debug-panel-muted)),
transparent 65%
);
border-radius: var(--yii-debug-radius-md);
background: color-mix(
in srgb,
var(--yii-debug-hue, var(--yii-debug-panel-muted)),
transparent 88%
);
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);
font-size: var(--yii-debug-fs-sm);
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}

.yii-debug-hero-text {
display: flex;
min-width: 0;
flex-direction: column;
gap: 2px;
}

.yii-debug-hero-title,
.yii-debug-hero-subtitle {
overflow: hidden;
font-family: var(--yii-debug-font-mono);
text-overflow: ellipsis;
white-space: nowrap;
}

.yii-debug-hero-title {
color: var(--yii-debug-panel-text);
font-size: var(--yii-debug-fs-base);
font-weight: 600;
}

.yii-debug-hero-subtitle {
color: var(--yii-debug-panel-muted);
font-size: var(--yii-debug-fs-xs);
}

.yii-debug-hero-status > .yii-debug-badge {
padding: 3px 12px;
font-family: var(--yii-debug-font-mono);
font-size: var(--yii-debug-fs-sm);
font-weight: 700;
letter-spacing: 0.02em;
}

.yii-debug-hero-metrics {
display: grid;
margin: 0;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
border-top: var(--yii-debug-border);
background: var(--yii-debug-panel-border);
gap: 1px;
}

.yii-debug-hero-metric {
min-width: 0;
padding: var(--yii-debug-space-3) var(--yii-debug-space-4);
background: var(--yii-debug-panel-surface-muted);

dt {
margin-bottom: 2px;
color: var(--yii-debug-panel-muted);
font-family: var(--yii-debug-font-sans);
font-size: 0.6875rem;
font-weight: 700;
letter-spacing: 0.07em;
text-transform: uppercase;
}

dd {
overflow: hidden;
margin: 0;
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;
}
}

/**
* ===================================================================
* Entity inventory — stat tiles, entity cards, file lists, and link
Expand Down
86 changes: 85 additions & 1 deletion src/Panel/PanelRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace PHPForge\Debug\Panel;

use JsonException;
use PHPForge\Debug\{ColumnStyle, PanelView};
use PHPForge\Debug\{ColumnStyle, PanelView, Tone};
use PHPForge\Debug\Helper\{Badge, CellMore, Disclosure, EmptyState, ExtensionPill, Format, Icon, Table, Trace};
use PHPForge\Debug\Panel\Db\SqlHighlighter;
use PHPForge\Debug\Presenter\{
Expand All @@ -19,6 +19,7 @@
FilesBlock,
GroupBlock,
HeadingBlock,
HeroBlock,
Inline,
LinkInline,
LinksBlock,
Expand Down Expand Up @@ -161,6 +162,7 @@ private function block(Block $block): string
: H2::tag()
->content($block->title)
->render(),
$block instanceof HeroBlock => self::hero($block),
$block instanceof LinksBlock => $this->links($block),
$block instanceof ManifestBlock => self::manifest($block),
$block instanceof OverviewBlock => $this->overview($block),
Expand Down Expand Up @@ -387,6 +389,88 @@ private function frames(TraceInline $inline): string
->render();
}

/**
* Renders the header identifying the subject of the panel, in the look of the Request overview.
*
* The status tone colors the accent rail, the mark, and the status chip; a header without a status stays neutral.
*
* @param HeroBlock $block Hero block to render.
*
* @return string Hero markup.
*/
private static function hero(HeroBlock $block): string
{
$identity = [];

if ($block->mark !== '') {
$identity[] = Span::tag()
->addAriaAttribute('hidden', 'true')
->class(Css::HERO_MARK)
->content($block->mark);
}

$text = [
Span::tag()
->class(Css::HERO_TITLE)
->title($block->title)
->content($block->title),
];

if ($block->subtitle !== '') {
$text[] = Span::tag()
->class(Css::HERO_SUBTITLE)
->title($block->subtitle)
->content($block->subtitle);
}

$identity[] = Div::tag()
->class(Css::HERO_TEXT)
->html(...$text);

$header = [
Div::tag()
->class(Css::HERO_IDENTITY)
->html(...$identity),
];

if ($block->status !== null) {
$header[] = Div::tag()
->class(Css::HERO_STATUS)
->html(Badge::render($block->status->label, $block->status->tone));
}

$parts = [
Header::tag()
->class(Css::HERO_HEADER)
->html(...$header),
];

if ($block->metrics !== []) {
$metrics = [];

foreach ($block->metrics as $metric) {
$metrics[] = Div::tag()
->class(Css::HERO_METRIC)
->html(
Dt::tag()->content($metric->label),
Dd::tag()
->title($metric->value)
->content($metric->value),
);
}

$parts[] = Dl::tag()
->class(Css::HERO_METRICS)
->html(...$metrics);
}

return Section::tag()
->addAriaAttribute('label', $block->title)
->class(Css::hero($block->status->tone ?? Tone::MUTED))
->html(...$parts)
->render();
}

/**
* Renders one inline value through the renderer its type selects.
*
Expand Down
18 changes: 14 additions & 4 deletions src/Panel/User/UserDataNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use function str_ends_with;
use function str_replace;
use function str_starts_with;
use function strcasecmp;
use function strlen;
use function substr;
use function trim;
Expand Down Expand Up @@ -193,10 +194,10 @@ private static function buildSections(array $identity, array $labels): array
{
$buckets = ['identity' => [], 'security' => [], 'timestamps' => [], 'other' => []];

foreach (['id', 'username', 'name', 'email'] as $key) {
if (isset($identity[$key])) {
$buckets['identity'][$key] = $identity[$key];
}
// The hero already shows the identifier, the email, and the name it uses as its title, so only a second name
// is left for the Identity section.
if (isset($identity['username'], $identity['name'])) {
$buckets['identity']['name'] = $identity['name'];
}

foreach ($identity as $key => $value) {
Expand Down Expand Up @@ -311,6 +312,9 @@ private static function labelFor(string $key, array $labels): string
/**
* Resolves the status display label and CSS variant for the raw status value.
*
* The Yii2 status codes resolve first; a textual status such as the name of a Yii3 status enum resolves by its
* label, case-insensitively.
*
* @return array{0: string, 1: string} `[label, variant]`.
*/
private static function resolveStatus(string $value): array
Expand All @@ -321,6 +325,12 @@ private static function resolveStatus(string $value): array
return [self::STATUS_MAP[$raw]['label'], self::STATUS_MAP[$raw]['variant']];
}

foreach (self::STATUS_MAP as $status) {
if (strcasecmp($status['label'], $raw) === 0) {
return [$status['label'], $status['variant']];
}
}

return [$raw === '' ? 'Unknown' : $raw, 'muted'];
}

Expand Down
22 changes: 6 additions & 16 deletions src/Panel/User/UserMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,19 @@ enum UserMessage: string
case EMPTY_HEADLINE = 'No authenticated user';

/**
* Identity accessor named in the empty state, whose resolution populates the panel.
* Call to action of the empty state, worded for any framework.
*/
case EMPTY_IDENTITY = 'Yii::$app->user->identity';
case EMPTY_SIGN_IN = 'Sign in and reload the page; the identity appears here once the application resolves it.';

/**
* Closing sentence of the empty-state call to action.
*/
case EMPTY_RESOLVES = ' resolves.';

/**
* Call to action of the empty state, preceding the identity accessor.
* Stable identifier associating the panel with the captured payload, also used as its icon key.
*/
case EMPTY_SIGN_IN = 'Sign in and reload the page; the identity appears here as soon as ';
case ID = 'user';

/**
* Stable identifier associating the panel with the captured payload, also used as its icon key.
* Glyph marking the attribute and RBAC sections.
*/
case ID = 'user';
case MARK = '::';

/**
* Header of the RBAC item name column.
Expand All @@ -89,11 +84,6 @@ enum UserMessage: string
*/
case RBAC_EMPTY = 'The auth manager granted no %s to this identity.';

/**
* `sprintf()` template of an RBAC section heading, naming the section and its item count.
*/
case RBAC_HEADING = '%s (%d)';

/**
* Section label of the granted roles.
*/
Expand Down
Loading
Loading