asw/docs/template-h1-title.md
Ludo 15a6db9c0e
refactor: rename content types to semantic taxonomy
- vault → notes (PKM-exported content)
- posts → articles (short-form, no TOC)
- papers → essays (long-form, with TOC)
- type: post → type: article (posts are just short articles)
- layouts/paper → layouts/essay
- 08a-paper.css → 08a-essay.css
- CSS: fix redundant li resets, remove role="main" from article,
  replace <small> prev/next labels, add console layout
- Update hugo.toml menus, internal URLs, front matter throughout
- Add docs/context.md, docs/css-refactor-plan.md
2026-04-11 13:36:58 +02:00

73 lines
2.5 KiB
Markdown

# Template Design: Handling the Markdown H1 / Front Matter Title Conflict
## The Problem
Standard markdown convention — Obsidian, agent-written files, generic `.md` — opens
with a level-1 heading as the document title:
```markdown
# My Note Title
Body text...
```
Hugo templates also render a title from front matter:
```html
<h1>{{ .Title }}</h1>
```
When both exist, the page gets two `<h1>` elements: one from the template,
one from the rendered markdown content. The content one also carries an
auto-generated `id` attribute from Hugo's heading anchor renderer.
## The Two Template Contracts
**Default (`_default/single.html`)** — bare markdown, minimal or no front matter.
The `# Title` in content IS the h1. The template header renders only metadata
(type, date, author, tags). No title rendered from front matter.
**Vault (`vault/single.html`)** — enriched front matter (`title`, `type`, `date`,
`author`, `tags`). Front matter `title` is authoritative. The `# Title` in content
is still present (markdown convention) but must be suppressed.
## The Fix: Engine-Agnostic Regex Strip
When a template owns the title (renders `<h1>{{ .Title }}</h1>` from front matter),
strip the first h1 from the rendered content before outputting it.
**Hugo:**
```
{{ replaceRE "<h1[^>]*>.*?</h1>" "" .Content 1 | safeHTML }}
```
**Jinja2 / Flask:**
```python
import re
content = re.sub(r'<h1[^>]*>.*?</h1>', '', content, count=1, flags=re.DOTALL)
```
**Nunjucks / Liquid / any engine:** equivalent string replace on the rendered HTML.
This is a string operation on already-rendered HTML, not a template-engine concept.
It ports to any engine without modification.
## Why Not Other Approaches
- **Author convention** (don't write `# Title` in vault files): breaks compatibility
with the entire markdown ecosystem.
- **Hugo render hooks** (`layouts/vault/_markup/render-heading.html`): Hugo-specific,
not portable.
- **CSS `display: none`**: h1 still exists in DOM — screen readers read it,
search engines index it. Semantically wrong.
## Engine-Agnostic Principle
ASW templates are prototyped in Hugo but must be portable to Flask/Jinja2 or
any other engine. Template logic should express *what*, not *how*:
- What: "strip h1 from content if front matter title is present"
- How: engine-specific implementation of the same string operation
Hugo-specific features (render hooks, shortcodes) are acceptable as prototyping
tools but should not become load-bearing parts of the template design.