Add ClientRouter and clean up legacy prefetch in BaseLayout

- Updated .gitignore to include GEMINI.md.
- Documented client router implementation and decisions in dev/continuity.md.
- Added ClientRouter component and theme persistence logic to BaseLayout.astro.
- Removed legacy intent‑based prefetch script.

Hubert The Eunuch
Trapped in this commit, I endure
This commit is contained in:
Nicholai 2026-01-02 02:05:32 -07:00
parent f31a1ec708
commit 7a24dcc387
3 changed files with 32 additions and 64 deletions

1
.gitignore vendored
View File

@ -36,3 +36,4 @@ src/utils/.env
# AGENTS.md symlink
AGENTS.md
GEMINI.md

View File

@ -113,3 +113,24 @@ export async function updateClasses(
---
## 2026-01-02 - Client Router Implementation
### Changes Made
- Updated `src/layouts/BaseLayout.astro` to use `<ClientRouter />` from `astro:transitions`.
- Modified theme initialization script to handle `astro:after-swap` events for persistent theming during navigation.
- Removed legacy "Intent-Based Prefetch" script as it is superseded by Astro's built-in router capabilities.
### Decisions
- Adopted Astro's `ClientRouter` to provide a smoother, SPA-like user experience with view transitions.
- Consolidated theme logic into a function `applyTheme()` that runs on both initial load and after view transitions to prevent theme flickering or resetting.
### How to Test
1. Open the site in a browser.
2. Toggle the theme to a non-default state (e.g., Light mode if default is Dark).
3. Click a navigation link (e.g., "Blog").
4. Verify the new page loads without a full refresh (no white flash).
5. Verify the selected theme persists on the new page.
### Next Steps
- [ ] Monitor for any script re-execution issues common with View Transitions.
- [ ] Consider adding custom transition animations for specific elements if needed.

View File

@ -1,5 +1,6 @@
---
import type { ImageMetadata } from 'astro';
import { ClientRouter } from 'astro:transitions';
import BaseHead from '../components/BaseHead.astro';
import Footer from '../components/Footer.astro';
import GridOverlay from '../components/GridOverlay.astro';
@ -67,9 +68,10 @@ const personSchema = {
<html lang="en" class="scroll-smooth" data-theme="dark">
<head>
<meta name="x-nicholai-marker" content={HTML_MARKER} />
<ClientRouter />
<!-- Theme initialization script - runs before page render to prevent flash -->
<script is:inline>
(function() {
function applyTheme() {
// Apply theme
const storedLocal = localStorage.getItem('theme');
const storedSession = sessionStorage.getItem('theme');
@ -84,7 +86,13 @@ const personSchema = {
if (savedColor) {
document.documentElement.style.setProperty('--color-brand-accent', savedColor);
}
})();
}
// Run immediately
applyTheme();
// Re-run on view transition
document.addEventListener('astro:after-swap', applyTheme);
</script>
<BaseHead
title={title}
@ -177,67 +185,5 @@ const personSchema = {
// Re-bind on Astro page transitions (if enabled)
document.addEventListener('astro:page-load', bindScrollAnimations);
</script>
<script>
// ===== INTENT-BASED PREFETCH (hover/focus) =====
// Lightweight prefetch to make navigation feel instant without a full SPA router.
const prefetched = new Set();
function isPrefetchableUrl(url) {
try {
const u = new URL(url, window.location.href);
if (u.origin !== window.location.origin) return false;
if (u.hash) return false;
if (u.pathname === window.location.pathname && u.search === window.location.search) return false;
return true;
} catch {
return false;
}
}
function prefetchDocument(url) {
if (!isPrefetchableUrl(url)) return;
const u = new URL(url, window.location.href);
const key = u.href;
if (prefetched.has(key)) return;
prefetched.add(key);
const link = document.createElement('link');
link.rel = 'prefetch';
link.as = 'document';
link.href = key;
document.head.appendChild(link);
}
function getAnchorFromEventTarget(target) {
if (!(target instanceof Element)) return null;
return target.closest('a[href]');
}
const schedule = (href) => {
// Don't block input; prefetch when the browser is idle if possible.
// @ts-ignore - requestIdleCallback isn't in all TS lib targets
if (window.requestIdleCallback) {
// @ts-ignore
window.requestIdleCallback(() => prefetchDocument(href), { timeout: 1000 });
} else {
setTimeout(() => prefetchDocument(href), 0);
}
};
document.addEventListener('mouseover', (e) => {
const a = getAnchorFromEventTarget(e.target);
const href = a?.getAttribute('href');
if (!href) return;
schedule(href);
}, { passive: true });
document.addEventListener('focusin', (e) => {
const a = getAnchorFromEventTarget(e.target);
const href = a?.getAttribute('href');
if (!href) return;
schedule(href);
}, { passive: true });
</script>
</body>
</html>