performance-optimization
Master perceived performance through optimistic UI, skeleton screens, and latency strategies. Learn the difference between actual and perceived latency. Use when optimizing load times, designing loading states, or improving user confidence during operations.
What this skill does
# Performance Optimization: Perceived vs Actual Latency
## Overview
**Perceived latency is more important than actual latency.** A 3-second operation that feels instant is better than a 1-second operation that feels slow. This skill teaches you to optimize how fast your interface *feels*, not just how fast it actually is.
## Core Principle: The Linear Method
The "Linear Method" popularized by Linear.com prioritizes a steady cadence of quality over frantic sprints. Applied to performance, this means:
**Speed is a feature, but perceived speed is the ultimate UX.**
When a user creates a task, the UI should show it as created *instantly*. The network request happens in the background. If it fails, the UI gracefully rolls back. This pattern builds trust—users know the system will handle the details.
## Actual vs Perceived Latency
### Actual Latency
The real time it takes for an operation to complete (measured in milliseconds).
```javascript
// Actual latency: 2000ms
const startTime = performance.now();
const result = await fetch('/api/data');
const endTime = performance.now();
console.log(`Actual latency: ${endTime - startTime}ms`);
```
### Perceived Latency
How fast the operation *feels* to the user. This is what matters.
**Factors that affect perceived latency:**
- Visual feedback (does something happen immediately?)
- Progress indication (is the user informed of progress?)
- Anticipation (does the UI predict what's coming?)
- Momentum (does the interface feel responsive?)
## The Latency Spectrum
### < 100ms: Instant
No feedback needed. The user perceives this as instant.
```javascript
// Instant - no loading state needed
const handleClick = () => {
updateLocalState(); // Instant
// Network request in background
fetch('/api/update').catch(() => rollback());
};
```
### 100ms - 1s: Subtle Feedback
Show a subtle indicator. A loading spinner is overkill; a slight opacity change or color shift is enough.
```css
/* Subtle feedback for 100-1000ms operations */
.button.loading {
opacity: 0.7;
cursor: wait;
}
/* Or a subtle pulse */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.button.loading {
animation: pulse 1s ease-in-out infinite;
}
```
### 1s - 10s: Skeleton or Spinner
Show a skeleton screen (if layout is known) or spinner (if layout is unknown).
```html
<!-- Skeleton for known layout -->
<div class="card-skeleton">
<div class="skeleton skeleton-image"></div>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text"></div>
</div>
<!-- Spinner for unknown layout -->
<div class="spinner"></div>
```
### > 10s: Progress Bar
Show a progress bar with time estimate.
```html
<div class="progress-container">
<div class="progress-bar" style="width: 65%"></div>
<span class="progress-text">Uploading... 2 of 3 files</span>
</div>
```
## Optimistic UI: The Gold Standard
Optimistic UI shows changes immediately, then syncs with the server.
### Pattern: Optimistic Update
```javascript
// 1. Update UI immediately (optimistic)
const newItem = { id: Date.now(), text: input.value };
setItems([...items, newItem]);
clearInput();
// 2. Send to server
const response = await fetch('/api/items', {
method: 'POST',
body: JSON.stringify(newItem)
});
// 3. Handle failure - rollback
if (!response.ok) {
setItems(items); // Remove the optimistic item
showError('Failed to save');
}
```
### Pattern: Optimistic Delete
```javascript
// 1. Remove from UI immediately
const updatedItems = items.filter(item => item.id !== id);
setItems(updatedItems);
// 2. Send delete request
const response = await fetch(`/api/items/${id}`, {
method: 'DELETE'
});
// 3. Handle failure - restore
if (!response.ok) {
setItems(items); // Restore the item
showError('Failed to delete');
}
```
### Pattern: Optimistic Like/Vote
```javascript
// 1. Update like count immediately
const newLikeCount = isLiked ? count - 1 : count + 1;
setLikeCount(newLikeCount);
setIsLiked(!isLiked);
// 2. Send to server
const response = await fetch(`/api/items/${id}/like`, {
method: 'POST',
body: JSON.stringify({ liked: !isLiked })
});
// 3. Handle failure - rollback
if (!response.ok) {
setLikeCount(count); // Restore original count
setIsLiked(isLiked); // Restore original state
showError('Failed to update');
}
```
## Perceived Performance Strategies
### 1. Instant Feedback
Show that something happened immediately, even if the operation isn't complete.
```javascript
// Bad - Wait for server response
const handleSubmit = async () => {
const response = await fetch('/api/submit', { body });
showSuccess('Saved!');
};
// Good - Show feedback immediately
const handleSubmit = async () => {
showSuccess('Saving...'); // Instant feedback
const response = await fetch('/api/submit', { body });
if (!response.ok) showError('Failed to save');
};
```
### 2. Progress Indication
Show progress for long operations. Even fake progress (that doesn't go to 100%) feels better than nothing.
```javascript
// Fake progress for unknown duration
const startFakeProgress = () => {
let progress = 10;
const interval = setInterval(() => {
progress += Math.random() * 20;
if (progress > 90) progress = 90; // Never reach 100%
setProgress(progress);
}, 500);
return interval;
};
// Real progress for known duration
const startRealProgress = (total) => {
let completed = 0;
const interval = setInterval(() => {
completed++;
setProgress((completed / total) * 100);
if (completed === total) clearInterval(interval);
}, 100);
return interval;
};
```
### 3. Anticipatory Design
Preload content before the user asks for it.
```javascript
// Preload next page when user scrolls near bottom
const handleScroll = () => {
if (isNearBottom()) {
preloadNextPage(); // Load before user clicks
}
};
// Preload hover targets
const handleMouseEnter = () => {
preloadUserProfile(userId); // Load on hover
};
```
### 4. Momentum Conservation
Respect the user's gesture velocity. If they swipe fast, the animation should feel fast.
```javascript
// Respect gesture velocity
const handleSwipe = (velocity) => {
const distance = Math.abs(velocity) * 100; // Distance based on velocity
const duration = Math.max(300, distance / 500); // Duration based on distance
animate({
from: currentPosition,
to: currentPosition + distance,
duration: duration,
easing: 'ease-out'
});
};
```
## Latency Masking Techniques
### 1. Skeleton Screens
Show a placeholder that matches the content structure.
```html
<!-- Skeleton that matches actual content -->
<div class="card-skeleton">
<div class="skeleton skeleton-image" style="height: 200px;"></div>
<div class="skeleton skeleton-text" style="width: 80%;"></div>
<div class="skeleton skeleton-text" style="width: 60%;"></div>
<div class="skeleton skeleton-text" style="width: 40%;"></div>
</div>
```
**Why it works:** The brain constructs a spatial map while waiting. When real content arrives, it feels like a natural transition, not a sudden appearance.
### 2. Blur-Up Technique
Load a low-quality image first, then replace with high-quality.
```html
<img
src="image-low-quality.jpg"
srcset="image-high-quality.jpg 1x"
loading="lazy"
/>
<!-- Or with CSS -->
<div class="image-container">
<img class="image-blur" src="image-low.jpg" />
<img class="image-sharp" src="image-high.jpg" />
</div>
```
```css
.image-blur {
filter: blur(20px);
opacity: 1;
transition: opacity 300ms;
}
.image-sharp {
opacity: 0;
transition: opacity 300ms;
}
.image-sharp.loaded {
opacity: 1;
}
```
### 3. Progressive Enhancement
Show basic content immediately, enhance as data loads.
```html
<!-- Show immediately -->
<article class="post">
<h1>Post Title</h1>
<p>First paragraph (server-rendered)</p>
</article>
<!-- Load additional content -->
<div id="comments-container">
<!-- Comments load via JavaScript -->
</div>
```
### 4. StagRelated in Design
contribute
IncludedLocal-only OSS contribution command center. Auto-refreshes the user's in-flight PR and issue state on invoke so conversations start with full context — no need to brief Claude on what's in flight. Helps the user find issues to contribute to on GitHub, builds per-repo dossiers of what each upstream expects (CLA, DCO, branch convention, AI policy, draft-first, review bots, issue templates), runs deterministic gates before any external action so AI-assisted contributions don't reach maintainers as slop. State is markdown-only: candidate files at ~/.contribute-system/candidates/, repo dossiers at ~/.contribute-system/research/, append-only event log at ~/.contribute-system/log.jsonl. No database, no cloud calls. Use when the user asks about their PRs / issues / contributions, wants to find new work to take on, claim an issue, build/refresh a repo's dossier, or draft a Design Issue or PR. Trigger with "/contribute", "what's my PR status", "find a contribution", "claim issue X", "draft a Design Issue for Y", "refresh dossier for Z".
architectural-analysis
IncludedUser-triggered deep architectural analysis of a codebase or scoped subtree across eight modes — information architecture, data flow, integration points, UI surfaces, interaction patterns, data model, control flow, and failure modes. This skill should be used when the user asks to "diagram this codebase," "map the architecture," "show the data flow," "give me an ERD," "trace control flow," "find the integration points," "verify the layout pattern," "audit the UX architecture," or any similar request whose primary deliverable is mermaid diagrams plus cited reports under docs/architecture/. Dispatches haiku/sonnet sub-agents in parallel for per-mode exploration, then verifies every citation mechanically before any node lands in a diagram. Not for one-off prose explanations of code (use code-explanation) or for high-level system design from scratch (use system-design).
mcp
IncludedModel Context Protocol (MCP) server development and tool management. Languages: Python, TypeScript. Capabilities: build MCP servers, integrate external APIs, discover/execute MCP tools, manage multi-server configs, design agent-centric tools. Actions: create, build, integrate, discover, execute, configure MCP servers/tools. Keywords: MCP, Model Context Protocol, MCP server, MCP tool, stdio transport, SSE transport, tool discovery, resource provider, prompt template, external API integration, Gemini CLI MCP, Claude MCP, agent tools, tool execution, server config. Use when: building MCP servers, integrating external APIs as MCP tools, discovering available MCP tools, executing MCP capabilities, configuring multi-server setups, designing tools for AI agents.
react-native-skia
IncludedDesign, build, debug, and optimise high-polish animated graphics in React Native or Expo using @shopify/react-native-skia, Reanimated, and Gesture Handler. Use when the user wants canvas-driven UI, shaders, paths, rich text, image filters, sprite fields, Skottie, video frames, snapshots, web CanvasKit setup, or performance tuning for custom motion-heavy elements such as loaders, hero art, cards, charts, progress indicators, particle systems, or gesture-driven surfaces. Also use when the user asks for fluid, glow, glass, blob, parallax, 60fps/120fps, or GPU-friendly animated effects in React Native, even if they do not explicitly say "Skia". Do not use for ordinary form/layout work with standard views.
plaid
IncludedProduct Led AI Development — guides founders from idea to launched product. Six capabilities: Idea (discover a product idea), Validate (pressure-test the idea against fatal flaws, problem reality, competition, and 2-week MVP feasibility), Plan (vision intake + document generation), Design (translate image references into a design.md spec), Launch (go-to-market strategy), and Build (roadmap execution). Use when someone says "PLAID", "plaid idea", "help me find an idea", "product idea", "idea from my business", "idea from my expertise", "plaid validate", "validate my idea", "pressure-test", "is this idea good", "find fatal flaws", "validate the problem", "plan a product", "define my vision", "generate a PRD", "product strategy", "plaid design", "design from image", "translate image to design", "create design.md", "extract design tokens", "plaid launch", "go-to-market", "launch plan", "GTM strategy", "launch playbook", "plaid build", "build the app", "start building", or "execute the roadmap".
nextjs-framer-motion-animations
IncludedAdds production-safe Motion for React or Framer Motion animations to Next.js apps, including reveal, hover and tap micro-interactions, whileInView, stagger, AnimatePresence, layout and layoutId transitions, reorder, scroll-linked UI, and lightweight route-content transitions. Use when the user asks to add, refactor, or debug Motion or Framer Motion in App Router or Pages Router codebases, especially around server/client boundaries, reduced motion, LazyMotion, bundle size, hydration, or route transitions. Avoid for GSAP-style timelines, WebGL or 3D scenes, heavy scroll storytelling, or CSS-only effects unless Motion is explicitly requested.