Files
joelhooks__joelclaw/docs/web.md

92 lines
6.9 KiB
Markdown

# joelclaw Web (`apps/web`)
Next.js 16 App Router site for `joelclaw.com`.
## Agent-oriented surfaces
- Human HTML pages: route `page.tsx` trees under `apps/web/app/`
- Markdown exports: `https://joelclaw.com/{slug}.md` (rewritten to `app/[slug]/md/route.ts`) and `https://joelclaw.com/sitemap.md`
- API discovery + machine endpoints: `https://joelclaw.com/api`, `https://joelclaw.com/api/search`, `https://joelclaw.com/api/pi-mono`, `https://joelclaw.com/api/docs`, `https://joelclaw.com/feed.xml`, `https://joelclaw.com/llms.txt`
## Post-content revalidation contract
Post-like Convex content updates (`article`, `tutorial`, `essay`, `note`) must invalidate **both** the human page and the markdown projection. Revalidating only `/${slug}` is insufficient because the public markdown twin is served from a separate route handler and can stay stale at the edge.
Required tags:
- `post:<slug>`
- `article:<slug>`
- `articles`
Required paths:
- `/`
- `/${slug}`
- `/${slug}.md`
- `/${slug}/md`
- `/feed.xml`
- `/sitemap.md`
## Public pi-mono corpus surface
- `apps/web/app/api/pi-mono/route.ts` is the discovery endpoint for the public `pi_mono_artifacts` corpus.
- `apps/web/app/api/search/route.ts` now accepts `collection=pi_mono_artifacts` on the public, Upstash-rate-limited search surface.
- The discovery payload at `/api/pi-mono` includes:
- corpus/search usage examples for `pi_mono_artifacts`
- current install steps for the public `contributing-to-pi-mono` skill
- current install steps for the public extension repo `joelhooks/contributing-to-pi-mono`
- Public search returns external GitHub URLs directly for pi-mono artifacts, so top-result next actions can point straight at the source issue/PR/comment/commit/release.
## `/cool` content routing
- `apps/web/app/cool/[slug]/page.tsx` resolves **tutorial posts first** at Convex slug `cool/<slug>`.
- If no tutorial exists for that slug, the route falls back to the legacy `discovery:<slug>` record.
- This keeps `/cool/...` tutorial URLs working even when an older discovery stub still exists for the same topic.
## ADR route aliases
- Vault and Convex keep the canonical ADR source slug from the filename, e.g. `0217-event-routing-queue-discipline`.
- Public web routes use the short alias form `/adrs/adr-0217`, derived from the ADR number.
- `apps/web/lib/adrs.ts` resolves `adr-####` (and bare `####`) back to the canonical Convex resource ID, so content sync does **not** need duplicate ADR records.
- `apps/web/app/adrs/[slug]/page.tsx` permanently redirects legacy full-slug ADR URLs to the short alias route while keeping review/live-update lookups pinned to the canonical Convex resource ID.
## Post display rules
- `apps/web/app/[slug]/page.tsx` renders the post title in the page header from Convex metadata.
- The markdown/MDX body must not render a second top-level H1 below that header.
- The post renderer strips a markdown H1 before passing content into `MDXRemote`.
- Regex: `content.replace(/^#\s+.*$/m, "").trim()`
- `apps/web/app/[slug]/opengraph-image.tsx`, `apps/web/app/[slug]/agent-md/route.ts`, and `apps/web/app/[slug]/md/route.ts` must **not** export `generateStaticParams()`. Let those dynamic-slug routes resolve per-request; build-time slug enumeration was crashing Vercel during page-data collection on Convex-backed reads.
- `apps/web/app/[slug]/opengraph-image.tsx` must also degrade gracefully if `getPost()` throws, rendering a slug-based generic OG image instead of taking the whole deploy down.
- Server-side Convex readers (`apps/web/lib/posts.ts`, `apps/web/lib/adrs.ts`, `apps/web/lib/discoveries.ts`, and `apps/web/app/network/page.tsx`) must resolve the Convex URL lazily inside `getConvexClient()`, strip literal `\\n` suffix pollution from env values, and call `setAdminAuth(process.env.CONVEX_DEPLOY_KEY)` when that key is present. Production build-time reads are not reliably public.
- During `phase-production-build`, if `CONVEX_URL`/`NEXT_PUBLIC_CONVEX_URL` is missing, Convex-backed static generation must degrade to empty lists / `null` lookups instead of throwing. This keeps Vercel deploys alive without restoring filesystem content as a canonical read path. Runtime requests should still fail loudly when Convex env is missing.
- Cache Components route validation rejects empty `generateStaticParams()` results, so slug routes use a build-only placeholder param when Convex-backed slug lists are empty. The placeholder must immediately resolve to `notFound()` and must never become a real content slug.
- Client-side Convex providers used by `(convex)` owner routes or realtime islands must no-op or render a fallback shell when `NEXT_PUBLIC_CONVEX_URL` is absent during prerender; never instantiate `ConvexReactClient` with an empty address.
## CLAWMAIL view-source convention
Regular HTML pages include a deterministic head marker script labeled `CLAWMAIL` from the root shell:
- Component: `apps/web/components/clawmail-source-comment.tsx`
- Mounted in: `apps/web/app/layout.tsx`
- Placement: first explicit child inside `<head>` in `app/layout.tsx` (before JSON-LD script)
- Marker form: `<script id="clawmail-agent-prompt" type="text/plain">...</script>`
The marker is intended for agents using **View Source** and includes:
1. A start path (`https://joelclaw.com/sitemap.md`) for route and markdown endpoint discovery
2. API discovery instructions (`https://joelclaw.com/api` → `https://joelclaw.com/api/search` and `https://joelclaw.com/api/docs`)
3. Markdown endpoint instructions (`https://joelclaw.com/{slug}.md`) with `Accept: text/markdown`
4. Plain-text hint endpoint instructions (`https://joelclaw.com/llms.txt`) with `Accept: text/plain`
5. Explicit `Content-Type` verification requirements for markdown/plain/json responses
6. A wrong-endpoint guard (`Content-Type: text/html` means fallback HTML / wrong route)
7. A concise claw-themed ASCII art header at the top of the marker payload for quick human scanning in raw source
Required content-type checks called out in the marker:
- `GET https://joelclaw.com/sitemap.md` with `Accept: text/markdown, text/plain;q=0.9` → expect `Content-Type` starting with `text/markdown` (`text/markdown; charset=utf-8`)
- `GET https://joelclaw.com/{slug}.md` with `Accept: text/markdown` → expect `Content-Type` starting with `text/markdown` (`text/markdown; charset=utf-8`)
- `GET https://joelclaw.com/llms.txt` with `Accept: text/plain` → expect `Content-Type` starting with `text/plain` (`text/plain; charset=utf-8`)
- `GET https://joelclaw.com/api` (and `/api/search`, `/api/docs`) with `Accept: application/json` → expect `Content-Type` starting with `application/json`
- If response `Content-Type` is `text/html`, treat it as fallback/wrong route and retry with the correct endpoint + `Accept` header.
This convention is intentionally scoped to the HTML layout only. Markdown/text route handlers (for example `https://joelclaw.com/sitemap.md`, `https://joelclaw.com/{slug}.md`, `https://joelclaw.com/llms.txt`, and API routes) are not wrapped by the layout and do not inject the CLAWMAIL head marker.