pwa-offline-first

Use when building offline-first Progressive Web Apps — Service Worker lifecycle, Workbox caching strategies, IndexedDB via Dexie.js, Background Sync for queued writes, Web Push notifications, Lighthouse gates, and Next.js PWA integration. Default for apps that must work on EDGE/2G or intermittent connectivity.

Best use case

pwa-offline-first is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when building offline-first Progressive Web Apps — Service Worker lifecycle, Workbox caching strategies, IndexedDB via Dexie.js, Background Sync for queued writes, Web Push notifications, Lighthouse gates, and Next.js PWA integration. Default for apps that must work on EDGE/2G or intermittent connectivity.

Teams using pwa-offline-first should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/pwa-offline-first/SKILL.md --create-dirs "https://raw.githubusercontent.com/peterbamuhigire/skills-web-dev/main/skills/mobile-cross/pwa-offline-first/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/pwa-offline-first/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How pwa-offline-first Compares

Feature / Agentpwa-offline-firstStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when building offline-first Progressive Web Apps — Service Worker lifecycle, Workbox caching strategies, IndexedDB via Dexie.js, Background Sync for queued writes, Web Push notifications, Lighthouse gates, and Next.js PWA integration. Default for apps that must work on EDGE/2G or intermittent connectivity.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# PWA Offline-First
Acknowledgement: Shared by Peter Bamuhigire, techguypeter.com, +256 784 464178.

<!-- dual-compat-start -->
## Use When

- Use when building offline-first Progressive Web Apps — Service Worker lifecycle, Workbox caching strategies, IndexedDB via Dexie.js, Background Sync for queued writes, Web Push notifications, Lighthouse gates, and Next.js PWA integration. Default for apps that must work on EDGE/2G or intermittent connectivity.
- The task needs reusable judgment, domain constraints, or a proven workflow rather than ad hoc advice.

## Do Not Use When

- The task is unrelated to `pwa-offline-first` or would be better handled by a more specific companion skill.
- The request only needs a trivial answer and none of this skill's constraints or references materially help.

## Required Inputs

- Gather relevant project context, constraints, and the concrete problem to solve; load `references` only as needed.
- Confirm the desired deliverable: design, code, review, migration plan, audit, or documentation.

## Workflow

- Read this `SKILL.md` first, then load only the referenced deep-dive files that are necessary for the task.
- Apply the ordered guidance, checklists, and decision rules in this skill instead of cherry-picking isolated snippets.
- Produce the deliverable with assumptions, risks, and follow-up work made explicit when they matter.

## Quality Standards

- Keep outputs execution-oriented, concise, and aligned with the repository's baseline engineering standards.
- Preserve compatibility with existing project conventions unless the skill explicitly requires a stronger standard.
- Prefer deterministic, reviewable steps over vague advice or tool-specific magic.

## Anti-Patterns

- Treating examples as copy-paste truth without checking fit, constraints, or failure modes.
- Loading every reference file by default instead of using progressive disclosure.

## Outputs

- A concrete result that fits the task: implementation guidance, review findings, architecture decisions, templates, or generated artifacts.
- Clear assumptions, tradeoffs, or unresolved gaps when the task cannot be completed from available context alone.
- References used, companion skills, or follow-up actions when they materially improve execution.

## References

- `references/tooling-and-tests.md` — full Vite + next-pwa configs, Lighthouse CI workflow, Playwright offline test scaffold.
<!-- dual-compat-end -->

## Why Offline-First for East Africa

Connectivity in Uganda, Kenya, and Tanzania is bimodal: urban fibre and 4G in Kampala CBD, Westlands, or Masaki, then EDGE/2G the moment a user boards a boda, enters a Bushenyi cooperative, or works inside a hospital ward with poor indoor coverage. Field workers counting inventory in Mbale, community health workers in Arua, and agents confirming MTN MoMo or Airtel Money disbursements in village kiosks all tolerate zero bars for minutes at a time. Co-working spaces on Kampala Road and Ngong Road suffer 30-second uplink stalls mid-upload. Every write path must queue locally and sync when connectivity returns; every read path must fall back to a cached response rather than a spinner.

## PWA Checklist

