Lossless Changelog
product

Strategies — Markdown-Authored Strategy Documents with Inline Citations

Strategy documents become first-class: markdown in, blog-style pages out, with inline citations and an index of preview cards.

Michael StatonMusic Editor

Changelog — 2026-04-25_01

Summary

This changeset adds a new Strategies section to the site where markdown-authored strategy documents render as full blog-style pages with inline citations, callout boxes, code blocks, and a sources list. It also introduces the site's first LFM (Lossless Flavored Markdown) integration for rich content rendering, and fixes a pronoun error in a team member's bio.

What stakeholders see:

  • New /strategies index page listing strategy articles as preview cards

  • Each article has its own page (/strategies/[slug]) with professional formatting, numbered source citations, and styled callout boxes


New content collection: Strategies

Content config

  • Added strategies collection to src/content.config.ts

  • Loader: glob({ pattern: '**/*.md', base: './src/content/strategies' })

  • Schema fields:

    • title (string, required)

    • description (string, optional)

    • author (string, optional)

    • date (coerced date, optional) — uses z.coerce.date() so string dates in frontmatter parse correctly

    • image (string, optional) — for card preview images

    • tags (string array, optional)

Content directory

  • src/content/strategies/ — already contained one file: why-markdown-needs-to-grow-up.md

  • No new content files were added; the existing file is now served by the new collection and pages


New pages

src/pages/strategies/index.astro

  • Route: /strategies

  • Loads all entries from the strategies collection via getCollection('strategies')

  • Renders each entry as a preview card in a responsive CSS grid (grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)))

  • Card displays: title, description, author, date, tags, optional cover image

  • Uses BaseThemeLayout wrapper consistent with other site pages

  • Hero section with gradient background using site CSS custom properties (--color-primary-950, --color-primary-900)

  • Empty state message when no strategies exist

src/pages/strategies/[slug].astro

  • Route: /strategies/:slug

  • Uses getStaticPaths to generate one page per strategy entry

  • Parses markdown body through parseContent() (see below), receiving an enriched MDAST tree and structured citation array

  • Renders article with:

    • Back link to /strategies

    • Header: title, description, author, date, tags

    • Markdown body via <AstroMarkdown node={tree} />

    • Sources section via <Sources citations={citations} /> (only rendered when citations exist)

  • Prose styling via scoped :global() selectors for headings, paragraphs, lists, links, blockquotes, code, and horizontal rules


LFM integration — Markdown rendering pipeline

Dependencies added

  • @lossless-group/lfm — already present at ^0.1.3 in package.json

  • mdast-util-to-string@^4.0.0 — added (used by AstroMarkdown.astro for heading ID generation)

Markdown components copied from packages/lfm-astro

Per the copy-and-adapt pattern documented in CLAUDE.md, the following components were copied from packages/lfm-astro/components/ into src/components/markdown/:

ComponentPurpose
AstroMarkdown.astroRecursive MDAST-to-JSX renderer. Walks the tree and renders 20+ node types including paragraphs, headings (with auto-generated id slugs), lists, links, images, code blocks, tables, blockquotes, directives, footnote references, and HTML pass-through.
Sources.astroRenders an ordered citation list at the bottom of an article. Each citation shows index number, linked title, source domain, and published date. Items are anchor-linked to inline [n] markers via id="source-{hex}".
Callout.astroRenders containerDirective nodes with name === "callout" as styled boxes. Supports types: info, tip, warning, danger, note, success, quote, example. Each type has distinct border/background colors.
CodeBlock.astroRenders fenced code blocks with a language label badge and dark background.
MarkdownImage.astroRenders :::image directives with support for float (left/right), captions (bottom/top/side), source attribution, and responsive unfloating via container queries.

Import paths: AstroMarkdown.astro imports CodeBlock, Callout, and MarkdownImage from ./ (same directory). No path changes were needed since all five components live together in src/components/markdown/.

Key rendering behaviors

  • footnoteReference nodes render as superscript [n] using node.data.citationIndex (sequential number), with an anchor link to #source-{hex}

  • footnoteDefinition nodes are suppressed (&& null) — their content is rendered by the Sources component instead

  • containerDirective nodes with name === "callout" route to the Callout component

  • containerDirective nodes with name === "image" route to the MarkdownImage component

  • Unrecognized node types fall through to a catch-all that renders their children


src/lib/parse-content.ts — Reusable markdown processing utility

This is the core addition that makes LFM rendering work across the site. It wraps parseMarkdown() from @lossless-group/lfm and polyfills two features that are missing from the published v0.1.3 but present in the unpublished v0.2.x workspace source.

Why this exists

The published @lossless-group/lfm@0.1.3 on GitHub Packages includes remark-gfm, remark-directive, and remark-callouts, but:

  1. remark-citations does not exist in 0.1.3 — it was added in 0.2.x (not yet published). Without it, footnoteReference nodes retain their raw hex identifiers (a1b2c3) instead of sequential numbers (1).

  2. remark-callouts has a regex bug in 0.1.3 — the CALLOUT_REGEX uses $ without the multiline flag, so when remark-parse merges the > [!type] Title line and the body text into a single text node with \n, the regex fails to match. Callouts render as plain blockquotes with raw [!tip] text.

