Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
f8ce0b7
Configure Vite with Tailwind CSS
avosalmon Aug 18, 2025
dad97ca
Register new renderer template
avosalmon Aug 18, 2025
82d38c1
Base layout with static content
avosalmon Aug 18, 2025
0888643
Use dashed border
avosalmon Aug 18, 2025
683eb1f
Add separator
avosalmon Aug 18, 2025
174e8b4
Replace hard-coded values
avosalmon Aug 19, 2025
9c8d2cf
Replace hard-coded query
avosalmon Aug 19, 2025
1fb06dd
Add empty state
avosalmon Aug 19, 2025
3bad2df
Clean up section container
avosalmon Aug 19, 2025
68b8270
Clean up
avosalmon Aug 19, 2025
f478559
Add section-container
avosalmon Aug 19, 2025
450f83d
Use section-container in topbar
avosalmon Aug 19, 2025
a9b66af
Clean up
avosalmon Aug 19, 2025
38b600d
Extract query component
avosalmon Aug 19, 2025
5b85647
Add request-header component
avosalmon Aug 20, 2025
abbd2e7
Add request-body component
avosalmon Aug 20, 2025
e6ef2b3
Add routing component
avosalmon Aug 20, 2025
b0ddee4
Add routing-parameter component
avosalmon Aug 20, 2025
1f0ab8d
Add overview component
avosalmon Aug 20, 2025
f564b6f
Add request-url component
avosalmon Aug 20, 2025
876c794
Add header component
avosalmon Aug 20, 2025
2f6df39
Add topbar component
avosalmon Aug 20, 2025
0cc4101
Add trace component skeleton
avosalmon Aug 20, 2025
bec472a
Group exception frames
avosalmon Aug 20, 2025
bbccc3f
Add formatted-source and file-with-line components
avosalmon Aug 21, 2025
0a22871
Add frames
avosalmon Aug 21, 2025
25664d9
Add previous frame
avosalmon Aug 21, 2025
ec5a8c2
Show code snippet without syntax highlight
avosalmon Aug 21, 2025
f974481
Use exceptionAsMarkdown
avosalmon Aug 21, 2025
cfd76db
Remove unused props
avosalmon Aug 21, 2025
5a78ca8
Assign snippet to a variable
avosalmon Aug 22, 2025
9ba4e9d
Syntax highlight code snippet
avosalmon Aug 22, 2025
bac1485
Update phiki
avosalmon Aug 22, 2025
a861ed6
Syntax highlight query, body, and route parameters
avosalmon Aug 22, 2025
daf5d32
Update phiki
avosalmon Aug 25, 2025
adfc38d
Use LineDecoration to highlight code snippet
avosalmon Aug 25, 2025
532ba63
Unescape highlighted text
avosalmon Aug 25, 2025
a71d76b
Add syntax-highlight component
avosalmon Aug 25, 2025
7b7093e
Handle files with less than 5 lines
avosalmon Aug 25, 2025
f64ba8a
Install Alpine.js
avosalmon Aug 25, 2025
73b965e
Add layout component
avosalmon Aug 25, 2025
84e041b
Add laravel-ascii component
avosalmon Aug 25, 2025
a91d284
Update method badge
avosalmon Aug 26, 2025
986d441
Mark frame as main
avosalmon Aug 26, 2025
782a371
Expand/collapse frames
avosalmon Aug 26, 2025
b1d8d3d
Use current color in SVGs
avosalmon Aug 27, 2025
ef05d21
Don't show callable when the frame doesn't have a class
avosalmon Aug 27, 2025
83ecab9
Copy request URL to clipboard
avosalmon Aug 29, 2025
a8855d2
Truncate long source
avosalmon Aug 29, 2025
9d926c5
Add tooltip
avosalmon Sep 1, 2025
e32b10b
wip: tooltip
avosalmon Sep 1, 2025
4b0cde1
Add side prop to tooltip
avosalmon Sep 1, 2025
d394d42
Add tooltip on request url
avosalmon Sep 1, 2025
82c2ac4
Prevent non-vendor frame from overflowing
avosalmon Sep 1, 2025
7a1306e
Allow horizontal scroll on request body
avosalmon Sep 1, 2025
2d15770
Add tooltip on request headers
avosalmon Sep 1, 2025
1d5f15c
Add tooltip on database query
avosalmon Sep 1, 2025
af23e44
Hide JS-dependent elements until Alpine is loaded
avosalmon Sep 4, 2025
f4b9e5b
Paginate queries
avosalmon Sep 4, 2025
c50901e
Add database icon
avosalmon Sep 4, 2025
a7a8bae
Add folder icons
avosalmon Sep 4, 2025
e98383e
Add copy icon
avosalmon Sep 4, 2025
c424c4b
Add globe icon
avosalmon Sep 4, 2025
7e63c29
Add alert icon
avosalmon Sep 4, 2025
abb927c
Copy exception as markdown
avosalmon Sep 4, 2025
88ae3ab
Add badge component
avosalmon Sep 5, 2025
3f7a7e3
Add http-method component
avosalmon Sep 5, 2025
b78c995
Expand frames on clicking parent div
avosalmon Sep 5, 2025
fb04ce6
Open file in editor
avosalmon Sep 5, 2025
727a691
Use pointer cursor on pagination buttons
avosalmon Sep 5, 2025
a950b66
Replace custom tooltip component with tippy.js
avosalmon Sep 5, 2025
23fec53
Upgrade phiki/phiki
avosalmon Sep 8, 2025
c980a18
Use dark-plus theme
avosalmon Sep 8, 2025
0334967
Use dvh instead of screen
avosalmon Sep 8, 2025
832a3b7
wip: light mode
avosalmon Sep 8, 2025
7a2cb14
Style gutter text
avosalmon Sep 9, 2025
6a84e0c
Only show the first 100 queries
avosalmon Sep 9, 2025
483e912
light mode styling
avosalmon Sep 9, 2025
14594a2
Add light theme for syntax highlighter
avosalmon Sep 9, 2025
bad2ba1
Uppercase keys
avosalmon Sep 9, 2025
c6d921e
Style copy as markdown button
avosalmon Sep 9, 2025
ca49d56
Clean up
avosalmon Sep 9, 2025
d92fdc5
Add hover effect on laravel ascii logo
avosalmon Sep 10, 2025
243d674
Add shadow
avosalmon Sep 10, 2025
d9e5886
Fix tooltip position for queries
avosalmon Sep 10, 2025
56afe9c
Tweak padding for mobile
avosalmon Sep 10, 2025
1ab301f
Allow HTML in tooltip
avosalmon Sep 11, 2025
777fea1
Show frame arguments
avosalmon Sep 11, 2025
ba96213
Syntax highlight frame source
avosalmon Sep 11, 2025
1c3fc8c
Add markdown template
avosalmon Sep 11, 2025
336d94d
Replace renderer directory
avosalmon Sep 11, 2025
f85d2d2
Simplify CSS rendering
avosalmon Sep 11, 2025
3bcba71
Remove unused method
avosalmon Sep 11, 2025
90ef93f
Revert unintended changes
avosalmon Sep 12, 2025
958a0c5
Rename array key
avosalmon Sep 12, 2025
5c180f4
Rename type to operator
avosalmon Sep 12, 2025
da8f0da
Set max width for tooltip
avosalmon Sep 12, 2025
2734439
Display vendor frames in two lines
avosalmon Sep 12, 2025
059ee24
Apply fixes from StyleCI
StyleCIBot Sep 12, 2025
abee620
Add empty state for routing context
avosalmon Sep 12, 2025
aeb042d
Only round top corners when frame is expanded
avosalmon Sep 13, 2025
8ffe31f
Update dot color for non-vendor frame
avosalmon Sep 15, 2025
aaaf065
Update topbar height
avosalmon Sep 15, 2025
9322f8f
Adjust padding around header section
avosalmon Sep 15, 2025
503d0e3
Move up request url and have it overlap the separator line
avosalmon Sep 15, 2025
6bd87b0
Adjust spacing
avosalmon Sep 15, 2025
bd944da
Adjust spacing for mobile
avosalmon Sep 15, 2025
a62d376
Replace shadow-sm with shadow-xs
avosalmon Sep 15, 2025
c967475
Apply bg-white without opacity in light mode
avosalmon Sep 15, 2025
ddd6e5b
Adjust spacing
avosalmon Sep 15, 2025
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
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"monolog/monolog": "^3.0",
"nesbot/carbon": "^3.8.4",
"nunomaduro/termwind": "^2.0",
"phiki/phiki": "v2.0.0",
"psr/container": "^1.1.1|^2.0.1",
"psr/log": "^1.0|^2.0|^3.0",
"psr/simple-cache": "^1.0|^2.0|^3.0",
Expand Down
59 changes: 48 additions & 11 deletions src/Illuminate/Foundation/Exceptions/Renderer/Exception.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,13 @@ public function class()
}

