diff --git a/docs/build-manual.py b/docs/build-manual.py
index 5f1227d..15bb4fb 100644
--- a/docs/build-manual.py
+++ b/docs/build-manual.py
@@ -20,8 +20,14 @@ import generate_component_registry
DOCS = Path(__file__).parent
MANUAL = DOCS / "manual"
FUNCTIONS = DOCS.parent / "functions"
+COMPLETIONS = DOCS.parent / "completions"
SLUG_DIR = "reference"
+# File-tree branches whose real directory contents get listed inline on the
+# Starlight site (never in the plain-text manual/man page, since only the
+# --site path runs box-drawing trees through _as_file_tree).
+EXPANDABLE_TREE_DIRS = {"functions/": FUNCTIONS, "completions/": COMPLETIONS}
+
def _is_function_page(path: Path, root: Path) -> bool:
"""True for a Section 5 category stub (not its index)."""
@@ -366,6 +372,11 @@ def _as_file_tree(para: list[str]) -> str | None:
depth = len(prefix.replace('\t', ' ')) // 4
indent = " " * (depth + 1)
out.append(f"{indent}- {name} {desc}".rstrip())
+ expand_dir = EXPANDABLE_TREE_DIRS.get(name)
+ if expand_dir is not None and expand_dir.is_dir():
+ child_indent = " " * (depth + 2)
+ for entry in sorted((p.name for p in expand_dir.iterdir() if p.is_file()), key=str.lower):
+ out.append(f"{child_indent}- {entry}")
out.append("")
return "\n".join(out)
diff --git a/docs/verify-manual.py b/docs/verify-manual.py
index 68806b7..b9f0a98 100644
--- a/docs/verify-manual.py
+++ b/docs/verify-manual.py
@@ -805,6 +805,61 @@ def test_as_file_tree_accepts_deeper_trees():
), f"unexpected file tree output:\n{out}"
+def test_as_file_tree_expands_functions_and_completions():
+ """The functions/ and completions/ branches list real directory contents."""
+ import build_manual
+
+ with tempfile.TemporaryDirectory() as d:
+ root = Path(d)
+ functions_dir = root / "functions"
+ functions_dir.mkdir()
+ (functions_dir / "zeta.fish").write_text("")
+ (functions_dir / "Alpha.fish").write_text("")
+
+ completions_dir = root / "completions"
+ completions_dir.mkdir()
+ (completions_dir / "bd.fish").write_text("")
+
+ original = dict(build_manual.EXPANDABLE_TREE_DIRS)
+ build_manual.EXPANDABLE_TREE_DIRS["functions/"] = functions_dir
+ build_manual.EXPANDABLE_TREE_DIRS["completions/"] = completions_dir
+ try:
+ para = [
+ "~/.config/fish/",
+ "├── functions/ Custom functions, one per file",
+ "└── completions/ Tab completion scripts",
+ ]
+ out = build_manual._as_file_tree(para)
+ finally:
+ build_manual.EXPANDABLE_TREE_DIRS.clear()
+ build_manual.EXPANDABLE_TREE_DIRS.update(original)
+
+ assert out == (
+ "\n"
+ "- ~/.config/fish/\n"
+ " - functions/ Custom functions, one per file\n"
+ " - Alpha.fish\n"
+ " - zeta.fish\n"
+ " - completions/ Tab completion scripts\n"
+ " - bd.fish\n"
+ ""
+ ), f"unexpected file tree output:\n{out}"
+
+
+def test_as_file_tree_leaves_unrelated_branches_unexpanded():
+ """Only the mapped directory names get expanded; everything else is untouched."""
+ import build_manual
+
+ para = [
+ "$__fish_user_dots_path/",
+ "├── secrets.fish API keys, tokens, passwords, personal identifiers",
+ "└── local.fish Machine-specific paths, env vars, and sourcing secrets",
+ ]
+ out = build_manual._as_file_tree(para)
+ assert "secrets.fish" in out and "local.fish" in out
+ assert len(out.splitlines()) == 5, f"unexpected expansion of unrelated branches:\n{out}"
+
+
def test_customization_notes_render_as_aside():
"""The real 07-customization NOTE paragraph converts to one intact