Updated the python site builder's regex to correctly parse file paths containing shell variables like $__fish_user_dots_path in comment titles. Also added fish_default_key_bindings to the known shell-heads list so those examples are correctly wrapped in a fenced code block rather than falling through to plain text and getting parsed as MDX headings.
Modified build-manual.py to strip punctuation and properly handle spaces/hyphens during link slug generation. This ensures top navigation link cards generated by the script actually match Starlight's URL format. Also fixed a few manual markdown links that mistakenly contained double dashes.
Created a custom Starlight 404 page (docs/manual/404.md) that includes LinkCards pointing to common sections (Installation, Troubleshooting, Function Reference). Also updated build-manual.py to prevent '404' from appearing in the generated sidebar.
Renamed the root 'Overview' entry under 'Functions Reference' to 'Categories'. Also renamed the inner category 'Overview' entries to '{Category} Overview'. Because Starlight uses the sidebar label for the 'Next/Previous' pagination buttons at the bottom of pages, having multiple pages labeled just 'Overview' caused the button text to ambiguously say 'Next: Overview' even when linking to a new category.
The sidebar previously started with 'Configuration Variables' which felt abrupt. Add a 'Home' link at the top pointing to the landing page (the one with install/functions-ref buttons). Seeded the sidebar list in build_site() so it persists across --site rebuilds.
- test_customization_notes_render_as_aside now asserts the four NOTE
bullets live inside the <Aside> tags, not merely anywhere on the
page — a re-wrapped bullet previously still passed because the
bullet text leaked into an untouched sibling paragraph.
- TREE_BRANCH_RE no longer matches an indented/continuation branch
line (dropped the leading `[│ ]*`), so a second-level tree falls
through to verbatim rendering instead of being silently flattened
to one level. Added test_as_file_tree_rejects_deeper_trees to guard it.
RETURNS previously conflated fish's $status exit code with genuine
stdout/printed output, e.g. rm listing "0/1" as if they were print
values rather than exit codes. Rename RETURNS to EXIT STATUS across
all 83 documented functions, and reintroduce RETURNS as a distinct
label reserved for the 15 functions that actually print to stdout.
Update build-manual.py's ENTRY_HEADS to render Exit Status before
Returns, manualtools.py's SECTIONS constant, and AGENTS.md's label
order and label-usage guidance to match. Add two verify-manual.py
regression tests: EXIT STATUS bodies must never contain stray
stdout/printed language, and Returns: must always render after
Exit Status: when both are present. Regenerate docs/fish-config.md.
_as_ruled_table() only matched a single solid run of dashes under the
header row. The "Fish Universal Variables" table in
07-customization.md uses the other common convention instead: one
dash run per column, gapped the same as the header (RST simple-table
style) — e.g. "------ ----------". That line failed RULE_RE's
whole-line match, so the table still fell through to a code block.
Split the rule line on the same CELL_SPLIT boundary used for data
rows and require every resulting group to be a solid dash run, which
accepts both conventions without adding a second code path.
No SSOT changes needed — the source table was already well-formed,
docs/fish-config.md round-trips unchanged, confirming this is a
site-only fix.
_as_table() only detects tables whose data rows are indented deeper
than a ":"-terminated label. The "Component Reference" tables use a
different convention (header, dashed rule, rows all at the same
indent), so they never matched and fell through to a plain indented
code block on the Starlight site.
Add _as_ruled_table() to recognize that header+rule+rows shape. It
supports N columns, folds word-wrapped continuation lines into the
previous row, and backtick-escapes cells containing "<" or "{" instead
of rejecting the table outright (unlike _as_table's stricter guard,
which those tables would otherwise trip on for angle-bracket
placeholders and brace globs).
Also fixes four ambiguous rows in 07-customization.md where two
columns had collapsed to a single space, making them indistinguishable
from a word-wrapped continuation.
Function EXAMPLE blocks now render as their own fish fence titled
"Examples" (matching the existing "Usage" title on the Synopsis fence),
triggered by a flat Example: label line mirroring the Synopsis: prefix.
Prose pages get two new generic titling signals in _render_para: a bare
indented path ending in a known extension is titled by its basename, and
a leading "# in <file>" / "# <file>" comment on a shell paragraph is
promoted to the fence title and stripped from the body. This picks up
local.fish/secrets.fish path displays and override examples in
07-customization.md for free, plus a bonus .logging_disabled hit.
10-personalization.md's secrets.fish block gets an explicit "# secrets.fish"
comment to title it the same way. The four local.fish examples keep their
existing descriptive comments rather than a redundant local.fish title.
Swap the Starlight scaffold README for one describing this project's
two-source SSOT and dev/deploy workflow. Give the generated function
synopsis fence a Starlight filename title (`fish title="name.fish"`)
so it reads as a snippet of the function it documents.
The man-page-style comment header above each function in functions/*.fish
becomes the SSOT for that function's documentation. Writing a new function
and documenting it are now the same act.
- manualtools.parse_functions() parses every header carrying a # CATEGORY;
absence of one is the opt-in, keeping bundled-plugin and prompt internals
out of the manual with no exclusion list to maintain.
- build-manual.py generates entries for both --concat and --site, with
**Dependencies:** rendered as links and a **Used by:** reverse index
computed in one pass. Cross-category links are the navigation win.
- docs/manual/05-functions/*.md reduced to frontmatter-only stubs. Every
intro measured zero words, so the category files were pure entry
containers; ordering, titles, and helpKeywords routing are untouched.
- _first_sentence() unwraps the leading hard-wrapped paragraph and skips
the whole Synopsis block, not just its label line. Site cards no longer
truncate mid-clause or show a synopsis as their description.
Verification, per the design spec:
- test_concat_roundtrips_original scoped to sections 0-4 and 6-11. It
guarded a format migration; this is a content migration.
- replaced by structural checks: one entry per categorised function, the
required sections present, every category resolving to a stub with no
stub empty, and every declared dependency resolving to a real function
or a type -q-guarded binary.
- public functions lacking # CATEGORY warn rather than fail, so a new
user-facing function going undocumented stays visible in CI.
24/24 checks pass. 94 entries generated from 94 parsed headers.
Also drops a stale claim from open-url's NOTES: config-help --html calls
xdg-open directly and has never called open-url.
Two-column option and subcommand blocks in the manual were falling through
to the indented-code fallback, rendering as an unhighlighted grey slab on
the site. `_as_table` detects a contiguous, column-aligned indented run and
emits a headerless GFM table instead; anything it cannot prove is tabular
still takes the old path.
15 blocks convert, 20 are correctly left alone. The concat and man-page
outputs are untouched — `prettify` is site-only, and the byte-exact
round-trip test stays green.
Cloudflare Pages reserves a top-level `functions/` directory in the deploy
output for Pages Functions (server-side handlers) and strips it from the
static-asset upload. The wrangler log for run 812 shows the collision
exactly: 159 files uploaded, which is precisely the number of files in
dist/ outside functions/ — all 108 files under it were dropped.
Nothing failed loudly. Astro built all 120 pages, wrangler reported
success, and the site worked under `astro preview`; only the deployed
copy 404'd on every function entry and category index.
Move the generated tree to /reference/ and add a test asserting the site
output never emits a name Pages reserves.
The manual is authored man-page style: every synopsis, example, option
table, and description sits in one 4-space-indented block. On the site
that renders as a single unhighlighted grey slab, because an indented
block declares no language.
Split each block into its paragraphs at site-build time and classify
them: synopsis and shell examples become ```fish fences, descriptions
become real prose, and column-aligned reference tables keep their
indentation. 175 blocks now highlight; the 412 lines of genuine tables
are left alone.
The transform is site-only. docs/manual/** keeps the indented form the
pandoc man-page pipeline and config-help depend on, and a test enforces
that no fence is ever written back to the SSOT.
Also:
- Point Expressive Code at the bundled Catppuccin Mocha/Latte themes so
code blocks match the palette in catppuccin.css.
- Build the functions sidebar group explicitly. `autogenerate` labelled
it with the raw directory slug and republished the directory index as
a child of the group it already titled, producing the duplicate
"Functions Reference" row.
- Skip `Synopsis:` lines when deriving card descriptions; they restated
the calling convention the card already shows as its title.
- Widen the palette: tinted heading levels, inline code, links, card
hover accents, aside accents, and table headers.
Fixes a bug where _split_entries stripped the leading indentation of an
entry's first line, detaching `Synopsis:` from the block it opens.
Add build_manual.build_site(), which walks docs/manual and emits the
Astro Starlight content collection: plain pages copied with pipeline-only
frontmatter (man/site/manTitle/helpKeywords) stripped, and *-functions/
category files exploded into one page per function plus a CardGrid/LinkCard
overview. Writes src/sidebar.json for astro.config.mjs to import. Wires
--site alongside the existing --concat flag.
Fixes two latent defects found while building the real site against the
strict content.config.ts schema:
- _split_entries now tracks fenced code blocks (like
manualtools.shift_headings does) so a `## ` inside a fence can't be
mistaken for an entry boundary.
- LinkCard title/description are escaped for JSX attribute context, since
shell synopses routinely contain `<placeholder>` angle brackets that
would otherwise open unterminated MDX/JSX parsing.
Also fixes the generated sidebar shape for the functions category: Starlight
0.39+ dropped support for a bare `autogenerate` sibling of `label` on a
top-level group, so the autogenerate config now nests inside `items`.
Verified with a full `astro build` (temporarily pointing astro.config.mjs
at the generated sidebar.json, then reverted since replacing that config
is a later task's deliverable): 120 pages built cleanly, no
content-collection/frontmatter/MDX errors.
index.md is both the man-page LANDING section and a Starlight content
page. Astro errors on any frontmatter key outside the fixed
man/site/manTitle/helpKeywords schema, so folding the source
fish-config.md pandoc header (title/section/header/date/author) onto
index.md under a fifth "pandoc" key would break the docs-site build
the moment it's scaffolded.
Relocate that block to docs/manual/_pandoc.yml: a plain, fence-free
YAML file that Astro content collections ignore (leading underscore).
split-manual.py now extracts it as raw text (no yaml.safe_load/
safe_dump round-trip) so build-manual.py's --concat re-emits it
byte-for-byte instead of merely "verbatim" in comment only. Also guard
build_concat against a manual/ tree with no _pandoc.yml/index.md,
removing the unconditional index.md parse that previously raised
FileNotFoundError on such a tree.
Regenerated docs/manual/ via split-manual.py; concat output remains
byte-identical to the pre-migration docs/fish-config.md.
Adds docs/build-manual.py (--concat) and a round-trip test in
verify-manual.py that reproduces the pre-migration fish-config.md
exactly from docs/manual/.
Fixes found by the round-trip check, applied in split-manual.py and
re-run to regenerate docs/manual/:
- NAME/SYNOPSIS/TABLE OF CONTENTS had no sidebar.order, so they sorted
after every numbered section instead of interleaving with DESCRIPTION
in original document order. All manual pages now get sidebar.order
from a single position counter matching source order.
- split_h1/split_h2 used .strip() on section bodies, which also ate
leading indentation on the first body line, corrupting the 4-space
indented code blocks that open SYNOPSIS and TABLE OF CONTENTS.
- The source's leading pandoc metadata block (title/section/header/
date/author) was discarded entirely by the splitter. It's now parsed
off via manualtools.parse and stashed on index.md under a "pandoc"
key; build-manual.py re-emits it verbatim ahead of the first heading.