From 956aa88f779fa7b7120510a71e61ce9bc096b179 Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Sat, 11 Apr 2026 20:51:24 +0900 Subject: [PATCH 1/2] feat(hud): cache savings badge (Wave 2-C) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude API's prompt caching discounts cache_read_input_tokens by 90%. This module quantifies that saving so the HUD can surface "💰$4.56 saved" as a badge appended to the cost segment. New lib/hud_cache_savings.py: - compute_cache_savings(cache_read_tokens, model_id) -> float Pure arithmetic helper with defensive coercion for malformed input. - format_cache_savings(stdin_data) -> str End-to-end renderer that reads Claude Code stdin, extracts cache_read_input_tokens and model.id, returns "💰$N.NN saved" or "" below the $0.01 noise floor. - Known model families: haiku ($0.80/M), sonnet ($3/M), opus ($15/M). Unknown models fall back to sonnet-tier pricing (conservative). 26 new tests in test_hud_cache_savings.py cover: - All 3 model families + unknown + empty - Case-insensitive matching - Zero / negative / non-numeric / numeric-string inputs - Empty stdin / missing context_window / missing current_usage - Below $0.01 noise floor - display_name fallback when model.id is absent - Two-decimal formatting Part of #1464 (Wave 0 statusbar refactor) --- .../hooks/lib/hud_cache_savings.py | 125 ++++++++++- .../tests/test_hud_cache_savings.py | 203 +++++++++++++++++- 2 files changed, 314 insertions(+), 14 deletions(-) diff --git a/packages/claude-code-plugin/hooks/lib/hud_cache_savings.py b/packages/claude-code-plugin/hooks/lib/hud_cache_savings.py index 8f4f843f..3bf79a29 100644 --- a/packages/claude-code-plugin/hooks/lib/hud_cache_savings.py +++ b/packages/claude-code-plugin/hooks/lib/hud_cache_savings.py @@ -1,14 +1,119 @@ -"""Cache-savings badge for CodingBuddy statusLine (#1326). +"""Cache savings calculator for CodingBuddy statusLine (#1326, Wave 2-C). -Wave 0 skeleton — reserved for **Wave 2-C**. +Claude API's prompt caching charges ``cache_read_input_tokens`` at +10% of the base input price — a 90% discount. This module quantifies +that discount so the HUD can surface "how much you saved by caching" +as a badge like ``"💰$4.56 saved"`` appended to the cost segment. -Planned contents (Wave 2-C owner fills): - * ``compute_cache_savings(cost_breakdown: dict) -> float`` — USD - avoided by cache hits - * ``format_cache_savings_badge(savings_usd: float) -> str`` +Primary entry points: -Source of truth for the computation is the stdin ``cost`` payload -(cached-input vs non-cached-input token counts combined with -``MODEL_PRICING`` — both already available in ``codingbuddy-hud``). -This module will be the single import target for Wave 3 assembly. +- :func:`compute_cache_savings` — pure arithmetic helper (tokens + + model_id → dollars saved). +- :func:`format_cache_savings` — end-to-end renderer that reads + Claude Code stdin, extracts the relevant fields, and returns the + formatted badge string (or ``""`` when there is nothing to show). """ +from __future__ import annotations + +from typing import Any, Dict + +# Money glyph — U+1F4B0 money bag emoji +_MONEY_GLYPH: str = "\U0001f4b0" # 💰 + +# cache_read tokens cost 10% of the input price, so the per-token +# savings equals 90% of the input price. +_CACHE_DISCOUNT: float = 0.90 + +# Minimum dollar savings required to show the badge. Hides noise +# below one cent so the status bar does not flicker on tiny reads. +_MIN_DISPLAY_USD: float = 0.01 + +# Baseline input prices in USD per million tokens. Mirrors the +# ``MODEL_PRICING`` table in ``codingbuddy-hud.py``. +_INPUT_PRICE_PER_M: Dict[str, float] = { + "haiku": 0.80, + "sonnet": 3.00, + "opus": 15.00, +} + +# Sonnet as the safe default when the model family cannot be +# identified. Avoids over-claiming savings on unknown tiers. +_DEFAULT_INPUT_PRICE_PER_M: float = 3.00 + + +def _input_price_per_million(model_id: str) -> float: + """Return the baseline input price (USD per million tokens). + + Case-insensitive substring match against the known family keys. + Falls back to the sonnet tier when no key matches. + """ + if not model_id: + return _DEFAULT_INPUT_PRICE_PER_M + lowered = model_id.lower() + for key, price in _INPUT_PRICE_PER_M.items(): + if key in lowered: + return price + return _DEFAULT_INPUT_PRICE_PER_M + + +def compute_cache_savings( + cache_read_tokens: Any, + model_id: str, +) -> float: + """Return the dollar amount saved by cache reads. + + Formula:: + + savings = cache_read_tokens * (input_price / 1_000_000) * 0.90 + + Defensive coercion: negative or non-numeric inputs return + ``0.0`` so callers never render a "saved -$0.12" surprise when + upstream payloads are malformed. + """ + try: + tokens = int(cache_read_tokens) + except (TypeError, ValueError): + return 0.0 + if tokens <= 0: + return 0.0 + price = _input_price_per_million(model_id) + return (tokens / 1_000_000.0) * price * _CACHE_DISCOUNT + + +def format_cache_savings(stdin_data: Dict[str, Any]) -> str: + """Render the cache savings badge from a stdin payload. + + Output format: + + ``💰$4.56 saved`` + + Returns an empty string when any of the following hold: + + * ``stdin_data`` is empty or has no ``context_window`` + * ``current_usage`` is missing + * ``cache_read_input_tokens`` is zero, absent, or negative + * Computed savings < ``$0.01`` (noise floor) + + Model identification is sourced from ``stdin_data.model.id`` + (or ``display_name`` fallback). Unknown models default to the + sonnet-tier input price so the display still shows a + conservative estimate. + """ + if not stdin_data: + return "" + + ctx = stdin_data.get("context_window") or {} + usage = ctx.get("current_usage") or {} + cache_read = usage.get("cache_read_input_tokens", 0) or 0 + + if not cache_read or (isinstance(cache_read, (int, float)) and cache_read <= 0): + return "" + + model_info = stdin_data.get("model") or {} + model_id = model_info.get("id") or model_info.get("display_name") or "" + + savings = compute_cache_savings(cache_read, model_id) + if savings < _MIN_DISPLAY_USD: + return "" + + return f"{_MONEY_GLYPH}${savings:.2f} saved" diff --git a/packages/claude-code-plugin/tests/test_hud_cache_savings.py b/packages/claude-code-plugin/tests/test_hud_cache_savings.py index 61e2a815..877b79bb 100644 --- a/packages/claude-code-plugin/tests/test_hud_cache_savings.py +++ b/packages/claude-code-plugin/tests/test_hud_cache_savings.py @@ -1,4 +1,4 @@ -"""Skeleton sanity for hud_cache_savings — Wave 2-C placeholder (#1463).""" +"""Behavior tests for hud_cache_savings (Wave 2-C / #1326).""" import os import sys @@ -9,7 +9,202 @@ if _p not in sys.path: sys.path.insert(0, _p) +import hud_cache_savings # noqa: E402 -def test_module_loads(): - """Contract: hud_cache_savings must be importable. Wave 2-C will add real assertions.""" - import hud_cache_savings # noqa: F401 +_MONEY = "\U0001f4b0" # 💰 + + +# --------------------------- _input_price_per_million ---------------------- + + +def test_input_price_haiku(): + assert hud_cache_savings._input_price_per_million("claude-haiku-4-5") == 0.80 + + +def test_input_price_sonnet(): + assert hud_cache_savings._input_price_per_million("claude-sonnet-4-6") == 3.00 + + +def test_input_price_opus(): + assert hud_cache_savings._input_price_per_million("claude-opus-4-6") == 15.00 + + +def test_input_price_unknown_defaults_to_sonnet(): + assert hud_cache_savings._input_price_per_million("gpt-4") == 3.00 + + +def test_input_price_empty_defaults(): + assert hud_cache_savings._input_price_per_million("") == 3.00 + + +def test_input_price_case_insensitive(): + assert hud_cache_savings._input_price_per_million("CLAUDE-OPUS-4") == 15.00 + + +# --------------------------- compute_cache_savings ------------------------ + + +def test_compute_zero_tokens_returns_zero(): + assert hud_cache_savings.compute_cache_savings(0, "opus") == 0.0 + + +def test_compute_negative_tokens_returns_zero(): + assert hud_cache_savings.compute_cache_savings(-100, "opus") == 0.0 + + +def test_compute_non_numeric_returns_zero(): + assert hud_cache_savings.compute_cache_savings("abc", "opus") == 0.0 + assert hud_cache_savings.compute_cache_savings(None, "opus") == 0.0 + + +def test_compute_opus_savings(): + """1M cache_read tokens on opus → 1M * $15/M * 0.9 = $13.50 saved.""" + result = hud_cache_savings.compute_cache_savings(1_000_000, "claude-opus") + assert abs(result - 13.50) < 0.001 + + +def test_compute_sonnet_savings(): + """1M cache_read tokens on sonnet → 1M * $3/M * 0.9 = $2.70 saved.""" + result = hud_cache_savings.compute_cache_savings(1_000_000, "claude-sonnet") + assert abs(result - 2.70) < 0.001 + + +def test_compute_haiku_savings(): + """1M cache_read tokens on haiku → 1M * $0.80/M * 0.9 = $0.72 saved.""" + result = hud_cache_savings.compute_cache_savings(1_000_000, "claude-haiku") + assert abs(result - 0.72) < 0.001 + + +def test_compute_scales_linearly(): + """Double the tokens → double the savings.""" + a = hud_cache_savings.compute_cache_savings(100_000, "opus") + b = hud_cache_savings.compute_cache_savings(200_000, "opus") + assert abs(b - 2 * a) < 0.001 + + +def test_compute_numeric_string_accepted(): + """Numeric string coerced via int().""" + result = hud_cache_savings.compute_cache_savings("500000", "sonnet") + assert result > 0 + + +# --------------------------- format_cache_savings ------------------------- + + +def test_format_empty_stdin_returns_empty(): + assert hud_cache_savings.format_cache_savings({}) == "" + + +def test_format_no_context_window_returns_empty(): + assert hud_cache_savings.format_cache_savings({"cost": {}}) == "" + + +def test_format_no_current_usage_returns_empty(): + stdin = {"context_window": {}} + assert hud_cache_savings.format_cache_savings(stdin) == "" + + +def test_format_zero_cache_read_returns_empty(): + stdin = { + "context_window": { + "current_usage": {"cache_read_input_tokens": 0} + } + } + assert hud_cache_savings.format_cache_savings(stdin) == "" + + +def test_format_missing_cache_read_returns_empty(): + stdin = { + "context_window": { + "current_usage": {"input_tokens": 1000} + } + } + assert hud_cache_savings.format_cache_savings(stdin) == "" + + +def test_format_below_one_cent_returns_empty(): + """Tiny savings (< $0.01) are hidden to avoid flicker.""" + stdin = { + "context_window": { + "current_usage": {"cache_read_input_tokens": 100} + }, + "model": {"id": "claude-sonnet"}, + } + # 100 tokens * $3/M * 0.9 = $0.00027 → below threshold + result = hud_cache_savings.format_cache_savings(stdin) + assert result == "" + + +def test_format_meaningful_savings_opus(): + """500K cache_read tokens on opus → $6.75 saved.""" + stdin = { + "context_window": { + "current_usage": {"cache_read_input_tokens": 500_000} + }, + "model": {"id": "claude-opus-4-6"}, + } + result = hud_cache_savings.format_cache_savings(stdin) + assert result.startswith(_MONEY) + assert "6.75" in result + assert "saved" in result + + +def test_format_uses_display_name_fallback(): + """When model.id is empty, fall back to display_name for pricing.""" + stdin = { + "context_window": { + "current_usage": {"cache_read_input_tokens": 1_000_000} + }, + "model": {"display_name": "Opus 4.6"}, + } + result = hud_cache_savings.format_cache_savings(stdin) + assert "13.50" in result + + +def test_format_unknown_model_uses_sonnet_default(): + """Unknown model → sonnet-tier pricing ($2.70 per 1M tokens).""" + stdin = { + "context_window": { + "current_usage": {"cache_read_input_tokens": 1_000_000} + }, + "model": {"id": "some-unknown"}, + } + result = hud_cache_savings.format_cache_savings(stdin) + assert "2.70" in result + + +def test_format_uses_money_glyph(): + stdin = { + "context_window": { + "current_usage": {"cache_read_input_tokens": 1_000_000} + }, + "model": {"id": "opus"}, + } + result = hud_cache_savings.format_cache_savings(stdin) + assert result.startswith(_MONEY) + + +def test_format_two_decimal_places(): + """Output always has 2 decimal places.""" + stdin = { + "context_window": { + "current_usage": {"cache_read_input_tokens": 100_000} + }, + "model": {"id": "opus"}, + } + result = hud_cache_savings.format_cache_savings(stdin) + # Should look like "💰$1.35 saved" + import re + + assert re.search(r"\$\d+\.\d{2} saved", result) + + +def test_format_negative_tokens_returns_empty(): + """Malformed payload with negative cache_read is silently skipped.""" + stdin = { + "context_window": { + "current_usage": {"cache_read_input_tokens": -500} + }, + "model": {"id": "opus"}, + } + assert hud_cache_savings.format_cache_savings(stdin) == "" From c1c81cd84193582f6c295217ee734a5282e97dbc 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: