[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
@@ -0,0 +1,133 @@
# Go Performance Profiling
## Toolchain
| Tool | Purpose | Command |
|---|---|---|
| `go tool pprof` | CPU / heap / block / mutex profiles | `go tool pprof -text -cum -nodecount=30 profile.pb.gz` |
| `go test -bench` | Micro-benchmarks | `go test -bench=BenchmarkX -benchtime=3s -benchmem -count=3` |
| `go test -cpuprofile` | Capture CPU profile during benchmarks | `go test -bench=BenchmarkX -cpuprofile=cpu.prof` |
| `go test -memprofile` | Capture heap profile during benchmarks | `go test -bench=BenchmarkX -memprofile=mem.prof` |
| `go build -gcflags="-m"` | Escape analysis (what allocates on heap) | `go build -gcflags="-m" ./path/to/pkg 2>&1 \| grep "escapes to heap"` |
| `go test -trace` | Execution trace (goroutine scheduling) | `go test -bench=BenchmarkX -trace=trace.out` |
| `benchstat` | Statistical comparison of benchmarks | `benchstat old.txt new.txt` |
## Data Extraction Script
Use `scripts/go-pprof.sh` to extract profiles into agent-readable markdown.
```bash
# CPU profile from a .prof file
.agents/skills/perf-optimization/scripts/go-pprof.sh cpu path/to/cpu.prof
# Heap profile from a live endpoint
.agents/skills/perf-optimization/scripts/go-pprof.sh heap http://localhost:6060/debug/pprof/heap
# CPU profile from a benchmark (generates and analyzes in one step)
.agents/skills/perf-optimization/scripts/go-pprof.sh bench ./internal/platform/server/... BenchmarkRequireAuth
```
## Go-Specific Optimization Patterns
### Escape Analysis
Before optimizing allocations, run escape analysis to understand WHY values escape to heap:
```bash
go build -gcflags="-m" ./internal/platform/server/ 2>&1 | grep "escapes to heap"
```
Common escape reasons:
- Returning a pointer to a local variable
- Storing a value in an interface (boxing)
- Closure captures
- Slice/map growth beyond initial capacity
### `sync.Pool` for Hot-Path Allocations
**When to use:** The profiler shows a specific type allocated/freed thousands of times per second on the same hot path.
**When NOT to use:** Objects that are long-lived, vary wildly in size, or have cleanup requirements.
```go
var bufPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
func process() {
buf := bufPool.Get().(*bytes.Buffer)
defer func() { buf.Reset(); bufPool.Put(buf) }()
// use buf...
}
```
### Pre-sized Slices and Maps
**Symptom:** `runtime.growslice` or `runtime.mapassign` in the profile — the runtime is repeatedly growing backing arrays.
```go
// ❌ Grows 3 times: cap 0 → 1 → 2 → 4
items := []Item{}
for _, raw := range data { items = append(items, parse(raw)) }
// ✅ One allocation
items := make([]Item, 0, len(data))
for _, raw := range data { items = append(items, parse(raw)) }
```
### `strings.Builder` Over `fmt.Sprintf` in Loops
**Symptom:** `fmt.Sprintf` or `runtime.convTstring` in allocation-heavy loops.
```go
var b strings.Builder
b.Grow(estimatedLen)
for _, part := range parts { b.WriteString(part) }
result := b.String()
```
### Benchmark Best Practices
```go
func BenchmarkX(b *testing.B) {
// Setup OUTSIDE the loop — don't measure init cost
fixture := buildFixture(b)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := doWork(fixture)
// Prevent compiler from eliminating the call
_ = result
}
}
```
**Statistical validity:** Always run with `-count=5` minimum and compare with `benchstat`:
```bash
go test -bench=BenchmarkX -benchmem -count=5 > old.txt
# ... make changes ...
go test -bench=BenchmarkX -benchmem -count=5 > new.txt
benchstat old.txt new.txt
```
## Go-Specific Anti-Patterns
1. **Don't use `sync.Pool` for everything.** It's only effective for truly hot paths with uniform objects. Misuse adds complexity without measurable gain.
2. **Don't fight the GC.** Go's GC is tuned for latency. If throughput matters more, tune `GOGC` before rewriting code.
3. **Don't use `unsafe` for performance.** The compiler's escape analysis and inlining are usually sufficient. `unsafe` breaks memory safety guarantees.
4. **Don't ignore `-race` when benchmarking.** Always verify correctness with `-race` before measuring performance without it.
## Irreducible Floors in Go
These are performance costs you cannot eliminate — recognize them and stop:
| Function | What it is | Why it's irreducible |
|---|---|---|
| `p256MulInternal` | P-256 scalar multiplication (ECDSA) | Hardware-optimized assembly |
| `runtime.mallocgc` | Go memory allocator | Symptom, not cause — fix the caller |
| `runtime.gcBgMarkWorker` | GC mark phase | Proportional to allocation rate — fix allocs |
| `runtime.futex` / `runtime.usleep` | OS synchronization | Scheduling overhead |
| `syscall.Syscall` | Kernel calls (I/O) | Irreducible for actual I/O ops |
| `crypto/aes` | AES encryption | Hardware AES-NI, cannot improve |
@@ -0,0 +1,12 @@
# Python Performance Profiling
> **Stub:** This module will be populated when Python profiling is needed in a project.
> Follow the pattern established in [go.md](go.md).
## Expected Content
- Toolchain: `py-spy`, `cProfile`, `memory_profiler`, `line_profiler`, `scalene`
- Patterns: `__slots__`, generator expressions, `collections.deque`, `numpy` vectorization
- Benchmarking: `timeit`, `pytest-benchmark`
- Irreducible floors: GIL contention, interpreter overhead, C extension boundaries
- Script: `scripts/python-pyspy.sh`
@@ -0,0 +1,12 @@
# Rust Performance Profiling
> **Stub:** This module will be populated when Rust profiling is needed in a project.
> Follow the pattern established in [go.md](go.md).
## Expected Content
- Toolchain: `perf`, `flamegraph`, `criterion`, `cargo-flamegraph`, `dhat`
- Patterns: Arena allocation, `#[inline]`, zero-copy parsing, `Box` vs stack
- Benchmarking: `criterion` setup, statistical comparison
- Irreducible floors: syscall overhead, TLS handshake, memory-mapped I/O
- Script: `scripts/rust-flamegraph.sh`