# 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:

```text
/Users/neeldhara/obsidian/public/.obsidian/plugins/microblog-sync
```

The versioned copy lives in this repo at:

```text
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:

```text
blogs/01-research/example-post/index.md
blogs/01-research/example-post/diagram.png
```

The folder path becomes the public URL:

```text
blogs/01-research/example-post/index.md
https://research.neeldhara.blog/example-post/
```

Nested folders are supported:

```text
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:

```yaml
---
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.

Use colocated relative assets:

```markdown
![Caption](image.png)
```

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. This is the canonical form:

```markdown
> **Note**
>
> Text of the callout.
```

Supported labels are `Note`, `Tip`, `Warning`, `Caution`, and `Aside`:

```markdown
> **Warning**
>
> This point needs attention.
```

Obsidian-style labels also work:

```markdown
> [!tip]
> This is a tip.
```

For quick writing, a few leading emoji markers are recognized and stripped:

```markdown
> 📝 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:

```text
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 label. 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:

```markdown
> **Aside:** text
```

### Emoji And Symbol Text

Write emoji, chess pieces, and card suits directly in Markdown:

```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:

```html
<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:

```text
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:

````markdown
```{.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:

```text
sites/<blog>/src/interactives/tape-layout/index.tsx
```

The component receives:

```ts
{
  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="..."`:

````markdown
```{.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:

````markdown
```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:

````markdown
```{.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:

```text
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:

```tex
\begin{theorem}[Hall's theorem]
\label{thm:hall}
...
\end{theorem}
```

Astro also accepts shorter forms when PDF export is not needed:

````markdown
```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:

```markdown
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:

```text
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:

```text
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 note`
- `Blog Family Sync: Publish current note`
- `Blog 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:

1. Sync vault content into the local repo.
2. Run the configured Astro build command.
3. Commit and push to Forgejo, where Dokploy picks up the change.

## PDF Export

PDF export uses Pandoc plus LuaLaTeX:

```bash
npm run pdf:post -- sites/research/src/content/research/example/index.md --out /tmp/example.pdf
```

The command runs:

- `scripts/pandoc-filters/semantic-blocks.lua` to convert interactive and
  environment fences, internal links, Markdown callouts, emoji, chess pieces,
  and card suits.
- `scripts/pandoc-templates/semantic-preamble.tex` to load `amsthm`,
  `cleveref`, `tcolorbox`, `emoji`, `chessfss`, and the shared theorem,
  callout, and symbol definitions.

Current smoke-test PDFs live outside the repo:

```text
/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`.
