joelclaw-web

$npx mdskill add joelhooks/joelclaw/joelclaw-web

Manages and updates the joelclaw.com Next.js web app for content, layout, and fixes.

  • Helps with writing blog posts, editing pages, and maintaining the public-facing website.
  • Integrates with Convex for drafting articles and Vercel for deployment on push.
  • Triggers on specific commands like 'write article about X' or 'update the site'.
  • Delivers results by executing changes in the codebase and revalidating paths as needed.

SKILL.md

.github/skills/joelclaw-webView on GitHub ↗
---
name: joelclaw-web
displayName: Joelclaw Web
description: "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."
version: 1.2.0
author: Joel Hooks
tags: [joelclaw, web, nextjs, content, site]
---

# 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)

More from joelhooks/joelclaw

SkillDescription
add-skillCreate new joelclaw skills with the idiomatic process — repo-canonical, symlinked, git-tracked, slogged. Triggers on 'add a skill', 'create skill', 'new skill', 'canonical skill', 'make a skill for', or any request to formalize a process or domain into a reusable skill.
adr-skillCreate and maintain Architecture Decision Records (ADRs) optimized for agentic coding workflows. Use when you need to propose, write, update, accept/reject, deprecate, or supersede an ADR; bootstrap an adr folder and index; consult existing ADRs before implementing changes; or enforce ADR conventions. This skill uses Socratic questioning to capture intent before drafting, and validates output against an agent-readiness checklist.
agent-discovery"Optimize websites, docs, and product surfaces for agent discoverability and operator UX. Use when working on agent SEO/AEO/GEO, crawl policy, markdown or JSON projections, llms.txt, sitemap.md, AGENTS.md guidance, content negotiation, accessibility for browser agents, or any request to make a site easier for pi, OpenCode, Claude Code, ChatGPT, Perplexity, or other agent harnesses to find and use."
agent-loopStart, monitor, and cancel durable multi-agent coding loops via Inngest. Use when the user wants to run autonomous coding workloads, execute a PRD with multiple stories, kick off an AFK coding session, have agents implement features from a plan, or manage running loops. Triggers on "start a coding loop", "run this PRD", "implement these stories", "go AFK and code this", "check loop status", "cancel the loop", "joelclaw loop", or any request for autonomous multi-story code execution.
agent-mail>-
agent-workloads"Compatibility alias for the canonical `workflow-rig` front door. Use when older prompts mention `agent-workloads` or when you need the legacy workload-planning guidance; for new work, load `workflow-rig` first."
clawmail>-
cli-design"Design and build agent-first CLIs with HATEOAS JSON responses, context-protecting output, and self-documenting command trees. Use when creating new CLI tools, adding commands to existing CLIs (joelclaw, slog), or reviewing CLI design for agent-friendliness. Triggers on 'build a CLI', 'add a command', 'CLI design', 'agent-friendly output', or any task involving command-line tool creation."
codex-prompting"Use this skill for any request to trigger, coordinate, or craft prompts for Codex. Use when user says 'send to codex', 'use codex', 'prompt codex', 'ask codex', 'delegate to codex', 'run in codex', or asks for a Codex-first execution handoff."
content-publish"Publish content to joelclaw.com via the Convex-first pipeline. Covers the full lifecycle: draft → review → publish → revalidate → verify. Handles secret leasing, tag conventions, content types (article, tutorial, note, essay), and verification gates. Use when: 'write article about X', 'publish article <slug>', 'draft a tutorial', 'publish this', 'push to convex', or any content publishing task."