Maintain an Elegant Markdown and Extended Markdown Render Pipeline
Blueprint: How Lossless Renders Markdown and Extended Markdown
1. Content Locations and Sources
- Root content monorepo
- Primary authoring may or may not happen on
- a directory OUTSIDE the current Astro project, thus needing custom routing.
- a directory INSIDE the current Astro project, but not the src/content file.
- a git submodule for easier collaborative editing of content.
- SOME COMBINATION OF THE ABOVE.
- Astro content collections wiring
- Once defined in
site/src/content.config.ts. - Uses a helper
resolveContentPath(relativePath)that:- Joins
contentBasePath(from env) with the relative path. - Converts the absolute path into a
file://URL for Astro’sglobloader.
- Collections are defined in the loosest way possible, where almost all metadata is optional and the object is a passthrough. This is because there are inconsistent behaviors in maintaining consistent YAML frontmatter, but we don't want to site builds to fail.
paths.blueprintsis mapped toresolveContentPath('lost-in-public/blueprints'), so this blueprint is rendered through the same pipeline.
- Generated markdown content
- Some markdown lives under
site/src/generated-content/**(e.g. generated essays, prompts, blueprints). resolveContentPathis aware of this: if a path already starts with./src/generated-content, it is left as-is and not re-resolved.
- MDX pages
- A separate
pagesCollectionis defined formdx-pages, but this blueprint focuses on the markdown → MDAST →AstroMarkdownpipeline. We have not successfully managed to implement MDX pages but only because we have been focused on Extended Markdown with adding various syntax and mapping it to a new component.
2. Astro Global Markdown Configuration
- Location:
site/astro.config.mjs→markdownblock. - Remark layer (global)
syntaxHighlight: falseto disable Astro’s built-in Shiki; highlighting is handled separately.remarkPlugins: []– all remark processing for articles has been moved to layout-level processing (primarilyOneArticle.astro).
- Rehype layer (global)
rehypeRaw(must come first):- Allows raw HTML inside markdown (
node.type === "html"), which is later rendered byAstroMarkdownor passed through.
rehypeAutolinkHeadings:- Appends
#anchor links to headings with CSS classes likeheader-anchor/header-anchor-symbol.
rehypeMermaid:- Handles mermaid diagrams with an
img-svgstrategy and dark mode.
- Takeaway
- Global markdown config handles HTML safety, heading anchors, and mermaid.
- All semantic markdown and extended-markdown behavior is defined in the layout + component pipeline below.
3. Primary Article Rendering Pipeline
3.1 Overview
The main “Lossless article” path for markdown is:
- Content collections (from
content.config.ts) provide a markdown string (body) and frontmatter (data). - A page/route uses
OneArticle.astroas its layout, passingcontentanddata. OneArticle.astrousesunified+ remark plugins to produce a transformed MDAST.OneArticleOnPage.astroreceives the transformed MDAST and frontmatter, splits out the table of contents, and passes the rest toAstroMarkdown.astro.AstroMarkdown.astrorecursively renders the MDAST nodes into HTML and Astro components, including all extended markdown features.
3.2 OneArticle.astro – Layout-Level Markdown Processing
- Location:
site/src/layouts/OneArticle.astro. - Inputs (props):
Component: usuallyOneArticleOnPage.astroor compatible article component.title: article heading.data: frontmatter / content metadata.content: raw markdown string.markdownFile?: optional path for debugging.
- Unified pipeline
- Builds a remark processor:
remarkParse– parse markdown into MDAST.remarkGfm– GitHub-flavored markdown support (tables, task lists, etc.).remarkBacklinks– custom plugin to handle Obsidian-style backlinks and internal links.remarkImages– custom plugin for image normalization and path handling.remarkDirective– parses directive syntax, creatingleafDirective,containerDirective,textDirectivenodes.remarkDirectiveToComponent– Lossless plugin that:- Validates directive names.
- Preserves directive nodes in the MDAST for downstream handling (no immediate HTML transform).
remarkCitations– citations and references processing.remarkTableOfContents– injects atableOfContentsnode into the MDAST.
- Directive node types (how remarkDirective shapes the AST)
textDirective- Inline, used inside paragraphs or headings.
- Example:
This is a :badge[New] inline directive. - Becomes a
textDirectivenode withname,attributes, and inlinechildren.
leafDirective- Block-level, no nested markdown content (self-contained block).
- Typical for single-line component-style directives.
- Example:
::figma-embed{src="https://www.figma.com/..." width="800"}. - Becomes a
leafDirectivenode withnameandattributes, usually an emptychildrenarray.
containerDirective- Block-level, with nested markdown children (lists, paragraphs, etc.).
- Used for directives that wrap other markdown, like tool galleries or slides.
- Example:markdown
:::tool-showcase - [[Tooling/AI-Toolkit/Tool Name|Display Name]] - [[vertical-toolkits/Category/Another Tool|Another Tool]] ::: - Becomes a
containerDirectivenode whosechildrencontain lists / listItems / paragraphs.
In the Lossless pipeline,remarkDirectiveandremarkDirectiveToComponentare responsible for creating and preserving these directive nodes in the MDAST.AstroMarkdown.astrothen inspectsnode.type(textDirective,leafDirective,containerDirective) andnode.name(e.g.figma-embed,tool-showcase,tooling-gallery,image-gallery,slides) to decide which Astro component to render and how to interpretattributesandchildren. - Flow:
mdast = processor.parse(content)transformedMdast = await processor.run(mdast)
- Output
- Passes
transformedMdastand normalizeddatainto theComponent(usuallyOneArticleOnPage).
3.3 OneArticleOnPage.astro – Layout + TOC + Info Sidebar
- Location:
site/src/components/articles/OneArticleOnPage.astro. - Responsibilities
- Layout for a single article including:
- Main content (
AstroMarkdown). - Optional
InfoSidebar(metadata, tags, authors, dates, semantic version, augmented_with, etc.). - Table of contents (desktop and mobile).
- Calculates
effectiveHeadingfromarticleHeadingordata.titleand displays it as the main rendered<h1>with aCopyLinkButton. - Normalizes
databefore passing it down:- Ensures
authorsis an array. - Formats dates.
- Prepares
dataForMarkdownpassed intoAstroMarkdown, optionally strippingtitleto avoid duplicate titles.
- TOC management
- Accepts
content: Root(MDAST root) fromOneArticle.astro. - Computes:
safeContent: always a validrootwithchildren.children:safeContent.children(defensive check).hProperties: fromsafeContent.data.hProperties || {}.
- Derives
tocNodeby searching forchild.type === 'tableOfContents'. - Passes
tocNode.data.maptoTableOfContents.astro. - For main content:
- Calls
AstroMarkdownwith a syntheticroot:type: 'root'.children: children.filter(child => child?.type !== 'tableOfContents').data: { hProperties }.
3.4 AstroMarkdown.astro – Core Extended Markdown Renderer
- Location:
site/src/components/markdown/AstroMarkdown.astro. - Inputs (props):
node: any MDAST node (root, heading, paragraph, link, directive, etc.).data: includespath,id, and propagated frontmatter fields.
- Core behaviors
- Maintains an explicit list
handled_typesincluding:- Standard nodes:
root,paragraph,text,heading,image,list,listItem,code,inlineCode,table*,strong,emphasis,break,html, etc. - Extended nodes:
citation,citations,citationReference,footnote*,tableOfContents,imageGallery,toolingGallery,thematicBreak. - Directive nodes:
leafDirective,containerDirective,textDirective.
- Uses recursive self-calls (
<Astro.self node={child} data={data} />) to walk the MDAST tree. - Enriches
data.dirpath = dirname(data.path)for image/gallery helpers.
- Major node handling (high level)
root: renders all children via recursion.heading: usesextractAllText+slugifyto produce stableids for headings and wraps them withCopyLinkButton.list/listItem: renders ordered/unordered lists with a.custom-liclass and nested spacing rules.table,tableRow,tableCell: renders semantic tables with a scrollable wrapper.link:- Detects YouTube videos / playlists / Shorts by URL patterns and renders:
YouTubeEmbed,YouTubePlaylistEmbed, orYouTubeShortsEmbed.
- Falls back to a normal
<a>withhPropertiesfor standard links.
code:- Handles legacy extended syntaxes using code block languages/meta:
toolingGalleryandyaml toolingGallery→ rendersToolingGalleryand shows a deprecation warning, encouraging:::tooling-gallerydirectives instead.imageGalleryandyaml imageGallery→ rendersImageGallerywith a deprecation warning, encouraging:::image-gallerydirectives.slides→ parses an inline config + backlink list and rendersSlidesEmbed.
- For all other languages:
- Delegates to
BaseCodeblockusinggetLanguageRoutingStrategyandisSpecialRendererLanguagefromshikiHighlighter.
inlineCode: styles inline code tokens.html: injects raw HTML viaset:html, relying onrehypeRawfromastro.config.mjs.blockquote: usesArticleCalloutfor styled callouts.citations/citation: usesArticleCitationsBlockandArticleCitationcomponents.
- Directive handling (extended markdown)There are two primary patterns for directives in the Lossless pipeline:
- Remark-time mapping and preservation (in
remark-directives.ts):- Directives are validated and preserved as directive nodes.
remarkDirectiveToComponentleaves them forAstroMarkdownto inspect.
- Render-time interpretation in
AstroMarkdown:AstroMarkdowninterpretsleafDirectiveandcontainerDirectivenodes and renders the appropriate components.
Key directive types handled inAstroMarkdown:figma-embed(leaf and container):- Uses props like
src/url,width,height, andauth-user. - Renders a Figma embed iframe with a footer link.
- Full architecture and Figma-specific behavior are documented in the existing blueprint “Maintain Directives in Extended Markdown Render Pipeline”.
tool-showcase(container):- Parses markdown list items inside the directive.
- Extracts backlink patterns
[[path/to/tool|Display Name]]using helper functions. - Resolves tools via
getCollection('tooling')andToolShowcaseIsland.astro. - Renders an interactive tool carousel.
tooling-gallery(container):- New directive-based replacement for the YAML
toolingGallerycode block. - Parses list items for backlinks and tag filters.
- Uses
getCollection('tooling')andresolveToolIdfromtoolUtils. - Renders
ToolingGallery.astrowith an optionalsmallvariant and tag filters.
portfolio-gallery(container):- Similar to
tooling-gallery, but forclient-portfolioscollection. - Parses list items for backlinks and
tag:filters. - Uses
getCollection('client-portfolios')andresolvePortfolioId. - Renders
PortfolioGallery.astrowith normalized portfolio data.
image-gallery(container):- Parses list items for links or plain text URLs.
- Builds a mini YAML-like code string and passes it to
ImageGallery.astro. - New directive-based replacement for
imageGalleryYAML code blocks.
slides(planned + partially implemented):- Under the directory & directive blueprints, a
slidesdirective is planned to use the same slide-selection model as theslidescode block andSlidesEmbed.astro. SlidesEmbed.astrobuilds an embed URL that targets the/slides/embed/[...slug].astroroute, which renders Reveal.js-based markdown decks.
All directive behavior is designed to be lossless across the pipeline:- Micromark (via
remark-parse) parses the directive syntax. remark-directiveconverts it into MDAST directive nodes.remarkDirectiveToComponentvalidates and preserves the nodes.AstroMarkdowninspects directive names and attributes, and renders appropriate components.
4. Simple Markdown Renderer (Non-Article Use)
- Location:
site/src/utils/simpleMarkdownRenderer.ts. - Purpose
- Provide a simple way to render markdown to HTML + plain text, primarily for:
- Previews.
- Email bodies.
- Tooling that does not use the full article layout.
- Pipeline
- Uses the same remark stack as
OneArticle.astro:remarkParse,remarkGfm,remarkBacklinks,remarkImages,remarkDirective,remarkDirectiveToComponent,remarkCitations,remarkTableOfContents.
- Then:
remarkRehype→rehypeStringify.
- Returns:
html– full rendered HTML string.plainText– string with all HTML tags stripped.
- Important note
- The simple renderer does not run through
AstroMarkdown.astro, so directive nodes will not become rich components here; they will be treated as transformed HTML according to how the plugins behave. - For full extended markdown behavior (directives, galleries, Figma, etc.), use the OneArticle + AstroMarkdown pipeline.
5. Debugging and Introspection
- Markdown debugger
site/src/utils/markdown/markdownDebugger.tsexposes amarkdownDebuggersingleton used by layout-level code.- Controlled via environment variables:
DEBUG_MARKDOWN– enable/disable logging.DEBUG_MARKDOWN_VERBOSE– enable detailed logs.DEBUG_AST– allow writing debug files viaastDebugger.
- Also supports URL query parameters:
?debug-markdownand?debug-markdown-verbose.
- DebugMarkdown component
OneArticle.astrocan renderDebugMarkdown.astrowhenmarkdownFileis provided.- Shows raw markdown and debugging aids for a specific source file.
- Recommended debugging flow
- Enable
DEBUG_MARKDOWNand optionallyDEBUG_AST. - Inspect
transformedMdaststructure to confirm:- Directives are present as
leafDirective/containerDirective. tableOfContentsnode is correctly injected.- Custom nodes like
citations,imageGallery, etc. are present.
6. How to Safely Extend the Pipeline
When adding new extended markdown features:
- 1. Start at the directive & AST layer
- Decide on a directive syntax (leaf vs container) or a code block language.
- Add validator / preservation logic in
remark-directives.tsif needed.
- 2. Add render-time handling in AstroMarkdown
- Extend the
handled_typeslist if introducing a new node type. - In
AstroMarkdown.astro, add a new branch for your directive:- Parse attributes / inner markdown content.
- Render an Astro component or island.
- 3. Keep layout responsibilities separate
OneArticle.astroremains the place for defining remark pipeline order.OneArticleOnPage.astromust continue to:- Extract
tableOfContentsinto a separate component. - Pass a clean
rootnode intoAstroMarkdown.
- 4. Respect content locations
- Add new collections in
src/content.config.tsusingresolveContentPath. - Keep authoring under the root
/contentdirectory unless there is a clear reason to usesite/src/generated-content.
- 5. Document changes
- For any substantial new extended markdown feature, create:
- A blueprint under
content/lost-in-public/blueprints. - Optionally, a spec under
content/specsfor deeper implementation details.
This blueprint ties together the Lossless site’s markdown and extended-markdown pipeline—across content locations, the Astro config, the unified/remark stack, and the
AstroMarkdown renderer—so future work can extend it without breaking existing behavior.