/**
* Get the first "non-vendor" frame index.
* Get the exception code.
*
* @return int
* @return int|string
*/
public function defaultFrame()
public function code()
{
$key = array_search(false, array_map(function (Frame $frame) {
return $frame->isFromVendor();
}, $this->frames()->all()));

return $key === false ? 0 : $key;
return $this->exception->getCode();
}

/**
Expand All @@ -120,9 +116,50 @@ public function frames()
array_shift($trace);
}

return new Collection(array_map(
fn (array $trace) => new Frame($this->exception, $classMap, $trace, $this->basePath), $trace,
));
$frames = [];
$previousFrame = null;

foreach (array_reverse($trace) as $frameData) {
$frame = new Frame($this->exception, $classMap, $frameData, $this->basePath, $previousFrame);
$frames[] = $frame;
$previousFrame = $frame;
}

$frames = array_reverse($frames);

foreach ($frames as $frame) {
if (! $frame->isFromVendor()) {
$frame->markAsMain();
break;
}
}

return new Collection($frames);
}

/**
* Get the exception's frames grouped by vendor status.
*
* @return array<int, array{is_vendor: bool, frames: array<int, Frame>}>
*/
public function frameGroups()
{
$groups = [];

foreach ($this->frames() as $frame) {
$isVendor = $frame->isFromVendor();

if (empty($groups) || $groups[array_key_last($groups)]['is_vendor'] !== $isVendor) {
$groups[] = [
'is_vendor' => $isVendor,
'frames' => [],
];
}

$groups[array_key_last($groups)]['frames'][] = $frame;
}

return $groups;
}

