[autoresearch] baseline: system prompt quality tests (5/5 passing)

This commit is contained in:
evandrodevbr committed 2026-04-11 05:27:36 -03:00
1 parent 05cc036f6d
commit 60b0101717
142 files changed
+18589 -9947

No files matched your search

@@ -0,0 +1,347 @@
# Frontend Performance Profiling — Vue 3 + Vite + PWA
## Stack Context
| Layer | Tool | Version |
|---|---|---|
| Framework | Vue 3 (SFC + Composition API) | ^3.5 |
| State | Pinia | ^3.0 |
| Router | Vue Router | ^5.0 |
| Bundler | Vite | ^7.3 |
| CSS | Tailwind CSS (Vite plugin) | ^4.2 |
| PWA | vite-plugin-pwa (Workbox) | ^1.2 |
| Backend client | Supabase JS | ^2.99 |
| Build | vue-tsc + vite build | — |
| Tests | Vitest + jsdom | ^4.1 |
---
## Profiling Toolchain
### Build-Time Analysis
| Tool | Purpose | Command |
|---|---|---|
| Vite bundle visualizer | Chunk sizes, tree-shaking effectiveness | `npx vite-bundle-visualizer` (generates `stats.html`) |
| `vite build --report` | Build timing and chunk summary | `npx vite build 2>&1` |
| `source-map-explorer` | Trace bytes to source files | `npx source-map-explorer dist/assets/*.js` |
| `bundlephobia` | Check dependency size before adding | `npx bundlephobia <package-name>` |
**Quick bundle size check (no extra dependency needed):**
```bash
# Build and report chunk sizes
cd apps/frontend && npx vite build 2>&1 | grep -E "(dist/|\.js|\.css)" | sort -t'│' -k2 -rn
```
**Detailed analysis with rollup-plugin-visualizer:**
```bash
# Install temporarily and run
cd apps/frontend && npx vite-bundle-visualizer
# Opens stats.html in browser — shows treemap of every module
```
### Runtime Analysis (Chrome DevTools)
| Tab | What it measures | When to use |
|---|---|---|
| **Performance** | Frame rate, scripting, rendering, painting | Janky animations, slow interactions |
| **Lighthouse** | LCP, FID/INP, CLS, TTI, bundle size | Overall page health score |
| **Network** | Waterfall, asset sizes, cache status | Slow loads, unnecessary requests |
| **Memory** | Heap snapshots, allocation timeline | Memory leaks, growing detached DOM |
| **Coverage** | Used vs unused JS/CSS bytes | Dead code, over-imported libraries |
### Core Web Vitals
| Metric | Target | What it measures |
|---|---|---|
| **LCP** (Largest Contentful Paint) | < 2.5s | How fast the main content appears |
| **INP** (Interaction to Next Paint) | < 200ms | How fast interactions respond |
| **CLS** (Cumulative Layout Shift) | < 0.1 | How much the layout shifts during load |
| **TTFB** (Time to First Byte) | < 800ms | Server response time |
| **FCP** (First Contentful Paint) | < 1.8s | How fast anything renders |
### Lighthouse CLI (Headless — for Scripted Analysis)
```bash
# Run Lighthouse from CLI (no browser GUI needed)
npx lighthouse http://localhost:4173 \
--output=json \
--output-path=./lighthouse-report.json \
--chrome-flags="--headless --no-sandbox" \
--only-categories=performance \
--preset=desktop
# For mobile (default):
npx lighthouse http://localhost:4173 \
--output=json \
--output-path=./lighthouse-report.json \
--chrome-flags="--headless --no-sandbox" \
--only-categories=performance
```
**Agent-readable extraction:**
```bash
# Extract key metrics from Lighthouse JSON
cat lighthouse-report.json | node -e "
const r = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
const a = r.audits;
console.log('| Metric | Score | Value |');
console.log('|---|---|---|');
['first-contentful-paint','largest-contentful-paint','total-blocking-time',
'cumulative-layout-shift','speed-index','interactive'].forEach(k => {
if(a[k]) console.log('| ' + a[k].title + ' | ' + (a[k].score*100) + ' | ' + a[k].displayValue + ' |');
});
"
```
---
## Vue/Vite-Specific Optimization Patterns
### Pattern: Vendor Chunk Splitting (Vite)
**Symptom:** Every deploy invalidates the entire JS bundle cache, including stable vendor libs (Vue, Supabase JS). See agnostic principle: *Artifact Partitioning by Change Frequency* in SKILL.md.
**Fix:** Add `manualChunks` to `vite.config.ts`:
```typescript
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor-vue': ['vue', 'vue-router', 'pinia'],
'vendor-supabase': ['@supabase/supabase-js'],
},
},
},
},
```
**Group by change frequency:**
- `vendor-vue`: framework runtime — changes only on major upgrades
- `vendor-supabase`: SDK/client — changes infrequently
- App code (auto): changes every deploy
**Verification:** After `vite build`, check `dist/assets/` for `vendor-vue-*.js` and `vendor-supabase-*.js` chunks.
### Pattern: Resource Hint Pipeline (HTML)
**Symptom:** Fonts load late because browser discovers them only after parsing CSS. See agnostic principle: *Dependency Discovery Parallelization* in SKILL.md.
**Fix:** Replace CSS `@import` with HTML `<link>` tags and add `preconnect` hints:
```html
<!-- In index.html <head> — BEFORE the CSS link -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=...&display=swap">
```
Then remove the `@import url(...)` from your CSS file.
**Safety:** Only `preconnect` to domains used on every page load. For runtime-determined domains (e.g., Supabase URL from env vars), skip the hint — hardcoding would break across environments.
### Pattern: Route-Level Code Splitting
**Symptom:** Lighthouse reports a large initial JS bundle with low Coverage score. All routes are in a single chunk.
**Fix:** Use dynamic imports for route components in Vue Router:
```typescript
// ❌ Eager import — everything in one chunk
import SiraatView from '@/views/SiraatView.vue'
// ✅ Lazy route — separate chunk, loaded on navigation
const SiraatView = () => import('@/views/SiraatView.vue')
```
**Verification:** After build, check `dist/assets/` — each lazy route should produce a separate `.js` chunk.
### Pattern: Component Lazy Loading
**Symptom:** Heavy components (charts, editors, modals) are loaded even when not visible.
**Fix:** Use `defineAsyncComponent` for below-the-fold or conditionally-rendered components:
```typescript
import { defineAsyncComponent } from 'vue'
const HeavyChart = defineAsyncComponent(() =>
import('@/features/analytics/HeavyChart.vue')
)
```
### Pattern: Pinia Store Hydration
**Symptom:** Multiple API calls for the same data on page load. Network waterfall shows redundant fetches.
**Fix:** Deduplicate fetches in Pinia stores with loading-state guards:
```typescript
const store = defineStore('lesson', () => {
const data = ref<Lesson | null>(null)
const loading = ref(false)
async function fetch(id: string) {
if (loading.value) return // ← prevent double-fetch
if (data.value?.id === id) return // ← already have it
loading.value = true
try {
data.value = await api.getLesson(id)
} finally {
loading.value = false
}
}
return { data, loading, fetch }
})
```
### Pattern: Image Optimization
**Symptom:** Lighthouse flags large images (LCP degradation). Network shows uncompressed PNGs.
**Fix checklist:**
1. Use `<img loading="lazy">` for below-the-fold images
2. Use WebP/AVIF formats (Vite handles this with plugins)
3. Specify explicit `width` and `height` attributes (prevents CLS)
4. Use `<picture>` with `srcset` for responsive images
### Pattern: Watchers / Computed Overuse
**Symptom:** DevTools Performance tab shows repeated "Scripting" blocks during reactivity updates.
**Fix:**
- Prefer `computed` over `watch` — computed values are lazy and cached
- Use `watchEffect` only when side effects are truly needed
- Use `shallowRef` / `shallowReactive` for large objects that don't need deep reactivity
```typescript
// ❌ Deep reactive on a 1000-item array — Vue tracks every nested property
const items = ref<Item[]>([])
// ✅ Shallow — Vue only tracks the array reference, not individual items
const items = shallowRef<Item[]>([])
```
### Pattern: Virtual Scrolling for Long Lists
**Symptom:** Rendering > 100 DOM nodes causes jank. DevTools shows long "Rendering" and "Painting" phases.
**Fix:** Use virtual scrolling (only render visible items):
```bash
npm install @tanstack/vue-virtual
```
### Pattern: PWA Cache Strategy Audit
**Symptom:** Repeat visits are slow despite service worker. Network tab shows cache misses.
**Audit checklist:**
1. Verify `navigator.serviceWorker.controller` is active (DevTools → Application → Service Workers)
2. Check `Cache Storage` in DevTools — are the expected caches populated?
3. Verify `runtimeCaching` patterns in `vite.config.ts` match actual API URL patterns
4. Check `networkTimeoutSeconds` — too high means slow fallback to cache
### Pattern: Tailwind CSS Purging
**Symptom:** Large CSS bundle despite using Tailwind. Build output shows > 50KB CSS.
**Fix:** Tailwind v4 (your version) uses automatic content detection. Verify:
1. No `@import` of the full Tailwind CSS build in production
2. The `@tailwindcss/vite` plugin is used (it is — confirmed in your config)
3. No dynamic class names that prevent tree-shaking (e.g., `class={`text-${color}-500`}`)
---
## Vue/Vite-Specific Anti-Patterns
1. **Don't eagerly import all routes.** This defeats Vite's code splitting. Always use dynamic `() => import()` for route components.
2. **Don't use `v-if` + `v-for` on the same element.** `v-if` has higher priority in Vue 3 — use a `<template>` wrapper.
3. **Don't deep-watch large arrays.** Use `shallowRef` or `watch(() => arr.length)` to avoid O(n) reactivity tracking.
4. **Don't ignore `key` in `v-for`.** Missing keys force Vue to re-render entire lists instead of patching individual items.
5. **Don't inline large SVGs when they could be `<img>` references.** Inline SVGs increase HTML parse time and bundle size.
6. **Don't load Supabase JS in every component.** Import the singleton client from `supabaseClient.ts` — avoid creating multiple GoTrue instances.
7. **Don't use plain functions for reactive template expressions.** If `v-if="isVisible()"` calls a function, Vue re-executes it on every render cycle. Use `computed` instead — Vue caches the result and only re-runs when dependencies change.
---
## Irreducible Floors in Vue/Vite/Browser
| Cost | What it is | Why it's irreducible |
|---|---|---|
| Vue runtime | Reactivity system, virtual DOM diffing | Framework overhead — ~30KB min gzipped |
| Supabase JS | GoTrue + PostgREST client | Auth + API client — ~40KB min gzipped |
| TLS handshake | HTTPS connection setup | Network latency — mitigated by HTTP/2, preconnect |
| Font loading | WOFF2 download + layout shift | Use `font-display: swap` and preload critical fonts |
| Service Worker registration | SW parse + install on first visit | One-time cost per SW update |
| DNS resolution | Supabase API domain lookup | Use `<link rel="dns-prefetch">` |
---
## Data Extraction Scripts
Use the scripts in `scripts/` for automated profiling:
```bash
# Always-works bundle analysis (no Chrome needed) — start here
scripts/frontend-lighthouse.sh bundle ./apps/frontend
# Lighthouse Core Web Vitals (needs Chrome/Chromium)
scripts/frontend-lighthouse.sh lighthouse http://localhost:4173 desktop
# With custom timeout (default: 120s)
LIGHTHOUSE_TIMEOUT=60 scripts/frontend-lighthouse.sh lighthouse http://localhost:4173
```
> **Agent tip:** If Lighthouse hangs (common in headless/CI environments), the script
> auto-kills after the timeout and suggests bundle mode. Always start with `bundle` mode
> since it works everywhere and provides the most actionable data (chunk sizes, vendor
> splitting status, oversized chunk flags).
## Recommended Profiling Workflow
1. **Bundle analysis first:** Run `scripts/frontend-lighthouse.sh bundle ./apps/frontend` to check chunk sizes
2. **Lighthouse desktop + mobile:** Run `scripts/frontend-lighthouse.sh lighthouse <url> desktop` and default (mobile) — compare scores
3. **Runtime profiling:** Use Chrome DevTools Performance tab to record user flows (navigation, exercise completion)
4. **Memory:** Take heap snapshots before and after a user flow — diff for leaks (detached DOM, event listeners)
5. **Coverage:** Run Coverage tool — identify dead JS/CSS shipped to the client
---
## Post-Optimization Clean Sweep Checklist
After applying the main fixes, run this checklist to catch leftover issues.
This was codified from the FATH frontend session (2026-03-23).
### Template Reactivity
- [ ] All `v-if` / `v-show` conditions use `computed` refs, not plain function calls
- [ ] `v-for` lists use stable `:key` values (not array index)
- [ ] No plain functions in template expressions that could be `computed`
### Component Loading
- [ ] All routes use `() => import()` (lazy routes)
- [ ] Modals, drawers, and sheets use `defineAsyncComponent` (they are never visible on initial render)
- [ ] Heavy sub-components within lazy routes use `defineAsyncComponent` if they are conditionally rendered
### Store Health
- [ ] Stores used by multiple co-mounted views have a concurrent-fetch guard (`if (isLoading) return`)
- [ ] No `deep: true` watchers on large arrays — use `shallowRef` + explicit trigger
- [ ] Computed getters are used instead of methods for derived store state
### Bundle Health
- [ ] Vendor libraries are isolated into stable chunks (`manualChunks`)
- [ ] CSS `@import` statements for external stylesheets (fonts, etc.) are replaced with HTML `<link>` tags
- [ ] `<link rel="preconnect">` hints exist for all statically-known third-party origins
- [ ] Supabase JS is imported only once from a singleton module, not in individual components
### Build Verification
- [ ] `npx vite build` — clean output, no warnings
- [ ] `npx vitest run` — all tests pass
- [ ] `npx vue-tsc -b` — no type errors
- [ ] Build output diff — no unexpected chunk size regressions