Adds a second level of granularity to the C1–C6 opinionated-component system: 24 independently-toggleable sub-categories, each with its own __fish_config_op_<category>_<subcategory> variable following the existing truthy/falsy/unset cascade one level deeper.
New sub-category cascade (__fish_config_op_cascade.fish) and a rewritten, self-identifying guard (__fish_config_op_enabled.fish) — callers compute their own identity via (status current-function)/(status basename) rather than passing a category name.
A reserved C0/always pseudo-category (always/on/always/off) as a per-function override escape hatch, fail-open for unclassified functions.
A Python registry generator (docs/generate_component_registry.py, grafted onto the existing docs pipeline) that parses a new # COMPONENT header convention across every guarded function/file and emits a committed fish data file the guard reads at shell startup.
All ~50 existing guarded files migrated to the new header + self-identifying call form, including config.fish itself and several files with multiple, independently-classified guard points (a "named site" sub-addressing scheme within one header).
Taxonomy documentation for all 24 sub-categories, plus new validation checks in the docs test suite.
config-settings gains drill-down navigation into a category's sub-categories, with real box-drawing rendering and toggle wiring.
Process
Built via subagent-driven development: design spec → implementation plan → 19 tasks, each implemented and independently reviewed (several through fix rounds), followed by a full whole-branch review and a bounded fix wave (re-reviewed clean), then a second small round closing out the remaining non-blocking follow-ups (also re-reviewed clean).
Several real bugs were caught and fixed during this process, most notably:
Fish rejects hyphens in variable names outright — would have silently broken 14 of the 24 sub-category slugs the first time anyone tried to toggle one. Fixed with a hyphen→underscore translation in the guard.
config.fish's cachyos-tricks guard was nearly reclassified from overrides to aliases, which would have been a silent, undocumented behavior change contradicting the original code's own comment. Caught before merge.
Two critical bugs in the config-settings TUI's toggle wiring and erase-height math, both verified via a real PTY session.
Tab/Shift-Tab not clearing drill-down navigation state, which froze cursor movement on the pre-existing Sponge/Paths pages — caught in final review, fixed and re-verified.
A registry-drift test that worked locally but couldn't fire meaningfully in CI (regenerated before the check ran) — CI now self-heals by committing the regenerated registry.
Test plan
python3 docs/verify-manual.py — 54/55 (only the known, CI-only test_concat_roundtrips_original failure, which self-resolves when this merges to main and CI regenerates the doc snapshot)
All 24 sub-categories verified to toggle end-to-end through the real guard (including all 14 hyphenated slugs)
All 75 migrated guard call sites audited to confirm they preserve their original C1–C6 category exactly
Registry/call-site cross-check: 64 registry keys ↔ 75 call sites, zero orphans in either direction
Fresh-shell smoke test (fish -c 'source config.fish')
config-settings drill-down navigation verified interactively over a real PTY session
Registry key/value quoting proven to prevent a real fish list-desync risk (not just cosmetic)
CI drift-check (test_committed_registry_matches_headers) proven genuinely falsifiable
Remaining minor nits (non-blocking, noted for transparency)
Quoting closes the glob-desync risk but not a hypothetical $ in a key (pre-existing pattern elsewhere in the file, not a regression introduced here).
The new sub-categories paragraph in 07-customization.md doesn't explicitly restate C5's opt-in exception inline (it's stated three paragraphs earlier on the same page, so no reader is actually misled).
## Summary
Adds a second level of granularity to the C1–C6 opinionated-component system: 24 independently-toggleable sub-categories, each with its own `__fish_config_op_<category>_<subcategory>` variable following the existing truthy/falsy/unset cascade one level deeper.
- New sub-category cascade (`__fish_config_op_cascade.fish`) and a rewritten, self-identifying guard (`__fish_config_op_enabled.fish`) — callers compute their own identity via `(status current-function)`/`(status basename)` rather than passing a category name.
- A reserved `C0`/`always` pseudo-category (`always/on`/`always/off`) as a per-function override escape hatch, fail-open for unclassified functions.
- A Python registry generator (`docs/generate_component_registry.py`, grafted onto the existing docs pipeline) that parses a new `# COMPONENT` header convention across every guarded function/file and emits a committed fish data file the guard reads at shell startup.
- All ~50 existing guarded files migrated to the new header + self-identifying call form, including `config.fish` itself and several files with multiple, independently-classified guard points (a "named site" sub-addressing scheme within one header).
- Taxonomy documentation for all 24 sub-categories, plus new validation checks in the docs test suite.
- `config-settings` gains drill-down navigation into a category's sub-categories, with real box-drawing rendering and toggle wiring.
## Process
Built via subagent-driven development: design spec → implementation plan → 19 tasks, each implemented and independently reviewed (several through fix rounds), followed by a full whole-branch review and a bounded fix wave (re-reviewed clean), then a second small round closing out the remaining non-blocking follow-ups (also re-reviewed clean).
Several real bugs were caught and fixed during this process, most notably:
- Fish rejects hyphens in variable names outright — would have silently broken 14 of the 24 sub-category slugs the first time anyone tried to toggle one. Fixed with a hyphen→underscore translation in the guard.
- `config.fish`'s `cachyos-tricks` guard was nearly reclassified from `overrides` to `aliases`, which would have been a silent, undocumented behavior change contradicting the original code's own comment. Caught before merge.
- Two critical bugs in the `config-settings` TUI's toggle wiring and erase-height math, both verified via a real PTY session.
- Tab/Shift-Tab not clearing drill-down navigation state, which froze cursor movement on the pre-existing Sponge/Paths pages — caught in final review, fixed and re-verified.
- A registry-drift test that worked locally but couldn't fire meaningfully in CI (regenerated before the check ran) — CI now self-heals by committing the regenerated registry.
## Test plan
- [x] `python3 docs/verify-manual.py` — 54/55 (only the known, CI-only `test_concat_roundtrips_original` failure, which self-resolves when this merges to main and CI regenerates the doc snapshot)
- [x] All 24 sub-categories verified to toggle end-to-end through the real guard (including all 14 hyphenated slugs)
- [x] All 75 migrated guard call sites audited to confirm they preserve their original C1–C6 category exactly
- [x] Registry/call-site cross-check: 64 registry keys ↔ 75 call sites, zero orphans in either direction
- [x] Fresh-shell smoke test (`fish -c 'source config.fish'`)
- [x] `config-settings` drill-down navigation verified interactively over a real PTY session
- [x] Registry key/value quoting proven to prevent a real fish list-desync risk (not just cosmetic)
- [x] CI drift-check (`test_committed_registry_matches_headers`) proven genuinely falsifiable
## Remaining minor nits (non-blocking, noted for transparency)
- Quoting closes the glob-desync risk but not a hypothetical `$` in a key (pre-existing pattern elsewhere in the file, not a regression introduced here).
- The new sub-categories paragraph in `07-customization.md` doesn't explicitly restate C5's opt-in exception inline (it's stated three paragraphs earlier on the same page, so no reader is actually misled).
collect_components() used dict.update(), which let conf.d silently
overwrite functions/ (or vice versa) when the same bare identity
appears in both, e.g. functions/auto-pull.fish and
conf.d/auto-pull.fish. The runtime guard can only ever look up the
bare status current-function/basename string, so both call sites
genuinely share one identity and their raw COMPONENT lines must be
concatenated, not replaced.
__fish_config_op_enabled built the sub-category override variable name by
concatenating the tag's slug verbatim, e.g. __fish_config_op_integrations_window-mgmt.
Fish variable names cannot contain hyphens, so any hyphenated sub-category
slug (14 of the 24 in the taxonomy) silently could never be overridden --
an unset such variable safely fell through via set -q, but attempting to
set it always errored with "invalid variable name", masking the defect
since no prior smoke test exercised an explicit sub-category-level toggle.
The registry tag itself (as authored in # COMPONENT headers and the
taxonomy docs) stays hyphenated; only the derived fish variable name is
translated.
Adds multi-site `# COMPONENT` headers and converts every guard call site in
conf.d/tricks.fish, conf.d/abbr.fish, functions/smart_exit.fish,
conf.d/yay-wrapper.fish, conf.d/paru-wrapper.fish, conf.d/wakatime.fish,
conf.d/first_run.fish, and config.fish (9 sites across 3 categories) to the
self-identifying `(status current-function|basename) <site>` calling
convention. Regenerates conf.d/__fish_config_op_registry.fish.
The site was mistagged aliases/filesystem in the prior commit. The original
code's own comment above the guarded block ("Surgically overriding the
distro config is opinionated (C3 overrides): skip it entirely when
overrides are disabled") documents this as an overrides decision, and it
gated __fish_config_op_overrides pre-migration. Retag to
overrides/environment (matching tricks.fish's own sibling site for the
equivalent action) so aliases=on/overrides=off keeps skipping
tricks.fish/ls/lt/cleanup/copy, as originally documented -- no silent
user-facing behavior change.
overrides-tricks conflated two distinct C3 concerns under one shared site
(overrides/environment), so __fish_config_op_overrides_key_bindings off
left tricks.fish's bang-bang bindings active while abbr.fish/puffer.fish/
config.fish's equivalent sites correctly disabled -- a half-dismantled
bang-bang system per docs/manual's own atomic-gating claim. Split into
tricks-manpager (overrides/environment, matches PAGER/EDITOR/CDPATH) and
tricks-bang (overrides/key-bindings, matches abbr.fish/puffer.fish/
config.fish's bang-related sites, per
docs/manual/08-components-reference/03-c3-key-and-environment-overrides.md).
Also fixes stale prose in config.fish's top-of-file comment: the guard
signature is now <identity> [<site>], not <category>.
Adds __config_settings_draw_subcat.fish, rendering a category's own
toggle plus its sub-category rows, sized dynamically from
__config_settings_subcats instead of the fixed 6-row category layout.
Wires the real per-category row count into config-settings.fish's
up/down handling (replacing Task 17's stub) and adds the
hyphen-to-underscore sub-category variable resolution to the
left/right toggle handlers, mirroring the translation the guard
function already applies.
Two box-drawing bugs surfaced while adapting the design doc's draft to
the real static sub-category data and were fixed rather than
transcribed: the title-dashes offset was off by 5, and several real
sub-category labels/descriptions (e.g. "Notifications", 13 chars;
several descriptions past 30 chars) exceed the narrower width tiers'
fixed field widths, so both fields are now defensively truncated
before padding to keep every row exactly iw+2 wide regardless of
content length.
Two bugs invisible to single-frame rendering, only reachable via the
interactive event loop:
- right/l and left/h resolved the sub-category variable name with
"$toggle_vars[(math ...)]"_(...) inside a quoted string -- fish
cannot expand a command-substitution index there ("Invalid index
value"), so the set never ran and varname silently kept the parent
category variable. Every arrow-key press on a sub-category row
toggled the parent category instead. Fixed by hoisting the category
variable into a plain local first, the same technique the down/j
case already used for its page index.
- __cs_dispatch_draw left panel_h fixed at 16 regardless of what it
actually drew, but the new sub-category page is n+7 lines (9-13,
always < 16). Every redraw/cleanup erase computed its height from
the stale constant, erasing too many rows and corrupting whatever
was above the panel. Fixed by having the dispatcher record the
actual printed height into panel_h after every draw, including
resetting it back to 16 on the value pages even when in_subcat is
stale from a Tab away without an intervening Escape.
Bundled fix wave for six findings from the sub-category-granularity
whole-branch review:
- config-settings: reset in_subcat on Tab/Shift-Tab so up/down keep
routing to cur_row instead of freezing on the Sponge/Paths pages
after a drill-down
- verify-manual: _parsed_components() now delegates to
generate_component_registry.collect_components() instead of a
lossy dict.update() merge, closing a taxonomy-check blind spot on
identity collisions
- verify-manual: add test_committed_registry_matches_headers to
catch the committed registry drifting from current # COMPONENT
headers
- build-docs workflow: trigger on conf.d/** and config.fish edits,
not just functions/**
- __config_settings_draw_subcat: show (Universal)/(Session) in the
drill-down title so the persistence scope is visible before toggling
- __config_settings_draw / config-settings: mention the Enter
sub-category drill-down in the on-screen hint and --help text
- generate_component_registry: send the "wrote ..." progress line to
stderr so it no longer corrupts --concat's stdout output
Sub-categories were previously undocumented outside README and the
per-category reference pages. Add a paragraph to 07-customization.md (the
canonical opinionated-components page) explaining the sub-category cascade,
a short note to the 08-components-reference index pointing readers at each
category's sub-category list, and a troubleshooting note that
__fish_config_op_<category>_<subcategory> variables follow the same
check/reset pattern as category variables.
- Quote both keys and values in the generated __fish_config_op_registry.fish
keys array so a future identity/tag containing a glob-special character
(*, ?, [) can't be silently dropped by fish's set, desyncing the
positionally-paired keys/values arrays.
- Deduplicate the tag list per site in build_registry() so an identity
collision across sources (e.g. auto-pull tagged autoexec/sync in both
functions/auto-pull.fish and conf.d/auto-pull.fish) no longer produces a
duplicated tag in the committed registry.
- Add a comment to the generated file's header noting it must sort first
among conf.d/*.fish guard-callers, since that ordering is currently
implicit in the filename rather than stated anywhere.
- CI's auto-commit step now also stages conf.d/__fish_config_op_registry.fish
alongside the docs it already regenerates, so a # COMPONENT header change
pushed without a manual __fish_config_op_registry_rebuild self-heals
instead of silently drifting from what CI just regenerated.
test_c0_tags_never_combine_with_contradiction_unwarned could never fail (its
own docstring said so) yet inflated the test_* pass count. Rename it to
warn_c0_tags_never_combine_with_contradiction, matching the file's existing
warn_* convention, and call it explicitly from main() alongside the other
three warn_* checks.
warn_functions_without_component also permanently false-positived on
functions/__fish_config_op_enabled.fish: the file's own function signature
and EXAMPLE prose contain the literal guard name, tripping the "calls the
guard" substring check against itself. Exclude the guard's own supporting
infrastructure files (__fish_config_op_enabled.fish,
__fish_config_op_cascade.fish, __fish_config_op_registry_lookup.fish) by
name -- an EXAMPLE-section-only exclusion wouldn't have worked here since
the false match is the function's own `function __fish_config_op_enabled`
definition line, not just its EXAMPLE block.
The wrapped description for the Enter key was indented one column past
every other description in the Navigation block. Drop the extra leading
space so it lines up at column 17 with the rest.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Adds a second level of granularity to the C1–C6 opinionated-component system: 24 independently-toggleable sub-categories, each with its own
__fish_config_op_<category>_<subcategory>variable following the existing truthy/falsy/unset cascade one level deeper.__fish_config_op_cascade.fish) and a rewritten, self-identifying guard (__fish_config_op_enabled.fish) — callers compute their own identity via(status current-function)/(status basename)rather than passing a category name.C0/alwayspseudo-category (always/on/always/off) as a per-function override escape hatch, fail-open for unclassified functions.docs/generate_component_registry.py, grafted onto the existing docs pipeline) that parses a new# COMPONENTheader convention across every guarded function/file and emits a committed fish data file the guard reads at shell startup.config.fishitself and several files with multiple, independently-classified guard points (a "named site" sub-addressing scheme within one header).config-settingsgains drill-down navigation into a category's sub-categories, with real box-drawing rendering and toggle wiring.Process
Built via subagent-driven development: design spec → implementation plan → 19 tasks, each implemented and independently reviewed (several through fix rounds), followed by a full whole-branch review and a bounded fix wave (re-reviewed clean), then a second small round closing out the remaining non-blocking follow-ups (also re-reviewed clean).
Several real bugs were caught and fixed during this process, most notably:
config.fish'scachyos-tricksguard was nearly reclassified fromoverridestoaliases, which would have been a silent, undocumented behavior change contradicting the original code's own comment. Caught before merge.config-settingsTUI's toggle wiring and erase-height math, both verified via a real PTY session.Test plan
python3 docs/verify-manual.py— 54/55 (only the known, CI-onlytest_concat_roundtrips_originalfailure, which self-resolves when this merges to main and CI regenerates the doc snapshot)fish -c 'source config.fish')config-settingsdrill-down navigation verified interactively over a real PTY sessiontest_committed_registry_matches_headers) proven genuinely falsifiableRemaining minor nits (non-blocking, noted for transparency)
$in a key (pre-existing pattern elsewhere in the file, not a regression introduced here).07-customization.mddoesn't explicitly restate C5's opt-in exception inline (it's stated three paragraphs earlier on the same page, so no reader is actually misled).The site was mistagged aliases/filesystem in the prior commit. The original code's own comment above the guarded block ("Surgically overriding the distro config is opinionated (C3 overrides): skip it entirely when overrides are disabled") documents this as an overrides decision, and it gated __fish_config_op_overrides pre-migration. Retag to overrides/environment (matching tricks.fish's own sibling site for the equivalent action) so aliases=on/overrides=off keeps skipping tricks.fish/ls/lt/cleanup/copy, as originally documented -- no silent user-facing behavior change.Two bugs invisible to single-frame rendering, only reachable via the interactive event loop: - right/l and left/h resolved the sub-category variable name with "$toggle_vars[(math ...)]"_(...) inside a quoted string -- fish cannot expand a command-substitution index there ("Invalid index value"), so the set never ran and varname silently kept the parent category variable. Every arrow-key press on a sub-category row toggled the parent category instead. Fixed by hoisting the category variable into a plain local first, the same technique the down/j case already used for its page index. - __cs_dispatch_draw left panel_h fixed at 16 regardless of what it actually drew, but the new sub-category page is n+7 lines (9-13, always < 16). Every redraw/cleanup erase computed its height from the stale constant, erasing too many rows and corrupting whatever was above the panel. Fixed by having the dispatcher record the actual printed height into panel_h after every draw, including resetting it back to 16 on the value pages even when in_subcat is stale from a Tab away without an intervening Escape.