mdcms/CLAUDE.md
Claude c0d84f8d1f
Add CLAUDE.md with codebase documentation for AI assistants
Documents the two-part architecture (mdcms.py CLI + index.html renderer),
menu option reference, content structure, frontmatter fields, category system,
nav.yml behaviour, dynamic post tags, known limitations, and key
implementation quirks (non-general YAML parsers, category code validation).

https://claude.ai/code/session_018gathVoTZhBFDJVp1mVaTc
2026-04-27 04:27:24 +00:00

127 lines
6.2 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this project is
MD-CMS is a markdown-based static site system with two distinct parts:
1. **`mdcms.py`** — a zero-dependency Python 3 CLI tool (standard library only). It scans content, generates `nav.yml` and `search.json`, validates config, and packages a zip for deployment.
2. **`website/index.html`** — a single-file browser renderer that reads markdown, config, and nav at runtime entirely client-side. There is no build pipeline, no compilation, no server.
The `website/` folder is the deployable artifact. `mdcms.py` lives outside it.
## Running the CLI
```bash
python3 mdcms.py
```
This opens an interactive menu. Key options:
| Option | Action |
|--------|--------|
| 1 | Validate config + nav, rebuild search.json, export `website.zip` |
| 2 | Interactive wizard to create `config.yml` and folder structure from scratch |
| 3 | Scan `pages/` and `posts/`, write `nav.yml` and `search.json` |
| 4 | Rebuild `search.json` only (faster, for content-only updates) |
| 5 | Check and interactively fix `config.yml` issues |
| 6 | Check and interactively fix `nav.yml` issues |
| 7 | Register/switch website paths (stored in `~/.mdcms/paths.json`) |
| 8 | Start `python3 -m http.server 8800` and open browser |
**Local preview:** Always use option 8 (or run `python3 -m http.server 8800` in `website/`) rather than opening `index.html` directly — browsers block local file access due to CORS.
## Architecture of `mdcms.py`
The file is a single-module Python script with these logical layers, in order:
1. **Path registry**`~/.mdcms/paths.json` stores named project paths. `active_path()` returns the currently selected project.
2. **Frontmatter parser** (`parse_frontmatter`) — reads `---` YAML blocks with a hand-rolled parser (no PyYAML). Returns a flat `{key: value}` dict with type coercion (bool, int, quoted strings).
3. **Category system** — category codes are parsed from `config.yml` by `parse_config_categories()`. Variant files use `basename.<code>.md` naming (e.g. `about.nb.md`). `identify_variant()` only recognises a suffix as a category code if it appears in the declared code list.
4. **Scanner** (`scan_and_categorize`) — walks `pages/` or `posts/`, skips drafts (`draft: true` in frontmatter), returns records including first 5000 chars of body for search indexing.
5. **Nav/search generators**`generate_nav_yml()` and `generate_search_json()` produce the output files. `parse_nav_yml()` is a hand-rolled parser for reading back nav.yml (not a general YAML parser — only handles the exact format it emits).
6. **Validators** (`validate_config`, `validate_nav`) — return lists of `{field, issue, current}` dicts for display; used in options 1, 5, and 6.
7. **Menu actions** (`do_*` functions) — one per menu option, called by `main_menu()`.
## Content structure inside `website/`
```
website/
index.html ← renderer (edit to change site behaviour)
config.yml ← required: sitename, navigation; rest optional
nav.yml ← generated; manual edits to section metadata are preserved on rebuild
search.json ← generated
pages/
home.md ← default landing page
about.md ← default variant
about.nb.md ← Norwegian variant (category suffix = nb)
posts/
2025-01-01-my-first-post.md
assets/
fonts/
images/
```
## Page frontmatter fields
All optional except `title`:
```yaml
---
title: Page Title
sort: 100 # controls nav ordering (lower = higher)
section-id: blog # assigns page to a nav section
draft: true # exclude from nav and search
author: Name
date: 2025-01-01
datetime: 2025-01-01 13:00 # use this for posts (not `date` alone — see known limitations)
modified: 2025-01-15 09:00
keywords: foo, bar
description: Short description for search
language: en
---
```
## nav.yml structure
Sections and pages are separate lists. `mdcms.py` preserves manual edits to section fields (`defaultname`, `sort`, `parent`, `parent-sort`, `pagesvisibility`, `categorynames`) on each rebuild. New sections are auto-created from `section-id` values found in frontmatter.
`pagesvisibility` can be `visible`, `hidden`, or `draft` (draft excludes pages from `search.json`).
For nested navigation, set `parent: <parent-section-code>` and `parent-sort` on a section.
## Category system
- `categories-use: yes` in `config.yml` enables categories
- `default-category.code` is required when categories are enabled
- Variant files: `<base>.<code>.md` — the suffix is only treated as a category if the code is declared in config
- `categories-sectionnames: per-category` requires each section in `nav.yml` to have a `categorynames` block with an entry per category code
- RTL is set per category via `direction: rtl`
## Dynamic post tags (mdcms code blocks)
Embed post lists in pages using fenced blocks:
````markdown
```mdcms
posts-datetime-reversechronological
limit: 10
paginate: yes
```
````
Reliable tags (others are known-broken): `posts-datetime-chronological-byyearmonth`, `posts-datetime-reversechronological`. Use `datetime` frontmatter (format: `YYYY-MM-DD HH:MM`) for posts — `date` alone does not work reliably.
## Known limitations
- `mdcms.py` always expects the deployable site in a `website/` subdirectory of the registered project path.
- Most `posts-*` tag variants are broken. Only `posts-datetime-chronological-byyearmonth` and `posts-datetime-reversechronological` reliably work.
- Section headings in the nav are non-clickable (sections-sitemap is not yet implemented).
## Key implementation details
- `parse_nav_yml()` and `parse_config_categories()` are **not** general YAML parsers — they only handle the exact indentation/format that `mdcms.py` itself produces. Do not assume they handle arbitrary YAML.
- Category code validation uses `CATEGORY_CODE_RE = re.compile(r"^[a-zA-Z0-9\-]+$")` — codes must match this.
- The `samplesite/` directory is a reference implementation with multi-language categories (English, Norwegian, Arabic including RTL). It is not deployed; it exists for reference and testing.
- `website/` in the repo root is a minimal starter template (single page, no categories).