- Served over HTTPS (localhost exempt); Web App Manifest present and linked from `<head>`.
- Service Worker registered at the narrowest scope needed.
- Icons at 192x192, 512x512, and maskable variants.
- `beforeinstallprompt` captured and surfaced as an in-app install button.
- Lighthouse PWA score greater than or equal to 90.
- Core content renders with JavaScript disabled; interactive features degrade gracefully.
- `start_url` returns HTTP 200 when offline (served from cache).

## Web App Manifest

```json
{
  "name": "Field Inventory Uganda",
  "short_name": "FieldInv",
  "id": "/",
  "start_url": "/?source=pwa",
  "scope": "/",
  "display": "standalone",
  "orientation": "portrait",
  "theme_color": "#0f172a",
  "background_color": "#ffffff",
  "lang": "en-UG",
  "icons": [
    { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
  ],
  "shortcuts": [
    { "name": "New Visit", "url": "/visits/new" },
    { "name": "Pending Sync", "url": "/sync" }
  ]
}
```

Link from head: `<link rel="manifest" href="/manifest.webmanifest">`.

## App Install Criteria

Chromium browsers gate the install prompt on a fixed set of conditions (`web.dev/articles/install-criteria`):

- App is served over HTTPS.
- Manifest includes `short_name` or `name`.
- Icons include a 192px and a 512px icon.
- Manifest declares `start_url`.
- `display` is one of `fullscreen`, `standalone`, `minimal-ui`, or `window-controls-overlay`.
- `prefer_related_applications` is absent or `false`.
- User has clicked or tapped the page at least once and spent at least 30 seconds viewing it.
- The web app is not already installed.

Other browsers apply similar criteria with minor differences. Capture and defer the prompt so you can fire it from your own UI:

```javascript
let deferredPrompt = null;
window.addEventListener('beforeinstallprompt', (event) => {
  event.preventDefault();
  deferredPrompt = event;
  showInstallButton();
});

document.querySelector('#install-app').addEventListener('click', async () => {
  if (!deferredPrompt) return;
  deferredPrompt.prompt();
  const { outcome } = await deferredPrompt.userChoice; // 'accepted' | 'dismissed'
  analytics.track('pwa_install_prompt', { outcome });
  deferredPrompt = null;
});

window.addEventListener('appinstalled', () => hideInstallButton());
```

## Service Worker Lifecycle

A Service Worker observes three lifecycle phases: Download, Install, Activate. Per MDN: "If this is the first time a service worker has been made available, installation is attempted, then after a successful installation, it is activated." The activate event "is generally a good time to clean up old caches and other things associated with the previous version." Functional events (`fetch`, `push`) wait on promises passed to `event.waitUntil()`. The browser checks for an updated worker on in-scope navigation, or on any worker event if it has not been downloaded in the last 24 hours.

Minimum registration:

```javascript
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js', { scope: '/' });
  });
}
```

States: `installing` -> `installed (waiting)` -> `activating` -> `activated`. A new version waits until all old-worker tabs close, unless `skipWaiting()` is called.

```javascript
// sw.js
const CACHE_VERSION = 'v2025-11-01';
const SHELL_CACHE = `shell-${CACHE_VERSION}`;

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(SHELL_CACHE).then((cache) =>
    cache.addAll(['/', '/offline.html', '/styles/app.css', '/scripts/app.js'])));
  self.skipWaiting();
});

self.addEventListener('activate', (event) => {
  event.waitUntil(caches.keys().then((keys) =>
    Promise.all(keys.filter((k) => k !== SHELL_CACHE).map((k) => caches.delete(k)))
  ).then(() => self.clients.claim()));
});
```

Client-side update prompt:

```javascript
navigator.serviceWorker.register('/sw.js').then((reg) => {
  reg.addEventListener('updatefound', () => {
    const incoming = reg.installing;
    incoming?.addEventListener('statechange', () => {
      if (incoming.state === 'installed' && navigator.serviceWorker.controller)
        showUpdateToast(() => incoming.postMessage({ type: 'SKIP_WAITING' }));
    });
  });
});
```

## Workbox Setup

