diff --git a/.ai/guidelines/resources.js.blade.php b/.ai/guidelines/resources.js.blade.php
index 0468cc909..295ce20c9 100644
--- a/.ai/guidelines/resources.js.blade.php
+++ b/.ai/guidelines/resources.js.blade.php
@@ -16,7 +16,7 @@
## Navigation and requests
-- Every Inertia `` to a GET page prefetches on hover (`prefetch`). A GET page never writes; the only exceptions are `log/{log}` (remembers the log), `notifications` (marks read, remembers the filter), `remotes/@{partner}` and `remotes/orgs/{org}` (mark read), and every link to them keeps `prefetch={false}`. `starred` also keeps it, because starring changes the list. A new GET page that must write joins this list.
+- Every Inertia `` to a GET page prefetches on hover (`prefetch`). A GET page never writes; the only exceptions are `log/{log}` (remembers the log), `notifications` (marks read, remembers the filter), `remotes/@{partner}` and `remotes/orgs/{org}` (mark read), and every link to them keeps `prefetch={false}`. The navigation's Log is the exception: it opens the remembered log and a prefetch never changes the remembered log, so it prefetches everywhere except on a log page, where it scrolls to the top instead. `starred` also keeps it, because starring changes the list. A new GET page that must write joins this list.
- Never prefetch a non-GET link, an external URL, a guest's sign-in link, a mention of a handle nobody here has (guests get a missing page, and a feed would ask for it on every hover), or a URL that is not an Inertia page (images, feeds, downloads, JSON).
- A click target that is not a link (the commit card surface) prefetches on `mousedown`, never on hover: scrolling a feed hovers every card.
- After an action that changes what a prefetched page shows, flush that page (`router.flush(url)`, `router.flushAll()`, or `invalidateCacheTags`), so the next click does not show the old state.
diff --git a/app/Http/Controllers/LogController.php b/app/Http/Controllers/LogController.php
index e6b5038ad..4aee29dd1 100644
--- a/app/Http/Controllers/LogController.php
+++ b/app/Http/Controllers/LogController.php
@@ -41,7 +41,7 @@ public function show(ShowLogRequest $request, #[CurrentUser] ?User $user, Log $l
if ($log === Log::Following) {
return Inertia::location(route('login'));
}
- } elseif ($user->log !== $log) {
+ } elseif ($user->log !== $log && ! $request->prefetch()) {
$action->handle($user, $log);
}
diff --git a/resources/js/components/app-nav.tsx b/resources/js/components/app-nav.tsx
index d064cbc9c..505085dcc 100644
--- a/resources/js/components/app-nav.tsx
+++ b/resources/js/components/app-nav.tsx
@@ -194,7 +194,7 @@ function NavLink({
refresh()->log)->toBe($log);
})->with(Log::cases());
+it('does not remember a log that is only prefetched', function (): void {
+ $user = User::factory()->create(['log' => Log::Latest]);
+
+ $this->actingAs($user)->withHeaders([
+ 'X-Inertia' => 'true',
+ 'X-Inertia-Version' => (string) new HandleInertiaRequests()->version(request()),
+ 'Purpose' => 'prefetch',
+ ])->get(route('log.show', Log::Following))->assertOk();
+
+ expect($user->refresh()->log)->toBe(Log::Latest);
+});
+
it('does not update the user when the log is unchanged', function (): void {
$user = User::factory()->create(['log' => Log::Trending]);
$updatedAt = $user->updated_at;