API

TS
import { parseContent } from '../lib/parse-content';
const { tree, citations } = await parseContent(markdownString);

Returns:

  • tree — enriched MDAST root node, ready to pass to <AstroMarkdown node={tree} />

  • citations — ordered array of Citation objects for <Sources citations={citations} />

Callout polyfill (fixCallouts)

  • Runs after parseMarkdown() returns the tree

  • Walks the tree looking for blockquote nodes whose first text child starts with [!type]

  • Matches against the first line only (splits on \n before matching), avoiding the multiline regex bug

  • Transforms matching blockquotes into containerDirective nodes with:

    • name: 'callout'

    • attributes: { type, title }

    • children: remainder of the paragraph text + subsequent blockquote children

    • data.hProperties: { class: 'callout callout-{type}' }

  • If remarkCallouts already transformed the blockquote (future LFM version), there's nothing left to match, so the polyfill is a no-op

Citation polyfill

  • Checks tree.data.citations.ordered — if populated (LFM >= 0.2), returns it directly

  • Otherwise, replicates the remark-citations logic:

    1. Collect: Walks tree depth-first collecting footnoteReference identifiers in document order

    2. Index: Assigns sequential 1-based indices by order of first appearance

    3. Enrich: Sets node.data.citationIndex and node.data.citationHex on each footnoteReference node

    4. Parse definitions: Extracts text from footnoteDefinition children, parses [Title](URL) and Published: YYYY-MM-DD patterns into structured Citation objects with fields: identifier, hex, index, title, url, source (hostname), publishedDate, raw, parsed

    5. Remove definitions: Filters footnoteDefinition nodes out of the tree (they're rendered via Sources component)

    6. Return: Sorted citation array

Pattern notes for other sites

This utility is designed to be portable. To implement LFM rendering in another site:

  1. Copy src/lib/parse-content.ts and src/components/markdown/*.astro from this site (or from packages/lfm-astro/)

  2. Install @lossless-group/lfm and mdast-util-to-string

  3. In any page: const { tree, citations } = await parseContent(entry.body)

  4. Render: <AstroMarkdown node={tree} /> and <Sources citations={citations} />

  5. When @lossless-group/lfm is published at 0.2.x+ with native remark-citations, the polyfills in parseContent will detect the existing output and skip themselves — no code changes needed

When to remove the polyfills

Once @lossless-group/lfm is published at a version that includes:

  • remark-citations with citationIndex enrichment

  • remark-callouts with a multiline-aware regex (or first-line splitting)

...the polyfill code in parseContent becomes dead weight. At that point:

  • Update package.json to require the new LFM version

  • Simplify parseContent to just const tree = await parseMarkdown(md); return { tree, citations: tree.data.citations.ordered ?? [] };

  • Or remove parse-content.ts entirely and call parseMarkdown directly in pages


Build status

  • pnpm build passes successfully

  • New pages generated:

    • /strategies/index.html

    • /strategies/why-markdown-needs-to-grow-up/index.html

  • Pre-existing Unsplash 403 warnings during prerender are unrelated (FactCard--w-Image component, missing/expired UNSPLASH_ACCESS_KEY)

Files changed (tracked)

  • package.json — added mdast-util-to-string@^4.0.0 dependency

  • src/content.config.ts — added strategies collection definition

  • src/content/people/team.json — bio text correction

  • src/content/people/team-extended.json — bio text correction

Files added (untracked)

  • src/lib/parse-content.ts — reusable LFM parsing + citation/callout polyfill

  • src/components/markdown/AstroMarkdown.astro — recursive MDAST renderer (copied from packages/lfm-astro)

  • src/components/markdown/Callout.astro — callout box component (copied from packages/lfm-astro)

  • src/components/markdown/CodeBlock.astro — code block component (copied from packages/lfm-astro)

  • src/components/markdown/MarkdownImage.astro — image directive component (copied from packages/lfm-astro)

  • src/components/markdown/Sources.astro — citation list component (copied from packages/lfm-astro)

  • src/pages/strategies/index.astro — strategies listing page

  • src/pages/strategies/[slug].astro — individual strategy article page

Known issues / follow-ups

  • LFM version: Site is on @lossless-group/lfm@^0.1.3. The parseContent polyfill covers the gap, but publishing LFM 0.2.x would let all sites drop the polyfill.

  • Callout regex bug: Exists in both the published 0.1.3 and the current workspace source (packages/lfm/src/plugins/remark-callouts.ts line 15). The CALLOUT_REGEX should either use the m (multiline) flag or split on \n before matching. This should be fixed in the LFM package itself.

  • Strategy content: Only one article exists (why-markdown-needs-to-grow-up.md). The infrastructure is ready for more.

  • No .npmrc in site root: The site relies on the monorepo root .npmrc for @lossless-group registry resolution. For independent deployment, a site-level .npmrc will be needed.