`vite-plugin-pwa` for Vite, `next-pwa` for Next.js. Default to `generateSW`; switch to `injectManifest` only when custom Service Worker logic is required. Full Vite and Next.js configurations live in `references/tooling-and-tests.md`.

## Caching Strategies

| Strategy | Use Case | TTL |
|---|---|---|
| CacheOnly | Static, versioned assets that don't refresh until the SW updates | Until next deploy |
| NetworkOnly | Content requiring freshness (HTML, mutations) where offline is not a priority | Never cached |
| CacheFirst | Immutable assets — fonts, hashed JS/CSS, versioned images | 30 days |
| NetworkFirst | HTML or APIs needing the latest version online with offline fallback | 10 min |
| StaleWhileRevalidate | Occasionally-updated content (avatars, help docs) where speed beats freshness | 24 hours |

```javascript
import { registerRoute } from 'workbox-routing';
import { NetworkFirst, CacheFirst, StaleWhileRevalidate, NetworkOnly } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';

registerRoute(({ url }) => url.pathname.startsWith('/api/profile'),
  new NetworkFirst({ cacheName: 'profile', networkTimeoutSeconds: 10 }));
registerRoute(({ request }) => request.destination === 'font',
  new CacheFirst({ cacheName: 'fonts',
    plugins: [new ExpirationPlugin({ maxAgeSeconds: 60 * 60 * 24 * 30 })] }));
registerRoute(({ url }) => url.pathname.startsWith('/help'),
  new StaleWhileRevalidate({ cacheName: 'help-docs' }));
registerRoute(({ url }) => url.pathname.startsWith('/api/payments'),
  new NetworkOnly(), 'POST');
```

Never cache mutation verbs. Workbox caches POST responses only when asked; doing so is almost always a bug.

## Cache Invalidation

Bump the precache manifest revision on every deploy; for runtime caches, embed a build hash in the `cacheName` so a deploy invalidates everything in one step. Workbox precaching ships `cleanupOutdatedCaches()` which "Adds an activate event listener which will clean up incompatible precaches that were created by older versions of Workbox." Call it once from your service worker entry. Use `workbox-expiration`'s `ExpirationPlugin({ maxEntries, maxAgeSeconds })` on each runtime route to bound disk usage on low-end Android devices.

## App Shell Architecture

The shell is the minimal HTML, CSS, and JavaScript needed to render UI chrome. Precached on install; content is fetched at runtime and layered in.

```text
App Shell (cached)  ---  API content (network + cache fallback)
  header, nav, skeleton     /api/visits, /api/customers
  critical CSS              images, documents
  app.js bootstrap          falls back to IndexedDB when offline
```

```javascript
// sw.ts (injectManifest mode)
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { NavigationRoute, registerRoute } from 'workbox-routing';

precacheAndRoute(self.__WB_MANIFEST);
const handler = createHandlerBoundToURL('/index.html');
registerRoute(new NavigationRoute(handler, { denylist: [/^\/api\//] }));
```

## IndexedDB with Dexie.js

IndexedDB is "a low-level API for client-side storage of significant amounts of structured data, including files/blobs" — a "transactional database system" with asynchronous reads and writes scoped to read-only or readwrite transactions. Schema migrations run inside an `IDBVersionChangeEvent` fired on `IDBOpenDBRequest.onupgradeneeded`. Same-origin policy applies.

| Storage | Use For | Limit | Notes |
|---|---|---|---|
| IndexedDB | Large or structured data, blobs, indexed queries | Browser-managed quota (often hundreds of MB) | Asynchronous, transactional |
| localStorage | Tiny key-value pairs, feature flags, last-route | ~5 MB per origin | Synchronous, blocks main thread |
| Cache Storage | HTTP request/response pairs (Workbox runtime caches) | Browser-managed | Owned by Service Worker |

Raw IndexedDB is verbose and transaction-leaky. Dexie wraps it with promises, typed tables, and migrations. Install with `npm install dexie`. Minimum example:

```javascript
import { Dexie } from 'dexie';
const db = new Dexie('MyDatabase');
db.version(1).stores({ friends: '++id, name, age' });
```

