Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/landing-page/__tests__/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('Next.js 16 Project Setup', () => {
});

test('Next.js 16.x is installed and locked', () => {
expect(pkg.dependencies.next).toBe('16.1.6');
expect(pkg.dependencies.next).toBe('16.2.3');
});

test('React 19.x is installed and locked', () => {
Expand Down
4 changes: 2 additions & 2 deletions apps/landing-page/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.563.0",
"next": "16.1.6",
"next": "16.2.3",
"next-intl": "^4.8.2",
"next-themes": "^0.4.6",
"prism-react-renderer": "^2.4.1",
Expand All @@ -56,7 +56,7 @@
"axe-core": "^4.11.1",
"babel-plugin-react-compiler": "1.0.0",
"eslint": "^9",
"eslint-config-next": "16.1.6",
"eslint-config-next": "16.2.3",
"happy-dom": "^20.8.8",
"jest-axe": "^10.0.0",
"madge": "^8.0.0",
Expand Down
42 changes: 27 additions & 15 deletions packages/claude-code-plugin/hooks/codingbuddy-hud.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,40 @@
sys.path.insert(0, _LIB_DIR)

# === test_hud.py compatibility re-exports — DO NOT REMOVE without coordinated test update ===
# Defensive fallback: statusLine is a hot path invoked by Claude Code on
# every render. If any lib module is temporarily broken (e.g. mid-wave
# refactor), fall back to minimal inline implementations so the status
# bar still renders instead of crashing the Claude Code subprocess.
# Narrow the fallback to ImportError only: real logic bugs in lib modules
# (SyntaxError, NameError, AttributeError) must surface immediately instead
# of being silently swallowed by a catch-all. If a lib module fails to import
# entirely, the outer main() try/except at the bottom of this file still
# emits the minimal safe output via the BUDDY_FACE constant.
try:
from hud_buddy import BUDDY_FACE # canonical SSoT via tiny_actor_presets
except Exception: # pragma: no cover - defensive
BUDDY_FACE = "\u25d5\u203f\u25d5" # ◕‿◕
except ImportError: # pragma: no cover - defensive
BUDDY_FACE = "◕‿◕" # minimal constant for safe-output path

try:
from hud_rate_limits import format_rate_limits
except Exception: # pragma: no cover - defensive
def format_rate_limits(stdin_data: dict) -> str: # type: ignore[misc]
return ""
from hud_rate_limits import format_rate_limits # noqa: F401 re-exported for test_hud.py
except ImportError: # pragma: no cover - defensive
pass # main() catch-all handles absence

try:
from hud_version import get_fresh_version as _get_fresh_version # backcompat alias
except Exception: # pragma: no cover - defensive
def _get_fresh_version( # type: ignore[misc]
hud_state: dict, *, plugins_file: str = ""
) -> str:
return hud_state.get("version", "")
except ImportError: # pragma: no cover - defensive
pass # main() catch-all handles absence

# Wave 2-B velocity + Wave 2-C cache savings hot-path suffixes for the cost segment.
# Hoisted to module top per perf-1485 H1 so format_status_line avoids a
# sys.modules lookup on every render (~0.47μs saved per call).
try:
from hud_velocity import format_velocity_segment as _format_velocity_segment
except ImportError: # pragma: no cover - defensive
def _format_velocity_segment(stdin_data, hud_state=None): # type: ignore[misc]
return ""

try:
from hud_cache_savings import format_cache_savings as _format_cache_savings
except ImportError: # pragma: no cover - defensive
def _format_cache_savings(stdin_data): # type: ignore[misc]
return ""

# Agent eye glyphs from .ai-rules agent definitions.
AGENT_GLYPHS = {
Expand Down
143 changes: 132 additions & 11 deletions packages/claude-code-plugin/hooks/lib/hud_context_bar.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,136 @@
"""Smart context bar visualization for CodingBuddy statusLine (#1326).
"""Smart context bar visualization for CodingBuddy statusLine (#1326, Wave 2-E).

Wave 0 skeleton — reserved for **Wave 2-E**.
Renders the context-window usage percentage as a compact visual
progress bar so users can see at a glance how close they are to
overflow:

Planned contents (Wave 2-E owner fills):
* ``CONTEXT_BAR_WIDTH: int`` — segment count
* ``CONTEXT_BAR_THRESHOLDS: tuple[float, float, float]`` — warning
/ danger / critical cut-offs
* ``render_context_bar(used_tokens: int, total_tokens: int) -> str``
``[████░░░░░░] 42%`` (safe — low usage)
``[███████░░░] 73%`` (warning — approaching the danger zone)
``[█████████▓] 92%⚠`` (critical — near exhaustion)

Wave 2-E will render the bar from the ``context`` payload already
parsed in ``codingbuddy-hud``. This file is a reserved import target
so Wave 3 integration can depend on ``hud_context_bar`` without
creating the module mid-merge.
Primary entry point: :func:`render_context_bar`.

The bar width is :data:`CONTEXT_BAR_WIDTH` (10 cells by default),
chosen to keep the status line compact while still providing
meaningful resolution (each cell represents ~10% of the budget).

Thresholds (:data:`CONTEXT_BAR_THRESHOLDS`) drive two visual
signals:

1. A dark-shade glyph (``▓``) replaces the trailing full block when
usage crosses the *danger* threshold — so the last cell pulses
visually even when the bar looks otherwise full.
2. A ``⚠`` suffix is appended when usage crosses the *warning*
threshold — a distinct text marker that survives greyscale /
monochrome renders.
"""
from __future__ import annotations

from typing import Any, Dict, Tuple

# ------------------------------------------------------------------------
# Constants
# ------------------------------------------------------------------------

#: Number of cells in the rendered bar. 10 gives ~10% resolution per cell.
CONTEXT_BAR_WIDTH: int = 10

#: (warning, danger, critical) thresholds as percentages.
#:
#: * ``warning`` (80) — append a ``⚠`` suffix
#: * ``danger`` (85) — replace the trailing full block with ``▓``
#: * ``critical`` (95) — both signals always active
CONTEXT_BAR_THRESHOLDS: Tuple[float, float, float] = (80.0, 85.0, 95.0)

# Block-drawing glyphs
_FULL = "\u2588" # █
_EMPTY = "\u2591" # ░
_DARK = "\u2593" # ▓

# Warning suffix
_WARN = "\u26a0" # ⚠


def _clamp_percentage(pct: Any) -> float:
"""Coerce an arbitrary value to a percentage in ``[0.0, 100.0]``.

Non-numeric inputs return ``0.0``. Values above 100 are capped
at 100; values below 0 are floored at 0.
"""
try:
value = float(pct)
except (TypeError, ValueError):
return 0.0
if value < 0.0:
return 0.0
if value > 100.0:
return 100.0
return value


def render_context_bar(
used_pct: Any,
*,
width: int = CONTEXT_BAR_WIDTH,
) -> str:
"""Render a context-bar string from a usage percentage.

Output format:

``[<bar>] <N>%[⚠]``

The bar contains ``width`` cells; each cell represents
``100 / width`` percent. The number of filled cells is
``round(used_pct / 100 * width)``. When usage crosses the
danger threshold, the last full block becomes ``▓`` to make
the "full" state visually distinct from a true max. When usage
crosses the warning threshold, a trailing ``⚠`` is appended.

Args:
used_pct: Context-window usage percentage (0-100).
Accepts ``int``/``float``/numeric string. Non-numeric
input renders as ``0%``.
width: Override the bar cell count (tests / layout tuning).

Returns an empty string when ``width <= 0``.
"""
if width <= 0:
return ""

pct = _clamp_percentage(used_pct)
warning, danger, _critical = CONTEXT_BAR_THRESHOLDS

# Number of filled cells (rounded for UX — 5% fills half a cell).
filled = int(round(pct / 100.0 * width))
if filled < 0:
filled = 0
if filled > width:
filled = width

# Build the bar
bar_cells = [_FULL] * filled + [_EMPTY] * (width - filled)

# Danger glyph replaces the trailing full block
if pct >= danger and filled > 0:
bar_cells[filled - 1] = _DARK

bar = "".join(bar_cells)
suffix = _WARN if pct >= warning else ""
return f"[{bar}] {pct:.0f}%{suffix}"


def format_context_bar_segment(stdin_data: Dict[str, Any]) -> str:
"""Render the context bar from a Claude Code stdin payload.

Extracts ``context_window.used_percentage`` and forwards to
:func:`render_context_bar`. Returns an empty string when the
field is absent — callers can append the result conditionally
without surrounding logic.
"""
if not stdin_data:
return ""
ctx = stdin_data.get("context_window") or {}
pct = ctx.get("used_percentage")
if pct is None:
return ""
return render_context_bar(pct)
Loading
Loading