diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..285f61f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,127 @@ +# 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).