13 KiB
Blog Content Bridge
The public Obsidian vault is the writing source of truth. The Astro sites are deployable targets that receive copied Markdown and assets.
Folder Map
| Vault folder | Astro content folder | Public domain |
|---|---|---|
blogs/01-research |
sites/research/src/content/research |
https://research.neeldhara.blog |
blogs/02-vibes |
sites/vibes/src/content/vibes |
https://vibes.neeldhara.blog |
blogs/03-puzzles |
sites/puzzles/src/content/puzzles |
https://puzzles.neeldhara.blog |
blogs/04-reflections |
sites/reflections/src/content/reflections |
https://reflections.neeldhara.blog |
blogs/05-poetry |
sites/poetry/src/content/poetry |
https://pieces.neeldhara.blog |
blogs/06-reviews |
sites/reviews/src/content/reviews |
https://reviews.neeldhara.blog |
blogs/07-art |
sites/art/src/content/art |
https://art.neeldhara.blog |
blogs/micro |
../micro/src/content/blog |
https://micro.neeldhara.blog |
The installed Obsidian plugin lives at:
/Users/neeldhara/obsidian/public/.obsidian/plugins/microblog-sync
The versioned copy lives in this repo at:
obsidian-plugin/microblog-sync
The plugin id remains microblog-sync, but Obsidian shows it as Blog Family
Sync.
Post Layout
Use one folder per post:
blogs/01-research/example-post/index.md
blogs/01-research/example-post/diagram.png
The folder path becomes the public URL:
blogs/01-research/example-post/index.md
https://research.neeldhara.blog/example-post/
Nested folders are supported:
blogs/04-reflections/exportober/2021/index.md
https://reflections.neeldhara.blog/exportober/2021/
Frontmatter
Every public post needs the fields required by the Astro content schema:
---
title: "Post title"
description: "One or two sentence summary for cards and RSS."
pubDate: "2024-05-10"
authorName: "Neeldhara"
---
Optional fields currently understood by the sites include updatedDate,
image, and authorImage. Prefer colocated images in the body over remote
stock images.
Use main-feature: true on at most one post per blog to pin a fixed featured
post on the neeldhara.blog homepage. The homepage still shows the two newest
non-featured posts for that blog, sorted by pubDate.
The Quarto import also preserved sourceCategories and sourcePath. Astro
strips unknown schema fields, but they are useful in Obsidian for provenance.
Markdown Conventions
Standard Markdown headings, lists, links, tables, code fences, footnotes, and raw HTML are preserved.
This section is the short writer-facing version. The reusable implementation
contract for future site families is documented in
docs/PARSER.md.
Use colocated relative assets:

