|
| 1 | +# Spec: Snippet Result Visualizer |
| 2 | + |
| 3 | +## Objective |
| 4 | + |
| 5 | +A client-side page on webperf-snippets that accepts the JSON return value of any snippet (copied |
| 6 | +from the browser console), detects the snippet type, and renders a formatted performance report. |
| 7 | + |
| 8 | +**Problem solved**: Sites protected by Akamai or strict CSP block extended DevTools console output, |
| 9 | +but the IIFE return value remains accessible as the last evaluated expression. The visualizer turns |
| 10 | +that object into a readable report without requiring any setup or CLI. |
| 11 | + |
| 12 | +**Target users**: Web performance engineers doing reviews on sites with restrictive security |
| 13 | +policies (e.g., FedEx, enterprise sites with Akamai). |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## Core Features |
| 18 | + |
| 19 | +1. **Paste area** — textarea accepting a raw JSON object (single result, not array) |
| 20 | +2. **Auto-parse** — live parse on input change (debounced 300ms); no submit button |
| 21 | +3. **Auto-detect** — identify snippet type from the `script` field (or structural heuristics) |
| 22 | +4. **Renderers** — three display modes: |
| 23 | + - **CWV** — metric + rating + value + optional LCP subparts |
| 24 | + - **Fonts** — loaded fonts table, used-above-fold table, issues |
| 25 | + - **Audit** — issues list (severity-colored) + items table (generic) |
| 26 | +5. **Export as Markdown** — copy the rendered report as Markdown to clipboard |
| 27 | +6. **Error state** — clear message for invalid JSON or unrecognized format |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +## Acceptance Criteria |
| 32 | + |
| 33 | +- [ ] Pasting a Fonts snippet result renders three sections: Loaded, Used above fold, Issues |
| 34 | +- [ ] Pasting an LCP result shows rating badge + value + subParts breakdown when present |
| 35 | +- [ ] Pasting any audit snippet result shows issues list + items table (up to 10 rows) |
| 36 | +- [ ] Pasting invalid JSON shows an inline error, not a crash |
| 37 | +- [ ] "Copy as Markdown" copies formatted Markdown to clipboard and shows confirmation |
| 38 | +- [ ] Page appears in the sidebar nav as "Visualizer" |
| 39 | +- [ ] No new npm dependencies introduced |
| 40 | + |
| 41 | +--- |
| 42 | + |
| 43 | +## Detection Logic |
| 44 | + |
| 45 | +``` |
| 46 | +result.script === "Fonts-Preloaded-Loaded-and-used-above-the-fold" → FontsRenderer |
| 47 | +result.rating != null → CWVRenderer |
| 48 | +Array.isArray(result.issues) → AuditRenderer |
| 49 | +otherwise → RawRenderer (formatted JSON) |
| 50 | +``` |
| 51 | + |
| 52 | +--- |
| 53 | + |
| 54 | +## Data Shapes (input contracts) |
| 55 | + |
| 56 | +**CWV metric** (LCP, CLS, INP, FCP, etc.): |
| 57 | +```json |
| 58 | +{ |
| 59 | + "script": "LCP", |
| 60 | + "metric": "LCP", |
| 61 | + "rating": "good | needs-improvement | poor", |
| 62 | + "value": 1234, |
| 63 | + "unit": "ms | score", |
| 64 | + "details": { "element": "...", "subParts": { "ttfb": {}, ... } } |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +**Fonts**: |
| 69 | +```json |
| 70 | +{ |
| 71 | + "script": "Fonts-Preloaded-Loaded-and-used-above-the-fold", |
| 72 | + "status": "ok", |
| 73 | + "details": { "preloadedCount": 2, "loadedCount": 3, "usedAboveFoldCount": 2, ... }, |
| 74 | + "items": [{ "family": "...", "weight": "400", "style": "normal", "display": "swap" }], |
| 75 | + "usedFonts": [{ "family": "...", "weight": "400", "style": "normal", "elements": 12 }], |
| 76 | + "issues": [{ "severity": "warning | error", "message": "..." }] |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +**Audit** (all other snippets): |
| 81 | +```json |
| 82 | +{ |
| 83 | + "script": "Find-render-blocking-resources", |
| 84 | + "status": "ok", |
| 85 | + "count": 3, |
| 86 | + "items": [{ "url": "...", "type": "script", "durationMs": 120 }], |
| 87 | + "issues": [{ "severity": "error | warning | info", "message": "..." }] |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +--- |
| 92 | + |
| 93 | +## Project Structure |
| 94 | + |
| 95 | +``` |
| 96 | +pages/ |
| 97 | + visualizer.mdx ← Nextra page (imports SnippetVisualizer) |
| 98 | +components/ |
| 99 | + SnippetVisualizer.jsx ← Main component (textarea + renderer dispatch) |
| 100 | + SnippetVisualizer/ |
| 101 | + CWVRenderer.jsx |
| 102 | + FontsRenderer.jsx |
| 103 | + AuditRenderer.jsx |
| 104 | + exportMarkdown.js ← Pure function: result → markdown string |
| 105 | +``` |
| 106 | + |
| 107 | +`pages/_meta.json` gets a new entry: |
| 108 | +```json |
| 109 | +"visualizer": { "title": "Visualizer" } |
| 110 | +``` |
| 111 | + |
| 112 | +--- |
| 113 | + |
| 114 | +## Code Style |
| 115 | + |
| 116 | +- **No new dependencies** — React hooks only (`useState`, `useMemo`, `useCallback`) |
| 117 | +- **CSS classes** — Nextra `nx-` utility classes for visual consistency; inline styles only for |
| 118 | + dynamic values (rating colors) |
| 119 | +- **No TypeScript** — plain `.jsx` / `.js`, matching the rest of the project |
| 120 | +- **No comments** unless the why is non-obvious |
| 121 | + |
| 122 | +--- |
| 123 | + |
| 124 | +## Markdown Export Format |
| 125 | + |
| 126 | +Single result exported as: |
| 127 | +```markdown |
| 128 | +## Fonts — Fonts-Preloaded-Loaded-and-used-above-the-fold |
| 129 | + |
| 130 | +### Loaded Fonts |
| 131 | +| Family | Weight | Style | Display | |
| 132 | +|--------|--------|-------|---------| |
| 133 | +| ... | 400 | normal| swap | |
| 134 | + |
| 135 | +### Used Above Fold |
| 136 | +| Family | Weight | Style | Elements | |
| 137 | +... |
| 138 | + |
| 139 | +### Issues |
| 140 | +- ⚠️ warning: Font preloaded without crossorigin... |
| 141 | +``` |
| 142 | + |
| 143 | +For CWV metrics: |
| 144 | +```markdown |
| 145 | +## LCP — 1.2s ✅ good |
| 146 | +... |
| 147 | +``` |
| 148 | + |
| 149 | +--- |
| 150 | + |
| 151 | +## Boundaries |
| 152 | + |
| 153 | +| Always | Ask First | Never | |
| 154 | +|--------|-----------|-------| |
| 155 | +| Handle invalid input gracefully | Adding a new npm dependency | Server-side code / API routes | |
| 156 | +| Keep all logic client-side | Changing next.config.js | TypeScript migration | |
| 157 | +| Use `nx-` classes for styling | Adding a new page category | Storing paste data anywhere | |
| 158 | +| Clear error feedback | | Sending data to any external service | |
0 commit comments