# 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..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: ` 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: `..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).