Quarto image attributes such as {width=70%} were removed during import. Going
forward, use normal Markdown and let Astro CSS handle presentation.
Callouts
Write callouts as portable blockquotes. The type controls the visual styling, but it does not create a visible heading by itself. This is the canonical form for a titleless note:
> **Note**
>
> Text of the callout.
Supported labels are Note, Tip, Warning, Caution, and Aside:
> **Warning**
>
> This point needs attention.
Obsidian-style bracket labels also work. A plain bracket label is still titleless:
> [!tip]
> This is a tip.
To show a heading, put it explicitly inside the bracket after : or |:
> [!tip: Useful observation]
>
> Text of the callout.
> [!warning|Check this assumption]
>
> This point needs attention.
The text after : or | is treated as the only callout heading. Do not write
> [!tip] Useful observation for a heading; that form is interpreted as a
titleless tip whose body begins with “Useful observation”. This keeps the
Markdown unambiguous across Astro, Pandoc, and LaTeX.
For quick writing, a few leading emoji markers are recognized and stripped:
> 📝 This becomes a note.
> 💡 This becomes a tip.
> ⚡ This also becomes a tip.
> ⚠️ This becomes a warning.
In Astro these render as <aside class="callout callout-note">, etc. In the
PDF pipeline they render as pastel tcolorbox blocks:
note -> blognotebox
tip -> blogtipbox
warning -> blogwarningbox
caution -> blogcautionbox
aside -> blogasidebox
The LaTeX boxes use tcolorbox with the breakable and enhanced libraries,
a light tinted background, a thin frame, a stronger left rule, and a small
attached title only when the Markdown explicitly provides one. This follows the
same design vocabulary as the DM book callouts: compact colored boxes that can
split across pages without becoming visually heavy.
Quarto margin notes such as [text]{.aside} were converted to:
> **Aside:** text
Emoji And Symbol Text
Write emoji, chess pieces, and card suits directly in Markdown:
I like this move: ♘f3. Hearts and diamonds are red: ♥ ♦. Spades and clubs are black: ♠ ♣.
This reminder is visible in both outputs: 🎯
Astro wraps these glyphs in small semantic spans so they get stable font fallbacks and accessible labels:
<span class="symbol symbol-chess" data-symbol="white-knight" aria-label="white knight">♘</span>
<span class="symbol symbol-card" data-symbol="heart" aria-label="heart">♥</span>
<span class="symbol symbol-emoji">🎯</span>
The PDF pipeline maps the same characters to LaTeX macros:
emoji and emoji sequences -> \BlogEmojiText{...} using the emoji package
♔ ♕ ♖ ♗ ♘ ♙ -> \BlogChessWhite{\symking}, etc. using chessfss
♚ ♛ ♜ ♝ ♞ ♟ -> \BlogChessBlack{\symking}, etc. using chessfss
♥ ♡ ♦ ♢ -> red \heartsuit / \diamondsuit via amssymb + xcolor
♠ ♤ ♣ ♧ -> black \spadesuit / \clubsuit via amssymb + xcolor
playing-card emoji -> \BlogEmojiText{...}
Use Unicode characters in the source. Do not write LaTeX-only symbols in posts unless the post is PDF-only, because raw LaTeX will not generally survive the HTML path.
Semantic Blocks
Use Pandoc-style fenced attributes for blocks that need to render differently in HTML and PDF. This is the preferred form because it works cleanly with both Astro and Pandoc/LuaLaTeX.
Interactive Blocks
An interactive block reserves a place in the post for a React component and also supplies fallback text for PDF or non-JavaScript rendering:
```{.interactive #int:tape-layout caption="Tape layout explorer"}
Drag files along a tape and compare the access cost of each arrangement.
```
The id after # is the semantic label. For interactives, use the int: prefix.
The component id is inferred by removing that prefix, so the example above
mounts this component when it exists:
sites/<blog>/src/interactives/tape-layout/index.tsx
The component receives:
{
id: "tape-layout",
label: "int:tape-layout",
caption: "Tape layout explorer",
description: "Drag files along a tape...",
fallback: "Drag files along a tape...",
attrs: { caption: "Tape layout explorer" }
}
If the component is not present, Astro renders the fallback text in the
interactive frame. For a different PDF fallback, add pdf="...":
```{.interactive #int:tape-layout caption="Tape layout explorer" pdf="The HTML version contains an interactive tape layout explorer."}
Implementation notes for the interactive can go here.
```
Astro also accepts the shorter Obsidian-friendly form:
```interactive id="tape-layout" caption="Tape layout explorer"
Drag files along a tape and compare the access cost.
```
Use the Pandoc-style form for posts that should export to PDF.
LaTeX-Style Environments
Use .env plus the environment class and a label:
```{.env .theorem #thm:hall title="Hall's theorem" html="callout"}
Every bipartite graph satisfying Hall's condition has a matching that covers
the left side.
```
Supported environment types are:
theorem, lemma, proposition, corollary, definition, example, remark, proof
In Astro, html="callout" renders the block like a callout. html="plain"
renders it as a quieter left-rule block. If html is omitted, theorem-like
blocks default to callout, and proof defaults to plain.
In PDF, the same block becomes a real LuaLaTeX environment:
\begin{theorem}[Hall's theorem]
\label{thm:hall}
...
\end{theorem}
Astro also accepts shorter forms when PDF export is not needed:
```theorem id="thm:hall" title="Hall's theorem"
Every bipartite graph satisfying Hall's condition has a matching...
```
Linking To Semantic Blocks
Use ordinary Markdown links to semantic ids:
The proof of [Hall's theorem](#thm:hall) is constructive.
See the [tape explorer](#int:tape-layout) for an interactive version.
Astro renders these as normal HTML links. The PDF pipeline converts internal
links whose target starts with # into \cref{...}, so the first link above
becomes \cref{thm:hall}.
Use these label prefixes consistently:
thm: theorem
lem: lemma
prop: proposition
cor: corollary
def: definition
ex: example
rem: remark
prf: proof
int: interactive
fig: figure
Import Rules
The import script is:
scripts/import-quarto-blog.mjs
It reads the Quarto archive, skips draft: true, converts index.qmd to
index.md, copies colocated assets, and writes a temporary tree under
/private/tmp/quarto-blog-import.
The first import made these category choices:
| Blog | Rule of thumb |
|---|---|
research |
CS/math exposition, lecture notes, and academic explainers. |
vibes |
LLM-assisted making, interactives, and generated-tool experiments. |
puzzles |
Puzzle/game/problem posts. |
reflections |
Essays, speeches, teaching/career notes, and Exportober context. |
poetry |
Poems. |
reviews |
Tool, workflow, software, website, book-list, and tutorial posts. |
art |
Visual/sketchnote/design/communications-oriented posts. |
See IMPORT_REPORT.md for the specific source-to-destination mapping.
Sync Rules
- Obsidian is the source of truth for day-to-day writing.
- Auto-sync copies changes from the matching
blogs/...folder into the matching local Astro content folder. - The plugin includes Markdown plus common web assets: images, PDFs, JSON/YAML, JS, CSS, HTML, and text files.
- One-way sync does not delete target files unless Delete orphaned target files is enabled.
- The initial Quarto import used replace-style sync to clear placeholder Astro posts. Day-to-day editing should use the plugin.
Preview And Publish
Use these Obsidian commands:
Blog Family Sync: Sync and preview current noteBlog Family Sync: Publish current noteBlog Family Sync: Publish all changes for current blog
Preview detects the current note's folder, starts the matching Astro dev server on its configured local port, and opens the post URL.
Publish does three things:
- Sync vault content into the local repo.
- Run the configured Astro build command.
- Commit and push to Forgejo, where Dokploy picks up the change.
PDF Export
PDF export uses Pandoc plus LuaLaTeX:
npm run pdf:post -- sites/research/src/content/research/example/index.md --out /tmp/example.pdf
The command runs:
scripts/pandoc-filters/semantic-blocks.luato convert interactive and environment fences, internal links, Markdown callouts, emoji, chess pieces, and card suits.scripts/pandoc-templates/semantic-preamble.texto loadamsthm,cleveref,tcolorbox,emoji,chessfss, and the shared theorem, callout, and symbol definitions.
Current smoke-test PDFs live outside the repo:
/private/tmp/blog-pdf-samples/callout-symbol-test.pdf
/private/tmp/blog-pdf-samples/vibes-building-interactives.pdf
The PDF route is intentionally separate from Astro. Astro owns HTML rendering;
Pandoc owns PDF rendering. The Markdown conventions above are the shared
contract between both outputs and can be reused for future site families such
as neeldhara.courses and books.neeldhara.com.