feat(config): add sub-category granularity to opinionated components #107

Merged
rootiest merged 30 commits from worktree-opinionated-subcategories into main 2026-08-19 01:42:26 +00:00
Owner

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

  • 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).
rootiest added 25 commits 2026-08-18 22:02:11 +00:00
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
rootiest added 4 commits 2026-08-18 22:21:33 +00:00
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.
rootiest added 1 commit 2026-08-19 01:37:20 +00:00
rootiest merged commit 01f4e376ae into main 2026-08-19 01:42:26 +00:00
rootiest deleted branch worktree-opinionated-subcategories 2026-08-19 01:42:26 +00:00
Sign in to join this conversation.