Skip to content

Commit 8f69c66

Browse files
fix: address Copilot review — SW origin guard, font var, history validation, theme flash
1 parent 6f232a1 commit 8f69c66

6 files changed

Lines changed: 30 additions & 24 deletions

File tree

frontend/app/globals.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
--color-monaco-red-dark: var(--monaco-red-dark);
1717
--font-sans: var(--font-geist-sans);
1818
--font-mono: var(--font-geist-mono);
19-
--font-lora: var(--font-lora);
2019
}
2120

2221
body {

frontend/app/layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export const metadata: Metadata = {
2424
statusBarStyle: "default",
2525
title: "Monalex",
2626
},
27+
// TODO: replace with a real 180×180 PNG (SVG is ignored by iOS for home screen icons)
28+
icons: { icon: "/icons/icon.svg" },
2729
};
2830

2931
export default function RootLayout({
@@ -36,7 +38,6 @@ export default function RootLayout({
3638
<head>
3739
<meta name="theme-color" content="#ce1126" />
3840
<meta name="mobile-web-app-capable" content="yes" />
39-
<link rel="apple-touch-icon" href="/icons/icon.svg" />
4041
{/* Prevent flash of wrong theme before React hydrates */}
4142
<script
4243
dangerouslySetInnerHTML={{

frontend/app/search/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ function useDebouncedCallback(fn: (q: string) => void, delay: number) {
2222

2323
function loadHistory(): string[] {
2424
try {
25-
return JSON.parse(localStorage.getItem(HISTORY_KEY) ?? "[]");
25+
const parsed = JSON.parse(localStorage.getItem(HISTORY_KEY) ?? "[]");
26+
if (!Array.isArray(parsed)) return [];
27+
return parsed.filter((h): h is string => typeof h === "string");
2628
} catch {
2729
return [];
2830
}

frontend/components/nav.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export default function Nav() {
6666
onClick={toggle}
6767
aria-label={theme === "dark" ? "Passer en mode clair" : "Passer en mode sombre"}
6868
className="ml-2 p-2 rounded-lg text-gray-500 dark:text-slate-400 hover:text-gray-900 dark:hover:text-slate-100 hover:bg-gray-100 dark:hover:bg-slate-800 transition-colors"
69+
suppressHydrationWarning
6970
>
7071
{theme === "dark" ? <SunIcon /> : <MoonIcon />}
7172
</button>
@@ -77,6 +78,7 @@ export default function Nav() {
7778
onClick={toggle}
7879
aria-label={theme === "dark" ? "Passer en mode clair" : "Passer en mode sombre"}
7980
className="p-2 rounded-lg text-gray-500 dark:text-slate-400 hover:bg-gray-100 dark:hover:bg-slate-800 transition-colors"
81+
suppressHydrationWarning
8082
>
8183
{theme === "dark" ? <SunIcon /> : <MoonIcon />}
8284
</button>

frontend/components/theme-provider.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { createContext, useContext, useEffect, useState } from "react";
3+
import { createContext, useContext, useState } from "react";
44

55
type Theme = "light" | "dark";
66

@@ -10,17 +10,13 @@ const ThemeCtx = createContext<{ theme: Theme; toggle: () => void }>({
1010
});
1111

1212
export function ThemeProvider({ children }: { children: React.ReactNode }) {
13-
const [theme, setTheme] = useState<Theme>("light");
14-
15-
useEffect(() => {
16-
const saved = localStorage.getItem("theme") as Theme | null;
17-
const preferred = window.matchMedia("(prefers-color-scheme: dark)").matches
18-
? "dark"
19-
: "light";
20-
const initial = saved ?? preferred;
21-
setTheme(initial);
22-
document.documentElement.classList.toggle("dark", initial === "dark");
23-
}, []);
13+
// Read the class already applied by the anti-FOUC inline <head> script so
14+
// the initial state matches the DOM without waiting for a useEffect.
15+
// Falls back to "light" during SSR where document is unavailable.
16+
const [theme, setTheme] = useState<Theme>(() => {
17+
if (typeof document === "undefined") return "light";
18+
return document.documentElement.classList.contains("dark") ? "dark" : "light";
19+
});
2420

2521
function toggle() {
2622
setTheme((prev) => {

frontend/public/sw.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,25 @@ self.addEventListener("activate", (e) => {
2020
self.addEventListener("fetch", (e) => {
2121
const { request } = e;
2222
if (request.method !== "GET") return;
23+
2324
const url = new URL(request.url);
24-
// Never cache API calls — always go to network
25+
// Only cache same-origin requests — skip cross-origin (fonts, analytics, etc.)
26+
if (url.origin !== self.location.origin) return;
27+
// Never cache API calls — always hit the network
2528
if (url.pathname.startsWith("/api/")) return;
2629

2730
e.respondWith(
28-
fetch(request)
29-
.then((res) => {
31+
fetch(request).then((res) => {
32+
// Only cache valid responses
33+
if (res.ok) {
3034
const clone = res.clone();
31-
caches.open(CACHE).then((c) => c.put(request, clone));
32-
return res;
33-
})
34-
.catch(() =>
35-
caches.match(request).then((r) => r ?? caches.match("/"))
36-
)
35+
e.waitUntil(
36+
caches.open(CACHE).then((c) => c.put(request, clone)).catch(() => {})
37+
);
38+
}
39+
return res;
40+
}).catch(() =>
41+
caches.match(request).then((r) => r ?? caches.match("/"))
42+
)
3743
);
3844
});

0 commit comments

Comments
 (0)