all writing

writings for discussion

how WFDs work, when to write one, the states a document can be in, frontmatter format, and the markdown features available.

writing things down is important. it forces you to think clearly about what you're building, what you've learned, and what you still don't understand. a half-formed idea in your head stays half-formed until you try to put it into sentences.

WFD (Writing for Discussion) is how i capture that process. it's a document format inspired by Oxide's RFD system, which itself draws from the original spirit of the IETF Request for Comments:

Notes are encouraged to be timely rather than polished. Philosophical positions without examples or other specifics, specific suggestions or implementation techniques without introductory or background explication, and explicit questions without any attempted answers are all acceptable. The minimum length for a note is one sentence.

the bar for writing a WFD is intentionally low. if you can explain it in a sentence, that's enough to start. polish comes later, or not at all.

when to write a wfd

anything worth remembering is worth writing down. some examples:

  • debugging a problem that took more than an hour
  • learning something that surprised you
  • building something and wanting to document the decisions
  • an opinion about tools, patterns, or process
  • something you'd want to reference later

there's no approval process. no review board. no minimum length beyond one sentence. if the idea matters to you, write it down.

writing conventions

don't repeat what another WFD already said

if something has been covered in a previous WFD, reference it instead of restating it. a link to the relevant WFD is always better than a paraphrase. this keeps documents focused and avoids content that drifts out of sync when the original gets updated.

drafts should use arguments for/against

when a WFD is in draft and hasn't reached a decision yet, lay out the tension explicitly:

markdown
arguments against:

- reason one
- reason two

arguments for:

- reason one
- reason two

this makes the open question visible. the reader can see exactly what's unresolved and why a decision hasn't been made yet. see WFD 18 and WFD 19 for examples.

wfd metadata and state

every WFD starts with YAML frontmatter:

yaml
---
number: 17
title: "writings for discussion"
updated: "2026-02-11T00:35:00"
state: "published"
labels: ["process"]
excerpt: "short description of what this document covers."
---

the fields:

field required description
number yes sequential WFD number. WFD 1, WFD 2, WFD 17. no gaps required.
title yes lowercase. short. descriptive.
updated yes ISO 8601 datetime with time. the only timestamp that matters.
state yes current lifecycle state (see below).
labels yes array of tags. keep it to three or fewer. use [] for none.
excerpt yes one or two sentences. shows up in search results and the listing page.

states

a WFD can be in any of these states:

state meaning
draft placeholder. not ready for anyone to read.
discussion actively being written or revised. feedback welcome.
published the idea is formed and the document says what it means to say.
committed i'm committing to this post. it's not going to change significantly.
living actively maintained. will be kept up to date on a best effort basis.
legacy written before the WFD system existed. migrated from the old blog format. may not follow current conventions.
abandoned the idea didn't pan out. kept for the record.

unlike Oxide's RFD process, there's no branch-per-document workflow, no pull request for discussion, and no formal review. documents are markdown files in a content directory. state changes are just a frontmatter edit.

numbering

WFDs are numbered sequentially. WFD 1, WFD 2, WFD 17. no dashes, no leading zeros in the display. the number is permanent. if a WFD is abandoned, the number stays taken.

updated, not created

there is no creation date. only updated. if you come back six months later and rewrite half the document, the timestamp reflects that. the creation date is noise. what matters is when the document was last touched.

the markdown engine

the rendering pipeline is custom. i didn't want to use an off-the-shelf markdown engine like MDX, Contentlayer, or Markdoc because they all impose opinions about how content should be structured, and i wanted full control over what syntax is available and how it renders.

the engine is built on remark and rehype for the base markdown-to-HTML conversion, with shiki for syntax highlighting. everything else is custom parsing on top.

the parser works in a single pass over the raw markdown lines. it maintains state machines for code blocks, admonitions, HTML blocks, and media groups. each line is checked against a series of regex patterns in order:

  1. if we're inside a fenced code block, accumulate lines until the closing fence.
  2. if we're inside an HTML block (<details>, <div>, etc.), pass lines through to remark as-is.
  3. if we're inside an admonition (> [!NOTE]), accumulate continuation lines that start with >.
  4. check for code block openings, heading patterns (for TOC extraction), admonition starts, component directives, and media lines.
  5. if a line matches ![alt](url), @gif[alt](url), @video[caption](url), or @embed[title](url), it's collected into a media group.
  6. consecutive media lines without a non-empty separator become a single carousel.
  7. everything else is accumulated as text and flushed through remark when a non-text block is encountered.

the output is an array of typed content blocks (text, code, media, mermaid, admonition, component) that React components consume directly. there's no intermediate AST transformation or plugin chain beyond what remark and rehype provide for the text blocks.

this means adding new syntax is straightforward: add a regex to parseMediaLine or a new block type to the line scanner, add a corresponding React component, and it's done. the @gif, @video, @embed, and @component syntaxes were all added this way.

the tradeoff is that the parser is imperative and stateful rather than declarative. it's not as elegant as a proper AST visitor pattern, but it's simple to debug and easy to extend. every feature in this document was added in under an hour.

writing format

WFDs are written in markdown with some extensions. everything below is available in any WFD.

text

