From 5835915177886fdcf1aaae28b9afe35e2f25b8de Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Sat, 11 Apr 2026 21:12:03 +0900 Subject: [PATCH 1/2] feat(hud): cost velocity indicator (Wave 2-B) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shows session spend rate next to absolute cost so users can see whether they're on a slow planning pass or a hot refactor burn: $1.23↗$0.08/m Trend glyphs: - ðŸ”Ĩ rate >= $0.20/min (hot burn) - ↗ $0.01 <= rate < $0.20/min (normal/rising) - → 0 < rate < $0.01/min (steady, very low) - ðŸ’Ī rate <= 0 (idle) Stateless implementation: rate = cost_usd / (duration_ms / 60k). Uses Claude Code stdin.cost.total_cost_usd and total_duration_ms directly, with hud_state.sessionStartTimestamp fallback for the elapsed-time component. Deliberately does NOT touch hud_state.py schema (costHistory was deferred per Wave 0 review). New lib/hud_velocity.py: - TREND_IDLE_MAX=0.01, TREND_HOT_MIN=0.20 constants - compute_spend_rate(cost_usd, duration_ms) -> float - trend_glyph(rate) -> str - format_velocity_segment(stdin_data, hud_state) -> str - format_cost_with_velocity(cost_usd, stdin, hud_state, *, is_exact) - _duration_ms_from_state helper for stdin fallback 33 new tests cover: - compute_spend_rate: zero/negative/non-numeric/numeric-string, known rates ($1/min, $0.50/30s, $6/10min) - trend_glyph: all 4 tiers + boundary values + non-numeric - format_velocity_segment: empty, missing cost, missing duration, zero rate, normal hot, rising tier, state fallback, no state - format_cost_with_velocity: exact/estimate prefix, no-velocity fallback, non-numeric cost, two decimals - Constant ordering 162/162 pass. Part of #1464 (Wave 0 statusbar refactor) --- .../hooks/lib/hud_velocity.py | 196 ++++++++++++++++- .../tests/test_hud_velocity.py | 208 +++++++++++++++++- 2 files changed, 389 insertions(+), 15 deletions(-) diff --git a/packages/claude-code-plugin/hooks/lib/hud_velocity.py b/packages/claude-code-plugin/hooks/lib/hud_velocity.py index 9e758375..9e81185d 100644 --- a/packages/claude-code-plugin/hooks/lib/hud_velocity.py +++ b/packages/claude-code-plugin/hooks/lib/hud_velocity.py @@ -1,15 +1,189 @@ -"""Cost velocity indicator for CodingBuddy statusLine (#1326). +"""Cost velocity indicator for CodingBuddy statusLine (#1326, Wave 2-B). -Wave 0 skeleton — reserved for **Wave 2-B**. +Shows how fast the session is burning dollars by rendering a +per-minute spend rate next to the absolute cost. Users can see at +a glance whether they're on a slow planning pass ($0.01/min) or a +heavy refactor burn ($0.50/min). -Planned contents (Wave 2-B owner fills): - * ``record_cost_sample(state_file: str, cost_usd: float, *, now=None) -> None`` - * ``compute_velocity(history: list[dict]) -> float`` — $/hour - * ``format_velocity_badge(velocity_usd_per_hour: float) -> str`` - * ``MAX_COST_HISTORY_ENTRIES`` constant +Output format:: -Wave 2-B will ALSO extend ``lib/hud_state.py`` with a -``"costHistory": []`` entry in both ``_EXTENDED_DEFAULTS`` and -``init_hud_state()`` (this is deliberately NOT done in Wave 0 — schema -design belongs with the feature owner). + $1.23↗$0.08/m + +- ``$1.23`` — absolute cost so far (passed through unchanged) +- ``↗/ðŸ”Ĩ/ðŸ’Ī`` — trend glyph reflecting the burn rate +- ``$0.08/m`` — computed spend rate (USD per minute) + +Wave 2-B ships a **stateless** session-average rate: ``rate = cost / +elapsed_minutes`` derived from Claude Code's own stdin ``cost`` and +``duration_ms`` fields. A richer windowed/ring-buffer implementation +can be added later without changing the public API; the stateless +version is sufficient for UX purposes and avoids touching the +``hud_state.py`` schema. + +Primary entry points: + +- :func:`compute_spend_rate` — pure arithmetic helper +- :func:`trend_glyph` — map rate → visual tier +- :func:`format_velocity_segment` — end-to-end renderer +- :func:`format_cost_with_velocity` — cost prefix + velocity suffix """ +from __future__ import annotations + +from typing import Any, Dict, Optional + +# ------------------------------------------------------------------------ +# Trend tier thresholds (USD per minute) +# ------------------------------------------------------------------------ + +#: Below this rate the session is considered idle / coasting. +TREND_IDLE_MAX: float = 0.01 + +#: Above this rate the session is a hot burn ("ðŸ”Ĩ"). +TREND_HOT_MIN: float = 0.20 + +# ------------------------------------------------------------------------ +# Glyphs +# ------------------------------------------------------------------------ + +_GLYPH_HOT = "\U0001f525" # ðŸ”Ĩ +_GLYPH_RISING = "\u2197" # ↗ +_GLYPH_STEADY = "\u2192" # → (sideways arrow for low steady rate) +_GLYPH_IDLE = "\U0001f4a4" # ðŸ’Ī (zzz face) + + +def compute_spend_rate(cost_usd: Any, duration_ms: Any) -> float: + """Return the session spend rate in USD per minute. + + Formula:: + + rate = cost_usd / (duration_ms / 60_000) + + Defensive coercion: + + * Non-numeric or negative inputs return ``0.0``. + * ``duration_ms <= 0`` returns ``0.0`` (avoids divide-by-zero). + * ``cost_usd == 0`` returns ``0.0`` regardless of duration. + """ + try: + cost = float(cost_usd) + duration = float(duration_ms) + except (TypeError, ValueError): + return 0.0 + if cost <= 0 or duration <= 0: + return 0.0 + minutes = duration / 60_000.0 + if minutes <= 0: + return 0.0 + return cost / minutes + + +def trend_glyph(rate_usd_per_min: float) -> str: + """Return a glyph reflecting the spend tier. + + Tiers: + + * ``rate >= TREND_HOT_MIN`` → ðŸ”Ĩ (hot burn) + * ``rate >= TREND_IDLE_MAX`` → ↗ (rising / normal) + * ``rate > 0`` → → (steady, very low) + * ``rate <= 0`` → ðŸ’Ī (idle, no meaningful rate) + """ + try: + rate = float(rate_usd_per_min) + except (TypeError, ValueError): + return _GLYPH_IDLE + if rate >= TREND_HOT_MIN: + return _GLYPH_HOT + if rate >= TREND_IDLE_MAX: + return _GLYPH_RISING + if rate > 0: + return _GLYPH_STEADY + return _GLYPH_IDLE + + +def format_velocity_segment( + stdin_data: Dict[str, Any], + hud_state: Optional[Dict[str, Any]] = None, +) -> str: + """Render the velocity suffix like ``↗$0.08/m``. + + Reads ``stdin_data.cost.total_cost_usd`` and + ``stdin_data.cost.total_duration_ms`` — both optional. Falls + back to ``hud_state.sessionStartTimestamp`` when stdin does not + supply the duration (useful when Claude Code omits the cost + payload early in a session). + + Returns an empty string when insufficient data is available to + compute a meaningful rate so callers can conditionally append + without extra guards. + """ + if not stdin_data: + return "" + + cost_info = stdin_data.get("cost") or {} + cost_usd = cost_info.get("total_cost_usd") + duration_ms = cost_info.get("total_duration_ms") + + if cost_usd is None: + return "" + + # When stdin doesn't carry duration, try computing from hud_state. + if duration_ms is None and hud_state: + duration_ms = _duration_ms_from_state(hud_state) + + if duration_ms is None: + return "" + + rate = compute_spend_rate(cost_usd, duration_ms) + if rate <= 0: + return "" + + glyph = trend_glyph(rate) + return f"{glyph}${rate:.2f}/m" + + +def format_cost_with_velocity( + cost_usd: Any, + stdin_data: Dict[str, Any], + hud_state: Optional[Dict[str, Any]] = None, + *, + is_exact: bool = True, +) -> str: + """Render the full cost segment with velocity appended. + + Output format: + + ``$1.23↗$0.08/m`` (when velocity is available) + ``$1.23`` (when velocity cannot be computed) + ``~$1.23↗$0.08/m`` (when cost is an estimate) + + Args: + cost_usd: Absolute session cost. Non-numeric → ``$0.00``. + stdin_data: Claude Code stdin payload (drives velocity). + hud_state: Optional HUD state for duration fallback. + is_exact: When ``False``, prefix with ``~`` to signal estimation. + """ + try: + cost = float(cost_usd) + except (TypeError, ValueError): + cost = 0.0 + prefix = "$" if is_exact else "~$" + velocity = format_velocity_segment(stdin_data, hud_state) + return f"{prefix}{cost:.2f}{velocity}" + + +def _duration_ms_from_state(hud_state: Dict[str, Any]) -> Optional[float]: + """Compute elapsed milliseconds from hud_state.sessionStartTimestamp.""" + ts = hud_state.get("sessionStartTimestamp", "") + if not ts: + return None + try: + from datetime import datetime, timezone + + start = datetime.fromisoformat(ts) + if start.tzinfo is None: + start = start.replace(tzinfo=timezone.utc) + now = datetime.now(timezone.utc) + delta = (now - start).total_seconds() * 1000.0 + return delta if delta > 0 else None + except (ValueError, TypeError): + return None diff --git a/packages/claude-code-plugin/tests/test_hud_velocity.py b/packages/claude-code-plugin/tests/test_hud_velocity.py index 53261cda..729e14c9 100644 --- a/packages/claude-code-plugin/tests/test_hud_velocity.py +++ b/packages/claude-code-plugin/tests/test_hud_velocity.py @@ -1,6 +1,7 @@ -"""Skeleton sanity for hud_velocity — Wave 2-B placeholder (#1463).""" +"""Behavior tests for hud_velocity cost spend rate (Wave 2-B / #1326).""" import os import sys +from datetime import datetime, timedelta, timezone _tests_dir = os.path.dirname(os.path.abspath(__file__)) _hooks_dir = os.path.join(os.path.dirname(_tests_dir), "hooks") @@ -9,7 +10,206 @@ if _p not in sys.path: sys.path.insert(0, _p) +import hud_velocity # noqa: E402 -def test_module_loads(): - """Contract: hud_velocity must be importable. Wave 2-B will add real assertions.""" - import hud_velocity # noqa: F401 +_HOT = "\U0001f525" +_RISING = "\u2197" +_STEADY = "\u2192" +_IDLE = "\U0001f4a4" + + +# --------------------------- compute_spend_rate --------------------------- + + +def test_compute_zero_cost_returns_zero(): + assert hud_velocity.compute_spend_rate(0, 60_000) == 0.0 + + +def test_compute_zero_duration_returns_zero(): + assert hud_velocity.compute_spend_rate(1.0, 0) == 0.0 + + +def test_compute_negative_cost_returns_zero(): + assert hud_velocity.compute_spend_rate(-1, 60_000) == 0.0 + + +def test_compute_negative_duration_returns_zero(): + assert hud_velocity.compute_spend_rate(1.0, -60_000) == 0.0 + + +def test_compute_non_numeric_cost_returns_zero(): + assert hud_velocity.compute_spend_rate("abc", 60_000) == 0.0 + + +def test_compute_non_numeric_duration_returns_zero(): + assert hud_velocity.compute_spend_rate(1.0, "abc") == 0.0 + + +def test_compute_1_dollar_1_minute(): + """$1 over 1 minute → $1/min.""" + assert hud_velocity.compute_spend_rate(1.0, 60_000) == 1.0 + + +def test_compute_half_dollar_30_seconds(): + """$0.50 over 30s → $1/min.""" + assert hud_velocity.compute_spend_rate(0.50, 30_000) == 1.0 + + +def test_compute_ten_minute_session(): + """$6 over 10 minutes → $0.60/min.""" + rate = hud_velocity.compute_spend_rate(6.0, 10 * 60_000) + assert abs(rate - 0.60) < 0.001 + + +def test_compute_numeric_strings_accepted(): + assert hud_velocity.compute_spend_rate("1.0", "60000") == 1.0 + + +# --------------------------- trend_glyph --------------------------------- + + +def test_trend_glyph_hot(): + """Rate >= $0.20/min → ðŸ”Ĩ.""" + assert hud_velocity.trend_glyph(0.25) == _HOT + assert hud_velocity.trend_glyph(1.0) == _HOT + + +def test_trend_glyph_rising(): + """$0.01 â‰Ī rate < $0.20 → ↗.""" + assert hud_velocity.trend_glyph(0.05) == _RISING + assert hud_velocity.trend_glyph(0.15) == _RISING + + +def test_trend_glyph_steady(): + """Positive but below idle-max → →.""" + assert hud_velocity.trend_glyph(0.005) == _STEADY + + +def test_trend_glyph_idle_zero(): + """Zero rate → ðŸ’Ī.""" + assert hud_velocity.trend_glyph(0.0) == _IDLE + + +def test_trend_glyph_idle_negative(): + """Negative rate (nonsense) → ðŸ’Ī.""" + assert hud_velocity.trend_glyph(-0.5) == _IDLE + + +def test_trend_glyph_non_numeric(): + assert hud_velocity.trend_glyph("abc") == _IDLE # type: ignore[arg-type] + + +def test_trend_glyph_threshold_boundaries(): + """Exact threshold values belong to the upper tier (inclusive).""" + assert hud_velocity.trend_glyph(hud_velocity.TREND_HOT_MIN) == _HOT + assert hud_velocity.trend_glyph(hud_velocity.TREND_IDLE_MAX) == _RISING + + +# --------------------------- format_velocity_segment --------------------- + + +def test_format_segment_empty_stdin_returns_empty(): + assert hud_velocity.format_velocity_segment({}) == "" + + +def test_format_segment_missing_cost_returns_empty(): + assert hud_velocity.format_velocity_segment({"cost": {}}) == "" + + +def test_format_segment_missing_duration_without_state_returns_empty(): + """Without hud_state fallback, missing duration → empty.""" + stdin = {"cost": {"total_cost_usd": 1.0}} + assert hud_velocity.format_velocity_segment(stdin) == "" + + +def test_format_segment_zero_rate_returns_empty(): + stdin = {"cost": {"total_cost_usd": 0.0, "total_duration_ms": 60_000}} + assert hud_velocity.format_velocity_segment(stdin) == "" + + +def test_format_segment_normal_rendering(): + """$1.00 over 60s → rate $1/min, hot burn tier.""" + stdin = {"cost": {"total_cost_usd": 1.0, "total_duration_ms": 60_000}} + result = hud_velocity.format_velocity_segment(stdin) + assert _HOT in result + assert "$1.00/m" in result + + +def test_format_segment_rising_tier(): + """$0.05 over 60s → rate $0.05/min → rising.""" + stdin = {"cost": {"total_cost_usd": 0.05, "total_duration_ms": 60_000}} + result = hud_velocity.format_velocity_segment(stdin) + assert _RISING in result + assert "$0.05/m" in result + + +def test_format_segment_duration_fallback_from_state(): + """When stdin lacks duration, use hud_state.sessionStartTimestamp.""" + start = (datetime.now(timezone.utc) - timedelta(minutes=10)).isoformat() + stdin = {"cost": {"total_cost_usd": 6.0}} + state = {"sessionStartTimestamp": start} + result = hud_velocity.format_velocity_segment(stdin, state) + assert result != "" + # Should be roughly $0.60/min + assert "$0.6" in result or "$0.5" in result # allow slight drift + + +def test_format_segment_duration_fallback_no_state(): + """No state → no fallback → empty.""" + stdin = {"cost": {"total_cost_usd": 6.0}} + assert hud_velocity.format_velocity_segment(stdin, None) == "" + + +# --------------------------- format_cost_with_velocity ------------------- + + +def test_cost_with_velocity_exact_prefix(): + stdin = {"cost": {"total_cost_usd": 1.23, "total_duration_ms": 60_000}} + result = hud_velocity.format_cost_with_velocity( + 1.23, stdin, is_exact=True + ) + assert result.startswith("$1.23") + + +def test_cost_with_velocity_estimate_prefix(): + stdin = {"cost": {"total_cost_usd": 1.23, "total_duration_ms": 60_000}} + result = hud_velocity.format_cost_with_velocity( + 1.23, stdin, is_exact=False + ) + assert result.startswith("~$1.23") + + +def test_cost_without_velocity_fallback(): + """Empty stdin → just the cost, no velocity suffix.""" + result = hud_velocity.format_cost_with_velocity(1.23, {}) + assert result == "$1.23" + + +def test_cost_non_numeric_coerced_to_zero(): + result = hud_velocity.format_cost_with_velocity("abc", {}) + assert result == "$0.00" + + +def test_cost_with_velocity_has_both_parts(): + stdin = {"cost": {"total_cost_usd": 1.23, "total_duration_ms": 60_000}} + result = hud_velocity.format_cost_with_velocity(1.23, stdin) + # Cost first, then velocity suffix + assert "$1.23" in result + assert "/m" in result + + +def test_cost_with_velocity_two_decimals(): + stdin = {"cost": {"total_cost_usd": 0.1234, "total_duration_ms": 60_000}} + result = hud_velocity.format_cost_with_velocity(0.1234, stdin) + assert "$0.12" in result + + +# --------------------------- constants ----------------------------------- + + +def test_hot_min_greater_than_idle_max(): + assert hud_velocity.TREND_HOT_MIN > hud_velocity.TREND_IDLE_MAX + + +def test_hot_min_is_positive(): + assert hud_velocity.TREND_HOT_MIN > 0 From 30a1d719685dc9f95db97bdb8632d528c2b3e0f6 Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Sat, 11 Apr 2026 21:40:59 +0900 Subject: [PATCH 2/2] fix(hud,landing): narrow fallback imports + bump next to 16.2.3 Combined Wave 0 polish items from the #1465/#1485 review cycle: 1. Narrow `except Exception` to `except ImportError` in the 3 lib fallback import blocks (qual-1465 HIGH-1). Real logic bugs (SyntaxError, NameError, AttributeError) inside lib modules now surface immediately instead of being silently swallowed by a catch-all. 2. Drop inline stub functions for format_rate_limits and _get_fresh_version (qual-1465 HIGH-2). Eliminates the signature drift between canonical lib definitions and in-file fallback stubs observed on the integrator branch (Wave 1-A plugin_json_file kwarg drift). The outer main() try/except still catches any runtime failure and emits the minimal safe output via the BUDDY_FACE constant. 3. Hoist hud_velocity and hud_cache_savings imports to module top as _format_velocity_segment and _format_cache_savings (perf-1485 H1). Eliminates ~0.47us sys.modules lookup per render. Integrator branch only - no-op on refactor/wave branches where the inline imports don't exist yet. 4. Bump next to 16.2.3 for GHSA-q4gf-8mx6-v5v3 (landing-security-check). Aligns eslint-config-next and updates setup.test.ts assertion. Refs: qual-1465 HIGH-1/2, perf-1485 H1, https://github.com/advisories/GHSA-q4gf-8mx6-v5v3 --- apps/landing-page/__tests__/setup.test.ts | 2 +- apps/landing-page/package.json | 4 +- .../hooks/codingbuddy-hud.py | 42 +++-- yarn.lock | 170 +++++++++++++++++- 4 files changed, 197 insertions(+), 21 deletions(-) diff --git a/apps/landing-page/__tests__/setup.test.ts b/apps/landing-page/__tests__/setup.test.ts index 00d1c0c6..1b5e64f8 100644 --- a/apps/landing-page/__tests__/setup.test.ts +++ b/apps/landing-page/__tests__/setup.test.ts @@ -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', () => { diff --git a/apps/landing-page/package.json b/apps/landing-page/package.json index 89265a00..a5c557ca 100644 --- a/apps/landing-page/package.json +++ b/apps/landing-page/package.json @@ -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", @@ -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", diff --git a/packages/claude-code-plugin/hooks/codingbuddy-hud.py b/packages/claude-code-plugin/hooks/codingbuddy-hud.py index c99ca493..a3472e4e 100644 --- a/packages/claude-code-plugin/hooks/codingbuddy-hud.py +++ b/packages/claude-code-plugin/hooks/codingbuddy-hud.py @@ -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 = { diff --git a/yarn.lock b/yarn.lock index 1eac05b0..76937473 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1561,6 +1561,13 @@ __metadata: languageName: node linkType: hard +"@next/env@npm:16.2.3": + version: 16.2.3 + resolution: "@next/env@npm:16.2.3" + checksum: 10c0/56c3fee8ea226efe59ef065e054380f872c00c45c9fe4475eaa45f80773c3c1adc3ead3ccdd77447d3c1aeb4b3004aaaa033dd4a100d3e572fd01b83f992dde8 + languageName: node + linkType: hard + "@next/eslint-plugin-next@npm:16.1.6": version: 16.1.6 resolution: "@next/eslint-plugin-next@npm:16.1.6" @@ -1570,6 +1577,15 @@ __metadata: languageName: node linkType: hard +"@next/eslint-plugin-next@npm:16.2.3": + version: 16.2.3 + resolution: "@next/eslint-plugin-next@npm:16.2.3" + dependencies: + fast-glob: "npm:3.3.1" + checksum: 10c0/be881aa89e0840ab60455b07a2bb9ec0d686c664a0d91e8ca815797a65ca71d7bd79d186b0df5b6892c2bf57bd07fa05421cd93e2812dfeaedfad5ed9fd1023e + languageName: node + linkType: hard + "@next/swc-darwin-arm64@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-darwin-arm64@npm:16.1.6" @@ -1577,6 +1593,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-darwin-arm64@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-darwin-arm64@npm:16.2.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@next/swc-darwin-x64@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-darwin-x64@npm:16.1.6" @@ -1584,6 +1607,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-darwin-x64@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-darwin-x64@npm:16.2.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@next/swc-linux-arm64-gnu@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-linux-arm64-gnu@npm:16.1.6" @@ -1591,6 +1621,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-arm64-gnu@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-linux-arm64-gnu@npm:16.2.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@next/swc-linux-arm64-musl@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-linux-arm64-musl@npm:16.1.6" @@ -1598,6 +1635,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-arm64-musl@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-linux-arm64-musl@npm:16.2.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@next/swc-linux-x64-gnu@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-linux-x64-gnu@npm:16.1.6" @@ -1605,6 +1649,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-x64-gnu@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-linux-x64-gnu@npm:16.2.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@next/swc-linux-x64-musl@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-linux-x64-musl@npm:16.1.6" @@ -1612,6 +1663,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-x64-musl@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-linux-x64-musl@npm:16.2.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@next/swc-win32-arm64-msvc@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-win32-arm64-msvc@npm:16.1.6" @@ -1619,6 +1677,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-arm64-msvc@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-win32-arm64-msvc@npm:16.2.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@next/swc-win32-x64-msvc@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-win32-x64-msvc@npm:16.1.6" @@ -1626,6 +1691,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-x64-msvc@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-win32-x64-msvc@npm:16.2.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -5276,6 +5348,15 @@ __metadata: languageName: node linkType: hard +"baseline-browser-mapping@npm:^2.9.19": + version: 2.10.17 + resolution: "baseline-browser-mapping@npm:2.10.17" + bin: + baseline-browser-mapping: dist/cli.cjs + checksum: 10c0/e792a92a6b206521681e3ab3a72770023f74a3274450bfe11ba55a075ba26f5820d5d2d02d92e25224b8d01e327b78fbf3e116bdc6ac74b3d9c52f5e3f4a048a + languageName: node + linkType: hard + "better-sqlite3@npm:^11.9.1": version: 11.10.0 resolution: "better-sqlite3@npm:11.10.0" @@ -7028,6 +7109,29 @@ __metadata: languageName: node linkType: hard +"eslint-config-next@npm:16.2.3": + version: 16.2.3 + resolution: "eslint-config-next@npm:16.2.3" + dependencies: + "@next/eslint-plugin-next": "npm:16.2.3" + eslint-import-resolver-node: "npm:^0.3.6" + eslint-import-resolver-typescript: "npm:^3.5.2" + eslint-plugin-import: "npm:^2.32.0" + eslint-plugin-jsx-a11y: "npm:^6.10.0" + eslint-plugin-react: "npm:^7.37.0" + eslint-plugin-react-hooks: "npm:^7.0.0" + globals: "npm:16.4.0" + typescript-eslint: "npm:^8.46.0" + peerDependencies: + eslint: ">=9.0.0" + typescript: ">=3.3.1" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/c6fd3accadb53c636f034baf4363d22847bf824c8ca1ecfa8047a4eee7882d156e75f60f37098357c7ae07e646dfaa23a176336abd3c74aa9a2df61aee984653 + languageName: node + linkType: hard + "eslint-config-prettier@npm:10.1.8": version: 10.1.8 resolution: "eslint-config-prettier@npm:10.1.8" @@ -8942,12 +9046,12 @@ __metadata: class-variance-authority: "npm:^0.7.1" clsx: "npm:^2.1.1" eslint: "npm:^9" - eslint-config-next: "npm:16.1.6" + eslint-config-next: "npm:16.2.3" happy-dom: "npm:^20.8.8" jest-axe: "npm:^10.0.0" lucide-react: "npm:^0.563.0" madge: "npm:^8.0.0" - next: "npm:16.1.6" + next: "npm:16.2.3" next-intl: "npm:^4.8.2" next-themes: "npm:^0.4.6" prettier: "npm:^3.4.2" @@ -9858,6 +9962,66 @@ __metadata: languageName: node linkType: hard +"next@npm:16.2.3": + version: 16.2.3 + resolution: "next@npm:16.2.3" + dependencies: + "@next/env": "npm:16.2.3" + "@next/swc-darwin-arm64": "npm:16.2.3" + "@next/swc-darwin-x64": "npm:16.2.3" + "@next/swc-linux-arm64-gnu": "npm:16.2.3" + "@next/swc-linux-arm64-musl": "npm:16.2.3" + "@next/swc-linux-x64-gnu": "npm:16.2.3" + "@next/swc-linux-x64-musl": "npm:16.2.3" + "@next/swc-win32-arm64-msvc": "npm:16.2.3" + "@next/swc-win32-x64-msvc": "npm:16.2.3" + "@swc/helpers": "npm:0.5.15" + baseline-browser-mapping: "npm:^2.9.19" + caniuse-lite: "npm:^1.0.30001579" + postcss: "npm:8.4.31" + sharp: "npm:^0.34.5" + styled-jsx: "npm:5.1.6" + peerDependencies: + "@opentelemetry/api": ^1.1.0 + "@playwright/test": ^1.51.1 + babel-plugin-react-compiler: "*" + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + dependenciesMeta: + "@next/swc-darwin-arm64": + optional: true + "@next/swc-darwin-x64": + optional: true + "@next/swc-linux-arm64-gnu": + optional: true + "@next/swc-linux-arm64-musl": + optional: true + "@next/swc-linux-x64-gnu": + optional: true + "@next/swc-linux-x64-musl": + optional: true + "@next/swc-win32-arm64-msvc": + optional: true + "@next/swc-win32-x64-msvc": + optional: true + sharp: + optional: true + peerDependenciesMeta: + "@opentelemetry/api": + optional: true + "@playwright/test": + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + bin: + next: dist/bin/next + checksum: 10c0/8a9d27fc773d69f7f471cf1a23bde2ab2950e0411ef3e0d5c1664ed9654e94c3304eae1c4283ec0fa4e70e7b3f4416913350e118e0c18e8b055693dc5d021883 + languageName: node + linkType: hard + "node-abi@npm:^3.3.0": version: 3.89.0 resolution: "node-abi@npm:3.89.0" @@ -11302,7 +11466,7 @@ __metadata: languageName: node linkType: hard -"sharp@npm:^0.34.4": +"sharp@npm:^0.34.4, sharp@npm:^0.34.5": version: 0.34.5 resolution: "sharp@npm:0.34.5" dependencies: