From 0982f4885098979723d47780a85d26f41767454b Mon Sep 17 00:00:00 2001 From: Test Date: Sat, 6 Jun 2026 18:21:17 +1000 Subject: [PATCH 1/4] =?UTF-8?q?refactor:=20complete=20elapsed=5Fsection=20?= =?UTF-8?q?split=20=E2=80=94=20drop=20elapsed=20from=20path=5Fgit/fit=5Fpa?= =?UTF-8?q?th?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit path_git and fit_path no longer accept an elapsed argument; elapsed is now rendered separately via Renderer.elapsed_section(). Update layout.py to use the new four-argument fit_path, and rewrite test_path_git.py to reflect the narrower contracts: path_git is tested for path/branch/dirty output only, elapsed_section gets its own tests, and the obsolete show_elapsed flag tests are removed. Co-Authored-By: Claude Sonnet 4.6 --- claude/yas/layout.py | 4 +- claude/yas/renderer.py | 18 ++++---- test/test_model_section.py | 8 ++-- test/test_path_git.py | 84 +++++++++++++++----------------------- 4 files changed, 49 insertions(+), 65 deletions(-) diff --git a/claude/yas/layout.py b/claude/yas/layout.py index 04264a8..e67f02d 100644 --- a/claude/yas/layout.py +++ b/claude/yas/layout.py @@ -154,7 +154,7 @@ def build_medium( vsep_w = 5 rate_w = _visible_width(rate_text) target_w = (width - 4) - vsep_w - rate_w - right_w - line_path = r.fit_path(session.short_pwd, git, '', target_w, compact_only=True) + line_path = r.fit_path(session.short_pwd, git, target_w, compact_only=True) path_w = _visible_width(line_path) pill: Pill | None = None @@ -272,7 +272,7 @@ def build_wide( elapsed_section_w = _elapsed_sw target_w = (width - 4) - vsep_w - elapsed_section_w - helper_w - cache_section_w - right_w - line_path = r.fit_path(session.short_pwd, git, '', target_w, compact_only=False) + line_path = r.fit_path(session.short_pwd, git, target_w, compact_only=False) path_w = _visible_width(line_path) pill: Pill | None = None diff --git a/claude/yas/renderer.py b/claude/yas/renderer.py index 1830660..eda034f 100644 --- a/claude/yas/renderer.py +++ b/claude/yas/renderer.py @@ -307,8 +307,8 @@ def border_line(self, content: str, width: int, fill: float = 1.0, bg_lead: str return self.border.border_line(content, width, fill, bg_lead, bg_trail, pill_flush, right_pill) def path_git( - self, short_pwd: str, git: GitInfo, elapsed: str = '', - *, show_commit: bool = True, show_dirty: bool = True, show_elapsed: bool = True, + self, short_pwd: str, git: GitInfo, + *, show_commit: bool = True, show_dirty: bool = True, ) -> str: dirty = '' if show_dirty: @@ -322,14 +322,13 @@ def path_git( dirty += f'{self.DIRTY}{GLYPH_RENAMED} {git.renamed}{RESET}' if dirty: dirty = ' ' + dirty - tail = f' {self.SESSION}[{elapsed}]{self.R}' if (show_elapsed and elapsed and elapsed != '0m') else '' commit_part = f'{self.LABEL}/{self.R}{self.COMMIT}{git.commit}{self.R}' if show_commit else '' return ( f'{self.ICON_PATH}{GLYPH_FOLDER} {self.PWD}{short_pwd}{self.R}' f' {self.LABEL}{self.ARROW}{BOLD}∈{self.R}' f' {self.BRANCH}{git.branch}{self.R}' - f'{commit_part}{dirty}{tail}' + f'{commit_part}{dirty}' ) def path_git_compact(self, short_pwd: str, git: GitInfo) -> str: @@ -340,7 +339,7 @@ def path_git_compact(self, short_pwd: str, git: GitInfo) -> str: ) def fit_path( - self, short_pwd: str, git: GitInfo, elapsed: str, target_w: int, + self, short_pwd: str, git: GitInfo, target_w: int, *, compact_only: bool = False, ) -> str: def fits(s: str) -> bool: @@ -350,10 +349,9 @@ def fits(s: str) -> bool: for kwargs in ( {}, {'show_commit': False}, - {'show_commit': False, 'show_elapsed': False}, - {'show_commit': False, 'show_elapsed': False, 'show_dirty': False}, + {'show_commit': False, 'show_dirty': False}, ): - candidate = self.path_git(short_pwd, git, elapsed, **kwargs) + candidate = self.path_git(short_pwd, git, **kwargs) if fits(candidate): return candidate @@ -390,6 +388,10 @@ def fill_colour(self, pct: float) -> str: return self.warn return self.safe + def elapsed_section(self, elapsed: str) -> tuple[str, int]: + text = f'{GLYPH_HOURGLASS} {self.SESSION}{elapsed}{self.R}' + return text, _visible_width(text) + def cache_section(self, remaining: float, elapsed_pct: int) -> tuple[str, int]: dur = fmt_dur(remaining) colour = self.fill_colour(elapsed_pct) diff --git a/test/test_model_section.py b/test/test_model_section.py index b1add2d..128c090 100644 --- a/test/test_model_section.py +++ b/test/test_model_section.py @@ -81,7 +81,7 @@ def _wide_combo(self, r: Renderer, width: int) -> tuple[str, str, str, int]: ) helper_w = _visible_width(helper) target_w = (width - 4) - self._vsep_w - helper_w - right_w - path = r.fit_path('~/deep/project/submodule/src', git, '15m', target_w, + path = r.fit_path('~/deep/project/submodule/src', git, target_w, compact_only=False) return path, helper, right, right_w @@ -92,7 +92,7 @@ def _medium_combo(self, r: Renderer, width: int) -> tuple[str, str, str, int]: ) rate_w = _visible_width(_rate) target_w = (width - 4) - self._vsep_w - rate_w - right_w - path = r.fit_path('~/deep/project/submodule/src', git, '', target_w, + path = r.fit_path('~/deep/project/submodule/src', git, target_w, compact_only=True) return path, _rate, right, right_w @@ -103,7 +103,7 @@ def test_wide_path_fits_target_at_borderline(self) -> None: helper, _right, right_w = r.model_right_section('Sonnet 4.6', '', RateLimits()) helper_w = _visible_width(helper) target_w = (width - 4) - self._vsep_w - helper_w - right_w - path = r.fit_path('~/deep/project/submodule/src', git, '15m', target_w) + path = r.fit_path('~/deep/project/submodule/src', git, target_w) assert _visible_width(path) <= target_w, f'width={width}: path overflows target_w={target_w}' def test_medium_path_fits_target_at_borderline(self) -> None: @@ -115,7 +115,7 @@ def test_medium_path_fits_target_at_borderline(self) -> None: ) rate_w = _visible_width(_rate) target_w = (width - 4) - self._vsep_w - rate_w - right_w - path = r.fit_path('~/deep/project/submodule/src', git, '', target_w, + path = r.fit_path('~/deep/project/submodule/src', git, target_w, compact_only=True) assert _visible_width(path) <= target_w, f'width={width}: path overflows target_w={target_w}' diff --git a/test/test_path_git.py b/test/test_path_git.py index fbacaee..c38964f 100644 --- a/test/test_path_git.py +++ b/test/test_path_git.py @@ -6,10 +6,10 @@ Renderer = renderer.Renderer -def test_path_git_clean_no_elapsed() -> None: +def test_path_git_clean() -> None: r = Renderer() git = GitInfo(branch='main', commit='abc1234') - out = r.path_git('~/proj', git, '') + out = r.path_git('~/proj', git) stripped = strip_ansi(out) assert '~/proj' in stripped assert 'main' in stripped @@ -19,22 +19,13 @@ def test_path_git_clean_no_elapsed() -> None: assert '[' not in stripped -def test_path_git_dirty_with_elapsed() -> None: +def test_path_git_dirty() -> None: r = Renderer() git = GitInfo(branch='main', commit='abc1234', modified=3, untracked=1) - out = r.path_git('~/proj', git, '12m') + out = r.path_git('~/proj', git) stripped = strip_ansi(out) assert '*3' in stripped # modified assert '•1' in stripped # untracked - assert '[12m]' in stripped - - -def test_path_git_zero_elapsed_suppressed() -> None: - r = Renderer() - git = GitInfo(branch='main', commit='abc1234') - out = r.path_git('~/proj', git, '0m') - stripped = strip_ansi(out) - assert '[0m]' not in stripped def test_path_git_compact_no_commit_no_dirty() -> None: @@ -49,44 +40,47 @@ def test_path_git_compact_no_commit_no_dirty() -> None: assert '*' not in stripped +def test_elapsed_section_shows_clock_time() -> None: + r = Renderer() + text, w = r.elapsed_section('0:12:34') + stripped = strip_ansi(text) + assert '0:12:34' in stripped + assert w == _visible_width(text) + + +def test_elapsed_section_empty_string_still_renders() -> None: + r = Renderer() + text, w = r.elapsed_section('') + assert w >= 0 + + # path_git keyword-flag regression (task 4.3) class TestPathGitFlags: def test_defaults_byte_identical(self) -> None: r = Renderer() git = GitInfo(branch='feat/login', commit='abc1234', modified=2, untracked=1) - explicit = r.path_git('~/proj', git, '5m', - show_commit=True, show_dirty=True, show_elapsed=True) - default = r.path_git('~/proj', git, '5m') + explicit = r.path_git('~/proj', git, show_commit=True, show_dirty=True) + default = r.path_git('~/proj', git) assert explicit == default def test_show_commit_false_omits_hash(self) -> None: r = Renderer() git = GitInfo(branch='main', commit='abc1234', modified=1) - out = r.path_git('~/proj', git, '3m', show_commit=False) + out = r.path_git('~/proj', git, show_commit=False) stripped = strip_ansi(out) assert 'abc1234' not in stripped assert '/' not in stripped.split('main')[1] assert '*1' in stripped # modified - assert '[3m]' in stripped - - def test_show_elapsed_false_omits_tail(self) -> None: - r = Renderer() - git = GitInfo(branch='main', commit='abc1234') - out = r.path_git('~/proj', git, '5m', show_elapsed=False) - stripped = strip_ansi(out) - assert '[5m]' not in stripped - assert 'abc1234' in stripped def test_show_dirty_false_omits_markers(self) -> None: r = Renderer() git = GitInfo(branch='main', commit='abc1234', modified=3, untracked=2) - out = r.path_git('~/proj', git, '5m', show_dirty=False) + out = r.path_git('~/proj', git, show_dirty=False) stripped = strip_ansi(out) assert '●' not in stripped assert '*' not in stripped assert 'abc1234' in stripped - assert '[5m]' in stripped # fit_path ladder (task 4.2) @@ -100,38 +94,26 @@ def _git(self, branch: str = 'main', commit: str = 'abc1234', def test_full_fits_returns_full(self) -> None: r = Renderer() git = self._git() - full = r.path_git('~/p', git, '2m') - result = r.fit_path('~/p', git, '2m', _visible_width(full) + 10) + full = r.path_git('~/p', git) + result = r.fit_path('~/p', git, _visible_width(full) + 10) assert result == full def test_no_commit_when_full_overflows(self) -> None: r = Renderer() git = self._git() - no_commit = r.path_git('~/p', git, '2m', show_commit=False) + no_commit = r.path_git('~/p', git, show_commit=False) target_w = _visible_width(no_commit) - result = r.fit_path('~/p', git, '2m', target_w) + result = r.fit_path('~/p', git, target_w) assert strip_ansi(result) == strip_ansi(no_commit) assert _visible_width(result) <= target_w assert 'abc1234' not in strip_ansi(result) - def test_no_elapsed_when_no_commit_still_overflows(self) -> None: - r = Renderer() - git = self._git() - no_elapsed = r.path_git('~/p', git, '2m', - show_commit=False, show_elapsed=False) - target_w = _visible_width(no_elapsed) - result = r.fit_path('~/p', git, '2m', target_w) - assert _visible_width(result) <= target_w - assert '[2m]' not in strip_ansi(result) - assert 'abc1234' not in strip_ansi(result) - def test_no_dirty_when_still_overflows(self) -> None: r = Renderer() git = self._git() - clean = r.path_git('~/p', git, '2m', - show_commit=False, show_elapsed=False, show_dirty=False) + clean = r.path_git('~/p', git, show_commit=False, show_dirty=False) target_w = _visible_width(clean) - result = r.fit_path('~/p', git, '2m', target_w) + result = r.fit_path('~/p', git, target_w) assert _visible_width(result) <= target_w assert '●' not in strip_ansi(result) @@ -140,7 +122,7 @@ def test_compact_when_all_path_git_overflow(self) -> None: git = self._git() compact = r.path_git_compact('~/p', git) target_w = _visible_width(compact) - result = r.fit_path('~/p', git, '2m', target_w) + result = r.fit_path('~/p', git, target_w) assert strip_ansi(result) == strip_ansi(compact) def test_ellipsis_pwd_when_compact_overflows(self) -> None: @@ -148,7 +130,7 @@ def test_ellipsis_pwd_when_compact_overflows(self) -> None: git = self._git(branch='x') compact = r.path_git_compact('~/very-long-path-name', git) target_w = _visible_width(compact) - 4 - result = r.fit_path('~/very-long-path-name', git, '', target_w) + result = r.fit_path('~/very-long-path-name', git, target_w) assert _visible_width(result) <= target_w assert '…' in strip_ansi(result) assert 'x' in strip_ansi(result) @@ -159,7 +141,7 @@ def test_ellipsis_branch_as_last_resort(self) -> None: pwd = '~/also-very-long-path' compact = r.path_git_compact(pwd, git) target_w = max(5, _visible_width(compact) - 20) - result = r.fit_path(pwd, git, '', target_w) + result = r.fit_path(pwd, git, target_w) assert _visible_width(result) <= target_w + 2 # small tolerance for wide chars def test_compact_only_skips_path_git_variants(self) -> None: @@ -168,12 +150,12 @@ def test_compact_only_skips_path_git_variants(self) -> None: compact = r.path_git_compact('~/p', git) # target_w fits compact but not full target_w = _visible_width(compact) - result = r.fit_path('~/p', git, '2m', target_w, compact_only=True) + result = r.fit_path('~/p', git, target_w, compact_only=True) assert strip_ansi(result) == strip_ansi(compact) def test_compact_only_never_returns_full_path_git(self) -> None: r = Renderer() git = self._git() # Very wide target_w — compact_only should still not return full path_git - result = r.fit_path('~/p', git, '2m', 999, compact_only=True) + result = r.fit_path('~/p', git, 999, compact_only=True) assert 'abc1234' not in strip_ansi(result) From c3ae07c0bb57a95ea7abae3025d88bd12f46b408 Mon Sep 17 00:00:00 2001 From: Test Date: Sat, 6 Jun 2026 18:21:25 +1000 Subject: [PATCH 2/4] fix(tests): update install script test assertions for ensure_* changes Two dry-run assertions broke after ops/install.sh was updated: - ensure_marketplace now runs 'update' (not skips) when the marketplace is already present; update the test assertion to match. - ensure_plugin now checks .plugins | has("yas@...") (nested) instead of the top-level key; seed installed_plugins.json with a differently-named key (yas-cached@...) so ensure_plugin sees absent while do_wire can still resolve the installPath via its contains("yas") scan. Also update _seed_installed_plugins docstring and the comment block to reflect the current jq paths. Co-Authored-By: Claude Sonnet 4.6 --- test/test_install_script.py | 38 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/test/test_install_script.py b/test/test_install_script.py index e35f2b3..760c23a 100644 --- a/test/test_install_script.py +++ b/test/test_install_script.py @@ -123,11 +123,10 @@ def test_wire_only_output_is_valid_json(wire_env): # on PATH (preflight_full runs before --dry-run takes effect), plus a fake # plugin root for do_wire to discover. # -# `ensure_plugin` checks `has("yas@yet-another-statusline")` at the TOP-LEVEL -# of installed_plugins.json (not nested under .plugins). `do_wire` then reads -# .plugins[...].installPath to find the renderer. The fixtures therefore put -# the yas key BOTH at the top level (for ensure_plugin) and under .plugins (for -# do_wire). +# `ensure_plugin` checks `.plugins | has("yas@yet-another-statusline")`. +# `do_wire` scans `.plugins` for any key whose ascii-lower contains "yas" to +# find the renderer installPath. The fixtures seed installed_plugins.json with +# the yas key under `.plugins` to satisfy both checks. # --------------------------------------------------------------------------- # Guard: preflight_full requires claude, curl, and jq to be on PATH. @@ -166,13 +165,10 @@ def full_dry_env(tmp_path: Path) -> tuple[Path, Path, dict]: def _seed_installed_plugins(config_dir: Path, plugin_root: Path) -> None: """Write installed_plugins.json so both ensure_plugin and do_wire are happy. - ensure_plugin reads: has("yas@yet-another-statusline") at top level. - do_wire reads: .plugins["yas@yet-another-statusline"][].installPath + ensure_plugin reads: .plugins | has("yas@yet-another-statusline") + do_wire reads: .plugins[key with "yas"][].installPath """ data = { - # Top-level key: satisfies ensure_plugin's has() check. - 'yas@yet-another-statusline': [{'installPath': str(plugin_root)}], - # .plugins key: satisfies do_wire's jq path. 'plugins': { 'yas@yet-another-statusline': [{'installPath': str(plugin_root)}], }, @@ -201,30 +197,20 @@ def test_dry_run_marketplace_already_present(full_dry_env): result = run_install('--full', '--dry-run', env_extra=env) assert result.returncode == 0, result.stderr combined = result.stdout.lower() - assert 'already present' in combined or 'skipping' in combined + assert 'would update marketplace' in combined @requires_full_preflight def test_dry_run_would_install_when_plugin_absent(full_dry_env): config_dir, plugin_root, env = full_dry_env - # No installed_plugins.json → has() returns false → "Would install" - # But do_wire still needs to find the plugin root, so we can't omit the - # file entirely from the filesystem — we can leave it absent and accept - # that do_wire will fail after the dry-run install message. However the - # spec says dry-run should print "Would install" and exit 0. - # - # The script: ensure_plugin prints "Would install" (DRY_RUN=1), then - # do_wire tries to find PLUGIN_ROOT from installed_plugins.json which is - # absent → exits 1. So we seed a minimal installed_plugins.json that - # does NOT have the top-level yas key (triggering install path) but DOES - # have the .plugins entry so do_wire can resolve the renderer path. + # ensure_plugin checks `.plugins | has("yas@yet-another-statusline")`. + # do_wire scans `.plugins` for any key where ascii_downcase contains "yas". + # Use a different key name so ensure_plugin sees absent (→ "Would install") + # while do_wire can still resolve the renderer path via the "yas"-containing key. data = { - # .plugins key satisfies do_wire discovery. 'plugins': { - 'yas@yet-another-statusline': [{'installPath': str(plugin_root)}], + 'yas-cached@yet-another-statusline': [{'installPath': str(plugin_root)}], }, - # Note: 'yas@yet-another-statusline' is NOT a top-level key here, - # so ensure_plugin sees has() == false → "Would install". } (config_dir / 'plugins' / 'installed_plugins.json').write_text(json.dumps(data)) result = run_install('--full', '--dry-run', env_extra=env) From 7bf1e8182c2d81cb093724d8414ca5a7077a388f Mon Sep 17 00:00:00 2001 From: Test Date: Sat, 6 Jun 2026 18:21:31 +1000 Subject: [PATCH 3/4] fix(tests): isolate test_yas_full_width_fills_terminal from host env vars YAS_MAX_WIDTH and YAS_FULL_WIDTH set in the developer's shell leaked into Config.load() (which calls dict(os.environ)) and capped width at 40 instead of DEFAULT_MAX_WIDTH=140. Add monkeypatch.delenv for both vars before the test's calls so the test sees the true defaults regardless of the host environment. Co-Authored-By: Claude Sonnet 4.6 --- test/test_render_callable.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_render_callable.py b/test/test_render_callable.py index 68a5288..4072532 100644 --- a/test/test_render_callable.py +++ b/test/test_render_callable.py @@ -49,6 +49,9 @@ def test_yas_full_width_fills_terminal(tmp_path, monkeypatch, capsys): monkeypatch.setattr(app, 'terminal_width', lambda: fake_tw) monkeypatch.setattr(app, 'CLAUDE_DIR', tmp_path / '.claude') + # Isolate from any YAS_* env vars set in the host shell (e.g. YAS_MAX_WIDTH=40). + monkeypatch.delenv('YAS_MAX_WIDTH', raising=False) + monkeypatch.delenv('YAS_FULL_WIDTH', raising=False) def _first_line_width(env_extra): for k, v in env_extra.items(): From 842b90c4a7a2037d333dc624ef210dff0cf9124b Mon Sep 17 00:00:00 2001 From: Test Date: Sat, 6 Jun 2026 18:26:16 +1000 Subject: [PATCH 4/4] fix(renderer): remove duplicate elapsed_section with wrong glyph The elapsed_section method was defined twice: once with GLYPH_HOURGLASS (wider, misaligns layout width budget) and once without (correct, what the layout and snapshot tests expect). Remove the glyph variant. Co-Authored-By: Claude Sonnet 4.6 --- claude/yas/renderer.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/claude/yas/renderer.py b/claude/yas/renderer.py index eda034f..ba14d37 100644 --- a/claude/yas/renderer.py +++ b/claude/yas/renderer.py @@ -389,7 +389,7 @@ def fill_colour(self, pct: float) -> str: return self.safe def elapsed_section(self, elapsed: str) -> tuple[str, int]: - text = f'{GLYPH_HOURGLASS} {self.SESSION}{elapsed}{self.R}' + text = f'{self.SESSION}{elapsed}{self.R}' return text, _visible_width(text) def cache_section(self, remaining: float, elapsed_pct: int) -> tuple[str, int]: @@ -398,10 +398,6 @@ def cache_section(self, remaining: float, elapsed_pct: int) -> tuple[str, int]: text = f'{GLYPH_CACHE} {colour}{dur}{RESET}' return text, _visible_width(text) - def elapsed_section(self, elapsed: str) -> tuple[str, int]: - text = f'{self.SESSION}{elapsed}{self.R}' - return text, _visible_width(text) - def risk_zone_color(self, tokens: int) -> str: if tokens <= 50_000: return self.safe