Skip to content

feat: unify widget hiding into a single hideable-state system - #430

Open
zachthedev wants to merge 1 commit into
sirmalloc:mainfrom
zachthedev:unified-hideable-states
Open

feat: unify widget hiding into a single hideable-state system#430
zachthedev wants to merge 1 commit into
sirmalloc:mainfrom
zachthedev:unified-hideable-states

Conversation

@zachthedev

@zachthedev zachthedev commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Closes #428 (my proposal from earlier today; happy to rework or split).

Nine per-widget hide keys (hideNoGit, hideNoJj, hideNoRemote, hideZero, hideWhenEmpty, hideIfDisabled, hideStatus, hideTitle, hideWhenNotFork) become one mechanism: widgets declare conditions via an optional getHideableStates(), enabled states live in metadata.hide as a comma-separated list, and a shared (h)ide… keybind opens a checklist for the selected widget.

Diff guide (121 files): ~55 widget files are the same mechanical conversion; most test changes are one-line key swaps.

Group Files What changed
Core shared/hideable.ts (new), types/Widget.ts state declarations + isHidden/get/set (~95 lines)
Migration utils/migrations.ts, types/Settings.ts v3 -> v4 converts legacy keys per widget type (hideNoGit also covered (no upstream) on Ahead/Behind and the no-PR placeholder on Git PR; on-disk git-pr alias handled); one test per mapping
TUI HideStatesEditor.tsx (new), items-editor wiring one injected h keybind opens the checklist
Renderer utils/renderer.ts merge-target-hidden pass: decorative custom-text/symbol items collapse with their merge target
Widgets ~55 files drop the bespoke toggle, declare states, call isHidden()
Tests ~42 files new: hideable/migration/renderer suites; existing: one-line key swaps
Docs USAGE.md, AGENTS.md new "Hiding Widgets Conditionally" section

Behavior:

  • Existing configs render identically after migration
  • Ahead/Behind's hardcoded 0/0 auto-hide becomes a default-enabled zero state, now opt-out-able
  • New opt-in states: zero (token widgets, session cost/clock, git counters), no-data (speed widgets, Block Timer, Git CI Status' -, usage widgets' [No credentials]-style errors), default-value (Output Style)
  • Reset timers are excluded from no-data: they bind h for the hour toggle; a registry test enforces the h reservation
  • Instant h toggles become a checklist (Space toggle, Enter save)
  • Downgrade: older versions ignore metadata.hide, so hidden placeholders reappear until upgrading back (in USAGE.md)
  • Dropped the dead hide: z.boolean() from WidgetItemSchema; no updatemessage added since rendering is unchanged

Open question: stock Ahead/Behind items now show (hide: zero) in the editor since defaults are surfaced honestly. Want default-enabled states suppressed from that annotation?

Happy to split (core conversion / new states / renderer pass) in whatever order you prefer.

Rebased onto current main. The conversion covers every widget that landed while this was open, so nothing is left on the old per-widget flags:

Tested: bun run lint clean; bun test 2064 pass, plus the one global-command-resolution failure main already has on this host (confirmed against a pristine main checkout). Exercised against my real config inside and outside repos.

Note: #347 (rules engine) reuses the hide: z.boolean() field this PR removes and adds rule-based hiding; if that lands first I'll rebase the schema change and align the hide semantics with it.

@sirmalloc

Copy link
Copy Markdown
Owner

@zachthedev Can you go ahead and rebase this on main and resolve any issues?

@zachthedev

Copy link
Copy Markdown
Contributor Author

@zachthedev Can you go ahead and rebase this on main and resolve any issues?

Will do sometime in the next 24 hours

@zachthedev
zachthedev force-pushed the unified-hideable-states branch from 4d51f8c to 9b4424d Compare June 15, 2026 18:10
@zachthedev

zachthedev commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main, conflicts resolved. #431 had merged into the same Git/JJ widgets, so they now carry both symbol-override and hideable; also extended the conversion to the Cache and Extra Usage Used widgets that landed since, with v3 -> v4 migration. lint clean; bun test green bar the pre-existing Windows-env failures. Ready for review.

@zachthedev

zachthedev commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

Merge-order note: #457 (Closes #450) also touches CompactionCounter hide-zero. It adds per-metric hide-zero on hideZero; this PR migrates hideZero to the unified system. Whichever merges second needs a small reconcile; if #457 lands first, this PR's conversion must also cover the metric mode.

@zachthedev
zachthedev force-pushed the unified-hideable-states branch from 9b4424d to 592db94 Compare June 16, 2026 04:00
@zachthedev

