Skip to content

Commit 53ee57c

Browse files
feat: enhance loading states and add hooks configuration
- Introduced loading states in PlotOfTheDay and RelatedSpecs components to prevent CLS. - Added hooks configuration in settings.json for pre-tool use and session management. - Updated HomePage to conditionally render PlotOfTheDay based on loading state. - Adjusted Suspense fallback in SpecPage for better loading experience. - Bumped pyplots version to 1.1.0.
1 parent f0d3957 commit 53ee57c

File tree

7 files changed

+82
-11
lines changed

7 files changed

+82
-11
lines changed

.claude/settings.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,49 @@
1717
},
1818
"env": {
1919
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
20+
},
21+
"hooks": {
22+
"PreToolUse": [
23+
{
24+
"matcher": "",
25+
"hooks": [
26+
{
27+
"type": "command",
28+
"command": "serena-hooks remind --client=claude-code"
29+
}
30+
]
31+
},
32+
{
33+
"matcher": "mcp__serena__*",
34+
"hooks": [
35+
{
36+
"type": "command",
37+
"command": "serena-hooks auto-approve --client=claude-code"
38+
}
39+
]
40+
}
41+
],
42+
"SessionStart": [
43+
{
44+
"matcher": "",
45+
"hooks": [
46+
{
47+
"type": "command",
48+
"command": "serena-hooks activate --client=claude-code"
49+
}
50+
]
51+
}
52+
],
53+
"Stop": [
54+
{
55+
"matcher": "",
56+
"hooks": [
57+
{
58+
"type": "command",
59+
"command": "serena-hooks cleanup --client=claude-code"
60+
}
61+
]
62+
}
63+
]
2064
}
2165
}

app/src/components/ImagesGrid.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@ export function ImagesGrid({
8383
sx={{
8484
display: 'flex',
8585
justifyContent: 'center',
86-
alignItems: 'center',
87-
my: 8,
86+
alignItems: 'flex-start',
87+
pt: 8,
88+
minHeight: '80vh',
8889
opacity: 0,
8990
animation: 'fadeInDelayed 1.5s ease-out 0.3s forwards',
9091
'@keyframes fadeInDelayed': {

app/src/components/PlotOfTheDay.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@ const mono = typography.fontFamily;
2929

3030
export function PlotOfTheDay() {
3131
const [data, setData] = useState<PlotOfTheDayData | null>(null);
32+
const [loading, setLoading] = useState(true);
3233
const [dismissed, setDismissed] = useState(() => window.sessionStorage.getItem('potd_dismissed') === 'true');
3334

3435
useEffect(() => {
3536
if (dismissed) return;
3637
fetch(`${API_URL}/insights/plot-of-the-day`)
3738
.then(r => { if (!r.ok) throw new Error(); return r.json(); })
3839
.then(setData)
39-
.catch(() => {});
40+
.catch(() => {})
41+
.finally(() => setLoading(false));
4042
}, [dismissed]);
4143

4244
const handleDismiss = useCallback((e: React.MouseEvent) => {
@@ -45,7 +47,16 @@ export function PlotOfTheDay() {
4547
window.sessionStorage.setItem('potd_dismissed', 'true');
4648
}, []);
4749

48-
if (!data || dismissed) return null;
50+
// Already dismissed — no space needed (user saw page before)
51+
if (dismissed) return null;
52+
53+
// Still loading — reserve space to prevent CLS
54+
if (loading) {
55+
return <Box sx={{ minHeight: { xs: 280, sm: 200 }, mb: 2 }} />;
56+
}
57+
58+
// Fetch failed or no data — collapse (post-initial-paint, negligible CLS)
59+
if (!data) return null;
4960

5061
return (
5162
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 2 }}>

app/src/components/RelatedSpecs.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ interface RelatedSpecsProps {
3838

3939
export function RelatedSpecs({ specId, mode = 'spec', library, onHoverTags }: RelatedSpecsProps) {
4040
const [related, setRelated] = useState<RelatedSpec[]>([]);
41+
const [loading, setLoading] = useState(true);
4142
const [expanded, setExpanded] = useState(false);
4243
const prevSpecIdRef = useRef(specId);
4344

@@ -49,21 +50,35 @@ export function RelatedSpecs({ specId, mode = 'spec', library, onHoverTags }: Re
4950

5051
useEffect(() => {
5152
let cancelled = false;
53+
setLoading(true);
5254
const params = new URLSearchParams({ limit: '24', mode });
5355
if (library && mode === 'full') params.set('library', library);
5456
fetch(`${API_URL}/insights/related/${specId}?${params}`)
5557
.then(r => { if (!r.ok) throw new Error(); return r.json(); })
56-
.then(data => { if (!cancelled) setRelated(data.related ?? []); })
57-
.catch(() => { if (!cancelled) setRelated([]); });
58+
.then(data => { if (!cancelled) { setRelated(data.related ?? []); setLoading(false); } })
59+
.catch(() => { if (!cancelled) { setRelated([]); setLoading(false); } });
5860
return () => { cancelled = true; };
5961
}, [specId, mode, library]);
6062

61-
if (related.length === 0) return null;
63+
// After loading, if no related specs, render nothing
64+
if (!loading && related.length === 0) return null;
6265

6366
// Collapsed: CSS hides extra rows via gridAutoRows:0 + overflow:hidden
6467

68+
// While loading, reserve space without showing the tab bar.
69+
// This prevents CLS both when results exist (common) and when they don't (rare).
70+
if (loading) {
71+
return (
72+
<Box sx={{ mt: 1.5, maxWidth: { xs: '100%', md: 1200, lg: 1400, xl: 1600 }, mx: 'auto', minHeight: { xs: 250, sm: 210 } }} />
73+
);
74+
}
75+
6576
return (
66-
<Box sx={{ mt: 1.5, maxWidth: { xs: '100%', md: 1200, lg: 1400, xl: 1600 }, mx: 'auto' }}>
77+
<Box sx={{
78+
mt: 1.5, maxWidth: { xs: '100%', md: 1200, lg: 1400, xl: 1600 }, mx: 'auto',
79+
animation: 'relatedFadeIn 0.4s ease-out',
80+
'@keyframes relatedFadeIn': { from: { opacity: 0 }, to: { opacity: 1 } },
81+
}}>
6782
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
6883
<Tabs
6984
value={expanded ? 0 : false}

app/src/pages/HomePage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export function HomePage() {
168168
</Helmet>
169169
<Header stats={stats} onRandom={handleRandom} />
170170

171-
{isFiltersEmpty(activeFilters) && !loading && <PlotOfTheDay />}
171+
{isFiltersEmpty(activeFilters) && <PlotOfTheDay />}
172172

173173
{error && (
174174
<Alert severity="error" sx={{ mb: 4, maxWidth: 500, mx: 'auto' }}>

app/src/pages/SpecPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ export function SpecPage() {
394394
{specData.description}
395395
</Typography>
396396

397-
<Suspense>
397+
<Suspense fallback={<Box sx={{ minHeight: 400 }} />}>
398398
{isOverviewMode ? (
399399
/* OVERVIEW MODE */
400400
<>

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)