For schema evolution declare additional `db.version(N).stores(...)` blocks; Dexie auto-runs the diff.

```typescript
// src/db/index.ts
import Dexie, { Table } from 'dexie';
type SyncStatus = 'synced' | 'pending' | 'error';

export interface Customer { id?: number; externalId: string; name: string; phone: string; district: string; syncStatus: SyncStatus; updatedAt: number; }
export interface Visit { id?: number; customerId: number; notes: string; amountUgx: number; capturedAt: number; syncStatus: SyncStatus; }
export interface PendingSync { id?: number; endpoint: string; method: 'POST' | 'PUT' | 'DELETE'; body: string; attemptCount: number; createdAt: number; lastError?: string; }

export class FieldDB extends Dexie {
  customers!: Table<Customer, number>;
  visits!: Table<Visit, number>;
  pendingSyncs!: Table<PendingSync, number>;
  constructor() {
    super('field-inventory-db');
    this.version(1).stores({
      customers: '++id, externalId, district, syncStatus, [district+syncStatus]',
      visits: '++id, customerId, capturedAt, syncStatus',
      pendingSyncs: '++id, endpoint, createdAt'
    });
  }
}

export const db = new FieldDB();
export const queuePendingCustomers = () => db.customers.where('syncStatus').equals('pending').toArray();
export const markCustomerSynced = (id: number) => db.customers.update(id, { syncStatus: 'synced' });
```

Compound indexes (`[district+syncStatus]`) let you query "all pending customers in Bushenyi" without a full-table scan.

## Offline Form Submissions

Write to IndexedDB first. Network is a best-effort add-on.

```typescript
import { db } from './db';

export async function saveVisit(input: Omit<Visit, 'id' | 'syncStatus'>) {
  const id = await db.visits.add({ ...input, syncStatus: 'pending' });
  await db.pendingSyncs.add({
    endpoint: '/api/visits', method: 'POST',
    body: JSON.stringify({ ...input, localId: id }),
    attemptCount: 0, createdAt: Date.now()
  });
  const reg = await navigator.serviceWorker.ready;
  if ('sync' in reg) await (reg as any).sync.register('sync-pending');
  return id;
}
```

The user sees "Saved" immediately; the Service Worker drains the queue on reconnect.

## Background Sync API

Feature-detect first; fall back to an `online` listener on platforms without Background Sync (Safari as of iOS 17).

```javascript
// sw.js
self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-pending') event.waitUntil(syncPendingData());
});

async function syncPendingData() {
  const db = await openDB();
  for (const row of await db.getAll('pendingSyncs')) {
    try {
      const res = await fetch(row.endpoint, {
        method: row.method,
        headers: { 'Content-Type': 'application/json' },
        body: row.body
      });
      if (res.ok) await db.delete('pendingSyncs', row.id);
      else if (res.status === 409) await handleConflict(db, row, await res.json());
      else await db.put('pendingSyncs', { ...row, attemptCount: row.attemptCount + 1, lastError: `${res.status}` });
    } catch (err) {
      await db.put('pendingSyncs', { ...row, attemptCount: row.attemptCount + 1, lastError: String(err) });
      throw err;
    }
  }
}
```

Fallback for Safari:

```javascript
window.addEventListener('online', () => {
  navigator.serviceWorker?.controller?.postMessage({ type: 'MANUAL_SYNC' });
});
```

## Conflict Resolution

1. **Last-write-wins** — trivial; acceptable for non-financial fields (notes, address corrections). Client timestamp overwrites server.
2. **Server-authoritative** — server rejects stale writes with HTTP 409 + canonical record. Use for money, stock quantities, MoMo refs.
3. **Timestamp-based merge** — each field carries an `updatedAt`; newer value per-field wins. Use when two offline clients edit the same record.

```typescript
async function handleConflict(db: IDBDatabase, row: PendingSync, server: any) {
  const local = JSON.parse(row.body);
  const merged = {
    ...server,
    notes: local.updatedAt > server.updatedAt ? local.notes : server.notes,
    amountUgx: server.amountUgx, // money: server wins
    updatedAt: Date.now()
  };
  await fetch(row.endpoint, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json', 'If-Match': server.etag },
    body: JSON.stringify(merged)
  });
}
```

Money defaults to server-authoritative: the ledger is the single source of truth.

## Offline-First UX Patterns

- Connection-aware UI: read `navigator.onLine` and listen for `online`/`offline` events to toggle a banner. For coarse bandwidth detection inspect `navigator.connection.effectiveType` (`'2g'`, `'3g'`, `'4g'`); fall back gracefully where the Network Information API is unavailable.
- Optimistic updates: write to IndexedDB and reflect in the UI immediately; queue the network sync via Background Sync; mark the row `pending` until the SW confirms server ack.
- Sync indicator: a single cross-app component showing `idle | syncing | offline | conflict`, driven by Background Sync events plus an outbox-count selector on the Dexie `pendingSyncs` table.
- Save-Data awareness: respect the `Save-Data: on` request header by skipping autoplay video, deferring large image prefetch, and trimming list pages.
- Treat any non-2xx response from a captive portal or flaky DNS as a retryable sync failure, not a hard error.

```javascript
window.addEventListener('online', () => updateBanner('online'));
window.addEventListener('offline', () => updateBanner('offline'));
db.pendingSyncs.count().then((n) => setSyncBadge(n));
```

## Push Notifications

Generate a VAPID keypair once per environment (`npx web-push generate-vapid-keys`) and store the private key on the server.

```typescript
// Client
export async function subscribePush(vapidPublicKey: string) {
  if ((await Notification.requestPermission()) !== 'granted') return null;
  const reg = await navigator.serviceWorker.ready;
  const subscription = await reg.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
  });
  await fetch('/api/push/subscribe', {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(subscription)
  });
  return subscription;
}
```

```javascript
// sw.js
self.addEventListener('push', (event) => {
  const data = event.data?.json() ?? { title: 'Update', body: '' };
  event.waitUntil(self.registration.showNotification(data.title, {
    body: data.body, icon: '/icons/icon-192.png', badge: '/icons/badge-72.png',
    data: { url: data.url ?? '/' }
  }));
});
self.addEventListener('notificationclick', (event) => {
  event.notification.close();
  event.waitUntil(self.clients.openWindow(event.notification.data.url));
});
```

## Testing and CI

Run Lighthouse CI on every PR (`@lhci/cli` against the static build) and a Playwright offline test that asserts the "save offline then sync on reconnect" round-trip. Workflow YAML and Playwright scaffold are in `references/tooling-and-tests.md`. Payment and authentication routes must be `NetworkOnly`; a cached 200 on `/api/payments/confirm` is a double-spend waiting to happen.

## Performance Budget

- Time to Interactive less than or equal to 3 s on Slow 3G (400 Kbps, 400 ms RTT).
- First Contentful Paint less than or equal to 1.8 s on Slow 3G; offline cache load less than or equal to 1 s.
- Precache manifest less than or equal to 1 MB; critical-path JS less than or equal to 170 KB compressed.
- Largest Contentful Paint less than or equal to 2.5 s at P75 (Core Web Vitals "Good").
- Cumulative Layout Shift less than 0.1; Interaction to Next Paint less than 200 ms.

Enforce via Lighthouse CI assertions; a red budget fails the build rather than warning.

## Companion Skills

- `nextjs-app-router` — Next.js App Router patterns, layouts, server/client components, route handlers used as the offline-aware API surface.
- `react-development` — component patterns and state for the optimistic-update / sync-indicator UI.
- `frontend-performance` — Core Web Vitals, bundle budget, render-path analysis; do not duplicate budgets here.
- `image-compression` — client-side image compression before offline upload queueing.

## Sources

- Workbox — `developer.chrome.com/docs/workbox`; Dexie.js — `dexie.org/docs`
- MDN Service Worker API — `developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API`
- web.dev PWA — `web.dev/progressive-web-apps`; Lighthouse CI — `github.com/GoogleChrome/lighthouse-ci`
- *Building Progressive Web Apps* — Tal Ater (O'Reilly)

Related Skills

api-design-first

8
from peterbamuhigire/skills-web-dev

Use when designing or building HTTP APIs — spec-first OpenAPI workflow, REST conventions, versioning, auth model, rate limiting, idempotency keys, error envelope, and observability notes. Produces the OpenAPI contract plus error/auth/idempotency/observability artifacts that frontend, mobile, security, and reliability skills consume. For endpoint-level security review load `vibe-security-skill`; for GraphQL-specific hardening load `graphql-patterns`.

web-app-security-audit

8
from peterbamuhigire/skills-web-dev

Use when auditing a PHP/JavaScript/HTML web application for security vulnerabilities. Covers configuration, authentication, authorization, input validation, XSS, API security, HTTP headers, and dependency scanning. Produces a severity-rated audit...

vibe-security-skill

8
from peterbamuhigire/skills-web-dev

Use when designing or reviewing security for a web application, API, or multi-tenant SaaS — produces threat model, abuse case list, auth/authz matrix, and secret handling plan; covers OWASP Top 10 2025 and the AI-code-generation blind spots. Neighbours — api-design-first owns auth model fields, deployment-release-engineering owns secret rotation choreography, ai-security and llm-security own model-specific threats.

network-security

8
from peterbamuhigire/skills-web-dev

Use when designing, hardening, or auditing network-layer security for self-managed Debian/Ubuntu SaaS infrastructure — firewalls (nftables/UFW), WAF (ModSecurity + OWASP CRS), VPN (WireGuard, OpenVPN, IPsec), TLS/PKI ops, IDS/IPS (Suricata, Fail2ban), zero-trust, SSH hardening, DDoS mitigation, DNS security. Complements web-app-security-audit (app layer) and cicd-devsecops (secrets/CI).

linux-security-hardening

8
from peterbamuhigire/skills-web-dev

Use when hardening a Debian/Ubuntu server — user/group/sudo hardening, file permission audits, PAM password policy + MFA, AppArmor mandatory access control, auditd system call logging, kernel sysctl hardening, file integrity monitoring (AIDE), rootkit detection (rkhunter/chkrootkit), unattended security patching, GRUB + UEFI + LUKS boot security, and CIS benchmark compliance.

dpia-generator

8
from peterbamuhigire/skills-web-dev

Generate a Data Protection Impact Assessment (DPIA), Uganda DPPA 2019-compliant. Use when producing or reviewing a data protection impact assessment, a privacy impact assessment, when uganda-dppa-compliance flags [DPIA-REQUIRED], or when processing large-scale or sensitive personal data for a new feature.

code-safety-scanner

8
from peterbamuhigire/skills-web-dev

Scan any codebase for 14 critical safety issues across security vulnerabilities, server stability (500 errors), and payment misconfigurations. Use when auditing code before deployment, reviewing AI-generated code for production readiness, or...

world-class-engineering

8
from peterbamuhigire/skills-web-dev

Use when designing, building, reviewing, or upgrading production software systems that must be secure, performant, maintainable, scalable, and user-centered. Apply before writing specs, code, architecture, APIs, databases, mobile apps, SaaS platforms, or ERP systems.

update-Codex-documentation

8
from peterbamuhigire/skills-web-dev

Update project documentation files (README.md, PROJECT_BRIEF.md, TECH_STACK.md, ARCHITECTURE.md, docs/API.md, docs/DATABASE.md, AGENTS.md, docs/plans/NEXT_FEATURES.md) when significant changes occur. MANDATORY at end of each work session to...

skill-writing

8
from peterbamuhigire/skills-web-dev

Use when creating or upgrading skills in this repository. Covers repository-specific frontmatter rules, progressive disclosure, reference-file strategy, validation, and the quality bar required for production-grade engineering skills.

skill-safety-audit

8
from peterbamuhigire/skills-web-dev

Scan new or updated skills for unsafe or malicious instructions (unknown tools, external installers, credential harvesting) before accepting them into the repository.

skill-composition-standards

8
from peterbamuhigire/skills-web-dev

Use when authoring a new skill, normalising an older skill, or reviewing a skill PR — defines the repository-wide house style (frontmatter, decision rules, anti-patterns, references), the output contracts each baseline-skill type must produce, and the input contracts each specialist skill must declare. This is the enforcement spine that makes the repository compose as a system, not a library of linked documents.