standard markdown: **bold**, *italic*, ~~strikethrough~~, `inline code`, [links](url).

bold text, italic text, bold and italic, strikethrough, inline code, a link

headings

use ## through ####. these are extracted for the table of contents sidebar on desktop and the bottom sheet on mobile. # is reserved for the document title.

code blocks

fenced with triple backticks. specify the language for syntax highlighting.

markdown
```typescript
const x: number = 42;
```
typescript
const x: number = 42;

tables

standard GFM tables. they render with rounded borders and a semi-transparent background.

markdown
| header | header |
|--------|--------|
| cell   | cell   |
feature syntax example
bold **text** bold
italic *text* italic
code `code` code

blockquotes

markdown
> quoted text goes here.

this is a blockquote. it can span multiple lines.

admonitions

GitHub-style alerts with five types:

markdown
> [!NOTE]
> useful information.

> [!TIP]
> helpful advice.

> [!IMPORTANT]
> key information.

> [!WARNING]
> urgent information.

> [!CAUTION]
> risk of negative outcome.

Custom titles:

markdown
> [!NOTE/Custom Title]
> content with a custom title.

images

markdown
![alt text](url)

consecutive images on adjacent lines automatically group into a scrollable carousel. images display at their natural aspect ratio with a max-height constraint.

mountain vista at dawn
mountain vista at dawn
forest path in autumn
forest path in autumn
coastal cliffs at sunset
coastal cliffs at sunset

gifs

markdown
@gif[description](url)

videos

markdown
@video[caption](url)
markdown
@embed[title](url)

wfd embeds

reference another WFD by slug or number to render an inline preview card with its title, excerpt, state, labels, and reading time.

markdown
@wfd[exploring-liquid-glass]
@wfd[14]
exploring liquid glass for the webapple introduced liquid glass at WWDC 2025. i want to understand how it actually works and figure out if it's possible to recreate it on the web. · 11 min read
pixel loaders are coolI got tired of boring spinners so I made a 3x3 grid that lights up based on adjacency rules. It's way more fun than it should be. · 5 min read

mermaid diagrams

markdown
```mermaid
graph TD
    A[Start] --> B{Decision}
    B -->|Yes| C[Done]
    B -->|No| D[Retry]
```

supports flowcharts, sequence diagrams, entity relationship diagrams, and anything else mermaid supports.

Loading diagram...
Loading diagram...

collapsible sections

markdown
<details>
<summary>click to expand</summary>

hidden content here. supports any markdown.

</details>

hidden content goes here. supports any markdown:

  • lists work fine
  • bold text renders correctly
  • even code: const x = 1

react components

markdown
@component[ComponentName]
@component[ComponentName]({"prop": "value"})

embeds interactive React components directly in the document. components are registered in BlogComponents.tsx and render inside a macOS-style frame.

pixel-loader

available components: PixelLoader, PixelLoaderDemo, PixelLoaderAdjacencyDemo, ThreeDemo

three.js scenes

Three.js scenes are a special case of react components. they render interactive 3D scenes inline using @react-three/fiber and @react-three/drei, dynamically imported with SSR disabled since WebGL requires a browser context.

markdown
@component[ThreeDemo]
@component[ThreeDemo]({"height": 400})
three.js-scene
loading scene...

the ThreeDemo component is a showcase. for actual WFD content, create purpose-built scene components in src/components/blog-demos/, register them in BlogComponents.tsx with next/dynamic and { ssr: false }, and reference them with @component[YourScene].

the pattern for adding a new Three.js component:

  1. create the component in src/components/blog-demos/YourScene.tsx with "use client"
  2. use @react-three/fiber's Canvas and @react-three/drei for helpers (controls, materials, environments)
  3. register in BlogComponents.tsx:
typescript
const YourScene = dynamic(() => import("./blog-demos/YourScene"), {
  ssr: false,
  loading: () => <ComponentFrame title="scene"><LoadingFallback /></ComponentFrame>,
});
  1. use in markdown: @component[YourScene]({"height": 400})

lists

unordered (-), ordered (1.), and task lists (- [ ] / - [x]).

  • first item
  • second item
    • nested item
  • third item
  1. first step
  2. second step
  3. third step
  • uncompleted task
  • completed task
  • another pending item

spoiler tags

discord-style spoiler syntax. text is hidden behind a blurred overlay and revealed on hover.

markdown
||spoiler text here||

this is a spoiler: the cake is a lie. hover to reveal it.

spoiler tags work inline with other text and respect inline code, so ||this is not a spoiler|| inside backticks renders as-is. added in WFD 27 because some posts need to discuss plot details without ruining things for people who haven't watched yet.

quick reference

element syntax
bold **text**
italic *text*
strikethrough ~~text~~
code `code`
link [text](url)
image ![alt](url)
heading ## heading
quote > quote
list - item
ordered list 1. item
task list - [ ] task
table | col |
code block ```
mermaid ```mermaid
note > [!NOTE]
video @video[caption](url)
gif @gif[caption](url)
embed @embed[title](url)
wfd embed @wfd[slug-or-number]
component @component[Name]
three.js scene @component[ThreeDemo]
collapsible <details>
spoiler ||text||