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

6.2 KiB

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

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 generatorsgenerate_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:

---
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:

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