From da44483a77a35a8a56184a27a0c6559eaf150a1d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 18:56:02 +0000 Subject: [PATCH 1/3] Build the redesigned article page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The article page follows the Paper design "V2 · Article — Serif text" with a 720px column: the back link to the home's Writing section, the meta line (date, reading time, project), the mono title, the text in Newsreader, then the "Part of the project" panel and Previous and Next. - prose.css styles the Markdown: headings, lists with muted bullets or mono numbers, inline code, code blocks, quotes, figures on a grey panel, and tables with numbers in 104px mono columns, a highlighted row and a caption. - rehype-article.mjs shapes the HTML for it: figures and their captions, YouTube embeds moved to youtube-nocookie.com and loaded lazily, tables (number columns, the row whose cells are all bold, a "Table:" caption). - remark-heading-levels.mjs shows the headings again, renumbered from h2. - src/data/projects.ts holds the three projects, their screenshots and which articles belong to each, for this page, the home and the share images. - The meta description falls back to an excerpt of the first paragraph. Implements #32. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013MdjUerTCBDeF5DG9puDd2 --- AGENTS.md | 2 + astro.config.mjs | 8 +- bun.lock | 1 + package.json | 1 + src/assets/projects/agent-sdks.jpg | Bin 0 -> 43946 bytes src/assets/projects/data-lineage.jpg | Bin 0 -> 36335 bytes src/assets/projects/voice-todos.jpg | Bin 0 -> 18629 bytes src/components/PrevNext.astro | 74 ++++++++ src/components/ProjectPanel.astro | 90 +++++++++ src/data/projects.ts | 72 ++++++++ src/lib/excerpt.ts | 63 +++++++ src/lib/reading-time.mjs | 11 ++ src/lib/rehype-article.mjs | 176 ++++++++++++++++++ src/lib/remark-heading-levels.mjs | 17 ++ src/pages/articles/[slug].astro | 229 ++++++++++------------- src/styles/legacy.css | 6 +- src/styles/prose.css | 266 +++++++++++++++++++++++++++ 17 files changed, 883 insertions(+), 133 deletions(-) create mode 100644 src/assets/projects/agent-sdks.jpg create mode 100644 src/assets/projects/data-lineage.jpg create mode 100644 src/assets/projects/voice-todos.jpg create mode 100644 src/components/PrevNext.astro create mode 100644 src/components/ProjectPanel.astro create mode 100644 src/data/projects.ts create mode 100644 src/lib/excerpt.ts create mode 100644 src/lib/reading-time.mjs create mode 100644 src/lib/rehype-article.mjs create mode 100644 src/lib/remark-heading-levels.mjs create mode 100644 src/styles/prose.css diff --git a/AGENTS.md b/AGENTS.md index 2ee8e4a..ae7c854 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,6 +9,8 @@ Josselin Perrus's personal site: an Astro 7 static site served by Cloudflare Wor - `src/fonts/`: the self-hosted fonts, DejaVu Sans Mono and Newsreader, subset by `scripts/subset-fonts.py`, with their licences. The site loads no outside fonts. - `src/content/writing/`: git submodule of [meaningfool-writing](https://github.com/meaningfool/meaningfool-writing), which holds every article, activity log and image. Read its `AGENTS.md` before editing content. - `src/content.config.ts`: the content collection, built from `articles/*.md` and `daily-logs/*.md` in the submodule. +- `src/pages/articles/[slug].astro`: the page of every article and activity log. The Markdown goes through the plugins in `src/lib/` set in `astro.config.mjs`: the `# Title` line is dropped, the headings are renumbered from h2, and `rehype-article.mjs` shapes figures, YouTube embeds (moved to youtube-nocookie.com) and tables for `src/styles/prose.css`. In a table, a row whose cells are all bold is highlighted, and a paragraph starting with `Table:` right after it, past a blank line, is its caption. An image alone in its paragraph is captioned with its title, or with its alt text when that has a space. +- `src/data/projects.ts`: the projects, with their screenshots in `src/assets/projects/`, and which articles belong to each, by article id. - `docs/document-sharing.md`: how the encrypted portfolio links on `/documents/` work and how to create them. - `wrangler.jsonc`: the Cloudflare Worker `meaningfool`, which serves `dist/` as static assets. - `worker/index.js`: the only Worker code. It answers byte-range requests for the videos in `/images/`, which Safari needs to play them. diff --git a/astro.config.mjs b/astro.config.mjs index d5e09bf..de98712 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -4,7 +4,10 @@ import { defineConfig } from 'astro/config'; import sitemap from '@astrojs/sitemap'; import { unified } from '@astrojs/markdown-remark'; import remarkBreaks from 'remark-breaks'; +import rehypeRaw from 'rehype-raw'; import remarkDropTitle from './src/lib/remark-drop-title.mjs'; +import remarkHeadingLevels from './src/lib/remark-heading-levels.mjs'; +import rehypeArticle from './src/lib/rehype-article.mjs'; import trailingSlashRedirects from './src/lib/trailing-slash-redirects.mjs'; import { postLastModified } from './src/lib/posts.mjs'; @@ -14,8 +17,11 @@ const lastModified = postLastModified(fileURLToPath(new URL('./src/content/writi export default defineConfig({ markdown: { processor: unified({ - remarkPlugins: [remarkBreaks, remarkDropTitle], + remarkPlugins: [remarkBreaks, remarkDropTitle, remarkHeadingLevels], + // rehype-raw first, so that rehypeArticle also sees the HTML written in the Markdown, such as embeds + rehypePlugins: [rehypeRaw, rehypeArticle], }), + shikiConfig: { theme: 'github-light' }, // code blocks sit on the white page, in a light frame (prose.css) }, integrations: [sitemap({ filter: (page) => !/^\/documents\/?$/.test(new URL(page).pathname), diff --git a/bun.lock b/bun.lock index 37833d0..d8a89a6 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,7 @@ "@astrojs/markdown-remark": "^7.3.0", "@astrojs/rss": "^4.0.19", "astro": "^7.4.3", + "rehype-raw": "^7.0.0", "remark-breaks": "^4.0.0", }, "devDependencies": { diff --git a/package.json b/package.json index e2d2201..d0496c5 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@astrojs/markdown-remark": "^7.3.0", "@astrojs/rss": "^4.0.19", "astro": "^7.4.3", + "rehype-raw": "^7.0.0", "remark-breaks": "^4.0.0" }, "scripts": { diff --git a/src/assets/projects/agent-sdks.jpg b/src/assets/projects/agent-sdks.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bca5c5e20b604b86a4ab1ca0b7ca76a3e58c4c45 GIT binary patch literal 43946 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 Ss$j8yC+7c$qt@eJvwsEdj!Q8B literal 0 HcmV?d00001 diff --git a/src/assets/projects/data-lineage.jpg b/src/assets/projects/data-lineage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..87cd77e75bde7fc2d8db70ad02fe693031d91b2f GIT binary patch literal 36335 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 Qz$4Vw2!7zJ695030A_Aq3;+NC literal 0 HcmV?d00001 diff --git a/src/assets/projects/voice-todos.jpg b/src/assets/projects/voice-todos.jpg new file mode 100644 index 0000000000000000000000000000000000000000..27050ee4eea3ee81d758a78baae2e370ecefac78 GIT binary patch literal 18629 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 V7F|&MSr2e5de>nFz<1d9{{SDIuv`ED literal 0 HcmV?d00001 diff --git a/src/components/PrevNext.astro b/src/components/PrevNext.astro new file mode 100644 index 0000000..d50e866 --- /dev/null +++ b/src/components/PrevNext.astro @@ -0,0 +1,74 @@ +--- +// Previous and Next at the end of a post: the older post on the left, the newer one on the right. +import type { CollectionEntry } from 'astro:content'; + +type Post = CollectionEntry<'writing'>; + +interface Props { + previous?: Post; + next?: Post; +} + +const { previous, next } = Astro.props; +const links = [ + { post: previous, label: '← Previous', rel: 'prev' }, + { post: next, label: 'Next →', rel: 'next' }, +]; +--- + +{(previous || next) && ( +

+)} + + diff --git a/src/components/ProjectPanel.astro b/src/components/ProjectPanel.astro new file mode 100644 index 0000000..52f3020 --- /dev/null +++ b/src/components/ProjectPanel.astro @@ -0,0 +1,90 @@ +--- +// "Part of the project", after an article that belongs to one: its screenshot, name, one line and link. On phones the +// screenshot goes above the text, full width. +import { Image } from 'astro:assets'; +import type { Project } from '../data/projects'; + +interface Props { + project: Project; +} + +const { project } = Astro.props; +--- + + + + diff --git a/src/data/projects.ts b/src/data/projects.ts new file mode 100644 index 0000000..cb2b177 --- /dev/null +++ b/src/data/projects.ts @@ -0,0 +1,72 @@ +// The projects shown on the site, newest first: the "Part of the project" panel and the project in the meta line of +// an article (#32), the Projects tiles of the home (#31) and the project count of the share images (#34). +// +// Which articles belong to which project is kept here, by article id (the file name without its date), because the +// articles' frontmatter in meaningfool-writing has no field for it. +import type { ImageMetadata } from 'astro'; +import voiceTodos from '../assets/projects/voice-todos.jpg'; +import agentSdks from '../assets/projects/agent-sdks.jpg'; +import dataLineage from '../assets/projects/data-lineage.jpg'; + +export interface Project { + slug: string; + name: string; + /** A shorter name, for narrow columns such as the home's Writing list. */ + shortName: string; + type: 'App' | 'Report'; + /** The month it came out, as YYYY-MM. */ + date: string; + /** One line that says what it is. */ + description: string; + link: string; + /** The text of the link, without the ↗. */ + linkText: string; + /** A 16:10 screenshot. */ + screenshot: ImageMetadata; + /** The ids of the articles written about it. */ + articles: string[]; +} + +export const projects: Project[] = [ + { + slug: 'voice-todos', + name: 'Voice Todos', + shortName: 'Voice Todos', + type: 'App', + date: '2026-05', + description: 'Talk, and your todo list builds itself as you speak.', + link: 'https://voice-todos.meaningfool.net/', + linkText: 'Try Voice Todos', + screenshot: voiceTodos, + articles: ['edit-collections-generative-ai', 'structured-extraction', 'thoughts-on-human-ai-collaboration'], + }, + { + slug: 'agent-sdks', + name: 'Agent SDKs for the Rest of Us', + shortName: 'Agent SDKs', + type: 'Report', + date: '2026-02', + description: 'A visual guide to agent frameworks for people who build products.', + link: 'https://agent-frameworks-report.meaningfool.net/', + linkText: 'Read the report', + screenshot: agentSdks, + articles: ['agent-frameworks-for-the-rest-of-us'], + }, + { + slug: 'data-lineage', + name: 'Data Lineage', + shortName: 'Data Lineage', + type: 'App', + date: '2025-12', + description: 'CSV files in, an interactive lineage graph out.', + link: 'https://lineage.meaningfool.net/', + linkText: 'Try Data Lineage', + screenshot: dataLineage, + articles: ['antigravity_is_onto_something'], + }, +]; + +/** The project an article belongs to, if any. */ +export function projectOf(articleId: string): Project | undefined { + return projects.find((project) => project.articles.includes(articleId)); +} diff --git a/src/lib/excerpt.ts b/src/lib/excerpt.ts new file mode 100644 index 0000000..d9d8fa8 --- /dev/null +++ b/src/lib/excerpt.ts @@ -0,0 +1,63 @@ +const ENTITIES: Record = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: ' ' }; + +function decode(html: string): string { + return html.replace(/&(#x[\da-f]+|#\d+|[a-z]+);/gi, (match, code: string) => { + if (code[0] !== '#') return ENTITIES[code.toLowerCase()] ?? match; + const point = code[1].toLowerCase() === 'x' ? parseInt(code.slice(2), 16) : parseInt(code.slice(1), 10); + return point <= 0x10ffff ? String.fromCodePoint(point) : match; + }); +} + +// A tag, whose quoted attribute values may hold a ">". +const TAG = /<(?:[^>"']|"[^"]*"|'[^']*')*>/g; +const textOf = (html: string) => decode(html.replace(TAG, '')).replace(/\s+/g, ' ').trim(); + +/** The HTML without its lists, quotes, tables and figures, innermost first, so that only the text's own paragraphs stay. */ +function withoutBlocks(html: string): string { + const innermost = /<(ul|ol|blockquote|table|figure)\b[^>]*>(?:(?!<\/?(?:ul|ol|blockquote|table|figure)\b)[\s\S])*<\/\1>/g; + for (let previous = ''; previous !== html; ) [previous, html] = [html, html.replace(innermost, ' ')]; + return html; +} + +/** A paragraph's text. Its line breaks become spaces, or full stops between a line and one that starts a sentence. */ +function paragraphText(html: string): string { + return html.split(//).map(textOf).filter(Boolean).reduce((text, line) => { + if (!text) return line; + return /[\p{L}\p{N}]$/u.test(text) && /^\p{Lu}/u.test(line) ? `${text}. ${line}` : `${text} ${line}`; + }, ''); +} + +const SENTENCE_END = /[.!?…]["”’)]?$/; + +/** + * A post's meta description when its frontmatter has none, from its rendered HTML: the text of its first paragraph, + * and of the next when the first is very short. Paragraphs in lists, quotes, tables and figures don't count. A + * paragraph that leads into a list ("…the likes:") is cut back to its last full sentence, or ends with an ellipsis, + * unless it is a short label, which is skipped. The result is cut at a word to stay within `max` characters, and is + * empty when the post has no paragraph to take. + */ +export function excerpt(html: string, max = 160): string { + let text = ''; + for (const [, inner] of withoutBlocks(html).matchAll(/

