# Specs for everettroeth.com Living documentation for proposed and in-progress site changes. Each spec is one feature, written before implementation, updated as things ship. - Site: https://everettroeth.com - Site index: https://everettroeth.com/llms.txt - Full essay corpus: https://everettroeth.com/llms-full.txt - Source: https://github.com/everettroeth/ev-site/tree/main/docs/specs ## Index - **SPEC-001** text-wrap balance and pretty [shipped, effort S] - **SPEC-002** code block copy button and language label [shipped, effort M] - **SPEC-003** RSS feed at /feed.xml [shipped, effort S] - **SPEC-004** prefetch Continue-Reading targets [shipped (covered by Next.js Link default prefetch + SPEC-005 Speculation Rules), effort S] - **SPEC-005** Speculation Rules API for instant navigation [shipped, effort M] - **SPEC-006** per-essay Open Graph image [shipped, effort M] - **SPEC-007** mobile responsive pass [shipped (verified 375 and 768), effort M] - **SPEC-008** orrery animation IntersectionObserver gate [shipped, effort S] - **SPEC-009** external link preview fetcher [shipped, effort L] - **SPEC-010** /threads topology navigation [shipped (early launch with 4 essays; will grow as content lands), effort L] - **SPEC-011** DESIGN.md as single source-of-truth [shipped, effort M] - **SPEC-012** agent accessibility surface [shipped (specs route + llms.txt update); MCP card and public Skill deferred, effort M] --- --- id: SPEC-001 title: text-wrap balance and pretty status: shipped effort: S frontier: CSS text-wrap balance, CSS text-wrap pretty related: ADR-0005 last_revised: 2026-05-13 --- # SPEC-001: text-wrap balance and pretty ## Goal Better line breaks across headlines and body prose without manually inserting non-breaking spaces. Headlines balance (no orphans, even line lengths). Long-form prose uses `pretty` for single-orphan avoidance and improved hyphenation hints. ## Current state Default `text-wrap: wrap`. Headlines occasionally produce widow words on narrow viewports. Body prose has ragged last lines. ## Implementation outline In `app/globals.css`: - `text-wrap: balance` on `article > header h1`, `article > header p` (subtitle), `.prose-editorial h2`, `.prose-editorial h3` - `text-wrap: pretty` on `.prose-editorial p`, `.prose-editorial li` - Add under a labeled comment block `/* === typography polish === */` ## Acceptance criteria - Headlines on 375-1100px viewports no longer leave a single word on the last line - Body paragraphs avoid orphaned final words where the rendering engine supports it - No layout shift, no perf regression - `pnpm typecheck && pnpm test` clean - Browser-verified: visit a-letter-to-the-room at 375 and 1280, observe headline line breaks ## Frontier features - `text-wrap: balance` (CSS Text Module Level 4, baseline 2024 in Chrome/Edge/Safari/Firefox) - `text-wrap: pretty` (Chrome 117+, gradual rollout; falls back to `wrap`) ## Edge cases - Long headlines may take more layout passes; UA implementations cap balance at about six lines - Firefox/Safari fall back to `wrap` for `pretty` until support lands; acceptable --- --- id: SPEC-002 title: code block copy button and language label status: shipped effort: M frontier: Clipboard API, ARIA live regions, rehype-pretty-code data attrs related: ADR-0005 last_revised: 2026-05-13 --- # SPEC-002: code block copy button and language label ## Goal Every code block in essays carries a quiet copy button in the corner and a small monospace language label. Clicking copy puts the code on the clipboard and pulses a "copied" affordance for ~1.5s. ## Current state Code blocks render via `rehype-pretty-code` with syntax highlighting. No copy affordance. Language is implicit. ## Implementation outline - New `components/mdx/code-block.tsx` client component wraps `
` in a relative container
- Override `pre` in `mdx-components.tsx`: `pre: (props) => `
- Read `data-language` from the wrapped `
` for the label
- Button uses `navigator.clipboard.writeText(preRef.current?.textContent ?? "")`
- `aria-live="polite"` region announces "copied"
- CSS in `globals.css` under `/* === code block copy === */`
- Position: language label top-left, copy button top-right; both tiny, low opacity, full opacity on hover/focus

## Acceptance criteria

- Copy button visible in top-right of every `
` rendered from MDX
- Click copies code text exactly as displayed
- "Copied" label appears for 1.5s then reverts to "copy"
- Language label visible in top-left when `data-language` present
- Mobile: button tappable (min 40x40 hit area, button itself can stay smaller via padding tricks)
- No console errors; existing syntax highlighting unchanged
- Browser-verified: copy from a code block, paste into shell, content matches source

## Frontier features

- Clipboard API (`navigator.clipboard`)
- ARIA live region
- `:has()` for parent-aware focus styles (optional)

## Edge cases

- Clipboard API requires secure context (HTTPS or localhost); works in Vercel and dev
- Old browsers without Clipboard API: button still renders, click is a no-op silently
- Very long code blocks: button stays in top-right via sticky positioning if needed


---

---
id: SPEC-003
title: RSS feed at /feed.xml
status: shipped
effort: S
frontier: Next.js App Router route handlers, Atom 1.0
related: ADR-0003
last_revised: 2026-05-13
---

# SPEC-003: RSS feed at /feed.xml

## Goal

Readers can subscribe to new essays via any RSS or Atom reader. Feed lives at `/feed.xml`.

## Current state

No feed. Readers rely on direct visits or social channels.

## Implementation outline

- New `app/feed.xml/route.ts` exporting a `GET()` handler
- Iterate `getAllArticles()`, excluding `unlisted`
- Render Atom 1.0 XML with `` per article: id (canonical URL), title, link, summary (description or preview), updated (revised or date), published, author
- Cache-Control: `public, max-age=300, s-maxage=3600`
- Add `` to root layout ``

## Acceptance criteria

- `GET /feed.xml` returns 200 with `application/atom+xml; charset=utf-8`
- Validates against the W3C Feed Validator
- Auto-discovery: reader apps find it via ``
- HTML in summaries is properly escaped (no broken CDATA)
- Dates in RFC 3339

## Frontier features

- Atom 1.0 (preferred over RSS 2.0 for richer metadata)
- Next.js Route Handlers with static rendering

## Edge cases

- Unlisted essays excluded
- Essays without `description` use the derived `preview` (already in articles meta)
- XML entities escaped in title and summary


---

---
id: SPEC-004
title: prefetch Continue-Reading targets
status: shipped (covered by Next.js Link default prefetch + SPEC-005 Speculation Rules)
effort: S
frontier: link rel=prefetch, Next.js Link prefetch
related: SPEC-005
last_revised: 2026-05-13
---

# SPEC-004: prefetch Continue-Reading targets

## Goal

The next-essay HTML and JS are warmed in the browser cache before the user clicks. Acts as the Firefox/Safari fallback when SPEC-005 (Speculation Rules) isn't supported.

## Current state

Next.js Link prefetches automatically when links enter the viewport. This SPEC formalizes and supplements that.

## Implementation outline

Two options:

- **Option A** (lighter): rely on default `` behavior (already in `components/continue-reading.tsx`). Verify in DevTools that Continue-Reading targets prefetch on viewport entry.
- **Option B** (explicit): emit `` for the three related slugs in `app/writing/[slug]/page.tsx` head.

Lean A first; add B only if Speculation Rules prerender (SPEC-005) doesn't cover the gap.

## Acceptance criteria

- DevTools Network shows prefetch entries for Continue-Reading targets when they scroll into view
- Click on a prefetched link reuses the cache (no duplicate fetch)
- Bandwidth budget unchanged for users who don't click through

## Frontier features

- `` baseline
- Next.js automatic Link prefetch

## Edge cases

- Respect `prefers-reduced-data` if it ships as a CSS media query and we ever support data saver


---

---
id: SPEC-005
title: Speculation Rules API for instant navigation
status: shipped
effort: M
frontier: Speculation Rules API, prerender, eagerness levels
related: SPEC-004, ADR-0006
last_revised: 2026-05-13
---

# SPEC-005: Speculation Rules API for instant navigation

## Goal

When a reader clicks a Continue-Reading link, the next page is already fully rendered in the browser. No spinner, no load. The click resolves instantly. Falls back to default navigation in unsupported browsers.

## Current state

Next.js prefetches link HTML/JS. The click still requires hydration and paint. Speculation Rules `prerender` goes further: the browser runs the next page in a hidden tab, ready to swap in on activation.

## Implementation outline

- In `app/writing/[slug]/page.tsx`, after `getRelated`, build:
  ```ts
  const rules = {
    prerender: [{
      urls: related.map((a) => `/writing/${a.slug}`),
      eagerness: "moderate"
    }],
  };
  ```
- Render `