# Docs-stack conventions

> This file is intentionally **identical** in `burin-labs/burin-code`
> (`site/docs-stack-conventions.md`) and `burin-labs/harn`
> (`website/docs-stack-conventions.md`). It is the shared, "common by
> convention" contract for the two hand-rolled Mintlify-style sites:
> burincode.com (marketing + help center) and harnlang.com (Diataxis docs).
>
> We deliberately do **not** share a published engine package. The two sites
> are still moving, live in separate repos with no shared monorepo, and a
> cross-repo npm package would add release-ordering coupling worse than the
> small duplication it removes. Instead we keep the **stack, vocabulary, and
> output contracts** aligned by copying patterns and keeping this document in
> sync. Revisit extracting a package only when both engines have stabilized and
> a third consumer appears.

## Shared stack

Both sites are **React 19 + Vite + React Router 7 + Tailwind CSS v4**, with the
markdown/MDX pipeline built on the unified (`remark` / `rehype`) ecosystem.
Content is authored as markdown/MDX with YAML frontmatter; rendering, nav, and
search are generated at build time.

| Concern | burincode.com (`site/`) | harnlang.com (`website/`) |
| --- | --- | --- |
| Content format | MDX (`@mdx-js/rollup`), JSX components inline | Markdown → HTML string at build time |
| Content source | `src/content/{help,posts,changelog}` | `docs/src` (Diataxis + `SUMMARY.md`) |
| Rendering | CSR (React Router `BrowserRouter`) | SSR + prerender (`prerender.mjs`) → static HTML |
| Nav source | frontmatter `category` + Diataxis order | `SUMMARY.md` (mdBook-style) + `{{#include}}` |
| Highlighting | `rehype-highlight` + Harn grammar (see below) | `rehype-highlight` + Harn grammar (see below) |
| Component authoring | `<Callout>` / `<Card>` / `<Steps>` (MDX) | GitHub-style alert blockquotes (markdown) |

The columns differ where the **content shape** legitimately differs (a
component-rich marketing/help site vs. a 220-page language reference). Everything
below is the part we keep **common**.

## Design tokens (semantic Tailwind v4 `@theme`)

Both `src/index.css` files expose the **same semantic token names**; only the
palette values differ (burin = engraver's vermilion, harn = teal). A component
or prose rule written against these names ports between repos unchanged. Keep
this set in sync — add tokens to both, with the same names:

```text
--color-accent-{50..950}
--color-surface  --color-surface-secondary  --color-surface-tertiary  --color-surface-elevated
--color-foreground  --color-foreground-secondary  --color-foreground-muted
--color-border  --color-border-strong
--color-nav-bg  --color-card-bg  --color-card-border  --color-input-bg  --color-input-border
--font-sans  --font-mono   (burin also: --font-display)
```

### Dark mode

Manual, class-based (`.dark` on `<html>`, set by a pre-hydration inline script
keyed on `localStorage`), **not** `prefers-color-scheme`. Both sites drive the
Tailwind `dark:` utility variant off that class:

```css
@custom-variant dark (&:where(.dark, .dark *));
```

Author dark overrides as either `dark:` utilities or `.dark .foo { … }` rules —
both resolve against the same `.dark` class.

## Prose contract

Rendered article/doc bodies are wrapped in a single `.prose` container and
styled by hand (not `@tailwindcss/typography`) so the two sites share one
typographic contract. Keep heading sizes, link colors (`--color-accent-700`
light / `--color-accent-300`/`400` dark), code, table, and blockquote rules
aligned. Inline `code` uses `--color-surface-tertiary`; `pre` resets to
`color: inherit` so highlight tokens win.

## Components (`.callout` contract)

Admonitions render to the **same HTML/class contract** in both repos, so the CSS
block in `index.css` is identical:

```html
<div class="callout callout-{note|tip|info|warning}">
  <svg class="callout-icon" …>…</svg>
  <div class="callout-body">
    <p class="callout-title">Tip</p>
    …markdown…
  </div>
</div>
```

Type vocabulary: `note` · `tip` · `info` · `warning`. Authoring differs by
surface but maps to the same output:

- **burincode.com (MDX):** `<Callout type="tip" title="…">…</Callout>`
- **harnlang.com (markdown):** GitHub-style alerts —
  `> [!NOTE]` · `> [!TIP]` · `> [!IMPORTANT]` (→ `info`) · `> [!WARNING]` ·
  `> [!CAUTION]` (→ `warning`).

Icons use the same SVG paths in both repos. Keep them in sync if either changes.

## Syntax highlighting (Harn grammar)

Code blocks are highlighted at build time with `rehype-highlight` (lowlight /
highlight.js). The **Harn language grammar** is shared by convention:

- Source of truth for keywords: `harn`'s
  `docs/theme/harn-keywords.js`, generated by
  `cargo run -p harn-cli -- dump-highlight-keywords` (`make gen-highlight`).
- `harn/website/vite-plugins/content.ts` builds the highlight.js language from
  that file directly.
- `burin-code/site/src/lib/harn-highlight.ts` **vendors** the same grammar
  builder and a snapshot of the keyword table (it has no Rust toolchain). When
  Harn's keywords change materially, refresh the vendored snapshot.

Highlighted tokens use highlight.js `.hljs-*` classes; both `index.css` files
carry the same token-class rules, recolored to each brand's accent.

## Search

Client-side, build-time index, no external service. Target record shape:

```ts
{ slug, url, title, sectionTitle?, headings?: string[], text }
```

harnlang.com already uses this shape (`_content/search.json`, AND-term
scoring). burincode.com's help search currently filters
`{ title, description, body }` substrings; converge it toward the shape above as
the help center grows.

## Navigation, TOC, prev/next

- **Prev/next** computed from the canonical order (Diataxis category + sort
  order on burin; `SUMMARY.md` order on harn).
- **On-this-page TOC** collects `h2`/`h3`, tracks the active heading with an
  `IntersectionObserver`.
- Sidebar/section nav is generated from the nav source above, never hardcoded.

## SSG / prerender status

- **harnlang.com:** prerendered to static HTML per route (`prerender.mjs`) —
  crawlable, works with JS disabled. This is the reference implementation.
- **burincode.com:** the SSR-entry + prerender pipeline is **wired** (ported
  from harn): `npm run build` emits static HTML per route. The site stays
  `noindex,nofollow` until launch, so the prerender currently just improves
  first-paint and makes the site crawl-ready. **Turning on SEO at launch is a
  one-line switch** — remove the `noindex,nofollow` meta in `index.html`; no
  further build or infra change is required. Keep the marketing/landing surface
  SSR-safe (guard `window`/`localStorage`/`matchMedia` access behind effects or
  `typeof window` checks) so the prerender never regresses.