([\s\S]*?)<\/p>/g)) { + let paragraph = paragraphText(inner); + if (!SENTENCE_END.test(paragraph)) { + // Its whole sentences, unless that leaves little more than an abbreviation such as "e.g." + const whole = paragraph.match(/^.*[.!?…]["”’)]?(?=\s)/)?.[0]; + const sentences = whole && whole.length >= 40 ? whole : undefined; + // A short label such as "Readings:" over a list says nothing on its own: skip it, or stop at it. + if (!sentences && paragraph.length < 50) { + if (text) break; + continue; + } + paragraph = sentences ?? `${paragraph.replace(/[\s,;:–—-]+$/, '')}…`; + text = text ? `${text} ${paragraph}` : paragraph; + break; + } + text = text ? `${text} ${paragraph}` : paragraph; + if (text.length >= 50) break; + } + if (text.length <= max) return text; + const cut = text.slice(0, max); + const end = cut.lastIndexOf(' '); + return `${(end > 0 ? cut.slice(0, end) : cut.slice(0, max - 1)).replace(/[\s,;:.–—-]+$/, '')}…`; +} diff --git a/src/lib/reading-time.mjs b/src/lib/reading-time.mjs new file mode 100644 index 0000000..875237c --- /dev/null +++ b/src/lib/reading-time.mjs @@ -0,0 +1,11 @@ +// Reading time, as the redesign shows it next to a post's date. + +/** + * Minutes to read a post: the words of its Markdown body at 230 a minute, rounded up, and at least 1. This gives the + * prototype's reading times (docs/redesign/prototype/maps.js) for all its articles but the longest, 40 minutes + * instead of 39. + */ +export function readingMinutes(markdown = '') { + const words = markdown.split(/\s+/).filter(Boolean).length; + return Math.max(1, Math.ceil(words / 230)); +} diff --git a/src/lib/rehype-article.mjs b/src/lib/rehype-article.mjs new file mode 100644 index 0000000..677f5ee --- /dev/null +++ b/src/lib/rehype-article.mjs @@ -0,0 +1,176 @@ +/** + * Shape a post's HTML for the article page's styles (src/styles/prose.css). It runs after rehype-raw, so the HTML + * written in the Markdown is made of elements too. + * + * - An image or a video alone in its paragraph becomes a figure, on a panel. The caption is the image's title, or its + * alt text when that is a phrase: an alt text without a space, such as `cost-of-rework`, is a file name. + * - YouTube embeds load from youtube-nocookie.com, lazily, in a 16:9 figure. + * - Tables get a wrapper that scrolls sideways when they don't fit. Columns holding only numbers are marked `num`, a row + * whose cells are all bold is marked `highlight`, and a paragraph right after a table that starts with "Table:" + * becomes its caption. + * - A numbered list that doesn't start at 1 keeps its numbers, which the page draws itself. + */ +const NUMBER = /^[-+−]?[$€£]?\d[\d.,:/]*\s?(?:%|[a-zµ]{1,3})?$/i; +const PLACEHOLDER = /^(?:[-–—]|n\/a)$/i; +const CAPTION = /^Table:\s*/; + +const element = (tagName, properties, children) => ({ type: 'element', tagName, properties, children }); +const isElement = (node, ...names) => node?.type === 'element' && (!names.length || names.includes(node.tagName)); +const isBlank = (node) => node.type === 'comment' || (node.type === 'text' && !node.value.trim()); +const content = (node) => (node.children ?? []).filter((child) => !isBlank(child)); +const textOf = (node) => (node.type === 'text' ? node.value : (node.children ?? []).map(textOf).join('')); +const only = (node) => { + const children = content(node); + return children.length === 1 ? children[0] : undefined; +}; +const addClass = (node, name) => { + node.properties.className = [...(node.properties.className ?? []), name]; +}; + +function figure(media, caption) { + const children = [element('div', { className: ['media'] }, [media])]; + if (caption) children.push(element('figcaption', {}, [{ type: 'text', value: caption }])); + return element('figure', {}, children); +} + +/** The figure for a paragraph that holds only an image (linked or not) or a video. */ +function mediaFigure(paragraph) { + const media = only(paragraph); + if (isElement(media, 'video')) return figure(media); + const image = isElement(media, 'a') ? only(media) : media; + if (!isElement(image, 'img')) return undefined; + const { title, alt } = image.properties; + if (typeof title === 'string' && title.trim()) { + delete image.properties.title; + return figure(media, title.trim()); + } + return figure(media, typeof alt === 'string' && /\s/.test(alt.trim()) ? alt.trim() : undefined); +} + +/** The youtube-nocookie.com address of a YouTube embed, keeping its parameters, such as the start time. */ +function noCookie(src) { + if (typeof src !== 'string') return undefined; + let url; + try { + url = new URL(src.startsWith('//') ? `https:${src}` : src); + } catch { + return undefined; + } + if (!/^https?:$/.test(url.protocol) || !/^(?:www\.|m\.)?youtube(?:-nocookie)?\.com$/.test(url.hostname)) return undefined; + const id = url.pathname.match(/^\/embed\/([\w-]+)\/?$/)?.[1]; + return id && `https://www.youtube-nocookie.com/embed/${id}${url.search}${url.hash}`; +} + +/** The youtube-nocookie.com address for an iframe that embeds a YouTube video; undefined for anything else. */ +const youTubeSrc = (node) => (isElement(node, 'iframe') ? noCookie(node.properties.src) : undefined); + +/** Load a YouTube embed from `src`, lazily. In a figure, the iframe's own sizes give way to the figure's 16:9. */ +function lazyNoCookie(iframe, src, inFigure) { + const { width, height, style, frameBorder, ...rest } = iframe.properties; + iframe.properties = { + ...(inFigure ? rest : iframe.properties), + src, + loading: 'lazy', + referrerPolicy: 'strict-origin-when-cross-origin', + }; + return iframe; +} + +function embedFigure(iframe) { + const result = figure(iframe); + addClass(result.children[0], 'embed'); + return result; +} + +function shapeTable(table, captionParagraph) { + const rows = []; + (function collect(node) { + for (const child of node.children ?? []) { + if (isElement(child, 'tr')) rows.push({ row: child, head: node.tagName === 'thead' }); + else if (isElement(child, 'thead', 'tbody', 'tfoot')) collect(child); + } + })(table); + const cells = (row) => row.children.filter((child) => isElement(child, 'th', 'td')); + const body = rows.filter(({ head }) => !head).map(({ row }) => row); + const columns = Math.max(0, ...rows.map(({ row }) => cells(row).length)); + for (let column = 0; column < columns; column++) { + const values = body.map((row) => (cells(row)[column] ? textOf(cells(row)[column]).trim() : '')); + const numeric = values.some((value) => NUMBER.test(value)) + && values.every((value) => !value || NUMBER.test(value) || PLACEHOLDER.test(value)); + if (!numeric) continue; + for (const { row } of rows) { + const cell = cells(row)[column]; + if (cell) addClass(cell, 'num'); + } + } + for (const row of body) { + const filled = cells(row).filter((cell) => content(cell).length); + if (filled.length && filled.every((cell) => isElement(only(cell), 'strong'))) addClass(row, 'highlight'); + } + if (captionParagraph) { + const [first, ...rest] = captionParagraph.children; + const children = first.value.replace(CAPTION, '') ? [{ ...first, value: first.value.replace(CAPTION, '') }, ...rest] : rest; + table.children.unshift(element('caption', {}, children)); + } + return element('div', { className: ['table'] }, [table]); +} + +function isCaption(node) { + const first = node?.children?.[0]; + return isElement(node, 'p') && first?.type === 'text' && CAPTION.test(first.value) + && Boolean(textOf(node).replace(CAPTION, '').trim()); +} + +function shape(parent) { + const children = parent.children ?? []; + // Whether a figure can go here: not in a paragraph, a link or another figure. + const holdsBlocks = parent.type === 'root' + || isElement(parent, 'div', 'li', 'blockquote', 'section', 'article', 'aside', 'details', 'td', 'th'); + for (let index = 0; index < children.length; index++) { + const node = children[index]; + if (!isElement(node)) continue; + + const media = holdsBlocks && isElement(node, 'p') && mediaFigure(node); + if (media) { + children[index] = media; + continue; + } + // A video written as an HTML block of its own, outside any paragraph. + if (isElement(node, 'video') && holdsBlocks) { + children[index] = figure(node); + continue; + } + // An embed alone in its own block, often a

that gave it a 16:9 box with inline styles. + const embed = holdsBlocks && isElement(node, 'div', 'p') ? only(node) : undefined; + const embedSrc = youTubeSrc(embed); + if (embedSrc) { + children[index] = embedFigure(lazyNoCookie(embed, embedSrc, true)); + continue; + } + // An embed among other content: a figure where a block can go, or else left in place with its own sizes. + const src = youTubeSrc(node); + if (src) { + lazyNoCookie(node, src, holdsBlocks); + if (holdsBlocks) children[index] = embedFigure(node); + continue; + } + if (isElement(node, 'table')) { + let next = index + 1; + while (next < children.length && isBlank(children[next])) next++; + const caption = isCaption(children[next]) ? children[next] : undefined; + if (caption) children.splice(index + 1, next - index); + shape(node); // what the cells hold + children[index] = shapeTable(node, caption); + continue; + } + const { start, style } = node.properties; + if (isElement(node, 'ol') && typeof start === 'number' && start !== 1) { + node.properties.style = [style, `counter-reset: n ${start - 1}`].filter(Boolean).join('; '); + } + shape(node); + } +} + +export default function rehypeArticle() { + return (tree) => shape(tree); +} diff --git a/src/lib/remark-heading-levels.mjs b/src/lib/remark-heading-levels.mjs new file mode 100644 index 0000000..007920c --- /dev/null +++ b/src/lib/remark-heading-levels.mjs @@ -0,0 +1,17 @@ +/** + * Give a post's headings a hierarchy under the page title, the page's only h1: the highest level the text uses becomes + * h2 and the others keep their distance to it. A post with `#` sections gets h2 sections and h3 subsections; one that + * starts at `###` gets them moved up. Runs after remark-drop-title, which removes the `# Title` line. + */ +export default function remarkHeadingLevels() { + return (tree) => { + const headings = []; + (function collect(node) { + if (node.type === 'heading') headings.push(node); + node.children?.forEach(collect); + })(tree); + if (!headings.length) return; + const shift = 2 - Math.min(...headings.map((heading) => heading.depth)); + for (const heading of headings) heading.depth = Math.min(6, heading.depth + shift); + }; +} diff --git a/src/pages/articles/[slug].astro b/src/pages/articles/[slug].astro index be96275..f3ea1e1 100644 --- a/src/pages/articles/[slug].astro +++ b/src/pages/articles/[slug].astro @@ -1,20 +1,56 @@ --- +// An article or an activity log, built from the Paper design "V2 · Article — Serif text" with its column widened to +// 720px: the back link, the meta line, the title and the text, then the project the article belongs to and the +// previous and next posts. The text's styles are in src/styles/prose.css. import type { GetStaticPaths } from 'astro'; import { getCollection, render, type CollectionEntry } from 'astro:content'; import Layout from '../../layouts/Layout.astro'; import JsonLd from '../../components/JsonLd.astro'; +import ProjectPanel from '../../components/ProjectPanel.astro'; +import PrevNext from '../../components/PrevNext.astro'; +import { projects, projectOf } from '../../data/projects'; +import { isDailyLog } from '../../lib/posts.mjs'; +import { newestFirst } from '../../lib/sort-posts'; +import { readingMinutes } from '../../lib/reading-time.mjs'; +import { excerpt } from '../../lib/excerpt'; +import '../../styles/prose.css'; + +type Post = CollectionEntry<'writing'>; + +interface Props { + article: Post; + previous?: Post; + next?: Post; +} export const getStaticPaths: GetStaticPaths = async () => { - const articles = await getCollection('writing'); - return articles.map((article) => ({ - params: { slug: article.id }, - props: article, - })); -} + const posts = await getCollection('writing'); + const ids = new Set(posts.map((post) => post.id)); + for (const project of projects) { + for (const id of project.articles) { + if (!ids.has(id)) console.warn(`src/data/projects.ts: ${project.name} lists an article that doesn't exist: ${id}`); + } + } -const article = Astro.props as CollectionEntry<'writing'>; + // Previous and Next go through the posts newest first: Previous is the older post, Next the newer one. Articles link + // to articles, as the redesigned home's Writing list (#31) holds only articles; activity logs link to activity logs. + return [posts.filter((post) => !isDailyLog(post)), posts.filter(isDailyLog)].flatMap((group) => { + const sorted = group.sort(newestFirst); + return sorted.map((article, index) => ({ + params: { slug: article.id }, + props: { article, previous: sorted[index + 1], next: sorted[index - 1] }, + })); + }); +}; + +const { article, previous, next } = Astro.props; const { Content } = await render(article); +const project = projectOf(article.id); +const minutes = readingMinutes(article.body ?? ''); +const description = article.data.description || excerpt(article.rendered?.html ?? '') || article.data.title; +const date = article.data.date; + // JSON-LD structured data for BlogPosting const canonical = new URL(Astro.url.pathname, Astro.site).toString(); const author = { @@ -26,12 +62,12 @@ const articleSchema = { "@context": "https://schema.org", "@type": "BlogPosting", "headline": article.data.title, - "description": article.data.description || article.data.title, + "description": description, "image": new URL('/og-default.jpg', Astro.site).toString(), "author": author, "publisher": author, - "datePublished": article.data.date.toISOString(), - "dateModified": (article.data.updated ?? article.data.date).toISOString(), + "datePublished": date.toISOString(), + "dateModified": (article.data.updated ?? date).toISOString(), "mainEntityOfPage": { "@type": "WebPage", "@id": canonical }, "url": canonical }; @@ -39,157 +75,92 @@ const articleSchema = { -
+
+ ← All writing
-
+
+

+ + · {minutes} min read{project && <> · {project.name}} +

{article.data.title}

-
-
+
+
+ {project && } + +
diff --git a/src/styles/legacy.css b/src/styles/legacy.css index 5201089..259e424 100644 --- a/src/styles/legacy.css +++ b/src/styles/legacy.css @@ -1,6 +1,6 @@ -/* The content styles of the pages from before the redesign, kept until each page is rebuilt: the home (#31), the - article page (#32) and About (#33). They apply inside an element with the legacy class, which those pages give their -
. Rebuilding a page drops the class, and the last one deletes this file. +/* The content styles of the pages from before the redesign, kept until each page is rebuilt: the home (#31) and About + (#33); the article page (#32) has been. They apply inside an element with the legacy class, which those pages give + their
. Rebuilding a page drops the class, and the last one deletes this file. Only the fonts, the weights and the colours changed with the redesign's foundations (#29). :where() gives each rule the specificity it had as a global rule, so the pages' own styles still win where they did. */ diff --git a/src/styles/prose.css b/src/styles/prose.css new file mode 100644 index 0000000..175371a --- /dev/null +++ b/src/styles/prose.css @@ -0,0 +1,266 @@ +/* The text of a post, from its Markdown: Newsreader 20px/32px (18px/29px on phones), with the headings, the code, the + numbers of lists and tables and the captions in mono. src/lib/rehype-article.mjs shapes the HTML these rules expect: + figures, embeds, tables. The selectors use :where() so that each rule weighs only as much as .prose. */ +.prose { + font-family: var(--serif); + font-size: 20px; + line-height: 32px; + overflow-wrap: break-word; +} + +.prose :where(p, ul, ol, li, figure, blockquote, pre, h2, h3, h4, h5, h6, hr, table, .table) { + margin: 0; +} + +/* The space above each block, which depends on the block before it. */ +.prose :where(* + p, * + pre, * + blockquote) { margin-top: 20px; } +.prose :where(* + ul, * + ol) { margin-top: 10px; } +.prose :where(* + figure) { margin-top: 24px; } +.prose :where(* + .table) { margin-top: 36px; } +.prose :where(* + h2, * + hr) { margin-top: 48px; } +.prose :where(* + h3) { margin-top: 36px; } +.prose :where(* + h4, * + h5, * + h6) { margin-top: 28px; } +.prose :where(h2, h3, h4, h5, h6) + :where(p, pre, blockquote) { margin-top: 12px; } +.prose :where(figure, .table) + :where(p, ul, ol, pre, blockquote) { margin-top: 36px; } +.prose :where(.table) + :where(h2) { margin-top: 56px; } +.prose :where(hr) + * { margin-top: 48px; } + +/* Headings */ +.prose :where(h2, h3, h4, h5, h6) { + font-family: var(--mono); + font-weight: 400; +} + +.prose :where(h2) { + font-size: 22px; + line-height: 30px; + letter-spacing: -0.04em; +} + +.prose :where(h3) { + font-size: 18px; + line-height: 26px; + letter-spacing: -0.03em; +} + +.prose :where(h4, h5, h6) { + font-size: 16px; + line-height: 24px; + letter-spacing: -0.02em; +} + +/* Links and code */ +.prose :where(a) { + color: var(--accent); + text-decoration: underline 1px; + text-underline-position: from-font; +} + +.prose :where(code) { + font-family: var(--mono); + font-size: 16px; +} + +.prose :where(h2, h3, h4, h5, h6, th, td, pre, caption, figcaption) code { + font-size: inherit; +} + +.prose :where(pre) { + overflow-x: auto; + padding: 16px 20px; + border: 1px solid var(--line); + font-family: var(--mono); + font-size: 14px; + line-height: 22px; +} + +/* Lists: muted bullets or mono numbers, hanging 22px or 28px. */ +.prose :where(ul, ol) { + display: grid; + grid-template-columns: minmax(0, 1fr); /* so that a long link wraps instead of widening the list */ + gap: 4px; + padding: 0; + list-style: none; +} + +.prose :where(ol) { + gap: 6px; + counter-reset: n; +} + +.prose :where(li) { + position: relative; + padding-left: 22px; +} + +.prose :where(ol > li) { + padding-left: 28px; + counter-increment: n; +} + +.prose :where(li)::before { + position: absolute; + left: 0; + content: '•'; + color: var(--muted); +} + +.prose :where(ol > li)::before { + content: counter(n) '.'; + font-family: var(--mono); + font-size: 13px; +} + +.prose :where(li > ul, li > ol) { + margin-top: 4px; +} + +.prose :where(blockquote) { + padding: 12px 20px; + border-left: 2px solid var(--accent); + background: var(--tint); +} + +.prose :where(hr) { + height: 0; + border: 0; + border-top: 1px solid var(--line); +} + +.prose :where(img, video) { + max-width: 100%; + height: auto; +} + +.prose :where(iframe) { + max-width: 100%; +} + +/* Figures: the image, the video or the embed on a grey panel, with a caption under it. */ +.prose :where(figure > .media) { + padding: 16px; + background: var(--panel); +} + +.prose :where(figure img, figure video) { + display: block; + max-width: 100%; + height: auto; + margin-inline: auto; + border: 1px solid var(--line); + background: var(--page); +} + +.prose :where(figure video) { + width: 100%; +} + +.prose :where(.embed iframe) { + display: block; + width: 100%; + height: auto; + aspect-ratio: 16 / 9; + border: 0; +} + +.prose :where(figcaption, caption) { + padding-top: 12px; + font-family: var(--mono); + font-size: 12px; + line-height: 19px; + text-align: left; + color: var(--muted); +} + +/* Tables: the header in small muted mono over an ink line, rows over a light line, numbers right-aligned in 104px mono + columns, and a highlighted row on the tint with an accent line on its left. */ +.prose :where(.table) { + overflow-x: auto; +} + +.prose :where(table) { + width: 100%; + border-collapse: separate; + border-spacing: 0; +} + +.prose :where(caption) { + caption-side: bottom; +} + +.prose :where(th, td) { + padding: 10px 0 10px 16px; + border-bottom: 1px solid var(--line); + vertical-align: baseline; + font-family: var(--serif); + font-size: 16px; + line-height: 21px; +} + +.prose :where(th, td):first-child { + padding-left: 12px; + border-left: 2px solid transparent; +} + +.prose :where(th, td):last-child { + padding-right: 12px; +} + +.prose :where(th) { + padding-top: 0; + padding-bottom: 9px; + border-bottom-color: var(--ink); + vertical-align: bottom; + text-align: left; + font-family: var(--mono); + font-weight: 400; + font-size: 11px; + line-height: 16px; + letter-spacing: 0.03em; + color: var(--muted); +} + +.prose :where(th, td).num { + box-sizing: content-box; + width: 104px; + padding-left: 0; + text-align: right; +} + +.prose :where(td).num { + white-space: nowrap; + font-family: var(--mono); + font-size: 13px; +} + +.prose :where(tr.highlight > td) { + background: var(--tint); +} + +.prose :where(tr.highlight > td):first-child { + border-left-color: var(--accent); +} + +/* The highlight replaces the bold that marks the row in the Markdown. */ +.prose :where(tr.highlight strong) { + font-weight: inherit; +} + +@media (max-width: 900px) { + .prose { + font-size: 18px; + line-height: 29px; + } + + .prose :where(td) { + font-size: 15px; + } + + .prose :where(th, td).num { + width: 64px; + } + + .prose :where(td).num { + font-size: 12px; + } +} From acfbd34cfb6d154b94d63d22f9fe43be6647fee5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 19:18:29 +0000 Subject: [PATCH 2/3] Keep each heading at most one level below the one before it "Agent Frameworks for the Rest of Us" opens with a `##` before its first `#` section, which the renumbering made an h3 straight under the page title. Headings now go at most one level deeper than the heading before them, the title included, so it becomes an h2 like the parts after it. No other post changes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013MdjUerTCBDeF5DG9puDd2 --- src/lib/remark-heading-levels.mjs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib/remark-heading-levels.mjs b/src/lib/remark-heading-levels.mjs index 007920c..f04b1f1 100644 --- a/src/lib/remark-heading-levels.mjs +++ b/src/lib/remark-heading-levels.mjs @@ -1,7 +1,9 @@ /** * Give a post's headings a hierarchy under the page title, the page's only h1: the highest level the text uses becomes * h2 and the others keep their distance to it. A post with `#` sections gets h2 sections and h3 subsections; one that - * starts at `###` gets them moved up. Runs after remark-drop-title, which removes the `# Title` line. + * starts at `###` gets them moved up. No heading goes more than one level below the one before it, the title included, + * so a `##` before the first `#` section becomes h2 rather than an h3 straight under the title. Runs after + * remark-drop-title, which removes the `# Title` line. */ export default function remarkHeadingLevels() { return (tree) => { @@ -12,6 +14,10 @@ export default function remarkHeadingLevels() { })(tree); if (!headings.length) return; const shift = 2 - Math.min(...headings.map((heading) => heading.depth)); - for (const heading of headings) heading.depth = Math.min(6, heading.depth + shift); + let previous = 1; // the page title + for (const heading of headings) { + heading.depth = Math.min(6, heading.depth + shift, previous + 1); + previous = heading.depth; + } }; } From 94fffd29d0e66475301cb1c2471bffd612222ba5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 19:27:44 +0000 Subject: [PATCH 3/3] Render the posts again when the Markdown plugins change Astro keeps each post's rendered HTML in node_modules/.astro, which the deploy workflow restores from its cache, and renders a post again only when the post or the config it compares changes. Plugin functions drop out of that comparison, so the preview of acfbd34 still served the previous heading levels. A hash of the plugins' sources and bun.lock now goes into the config, as rehypeArticle's unused options. The built site is unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013MdjUerTCBDeF5DG9puDd2 --- astro.config.mjs | 12 +++++++++++- src/lib/rehype-article.mjs | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/astro.config.mjs b/astro.config.mjs index de98712..9b68b24 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,4 +1,6 @@ // @ts-check +import { createHash } from 'node:crypto'; +import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { defineConfig } from 'astro/config'; import sitemap from '@astrojs/sitemap'; @@ -13,13 +15,21 @@ import { postLastModified } from './src/lib/posts.mjs'; const lastModified = postLastModified(fileURLToPath(new URL('./src/content/writing', import.meta.url))); +// Astro keeps each post's rendered HTML between builds, in node_modules/.astro, which the deploy workflow restores from +// its cache, and renders a post again only when the post or this config changes. The Markdown plugins' code and the +// installed packages aren't part of the config it compares, so a hash of them is, as rehypeArticle's options. +const markdownSources = ['src/lib/remark-drop-title.mjs', 'src/lib/remark-heading-levels.mjs', 'src/lib/rehype-article.mjs', 'bun.lock']; +const markdownDigest = createHash('sha256') + .update(markdownSources.map((file) => readFileSync(new URL(`./${file}`, import.meta.url), 'utf8')).join('\0')) + .digest('hex'); + // https://astro.build/config export default defineConfig({ markdown: { processor: unified({ remarkPlugins: [remarkBreaks, remarkDropTitle, remarkHeadingLevels], // rehype-raw first, so that rehypeArticle also sees the HTML written in the Markdown, such as embeds - rehypePlugins: [rehypeRaw, rehypeArticle], + rehypePlugins: [rehypeRaw, [rehypeArticle, { markdownDigest }]], }), shikiConfig: { theme: 'github-light' }, // code blocks sit on the white page, in a light frame (prose.css) }, diff --git a/src/lib/rehype-article.mjs b/src/lib/rehype-article.mjs index 677f5ee..9046c27 100644 --- a/src/lib/rehype-article.mjs +++ b/src/lib/rehype-article.mjs @@ -171,6 +171,8 @@ function shape(parent) { } } +// Its options go unused: astro.config.mjs passes a hash of the Markdown plugins there, so that Astro renders the posts +// again when they change. export default function rehypeArticle() { return (tree) => shape(tree); }