From d725c3199652bc4ce9b8bb8192bddc462e7e4e9e Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Tue, 3 Feb 2026 10:37:59 +0000
Subject: [PATCH 1/2] Optimize header notification count check
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Replaced `auth()->user()->unreadNotifications->count()` with `auth()->user()->unreadNotifications()->exists()` in `resources/views/components/layouts/⚡header.blade.php`.
This prevents hydrating all unread notification models, significantly reducing memory usage for users with many notifications.
Also consolidated the check into a single variable `$hasUnreadNotifications` to ensure only one query is executed per render.
Fixed syntax errors in `User.php` and `Comment.php` related to method chaining on instantiation.
Added `HeaderPerformanceTest.php` to verify the optimization.
Co-authored-by: yilanboy <27554321+yilanboy@users.noreply.github.com>
---
app/Models/Comment.php | 6 +-
app/Models/User.php | 6 +-
.../layouts/\342\232\241header.blade.php" | 8 ++-
tests/Feature/HeaderPerformanceTest.php | 58 +++++++++++++++++++
4 files changed, 72 insertions(+), 6 deletions(-)
create mode 100644 tests/Feature/HeaderPerformanceTest.php
diff --git a/app/Models/Comment.php b/app/Models/Comment.php
index 99071d7e..1b639cab 100644
--- a/app/Models/Comment.php
+++ b/app/Models/Comment.php
@@ -77,10 +77,12 @@ protected function hierarchy(): Attribute
CommentHierarchy
SQL;
- return new Attribute(
+ $attribute = new Attribute(
get: fn ($value) => Arr::first(
DB::select($query, ['id' => $this->id])
)
- )->shouldCache();
+ );
+
+ return $attribute->shouldCache();
}
}
diff --git a/app/Models/User.php b/app/Models/User.php
index d3c5b7bc..2b5d7d8b 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -81,9 +81,11 @@ public function notifyNewComment(NewComment $instance): void
protected function gravatarUrl(): Attribute
{
- return new Attribute(
+ $attribute = new Attribute(
get: fn ($value) => get_gravatar(email: $this->email, size: 512)
- )->shouldCache();
+ );
+
+ return $attribute->shouldCache();
}
public function passkeys(): MorphMany
diff --git "a/resources/views/components/layouts/\342\232\241header.blade.php" "b/resources/views/components/layouts/\342\232\241header.blade.php"
index 8284cc54..c2847765 100644
--- "a/resources/views/components/layouts/\342\232\241header.blade.php"
+++ "b/resources/views/components/layouts/\342\232\241header.blade.php"
@@ -55,6 +55,10 @@ public function logout(Logout $logout): void
@endscript
+@php
+ $hasUnreadNotifications = auth()->check() ? auth()->user()->unreadNotifications()->exists() : false;
+@endphp
+
- @if (auth()->user()->unreadNotifications->count() > 0)
+ @if ($hasUnreadNotifications)
@@ -315,7 +319,7 @@ class="rounded-full text-zinc-500 dark:text-zinc-400 dark:hover:text-zinc-50 hov
- @if (auth()->user()->unreadNotifications->count() > 0)
+ @if ($hasUnreadNotifications)
diff --git a/tests/Feature/HeaderPerformanceTest.php b/tests/Feature/HeaderPerformanceTest.php
new file mode 100644
index 00000000..e204fa4a
--- /dev/null
+++ b/tests/Feature/HeaderPerformanceTest.php
@@ -0,0 +1,58 @@
+create();
+
+ // Create 50 unread notifications
+ for ($i = 0; $i < 50; $i++) {
+ DatabaseNotification::create([
+ 'id' => Str::uuid()->toString(),
+ 'type' => 'App\Notifications\TestNotification',
+ 'notifiable_type' => User::class,
+ 'notifiable_id' => $user->id,
+ 'data' => ['message' => 'test'],
+ 'read_at' => null,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ]);
+ }
+
+ $this->actingAs($user);
+
+ DB::enableQueryLog();
+ DB::flushQueryLog();
+
+ $start = microtime(true);
+
+ Livewire::test('layouts.header');
+
+ $end = microtime(true);
+ $duration = $end - $start;
+
+ $queries = DB::getQueryLog();
+ $queryCount = count($queries);
+
+ $hasFullSelect = collect($queries)->contains(function ($query) {
+ // SQLite uses double quotes, MySQL uses backticks.
+ // We just check for "select * from" and "notifications"
+ return str_contains(strtolower($query['query']), 'select * from')
+ && str_contains(strtolower($query['query']), 'notifications');
+ });
+
+ $hasExists = collect($queries)->contains(function ($query) {
+ return str_contains(strtolower($query['query']), 'exists');
+ });
+
+ // Verify results
+ expect($queryCount)->toBeLessThanOrEqual(3);
+ expect($hasExists)->toBeTrue();
+ // We can't strict check "Has Full Select" because exists() contains the string.
+ // But we can check that we used the optimized path.
+});
From 011bbadd8a256813e86a224b1cbd70162676f0d5 Mon Sep 17 00:00:00 2001
From: Allen
Date: Wed, 4 Feb 2026 15:12:01 +0800
Subject: [PATCH 2/2] =?UTF-8?q?fix:=20simplify=20unread=20notifications=20?=
=?UTF-8?q?check=20in=20=E2=9A=A1header=20blade=20component?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../views/components/layouts/\342\232\241header.blade.php" | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git "a/resources/views/components/layouts/\342\232\241header.blade.php" "b/resources/views/components/layouts/\342\232\241header.blade.php"
index c2847765..f0d68283 100644
--- "a/resources/views/components/layouts/\342\232\241header.blade.php"
+++ "b/resources/views/components/layouts/\342\232\241header.blade.php"
@@ -56,7 +56,7 @@ public function logout(Logout $logout): void
@endscript
@php
- $hasUnreadNotifications = auth()->check() ? auth()->user()->unreadNotifications()->exists() : false;
+ $hasUnreadNotifications = auth()->check() && auth()->user()->unreadNotifications()->exists();
@endphp