/**
Expand Down
87 changes: 84 additions & 3 deletions src/Illuminate/Foundation/Exceptions/Renderer/Frame.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,36 @@ class Frame
*/
protected $basePath;

/**
* The previous frame.
*
* @var \Illuminate\Foundation\Exceptions\Renderer\Frame|null
*/
protected $previous;

/**
* Whether this frame is the main (first non-vendor) frame.
*
* @var bool
*/
protected $isMain = false;

/**
* Create a new frame instance.
*
* @param \Symfony\Component\ErrorHandler\Exception\FlattenException $exception
* @param array<string, string> $classMap
* @param array{file: string, line: int, class?: string, type?: string, function?: string} $frame
* @param array{file: string, line: int, class?: string, type?: string, function?: string, args?: array} $frame
* @param string $basePath
* @param \Illuminate\Foundation\Exceptions\Renderer\Frame|null $previous
*/
public function __construct(FlattenException $exception, array $classMap, array $frame, string $basePath)
public function __construct(FlattenException $exception, array $classMap, array $frame, string $basePath, ?Frame $previous = null)
{
$this->exception = $exception;
$this->classMap = $classMap;
$this->frame = $frame;
$this->basePath = $basePath;
$this->previous = $previous;
}

/**
Expand Down Expand Up @@ -95,7 +111,11 @@ public function class()
*/
public function file()
{
return str_replace($this->basePath.'/', '', $this->frame['file']);
return match (true) {
! isset($this->frame['file']) => '[internal function]',
! is_string($this->frame['file']) => '[unknown file]',
default => str_replace($this->basePath.'/', '', $this->frame['file']),
};
}

/**
Expand All @@ -114,6 +134,16 @@ public function line()
return $this->frame['line'] > $maxLines ? 1 : $this->frame['line'];
}

/**
* Get the frame's function operator.
*
* @return '::'|'->'|''
*/
public function operator()
{
return $this->frame['type'];
}

/**
* Get the frame's function or method.
*
Expand All @@ -127,6 +157,27 @@ public function callable()
};
}

/**
* Get the frame's arguments.
*
* @return array
*/
public function args()
{
if (! isset($this->frame['args']) || ! is_array($this->frame['args']) || count($this->frame['args']) === 0) {
return [];
}

return array_map(function ($argument) {
[$key, $value] = $argument;

return match ($key) {
'object' => "{$key}({$value})",
default => $key,
};
}, $this->frame['args']);
}

