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/packages/claude-code-plugin/hooks/lib/hud_context_bar.py b/packages/claude-code-plugin/hooks/lib/hud_context_bar.py index 5ec9dcd5..eeb2e67a 100644 --- a/packages/claude-code-plugin/hooks/lib/hud_context_bar.py +++ b/packages/claude-code-plugin/hooks/lib/hud_context_bar.py @@ -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: + + ``[] %[⚠]`` + + 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) diff --git a/packages/claude-code-plugin/tests/test_hud_context_bar.py b/packages/claude-code-plugin/tests/test_hud_context_bar.py index 51802b8e..d195e43c 100644 --- a/packages/claude-code-plugin/tests/test_hud_context_bar.py +++ b/packages/claude-code-plugin/tests/test_hud_context_bar.py @@ -1,4 +1,4 @@ -"""Skeleton sanity for hud_context_bar — Wave 2-E placeholder (#1463).""" +"""Behavior tests for hud_context_bar (Wave 2-E / #1326).""" import os import sys @@ -9,7 +9,205 @@ if _p not in sys.path: sys.path.insert(0, _p) +import hud_context_bar # noqa: E402 -def test_module_loads(): - """Contract: hud_context_bar must be importable. Wave 2-E will add real assertions.""" - import hud_context_bar # noqa: F401 +_FULL = "\u2588" +_EMPTY = "\u2591" +_DARK = "\u2593" +_WARN = "\u26a0" + + +# --------------------------- render_context_bar: basic shape -------------- + + +def test_zero_percent_all_empty(): + result = hud_context_bar.render_context_bar(0) + assert _FULL not in result + assert _EMPTY * hud_context_bar.CONTEXT_BAR_WIDTH in result + assert "0%" in result + + +def test_fifty_percent_half_filled(): + result = hud_context_bar.render_context_bar(50) + # 50% of 10 cells = 5 filled + assert _FULL * 5 in result + assert _EMPTY * 5 in result + assert "50%" in result + + +def test_hundred_percent_all_filled(): + """100% shows the full bar but also danger glyph + warning suffix.""" + result = hud_context_bar.render_context_bar(100) + # Danger glyph replaces last cell + assert _DARK in result + # And warning suffix + assert _WARN in result + assert "100%" in result + + +# --------------------------- shape: format -------------------------------- + + +def test_output_has_bracket_wrapper(): + result = hud_context_bar.render_context_bar(42) + assert result.startswith("[") + assert "] " in result + + +def test_output_shows_percent_symbol(): + result = hud_context_bar.render_context_bar(42) + assert "42%" in result + + +# --------------------------- rounding ------------------------------------- + + +def test_rounding_nearest_5pct_fills_half_cell(): + """5% rounds up to 1 filled cell (round-half-to-even).""" + result = hud_context_bar.render_context_bar(5) + # filled = round(5/100 * 10) = round(0.5) = 0 (banker's rounding) + # So 0 full cells expected. + assert _FULL not in result + + +def test_rounding_15pct_fills_2_cells(): + """15% → 1.5 → rounds to 2 (banker's rounding to even).""" + result = hud_context_bar.render_context_bar(15) + assert _FULL * 2 in result + + +def test_rounding_95pct_fills_10_with_danger(): + result = hud_context_bar.render_context_bar(95) + # filled = round(9.5) = 10 (banker's) → full bar with danger glyph + assert result.count(_FULL) + result.count(_DARK) == 10 + assert _DARK in result + assert _WARN in result + + +# --------------------------- warning / danger thresholds ------------------ + + +def test_below_warning_no_suffix(): + result = hud_context_bar.render_context_bar(50) + assert _WARN not in result + + +def test_at_warning_threshold_adds_suffix(): + """80% is the warning threshold (inclusive).""" + result = hud_context_bar.render_context_bar(80) + assert _WARN in result + + +def test_above_warning_has_suffix(): + result = hud_context_bar.render_context_bar(85) + assert _WARN in result + + +def test_below_danger_no_dark_glyph(): + result = hud_context_bar.render_context_bar(80) + # 80 is warning but below danger (85) → no dark glyph + assert _DARK not in result + + +def test_at_danger_threshold_has_dark_glyph(): + """85% is the danger threshold (inclusive).""" + result = hud_context_bar.render_context_bar(85) + assert _DARK in result + + +def test_above_danger_has_dark_glyph(): + result = hud_context_bar.render_context_bar(92) + assert _DARK in result + + +# --------------------------- clamping ------------------------------------- + + +def test_negative_clamped_to_zero(): + result = hud_context_bar.render_context_bar(-50) + assert "0%" in result + assert _FULL not in result + + +def test_above_100_clamped_to_100(): + result = hud_context_bar.render_context_bar(150) + assert "100%" in result + + +def test_non_numeric_treated_as_zero(): + result = hud_context_bar.render_context_bar("abc") + assert "0%" in result + + +def test_none_treated_as_zero(): + result = hud_context_bar.render_context_bar(None) + assert "0%" in result + + +def test_numeric_string_accepted(): + result = hud_context_bar.render_context_bar("42") + assert "42%" in result + + +# --------------------------- custom width --------------------------------- + + +def test_custom_width_20(): + result = hud_context_bar.render_context_bar(50, width=20) + # 50% of 20 = 10 filled + assert _FULL * 10 in result + + +def test_width_zero_returns_empty(): + assert hud_context_bar.render_context_bar(50, width=0) == "" + + +def test_width_one_minimal_bar(): + """Width 1 is a degenerate but valid case.""" + result = hud_context_bar.render_context_bar(100, width=1) + assert "[" in result + assert "]" in result + assert "100%" in result + + +# --------------------------- format_context_bar_segment ------------------- + + +def test_segment_empty_stdin(): + assert hud_context_bar.format_context_bar_segment({}) == "" + + +def test_segment_no_context_window(): + assert hud_context_bar.format_context_bar_segment({"cost": {}}) == "" + + +def test_segment_missing_used_percentage(): + stdin = {"context_window": {"total_tokens": 1000}} + assert hud_context_bar.format_context_bar_segment(stdin) == "" + + +def test_segment_normal_render(): + stdin = {"context_window": {"used_percentage": 42}} + result = hud_context_bar.format_context_bar_segment(stdin) + assert "42%" in result + assert "[" in result + + +def test_segment_zero_renders(): + """Zero percent still renders (not same as missing).""" + stdin = {"context_window": {"used_percentage": 0}} + result = hud_context_bar.format_context_bar_segment(stdin) + assert "0%" in result + + +# --------------------------- constants ------------------------------------ + + +def test_context_bar_width_default(): + assert hud_context_bar.CONTEXT_BAR_WIDTH == 10 + + +def test_thresholds_ordered(): + """warning ≤ danger ≤ critical.""" + w, d, c = hud_context_bar.CONTEXT_BAR_THRESHOLDS + assert w <= d <= c 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: