Skip to content

Commit 33f4bc0

Browse files
Merge remote-tracking branch 'origin/main'
2 parents 7e25c73 + 71c2417 commit 33f4bc0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+12207
-94
lines changed

app/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"react-dom": "^19.2.4",
2828
"react-helmet-async": "^3.0.0",
2929
"react-router-dom": "^7.13.1",
30-
"react-syntax-highlighter": "^16.1.1"
30+
"react-syntax-highlighter": "^16.1.1",
31+
"web-vitals": "^5.1.0"
3132
},
3233
"devDependencies": {
3334
"@eslint/js": "^10.0.1",
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Core Web Vitals tracking via web-vitals library.
3+
* Reports LCP, CLS, and INP to Plausible as custom events.
4+
* Only runs in production (pyplots.ai), dynamically imported for zero dev cost.
5+
*/
6+
export function reportWebVitals() {
7+
if (
8+
typeof window === 'undefined' ||
9+
window.location.hostname !== 'pyplots.ai'
10+
) {
11+
return;
12+
}
13+
14+
import('web-vitals').then(({ onLCP, onCLS, onINP }) => {
15+
onLCP((metric) => {
16+
window.plausible?.('LCP', {
17+
props: {
18+
value: String(Math.round(metric.value / 100) * 100),
19+
rating: metric.rating,
20+
},
21+
});
22+
});
23+
24+
onCLS((metric) => {
25+
window.plausible?.('CLS', {
26+
props: {
27+
value: String(Math.round(metric.value * 100) / 100),
28+
rating: metric.rating,
29+
},
30+
});
31+
});
32+
33+
onINP((metric) => {
34+
window.plausible?.('INP', {
35+
props: {
36+
value: String(Math.round(metric.value / 50) * 50),
37+
rating: metric.rating,
38+
},
39+
});
40+
});
41+
})
42+
.catch(() => {});
43+
}

app/src/components/SpecTabs.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,17 +202,18 @@ export function SpecTabs({
202202
}, [code, specId, libraryId, onTrackEvent]);
203203

204204
const handleTabChange = (_: React.SyntheticEvent, newValue: number) => {
205+
// In overview mode, only Spec tab exists at index 0
206+
const tabNames = overviewMode
207+
? ['specification']
208+
: ['code', 'specification', 'implementation', 'quality'];
209+
205210
// Toggle: clicking same tab collapses it
206211
if (tabIndex === newValue) {
212+
onTrackEvent?.('tab_toggle', { action: 'close', tab: tabNames[tabIndex], library: libraryId || undefined });
207213
setTabIndex(null);
208-
onTrackEvent?.('tab_collapse', { library: libraryId });
209214
} else {
210215
setTabIndex(newValue);
211-
// In overview mode, only Spec tab exists at index 0
212-
const tabNames = overviewMode
213-
? ['specification']
214-
: ['code', 'specification', 'implementation', 'quality'];
215-
onTrackEvent?.('tab_click', { tab: tabNames[newValue], library: libraryId });
216+
onTrackEvent?.('tab_toggle', { action: 'open', tab: tabNames[newValue], library: libraryId || undefined });
216217
}
217218
};
218219

app/src/components/ToolbarActions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function GridSizeToggle({ imageSize, onImageSizeChange, onTrackEvent }: T
5050
const handleToggle = () => {
5151
const newSize = imageSize === 'normal' ? 'compact' : 'normal';
5252
onImageSizeChange(newSize);
53-
onTrackEvent('toggle_grid_size', { size: newSize });
53+
onTrackEvent('grid_resize', { size: newSize });
5454
};
5555

5656
const handleKeyDown = (e: React.KeyboardEvent) => {

app/src/hooks/useFilterState.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ export function useFilterState({
225225
// Clear animation state
226226
setTimeout(() => setRandomAnimation(null), 1000);
227227

228-
onTrackEvent('random', { category: randomCategory, value: randomValue, method });
228+
onTrackEvent('random_filter', { category: randomCategory, value: randomValue, method });
229229
},
230230
[filterCounts, globalCounts, onTrackEvent]
231231
);

app/src/main.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client';
33
import CssBaseline from '@mui/material/CssBaseline';
44
import { ThemeProvider, createTheme } from '@mui/material/styles';
55
import { AppRouter } from './router';
6+
import { reportWebVitals } from './analytics/reportWebVitals';
67

78
// Import MonoLisa font - hosted on GCS (all text uses MonoLisa)
89
import './styles/fonts.css';
@@ -46,3 +47,5 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
4647
</ThemeProvider>
4748
</React.StrictMode>
4849
);
50+
51+
reportWebVitals();

app/src/pages/HomePage.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,8 @@ export function HomePage() {
112112
const specId = img.spec_id || '';
113113
const library = img.library;
114114
navigate(`/${specId}/${library}`);
115-
trackEvent('navigate_to_spec', { spec: specId, library });
116115
},
117-
[navigate, trackEvent, saveScrollPosition]
116+
[navigate, saveScrollPosition]
118117
);
119118

120119
// Close tooltip when clicking anywhere

app/src/pages/LegalPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ export function LegalPage() {
404404
</Paper>
405405

406406
<Typography sx={{ textAlign: 'center', fontSize: '0.8rem', color: '#9ca3af', mt: 2 }}>
407-
Last updated: January 2026
407+
Last updated: March 2026
408408
</Typography>
409409
</Box>
410410

app/src/pages/SpecPage.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ interface SpecDetail {
3434
export function SpecPage() {
3535
const { specId, library: urlLibrary } = useParams();
3636
const navigate = useNavigate();
37-
const { trackEvent } = useAnalytics();
37+
const { trackPageview, trackEvent } = useAnalytics();
3838
const { librariesData } = useAppData();
3939

4040
const [specData, setSpecData] = useState<SpecDetail | null>(null);
@@ -100,25 +100,22 @@ export function SpecPage() {
100100
(libraryId: string) => {
101101
setImageLoaded(false);
102102
navigate(`/${specId}/${libraryId}`, { replace: true });
103-
trackEvent('switch_library', { spec: specId, library: libraryId });
104103
},
105-
[specId, navigate, trackEvent]
104+
[specId, navigate]
106105
);
107106

108107
// Handle implementation click (in overview mode)
109108
const handleImplClick = useCallback(
110109
(libraryId: string) => {
111110
navigate(`/${specId}/${libraryId}`);
112-
trackEvent('select_implementation', { spec: specId, library: libraryId });
113111
},
114-
[specId, navigate, trackEvent]
112+
[specId, navigate]
115113
);
116114

117115
// Handle image click (in detail mode - go back to overview)
118116
const handleImageClick = useCallback(() => {
119117
navigate(`/${specId}`);
120-
trackEvent('back_to_overview', { spec: specId, library: selectedLibrary || undefined });
121-
}, [specId, selectedLibrary, navigate, trackEvent]);
118+
}, [specId, navigate]);
122119

123120
// Handle download
124121
const handleDownload = useCallback(
@@ -171,12 +168,12 @@ export function SpecPage() {
171168
useEffect(() => {
172169
if (specData) {
173170
if (isOverviewMode) {
174-
trackEvent('view_spec_overview', { spec: specId });
171+
trackPageview(`/${specId}`);
175172
} else if (selectedLibrary) {
176-
trackEvent('view_spec', { spec: specId, library: selectedLibrary });
173+
trackPageview(`/${specId}/${selectedLibrary}`);
177174
}
178175
}
179-
}, [specData, isOverviewMode, selectedLibrary, specId, trackEvent]);
176+
}, [specData, isOverviewMode, selectedLibrary, specId, trackPageview]);
180177

181178
// Loading state
182179
if (loading) {

app/yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2653,6 +2653,11 @@ w3c-xmlserializer@^5.0.0:
26532653
dependencies:
26542654
xml-name-validator "^5.0.0"
26552655

2656+
web-vitals@^5.1.0:
2657+
version "5.1.0"
2658+
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-5.1.0.tgz#2f117e92c8c4eeb107cb163cbb482ac20d685ebd"
2659+
integrity sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==
2660+
26562661
webidl-conversions@^8.0.1:
26572662
version "8.0.1"
26582663
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-8.0.1.tgz#0657e571fe6f06fcb15ca50ed1fdbcb495cd1686"

0 commit comments

Comments
 (0)