/**
* Get the frame's code snippet.
*
Expand Down Expand Up @@ -157,4 +208,34 @@ public function isFromVendor()
return ! str_starts_with($this->frame['file'], $this->basePath)
|| str_starts_with($this->frame['file'], $this->basePath.'/vendor');
}

/**
* Get the previous frame.
*
* @return \Illuminate\Foundation\Exceptions\Renderer\Frame|null
*/
public function previous()
{
return $this->previous;
}

/**
* Mark this frame as the main frame.
*
* @return void
*/
public function markAsMain()
{
$this->isMain = true;
}

/**
* Determine if this is the main frame.
*
* @return bool
*/
public function isMain()
{
return $this->isMain;
}
}
2 changes: 1 addition & 1 deletion src/Illuminate/Foundation/Exceptions/Renderer/Listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function queries()
*/
public function onQueryExecuted(QueryExecuted $event)
{
if (count($this->queries) === 100) {
if (count($this->queries) === 101) {
Copy link
Contributor Author

@avosalmon avosalmon Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capture up to 101 queries to know whether there are more than 100 queries. We only display the first 100 queries on the UI.

return;
}

Expand Down
15 changes: 1 addition & 14 deletions src/Illuminate/Foundation/Exceptions/Renderer/Renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Exceptions\Renderer\Mappers\BladeMapper;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Throwable;

Expand Down Expand Up @@ -108,19 +107,7 @@ public function render(Request $request, Throwable $throwable)
*/
public static function css()
{
return (new Collection([
['styles.css', []],
['light-mode.css', ['data-theme' => 'light']],
['dark-mode.css', ['data-theme' => 'dark']],
]))->map(function ($fileAndAttributes) {
[$filename, $attributes] = $fileAndAttributes;

return '<style '.(new Collection($attributes))->map(function ($value, $attribute) {
return $attribute.'="'.$value.'"';
})->implode(' ').'>'
.file_get_contents(static::DIST.$filename)
.'</style>';
})->implode('');
return '<style>'.file_get_contents(static::DIST.'styles.css').'</style>';
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@props(['type' => 'default', 'variant' => 'soft'])

@php
$baseClasses = 'inline-flex w-fit shrink-0 items-center justify-center gap-1 font-mono leading-3 uppercase transition-colors dark:border [&_svg]:size-2.5 h-6 min-w-5 rounded-md px-1.5 text-xs/none';

$types = [
'default' => [
'soft' => 'bg-black/8 text-neutral-900 dark:border-neutral-700 dark:bg-white/10 dark:text-neutral-100',
'solid' => 'bg-neutral-600 text-neutral-100 dark:border-neutral-500 dark:bg-neutral-600',
],
'success' => [
'soft' => 'bg-emerald-200 text-emerald-900 dark:border-emerald-600 dark:bg-emerald-900/70 dark:text-emerald-400',
'solid' => 'bg-emerald-600 dark:border-emerald-500 dark:bg-emerald-600',
],
'primary' => [
'soft' => 'bg-blue-100 text-blue-900 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-300',
'solid' => 'bg-blue-700 dark:border-blue-600 dark:bg-blue-700',
],
'error' => [
'soft' => 'bg-rose-200 text-rose-900 dark:border-rose-900 dark:bg-rose-950 dark:text-rose-100 dark:[&_svg]:!text-white',
'solid' => 'bg-rose-600 dark:border-rose-500 dark:bg-rose-600',
],
'alert' => [
'soft' => 'bg-amber-200 text-amber-900 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-300',
'solid' => 'bg-amber-600 dark:border-amber-500 dark:bg-amber-600',
],
'white' => [
'soft' => 'bg-white text-neutral-900 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100',
'solid' => 'bg-black/10 text-neutral-900 dark:text-neutral-900 dark:bg-white',
],
];

$variants = [
'soft' => '',
'solid' => 'text-white dark:text-white [&_svg]:!text-white',
];

$typeClasses = $types[$type][$variant] ?? $types['default']['soft'];
$variantClasses = $variants[$variant] ?? $variants['soft'];

$classes = implode(' ', [$baseClasses, $typeClasses, $variantClasses]);

@endphp

<div {{ $attributes->merge(['class' => $classes]) }}>
{{ $slot }}
</div>

This file was deleted.

Loading