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..520f8444 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 = { @@ -480,6 +492,21 @@ def main(): state_file = os.environ.get("CODINGBUDDY_HUD_STATE_FILE", DEFAULT_STATE_FILE) hud_state = read_state(state_file) + # Wave 1-B: self-heal stale state (e.g. manual-fix marker, + # old timestamp, stdin session mismatch) before rendering so + # the HUD never shows leftover fields from a prior session. + try: + from hud_session import detect_stale_session, heal_stale_state + stdin_session_id = ( + stdin_data.get("session_id") if stdin_data else "" + ) or "" + if detect_stale_session( + hud_state, stdin_session_id=stdin_session_id + ): + hud_state = heal_stale_state(hud_state) + except Exception: + pass # never block rendering on self-heal failure + env_agent = os.environ.get("CODINGBUDDY_ACTIVE_AGENT", "") output = format_status_line(stdin_data, hud_state, env_agent) diff --git a/packages/claude-code-plugin/hooks/lib/hud_session.py b/packages/claude-code-plugin/hooks/lib/hud_session.py index 44f5e0de..5fad64e1 100644 --- a/packages/claude-code-plugin/hooks/lib/hud_session.py +++ b/packages/claude-code-plugin/hooks/lib/hud_session.py @@ -1,16 +1,155 @@ -"""Session self-heal and stale state detection (#1326). +"""Session self-heal and stale state detection (#1326, Wave 1-B). -Wave 0 skeleton — reserved for **Wave 1-B**. +Addresses the bug where ``hud-state.json`` retains stale fields from +a previous session (e.g., ``sessionId="manual-fix"``, ``version="5.2.0"``) +and the statusLine renders them as if they were current. This was the +root cause of the bug report: "현재 PLAN 모드인데 ACT로 되어 있고". -Planned contents (Wave 1-B owner fills): - * ``detect_stale_session(state: dict, *, now: datetime | None = None) -> bool`` - * ``reset_stale_session(state_file: str) -> None`` - * ``SESSION_STALE_SECONDS`` constant +When Claude Code invokes statusLine, stdin carries the real session +ID. If it does not match ``hud_state.sessionId``, the leftover state +is a snapshot from a different session (or a manual edit) and must +be healed before rendering. Additionally, any state older than +``SESSION_STALE_SECONDS`` is treated as stale even without a stdin +mismatch so abandoned sessions do not bleed into fresh ones. -The current monolith embeds no session self-heal logic; Wave 1-B will -introduce both the helpers and their call site in -``codingbuddy-hud.format_status_line`` (or its Wave 1-D successor in -``hud_layout``). This module exists as a placeholder so Wave 1-B can -commit to its own sub-branch without racing other Wave workers to -create the file. +Healing is a *soft reset*: the cleared fields (currentMode, version, +activeAgent, phase, focus, blockerCount) are overwritten in memory +but the file on disk is not touched — that is the responsibility of +``session-start.py`` or an explicit ``reset_stale_session()`` call. """ +from __future__ import annotations + +from datetime import datetime, timezone +from typing import Any, Dict, Optional + +# A session older than this is considered stale even when the session +# ID matches. Four hours covers lunch breaks and short meetings but +# catches overnight leftovers and manual edits from yesterday. +SESSION_STALE_SECONDS = 4 * 60 * 60 # 4 hours + +# sessionId values that indicate a not-really-a-session state. Any +# match triggers an immediate heal regardless of other signals. +_REPAIR_MARKERS = frozenset({"", "manual-fix", "unknown", "none"}) + + +def detect_stale_session( + state: Dict[str, Any], + *, + now: Optional[datetime] = None, + stdin_session_id: str = "", +) -> bool: + """Return True if ``state`` should be healed before rendering. + + Staleness indicators (any one triggers stale): + + 1. ``state`` is empty (nothing to heal — returns False). + 2. ``state.sessionId`` is a repair marker (``""``, ``"manual-fix"``, + ``"unknown"``, ``"none"``). + 3. ``stdin_session_id`` is non-empty and differs from + ``state.sessionId`` — caller is from a different session. + 4. ``state.sessionStartTimestamp`` is older than + :data:`SESSION_STALE_SECONDS` or unparseable. + + Args: + state: Current HUD state dict from ``read_hud_state``. + now: Optional clock override for deterministic age tests. + Defaults to ``datetime.now(timezone.utc)``. + stdin_session_id: The current Claude Code session id read + from stdin. Empty string means "not available — skip + mismatch check". + """ + if not state: + return False + + session_id = state.get("sessionId", "") or "" + + # (2) Repair marker check + if session_id in _REPAIR_MARKERS: + return True + + # (3) stdin mismatch check + if stdin_session_id and session_id != stdin_session_id: + return True + + # (4) Age check — prefer `updatedAt` (refreshed on every + # `update_hud_state` write) so long active sessions do not + # falsely flag stale after SESSION_STALE_SECONDS. Fall back + # to `sessionStartTimestamp` when `updatedAt` is absent. + ts = state.get("updatedAt", "") or state.get("sessionStartTimestamp", "") + if ts: + try: + start = datetime.fromisoformat(ts) + if start.tzinfo is None: + start = start.replace(tzinfo=timezone.utc) + current = now or datetime.now(timezone.utc) + age_seconds = (current - start).total_seconds() + if age_seconds > SESSION_STALE_SECONDS: + return True + except (ValueError, TypeError): + # Unparseable timestamp => definitely stale + return True + + return False + + +def heal_stale_state(state: Dict[str, Any]) -> Dict[str, Any]: + """Return a *new* state dict with ephemeral fields cleared. + + Does **not** mutate the input and does **not** write to disk. The + caller is expected to pass the healed copy to ``format_status_line`` + immediately; persisting a fresh baseline is the responsibility of + ``session-start.py`` on the next session boot or of + :func:`reset_stale_session` for callers that want durability now. + + Cleared fields (so the HUD renders a safe default): + + - ``currentMode`` → ``None`` (statusLine shows "Ready") + - ``version`` → ``""`` (hud_version falls back to plugin.json) + - ``activeAgent`` → ``None`` + - ``phase`` → ``"ready"`` + - ``focus`` → ``None`` + - ``blockerCount``→ ``0`` + + Preserved fields: + + - ``sessionId`` (so debugging can see what was there) + - ``sessionStartTimestamp`` (for audit / forensics) + - Any other field not listed above + """ + healed: Dict[str, Any] = dict(state) + healed["currentMode"] = None + healed["version"] = "" + healed["activeAgent"] = None + healed["phase"] = "ready" + healed["focus"] = None + healed["blockerCount"] = 0 + return healed + + +def reset_stale_session(state_file: str) -> None: + """Persist a healed copy of ``state_file`` to disk. + + Reads the current state, runs :func:`detect_stale_session` on it, + and if stale, writes the healed copy via ``hud_state.update_hud_state``. + Intended for call sites that need durable healing (e.g., session + boot). No-ops silently on any failure so it never blocks the caller. + """ + try: + from hud_state import read_hud_state, update_hud_state + + current = read_hud_state(state_file, fill_defaults=False) + if not detect_stale_session(current): + return + healed = heal_stale_state(current) + # update_hud_state merges kwargs — only pass the fields we healed + update_hud_state( + state_file=state_file, + currentMode=healed["currentMode"], + version=healed["version"], + activeAgent=healed["activeAgent"], + phase=healed["phase"], + focus=healed["focus"], + blockerCount=healed["blockerCount"], + ) + except Exception: + pass diff --git a/packages/claude-code-plugin/tests/test_hud_session.py b/packages/claude-code-plugin/tests/test_hud_session.py index 36d1bc8d..9a979597 100644 --- a/packages/claude-code-plugin/tests/test_hud_session.py +++ b/packages/claude-code-plugin/tests/test_hud_session.py @@ -1,6 +1,7 @@ -"""Skeleton sanity for hud_session — Wave 1-B placeholder (#1463).""" +"""Behavior tests for hud_session self-heal (Wave 1-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,264 @@ if _p not in sys.path: sys.path.insert(0, _p) +import hud_session # noqa: E402 -def test_module_loads(): - """Contract: hud_session must be importable. Wave 1-B will add real assertions.""" - import hud_session # noqa: F401 + +# --------------------------- detect_stale_session --------------------------- + + +def test_empty_state_not_stale(): + """Empty state is not stale — there is nothing to heal.""" + assert hud_session.detect_stale_session({}) is False + + +def test_none_state_not_stale(): + """None-ish state is not stale.""" + assert hud_session.detect_stale_session(None) is False # type: ignore[arg-type] + + +def test_manual_fix_marker_is_stale(): + """'manual-fix' is a known repair marker.""" + assert hud_session.detect_stale_session({"sessionId": "manual-fix"}) is True + + +def test_empty_session_id_is_stale(): + """Empty sessionId indicates uninitialized state.""" + assert hud_session.detect_stale_session({"sessionId": ""}) is True + + +def test_unknown_marker_is_stale(): + """'unknown' is treated as a repair marker.""" + assert hud_session.detect_stale_session({"sessionId": "unknown"}) is True + + +def test_none_marker_is_stale(): + """'none' string is treated as a repair marker.""" + assert hud_session.detect_stale_session({"sessionId": "none"}) is True + + +def test_stdin_mismatch_is_stale(): + """When stdin provides a different sessionId, state is stale.""" + now = datetime.now(timezone.utc).isoformat() + state = {"sessionId": "abc-123", "sessionStartTimestamp": now} + assert ( + hud_session.detect_stale_session(state, stdin_session_id="def-456") + is True + ) + + +def test_stdin_match_not_stale(): + """When stdin matches and timestamp is fresh, state is valid.""" + now = datetime.now(timezone.utc).isoformat() + state = {"sessionId": "abc-123", "sessionStartTimestamp": now} + assert ( + hud_session.detect_stale_session(state, stdin_session_id="abc-123") + is False + ) + + +def test_empty_stdin_session_id_skips_mismatch_check(): + """Empty stdin_session_id means 'not available' — skip that check.""" + now = datetime.now(timezone.utc).isoformat() + state = {"sessionId": "abc-123", "sessionStartTimestamp": now} + assert hud_session.detect_stale_session(state, stdin_session_id="") is False + + +def test_old_timestamp_is_stale(): + """Timestamp older than SESSION_STALE_SECONDS triggers stale.""" + old = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat() + state = {"sessionId": "abc-123", "sessionStartTimestamp": old} + assert hud_session.detect_stale_session(state) is True + + +def test_recent_timestamp_not_stale(): + """Timestamp within SESSION_STALE_SECONDS is fresh.""" + recent = (datetime.now(timezone.utc) - timedelta(minutes=5)).isoformat() + state = {"sessionId": "abc-123", "sessionStartTimestamp": recent} + assert hud_session.detect_stale_session(state) is False + + +def test_unparseable_timestamp_is_stale(): + """Garbage timestamp is treated as stale.""" + state = { + "sessionId": "abc-123", + "sessionStartTimestamp": "not-a-timestamp", + } + assert hud_session.detect_stale_session(state) is True + + +def test_naive_timestamp_treated_as_utc(): + """Naive datetime string (no tz) is interpreted as UTC.""" + now = datetime.now(timezone.utc) + naive_str = now.replace(tzinfo=None).isoformat() + state = {"sessionId": "abc-123", "sessionStartTimestamp": naive_str} + assert hud_session.detect_stale_session(state, now=now) is False + + +def test_now_override_for_deterministic_age_check(): + """Clock override makes age checks deterministic.""" + fixed_now = datetime(2026, 4, 11, 12, 0, 0, tzinfo=timezone.utc) + old = (fixed_now - timedelta(hours=5)).isoformat() + fresh = (fixed_now - timedelta(hours=1)).isoformat() + assert ( + hud_session.detect_stale_session( + {"sessionId": "abc", "sessionStartTimestamp": old}, now=fixed_now + ) + is True + ) + assert ( + hud_session.detect_stale_session( + {"sessionId": "abc", "sessionStartTimestamp": fresh}, now=fixed_now + ) + is False + ) + + +def test_repair_marker_beats_fresh_timestamp(): + """Repair marker triggers stale even when timestamp is fresh.""" + now = datetime.now(timezone.utc).isoformat() + state = {"sessionId": "manual-fix", "sessionStartTimestamp": now} + assert hud_session.detect_stale_session(state) is True + + +# --------------------------- heal_stale_state ------------------------------- + + +def test_heal_clears_ephemeral_fields(): + """heal_stale_state zeroes out rendering-relevant fields.""" + state = { + "sessionId": "abc-123", + "sessionStartTimestamp": "2026-04-01T00:00:00+00:00", + "currentMode": "ACT", + "version": "5.2.0", + "activeAgent": "code-reviewer", + "phase": "executing", + "focus": "debugging", + "blockerCount": 3, + } + healed = hud_session.heal_stale_state(state) + assert healed["currentMode"] is None + assert healed["version"] == "" + assert healed["activeAgent"] is None + assert healed["phase"] == "ready" + assert healed["focus"] is None + assert healed["blockerCount"] == 0 + + +def test_heal_preserves_session_id_and_timestamp(): + """heal_stale_state keeps sessionId and sessionStartTimestamp intact.""" + state = { + "sessionId": "abc-123", + "sessionStartTimestamp": "2026-04-01T00:00:00+00:00", + "currentMode": "ACT", + } + healed = hud_session.heal_stale_state(state) + assert healed["sessionId"] == "abc-123" + assert healed["sessionStartTimestamp"] == "2026-04-01T00:00:00+00:00" + + +def test_heal_does_not_mutate_input(): + """heal_stale_state returns a copy, not a mutated input.""" + state = { + "sessionId": "abc-123", + "currentMode": "ACT", + "version": "5.2.0", + } + healed = hud_session.heal_stale_state(state) + assert state["currentMode"] == "ACT" # original untouched + assert state["version"] == "5.2.0" + assert healed is not state + + +def test_heal_preserves_unknown_fields(): + """Fields not in the clear-set are preserved.""" + state = { + "sessionId": "abc-123", + "currentMode": "ACT", + "customField": "should survive", + "anotherCustom": 42, + } + healed = hud_session.heal_stale_state(state) + assert healed["customField"] == "should survive" + assert healed["anotherCustom"] == 42 + + +def test_heal_empty_state(): + """Healing an empty dict produces a dict with default clears.""" + healed = hud_session.heal_stale_state({}) + assert healed["currentMode"] is None + assert healed["version"] == "" + assert healed["phase"] == "ready" + + +# --------------------------- reset_stale_session ---------------------------- + + +def test_reset_stale_session_noop_on_fresh_state(tmp_path): + """reset_stale_session should not modify a fresh state file.""" + import json + + state_file = tmp_path / "hud-state.json" + fresh = { + "sessionId": "abc-123", + "sessionStartTimestamp": datetime.now(timezone.utc).isoformat(), + "currentMode": "ACT", + "version": "5.5.0", + } + state_file.write_text(json.dumps(fresh), encoding="utf-8") + + hud_session.reset_stale_session(str(state_file)) + + after = json.loads(state_file.read_text(encoding="utf-8")) + assert after["currentMode"] == "ACT" # unchanged + assert after["version"] == "5.5.0" + + +def test_reset_stale_session_heals_repair_marker(tmp_path): + """reset_stale_session writes healed fields to a marker-tainted file.""" + import json + + state_file = tmp_path / "hud-state.json" + stale = { + "sessionId": "manual-fix", + "sessionStartTimestamp": datetime.now(timezone.utc).isoformat(), + "currentMode": "ACT", + "version": "5.2.0", + "activeAgent": "old-agent", + "phase": "executing", + "focus": "old-focus", + "blockerCount": 5, + } + state_file.write_text(json.dumps(stale), encoding="utf-8") + + hud_session.reset_stale_session(str(state_file)) + + after = json.loads(state_file.read_text(encoding="utf-8")) + assert after["currentMode"] is None + assert after["version"] == "" + assert after["activeAgent"] is None + assert after["phase"] == "ready" + assert after["focus"] is None + assert after["blockerCount"] == 0 + + +def test_reset_stale_session_silent_on_missing_file(tmp_path): + """reset_stale_session never raises when the file is missing.""" + missing = tmp_path / "absent.json" + # Should not raise + hud_session.reset_stale_session(str(missing)) + + +def test_reset_stale_session_silent_on_malformed_file(tmp_path): + """reset_stale_session never raises when the file is not JSON.""" + bad = tmp_path / "hud-state.json" + bad.write_text("not a json at all", encoding="utf-8") + hud_session.reset_stale_session(str(bad)) + + +# --------------------------- constants -------------------------------------- + + +def test_session_stale_seconds_is_four_hours(): + """Document the stale threshold constant.""" + assert hud_session.SESSION_STALE_SECONDS == 4 * 60 * 60 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: