joelclaw-web
Update and maintain joelclaw.com — the Next.js web app at apps/web/. Use when writing blog posts, editing pages, updating the network page, changing layout/header/footer, adding components, or fixing anything on the site. Hard content triggers: 'write article about X' (draft in Convex), 'publish article <slug>' (set draft=false + revalidate tags/paths). Also triggers on: 'update the site', 'write a post', 'fix the blog', 'joelclaw.com', 'update network page', 'add a page', 'change the header', or any task involving the public-facing web app.
What this skill does
# joelclaw.com Web App Next.js app at `apps/web/` in the joelclaw monorepo (`~/Code/joelhooks/joelclaw/`). Deployed to Vercel on push to `main`. Dark theme, minimal, system-y aesthetic. ## OPSEC Rules **Never expose real infrastructure details on public pages.** - **Node/host names**: Use Stephen King universe aliases, never real tailnet hostnames - **Ports**: Do not publish port numbers for any service - **Usernames**: Strip `com.joel.` or similar prefixes from service identifiers - **IPs/subnets**: Never show real IP addresses or CIDR ranges - **Brands/models of network gear**: Generalize (e.g. "NAS" not "Synology DS1821+") - **Tailscale**: OK to mention as the mesh VPN product, but no tailnet name or node IPs Current King universe mapping (network page): | Role | Alias | Source | |------|-------|--------| | Mac Mini (control plane) | Overlook | The Shining | | NAS (archive) | Derry | IT | | Laptop (dev machine) | Flagg | The Dark Tower | | Linux server | Blaine | The Dark Tower | | Router (exit node) | Todash | The Dark Tower | ## Content Model (Convex-first) Canonical runtime content lives in Convex `contentResources`: - `article:<slug>` (`type = article`) - `adr:<slug>` (`type = adr`) - `discovery:<slug>` (`type = discovery`) Filesystem content under `apps/web/content/` is seed/backfill material, not runtime source. Runtime read policy: - `apps/web/lib/posts.ts` → Convex-first articles (`article:*`) - `apps/web/lib/adrs.ts` → Convex-first ADRs (`adr:*`) - `apps/web/lib/discoveries.ts` → Convex-first discoveries (`discovery:*`) - Optional local escape hatches (non-production only): - `JOELCLAW_ALLOW_FILESYSTEM_POSTS_FALLBACK=1` (articles) - `JOELCLAW_ALLOW_FILESYSTEM_CONTENT_FALLBACK=1` (ADRs/discoveries) Article fields still mirror MDX frontmatter shape: ```yaml --- title: "Post Title" type: "article" | "essay" | "note" | "tutorial" date: "2026-02-19T11:00:00" # ISO datetime, NOT just date updated: "2026-02-19T14:30:00" # optional, bumps sort position description: "One-liner for cards and meta" tags: ["tag1", "tag2"] draft: true # optional, hides from prod source: "https://..." # optional, for video-notes channel: "Channel Name" # optional, for video-notes duration: "00:42:02" # optional, for video-notes --- ``` **Sorting**: Posts sort by `updated ?? date` descending. Use full ISO datetimes (not bare dates) for deterministic ordering. Setting `updated` bumps a post to the top without changing its original publish date. **Slugs**: Derived from `fields.slug` in Convex (`resourceId = article:<slug>`). ## Hard Trigger Workflow ### `write article about X` 1. Generate slug from title/topic. 2. Upsert `contentResources` with `resourceId = article:<slug>`, `type = "article"`, full MDX body in `fields.content`, and `fields.draft = true`. 3. Set `fields.date` to current ISO timestamp. 4. Return slug + draft preview link (`/<slug>` if draft-visible in dev). ### `publish article <slug>` 1. Read `article:<slug>` from Convex. 2. Patch/upsert with `fields.draft = false` and `fields.updated = now`. 3. Revalidate all affected surfaces via `POST /api/revalidate` with: - tags: `post:<slug>`, `article:<slug>`, `articles` - paths: `/`, `/<slug>`, `/<slug>.md`, `/<slug>/md`, `/feed.xml`, `/sitemap.md` 4. Verify `/`, `/<slug>`, `/<slug>.md`, and `/feed.xml` include the published post. ## Media Embeds **Always embed YouTube videos** in `/cool` discoveries and articles when they add context. Use the `<YouTube id="VIDEO_ID" />` MDX component (available in both `.mdx` articles and `.md` discoveries). Extract the video ID from the URL (`youtube.com/watch?v=VIDEO_ID`). Place embeds near the top of the relevant section, before the prose discussion. ## Writing Voice Use the canonical `joel-writing-style` skill for prose. Key traits: direct, first-person when the claim is actually Joel's, strategic profanity, short paragraphs, bold emphasis, conversational but technical. Never corporate-speak and never fabricate Joel's beliefs or philosophy. ## Design System - **Theme**: Dark (`bg-[#0a0a0a]`), neutral grays, `--color-claw: #ff1493` (hot pink accent) - **Fonts**: Geist Sans (body), Geist Mono (code/data), Dank Mono (code blocks with ligatures) - **Content width**: `max-w-2xl` (672px) — intentionally narrow for reading - **Header**: Single row — claw icon + "JoelClaw" left, nav links + search right. No tagline in header. - **Nav items**: Writing (`/`), Cool (`/cool`), ADRs (`/adrs`), Network (`/network`) - **Active nav**: White text vs neutral-500 for inactive, detected via `usePathname()` - **Search**: ⌘K dialog using pagefind, type-based icons/badges - **Mobile**: Full-screen overlay nav via `MobileNav` component - **Code blocks**: Catppuccin Macchiato theme, rehype-pretty-code - **Sidenotes**: Tufte-style CSS sidenotes (pure CSS, no JS) ## Key Files | File | Purpose | |------|---------| | `app/layout.tsx` | Root layout, fonts, metadata, footer | | `app/page.tsx` | Home page (post list) | | `app/[slug]/page.tsx` | Post detail pages | | `app/adrs/page.tsx` | ADR list | | `app/adrs/[slug]/page.tsx` | ADR detail (strips H1 to avoid duplicate title) | | `app/cool/page.tsx` | Cool/discoveries list | | `app/network/page.tsx` | Infrastructure status page | | `components/site-header.tsx` | Header with active nav (client component) | | `components/mobile-nav.tsx` | Mobile overlay nav | | `components/search-dialog.tsx` | ⌘K search | | `lib/posts.ts` | Article loading from Convex (`article:*`) | | `lib/adrs.ts` | ADR loading from Convex (`adr:*`) | | `lib/discoveries.ts` | Discovery loading from Convex (`discovery:*`) | | `lib/constants.ts` | Site name, URL, tagline | | `lib/claw.ts` | SVG path for claw icon | ## ADR Display Rules - ADR runtime source is Convex (`contentResources` with `resourceId = adr:<slug>`) - Vault sync still updates repo snapshots under `apps/web/content/adrs/` - Project snapshots into Convex with: `bun scripts/seed-adrs-discoveries.ts` - The detail page (`app/adrs/[slug]/page.tsx`) strips the H1 from markdown content because the page already renders the title with ADR number prefix - Regex: `content.replace(/^#\s+(?:ADR-\d+:\s*)?.*$/m, "").trim()` ## Adding a New Post 1. Draft in Convex (`contentResources.upsert`, `resourceId = article:<slug>`, `draft: true`). 2. Use ISO datetime in `date` field. 3. Add images to `apps/web/public/images/<slug>/` if needed and reference `/images/<slug>/...` in MDX. 4. Publish by setting `draft: false`, then revalidate tags + paths (`post:<slug>`, `article:<slug>`, `articles`, `/`, `/<slug>`, `/<slug>.md`, `/<slug>/md`, `/feed.xml`, `/sitemap.md`). 5. Verify route + markdown twin + homepage + feed consistency. Backfill scripts: - `scripts/seed-articles.ts` for article resources - `scripts/seed-adrs-discoveries.ts` for ADR + discovery resources ## Network Page The network page (`app/network/page.tsx`) shows real infrastructure with aliased names. When updating: 1. Check actual system state (`kubectl get pods`, `tailscale status`, `launchctl print`, etc.) 2. Apply OPSEC rules — alias all hostnames, strip ports/IPs/usernames 3. Keep data arrays at top of file for easy updates 4. Status dots: green (Online) with ping animation, yellow (Idle), gray (Offline)
Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.