Copy link
Copy Markdown
Contributor Author

@zachthedev Can you go ahead and rebase this on main and resolve any issues?

Ready for review. Thanks much!

@zachthedev
zachthedev force-pushed the unified-hideable-states branch from 592db94 to 36a0a27 Compare June 18, 2026 00:18
@zachthedev
zachthedev force-pushed the unified-hideable-states branch 3 times, most recently from 0db28b9 to fd0f726 Compare August 6, 2026 00:29
@zachthedev

zachthedev commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main, ten conflicts resolved. Conversion extended to what landed
while this sat open: #457's metric mode, #466's Cache Timer (hideWhenEmpty -> empty),
#492's GitHub CI Status, #522's Weekly Fable Usage. Their h binds go back to the shared
hide checklist, which owns h.

Four migration bugs, all fixed:

  • git-ci-status had no V4_HIDE_FLAG_RULES entry. migrateItemHideFlags bails on
    unlisted types, so a v3 config lost hideNoGit and config.ts wrote it back at version
    4, so the pass never reruns. Shipped in v2.2.24.
  • stateOrder doubles as the filter for the merged list, so a state missing from it was
    dropped. The seven git counters lost zero, the three Extra Usage widgets lost no-data.
  • A type naming an Object.prototype member resolved through the object literal, passed
    the if (!rule) guard, then threw. Unhandled rejection on feat: add config import/export with preview and merge modes #427's import path, swallowed
    into default settings on load. Guarded with Object.hasOwn.
  • A written hide list merged with defaultEnabled instead of replacing it, contradicting
    isHidden, where a present list is authoritative. {"hide":"","hideNoGit":"false"} undid
    an explicit opt-out.

One new state: Git CI Status declares no-data for its - placeholder, opt-in. It reads
the same cache as Git PR, so hiding Git PR's no-PR placeholder left a bare - beside it.

The old test sampled one widget per rule constant with no tie to the registry, which is how
these slipped. It now walks the registry and checks every widget declaring a hideable state,
plus a structural check of each rule against its widget. Each fix fails without it: 1 for
git-ci-status, 7 for the git counters, 3 for Extra Usage, 4 for the prototype types, 2 for
the authoritative-list change. Docs picked up the cache family and Weekly Fable Usage,
neither of which had a documented state key.

lint clean, bun test 2064 pass bar the global-command-resolution failure main has on
this host.

Implements the unified hideable-state system proposed in sirmalloc#428.

- Add src/widgets/shared/hideable.ts: widgets declare hideable conditions
  via getHideableStates(); a single metadata.hide key stores enabled state
  keys as a comma-separated list; isHidden() replaces the nine per-widget
  accessors (hideNoGit, hideNoJj, hideNoRemote, hideZero, hideWhenEmpty,
  hideIfDisabled, hideStatus/hideTitle, hideWhenNotFork)
- Convert existing configs through the standard settings migration
  (v3 -> v4): legacy boolean flags map to the states each widget type
  actually hid, so existing configs render identically; default-enabled
  states are folded into written lists to preserve behavior
- Replace per-widget hide toggles with one (h)ide… keybind that opens a
  shared checklist editor (HideStatesEditor); the items editor injects the
  keybind for any widget declaring states and shows enabled states as
  (hide: no-git, zero); 'h' is reserved for this purpose, enforced by a
  registry-wide test
- Add previously missing hide options: zero for token/session-cost/
  session-clock widgets and for the git count widgets (changes,
  insertions, deletions, staged/unstaged/untracked files, conflicts),
  no-data for speed widgets, Block Timer, and the usage widgets' error
  placeholders, default-value for Output Style, and a separate no-data
  state for Git PR
- Git Ahead/Behind's hardcoded 0/0 auto-hide becomes a declared
  default-enabled zero state, so it can now be opted out to show ↑0↓0
- Decorative custom-text/custom-symbol items can opt into
  merge-target-hidden: the renderer collapses them when the widget they
  are merged with renders nothing, so icon prefixes no longer orphan
- Remove the dead top-level hide field from WidgetItemSchema
- Remove shared/git-no-git.ts; slim shared/git-remote.ts and
  shared/extra-usage-disabled.ts to their remaining responsibilities
- Update docs/USAGE.md and tests; add coverage for the hideable module,
  the v3 -> v4 migration, the renderer merge-target pass, and all newly
  hideable states

Co-authored-by: Claude <noreply@anthropic.com>
@zachthedev
zachthedev force-pushed the unified-hideable-states branch from fd0f726 to d78c5c2 Compare August 14, 2026 23:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: unified hideable-state system for widgets

2 participants