project-brainstorm
Load when a user has a vague idea, problem, or inspiration but isn't sure what to build. Use before /project-specify when the project concept needs crystallizing from loose thoughts into something concrete.
offline-first-architecture
Load when building offline-first applications that work without network connectivity. Applies when implementing local storage, sync queues, conflict resolution, or background sync.
Full skill instructions
Follow these patterns for applications that work without network connectivity. Covers local-first data, sync strategies, conflict resolution, and background sync.
Apply when building mobile apps, PWAs, or any application requiring reliable offline functionality.
| Storage | Size Limit | Use Case |
|---|---|---|
| IndexedDB | ~50% of disk | Structured data, queries |
| localStorage | ~5-10MB | Small key-value |
| SQLite (native) | Unlimited | Mobile apps, large datasets |
| OPFS | Large | File storage in browser |
import Dexie, { Table } from 'dexie';
interface Todo {
id: string;
title: string;
completed: boolean;
updatedAt: number;
syncStatus: 'synced' | 'pending' | 'conflict';
}
class AppDatabase extends Dexie {
todos!: Table<Todo>;
constructor() {
super('AppDB');
this.version(1).stores({
todos: 'id, syncStatus, updatedAt',
});
}
}
const db = new AppDatabase();
// Offline-first write
async function updateTodo(id: string, updates: Partial<Todo>) {
await db.todos.update(id, {
...updates,
updatedAt: Date.now(),
syncStatus: 'pending',
});
// Queue for sync
await syncQueue.add({ type: 'update', entity: 'todo', id, updates });
}
interface SyncOperation {
id: string;
type: 'create' | 'update' | 'delete';
entity: string;
entityId: string;
data: any;
createdAt: number;
retryCount: number;
}
class SyncQueue {
private db: Dexie;
async add(operation: Omit<SyncOperation, 'id' | 'createdAt' | 'retryCount'>) {
await this.db.table('syncQueue').add({
...operation,
id: crypto.randomUUID(),
createdAt: Date.now(),
retryCount: 0,
});
}
async process() {
const operations = await this.db.table('syncQueue')
.orderBy('createdAt')
.toArray();
for (const op of operations) {
try {
await this.executeOperation(op);
await this.db.table('syncQueue').delete(op.id);
} catch (error) {
if (op.retryCount >= 3) {
await this.handleFailure(op, error);
} else {
await this.db.table('syncQueue').update(op.id, {
retryCount: op.retryCount + 1,
});
}
}
}
}
private async executeOperation(op: SyncOperation) {
const response = await fetch(`/api/${op.entity}/${op.entityId}`, {
method: op.type === 'create' ? 'POST' : op.type === 'delete' ? 'DELETE' : 'PUT',
body: JSON.stringify(op.data),
});
if (!response.ok) throw new Error(`Sync failed: ${response.status}`);
}
}
// Listen for online status
window.addEventListener('online', () => {
syncQueue.process();
});
// Also process periodically
setInterval(() => {
if (navigator.onLine) {
syncQueue.process();
}
}, 30000);
async function resolveConflict(local: Todo, server: Todo): Promise<Todo> {
// Compare timestamps
return local.updatedAt > server.updatedAt ? local : server;
}
async function mergeConflict(local: Todo, server: Todo, base: Todo): Promise<Todo> {
const merged = { ...server };
// For each field, check if local changed it from base
for (const key of Object.keys(local)) {
const localChanged = local[key] !== base[key];
const serverChanged = server[key] !== base[key];
if (localChanged && !serverChanged) {
// Local changed, server didn't → use local
merged[key] = local[key];
} else if (localChanged && serverChanged) {
// Both changed → conflict, need resolution
merged[key] = await resolveFieldConflict(key, local[key], server[key]);
}
// If only server changed or neither changed, keep server value
}
return merged;
}
async function handleConflict(local: Todo, server: Todo): Promise<Todo> {
// Store conflict for user review
await db.conflicts.add({
entity: 'todo',
entityId: local.id,
localVersion: local,
serverVersion: server,
});
// Mark as conflict in UI
await db.todos.update(local.id, { syncStatus: 'conflict' });
// Show conflict resolution UI
showConflictDialog(local.id);
return server; // Use server as current until resolved
}
// Register for background sync
async function requestSync() {
if ('serviceWorker' in navigator && 'sync' in window.SyncManager) {
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('sync-data');
}
}
// Service worker: handle sync event
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-data') {
event.waitUntil(syncData());
}
});
async function syncData() {
const queue = await getQueuedOperations();
for (const op of queue) {
await executeOperation(op);
await removeFromQueue(op.id);
}
}
import { useMutation, useQueryClient } from '@tanstack/react-query';
function useTodoMutation() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
// Cancel in-flight queries
await queryClient.cancelQueries({ queryKey: ['todos'] });
// Snapshot previous
const previous = queryClient.getQueryData(['todos']);
// Optimistically update UI
queryClient.setQueryData(['todos'], (old) =>
old.map(t => t.id === newTodo.id ? newTodo : t)
);
// Store in local DB with pending status
await db.todos.put({ ...newTodo, syncStatus: 'pending' });
return { previous };
},
onError: (err, newTodo, context) => {
// Rollback on error
queryClient.setQueryData(['todos'], context.previous);
await db.todos.update(newTodo.id, { syncStatus: 'error' });
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
}
interface VectorClock {
[nodeId: string]: number;
}
function incrementClock(clock: VectorClock, nodeId: string): VectorClock {
return { ...clock, [nodeId]: (clock[nodeId] || 0) + 1 };
}
function compareClocks(a: VectorClock, b: VectorClock): 'before' | 'after' | 'concurrent' {
let aBeforeB = false;
let bBeforeA = false;
const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]);
for (const key of allKeys) {
const aVal = a[key] || 0;
const bVal = b[key] || 0;
if (aVal < bVal) aBeforeB = true;
if (bVal < aVal) bBeforeA = true;
}
if (aBeforeB && !bBeforeA) return 'before';
if (bBeforeA && !aBeforeB) return 'after';
return 'concurrent';
}
Transactions auto-commit after microtask. Keep transactions short.
Check available storage, handle quota exceeded:
const estimate = await navigator.storage.estimate();
const usedPercent = (estimate.usage / estimate.quota) * 100;
navigator.onLine can be wrong. Always handle network errors gracefully.
Don't rely on client timestamps for ordering. Use server-assigned sequence numbers when possible.
Sync incrementally (since last sync timestamp) rather than full dataset.
| Task | Pattern |
|---|---|
| Local storage | IndexedDB with Dexie |
| Sync queue | Store operations, process on reconnect |
| Conflict resolution | Last-write-wins or field-level merge |
| Optimistic UI | Update immediately, rollback on error |
| Background sync | Service Worker Sync API |
| Version tracking | updatedAt or vector clocks |
Load when a user has a vague idea, problem, or inspiration but isn't sure what to build. Use before /project-specify when the project concept needs crystallizing from loose thoughts into something concrete.
Load when using Docker for containerizing applications. Applies when creating Dockerfiles, docker-compose setups, multi-stage builds, or optimizing container images.
Enforce visual design quality in every UI file. Fires for .tsx, .jsx, .vue, .svelte, and similar component files. Ensures implementations express the project's design personality rather than producing generic/boilerplate UI.
Load after project.md exists to fill gaps through targeted Q&A. Use when project goals, target users, or scope feel underspecified. Run before project-context or project-architecture to prevent rework downstream.
DEPRECATED in Speck v7. Use /speck-skeptical-review + /speck-decision-log instead — they replace outline-style research mapping with structured alternative enumeration and decision locking. /story-plan now performs just-in-time research inline. This skill remains for v6 compatibility but redirect...
Load (Platform play level only) to define technical principles and architectural guardrails for the project. Produces constitution.md — optional but valuable when a team needs explicit coding standards, governance rules, or non-negotiable constraints. FIRST ACTION after loading: read template at ...
Load when integrating PostHog for product analytics and feature flags. Applies when implementing event tracking, A/B testing, session replay, or funnel analysis.
Load when all epics are validated and /audit has run for the project. Verifies the project meets its original vision and product-contract.md paid promise. Runs end-to-end JTBD smoke test via /larp across all personas. Declares a project-level readiness state. Produces project-validation-report.md...
Load for brownfield stories where existing code must be understood before planning. Use when a story extends, modifies, or integrates with significant existing functionality. Produces codebase-scan.md — skip for fully greenfield stories. FIRST ACTION after loading: read template at .speck/templat...
Load when building serverless applications with AWS Lambda, Vercel Functions, or Cloudflare Workers. Applies when implementing FaaS patterns, managing cold starts, or optimizing serverless costs.
Load for brownfield epics to analyze existing code patterns and implementations relevant to this epic's domain before planning. Use when the epic modifies or extends significant existing functionality. Skip for fully greenfield epics. FIRST ACTION after loading: read template at .speck/templates/...
Load after epic-tech-spec.md and epic-breakdown.md exist — quality check for consistency and completeness before any story work begins. Catches spec drift and missing coverage between the tech spec and story breakdown. FIRST ACTION after loading: read template at .speck/templates/epic/epic-analys...
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience