Type: board_list

A board_list is the multi-section grouped list used by the root / of this site. Visually it looks like several board cards stacked top-to-bottom, one per section. Unlike board_group, the sections are virtual labels stored inline, not real sub-folders, and the items in each section are direct children of this resource’s own folder. Rendering lives in src/content/board_list.rs.

Rendering style

For each Section { name, desc_raw, items }, the page renders one render_board_card(...) call (the same helper that board uses):

  • title = section.name.get(lang)
  • desc_md = section.desc_raw -> OptionLangDict -> get(lang) (markdown-compiled by the helper)
  • children = section’s items resolved to (slug, Header, folder) against the board_list’s own folder
  • href_prefix = "" and view_all_href = None — there is no separate section index page to view, so no trailing link.

The resource’s own name / desc (from its Header) are stored but not rendered on the page itself. They exist so that, when the root site / points to this board_list, the navbar / <title> / SEO tags still know what to call the site. On /, that name is what shows in the page title.

ctx.json shape

This is the live root ctx.json for this site (from programfiles/content/ctx.json):

{
    "type": "board_list",
    "name": {
        "en": "FDS Documents",
        "ja": "FDS 文書",
        "zh": "FDS 文档"
    },
    "content": [
        {
            "name": "FDS",
            "items": [
                "policies",
                "cooperation",
                "news",
                "doc"
            ]
        },
        {
            "name": "Events",
            "desc": {
                "en": "Time-bound community events."
            },
            "items": [
                "minecraft_server"
            ]
        }
    ]
}

Field reference

FieldTypeRequiredNotes
type"board_list"yesExact discriminator string.
namestring or LangDictyesThe page’s own header. On /, this is what the navbar and <title> show. See Common: Header and LangDict.
descstring or LangDictnoStored on the header; not rendered on the page itself.
iconstringnoSame resolution rules as board; used by a parent that references this board_list (or in the favicon path on /).
bannerstring or LangDictnoStored but not rendered by board_list — present for parity with the Header model.
contentarray of section objectsyesEach section: { name, desc?, items: [slug, ...] }. See below.

content semantics — section objects

For board_list (and only board_list / board_group variants), content is not a flat slug list. Each entry is an object:

{
    "name": "FDS",
    "desc": { "en": "..." },
    "items": ["policies", "cooperation", "news", "doc"]
}
  • name — string or LangDict, used as the section card title. Stored as a LangDict (via From<Value>) so all the normal language fallback rules apply.
  • desc — string or LangDict, optional. Stored raw as desc_raw: Value so we can preserve the on-disk shape across save/load round-trips; rendered via OptionLangDict::get(lang) and markdown-compiled by render_board_card.
  • items — array of slug strings. Each slug must match a sub-folder of the board_list’s own folder (not a section sub-folder; sections aren’t folders).

Render flow

  1. BoardList::read(path) reads ctx.json, builds Header, and walks ctx.content[] building a Vec<Section>. For each section it pulls name (as LangDict), desc_raw (as raw Value), and string items.
  2. BoardList::render skips the banner (this type does not render banners) and emits one render_board_card(...) per section. For each item slug, the child’s ctx.json is read; missing/unparseable -> Header::fallback(slug).
  3. Item rows deep-link to siblings: <self_url>/<item_slug>/. No “View all” link.

Editor capabilities

The edit route supports a richer surface here, because sections are first-class:

  • rename(lang, new_name) — rename the board_list’s own header (visible on / in title bar).
  • set_icon(icon) — empty clears.
  • set_desc(lang, desc) — empty clears that lang.
  • add_section(lang, name) — pushes a fresh Section with a LangDict name set in lang, empty desc_raw, no items.
  • rename_section(idx, lang, name) — per-lang section rename.
  • set_section_desc(idx, lang, desc) — promotes desc_raw from None or plain-string to a dict (preserving the existing default-language value), then sets the per-lang entry.
  • remove_section(idx), move_section(from, to) — section list ops; out-of-range -> Err(io::Error{InvalidInput}).
  • add_item_to_section(section_idx, slug), remove_item_from_section(section_idx, item_idx), move_item(section_idx, from, to) — per-section item ops; out-of-range -> Err(io::Error{InvalidInput}).

There is no clear-all-langs path on set_section_desc (unlike the Header’s set_desc); to clear a section desc you currently have to write empty strings into every language slot or hand-edit ctx.json.

Save

BoardList::save() rebuilds ctx.json from scratch:

let mut ctx = object!({});
ctx.set("type", "board_list");
self.header.write_into(&mut ctx);
let mut content_val = object!([]);
for sec in &self.sections {
    let mut sec_val = object!({});
    sec_val.set("name", sec.name.value().clone());
    if !matches!(sec.desc_raw, Value::None) {
        sec_val.set("desc", sec.desc_raw.clone());
    }
    let mut items_val = object!([]);
    for slug in &sec.items {
        items_val.push(slug.clone().into());
    }
    sec_val.set("items", items_val);
    content_val.push(sec_val);
}
ctx.set("content", content_val);

The desc_raw field is round-tripped verbatim: if the user provided a plain-string desc, it’s saved as a plain string; if it was promoted to a dict by set_section_desc, it’s saved as a dict. The None case is omitted entirely (no empty "desc" key on disk).

Gotchas

  • Section item slugs resolve against the board_list’s own folder, not a section folder. Sections are labels, not directories. This is the headline difference from board_group.
  • All section / item index ops return Err(io::Error{InvalidInput}) on out-of-range — they do not panic and do not silently no-op.
  • A section’s name defaults to a plain string when first set (add_section writes {lang: name} directly, so the LangDict already starts as a dict).
  • The board_list’s own name is what the navbar and <title> show on /. Empty it out at your peril.
  • banner on the Header is parsed but not rendered by board_list — parity-only.