Type: mdbook

An mdbook resource is an ordered set of markdown chapters that share one resource folder. One sidebar lists every chapter; the main pane renders the active one. Implementation: src/content/mdbook.rs.

Purpose

Render a multi-chapter document with a sidebar of links. Used for governance documents (the Constitution), longer-form references, and anything where ordering matters and chapters share a parent.

ctx.json shape

{
    "type": "mdbook",
    "name": {
        "en": "Charter",
        "ja": "基本法",
        "zh": "基本法"
    },
    "desc": {
        "en": "FDS's founding charter.",
        "ja": "FDSの設立基本法。",
        "zh": "FDS 立组文件。"
    },
    "content": [
        {
            "name": "2025.07.15 版本",
            "file": {
                "en": "constitution_2025.07.15_en",
                "ja": "constitution_2025.07.15_ja",
                "zh": "constitution_2025.07.15"
            }
        },
        {
            "name": "2025.05.16 版本",
            "file": "constitution_2025.05.16"
        }
    ]
}

Field reference

FieldTypeRequiredNotes
typestring — must be "mdbook"yesDiscriminator.
nameLangDictyesBook title. See Common: Header and LangDict.
iconstringoptionalURL or path.
descOptionLangDictoptionalmeta description / OG.
bannerOptionLangDictoptionalBanner image URL.
contentarray of chapter objectsyesSee below. Empty array is legal but breaks the download endpoint.
filestringhidden / runtimeNot normally present in JSON. Populated at runtime from the URL’s trailing segment — see “Active chapter”.

Each chapter object:

FieldTypeRequiredNotes
namestring OR LangDictyesSidebar label for this chapter.
filestring OR LangDictyesFilename stem (no .md) of the chapter body.

Order matters. Array order = sidebar order = download zip ordering.

Active chapter

The mdbook resource’s URL is its folder. When a visitor navigates to a chapter, the URL gains a trailing segment — e.g. /policies/constitution/constitution_2025.07.15. The dispatcher splits this into folder + file (via fop::split_path) and passes the file to MdBook::new as the file field.

MdBook::get_file(lang):

  • Returns self.file when non-empty (a specific chapter was named in the URL).
  • Falls back to content[0].file[lang] when the URL hit the folder itself with no trailing segment.

This is why you almost never set file in ctx.json — it’s a per-request runtime value.

File-on-disk layout

<resource_folder>/
    ctx.json
    <chapter1_file>.md
    <chapter2_file_en>.md
    <chapter2_file_ja>.md
    <chapter2_file_zh>.md
    ...

One .md per chapter per language. If a chapter’s file is a string, that single .md is shared across languages.

Render flow

MdBook::render:

  1. Open <div class="container-func"><div class="grid-container"><div class="links">.
  2. For each chapter in content, emit <p><a href="<chapter_file_for_lang>">{chapter_name}</a></p>. The href is a bare slug — it works because all chapters live in the same folder, so the browser resolves it as a sibling URL.
  3. Open <div class="markdown-content">, append the compiled markdown of the active chapter (via md_content(lang)read_markdown_file), close all wrappers.
  4. The outer content.rs::render wraps the result in the banner/breadcrumb/SEO chrome from general.html.

Editor capabilities

?view=edit (see Route: edit) exposes:

  • Rename the book for the active language (rename).
  • Set/clear icon, set/clear desc via the Header editor.
  • Add chapteradd_chapter(lang, name, file_slug) appends a chapter entry to content and creates an empty <file_slug>.md on disk if it doesn’t already exist.
  • Remove chapterdelete_chapter(idx) removes the entry. The .md file on disk is not deleted automatically — clean it up by hand if you want it gone.
  • Reorder chaptermove_chapter(from, to).
  • Rename chapter per languageset_chapter_name(idx, lang, name).
  • Edit chapter bodyread_chapter_body(file_slug) / write_chapter_body(file_slug, body).

Active language comes from the navbar dropdown.

Download behavior

?view=download returns a .zip of every chapter’s .md content for the active language. Filenames inside the zip are zero-padded with chapter index — 01-<slug>.md, 02-<slug>.md, … — so an unzip preserves reading order even when the slugs are not alphabetical. See Route: download for the full handler.

When content is empty, the download endpoint returns 404 ("No chapters to download").

Save semantics

MdBook::save():

  1. Reads the existing ctx.json (preserves unknown keys).
  2. Sets type = "mdbook".
  3. Writes the Header (name, plus icon/desc/banner only when set).
  4. Writes content from the in-memory Value — the entire chapter array is replaced.
  5. JSON-encodes.

Notably the runtime file field is not written back — save() never persists the URL-derived active-chapter slug.

Gotchas / common edit mistakes

  • Mismatched file slugs and .md filenames. Renaming a chapter’s file in JSON without renaming the file on disk leaves the chapter rendering blank.
  • Leaving orphan .md files after delete_chapter. The handler doesn’t clean up. Decide deliberately whether to delete the file or keep it as a backup.
  • Reordering by editing JSON by hand. Safe, but remember the download zip’s 01-, 02- prefixes shift with the new order.
  • Chapter name / file as a bare string. Legal — every language sees the same value. Mix-and-match per chapter is fine (see the example).
  • Empty content. The resource renders an empty sidebar and an empty main pane. ?view=download 404s. Useful as a placeholder; otherwise add a chapter.
  • Setting file at the root level in ctx.json. The reader will pick it up and pin the active chapter to that slug regardless of URL. Almost never what you want — leave it out.