wal.sh Design System
Table of Contents
1. Brand identity
1.1. Site purpose
wal.sh is a personal research archive. It collects technical notes, conference observations, and extended investigations in the areas of programming languages, distributed systems, AI agent architectures, and formal methods. The audience is technical and self-selecting. There is no marketing copy and no onboarding flow. Readers arrive already knowing what they are looking for.
The site does not have a product to sell. Its purpose is to make research thinking legible across time and across sessions.
1.2. Voice
The writing voice is direct and claim-forward. Notes open with an assertion or a concrete fact, not a description of what the note will cover. Abstractions are introduced after the concrete example that motivates them, not before.
Sentences are complete. Lists are used for enumeration, not as a substitute for prose. Code blocks contain code; prose describes what the code does and why it matters.
1.3. Visual tone
The visual language is quiet and scholarly. The page presents text with a minimum of decoration. Color appears in diagrams, not in navigation or prose layout. The chrome (header, footer, links) is subdued — greys and a single blue accent. Diagrams are the visual centerpiece of each research page; they carry structure and color while the surrounding page recedes.
The design deliberately avoids:
- Pull quotes, callout boxes, or highlight panels on prose sections
- Colored backgrounds behind text
- Decorative icons in navigation
- Heavy border treatments on content elements
The one exception is the .ai block, which uses a pale blue left-border
treatment to mark content that originated from an AI assistant.
2. Color palette
2.1. Primary palette: Tailwind 100/700 pastel system
This palette governs all Graphviz diagrams. Each color family encodes a fixed semantic role across all diagrams on the site. The mapping must not be broken for aesthetic reasons — it is a protocol, not a preference.
| Family | Fill (100) | Border / Text (700) | Cluster BG (50) | Semantic role |
|---|---|---|---|---|
| Blue | #dbeafe |
#1d4ed8 |
#eff6ff |
Primary data flow, input, process |
| Purple | #ede9fe |
#6b21a8 |
#f5f3ff |
Abstraction, protocol, API |
| Green | #dcfce7 |
#15803d |
#f0fdf4 |
Output, success, storage |
| Amber | #fef3c7 |
#b45309 |
#fffbeb |
Config, orchestration, external |
| Yellow | #fef9c3 |
#a16207 |
#fefce8 |
Warning, intermediate, cache |
| Red | #fee2e2 |
#b91c1c |
#fef2f2 |
Error, critical path, alert |
The 100-weight fills are the pastel node backgrounds. The 700-weight values serve triple duty: node border, node text color, and cluster label. The 50-weight is for cluster backgrounds only, lighter than the node fill so the cluster recedes behind its contents.
All hex values are full 6-digit. Shorthand hex (#d63) is prohibited.
Named HTML colors (lightblue, yellow) are prohibited. They are not
reproducible across Graphviz renderers.
2.2. Neutral palette: CSS variables and prose greys
These values come from the style.css :root block and are used for
page chrome, text, and UI elements.
| Variable | Value | Usage |
|---|---|---|
--bg |
#fafafa |
Page background |
--bg-content |
#ffffff |
Content area background |
--text |
#1a1a1a |
Body text, headings |
--text-secondary |
#666 |
Author, date, nav links, secondary labels |
--accent |
#0066cc |
Links, blockquote border, postamble links |
--accent-light |
#e6f2ff |
Link hover background, .ai block background |
--border |
#eaeaea |
Horizontal rules, table cell borders, header border |
--code-bg |
#f6f8fa |
Inline code background, pre background |
--success |
#4CAF50 |
DONE todo-state badge |
--warning |
#FFA500 |
TODO todo-state badge |
Additional grey values appear in component-specific contexts:
| Value | Context |
|---|---|
#888 |
Diagram edge default color, webring muted label |
#555 |
Diagram edge label text |
#444 |
Webring border fallback (if --border not set) |
2.3. Meaning mapping across the site
The same blue accent (#0066cc) serves as the link color in prose,
the blockquote border, and the .ai block border. This creates a
consistent "interactive or generated" signal: anything with that blue
treatment is either a link to follow or content that came from an AI
source.
Diagram colors are intentionally distinct from the CSS neutral palette.
The pastel diagram fills (#dbeafe, #dcfce7, etc.) do not appear in
the site CSS. This separation prevents visual bleed between page chrome
and diagram semantics.
3. Typography
3.1. Body text
| Property | Value |
|---|---|
| Font family | Inter, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif |
| Font size | 17px (desktop), 16px (mobile, max-width 768px) |
| Font weight | 400 (regular) |
| Line height | 1.8 |
| Color | #1a1a1a (--text) |
| Max width | 1200px (1400px at min-width 1400px) |
| Padding | 3rem clamp(1rem, 5vw, 4rem) |
3.2. Headings
All headings use font-weight: 700 and letter-spacing: -0.02em.
Color is #1a1a1a, same as body text.
| Level | Size | Top margin |
|---|---|---|
| h1 | 2.5rem | 2.5rem |
| h2 | 1.75rem | 3rem |
| h3 | 1.25rem | 2.5rem |
| h4 | 1.1rem | 2.5rem |
| h5 | 1rem | 2.5rem |
| h6 | 0.9rem | 2.5rem |
The page h1.title from org-publish is hidden via #content > h1 { display: none }.
The visible page title is emitted by the preamble as a <header class"page-header"><h1>=
on non-index pages. Index pages use the org-mode h1 directly.
3.3. Links
Links use color: var(--accent) (#0066cc) with text-decoration: underline and
text-underline-offset: 2px. On hover, the background becomes var(--accent-light)
(#e6f2ff). Nav links in the site header suppress the underline and use
var(--text-secondary) at 0.9rem.
3.4. Code blocks
Two distinct code block treatments coexist:
3.4.1. Inline code and plain pre blocks
| Property | Value |
|---|---|
| Font family | JetBrains Mono, Fira Code, Consolas, monospace |
| Background | #f6f8fa (--code-bg) |
| Border | 1px solid #eaeaea (--border) for pre |
| Border radius | 4px |
| Font size (inline) | 0.85em |
| Font size (pre) | 0.9rem |
| Line height (pre) | 1.5 |
| Padding (inline) | 0.15rem 0.35rem |
| Padding (pre) | 1.25rem |
3.4.2. Org src blocks (language-tagged)
Org source blocks with a language tag (#+begin_src python, etc.) receive
the Catppuccin Mocha dark theme treatment:
| Property | Value |
|---|---|
| Background | #1e1e2e |
| Text | #cdd6f4 |
| Border | None (dark background makes a border redundant) |
| Border radius | 8px |
| Box shadow | 0 2px 8px rgba(0,0,0,0.15) |
A language label is prepended via CSS ::before pseudo-element in
#6c7086 at 0.7rem uppercase. The label classes are:
src-python, src-javascript, src-typescript, src-rust, src-json,
src-sql, src-bash / src-shell / src-sh, src-elisp / src-emacs-lisp,
src-scheme, src-clojure, src-yaml, src-toml, src-hcl.
Blocks tagged src-text suppress the label entirely.
Syntax highlighting uses semantic CSS classes output by Emacs htmlize
in css mode:
| Class | Color | Meaning |
|---|---|---|
.org-keyword |
#cba6f7 |
Keywords (def, return, if, etc.) |
.org-string |
#a6e3a1 |
Strings and docstrings |
.org-builtin |
#f38ba8 |
Built-in functions |
.org-function-name |
#89b4fa |
Function / method names |
.org-variable-name |
#f9e2af |
Variable names |
.org-type |
#89dceb |
Type names and annotations |
.org-constant |
#fab387 |
Constants, numbers, True/False |
.org-comment |
#6c7086 |
Comments (italic) |
.org-preprocessor |
#f5c2e7 |
Decorators |
.org-operator |
#94e2d5 |
Operators and punctuation |
3.5. Diagram text
All diagram text uses fontname"Helvetica"=. Three sizes, applied
consistently across all diagrams:
| Element | fontsize |
|---|---|
| Graph label | 11 |
| Node label | 10 |
| Edge label | 9 |
3.6. Secondary text
Navigation links, author lines, dates, and footer text use
var(--text-secondary) (#666) at reduced sizes (0.85rem–0.95rem).
4. Layout
4.1. Page structure
Every published page follows this vertical structure:
<header class="site-header"> preamble: sitewide nav + logo
<div class="breadcrumbs"> preamble: path from home to current page
<header class="page-header"> preamble: page h1 (non-index pages only)
[org-mode content body]
banner image (728x90 greyscale)
prose sections
diagrams
tables
code blocks
#postamble postamble: author, date, build info
postamble: adtech includes (conditional)
postamble: web-vitals (conditional)
.webring postamble: webring navigation
The content body is the org-mode HTML export. The page title h1 is
suppressed from the org export (display: none) and re-emitted by the
preamble so it appears before breadcrumbs do not interrupt the title.
4.2. Sitewide header
The site header is a flex row with justify-content: space-between,
separated from content by a 1px #eaeaea border-bottom and 3rem
margin-bottom.
Left: navigation list with four items (Home, Current, Research, Events)
in var(--text-secondary) at 0.9rem.
Right: logo image (48x48px) linking to /, wrapped in .site-logo.
4.3. Breadcrumbs
Generated programmatically from the file path relative to the site root.
The root index page emits no breadcrumbs. All other pages show a path:
home > section > subsection > page (YYYY-MM-DD). Breadcrumb links use
var(--text-secondary) at 0.85rem, with last-modified date in parentheses
on the leaf entry.
4.4. Banner
Each research page opens with a 728x90 greyscale leaderboard banner
image. Banners sit between the page-header and the first body section.
Images are centered via display: block; margin: 2rem auto.
Org reference form:
#+ATTR_HTML: :alt <description> :class banner-leaderboard :width 728 :height 90 [[file:banner.png]]
Banner images are produced from a source graphic using:
magick input.png -resize 728x90^ -gravity center -extent 728x90 -colorspace Gray output.png
The greyscale constraint is intentional: diagrams carry the color on the page. A color banner would compete with the diagrams for attention.
4.5. Diagrams
Diagrams are PNG images exported from Graphviz .dot files by org-babel
during publish. They are placed via standard org image links:
#+CAPTION: <description of what the diagram shows> #+NAME: fig:<identifier> #+ATTR_HTML: :alt <alt text describing content for screen readers> [[file:<diagram-name>.png]]
Images are max-width: 100%, height: auto, centered with 2rem margins.
No explicit width or height is set in HTML for diagram images; they scale
to content width.
4.6. Postamble
The postamble is generated by wal-sh/org-html-postamble. It contains:
<p class"author">= — Author name<p class"email">= — Email in angle brackets (via CSS::before/::after)<p class"date">= — Last modified timestamp from file system<p class"build-info"><code>= — build timestamp and git SHA
Followed by conditional adtech performance-art includes, web-vitals measurement, and the webring navigation element.
The #postamble element uses font-size: 0.85em, color: var(--text-secondary),
centered, with a 1px #eaeaea border-top and 3rem top margin.
5. Component inventory
5.1. Banners
728x90 pixels, greyscale, leaderboard format. One per research page,
placed immediately after the page title / first section heading. Alt
text is required and should describe the visual content meaningfully for
screen readers. The :class banner-leaderboard attribute is always set.
5.2. Diagrams
Graphviz dot diagrams, compiled to PNG via org-babel #+begin_src dot
blocks during publish. Every diagram follows the palette and node
conventions described in section 6. Each diagram image requires:
#+CAPTION— prose description of the claim the diagram makes#+NAME— afig:prefixed identifier for internal linking#+ATTR_HTMLwith:alt— screen-reader description
The site had 53+ dot files as of 2026-05-19, all following the canonical palette. Mermaid diagrams in older pages have been converted to dot.
5.3. Code blocks
Org source blocks with language tags export with the dark Catppuccin
Mocha theme. Plain #+begin_example blocks export as plain grey
<pre> elements. The language label is rendered via CSS ::before.
The :eval no :tangle no header args appear on code blocks that are
documentation examples not intended for execution.
5.4. Tables
Org tables export as HTML tables with width: 100%, border-collapse: collapse,
and margin: 1.5rem 0. Cell padding is 0.75rem 1rem. Rows are separated
by a 1px #eaeaea border-bottom. Headers use font-weight: 600. No
zebra striping. Tables scroll horizontally on narrow viewports.
5.5. Blockquotes
Left border: 3px solid #0066cc (var(--accent)). Italic text in
var(--text-secondary). No background fill.
5.6. AI blocks
<div class"ai">= is used to mark content that originated from an AI
assistant. It uses var(--accent-light) background and a 3px
var(--accent) left border, matching the blockquote treatment.
A copy button (.copy-javascript) is positioned absolutely in the
top-right corner.
5.7. Todo state badges
Org TODO/DONE keywords export as inline badges:
| State | Color |
|---|---|
| TODO | #FFA500 text on 10% orange bg |
| DONE | #4CAF50 text on 10% green bg |
0.7em, uppercase, padded, border-radius: 3px.
5.8. Property drawers
Exported as .property-drawer in var(--text-secondary) at 0.8em,
using font-family: monospace and white-space: pre.
5.9. External and internal links
Both internal [[file:...]] links and external [[https://...]] links
render identically: #0066cc with underline, pale blue hover background.
The distinction between internal and external is not visually signaled
beyond what the URL itself communicates.
5.10. Webring navigation
The webring element appears at the very bottom of every page, outside
the main postamble block. It is a <nav class"webring">= with prev
and next links populated by /static/js/webring.js at runtime.
Styling: centered, padding: 0.75rem 0, border-top: 1px solid var(--border),
font-size: 0.85rem, opacity: 0.7 (rising to 1 on hover). The
.ring-label element uses font-style: italic and color: var(--muted, #888).
6. Diagram conventions
6.1. Three governing principles
- Color encodes semantics, not decoration. Each of the six color families carries a fixed meaning across all diagrams. Blue is input and primary flow. Green is output and success. Red is error and critical path. A reader can identify state semantics from color before reading labels.
- Diagrams carry the color; banners stay quiet. Research page banners are greyscale. The pastel diagrams are the visual centerpiece of the page. A color banner would compete with the diagram for the reader's attention.
- The palette is a protocol, not a preference. The Tailwind 100/700 pairs were chosen because they are widely known, machine-readable, and have accessible contrast ratios. The cost of replacing them would require updating every diagram simultaneously.
6.2. Node conventions
Every node uses shape=box with style"rounded,filled"=. Three color
attributes are mandatory and must come from the same family:
fillcolor— 100-weight pastel fillcolor— 700-weight borderfontcolor— 700-weight text (black text on a pastel node means this attribute is missing)
Special shapes:
shape=note— annotation nodes and legend entries; still uses the paletteshape=diamond— decision points in pipelines; typically ambershape=plain— invisible structural nodes (start/end markers in state machines); no fill, no border
6.3. Cluster conventions
Default cluster style is border-only: a colored border with a white interior. The cluster border and label both use the 700-weight of the dominant color family of its contents.
subgraph cluster_name {
label="Cluster Title";
style="rounded";
color="#1d4ed8"; // border: 700-weight
fontcolor="#1d4ed8"; // label: 700-weight
fontname="Helvetica";
fontsize=11;
}
Use style"rounded,filled"= with a 50-weight fillcolor only when the
cluster background needs to visually separate regions — layer stacks
or side-by-side comparisons. Prefer border-only for all other cases.
Never use #fafafa for clusters; it has no semantic meaning.
6.4. Edge conventions
| Condition | color | fontcolor | style |
|---|---|---|---|
| Default | #888 |
#555 |
solid |
| Failure / error path | #b91c1c |
#b91c1c |
solid |
| Optional / fallback | #888 |
#555 |
dashed |
| Deprecated / pressure | #888 |
#555 |
dotted |
| Feedback loop | #888 |
#555 |
dashed |
The feedback edge in a process loop is always dashed and uses the default grey, signaling that the loop is structural rather than representing an error or degraded path.
6.5. The six archetypes
6.5.1. Pipeline
Data flowing through sequential transformations. Layout: rankdir=LR.
Color follows the data lifecycle: blue for ingestion, amber for
processing, green for output, yellow for intermediate caches and queues.
Clusters group stages by concern; each cluster border matches its
dominant color family.
Examples: diagram-rag-pipeline, diagram-ci-cd-pipeline,
diagram-observability, jq/diagram-pipeline.
6.5.2. Layer stack
Architectural layers from top (user-facing) to bottom (substrate).
Layout: rankdir=TB. Each layer is a cluster with a distinct color
family. Top layer is typically blue (closest to the user); lower layers
shift through purple, amber, and green. Cross-layer edges use default
grey; failure-path edges use red.
Examples: diagram-feature-surface, diagram-tool-systems-layers,
diagram-llm-frameworks-taxonomy.
6.5.3. Comparison
Two or more systems placed side by side. Layout: rankdir=TB with
clusters arranged horizontally. Each system gets a distinct color
family. The contrast between cluster colors is the primary visual
signal. Rule: never use the same color family for both sides of a
comparison.
Examples: diagram-isolation-comparison (blue vs amber),
diagram-datalayer-schema-compare (four formats, four colors),
diagram-eda-vs-actor.
6.5.4. State machine
Transitions between states. Layout: rankdir=LR. Each state gets the
color that reflects its semantic nature: green for healthy/fresh, amber
for degraded/stale, blue for active/processing, purple for fallback, red
for terminal/error. State machines are the only archetype where individual
nodes routinely use different color families within the same diagram.
Examples: diagram-cache-states (HTTP cache per RFC 9111),
diagram-order-states, diagram-process-lifecycle.
6.5.5. Process loop
Cyclical workflow with named phases. Layout: rankdir=LR to emphasize
forward flow, with a dashed feedback edge closing the loop. Each phase
is a cluster with a distinct color family, cycling through blue, purple,
amber, green. Exception nodes use red with red dashed edges.
Example: diagram-review-loop (elenctic vibe code review).
6.5.6. Ecosystem map
Relationships between tools, libraries, or systems in a domain.
Layout: rankdir=TB with clusters grouping related items by category.
Color assignment follows function: blue for core implementations, purple
for protocols/standards, green for outputs/tooling, amber for
configuration/build, yellow for community/ecosystem, red for deprecated
or risky components.
Examples: diagram-scheme-llm-toolkit, diagram-ecosystem (Unix V4
games), diagram-clojure-ecosystem.
6.6. ASCII diagram policy
ASCII arrow diagrams in #+begin_example blocks that represent data
flows, state machines, or architectures should be converted to Graphviz
dot diagrams. ASCII directory trees (├── .config/) and inline prose
arrows stay as text.
6.7. What to avoid
- Shorthand hex (
#369,#d63) - Named HTML colors (
lightblue,yellow) - Dark backgrounds with light text (inverts the site's light theme)
- Hard saturated fills (always use the 100-weight pastel)
- Non-Helvetica fonts
- Neutral grey clusters (
#444/#fafafa) - Missing
fontcolor(produces black text on pastel background)
7. Content conventions
7.1. Required org headers
Every published page requires these five headers:
| Header | Example value |
|---|---|
#+TITLE |
Claude Code Features --- 2026 Q2 |
#+AUTHOR |
Jason Walsh |
#+EMAIL |
j@wal.sh |
#+DATE |
2026-04-17 |
#+DESCRIPTION |
One paragraph, used for search and social sharing |
Optional but common:
#+URL— canonical URL for the page#+KEYWORDS— comma-separated, lowercase, hyphen-separated#+OPTIONS: toc:2 num:t— table of contents depth and section numbering#+SUBTITLE— appears under the title in some contexts
7.2. Template structure
New research notes are scaffolded from site/templates/research-note-dir/index.org
for directory-form notes, or site/templates/research-note.org for flat files.
The template provides the correct header set and a placeholder body. The slug
must be kebab-case (^[a-z0-9][a-z0-9-]*[a-z0-9]$).
7.3. Research pages vs event pages
Research pages live in site/research/ (flat .org files) or
site/research/<slug>/index.org (directory form). Directory form is
preferred for notes that include multiple images, diagrams, or
supplementary files.
Event pages live in site/events/<conference>/<year>/index.org. They
record observations from attended conferences and follow the same header
requirements.
7.4. current focus section on the homepage
The * current focus section in site/index.org lists recent research
in reverse chronological order, newest first. Each entry follows this
format:
- YYYY-MM [[file:research/<slug>/index.org][display title]] --- one-line abstract
Older items are moved to the * 2025, * past, or ** 20XX sections
below current focus. The current focus section should contain only the
most recent entries (roughly the last six to twelve months).
External links (GitHub repos, external sites) use [[https://...][title]]
in the same list.
7.5. Org link forms
Internal links to published pages use [[file:research/<slug>/index.org]]
or [[file:research/<slug>.org]]. The Emacs export filter
wal-sh/clean-url-filter rewrites foo/index.html to foo/ and
foo.html to foo in the published HTML, producing clean extensionless
URLs.
7.6. Excluded from publish
The following are excluded from org-publish:
README.org,SECURITY.org,CLAUDE.md,HEALTH_CHECKS.orgsite/includes/— HTML fragments assembled at publish timesite/_drafts/— work in progress not ready for publicationsite/templates/— scaffolding filessite/research/ai_model_outputs/— raw data files- Files matching
*-YYYY.org— year-template placeholders
7.7. Build info
The postamble includes a build timestamp and git SHA in the form
build: YYYY-MM-DD HH:MM | sha: <short-sha>. This appears in a <code>
element inside a .build-info paragraph.
7.8. Draft workflow
Half-formed notes belong in site/_drafts/. That directory is excluded
from org-publish, so drafts can be committed without appearing on the
live site. When ready, the file is moved into the appropriate
site/research/ or site/events/ path.
