# Website Content

> Write, rewrite, or improve content for the site owner's personal website (this repo). Use when the user asks to write a new writing entry, investing article, stock thesis, portfolio journal, trading lesson, project entry, update about page copy, update homepage copy, write frontmatter, draft an essay, add an image, find an image for a post, or use a user-provided image; or says "write content for," "new blog post," "add a project," "update the about page," "write a writing entry," "draft copy for the site," or "write an investing post." This skill covers MDX content authoring, investing frontmatter, page copy, images, and handing shared writing/project entries off to translation.

- Skill: `wchen02/website-content` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add wchen02/website-content`
- Raw SKILL.md: https://api.skillmd.com/api/skills/wchen02/website-content/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: wchen02 (https://skillmd.com/u/wchen02)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/wchen02/website-content

---


# Website Content

Write content for the site in the site owner's voice. The site splits content into two buckets:

- **Site copy**: Locale-scoped JSON only under `content/locales/<locale>/site/*.json`. The app loads these via `src/lib/content.ts`; there is no shared `content/site/` source for home, about, or UI copy. Supported locales: `en`, `es`, `zh` (see `src/lib/locales.ts`).
- **Writing and projects**: MDX entries. For each locale, the app uses *either* that locale’s folder *or* the shared folder—not both. If `content/locales/<locale>/writing` exists, that locale sees only files in it; otherwise it uses `content/writing`. Same for projects: `content/locales/<locale>/projects` or else `content/projects`. Shared entries in `content/writing` and `content/projects` are the canonical default; use the content-translation skill to produce locale-specific copies after creating or updating them.

## Before Writing

1. Read [references/voice-guide.md](references/voice-guide.md) to internalize the site owner's voice (customize that guide for your own voice if you forked this template).
2. Read [references/content-schemas.md](references/content-schemas.md) for the content type being written.
3. If updating homepage/about or other page-level copy, also read [references/page-patterns.md](references/page-patterns.md).
4. For every new writing, investing, or project entry, read [references/images.md](references/images.md) and create a 16:9 raster feature image (`.jpg` or `.png`, not `.svg`) before finalizing the entry.

## Content Types

### Writing Entry (Essay / Quick Thought)

Choose the file location based on whether the piece is shared or locale-specific:

- Default/shared content: create or edit `content/writing/[slug].mdx`
- Locale-specific translation or locale-only piece: create or edit `content/locales/<locale>/writing/[slug].mdx`

Prefer the locale-specific path when the user explicitly wants translated or locale-specific writing. Use the shared path when the content is the canonical default entry consumed as fallback content.

After creating or updating a shared entry in `content/writing/[slug].mdx`, immediately read `.agents/skills/content-translation/SKILL.md` and use it to translate the finished entry into every other locale discovered under `content/locales`.

**Frontmatter requirements** (see `content-schemas.md` for full spec):
- `title`: evocative but honest; not clickbait
- `publishDate`: ISO 8601 datetime
- `theme`: reuse existing themes like `Architecture`, `Developer Tools`, `Infrastructure`, or `Workflow` when possible
- `tags`: 2-4 lowercase, specific tags
- `ogImage`: absolute site URL for the feature image, for example `https://wchen.ai/writing/<slug>/feature.jpg`
- `featured`: `true` only for foundational or especially important pieces
- `draft`: `true` for work-in-progress

For investing content, use `theme: "Investing"` and add the `investing` object described below.

**Body guidelines:**
- 200-1500 words
- No H1; the title renders from frontmatter
- Structure: provocation -> problem reframe -> position -> closing conviction
- Keep paragraphs short: 1-3 sentences
- Use *italics* sparingly for emphasis on key reframes
- End with a forward-looking conviction, not a summary
- Feature image: always create or source one as a 16:9 `.jpg` or `.png` (not `.svg`), save it under `public/writing/<slug>/`, set `ogImage`, and include the Markdown image in the body near the opening where it supports the narrative. For **sourcing royalty-free images** or **using a user-provided image**, read [references/images.md](references/images.md). Mechanics (paths, syntax) in content-schemas.md.

**Slug**: derive from the core concept in lowercase kebab case, for example `context-aware-agents.mdx`.

### Investing Entry (Stock Thesis / Portfolio Journal / Learning Note)

Investing entries are writing entries with `theme: "Investing"`. They render under `/investing` and use investing-specific cards. Choose the entry kind before writing:

- Decision-oriented entries: `stock-thesis`, `portfolio-journal`, `watchlist`, `postmortem`, `process-note`
- Lightweight entries: `learning`, `advice`, `habit`

Use a `Decision Record` only when the entry makes or reviews an investment decision. Set `showDecisionRecord: true` and include thesis/invalidation/risk fields for stock theses, watchlist notes, postmortems, or decision-heavy portfolio journals. For learning, advice, habit, or general process notes, keep `showDecisionRecord` omitted or `false` and provide only `kind` plus `summary` unless more fields genuinely help.

**Required investing frontmatter minimum:**

```yaml
theme: "Investing"
tags: ["investing", "portfolio", "public-journal"]
investing:
  kind: "learning" # stock-thesis | portfolio-journal | watchlist | postmortem | process-note | learning | advice | habit
  summary: "One concise sentence describing what this note helps inspect."
```

**Decision-record frontmatter pattern:**

```yaml
investing:
  kind: "stock-thesis"
  showDecisionRecord: true
  ticker: "AAPL" # optional
  company: "Apple" # optional
  status: "watching" # watching | open | closed | review
  direction: "long" # long | short | neutral
  horizon: "6-18 months"
  disclosure: "No current position"
  summary: "The setup in one sentence."
  thesis: "What I believe the market is missing."
  invalidation: "What evidence would prove the thesis wrong."
  catalysts:
    - "Upcoming product cycle"
  decisionTriggers:
    - "Add if valuation compresses while thesis remains intact."
  risk: "The main way this can go wrong."
  lastReviewed: "2026-05-09T02:45:00Z"
```

**Investing body guidelines:**
- Always include a short disclaimer in the body for public stock/trading posts: `Nothing here is financial advice.`
- For stock theses: answer `Why now?`, `What is the market missing?`, `What is already priced in?`, `What would make me add, trim, exit, or avoid?`
- For learning/advice/habit posts: focus on the behavior, mistake pattern, or operating rule; do not force ticker, status, thesis, or invalidation fields.
- Avoid performance theater. Prefer decision quality, thesis integrity, review cadence, and mistake taxonomy over return bragging.
- Feature images for investing posts should use the high-contrast editorial thumbnail style in [references/images.md](references/images.md): bold headline energy, market charts, red/green risk contrast, and clear visual framing around the trade, watchlist, or investing behavior.

### Project Entry

Choose the file location based on whether the project entry is shared or locale-specific:

- Default/shared content: create or edit `content/projects/[slug].mdx`
- Locale-specific translation or locale-only entry: create or edit `content/locales/<locale>/projects/[slug].mdx`

Prefer the locale-specific path when the user asks for a translated or locale-only project entry. Use the shared path for the canonical default entry consumed as fallback content.

After creating or updating a shared entry in `content/projects/[slug].mdx`, immediately read `.agents/skills/content-translation/SKILL.md` and use it to translate the finished entry into every other locale discovered under `content/locales`.

**Frontmatter requirements** (see `content-schemas.md` for full spec):
- `motivation`: start with `I wanted to...` or `I needed...`
- `problemAddressed`: state the broken status quo vividly and concretely
- `learnings`: honest and specific; optional
- `type`: at least one of `app`, `agent`, `experiment`, `skill`, `library`, or `tool`
- `status`: `active`, `archived`, or `in-progress`
- `ogImage`: absolute site URL for the feature image, for example `https://wchen.ai/projects/<slug>/hero.jpg`

**Body guidelines:**
- Start with a brief context paragraph, then use structured H2 sections
- Common sections: `How it works`, `The Vision`, `Architecture`, `Results`
- Include code snippets only when they add real clarity
- Focus on narrative arc, not feature lists
- Feature image: always create or source one as a 16:9 `.jpg` or `.png` (not `.svg`), save it under `public/projects/<slug>/`, set `ogImage`, and include the Markdown image near the opening. For **sourcing royalty-free images** or **using a user-provided image**, read [references/images.md](references/images.md). Mechanics (paths, syntax) in content-schemas.md.

### Homepage Copy

Do not edit `src/app/page.tsx`; it is a locale redirect shell. Update locale content instead:

- Hero and section copy: `content/locales/<locale>/site/home.json`
- Shared identity and contact labels used on the page: `content/locales/<locale>/site/profile.json`

See `page-patterns.md` for the rendered section structure in `src/app/[locale]/page.tsx`.

Key constraints:
- Hero: intro plus two short supporting paragraphs
- Current Focus: short description plus two paragraphs
- A new visitor should understand who the site owner is, what they are building, and how to reach them within 15 seconds
- For route values like the about link, store the unprefixed path such as `/about`; localization is applied at runtime

### About Page Copy

Do not edit `src/app/about/page.tsx`; it is a locale redirect shell. Update locale content instead:

- Page copy: `content/locales/<locale>/site/about.json`
- Shared CTA/contact strings used near the page: `content/locales/<locale>/site/profile.json`

See `page-patterns.md` for the rendered section structure in `src/app/[locale]/about/page.tsx`.

Key constraints:
- Philosophy: 3 paragraphs on core beliefs about building software
- Interests: 4 cards, each with a strong H3 topic and 1-2 sentence description
- Background: 3 paragraphs; tell the career arc as a story, not a resume
- Principles: 3 items, each with a short imperative phrase plus a one-sentence expansion

### Shared Site Copy

All editable site copy lives under `content/locales/<locale>/site/`. Use these files when the request is not an MDX entry:

- `content/locales/<locale>/site/profile.json`: site identity, metadata defaults, nav labels, CTA copy, contact copy, not-found copy
- `content/locales/<locale>/site/home.json`: homepage hero and section copy
- `content/locales/<locale>/site/about.json`: about-page copy
- `content/locales/<locale>/site/newsletter.json`: newsletter email subjects, previews, button labels, recurring digest copy
- `content/locales/<locale>/site/ui.json`: shared UI strings (language switcher, theme toggle, share button, search, writing/project labels)
- `content/locales/<locale>/site/forms.json`: contact/newsletter form labels, placeholders, button copy
- `content/locales/<locale>/site/system.json`: validation, API, and fallback system messages

Do not edit `content/site/newsletter-state.json` for copy—it is managed by the recurring-newsletter build script and tracks which slugs have been sent.

## Translation Handoff

Use the translation handoff only for shared MDX entries:

- Trigger it after finishing `content/writing/[slug].mdx`
- Trigger it after finishing `content/projects/[slug].mdx`
- Do not trigger it for `content/locales/<locale>/...` source files
- Do not trigger it for homepage, about, newsletter, UI, forms, or system JSON copy

## Audio Publish Handoff

If the task creates or updates writing, investing, or project content that should have audio, do not assume generated audio is committed to Git.

Use this manual handoff after the content and any translations are finished:

1. Run `pnpm audio:generate` to create local audio artifacts under `public/audio/` (`writing`, `investing`, or `projects` by content type).
2. Verify the affected page(s) locally if audio playback/highlighting matters to the task.
3. Run `pnpm audio:publish` to upload the generated assets and `audio-manifest.json` to R2.
4. Remind the user that production builds need `AUDIO_SOURCE=r2` and `R2_AUDIO_PUBLIC_BASE_URL` configured.

Do not silently generate or upload audio as part of every content-writing task unless the user explicitly asks for that step.

## Newsletter digest images

Every new writing, investing, and project entry should set `ogImage` to its feature image. When adding or changing `ogImage` on writing/project MDX (thumbnails in recurring digests), see **`docs/newsletter-digest-images.md`**. The site serves files from `public/`; CI mirrors eligible images to R2 with `pnpm content-images:publish` so digest emails do not 404 before deploy.

## Voice Checklist

Run this check against every piece of content before finalizing:

- [ ] First person throughout when the content is narrative copy
- [ ] No corporate filler like `leverage`, `synergy`, `innovative`, `cutting-edge`
- [ ] No hedging language like `kind of`, `sort of`, `maybe`
- [ ] No exclamation points unless the user explicitly wants that tone
- [ ] No emojis in body copy
- [ ] Every paragraph advances one idea
- [ ] Paragraphs are 1-3 sentences when writing prose
- [ ] The ending lands on conviction, not recap
- [ ] New writing, investing, and project entries include a 16:9 `.jpg` or `.png` feature image file, body image reference, meaningful alt text, and `ogImage`
- [ ] Frontmatter or JSON shape matches the active schema (Zod in `src/lib/schemas.ts`; invalid content fails the build; see also `scripts/validate-links.ts`, `validate-theme-descriptors.ts`, `validate-metadata.ts`)

## File Placement

```text
public/
  writing/[slug]/                         -> Image assets for writing entry [slug]; feature images use 16:9 .jpg/.png, e.g. /writing/<slug>/feature.jpg
  projects/[slug]/                        -> Image assets for project entry [slug]; feature images use 16:9 .jpg/.png, e.g. /projects/<slug>/hero.png
content/
  writing/[slug].mdx                      -> Shared/default writing entries (canonical)
  projects/[slug].mdx                     -> Shared/default project entries (canonical)
  site/
    newsletter-state.json                 -> Script-managed; do not edit for copy
  locales/<locale>/site/                   -> Locales: en, es, zh
    profile.json                          -> Site identity, nav, CTA, metadata, contact, not-found
    home.json                             -> Homepage copy
    about.json                            -> About-page copy
    newsletter.json                       -> Newsletter email and flow copy
    ui.json                               -> Shared UI strings (share, search, theme, etc.)
    forms.json                            -> Form labels and placeholders
    system.json                           -> Validation and system messages
  locales/<locale>/writing/[slug].mdx     -> Locale-specific writing (used when present instead of shared)
  locales/<locale>/projects/[slug].mdx    -> Locale-specific projects (used when present instead of shared)
src/app/[locale]/
  page.tsx                                -> Homepage renderer
  about/page.tsx                          -> About-page renderer
```

