writings for discussion
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.
This WFD supersedes WFD 12. WFD 12 remains in the archive as a legacy document. WFDs are never deleted, even when superseded, because they're a record of how things were at the time they were written.
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
This document says "you" a lot. The WFD format isn't proprietary or exclusive to this site. If the structure works for you, take it. Adapt it. Make it yours. That's the whole point.
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:
arguments against:
- reason one
- reason two
arguments for:
- reason one
- reason twothis 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:
---
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. |
States are freeform text. These are suggestions. I might not even follow them myself because I felt like a word meant something different that day, or because I wanted a state that doesn't exist yet. Type whatever makes sense.
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:
- If we're inside a fenced code block, accumulate lines until the closing fence.
- If we're inside an HTML block (
<details>,<div>, etc.), pass lines through to remark as-is. - If we're inside an admonition (
> [!NOTE]), accumulate continuation lines that start with>. - Check for code block openings, heading patterns (for TOC extraction), admonition starts, component directives, and media lines.
- If a line matches
,@gif[alt](url),@video[caption](url), or@embed[title](url), it's collected into a media group. - Consecutive media lines without a non-empty separator become a single carousel.
- 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.
```typescript
const x: number = 42;
```const x: number = 42;tables
Standard GFM tables. They render with rounded borders and a semi-transparent background.
| header | header |
|--------|--------|
| cell | cell || feature | syntax | example |
|---|---|---|
| bold | **text** |
bold |
| italic | *text* |
italic |
| code | `code` |
code |
blockquotes
> quoted text goes here.this is a blockquote. it can span multiple lines.
admonitions
GitHub-style alerts with five types:
> [!NOTE]
> useful information.
> [!TIP]
> helpful advice.
> [!IMPORTANT]
> key information.
> [!WARNING]
> urgent information.
> [!CAUTION]
> risk of negative outcome.Custom titles:
> [!NOTE/Custom Title]
> content with a custom title.useful information that readers should know, even when skimming.
helpful advice for doing things better or more easily.
key information that readers need to achieve their goal.
urgent information that needs immediate attention to avoid problems.
advises about risks or negative outcomes of certain actions.
admonitions can have custom titles instead of the default.
images
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
forest path in autumn
coastal cliffs at sunset
gifs
@gif[description](url)videos
@video[caption](url)link embeds
@embed[title](url)mermaid diagrams
```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.
collapsible sections
<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
@component[ComponentName]
@component[ComponentName]({"prop": "value"})Embeds interactive React components directly in the document.
lists
Unordered (-), ordered (1.), and task lists (- [ ] / - [x]).
- first item
- second item
- nested item
- third item
- first step
- second step
- third step
- uncompleted task
- completed task
- another pending item
quick reference
| element | syntax |
|---|---|
| bold | **text** |
| italic | *text* |
| strikethrough | ~~text~~ |
| code | `code` |
| link | [text](url) |
| image |  |
| 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) |
| component | @component[Name] |
| collapsible | <details> |