From 164d22f2bb0aefea39426ffc5a2870fc773061f7 Mon Sep 17 00:00:00 2001 From: maziggy Date: Mon, 26 Jan 2026 13:17:43 +0100 Subject: [PATCH 01/28] Add security scanning to CI pipeline - Add pip-audit check to PR workflow (non-blocking warning) - Add npm audit check to PR workflow (non-blocking warning) - Create scheduled weekly security audit workflow that: - Runs strict pip-audit and npm audit - Creates/updates GitHub issues when vulnerabilities found - Uploads audit results as artifacts - Supports manual trigger via workflow_dispatch --- .github/workflows/ci.yml | 45 +++++++ .github/workflows/security.yml | 213 +++++++++++++++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 .github/workflows/security.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e769a08b..e9f862b8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,28 @@ jobs: - name: Run ruff format check run: ruff format --check backend/ + backend-security: + name: Backend Security + runs-on: ubuntu-latest + if: github.event_name == 'push' || github.actor != github.repository_owner + continue-on-error: true + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pip-audit + + - name: Run pip-audit + run: pip-audit --desc on + backend-tests: name: Backend Tests runs-on: ubuntu-latest @@ -103,6 +125,29 @@ jobs: working-directory: frontend run: npm run lint + frontend-security: + name: Frontend Security + runs-on: ubuntu-latest + if: github.event_name == 'push' || github.actor != github.repository_owner + continue-on-error: true + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + working-directory: frontend + run: npm ci + + - name: Run npm audit + working-directory: frontend + run: npm audit --audit-level=moderate + frontend-typecheck: name: Frontend Type Check runs-on: ubuntu-latest diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 000000000..ae9f89b4a --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,213 @@ +name: Security Audit + +on: + schedule: + # Run weekly on Monday at 6:00 UTC + - cron: '0 6 * * 1' + workflow_dispatch: + # Allow manual trigger + +env: + PYTHON_VERSION: '3.11' + NODE_VERSION: '20' + +jobs: + backend-audit: + name: Backend Security Audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pip-audit + + - name: Run pip-audit + id: pip-audit + run: | + pip-audit --desc on --format json --output pip-audit-results.json || echo "vulnerabilities_found=true" >> $GITHUB_OUTPUT + pip-audit --desc on || true + + - name: Upload audit results + if: always() + uses: actions/upload-artifact@v4 + with: + name: pip-audit-results + path: pip-audit-results.json + retention-days: 30 + + - name: Create issue on vulnerability + if: steps.pip-audit.outputs.vulnerabilities_found == 'true' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const results = JSON.parse(fs.readFileSync('pip-audit-results.json', 'utf8')); + + // Build vulnerability table + let table = '| Package | Version | Vulnerability | Fix Version |\n'; + table += '|---------|---------|---------------|-------------|\n'; + + for (const vuln of results.dependencies || []) { + for (const v of vuln.vulns || []) { + table += `| ${vuln.name} | ${vuln.version} | ${v.id} | ${v.fix_versions?.join(', ') || 'N/A'} |\n`; + } + } + + const title = `Security Alert: ${results.dependencies?.reduce((acc, d) => acc + (d.vulns?.length || 0), 0) || 0} Python vulnerabilities found`; + + // Check for existing open issue + const existingIssues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: 'security,automated' + }); + + const existingIssue = existingIssues.data.find(i => i.title.startsWith('Security Alert:') && i.title.includes('Python')); + + const body = `## Automated Security Audit Results + + The weekly security audit found vulnerabilities in Python dependencies. + + ${table} + + ### Recommended Actions + + 1. Review each vulnerability + 2. Update affected packages: \`pip install --upgrade \` + 3. Run \`pip-audit\` locally to verify fixes + 4. Close this issue when resolved + + --- + *This issue was automatically created by the security audit workflow.*`; + + if (existingIssue) { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: existingIssue.number, + body: body + }); + console.log(`Updated existing issue #${existingIssue.number}`); + } else { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: title, + body: body, + labels: ['security', 'automated', 'dependencies'] + }); + console.log('Created new security issue'); + } + + frontend-audit: + name: Frontend Security Audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + working-directory: frontend + run: npm ci + + - name: Run npm audit + id: npm-audit + working-directory: frontend + run: | + npm audit --json > npm-audit-results.json || echo "vulnerabilities_found=true" >> $GITHUB_OUTPUT + npm audit --audit-level=moderate || true + + - name: Upload audit results + if: always() + uses: actions/upload-artifact@v4 + with: + name: npm-audit-results + path: frontend/npm-audit-results.json + retention-days: 30 + + - name: Create issue on vulnerability + if: steps.npm-audit.outputs.vulnerabilities_found == 'true' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const results = JSON.parse(fs.readFileSync('frontend/npm-audit-results.json', 'utf8')); + + const vulns = results.vulnerabilities || {}; + const vulnCount = Object.keys(vulns).length; + + if (vulnCount === 0) { + console.log('No vulnerabilities to report'); + return; + } + + // Build vulnerability table + let table = '| Package | Severity | Via | Fix |\n'; + table += '|---------|----------|-----|-----|\n'; + + for (const [name, info] of Object.entries(vulns)) { + const via = Array.isArray(info.via) ? info.via.map(v => typeof v === 'string' ? v : v.name).join(', ') : info.via; + table += `| ${name} | ${info.severity} | ${via} | ${info.fixAvailable ? 'Yes' : 'No'} |\n`; + } + + const title = `Security Alert: ${vulnCount} npm vulnerabilities found`; + + // Check for existing open issue + const existingIssues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: 'security,automated' + }); + + const existingIssue = existingIssues.data.find(i => i.title.startsWith('Security Alert:') && i.title.includes('npm')); + + const body = `## Automated Security Audit Results + + The weekly security audit found vulnerabilities in npm dependencies. + + ${table} + + ### Recommended Actions + + 1. Review each vulnerability: \`npm audit\` + 2. Auto-fix if possible: \`npm audit fix\` + 3. Manual fix for breaking changes: \`npm audit fix --force\` (review changes!) + 4. Close this issue when resolved + + --- + *This issue was automatically created by the security audit workflow.*`; + + if (existingIssue) { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: existingIssue.number, + body: body + }); + console.log(`Updated existing issue #${existingIssue.number}`); + } else { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: title, + body: body, + labels: ['security', 'automated', 'dependencies'] + }); + console.log('Created new security issue'); + } From 580225a38d15a474853ff387aef93e0db3f9aa95 Mon Sep 17 00:00:00 2001 From: maziggy Date: Mon, 26 Jan 2026 13:20:33 +0100 Subject: [PATCH 02/28] Add security scanning to CI pipeline - Add pip-audit check to PR workflow (non-blocking warning) - Add npm audit check to PR workflow (non-blocking, high severity only) - Create scheduled weekly security audit workflow that: - Runs strict pip-audit and npm audit - Creates/updates GitHub issues when vulnerabilities found - Uploads audit results as artifacts - Supports manual trigger via workflow_dispatch --- .github/workflows/ci.yml | 2 +- .github/workflows/security.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9f862b8c..9d07aecd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -146,7 +146,7 @@ jobs: - name: Run npm audit working-directory: frontend - run: npm audit --audit-level=moderate + run: npm audit --audit-level=high frontend-typecheck: name: Frontend Type Check diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index ae9f89b4a..020103e15 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -130,7 +130,7 @@ jobs: working-directory: frontend run: | npm audit --json > npm-audit-results.json || echo "vulnerabilities_found=true" >> $GITHUB_OUTPUT - npm audit --audit-level=moderate || true + npm audit --audit-level=high || true - name: Upload audit results if: always() From 2da519a86e8a4b25c93a69eb1d68e080179dd480 Mon Sep 17 00:00:00 2001 From: maziggy Date: Mon, 26 Jan 2026 13:24:15 +0100 Subject: [PATCH 03/28] Replace python-jose with PyJWT to eliminate ecdsa vulnerability --- =2.8.0 | 5 +++++ backend/app/core/auth.py | 3 ++- requirements.txt | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 =2.8.0 diff --git a/=2.8.0 b/=2.8.0 new file mode 100644 index 000000000..9f7171c00 --- /dev/null +++ b/=2.8.0 @@ -0,0 +1,5 @@ +Collecting PyJWT + Downloading PyJWT-2.10.1-py3-none-any.whl.metadata (4.0 kB) +Downloading PyJWT-2.10.1-py3-none-any.whl (22 kB) +Installing collected packages: PyJWT +Successfully installed PyJWT-2.10.1 diff --git a/backend/app/core/auth.py b/backend/app/core/auth.py index f79a66b49..fb3c1c367 100644 --- a/backend/app/core/auth.py +++ b/backend/app/core/auth.py @@ -4,9 +4,10 @@ import secrets from datetime import datetime, timedelta from typing import Annotated +import jwt from fastapi import Depends, Header, HTTPException, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer -from jose import JWTError, jwt +from jwt.exceptions import PyJWTError as JWTError from passlib.context import CryptContext from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession diff --git a/requirements.txt b/requirements.txt index 27238e913..1a7fc23e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -38,7 +38,7 @@ qrcode[pil]>=7.4.0 psutil>=6.0.0 # Authentication -python-jose[cryptography]>=3.3.0 +PyJWT>=2.8.0 passlib[bcrypt]>=1.7.4 # Development From 51df60cb91732f8dea4ae44b48c2f835836817f3 Mon Sep 17 00:00:00 2001 From: maziggy Date: Mon, 26 Jan 2026 15:52:23 +0100 Subject: [PATCH 04/28] Fixed CI --- .github/workflows/ci.yml | 4 ++++ .github/workflows/security.yml | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d07aecd3..1354c1ef3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,10 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Minimum permissions for all jobs +permissions: + contents: read + jobs: # ============================================================================ # Backend Checks diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 020103e15..957e191ae 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -11,10 +11,17 @@ env: PYTHON_VERSION: '3.11' NODE_VERSION: '20' +# Default permissions for all jobs +permissions: + contents: read + jobs: backend-audit: name: Backend Security Audit runs-on: ubuntu-latest + permissions: + contents: read + issues: write steps: - uses: actions/checkout@v4 @@ -111,6 +118,9 @@ jobs: frontend-audit: name: Frontend Security Audit runs-on: ubuntu-latest + permissions: + contents: read + issues: write steps: - uses: actions/checkout@v4 From aa5ab135c7989b00670ffbb9da763322d22765c8 Mon Sep 17 00:00:00 2001 From: maziggy Date: Tue, 27 Jan 2026 11:14:48 +0100 Subject: [PATCH 05/28] Added stale issues workflow --- .github/workflows/stale.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..f2d4b707d --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,17 @@ +name: Close stale issues + +on: + schedule: + - cron: '0 0 * * *' # Run daily at midnight UTC + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + stale-issue-message: 'This issue has been marked as stale due to inactivity. It will be closed in 7 days if there is no further activity.' + close-issue-message: 'Closed due to inactivity. Feel free to reopen if this is still relevant.' + days-before-stale: 21 + days-before-close: 7 + stale-issue-label: 'feedback' From 56efb44c4ff11c3e2ad013f6fbc68dbb5b42c682 Mon Sep 17 00:00:00 2001 From: maziggy Date: Tue, 27 Jan 2026 11:24:49 +0100 Subject: [PATCH 06/28] Added explicit permissions: issues: write --- .github/workflows/stale.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f2d4b707d..a4f53e2a3 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -4,6 +4,9 @@ on: schedule: - cron: '0 0 * * *' # Run daily at midnight UTC +permissions: + issues: write + jobs: stale: runs-on: ubuntu-latest From a1c59fd6cbf76acd87a84ef665728c926d07d8e8 Mon Sep 17 00:00:00 2001 From: maziggy Date: Wed, 28 Jan 2026 07:13:01 +0100 Subject: [PATCH 07/28] Updated the workflow: - Changed stale-issue-label from feedback to stale (so stale issues get the "stale" label) - Added remove-issue-labels: 'feedback' to remove the feedback label when issues are auto-closed --- .github/workflows/stale.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index a4f53e2a3..4ada6255e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -17,4 +17,5 @@ jobs: close-issue-message: 'Closed due to inactivity. Feel free to reopen if this is still relevant.' days-before-stale: 21 days-before-close: 7 - stale-issue-label: 'feedback' + stale-issue-label: 'stale' + remove-issue-labels: 'feedback' From 180752f7e6926fcdb3edc8fa5afbdcb9fbdc6889 Mon Sep 17 00:00:00 2001 From: maziggy Date: Wed, 28 Jan 2026 09:15:26 +0100 Subject: [PATCH 08/28] Updated Github workflow --- .github/workflows/issue-closed.yml | 29 +++++++++++++++++++++++++++++ .github/workflows/stale.yml | 1 - 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/issue-closed.yml diff --git a/.github/workflows/issue-closed.yml b/.github/workflows/issue-closed.yml new file mode 100644 index 000000000..17bf9a5fb --- /dev/null +++ b/.github/workflows/issue-closed.yml @@ -0,0 +1,29 @@ +name: Clean up closed issues + +on: + issues: + types: [closed] + +permissions: + issues: write + +jobs: + remove-labels: + runs-on: ubuntu-latest + steps: + - name: Remove feedback label + uses: actions/github-script@v7 + with: + script: | + const issue = context.payload.issue; + const hasLabel = issue.labels.some(l => l.name === 'feedback'); + + if (hasLabel) { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + name: 'feedback' + }); + console.log(`Removed 'feedback' label from issue #${issue.number}`); + } diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 4ada6255e..03e2b7824 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -18,4 +18,3 @@ jobs: days-before-stale: 21 days-before-close: 7 stale-issue-label: 'stale' - remove-issue-labels: 'feedback' From 54abee53f8582d75a6fdbc73d190e26122c2adb4 Mon Sep 17 00:00:00 2001 From: maziggy Date: Wed, 28 Jan 2026 09:15:26 +0100 Subject: [PATCH 09/28] Updated Github workflow --- .github/workflows/issue-closed.yml | 29 +++++++++++++++++++++++++++++ .github/workflows/stale.yml | 1 - 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/issue-closed.yml diff --git a/.github/workflows/issue-closed.yml b/.github/workflows/issue-closed.yml new file mode 100644 index 000000000..17bf9a5fb --- /dev/null +++ b/.github/workflows/issue-closed.yml @@ -0,0 +1,29 @@ +name: Clean up closed issues + +on: + issues: + types: [closed] + +permissions: + issues: write + +jobs: + remove-labels: + runs-on: ubuntu-latest + steps: + - name: Remove feedback label + uses: actions/github-script@v7 + with: + script: | + const issue = context.payload.issue; + const hasLabel = issue.labels.some(l => l.name === 'feedback'); + + if (hasLabel) { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + name: 'feedback' + }); + console.log(`Removed 'feedback' label from issue #${issue.number}`); + } diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 4ada6255e..03e2b7824 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -18,4 +18,3 @@ jobs: days-before-stale: 21 days-before-close: 7 stale-issue-label: 'stale' - remove-issue-labels: 'feedback' From 65a6e7791bce36511e68400fd8e17281826054dc Mon Sep 17 00:00:00 2001 From: maziggy Date: Wed, 28 Jan 2026 09:34:56 +0100 Subject: [PATCH 10/28] Fix Bambu Cloud preset lookup by transforming filament_id to setting_id Printers report filament_id (e.g., GFA00) but the Bambu Cloud API expects setting_id format which has an "S" inserted after "GF" (e.g., GFSA00). - Add _filament_id_to_setting_id() helper function - Transform IDs before API calls: GFx## -> GFSx## - User presets (P-prefix) and already-correct IDs unchanged - Improved warning message to show both original and transformed IDs --- backend/app/api/routes/cloud.py | 38 ++++++++++++++- scripts/debug_preset.py | 82 +++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 scripts/debug_preset.py diff --git a/backend/app/api/routes/cloud.py b/backend/app/api/routes/cloud.py index 36ebf38d3..f1dc73cc5 100644 --- a/backend/app/api/routes/cloud.py +++ b/backend/app/api/routes/cloud.py @@ -267,6 +267,34 @@ _filament_cache_time: float = 0 FILAMENT_CACHE_TTL = 300 # 5 minutes +def _filament_id_to_setting_id(filament_id: str) -> str: + """ + Convert filament_id to setting_id format for Bambu Cloud API. + + Printers report filament_id (e.g., GFA00, GFG02) but the API expects + setting_id format which has an "S" inserted after "GF" (e.g., GFSA00, GFSG02). + + User presets (starting with "P") and already-correct IDs are returned unchanged. + """ + if not filament_id: + return filament_id + + # User presets start with "P" - leave unchanged + if filament_id.startswith("P"): + return filament_id + + # Official Bambu presets: GFx## -> GFSx## + # Check if it matches the filament_id pattern (GF followed by letter and digits) + if filament_id.startswith("GF") and len(filament_id) >= 4: + # Check if it's already a setting_id (has S after GF) + if filament_id[2] == "S": + return filament_id + # Insert "S" after "GF": GFA00 -> GFSA00 + return f"GFS{filament_id[2:]}" + + return filament_id + + @router.post("/filament-info") async def get_filament_info(setting_ids: list[str] = Body(...), db: AsyncSession = Depends(get_db)): """ @@ -308,7 +336,10 @@ async def get_filament_info(setting_ids: list[str] = Body(...), db: AsyncSession continue try: - data = await cloud.get_setting_detail(setting_id) + # Transform filament_id to setting_id format (GFA00 -> GFSA00) + api_setting_id = _filament_id_to_setting_id(setting_id) + + data = await cloud.get_setting_detail(api_setting_id) setting = data.get("setting", {}) # Extract name (e.g., "Bambu PLA Basic Jade White") @@ -323,11 +354,14 @@ async def get_filament_info(setting_ids: list[str] = Body(...), db: AsyncSession k_value = None info = {"name": name, "k": k_value} + # Cache using original ID so frontend gets expected response _filament_cache[setting_id] = info result[setting_id] = info except Exception as e: - logger.warning(f"Failed to get cloud preset {setting_id}: {e}") + logger.warning( + f"Failed to get cloud preset {setting_id} (API ID: {_filament_id_to_setting_id(setting_id)}): {e}" + ) # Cache the failure to avoid repeated requests _filament_cache[setting_id] = {"name": "", "k": None} result[setting_id] = {"name": "", "k": None} diff --git a/scripts/debug_preset.py b/scripts/debug_preset.py new file mode 100644 index 000000000..c52d359f3 --- /dev/null +++ b/scripts/debug_preset.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""Debug script to investigate Bambu Cloud preset API responses.""" + +import asyncio +import json +import sys +from pathlib import Path + +# Add backend to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +import httpx +from sqlalchemy import create_engine, text + +from backend.app.core.config import settings + +# Test preset IDs (from the warning logs) +TEST_IDS = ["GFG02", "GFL05", "GFA00", "GFA02", "GFA06"] + + +def get_token_from_db() -> str | None: + """Get the stored token from the database.""" + db_path = settings.base_dir / "bambuddy.db" + engine = create_engine(f"sqlite:///{db_path}") + + with engine.connect() as conn: + result = conn.execute(text("SELECT value FROM settings WHERE key = 'bambu_cloud_token'")) + row = result.fetchone() + + if row and row[0]: + return row[0] + return None + + +async def test_preset(setting_id: str, token: str, base_url: str = "https://api.bambulab.com"): + """Test fetching a single preset and show full response.""" + url = f"{base_url}/v1/iot-service/api/slicer/setting/{setting_id}" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + + print(f"\n{'=' * 60}") + print(f"Testing preset: {setting_id}") + print(f"URL: {url}") + print(f"{'=' * 60}") + + async with httpx.AsyncClient() as client: + response = await client.get(url, headers=headers) + + print(f"Status: {response.status_code}") + print("\nResponse body:") + try: + data = response.json() + print(json.dumps(data, indent=2)) + except Exception: + print(response.text) + + return response.status_code + + +async def main(): + # Get token from DB + token = get_token_from_db() + + if not token: + print("Could not find token in database.") + print("Make sure you're logged into Bambu Cloud in Bambuddy.") + sys.exit(1) + + print(f"Found token in database (length: {len(token)})") + + # Allow testing specific preset IDs from command line + test_ids = sys.argv[1:] if len(sys.argv) > 1 else TEST_IDS + + # Test each preset + for preset_id in test_ids: + await test_preset(preset_id, token) + + +if __name__ == "__main__": + asyncio.run(main()) From 2afdbc5e607d608f6045a1f53f03b78db51465f5 Mon Sep 17 00:00:00 2001 From: maziggy Date: Wed, 28 Jan 2026 13:56:37 +0100 Subject: [PATCH 11/28] Fix K-profiles backup status showing incorrect printer connection count The GitHub backup settings page was showing "1/2 connected" even when both printers were connected. This happened because the code read printer status from the React Query cache, which is only populated by WebSocket messages received on other pages. Changed to use useQueries to fetch printer status directly from the API (/api/v1/printers/{id}/status) instead of relying on cached WebSocket data. Results are cached for 10s and refreshed every 30s. --- CHANGELOG.md | 1 + .../src/components/GitHubBackupSettings.tsx | 22 +- static/assets/index-BkOQ27rS.js | 8729 ----------------- static/assets/index-DuO_7tQ3.js | 8665 ++++++++++++++++ static/index.html | 2 +- 5 files changed, 8682 insertions(+), 8737 deletions(-) delete mode 100644 static/assets/index-BkOQ27rS.js create mode 100644 static/assets/index-DuO_7tQ3.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d9c412d4..49efef186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,6 +114,7 @@ All notable changes to Bambuddy will be documented in this file. - Text wrap toggle: "Wrap" button in header to wrap long names instead of truncating - Both settings persist in localStorage - Tooltip shows full name on hover +- **K-Profiles Backup Status** - Fixed GitHub backup settings showing incorrect printer connection count (e.g., "1/2 connected" when both printers are connected); now fetches status from API instead of relying on WebSocket cache ## [0.1.6b11] - 2026-01-22 diff --git a/frontend/src/components/GitHubBackupSettings.tsx b/frontend/src/components/GitHubBackupSettings.tsx index 89053b88e..5c00a8b57 100644 --- a/frontend/src/components/GitHubBackupSettings.tsx +++ b/frontend/src/components/GitHubBackupSettings.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useRef, useCallback } from 'react'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useQuery, useQueries, useMutation, useQueryClient } from '@tanstack/react-query'; import { Github, Play, @@ -27,7 +27,6 @@ import type { ScheduleType, CloudAuthStatus, Printer, - PrinterStatus, } from '../api/client'; import { Card, CardContent, CardHeader } from './Card'; import { Button } from './Button'; @@ -149,11 +148,20 @@ export function GitHubBackupSettings() { queryFn: api.getPrinters, }); - // Get printer statuses from cache (populated by WebSocket on other pages) - const printerStatuses = printers?.map(p => { - const status = queryClient.getQueryData(['printerStatus', p.id]); - return { printer: p, connected: status?.connected ?? false }; - }) ?? []; + // Fetch printer statuses from API (not just cache) to get accurate connection status + const printerStatusQueries = useQueries({ + queries: (printers ?? []).map(printer => ({ + queryKey: ['printerStatus', printer.id], + queryFn: () => api.getPrinterStatus(printer.id), + staleTime: 10000, // Consider stale after 10s + refetchInterval: 30000, // Refresh every 30s + })), + }); + + const printerStatuses = (printers ?? []).map((printer, index) => ({ + printer, + connected: printerStatusQueries[index]?.data?.connected ?? false, + })); const totalPrinters = printerStatuses.length; const connectedPrinters = printerStatuses.filter(p => p.connected).length; diff --git a/static/assets/index-BkOQ27rS.js b/static/assets/index-BkOQ27rS.js deleted file mode 100644 index d0e566d52..000000000 --- a/static/assets/index-BkOQ27rS.js +++ /dev/null @@ -1,8729 +0,0 @@ -function Jse(t,e){for(var n=0;nr[i]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const a of i)if(a.type==="childList")for(const o of a.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(i){const a={};return i.integrity&&(a.integrity=i.integrity),i.referrerPolicy&&(a.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?a.credentials="include":i.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function r(i){if(i.ep)return;i.ep=!0;const a=n(i);fetch(i.href,a)}})();var m_=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function pl(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var WA={exports:{}},ob={};var ez;function eoe(){if(ez)return ob;ez=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,a){var o=null;if(a!==void 0&&(o=""+a),i.key!==void 0&&(o=""+i.key),"key"in i){a={};for(var l in i)l!=="key"&&(a[l]=i[l])}else a=i;return i=a.ref,{$$typeof:t,type:r,key:o,ref:i!==void 0?i:null,props:a}}return ob.Fragment=e,ob.jsx=n,ob.jsxs=n,ob}var tz;function toe(){return tz||(tz=1,WA.exports=eoe()),WA.exports}var s=toe(),qA={exports:{}},gn={};var nz;function noe(){if(nz)return gn;nz=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),a=Symbol.for("react.consumer"),o=Symbol.for("react.context"),l=Symbol.for("react.forward_ref"),c=Symbol.for("react.suspense"),u=Symbol.for("react.memo"),d=Symbol.for("react.lazy"),f=Symbol.for("react.activity"),m=Symbol.iterator;function p(z){return z===null||typeof z!="object"?null:(z=m&&z[m]||z["@@iterator"],typeof z=="function"?z:null)}var b={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},y=Object.assign,x={};function v(z,G,ne){this.props=z,this.context=G,this.refs=x,this.updater=ne||b}v.prototype.isReactComponent={},v.prototype.setState=function(z,G){if(typeof z!="object"&&typeof z!="function"&&z!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,z,G,"setState")},v.prototype.forceUpdate=function(z){this.updater.enqueueForceUpdate(this,z,"forceUpdate")};function S(){}S.prototype=v.prototype;function M(z,G,ne){this.props=z,this.context=G,this.refs=x,this.updater=ne||b}var A=M.prototype=new S;A.constructor=M,y(A,v.prototype),A.isPureReactComponent=!0;var T=Array.isArray;function k(){}var E={H:null,A:null,T:null,S:null},O=Object.prototype.hasOwnProperty;function C(z,G,ne){var ie=ne.ref;return{$$typeof:t,type:z,key:G,ref:ie!==void 0?ie:null,props:ne}}function D(z,G){return C(z.type,G,z.props)}function U(z){return typeof z=="object"&&z!==null&&z.$$typeof===t}function L(z){var G={"=":"=0",":":"=2"};return"$"+z.replace(/[=:]/g,function(ne){return G[ne]})}var q=/\/+/g;function j(z,G){return typeof z=="object"&&z!==null&&z.key!=null?L(""+z.key):G.toString(36)}function W(z){switch(z.status){case"fulfilled":return z.value;case"rejected":throw z.reason;default:switch(typeof z.status=="string"?z.then(k,k):(z.status="pending",z.then(function(G){z.status==="pending"&&(z.status="fulfilled",z.value=G)},function(G){z.status==="pending"&&(z.status="rejected",z.reason=G)})),z.status){case"fulfilled":return z.value;case"rejected":throw z.reason}}throw z}function K(z,G,ne,ie,ue){var me=typeof z;(me==="undefined"||me==="boolean")&&(z=null);var ce=!1;if(z===null)ce=!0;else switch(me){case"bigint":case"string":case"number":ce=!0;break;case"object":switch(z.$$typeof){case t:case e:ce=!0;break;case d:return ce=z._init,K(ce(z._payload),G,ne,ie,ue)}}if(ce)return ue=ue(z),ce=ie===""?"."+j(z,0):ie,T(ue)?(ne="",ce!=null&&(ne=ce.replace(q,"$&/")+"/"),K(ue,G,ne,"",function(Ne){return Ne})):ue!=null&&(U(ue)&&(ue=D(ue,ne+(ue.key==null||z&&z.key===ue.key?"":(""+ue.key).replace(q,"$&/")+"/")+ce)),G.push(ue)),1;ce=0;var Ae=ie===""?".":ie+":";if(T(z))for(var pe=0;pe>>1,ee=K[re];if(0>>1;rei(ne,Z))iei(ue,ne)?(K[re]=ue,K[ie]=Z,re=ie):(K[re]=ne,K[G]=Z,re=G);else if(iei(ue,Z))K[re]=ue,K[ie]=Z,re=ie;else break e}}return V}function i(K,V){var Z=K.sortIndex-V.sortIndex;return Z!==0?Z:K.id-V.id}if(t.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var a=performance;t.unstable_now=function(){return a.now()}}else{var o=Date,l=o.now();t.unstable_now=function(){return o.now()-l}}var c=[],u=[],d=1,f=null,m=3,p=!1,b=!1,y=!1,x=!1,v=typeof setTimeout=="function"?setTimeout:null,S=typeof clearTimeout=="function"?clearTimeout:null,M=typeof setImmediate<"u"?setImmediate:null;function A(K){for(var V=n(u);V!==null;){if(V.callback===null)r(u);else if(V.startTime<=K)r(u),V.sortIndex=V.expirationTime,e(c,V);else break;V=n(u)}}function T(K){if(y=!1,A(K),!b)if(n(c)!==null)b=!0,k||(k=!0,L());else{var V=n(u);V!==null&&W(T,V.startTime-K)}}var k=!1,E=-1,O=5,C=-1;function D(){return x?!0:!(t.unstable_now()-CK&&D());){var re=f.callback;if(typeof re=="function"){f.callback=null,m=f.priorityLevel;var ee=re(f.expirationTime<=K);if(K=t.unstable_now(),typeof ee=="function"){f.callback=ee,A(K),V=!0;break t}f===n(c)&&r(c),A(K)}else r(c);f=n(c)}if(f!==null)V=!0;else{var z=n(u);z!==null&&W(T,z.startTime-K),V=!1}}break e}finally{f=null,m=Z,p=!1}V=void 0}}finally{V?L():k=!1}}}var L;if(typeof M=="function")L=function(){M(U)};else if(typeof MessageChannel<"u"){var q=new MessageChannel,j=q.port2;q.port1.onmessage=U,L=function(){j.postMessage(null)}}else L=function(){v(U,0)};function W(K,V){E=v(function(){K(t.unstable_now())},V)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(K){K.callback=null},t.unstable_forceFrameRate=function(K){0>K||125re?(K.sortIndex=Z,e(u,K),n(c)===null&&K===n(u)&&(y?(S(E),E=-1):y=!0,W(T,Z-re))):(K.sortIndex=ee,e(c,K),b||p||(b=!0,k||(k=!0,L()))),K},t.unstable_shouldYield=D,t.unstable_wrapCallback=function(K){var V=m;return function(){var Z=m;m=V;try{return K.apply(this,arguments)}finally{m=Z}}}})(YA)),YA}var az;function aoe(){return az||(az=1,XA.exports=ioe()),XA.exports}var QA={exports:{}},ja={};var sz;function soe(){if(sz)return ja;sz=1;var t=Kg();function e(c){var u="https://react.dev/errors/"+c;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(t)}catch(e){console.error(e)}}return t(),QA.exports=soe(),QA.exports}var lz;function ooe(){if(lz)return lb;lz=1;var t=aoe(),e=Kg(),n=_q();function r(h){var g="https://react.dev/errors/"+h;if(1ee||(h.current=re[ee],re[ee]=null,ee--)}function ne(h,g){ee++,re[ee]=h.current,h.current=g}var ie=z(null),ue=z(null),me=z(null),ce=z(null);function Ae(h,g){switch(ne(me,g),ne(ue,h),ne(ie,null),g.nodeType){case 9:case 11:h=(h=g.documentElement)&&(h=h.namespaceURI)?SU(h):0;break;default:if(h=g.tagName,g=g.namespaceURI)g=SU(g),h=MU(g,h);else switch(h){case"svg":h=1;break;case"math":h=2;break;default:h=0}}G(ie),ne(ie,h)}function pe(){G(ie),G(ue),G(me)}function Ne(h){h.memoizedState!==null&&ne(ce,h);var g=ie.current,_=MU(g,h.type);g!==_&&(ne(ue,h),ne(ie,_))}function fe(h){ue.current===h&&(G(ie),G(ue)),ce.current===h&&(G(ce),rb._currentValue=Z)}var oe,Be;function H(h){if(oe===void 0)try{throw Error()}catch(_){var g=_.stack.trim().match(/\n( *(at )?)/);oe=g&&g[1]||"",Be=-1<_.stack.indexOf(` - at`)?" ()":-1<_.stack.indexOf("@")?"@unknown:0:0":""}return` -`+oe+h+Be}var J=!1;function X(h,g){if(!h||J)return"";J=!0;var _=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var N={DetermineComponentFrameRoot:function(){try{if(g){var vt=function(){throw Error()};if(Object.defineProperty(vt.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(vt,[])}catch(ft){var st=ft}Reflect.construct(h,[],vt)}else{try{vt.call()}catch(ft){st=ft}h.call(vt.prototype)}}else{try{throw Error()}catch(ft){st=ft}(vt=h())&&typeof vt.catch=="function"&&vt.catch(function(){})}}catch(ft){if(ft&&st&&typeof ft.stack=="string")return[ft.stack,st.stack]}return[null,null]}};N.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var R=Object.getOwnPropertyDescriptor(N.DetermineComponentFrameRoot,"name");R&&R.configurable&&Object.defineProperty(N.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var F=N.DetermineComponentFrameRoot(),Q=F[0],ge=F[1];if(Q&&ge){var De=Q.split(` -`),it=ge.split(` -`);for(R=N=0;NR||De[N]!==it[R]){var xt=` -`+De[N].replace(" at new "," at ");return h.displayName&&xt.includes("")&&(xt=xt.replace("",h.displayName)),xt}while(1<=N&&0<=R);break}}}finally{J=!1,Error.prepareStackTrace=_}return(_=h?h.displayName||h.name:"")?H(_):""}function B(h,g){switch(h.tag){case 26:case 27:case 5:return H(h.type);case 16:return H("Lazy");case 13:return h.child!==g&&g!==null?H("Suspense Fallback"):H("Suspense");case 19:return H("SuspenseList");case 0:case 15:return X(h.type,!1);case 11:return X(h.type.render,!1);case 1:return X(h.type,!0);case 31:return H("Activity");default:return""}}function I(h){try{var g="",_=null;do g+=B(h,_),_=h,h=h.return;while(h);return g}catch(N){return` -Error generating stack: `+N.message+` -`+N.stack}}var le=Object.prototype.hasOwnProperty,Te=t.unstable_scheduleCallback,Le=t.unstable_cancelCallback,Ee=t.unstable_shouldYield,$=t.unstable_requestPaint,P=t.unstable_now,Y=t.unstable_getCurrentPriorityLevel,xe=t.unstable_ImmediatePriority,we=t.unstable_UserBlockingPriority,ve=t.unstable_NormalPriority,Qe=t.unstable_LowPriority,Ie=t.unstable_IdlePriority,qe=t.log,Je=t.unstable_setDisableYieldValue,Ze=null,se=null;function te(h){if(typeof qe=="function"&&Je(h),se&&typeof se.setStrictMode=="function")try{se.setStrictMode(Ze,h)}catch{}}var Me=Math.clz32?Math.clz32:ae,Fe=Math.log,ot=Math.LN2;function ae(h){return h>>>=0,h===0?32:31-(Fe(h)/ot|0)|0}var je=256,be=262144,Oe=4194304;function Ue(h){var g=h&42;if(g!==0)return g;switch(h&-h){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return h&261888;case 262144:case 524288:case 1048576:case 2097152:return h&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return h&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return h}}function Ce(h,g,_){var N=h.pendingLanes;if(N===0)return 0;var R=0,F=h.suspendedLanes,Q=h.pingedLanes;h=h.warmLanes;var ge=N&134217727;return ge!==0?(N=ge&~F,N!==0?R=Ue(N):(Q&=ge,Q!==0?R=Ue(Q):_||(_=ge&~h,_!==0&&(R=Ue(_))))):(ge=N&~F,ge!==0?R=Ue(ge):Q!==0?R=Ue(Q):_||(_=N&~h,_!==0&&(R=Ue(_)))),R===0?0:g!==0&&g!==R&&(g&F)===0&&(F=R&-R,_=g&-g,F>=_||F===32&&(_&4194048)!==0)?g:R}function We(h,g){return(h.pendingLanes&~(h.suspendedLanes&~h.pingedLanes)&g)===0}function pt(h,g){switch(h){case 1:case 2:case 4:case 8:case 64:return g+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return g+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function kt(){var h=Oe;return Oe<<=1,(Oe&62914560)===0&&(Oe=4194304),h}function Lt(h){for(var g=[],_=0;31>_;_++)g.push(h);return g}function ln(h,g){h.pendingLanes|=g,g!==268435456&&(h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0)}function Gt(h,g,_,N,R,F){var Q=h.pendingLanes;h.pendingLanes=_,h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0,h.expiredLanes&=_,h.entangledLanes&=_,h.errorRecoveryDisabledLanes&=_,h.shellSuspendCounter=0;var ge=h.entanglements,De=h.expirationTimes,it=h.hiddenUpdates;for(_=Q&~_;0<_;){var xt=31-Me(_),vt=1<"u")return null;try{return h.activeElement||h.body}catch{return h.body}}var Rt=/[\n"\\]/g;function $t(h){return h.replace(Rt,function(g){return"\\"+g.charCodeAt(0).toString(16)+" "})}function Ln(h,g,_,N,R,F,Q,ge){h.name="",Q!=null&&typeof Q!="function"&&typeof Q!="symbol"&&typeof Q!="boolean"?h.type=Q:h.removeAttribute("type"),g!=null?Q==="number"?(g===0&&h.value===""||h.value!=g)&&(h.value=""+ye(g)):h.value!==""+ye(g)&&(h.value=""+ye(g)):Q!=="submit"&&Q!=="reset"||h.removeAttribute("value"),g!=null?Zt(h,Q,ye(g)):_!=null?Zt(h,Q,ye(_)):N!=null&&h.removeAttribute("value"),R==null&&F!=null&&(h.defaultChecked=!!F),R!=null&&(h.checked=R&&typeof R!="function"&&typeof R!="symbol"),ge!=null&&typeof ge!="function"&&typeof ge!="symbol"&&typeof ge!="boolean"?h.name=""+ye(ge):h.removeAttribute("name")}function bi(h,g,_,N,R,F,Q,ge){if(F!=null&&typeof F!="function"&&typeof F!="symbol"&&typeof F!="boolean"&&(h.type=F),g!=null||_!=null){if(!(F!=="submit"&&F!=="reset"||g!=null)){Mt(h);return}_=_!=null?""+ye(_):"",g=g!=null?""+ye(g):_,ge||g===h.value||(h.value=g),h.defaultValue=g}N=N??R,N=typeof N!="function"&&typeof N!="symbol"&&!!N,h.checked=ge?h.checked:!!N,h.defaultChecked=!!N,Q!=null&&typeof Q!="function"&&typeof Q!="symbol"&&typeof Q!="boolean"&&(h.name=Q),Mt(h)}function Zt(h,g,_){g==="number"&&Tt(h.ownerDocument)===h||h.defaultValue===""+_||(h.defaultValue=""+_)}function cn(h,g,_,N){if(h=h.options,g){g={};for(var R=0;R<_.length;R++)g["$"+_[R]]=!0;for(_=0;_"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),WE=!1;if(gc)try{var _x={};Object.defineProperty(_x,"passive",{get:function(){WE=!0}}),window.addEventListener("test",_x,_x),window.removeEventListener("test",_x,_x)}catch{WE=!1}var Ru=null,qE=null,sv=null;function MI(){if(sv)return sv;var h,g=qE,_=g.length,N,R="value"in Ru?Ru.value:Ru.textContent,F=R.length;for(h=0;h<_&&g[h]===R[h];h++);var Q=_-h;for(N=1;N<=Q&&g[_-N]===R[F-N];N++);return sv=R.slice(h,1=Mx),kI=" ",jI=!1;function PI(h,g){switch(h){case"keyup":return _ae.indexOf(g.keyCode)!==-1;case"keydown":return g.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function RI(h){return h=h.detail,typeof h=="object"&&"data"in h?h.data:null}var Sm=!1;function Sae(h,g){switch(h){case"compositionend":return RI(g);case"keypress":return g.which!==32?null:(jI=!0,kI);case"textInput":return h=g.data,h===kI&&jI?null:h;default:return null}}function Mae(h,g){if(Sm)return h==="compositionend"||!ZE&&PI(h,g)?(h=MI(),sv=qE=Ru=null,Sm=!1,h):null;switch(h){case"paste":return null;case"keypress":if(!(g.ctrlKey||g.altKey||g.metaKey)||g.ctrlKey&&g.altKey){if(g.char&&1=g)return{node:_,offset:g-h};h=N}e:{for(;_;){if(_.nextSibling){_=_.nextSibling;break e}_=_.parentNode}_=void 0}_=BI(_)}}function HI(h,g){return h&&g?h===g?!0:h&&h.nodeType===3?!1:g&&g.nodeType===3?HI(h,g.parentNode):"contains"in h?h.contains(g):h.compareDocumentPosition?!!(h.compareDocumentPosition(g)&16):!1:!1}function VI(h){h=h!=null&&h.ownerDocument!=null&&h.ownerDocument.defaultView!=null?h.ownerDocument.defaultView:window;for(var g=Tt(h.document);g instanceof h.HTMLIFrameElement;){try{var _=typeof g.contentWindow.location.href=="string"}catch{_=!1}if(_)h=g.contentWindow;else break;g=Tt(h.document)}return g}function t2(h){var g=h&&h.nodeName&&h.nodeName.toLowerCase();return g&&(g==="input"&&(h.type==="text"||h.type==="search"||h.type==="tel"||h.type==="url"||h.type==="password")||g==="textarea"||h.contentEditable==="true")}var Pae=gc&&"documentMode"in document&&11>=document.documentMode,Mm=null,n2=null,Tx=null,r2=!1;function GI(h,g,_){var N=_.window===_?_.document:_.nodeType===9?_:_.ownerDocument;r2||Mm==null||Mm!==Tt(N)||(N=Mm,"selectionStart"in N&&t2(N)?N={start:N.selectionStart,end:N.selectionEnd}:(N=(N.ownerDocument&&N.ownerDocument.defaultView||window).getSelection(),N={anchorNode:N.anchorNode,anchorOffset:N.anchorOffset,focusNode:N.focusNode,focusOffset:N.focusOffset}),Tx&&Ax(Tx,N)||(Tx=N,N=Jv(n2,"onSelect"),0>=Q,R-=Q,_l=1<<32-Me(g)+R|_<vn?(Fn=Jt,Jt=null):Fn=Jt.sibling;var Xn=st(Ye,Jt,nt[vn],bt);if(Xn===null){Jt===null&&(Jt=Fn);break}h&&Jt&&Xn.alternate===null&&g(Ye,Jt),ze=F(Xn,ze,vn),Kn===null?tn=Xn:Kn.sibling=Xn,Kn=Xn,Jt=Fn}if(vn===nt.length)return _(Ye,Jt),zn&&bc(Ye,vn),tn;if(Jt===null){for(;vnvn?(Fn=Jt,Jt=null):Fn=Jt.sibling;var td=st(Ye,Jt,Xn.value,bt);if(td===null){Jt===null&&(Jt=Fn);break}h&&Jt&&td.alternate===null&&g(Ye,Jt),ze=F(td,ze,vn),Kn===null?tn=td:Kn.sibling=td,Kn=td,Jt=Fn}if(Xn.done)return _(Ye,Jt),zn&&bc(Ye,vn),tn;if(Jt===null){for(;!Xn.done;vn++,Xn=nt.next())Xn=vt(Ye,Xn.value,bt),Xn!==null&&(ze=F(Xn,ze,vn),Kn===null?tn=Xn:Kn.sibling=Xn,Kn=Xn);return zn&&bc(Ye,vn),tn}for(Jt=N(Jt);!Xn.done;vn++,Xn=nt.next())Xn=ft(Jt,Ye,vn,Xn.value,bt),Xn!==null&&(h&&Xn.alternate!==null&&Jt.delete(Xn.key===null?vn:Xn.key),ze=F(Xn,ze,vn),Kn===null?tn=Xn:Kn.sibling=Xn,Kn=Xn);return h&&Jt.forEach(function(Zse){return g(Ye,Zse)}),zn&&bc(Ye,vn),tn}function dr(Ye,ze,nt,bt){if(typeof nt=="object"&&nt!==null&&nt.type===y&&nt.key===null&&(nt=nt.props.children),typeof nt=="object"&&nt!==null){switch(nt.$$typeof){case p:e:{for(var tn=nt.key;ze!==null;){if(ze.key===tn){if(tn=nt.type,tn===y){if(ze.tag===7){_(Ye,ze.sibling),bt=R(ze,nt.props.children),bt.return=Ye,Ye=bt;break e}}else if(ze.elementType===tn||typeof tn=="object"&&tn!==null&&tn.$$typeof===O&&Rh(tn)===ze.type){_(Ye,ze.sibling),bt=R(ze,nt.props),Dx(bt,nt),bt.return=Ye,Ye=bt;break e}_(Ye,ze);break}else g(Ye,ze);ze=ze.sibling}nt.type===y?(bt=Th(nt.props.children,Ye.mode,bt,nt.key),bt.return=Ye,Ye=bt):(bt=gv(nt.type,nt.key,nt.props,null,Ye.mode,bt),Dx(bt,nt),bt.return=Ye,Ye=bt)}return Q(Ye);case b:e:{for(tn=nt.key;ze!==null;){if(ze.key===tn)if(ze.tag===4&&ze.stateNode.containerInfo===nt.containerInfo&&ze.stateNode.implementation===nt.implementation){_(Ye,ze.sibling),bt=R(ze,nt.children||[]),bt.return=Ye,Ye=bt;break e}else{_(Ye,ze);break}else g(Ye,ze);ze=ze.sibling}bt=u2(nt,Ye.mode,bt),bt.return=Ye,Ye=bt}return Q(Ye);case O:return nt=Rh(nt),dr(Ye,ze,nt,bt)}if(W(nt))return Qt(Ye,ze,nt,bt);if(L(nt)){if(tn=L(nt),typeof tn!="function")throw Error(r(150));return nt=tn.call(nt),an(Ye,ze,nt,bt)}if(typeof nt.then=="function")return dr(Ye,ze,Sv(nt),bt);if(nt.$$typeof===M)return dr(Ye,ze,yv(Ye,nt),bt);Mv(Ye,nt)}return typeof nt=="string"&&nt!==""||typeof nt=="number"||typeof nt=="bigint"?(nt=""+nt,ze!==null&&ze.tag===6?(_(Ye,ze.sibling),bt=R(ze,nt),bt.return=Ye,Ye=bt):(_(Ye,ze),bt=c2(nt,Ye.mode,bt),bt.return=Ye,Ye=bt),Q(Ye)):_(Ye,ze)}return function(Ye,ze,nt,bt){try{Rx=0;var tn=dr(Ye,ze,nt,bt);return Om=null,tn}catch(Jt){if(Jt===Dm||Jt===_v)throw Jt;var Kn=Ps(29,Jt,null,Ye.mode);return Kn.lanes=bt,Kn.return=Ye,Kn}}}var Oh=mF(!0),pF=mF(!1),Fu=!1;function w2(h){h.updateQueue={baseState:h.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function S2(h,g){h=h.updateQueue,g.updateQueue===h&&(g.updateQueue={baseState:h.baseState,firstBaseUpdate:h.firstBaseUpdate,lastBaseUpdate:h.lastBaseUpdate,shared:h.shared,callbacks:null})}function Uu(h){return{lane:h,tag:0,payload:null,callback:null,next:null}}function zu(h,g,_){var N=h.updateQueue;if(N===null)return null;if(N=N.shared,(Jn&2)!==0){var R=N.pending;return R===null?g.next=g:(g.next=R.next,R.next=g),N.pending=g,g=pv(h),ZI(h,null,_),g}return mv(h,N,g,_),pv(h)}function Ox(h,g,_){if(g=g.updateQueue,g!==null&&(g=g.shared,(_&4194048)!==0)){var N=g.lanes;N&=h.pendingLanes,_|=N,g.lanes=_,gt(h,_)}}function M2(h,g){var _=h.updateQueue,N=h.alternate;if(N!==null&&(N=N.updateQueue,_===N)){var R=null,F=null;if(_=_.firstBaseUpdate,_!==null){do{var Q={lane:_.lane,tag:_.tag,payload:_.payload,callback:null,next:null};F===null?R=F=Q:F=F.next=Q,_=_.next}while(_!==null);F===null?R=F=g:F=F.next=g}else R=F=g;_={baseState:N.baseState,firstBaseUpdate:R,lastBaseUpdate:F,shared:N.shared,callbacks:N.callbacks},h.updateQueue=_;return}h=_.lastBaseUpdate,h===null?_.firstBaseUpdate=g:h.next=g,_.lastBaseUpdate=g}var N2=!1;function Lx(){if(N2){var h=Rm;if(h!==null)throw h}}function Ix(h,g,_,N){N2=!1;var R=h.updateQueue;Fu=!1;var F=R.firstBaseUpdate,Q=R.lastBaseUpdate,ge=R.shared.pending;if(ge!==null){R.shared.pending=null;var De=ge,it=De.next;De.next=null,Q===null?F=it:Q.next=it,Q=De;var xt=h.alternate;xt!==null&&(xt=xt.updateQueue,ge=xt.lastBaseUpdate,ge!==Q&&(ge===null?xt.firstBaseUpdate=it:ge.next=it,xt.lastBaseUpdate=De))}if(F!==null){var vt=R.baseState;Q=0,xt=it=De=null,ge=F;do{var st=ge.lane&-536870913,ft=st!==ge.lane;if(ft?(In&st)===st:(N&st)===st){st!==0&&st===Pm&&(N2=!0),xt!==null&&(xt=xt.next={lane:0,tag:ge.tag,payload:ge.payload,callback:null,next:null});e:{var Qt=h,an=ge;st=g;var dr=_;switch(an.tag){case 1:if(Qt=an.payload,typeof Qt=="function"){vt=Qt.call(dr,vt,st);break e}vt=Qt;break e;case 3:Qt.flags=Qt.flags&-65537|128;case 0:if(Qt=an.payload,st=typeof Qt=="function"?Qt.call(dr,vt,st):Qt,st==null)break e;vt=f({},vt,st);break e;case 2:Fu=!0}}st=ge.callback,st!==null&&(h.flags|=64,ft&&(h.flags|=8192),ft=R.callbacks,ft===null?R.callbacks=[st]:ft.push(st))}else ft={lane:st,tag:ge.tag,payload:ge.payload,callback:ge.callback,next:null},xt===null?(it=xt=ft,De=vt):xt=xt.next=ft,Q|=st;if(ge=ge.next,ge===null){if(ge=R.shared.pending,ge===null)break;ft=ge,ge=ft.next,ft.next=null,R.lastBaseUpdate=ft,R.shared.pending=null}}while(!0);xt===null&&(De=vt),R.baseState=De,R.firstBaseUpdate=it,R.lastBaseUpdate=xt,F===null&&(R.shared.lanes=0),Gu|=Q,h.lanes=Q,h.memoizedState=vt}}function gF(h,g){if(typeof h!="function")throw Error(r(191,h));h.call(g)}function xF(h,g){var _=h.callbacks;if(_!==null)for(h.callbacks=null,h=0;h<_.length;h++)gF(_[h],g)}var Lm=z(null),Nv=z(0);function bF(h,g){h=Cc,ne(Nv,h),ne(Lm,g),Cc=h|g.baseLanes}function E2(){ne(Nv,Cc),ne(Lm,Lm.current)}function A2(){Cc=Nv.current,G(Lm),G(Nv)}var Rs=z(null),lo=null;function Bu(h){var g=h.alternate;ne(fi,fi.current&1),ne(Rs,h),lo===null&&(g===null||Lm.current!==null||g.memoizedState!==null)&&(lo=h)}function T2(h){ne(fi,fi.current),ne(Rs,h),lo===null&&(lo=h)}function yF(h){h.tag===22?(ne(fi,fi.current),ne(Rs,h),lo===null&&(lo=h)):$u()}function $u(){ne(fi,fi.current),ne(Rs,Rs.current)}function Ds(h){G(Rs),lo===h&&(lo=null),G(fi)}var fi=z(0);function Ev(h){for(var g=h;g!==null;){if(g.tag===13){var _=g.memoizedState;if(_!==null&&(_=_.dehydrated,_===null||DA(_)||OA(_)))return g}else if(g.tag===19&&(g.memoizedProps.revealOrder==="forwards"||g.memoizedProps.revealOrder==="backwards"||g.memoizedProps.revealOrder==="unstable_legacy-backwards"||g.memoizedProps.revealOrder==="together")){if((g.flags&128)!==0)return g}else if(g.child!==null){g.child.return=g,g=g.child;continue}if(g===h)break;for(;g.sibling===null;){if(g.return===null||g.return===h)return null;g=g.return}g.sibling.return=g.return,g=g.sibling}return null}var _c=0,yn=null,cr=null,vi=null,Av=!1,Im=!1,Lh=!1,Tv=0,Fx=0,Fm=null,$ae=0;function Zr(){throw Error(r(321))}function C2(h,g){if(g===null)return!1;for(var _=0;_F?F:8;var Q=K.T,ge={};K.T=ge,V2(h,!1,g,_);try{var De=R(),it=K.S;if(it!==null&&it(ge,De),De!==null&&typeof De=="object"&&typeof De.then=="function"){var xt=Bae(De,N);zx(h,g,xt,Is(h))}else zx(h,g,N,Is(h))}catch(vt){zx(h,g,{then:function(){},status:"rejected",reason:vt},Is())}finally{V.p=F,Q!==null&&ge.types!==null&&(Q.types=ge.types),K.T=Q}}function qae(){}function $2(h,g,_,N){if(h.tag!==5)throw Error(r(476));var R=XF(h).queue;KF(h,R,g,Z,_===null?qae:function(){return YF(h),_(N)})}function XF(h){var g=h.memoizedState;if(g!==null)return g;g={memoizedState:Z,baseState:Z,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:wc,lastRenderedState:Z},next:null};var _={};return g.next={memoizedState:_,baseState:_,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:wc,lastRenderedState:_},next:null},h.memoizedState=g,h=h.alternate,h!==null&&(h.memoizedState=g),g}function YF(h){var g=XF(h);g.next===null&&(g=h.alternate.memoizedState),zx(h,g.next.queue,{},Is())}function H2(){return ha(rb)}function QF(){return mi().memoizedState}function ZF(){return mi().memoizedState}function Kae(h){for(var g=h.return;g!==null;){switch(g.tag){case 24:case 3:var _=Is();h=Uu(_);var N=zu(g,h,_);N!==null&&(ps(N,g,_),Ox(N,g,_)),g={cache:b2()},h.payload=g;return}g=g.return}}function Xae(h,g,_){var N=Is();_={lane:N,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null},Dv(h)?e5(g,_):(_=o2(h,g,_,N),_!==null&&(ps(_,h,N),t5(_,g,N)))}function JF(h,g,_){var N=Is();zx(h,g,_,N)}function zx(h,g,_,N){var R={lane:N,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null};if(Dv(h))e5(g,R);else{var F=h.alternate;if(h.lanes===0&&(F===null||F.lanes===0)&&(F=g.lastRenderedReducer,F!==null))try{var Q=g.lastRenderedState,ge=F(Q,_);if(R.hasEagerState=!0,R.eagerState=ge,js(ge,Q))return mv(h,g,R,0),vr===null&&fv(),!1}catch{}if(_=o2(h,g,R,N),_!==null)return ps(_,h,N),t5(_,g,N),!0}return!1}function V2(h,g,_,N){if(N={lane:2,revertLane:wA(),gesture:null,action:N,hasEagerState:!1,eagerState:null,next:null},Dv(h)){if(g)throw Error(r(479))}else g=o2(h,_,N,2),g!==null&&ps(g,h,2)}function Dv(h){var g=h.alternate;return h===yn||g!==null&&g===yn}function e5(h,g){Im=Av=!0;var _=h.pending;_===null?g.next=g:(g.next=_.next,_.next=g),h.pending=g}function t5(h,g,_){if((_&4194048)!==0){var N=g.lanes;N&=h.pendingLanes,_|=N,g.lanes=_,gt(h,_)}}var Bx={readContext:ha,use:kv,useCallback:Zr,useContext:Zr,useEffect:Zr,useImperativeHandle:Zr,useLayoutEffect:Zr,useInsertionEffect:Zr,useMemo:Zr,useReducer:Zr,useRef:Zr,useState:Zr,useDebugValue:Zr,useDeferredValue:Zr,useTransition:Zr,useSyncExternalStore:Zr,useId:Zr,useHostTransitionStatus:Zr,useFormState:Zr,useActionState:Zr,useOptimistic:Zr,useMemoCache:Zr,useCacheRefresh:Zr};Bx.useEffectEvent=Zr;var n5={readContext:ha,use:kv,useCallback:function(h,g){return Wa().memoizedState=[h,g===void 0?null:g],h},useContext:ha,useEffect:UF,useImperativeHandle:function(h,g,_){_=_!=null?_.concat([h]):null,Pv(4194308,4,HF.bind(null,g,h),_)},useLayoutEffect:function(h,g){return Pv(4194308,4,h,g)},useInsertionEffect:function(h,g){Pv(4,2,h,g)},useMemo:function(h,g){var _=Wa();g=g===void 0?null:g;var N=h();if(Lh){te(!0);try{h()}finally{te(!1)}}return _.memoizedState=[N,g],N},useReducer:function(h,g,_){var N=Wa();if(_!==void 0){var R=_(g);if(Lh){te(!0);try{_(g)}finally{te(!1)}}}else R=g;return N.memoizedState=N.baseState=R,h={pending:null,lanes:0,dispatch:null,lastRenderedReducer:h,lastRenderedState:R},N.queue=h,h=h.dispatch=Xae.bind(null,yn,h),[N.memoizedState,h]},useRef:function(h){var g=Wa();return h={current:h},g.memoizedState=h},useState:function(h){h=I2(h);var g=h.queue,_=JF.bind(null,yn,g);return g.dispatch=_,[h.memoizedState,_]},useDebugValue:z2,useDeferredValue:function(h,g){var _=Wa();return B2(_,h,g)},useTransition:function(){var h=I2(!1);return h=KF.bind(null,yn,h.queue,!0,!1),Wa().memoizedState=h,[!1,h]},useSyncExternalStore:function(h,g,_){var N=yn,R=Wa();if(zn){if(_===void 0)throw Error(r(407));_=_()}else{if(_=g(),vr===null)throw Error(r(349));(In&127)!==0||SF(N,g,_)}R.memoizedState=_;var F={value:_,getSnapshot:g};return R.queue=F,UF(NF.bind(null,N,F,h),[h]),N.flags|=2048,Um(9,{destroy:void 0},MF.bind(null,N,F,_,g),null),_},useId:function(){var h=Wa(),g=vr.identifierPrefix;if(zn){var _=wl,N=_l;_=(N&~(1<<32-Me(N)-1)).toString(32)+_,g="_"+g+"R_"+_,_=Tv++,0<_&&(g+="H"+_.toString(32)),g+="_"}else _=$ae++,g="_"+g+"r_"+_.toString(32)+"_";return h.memoizedState=g},useHostTransitionStatus:H2,useFormState:DF,useActionState:DF,useOptimistic:function(h){var g=Wa();g.memoizedState=g.baseState=h;var _={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return g.queue=_,g=V2.bind(null,yn,!0,_),_.dispatch=g,[h,g]},useMemoCache:D2,useCacheRefresh:function(){return Wa().memoizedState=Kae.bind(null,yn)},useEffectEvent:function(h){var g=Wa(),_={impl:h};return g.memoizedState=_,function(){if((Jn&2)!==0)throw Error(r(440));return _.impl.apply(void 0,arguments)}}},G2={readContext:ha,use:kv,useCallback:GF,useContext:ha,useEffect:U2,useImperativeHandle:VF,useInsertionEffect:BF,useLayoutEffect:$F,useMemo:WF,useReducer:jv,useRef:FF,useState:function(){return jv(wc)},useDebugValue:z2,useDeferredValue:function(h,g){var _=mi();return qF(_,cr.memoizedState,h,g)},useTransition:function(){var h=jv(wc)[0],g=mi().memoizedState;return[typeof h=="boolean"?h:Ux(h),g]},useSyncExternalStore:wF,useId:QF,useHostTransitionStatus:H2,useFormState:OF,useActionState:OF,useOptimistic:function(h,g){var _=mi();return TF(_,cr,h,g)},useMemoCache:D2,useCacheRefresh:ZF};G2.useEffectEvent=zF;var r5={readContext:ha,use:kv,useCallback:GF,useContext:ha,useEffect:U2,useImperativeHandle:VF,useInsertionEffect:BF,useLayoutEffect:$F,useMemo:WF,useReducer:L2,useRef:FF,useState:function(){return L2(wc)},useDebugValue:z2,useDeferredValue:function(h,g){var _=mi();return cr===null?B2(_,h,g):qF(_,cr.memoizedState,h,g)},useTransition:function(){var h=L2(wc)[0],g=mi().memoizedState;return[typeof h=="boolean"?h:Ux(h),g]},useSyncExternalStore:wF,useId:QF,useHostTransitionStatus:H2,useFormState:IF,useActionState:IF,useOptimistic:function(h,g){var _=mi();return cr!==null?TF(_,cr,h,g):(_.baseState=h,[h,_.queue.dispatch])},useMemoCache:D2,useCacheRefresh:ZF};r5.useEffectEvent=zF;function W2(h,g,_,N){g=h.memoizedState,_=_(N,g),_=_==null?g:f({},g,_),h.memoizedState=_,h.lanes===0&&(h.updateQueue.baseState=_)}var q2={enqueueSetState:function(h,g,_){h=h._reactInternals;var N=Is(),R=Uu(N);R.payload=g,_!=null&&(R.callback=_),g=zu(h,R,N),g!==null&&(ps(g,h,N),Ox(g,h,N))},enqueueReplaceState:function(h,g,_){h=h._reactInternals;var N=Is(),R=Uu(N);R.tag=1,R.payload=g,_!=null&&(R.callback=_),g=zu(h,R,N),g!==null&&(ps(g,h,N),Ox(g,h,N))},enqueueForceUpdate:function(h,g){h=h._reactInternals;var _=Is(),N=Uu(_);N.tag=2,g!=null&&(N.callback=g),g=zu(h,N,_),g!==null&&(ps(g,h,_),Ox(g,h,_))}};function i5(h,g,_,N,R,F,Q){return h=h.stateNode,typeof h.shouldComponentUpdate=="function"?h.shouldComponentUpdate(N,F,Q):g.prototype&&g.prototype.isPureReactComponent?!Ax(_,N)||!Ax(R,F):!0}function a5(h,g,_,N){h=g.state,typeof g.componentWillReceiveProps=="function"&&g.componentWillReceiveProps(_,N),typeof g.UNSAFE_componentWillReceiveProps=="function"&&g.UNSAFE_componentWillReceiveProps(_,N),g.state!==h&&q2.enqueueReplaceState(g,g.state,null)}function Ih(h,g){var _=g;if("ref"in g){_={};for(var N in g)N!=="ref"&&(_[N]=g[N])}if(h=h.defaultProps){_===g&&(_=f({},_));for(var R in h)_[R]===void 0&&(_[R]=h[R])}return _}function s5(h){hv(h)}function o5(h){console.error(h)}function l5(h){hv(h)}function Ov(h,g){try{var _=h.onUncaughtError;_(g.value,{componentStack:g.stack})}catch(N){setTimeout(function(){throw N})}}function c5(h,g,_){try{var N=h.onCaughtError;N(_.value,{componentStack:_.stack,errorBoundary:g.tag===1?g.stateNode:null})}catch(R){setTimeout(function(){throw R})}}function K2(h,g,_){return _=Uu(_),_.tag=3,_.payload={element:null},_.callback=function(){Ov(h,g)},_}function u5(h){return h=Uu(h),h.tag=3,h}function d5(h,g,_,N){var R=_.type.getDerivedStateFromError;if(typeof R=="function"){var F=N.value;h.payload=function(){return R(F)},h.callback=function(){c5(g,_,N)}}var Q=_.stateNode;Q!==null&&typeof Q.componentDidCatch=="function"&&(h.callback=function(){c5(g,_,N),typeof R!="function"&&(Wu===null?Wu=new Set([this]):Wu.add(this));var ge=N.stack;this.componentDidCatch(N.value,{componentStack:ge!==null?ge:""})})}function Yae(h,g,_,N,R){if(_.flags|=32768,N!==null&&typeof N=="object"&&typeof N.then=="function"){if(g=_.alternate,g!==null&&jm(g,_,R,!0),_=Rs.current,_!==null){switch(_.tag){case 31:case 13:return lo===null?qv():_.alternate===null&&Jr===0&&(Jr=3),_.flags&=-257,_.flags|=65536,_.lanes=R,N===wv?_.flags|=16384:(g=_.updateQueue,g===null?_.updateQueue=new Set([N]):g.add(N),yA(h,N,R)),!1;case 22:return _.flags|=65536,N===wv?_.flags|=16384:(g=_.updateQueue,g===null?(g={transitions:null,markerInstances:null,retryQueue:new Set([N])},_.updateQueue=g):(_=g.retryQueue,_===null?g.retryQueue=new Set([N]):_.add(N)),yA(h,N,R)),!1}throw Error(r(435,_.tag))}return yA(h,N,R),qv(),!1}if(zn)return g=Rs.current,g!==null?((g.flags&65536)===0&&(g.flags|=256),g.flags|=65536,g.lanes=R,N!==f2&&(h=Error(r(422),{cause:N}),kx(io(h,_)))):(N!==f2&&(g=Error(r(423),{cause:N}),kx(io(g,_))),h=h.current.alternate,h.flags|=65536,R&=-R,h.lanes|=R,N=io(N,_),R=K2(h.stateNode,N,R),M2(h,R),Jr!==4&&(Jr=2)),!1;var F=Error(r(520),{cause:N});if(F=io(F,_),Xx===null?Xx=[F]:Xx.push(F),Jr!==4&&(Jr=2),g===null)return!0;N=io(N,_),_=g;do{switch(_.tag){case 3:return _.flags|=65536,h=R&-R,_.lanes|=h,h=K2(_.stateNode,N,h),M2(_,h),!1;case 1:if(g=_.type,F=_.stateNode,(_.flags&128)===0&&(typeof g.getDerivedStateFromError=="function"||F!==null&&typeof F.componentDidCatch=="function"&&(Wu===null||!Wu.has(F))))return _.flags|=65536,R&=-R,_.lanes|=R,R=u5(R),d5(R,h,_,N),M2(_,R),!1}_=_.return}while(_!==null);return!1}var X2=Error(r(461)),_i=!1;function fa(h,g,_,N){g.child=h===null?pF(g,null,_,N):Oh(g,h.child,_,N)}function h5(h,g,_,N,R){_=_.render;var F=g.ref;if("ref"in N){var Q={};for(var ge in N)ge!=="ref"&&(Q[ge]=N[ge])}else Q=N;return jh(g),N=k2(h,g,_,Q,F,R),ge=j2(),h!==null&&!_i?(P2(h,g,R),Sc(h,g,R)):(zn&&ge&&d2(g),g.flags|=1,fa(h,g,N,R),g.child)}function f5(h,g,_,N,R){if(h===null){var F=_.type;return typeof F=="function"&&!l2(F)&&F.defaultProps===void 0&&_.compare===null?(g.tag=15,g.type=F,m5(h,g,F,N,R)):(h=gv(_.type,null,N,g,g.mode,R),h.ref=g.ref,h.return=g,g.child=h)}if(F=h.child,!rA(h,R)){var Q=F.memoizedProps;if(_=_.compare,_=_!==null?_:Ax,_(Q,N)&&h.ref===g.ref)return Sc(h,g,R)}return g.flags|=1,h=xc(F,N),h.ref=g.ref,h.return=g,g.child=h}function m5(h,g,_,N,R){if(h!==null){var F=h.memoizedProps;if(Ax(F,N)&&h.ref===g.ref)if(_i=!1,g.pendingProps=N=F,rA(h,R))(h.flags&131072)!==0&&(_i=!0);else return g.lanes=h.lanes,Sc(h,g,R)}return Y2(h,g,_,N,R)}function p5(h,g,_,N){var R=N.children,F=h!==null?h.memoizedState:null;if(h===null&&g.stateNode===null&&(g.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),N.mode==="hidden"){if((g.flags&128)!==0){if(F=F!==null?F.baseLanes|_:_,h!==null){for(N=g.child=h.child,R=0;N!==null;)R=R|N.lanes|N.childLanes,N=N.sibling;N=R&~F}else N=0,g.child=null;return g5(h,g,F,_,N)}if((_&536870912)!==0)g.memoizedState={baseLanes:0,cachePool:null},h!==null&&vv(g,F!==null?F.cachePool:null),F!==null?bF(g,F):E2(),yF(g);else return N=g.lanes=536870912,g5(h,g,F!==null?F.baseLanes|_:_,_,N)}else F!==null?(vv(g,F.cachePool),bF(g,F),$u(),g.memoizedState=null):(h!==null&&vv(g,null),E2(),$u());return fa(h,g,R,_),g.child}function $x(h,g){return h!==null&&h.tag===22||g.stateNode!==null||(g.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),g.sibling}function g5(h,g,_,N,R){var F=v2();return F=F===null?null:{parent:yi._currentValue,pool:F},g.memoizedState={baseLanes:_,cachePool:F},h!==null&&vv(g,null),E2(),yF(g),h!==null&&jm(h,g,N,!0),g.childLanes=R,null}function Lv(h,g){return g=Fv({mode:g.mode,children:g.children},h.mode),g.ref=h.ref,h.child=g,g.return=h,g}function x5(h,g,_){return Oh(g,h.child,null,_),h=Lv(g,g.pendingProps),h.flags|=2,Ds(g),g.memoizedState=null,h}function Qae(h,g,_){var N=g.pendingProps,R=(g.flags&128)!==0;if(g.flags&=-129,h===null){if(zn){if(N.mode==="hidden")return h=Lv(g,N),g.lanes=536870912,$x(null,h);if(T2(g),(h=Ar)?(h=CU(h,oo),h=h!==null&&h.data==="&"?h:null,h!==null&&(g.memoizedState={dehydrated:h,treeContext:Du!==null?{id:_l,overflow:wl}:null,retryLane:536870912,hydrationErrors:null},_=eF(h),_.return=g,g.child=_,da=g,Ar=null)):h=null,h===null)throw Lu(g);return g.lanes=536870912,null}return Lv(g,N)}var F=h.memoizedState;if(F!==null){var Q=F.dehydrated;if(T2(g),R)if(g.flags&256)g.flags&=-257,g=x5(h,g,_);else if(g.memoizedState!==null)g.child=h.child,g.flags|=128,g=null;else throw Error(r(558));else if(_i||jm(h,g,_,!1),R=(_&h.childLanes)!==0,_i||R){if(N=vr,N!==null&&(Q=Kt(N,_),Q!==0&&Q!==F.retryLane))throw F.retryLane=Q,Ah(h,Q),ps(N,h,Q),X2;qv(),g=x5(h,g,_)}else h=F.treeContext,Ar=co(Q.nextSibling),da=g,zn=!0,Ou=null,oo=!1,h!==null&&rF(g,h),g=Lv(g,N),g.flags|=4096;return g}return h=xc(h.child,{mode:N.mode,children:N.children}),h.ref=g.ref,g.child=h,h.return=g,h}function Iv(h,g){var _=g.ref;if(_===null)h!==null&&h.ref!==null&&(g.flags|=4194816);else{if(typeof _!="function"&&typeof _!="object")throw Error(r(284));(h===null||h.ref!==_)&&(g.flags|=4194816)}}function Y2(h,g,_,N,R){return jh(g),_=k2(h,g,_,N,void 0,R),N=j2(),h!==null&&!_i?(P2(h,g,R),Sc(h,g,R)):(zn&&N&&d2(g),g.flags|=1,fa(h,g,_,R),g.child)}function b5(h,g,_,N,R,F){return jh(g),g.updateQueue=null,_=_F(g,N,_,R),vF(h),N=j2(),h!==null&&!_i?(P2(h,g,F),Sc(h,g,F)):(zn&&N&&d2(g),g.flags|=1,fa(h,g,_,F),g.child)}function y5(h,g,_,N,R){if(jh(g),g.stateNode===null){var F=Am,Q=_.contextType;typeof Q=="object"&&Q!==null&&(F=ha(Q)),F=new _(N,F),g.memoizedState=F.state!==null&&F.state!==void 0?F.state:null,F.updater=q2,g.stateNode=F,F._reactInternals=g,F=g.stateNode,F.props=N,F.state=g.memoizedState,F.refs={},w2(g),Q=_.contextType,F.context=typeof Q=="object"&&Q!==null?ha(Q):Am,F.state=g.memoizedState,Q=_.getDerivedStateFromProps,typeof Q=="function"&&(W2(g,_,Q,N),F.state=g.memoizedState),typeof _.getDerivedStateFromProps=="function"||typeof F.getSnapshotBeforeUpdate=="function"||typeof F.UNSAFE_componentWillMount!="function"&&typeof F.componentWillMount!="function"||(Q=F.state,typeof F.componentWillMount=="function"&&F.componentWillMount(),typeof F.UNSAFE_componentWillMount=="function"&&F.UNSAFE_componentWillMount(),Q!==F.state&&q2.enqueueReplaceState(F,F.state,null),Ix(g,N,F,R),Lx(),F.state=g.memoizedState),typeof F.componentDidMount=="function"&&(g.flags|=4194308),N=!0}else if(h===null){F=g.stateNode;var ge=g.memoizedProps,De=Ih(_,ge);F.props=De;var it=F.context,xt=_.contextType;Q=Am,typeof xt=="object"&&xt!==null&&(Q=ha(xt));var vt=_.getDerivedStateFromProps;xt=typeof vt=="function"||typeof F.getSnapshotBeforeUpdate=="function",ge=g.pendingProps!==ge,xt||typeof F.UNSAFE_componentWillReceiveProps!="function"&&typeof F.componentWillReceiveProps!="function"||(ge||it!==Q)&&a5(g,F,N,Q),Fu=!1;var st=g.memoizedState;F.state=st,Ix(g,N,F,R),Lx(),it=g.memoizedState,ge||st!==it||Fu?(typeof vt=="function"&&(W2(g,_,vt,N),it=g.memoizedState),(De=Fu||i5(g,_,De,N,st,it,Q))?(xt||typeof F.UNSAFE_componentWillMount!="function"&&typeof F.componentWillMount!="function"||(typeof F.componentWillMount=="function"&&F.componentWillMount(),typeof F.UNSAFE_componentWillMount=="function"&&F.UNSAFE_componentWillMount()),typeof F.componentDidMount=="function"&&(g.flags|=4194308)):(typeof F.componentDidMount=="function"&&(g.flags|=4194308),g.memoizedProps=N,g.memoizedState=it),F.props=N,F.state=it,F.context=Q,N=De):(typeof F.componentDidMount=="function"&&(g.flags|=4194308),N=!1)}else{F=g.stateNode,S2(h,g),Q=g.memoizedProps,xt=Ih(_,Q),F.props=xt,vt=g.pendingProps,st=F.context,it=_.contextType,De=Am,typeof it=="object"&&it!==null&&(De=ha(it)),ge=_.getDerivedStateFromProps,(it=typeof ge=="function"||typeof F.getSnapshotBeforeUpdate=="function")||typeof F.UNSAFE_componentWillReceiveProps!="function"&&typeof F.componentWillReceiveProps!="function"||(Q!==vt||st!==De)&&a5(g,F,N,De),Fu=!1,st=g.memoizedState,F.state=st,Ix(g,N,F,R),Lx();var ft=g.memoizedState;Q!==vt||st!==ft||Fu||h!==null&&h.dependencies!==null&&bv(h.dependencies)?(typeof ge=="function"&&(W2(g,_,ge,N),ft=g.memoizedState),(xt=Fu||i5(g,_,xt,N,st,ft,De)||h!==null&&h.dependencies!==null&&bv(h.dependencies))?(it||typeof F.UNSAFE_componentWillUpdate!="function"&&typeof F.componentWillUpdate!="function"||(typeof F.componentWillUpdate=="function"&&F.componentWillUpdate(N,ft,De),typeof F.UNSAFE_componentWillUpdate=="function"&&F.UNSAFE_componentWillUpdate(N,ft,De)),typeof F.componentDidUpdate=="function"&&(g.flags|=4),typeof F.getSnapshotBeforeUpdate=="function"&&(g.flags|=1024)):(typeof F.componentDidUpdate!="function"||Q===h.memoizedProps&&st===h.memoizedState||(g.flags|=4),typeof F.getSnapshotBeforeUpdate!="function"||Q===h.memoizedProps&&st===h.memoizedState||(g.flags|=1024),g.memoizedProps=N,g.memoizedState=ft),F.props=N,F.state=ft,F.context=De,N=xt):(typeof F.componentDidUpdate!="function"||Q===h.memoizedProps&&st===h.memoizedState||(g.flags|=4),typeof F.getSnapshotBeforeUpdate!="function"||Q===h.memoizedProps&&st===h.memoizedState||(g.flags|=1024),N=!1)}return F=N,Iv(h,g),N=(g.flags&128)!==0,F||N?(F=g.stateNode,_=N&&typeof _.getDerivedStateFromError!="function"?null:F.render(),g.flags|=1,h!==null&&N?(g.child=Oh(g,h.child,null,R),g.child=Oh(g,null,_,R)):fa(h,g,_,R),g.memoizedState=F.state,h=g.child):h=Sc(h,g,R),h}function v5(h,g,_,N){return Ch(),g.flags|=256,fa(h,g,_,N),g.child}var Q2={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function Z2(h){return{baseLanes:h,cachePool:cF()}}function J2(h,g,_){return h=h!==null?h.childLanes&~_:0,g&&(h|=Ls),h}function _5(h,g,_){var N=g.pendingProps,R=!1,F=(g.flags&128)!==0,Q;if((Q=F)||(Q=h!==null&&h.memoizedState===null?!1:(fi.current&2)!==0),Q&&(R=!0,g.flags&=-129),Q=(g.flags&32)!==0,g.flags&=-33,h===null){if(zn){if(R?Bu(g):$u(),(h=Ar)?(h=CU(h,oo),h=h!==null&&h.data!=="&"?h:null,h!==null&&(g.memoizedState={dehydrated:h,treeContext:Du!==null?{id:_l,overflow:wl}:null,retryLane:536870912,hydrationErrors:null},_=eF(h),_.return=g,g.child=_,da=g,Ar=null)):h=null,h===null)throw Lu(g);return OA(h)?g.lanes=32:g.lanes=536870912,null}var ge=N.children;return N=N.fallback,R?($u(),R=g.mode,ge=Fv({mode:"hidden",children:ge},R),N=Th(N,R,_,null),ge.return=g,N.return=g,ge.sibling=N,g.child=ge,N=g.child,N.memoizedState=Z2(_),N.childLanes=J2(h,Q,_),g.memoizedState=Q2,$x(null,N)):(Bu(g),eA(g,ge))}var De=h.memoizedState;if(De!==null&&(ge=De.dehydrated,ge!==null)){if(F)g.flags&256?(Bu(g),g.flags&=-257,g=tA(h,g,_)):g.memoizedState!==null?($u(),g.child=h.child,g.flags|=128,g=null):($u(),ge=N.fallback,R=g.mode,N=Fv({mode:"visible",children:N.children},R),ge=Th(ge,R,_,null),ge.flags|=2,N.return=g,ge.return=g,N.sibling=ge,g.child=N,Oh(g,h.child,null,_),N=g.child,N.memoizedState=Z2(_),N.childLanes=J2(h,Q,_),g.memoizedState=Q2,g=$x(null,N));else if(Bu(g),OA(ge)){if(Q=ge.nextSibling&&ge.nextSibling.dataset,Q)var it=Q.dgst;Q=it,N=Error(r(419)),N.stack="",N.digest=Q,kx({value:N,source:null,stack:null}),g=tA(h,g,_)}else if(_i||jm(h,g,_,!1),Q=(_&h.childLanes)!==0,_i||Q){if(Q=vr,Q!==null&&(N=Kt(Q,_),N!==0&&N!==De.retryLane))throw De.retryLane=N,Ah(h,N),ps(Q,h,N),X2;DA(ge)||qv(),g=tA(h,g,_)}else DA(ge)?(g.flags|=192,g.child=h.child,g=null):(h=De.treeContext,Ar=co(ge.nextSibling),da=g,zn=!0,Ou=null,oo=!1,h!==null&&rF(g,h),g=eA(g,N.children),g.flags|=4096);return g}return R?($u(),ge=N.fallback,R=g.mode,De=h.child,it=De.sibling,N=xc(De,{mode:"hidden",children:N.children}),N.subtreeFlags=De.subtreeFlags&65011712,it!==null?ge=xc(it,ge):(ge=Th(ge,R,_,null),ge.flags|=2),ge.return=g,N.return=g,N.sibling=ge,g.child=N,$x(null,N),N=g.child,ge=h.child.memoizedState,ge===null?ge=Z2(_):(R=ge.cachePool,R!==null?(De=yi._currentValue,R=R.parent!==De?{parent:De,pool:De}:R):R=cF(),ge={baseLanes:ge.baseLanes|_,cachePool:R}),N.memoizedState=ge,N.childLanes=J2(h,Q,_),g.memoizedState=Q2,$x(h.child,N)):(Bu(g),_=h.child,h=_.sibling,_=xc(_,{mode:"visible",children:N.children}),_.return=g,_.sibling=null,h!==null&&(Q=g.deletions,Q===null?(g.deletions=[h],g.flags|=16):Q.push(h)),g.child=_,g.memoizedState=null,_)}function eA(h,g){return g=Fv({mode:"visible",children:g},h.mode),g.return=h,h.child=g}function Fv(h,g){return h=Ps(22,h,null,g),h.lanes=0,h}function tA(h,g,_){return Oh(g,h.child,null,_),h=eA(g,g.pendingProps.children),h.flags|=2,g.memoizedState=null,h}function w5(h,g,_){h.lanes|=g;var N=h.alternate;N!==null&&(N.lanes|=g),g2(h.return,g,_)}function nA(h,g,_,N,R,F){var Q=h.memoizedState;Q===null?h.memoizedState={isBackwards:g,rendering:null,renderingStartTime:0,last:N,tail:_,tailMode:R,treeForkCount:F}:(Q.isBackwards=g,Q.rendering=null,Q.renderingStartTime=0,Q.last=N,Q.tail=_,Q.tailMode=R,Q.treeForkCount=F)}function S5(h,g,_){var N=g.pendingProps,R=N.revealOrder,F=N.tail;N=N.children;var Q=fi.current,ge=(Q&2)!==0;if(ge?(Q=Q&1|2,g.flags|=128):Q&=1,ne(fi,Q),fa(h,g,N,_),N=zn?Cx:0,!ge&&h!==null&&(h.flags&128)!==0)e:for(h=g.child;h!==null;){if(h.tag===13)h.memoizedState!==null&&w5(h,_,g);else if(h.tag===19)w5(h,_,g);else if(h.child!==null){h.child.return=h,h=h.child;continue}if(h===g)break e;for(;h.sibling===null;){if(h.return===null||h.return===g)break e;h=h.return}h.sibling.return=h.return,h=h.sibling}switch(R){case"forwards":for(_=g.child,R=null;_!==null;)h=_.alternate,h!==null&&Ev(h)===null&&(R=_),_=_.sibling;_=R,_===null?(R=g.child,g.child=null):(R=_.sibling,_.sibling=null),nA(g,!1,R,_,F,N);break;case"backwards":case"unstable_legacy-backwards":for(_=null,R=g.child,g.child=null;R!==null;){if(h=R.alternate,h!==null&&Ev(h)===null){g.child=R;break}h=R.sibling,R.sibling=_,_=R,R=h}nA(g,!0,_,null,F,N);break;case"together":nA(g,!1,null,null,void 0,N);break;default:g.memoizedState=null}return g.child}function Sc(h,g,_){if(h!==null&&(g.dependencies=h.dependencies),Gu|=g.lanes,(_&g.childLanes)===0)if(h!==null){if(jm(h,g,_,!1),(_&g.childLanes)===0)return null}else return null;if(h!==null&&g.child!==h.child)throw Error(r(153));if(g.child!==null){for(h=g.child,_=xc(h,h.pendingProps),g.child=_,_.return=g;h.sibling!==null;)h=h.sibling,_=_.sibling=xc(h,h.pendingProps),_.return=g;_.sibling=null}return g.child}function rA(h,g){return(h.lanes&g)!==0?!0:(h=h.dependencies,!!(h!==null&&bv(h)))}function Zae(h,g,_){switch(g.tag){case 3:Ae(g,g.stateNode.containerInfo),Iu(g,yi,h.memoizedState.cache),Ch();break;case 27:case 5:Ne(g);break;case 4:Ae(g,g.stateNode.containerInfo);break;case 10:Iu(g,g.type,g.memoizedProps.value);break;case 31:if(g.memoizedState!==null)return g.flags|=128,T2(g),null;break;case 13:var N=g.memoizedState;if(N!==null)return N.dehydrated!==null?(Bu(g),g.flags|=128,null):(_&g.child.childLanes)!==0?_5(h,g,_):(Bu(g),h=Sc(h,g,_),h!==null?h.sibling:null);Bu(g);break;case 19:var R=(h.flags&128)!==0;if(N=(_&g.childLanes)!==0,N||(jm(h,g,_,!1),N=(_&g.childLanes)!==0),R){if(N)return S5(h,g,_);g.flags|=128}if(R=g.memoizedState,R!==null&&(R.rendering=null,R.tail=null,R.lastEffect=null),ne(fi,fi.current),N)break;return null;case 22:return g.lanes=0,p5(h,g,_,g.pendingProps);case 24:Iu(g,yi,h.memoizedState.cache)}return Sc(h,g,_)}function M5(h,g,_){if(h!==null)if(h.memoizedProps!==g.pendingProps)_i=!0;else{if(!rA(h,_)&&(g.flags&128)===0)return _i=!1,Zae(h,g,_);_i=(h.flags&131072)!==0}else _i=!1,zn&&(g.flags&1048576)!==0&&nF(g,Cx,g.index);switch(g.lanes=0,g.tag){case 16:e:{var N=g.pendingProps;if(h=Rh(g.elementType),g.type=h,typeof h=="function")l2(h)?(N=Ih(h,N),g.tag=1,g=y5(null,g,h,N,_)):(g.tag=0,g=Y2(null,g,h,N,_));else{if(h!=null){var R=h.$$typeof;if(R===A){g.tag=11,g=h5(null,g,h,N,_);break e}else if(R===E){g.tag=14,g=f5(null,g,h,N,_);break e}}throw g=j(h)||h,Error(r(306,g,""))}}return g;case 0:return Y2(h,g,g.type,g.pendingProps,_);case 1:return N=g.type,R=Ih(N,g.pendingProps),y5(h,g,N,R,_);case 3:e:{if(Ae(g,g.stateNode.containerInfo),h===null)throw Error(r(387));N=g.pendingProps;var F=g.memoizedState;R=F.element,S2(h,g),Ix(g,N,null,_);var Q=g.memoizedState;if(N=Q.cache,Iu(g,yi,N),N!==F.cache&&x2(g,[yi],_,!0),Lx(),N=Q.element,F.isDehydrated)if(F={element:N,isDehydrated:!1,cache:Q.cache},g.updateQueue.baseState=F,g.memoizedState=F,g.flags&256){g=v5(h,g,N,_);break e}else if(N!==R){R=io(Error(r(424)),g),kx(R),g=v5(h,g,N,_);break e}else for(h=g.stateNode.containerInfo,h.nodeType===9?h=h.body:h=h.nodeName==="HTML"?h.ownerDocument.body:h,Ar=co(h.firstChild),da=g,zn=!0,Ou=null,oo=!0,_=pF(g,null,N,_),g.child=_;_;)_.flags=_.flags&-3|4096,_=_.sibling;else{if(Ch(),N===R){g=Sc(h,g,_);break e}fa(h,g,N,_)}g=g.child}return g;case 26:return Iv(h,g),h===null?(_=OU(g.type,null,g.pendingProps,null))?g.memoizedState=_:zn||(_=g.type,h=g.pendingProps,N=e_(me.current).createElement(_),N[sn]=g,N[Rr]=h,ma(N,_,h),de(N),g.stateNode=N):g.memoizedState=OU(g.type,h.memoizedProps,g.pendingProps,h.memoizedState),null;case 27:return Ne(g),h===null&&zn&&(N=g.stateNode=PU(g.type,g.pendingProps,me.current),da=g,oo=!0,R=Ar,Yu(g.type)?(LA=R,Ar=co(N.firstChild)):Ar=R),fa(h,g,g.pendingProps.children,_),Iv(h,g),h===null&&(g.flags|=4194304),g.child;case 5:return h===null&&zn&&((R=N=Ar)&&(N=Tse(N,g.type,g.pendingProps,oo),N!==null?(g.stateNode=N,da=g,Ar=co(N.firstChild),oo=!1,R=!0):R=!1),R||Lu(g)),Ne(g),R=g.type,F=g.pendingProps,Q=h!==null?h.memoizedProps:null,N=F.children,jA(R,F)?N=null:Q!==null&&jA(R,Q)&&(g.flags|=32),g.memoizedState!==null&&(R=k2(h,g,Hae,null,null,_),rb._currentValue=R),Iv(h,g),fa(h,g,N,_),g.child;case 6:return h===null&&zn&&((h=_=Ar)&&(_=Cse(_,g.pendingProps,oo),_!==null?(g.stateNode=_,da=g,Ar=null,h=!0):h=!1),h||Lu(g)),null;case 13:return _5(h,g,_);case 4:return Ae(g,g.stateNode.containerInfo),N=g.pendingProps,h===null?g.child=Oh(g,null,N,_):fa(h,g,N,_),g.child;case 11:return h5(h,g,g.type,g.pendingProps,_);case 7:return fa(h,g,g.pendingProps,_),g.child;case 8:return fa(h,g,g.pendingProps.children,_),g.child;case 12:return fa(h,g,g.pendingProps.children,_),g.child;case 10:return N=g.pendingProps,Iu(g,g.type,N.value),fa(h,g,N.children,_),g.child;case 9:return R=g.type._context,N=g.pendingProps.children,jh(g),R=ha(R),N=N(R),g.flags|=1,fa(h,g,N,_),g.child;case 14:return f5(h,g,g.type,g.pendingProps,_);case 15:return m5(h,g,g.type,g.pendingProps,_);case 19:return S5(h,g,_);case 31:return Qae(h,g,_);case 22:return p5(h,g,_,g.pendingProps);case 24:return jh(g),N=ha(yi),h===null?(R=v2(),R===null&&(R=vr,F=b2(),R.pooledCache=F,F.refCount++,F!==null&&(R.pooledCacheLanes|=_),R=F),g.memoizedState={parent:N,cache:R},w2(g),Iu(g,yi,R)):((h.lanes&_)!==0&&(S2(h,g),Ix(g,null,null,_),Lx()),R=h.memoizedState,F=g.memoizedState,R.parent!==N?(R={parent:N,cache:N},g.memoizedState=R,g.lanes===0&&(g.memoizedState=g.updateQueue.baseState=R),Iu(g,yi,N)):(N=F.cache,Iu(g,yi,N),N!==R.cache&&x2(g,[yi],_,!0))),fa(h,g,g.pendingProps.children,_),g.child;case 29:throw g.pendingProps}throw Error(r(156,g.tag))}function Mc(h){h.flags|=4}function iA(h,g,_,N,R){if((g=(h.mode&32)!==0)&&(g=!1),g){if(h.flags|=16777216,(R&335544128)===R)if(h.stateNode.complete)h.flags|=8192;else if(Q5())h.flags|=8192;else throw Dh=wv,_2}else h.flags&=-16777217}function N5(h,g){if(g.type!=="stylesheet"||(g.state.loading&4)!==0)h.flags&=-16777217;else if(h.flags|=16777216,!zU(g))if(Q5())h.flags|=8192;else throw Dh=wv,_2}function Uv(h,g){g!==null&&(h.flags|=4),h.flags&16384&&(g=h.tag!==22?kt():536870912,h.lanes|=g,Hm|=g)}function Hx(h,g){if(!zn)switch(h.tailMode){case"hidden":g=h.tail;for(var _=null;g!==null;)g.alternate!==null&&(_=g),g=g.sibling;_===null?h.tail=null:_.sibling=null;break;case"collapsed":_=h.tail;for(var N=null;_!==null;)_.alternate!==null&&(N=_),_=_.sibling;N===null?g||h.tail===null?h.tail=null:h.tail.sibling=null:N.sibling=null}}function Tr(h){var g=h.alternate!==null&&h.alternate.child===h.child,_=0,N=0;if(g)for(var R=h.child;R!==null;)_|=R.lanes|R.childLanes,N|=R.subtreeFlags&65011712,N|=R.flags&65011712,R.return=h,R=R.sibling;else for(R=h.child;R!==null;)_|=R.lanes|R.childLanes,N|=R.subtreeFlags,N|=R.flags,R.return=h,R=R.sibling;return h.subtreeFlags|=N,h.childLanes=_,g}function Jae(h,g,_){var N=g.pendingProps;switch(h2(g),g.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Tr(g),null;case 1:return Tr(g),null;case 3:return _=g.stateNode,N=null,h!==null&&(N=h.memoizedState.cache),g.memoizedState.cache!==N&&(g.flags|=2048),vc(yi),pe(),_.pendingContext&&(_.context=_.pendingContext,_.pendingContext=null),(h===null||h.child===null)&&(km(g)?Mc(g):h===null||h.memoizedState.isDehydrated&&(g.flags&256)===0||(g.flags|=1024,m2())),Tr(g),null;case 26:var R=g.type,F=g.memoizedState;return h===null?(Mc(g),F!==null?(Tr(g),N5(g,F)):(Tr(g),iA(g,R,null,N,_))):F?F!==h.memoizedState?(Mc(g),Tr(g),N5(g,F)):(Tr(g),g.flags&=-16777217):(h=h.memoizedProps,h!==N&&Mc(g),Tr(g),iA(g,R,h,N,_)),null;case 27:if(fe(g),_=me.current,R=g.type,h!==null&&g.stateNode!=null)h.memoizedProps!==N&&Mc(g);else{if(!N){if(g.stateNode===null)throw Error(r(166));return Tr(g),null}h=ie.current,km(g)?iF(g):(h=PU(R,N,_),g.stateNode=h,Mc(g))}return Tr(g),null;case 5:if(fe(g),R=g.type,h!==null&&g.stateNode!=null)h.memoizedProps!==N&&Mc(g);else{if(!N){if(g.stateNode===null)throw Error(r(166));return Tr(g),null}if(F=ie.current,km(g))iF(g);else{var Q=e_(me.current);switch(F){case 1:F=Q.createElementNS("http://www.w3.org/2000/svg",R);break;case 2:F=Q.createElementNS("http://www.w3.org/1998/Math/MathML",R);break;default:switch(R){case"svg":F=Q.createElementNS("http://www.w3.org/2000/svg",R);break;case"math":F=Q.createElementNS("http://www.w3.org/1998/Math/MathML",R);break;case"script":F=Q.createElement("div"),F.innerHTML=" + From be34b778fff0b4d7b696cb359023265a71b2a873 Mon Sep 17 00:00:00 2001 From: maziggy Date: Wed, 28 Jan 2026 15:57:43 +0100 Subject: [PATCH 12/28] Uodated CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49efef186..b7b4a47c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to Bambuddy will be documented in this file. -## [0.1.6] - Not released +## [0.1.6-final] - Not released ### New Features - **GitHub Profile Backup** - Automatically backup your Cloud profiles, K-profiles and settings to a GitHub repository: From c203d9bf5bd9c4b5bada54f5bb1ada52d3fba4de Mon Sep 17 00:00:00 2001 From: maziggy Date: Wed, 28 Jan 2026 16:22:33 +0100 Subject: [PATCH 13/28] Fix multi-plate thumbnail display in queue and add archive plate browsing - Queue now shows correct plate thumbnail based on selected plate_id (previously always showed plate 1 regardless of selection) - Archive cards now support plate browsing for multi-plate 3MF files: - Left/right arrows to cycle through plates on hover - Dot indicators at bottom show current plate (clickable) - Lazy-loads plate data only when user hovers - Added getArchivePlateThumbnail and getLibraryFilePlateThumbnail API methods Closes #166 --- CHANGELOG.md | 8 + README.md | 1 + .../src/__tests__/pages/ArchivesPage.test.tsx | 40 +++++ .../src/__tests__/pages/QueuePage.test.tsx | 21 +++ frontend/src/api/client.ts | 4 + frontend/src/pages/ArchivesPage.tsx | 87 ++++++++++- frontend/src/pages/QueuePage.tsx | 14 +- ...{index-nwJjDqT-.css => index-DFuUL8IF.css} | 2 +- .../{index-DuO_7tQ3.js => index-abyjQYWw.js} | 146 +++++++++--------- static/index.html | 4 +- 10 files changed, 245 insertions(+), 82 deletions(-) rename static/assets/{index-nwJjDqT-.css => index-DFuUL8IF.css} (76%) rename static/assets/{index-DuO_7tQ3.js => index-abyjQYWw.js} (76%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7b4a47c9..cfb64f5ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to Bambuddy will be documented in this file. ## [0.1.6-final] - Not released ### New Features +- **Archive Plate Browsing** - Browse plate thumbnails directly in archive cards (Issue #166): + - Hover over archive card to reveal plate navigation for multi-plate files + - Left/right arrows to cycle through plate thumbnails + - Dot indicators show current plate (clickable to jump to specific plate) + - Lazy-loads plate data only when user hovers - **GitHub Profile Backup** - Automatically backup your Cloud profiles, K-profiles and settings to a GitHub repository: - Configure GitHub repository URL and Personal Access Token - Schedule backups hourly, daily, or weekly @@ -84,6 +89,9 @@ All notable changes to Bambuddy will be documented in this file. - Tri-state toggles: unchanged / on / off for each setting ### Fixes +- **Multi-Plate Thumbnail in Queue** - Fixed queue items showing wrong thumbnail for multi-plate files (Issue #166): + - Queue now displays the correct plate thumbnail based on selected plate + - Previously always showed plate 1 thumbnail regardless of selection - **HMS Error Notifications** - Get notified when printer errors occur (Issue #84): - Automatic notifications for HMS errors (AMS issues, nozzle problems, etc.) - Human-readable error messages (853 error codes translated) diff --git a/README.md b/README.md index 3f4899375..999972dbb 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ - Photo attachments & failure analysis - Timelapse editor (trim, speed, music) - Re-print to any connected printer with AMS mapping (auto-match or manual slot selection, multi-plate support) +- Plate thumbnail browsing for multi-plate archives (hover to navigate between plates) - Archive comparison (side-by-side diff) ### 📊 Monitoring & Control diff --git a/frontend/src/__tests__/pages/ArchivesPage.test.tsx b/frontend/src/__tests__/pages/ArchivesPage.test.tsx index 3ae8949a3..0b19a0fd1 100644 --- a/frontend/src/__tests__/pages/ArchivesPage.test.tsx +++ b/frontend/src/__tests__/pages/ArchivesPage.test.tsx @@ -261,4 +261,44 @@ describe('ArchivesPage', () => { }); }); }); + + describe('plate navigation', () => { + it('renders archive cards with thumbnails', async () => { + render(); + + await waitFor(() => { + // Archive cards should render with their thumbnails + expect(screen.getByText('Benchy')).toBeInTheDocument(); + // Thumbnail images should be present (archive cards have img elements) + const images = document.querySelectorAll('img[alt="Benchy"]'); + expect(images.length).toBeGreaterThanOrEqual(0); + }); + }); + + it('fetches plate data for multi-plate archives on hover', async () => { + // Setup handler for plates endpoint + server.use( + http.get('/api/v1/archives/:id/plates', ({ params }) => { + return HttpResponse.json({ + archive_id: Number(params.id), + filename: 'test.3mf', + plates: [ + { index: 0, name: 'Plate 1', objects: ['Object A'], has_thumbnail: true, thumbnail_url: '/thumb1.png', print_time_seconds: 3600, filament_used_grams: 10, filaments: [] }, + { index: 1, name: 'Plate 2', objects: ['Object B'], has_thumbnail: true, thumbnail_url: '/thumb2.png', print_time_seconds: 1800, filament_used_grams: 5, filaments: [] }, + ], + is_multi_plate: true, + }); + }) + ); + + render(); + + await waitFor(() => { + expect(screen.getByText('Benchy')).toBeInTheDocument(); + }); + + // Archives with multi-plate support will show navigation on hover + // The plates API is called lazily when hovering + }); + }); }); diff --git a/frontend/src/__tests__/pages/QueuePage.test.tsx b/frontend/src/__tests__/pages/QueuePage.test.tsx index 3b958ad39..dbb7a792a 100644 --- a/frontend/src/__tests__/pages/QueuePage.test.tsx +++ b/frontend/src/__tests__/pages/QueuePage.test.tsx @@ -215,6 +215,27 @@ describe('QueuePage', () => { expect(printerElements.length).toBeGreaterThan(0); }); }); + + it('renders queue items with plate_id correctly', async () => { + // Override with queue items that have plate_id set + server.use( + http.get('/api/v1/queue/', () => { + return HttpResponse.json([ + { + ...mockQueueItems[0], + plate_id: 2, + archive_name: 'Multi-plate Print', + }, + ]); + }) + ); + + render(); + + await waitFor(() => { + expect(screen.getByText('Multi-plate Print')).toBeInTheDocument(); + }); + }); }); describe('empty state', () => { diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 6ccef4b6e..75ce54115 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1984,6 +1984,8 @@ export const api = { method: 'POST', }), getArchiveThumbnail: (id: number) => `${API_BASE}/archives/${id}/thumbnail?v=${Date.now()}`, + getArchivePlateThumbnail: (id: number, plateIndex: number) => + `${API_BASE}/archives/${id}/plate-thumbnail/${plateIndex}`, getArchiveDownload: (id: number) => `${API_BASE}/archives/${id}/download`, getArchiveGcode: (id: number) => `${API_BASE}/archives/${id}/gcode`, getArchivePlatePreview: (id: number) => `${API_BASE}/archives/${id}/plate-preview`, @@ -3041,6 +3043,8 @@ export const api = { request<{ status: string; message: string }>(`/library/files/${id}`, { method: 'DELETE' }), getLibraryFileDownloadUrl: (id: number) => `${API_BASE}/library/files/${id}/download`, getLibraryFileThumbnailUrl: (id: number) => `${API_BASE}/library/files/${id}/thumbnail`, + getLibraryFilePlateThumbnail: (id: number, plateIndex: number) => + `${API_BASE}/library/files/${id}/plate-thumbnail/${plateIndex}`, getLibraryFileGcodeUrl: (id: number) => `${API_BASE}/library/files/${id}/gcode`, moveLibraryFiles: (fileIds: number[], folderId: number | null) => request<{ status: string; moved: number }>('/library/files/move', { diff --git a/frontend/src/pages/ArchivesPage.tsx b/frontend/src/pages/ArchivesPage.tsx index 8d023585d..f539d2787 100644 --- a/frontend/src/pages/ArchivesPage.tsx +++ b/frontend/src/pages/ArchivesPage.tsx @@ -41,6 +41,8 @@ import { GitCompare, Loader2, FolderKanban, + ChevronLeft, + ChevronRight, } from 'lucide-react'; import { api } from '../api/client'; import { openInSlicer } from '../utils/slicer'; @@ -133,9 +135,23 @@ function ArchiveCard({ const [showDeleteSource3mfConfirm, setShowDeleteSource3mfConfirm] = useState(false); const [showDeleteF3dConfirm, setShowDeleteF3dConfirm] = useState(false); const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null); + const [currentPlateIndex, setCurrentPlateIndex] = useState(null); + const [showPlateNav, setShowPlateNav] = useState(false); const source3mfInputRef = useRef(null); const f3dInputRef = useRef(null); + // Fetch plates data for multi-plate browsing (lazy - only when hovering) + const { data: platesData } = useQuery({ + queryKey: ['archive-plates', archive.id], + queryFn: () => api.getArchivePlates(archive.id), + enabled: showPlateNav, // Only fetch when user hovers to see navigation + staleTime: 5 * 60 * 1000, // Cache for 5 minutes + }); + + const plates = platesData?.plates ?? []; + const isMultiPlate = platesData?.is_multi_plate ?? false; + const displayPlateIndex = currentPlateIndex ?? 0; + const source3mfUploadMutation = useMutation({ mutationFn: (file: File) => api.uploadSource3mf(archive.id, file), onSuccess: (data) => { @@ -505,11 +521,19 @@ function ArchiveCard({ )} - {/* Thumbnail */} -
+ {/* Thumbnail with plate navigation */} +
setShowPlateNav(true)} + onMouseLeave={() => setShowPlateNav(false)} + > {archive.thumbnail_path ? ( 0 + ? api.getArchivePlateThumbnail(archive.id, plates[displayPlateIndex]?.index ?? 0) + : api.getArchiveThumbnail(archive.id) + } alt={archive.print_name || archive.filename} className="w-full h-full object-cover" /> @@ -518,6 +542,63 @@ function ArchiveCard({
)} + {/* Plate navigation - only show for multi-plate archives */} + {isMultiPlate && plates.length > 1 && ( + <> + {/* Left arrow */} + + {/* Right arrow */} + + {/* Dots indicator */} +
+ {plates.map((plate, idx) => ( +
+ + )} {/* Context menu button - visible on mobile, shows on hover for desktop */}
diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index 5deb4d8c4..7d1af648b 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -407,6 +407,7 @@ export function SettingsPage() { settings.energy_cost_per_kwh !== localSettings.energy_cost_per_kwh || settings.energy_tracking_mode !== localSettings.energy_tracking_mode || settings.check_updates !== localSettings.check_updates || + (settings.check_printer_firmware ?? true) !== (localSettings.check_printer_firmware ?? true) || settings.notification_language !== localSettings.notification_language || settings.ams_humidity_good !== localSettings.ams_humidity_good || settings.ams_humidity_fair !== localSettings.ams_humidity_fair || @@ -469,6 +470,7 @@ export function SettingsPage() { energy_cost_per_kwh: localSettings.energy_cost_per_kwh, energy_tracking_mode: localSettings.energy_tracking_mode, check_updates: localSettings.check_updates, + check_printer_firmware: localSettings.check_printer_firmware, notification_language: localSettings.notification_language, ams_humidity_good: localSettings.ams_humidity_good, ams_humidity_fair: localSettings.ams_humidity_fair, @@ -1296,6 +1298,23 @@ export function SettingsPage() {
+
+
+

Check printer firmware

+

+ Check for printer firmware updates from Bambu Lab +

+
+ +
diff --git a/static/assets/index-abyjQYWw.js b/static/assets/index-CKmo1TxA.js similarity index 72% rename from static/assets/index-abyjQYWw.js rename to static/assets/index-CKmo1TxA.js index e8d31607f..730ca0279 100644 --- a/static/assets/index-abyjQYWw.js +++ b/static/assets/index-CKmo1TxA.js @@ -1,49 +1,49 @@ -function jae(t,e){for(var n=0;nr[i]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const a of i)if(a.type==="childList")for(const o of a.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(i){const a={};return i.integrity&&(a.integrity=i.integrity),i.referrerPolicy&&(a.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?a.credentials="include":i.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function r(i){if(i.ep)return;i.ep=!0;const a=n(i);fetch(i.href,a)}})();var Jv=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Fo(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var _T={exports:{}},Kx={};var _U;function Pae(){if(_U)return Kx;_U=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,a){var o=null;if(a!==void 0&&(o=""+a),i.key!==void 0&&(o=""+i.key),"key"in i){a={};for(var l in i)l!=="key"&&(a[l]=i[l])}else a=i;return i=a.ref,{$$typeof:t,type:r,key:o,ref:i!==void 0?i:null,props:a}}return Kx.Fragment=e,Kx.jsx=n,Kx.jsxs=n,Kx}var wU;function Rae(){return wU||(wU=1,_T.exports=Pae()),_T.exports}var s=Rae(),wT={exports:{}},xn={};var SU;function Dae(){if(SU)return xn;SU=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),a=Symbol.for("react.consumer"),o=Symbol.for("react.context"),l=Symbol.for("react.forward_ref"),c=Symbol.for("react.suspense"),u=Symbol.for("react.memo"),d=Symbol.for("react.lazy"),f=Symbol.for("react.activity"),m=Symbol.iterator;function p(z){return z===null||typeof z!="object"?null:(z=m&&z[m]||z["@@iterator"],typeof z=="function"?z:null)}var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},y=Object.assign,b={};function v(z,V,se){this.props=z,this.context=V,this.refs=b,this.updater=se||x}v.prototype.isReactComponent={},v.prototype.setState=function(z,V){if(typeof z!="object"&&typeof z!="function"&&z!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,z,V,"setState")},v.prototype.forceUpdate=function(z){this.updater.enqueueForceUpdate(this,z,"forceUpdate")};function M(){}M.prototype=v.prototype;function S(z,V,se){this.props=z,this.context=V,this.refs=b,this.updater=se||x}var A=S.prototype=new M;A.constructor=S,y(A,v.prototype),A.isPureReactComponent=!0;var T=Array.isArray;function C(){}var E={H:null,A:null,T:null,S:null},O=Object.prototype.hasOwnProperty;function k(z,V,se){var oe=se.ref;return{$$typeof:t,type:z,key:V,ref:oe!==void 0?oe:null,props:se}}function R(z,V){return k(z.type,V,z.props)}function F(z){return typeof z=="object"&&z!==null&&z.$$typeof===t}function L(z){var V={"=":"=0",":":"=2"};return"$"+z.replace(/[=:]/g,function(se){return V[se]})}var K=/\/+/g;function j(z,V){return typeof z=="object"&&z!==null&&z.key!=null?L(""+z.key):V.toString(36)}function G(z){switch(z.status){case"fulfilled":return z.value;case"rejected":throw z.reason;default:switch(typeof z.status=="string"?z.then(C,C):(z.status="pending",z.then(function(V){z.status==="pending"&&(z.status="fulfilled",z.value=V)},function(V){z.status==="pending"&&(z.status="rejected",z.reason=V)})),z.status){case"fulfilled":return z.value;case"rejected":throw z.reason}}throw z}function Y(z,V,se,oe,he){var me=typeof z;(me==="undefined"||me==="boolean")&&(z=null);var ue=!1;if(z===null)ue=!0;else switch(me){case"bigint":case"string":case"number":ue=!0;break;case"object":switch(z.$$typeof){case t:case e:ue=!0;break;case d:return ue=z._init,Y(ue(z._payload),V,se,oe,he)}}if(ue)return he=he(z),ue=oe===""?"."+j(z,0):oe,T(he)?(se="",ue!=null&&(se=ue.replace(K,"$&/")+"/"),Y(he,V,se,"",function(Me){return Me})):he!=null&&(F(he)&&(he=R(he,se+(he.key==null||z&&z.key===he.key?"":(""+he.key).replace(K,"$&/")+"/")+ue)),V.push(he)),1;ue=0;var Ee=oe===""?".":oe+":";if(T(z))for(var pe=0;pe>>1,re=Y[ee];if(0>>1;eei(se,Z))oei(he,se)?(Y[ee]=he,Y[oe]=Z,ee=oe):(Y[ee]=se,Y[V]=Z,ee=V);else if(oei(he,Z))Y[ee]=he,Y[oe]=Z,ee=oe;else break e}}return W}function i(Y,W){var Z=Y.sortIndex-W.sortIndex;return Z!==0?Z:Y.id-W.id}if(t.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var a=performance;t.unstable_now=function(){return a.now()}}else{var o=Date,l=o.now();t.unstable_now=function(){return o.now()-l}}var c=[],u=[],d=1,f=null,m=3,p=!1,x=!1,y=!1,b=!1,v=typeof setTimeout=="function"?setTimeout:null,M=typeof clearTimeout=="function"?clearTimeout:null,S=typeof setImmediate<"u"?setImmediate:null;function A(Y){for(var W=n(u);W!==null;){if(W.callback===null)r(u);else if(W.startTime<=Y)r(u),W.sortIndex=W.expirationTime,e(c,W);else break;W=n(u)}}function T(Y){if(y=!1,A(Y),!x)if(n(c)!==null)x=!0,C||(C=!0,L());else{var W=n(u);W!==null&&G(T,W.startTime-Y)}}var C=!1,E=-1,O=5,k=-1;function R(){return b?!0:!(t.unstable_now()-kY&&R());){var ee=f.callback;if(typeof ee=="function"){f.callback=null,m=f.priorityLevel;var re=ee(f.expirationTime<=Y);if(Y=t.unstable_now(),typeof re=="function"){f.callback=re,A(Y),W=!0;break t}f===n(c)&&r(c),A(Y)}else r(c);f=n(c)}if(f!==null)W=!0;else{var z=n(u);z!==null&&G(T,z.startTime-Y),W=!1}}break e}finally{f=null,m=Z,p=!1}W=void 0}}finally{W?L():C=!1}}}var L;if(typeof S=="function")L=function(){S(F)};else if(typeof MessageChannel<"u"){var K=new MessageChannel,j=K.port2;K.port1.onmessage=F,L=function(){j.postMessage(null)}}else L=function(){v(F,0)};function G(Y,W){E=v(function(){Y(t.unstable_now())},W)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(Y){Y.callback=null},t.unstable_forceFrameRate=function(Y){0>Y||125ee?(Y.sortIndex=Z,e(u,Y),n(c)===null&&Y===n(u)&&(y?(M(E),E=-1):y=!0,G(T,Z-ee))):(Y.sortIndex=re,e(c,Y),x||p||(x=!0,C||(C=!0,L()))),Y},t.unstable_shouldYield=R,t.unstable_wrapCallback=function(Y){var W=m;return function(){var Z=m;m=W;try{return Y.apply(this,arguments)}finally{m=Z}}}})(NT)),NT}var EU;function Iae(){return EU||(EU=1,MT.exports=Lae()),MT.exports}var ET={exports:{}},Ma={};var TU;function Fae(){if(TU)return Ma;TU=1;var t=Dg();function e(c){var u="https://react.dev/errors/"+c;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(t)}catch(e){console.error(e)}}return t(),ET.exports=Fae(),ET.exports}var CU;function Uae(){if(CU)return Xx;CU=1;var t=Iae(),e=Dg(),n=aW();function r(h){var g="https://react.dev/errors/"+h;if(1re||(h.current=ee[re],ee[re]=null,re--)}function se(h,g){re++,ee[re]=h.current,h.current=g}var oe=z(null),he=z(null),me=z(null),ue=z(null);function Ee(h,g){switch(se(me,g),se(he,h),se(oe,null),g.nodeType){case 9:case 11:h=(h=g.documentElement)&&(h=h.namespaceURI)?VF(h):0;break;default:if(h=g.tagName,g=g.namespaceURI)g=VF(g),h=GF(g,h);else switch(h){case"svg":h=1;break;case"math":h=2;break;default:h=0}}V(oe),se(oe,h)}function pe(){V(oe),V(he),V(me)}function Me(h){h.memoizedState!==null&&se(ue,h);var g=oe.current,_=GF(g,h.type);g!==_&&(se(he,h),se(oe,_))}function ve(h){he.current===h&&(V(oe),V(he)),ue.current===h&&(V(ue),Vx._currentValue=Z)}var te,ze;function H(h){if(te===void 0)try{throw Error()}catch(_){var g=_.stack.trim().match(/\n( *(at )?)/);te=g&&g[1]||"",ze=-1<_.stack.indexOf(` +function jae(t,e){for(var n=0;nr[i]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const a of i)if(a.type==="childList")for(const o of a.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(i){const a={};return i.integrity&&(a.integrity=i.integrity),i.referrerPolicy&&(a.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?a.credentials="include":i.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function r(i){if(i.ep)return;i.ep=!0;const a=n(i);fetch(i.href,a)}})();var Jv=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Fo(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var _T={exports:{}},Kx={};var _U;function Pae(){if(_U)return Kx;_U=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,a){var o=null;if(a!==void 0&&(o=""+a),i.key!==void 0&&(o=""+i.key),"key"in i){a={};for(var l in i)l!=="key"&&(a[l]=i[l])}else a=i;return i=a.ref,{$$typeof:t,type:r,key:o,ref:i!==void 0?i:null,props:a}}return Kx.Fragment=e,Kx.jsx=n,Kx.jsxs=n,Kx}var wU;function Rae(){return wU||(wU=1,_T.exports=Pae()),_T.exports}var s=Rae(),wT={exports:{}},bn={};var SU;function Dae(){if(SU)return bn;SU=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),a=Symbol.for("react.consumer"),o=Symbol.for("react.context"),l=Symbol.for("react.forward_ref"),c=Symbol.for("react.suspense"),u=Symbol.for("react.memo"),d=Symbol.for("react.lazy"),f=Symbol.for("react.activity"),m=Symbol.iterator;function g(z){return z===null||typeof z!="object"?null:(z=m&&z[m]||z["@@iterator"],typeof z=="function"?z:null)}var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},y=Object.assign,b={};function v(z,G,ae){this.props=z,this.context=G,this.refs=b,this.updater=ae||x}v.prototype.isReactComponent={},v.prototype.setState=function(z,G){if(typeof z!="object"&&typeof z!="function"&&z!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,z,G,"setState")},v.prototype.forceUpdate=function(z){this.updater.enqueueForceUpdate(this,z,"forceUpdate")};function M(){}M.prototype=v.prototype;function S(z,G,ae){this.props=z,this.context=G,this.refs=b,this.updater=ae||x}var A=S.prototype=new M;A.constructor=S,y(A,v.prototype),A.isPureReactComponent=!0;var T=Array.isArray;function C(){}var E={H:null,A:null,T:null,S:null},D=Object.prototype.hasOwnProperty;function k(z,G,ae){var se=ae.ref;return{$$typeof:t,type:z,key:G,ref:se!==void 0?se:null,props:ae}}function R(z,G){return k(z.type,G,z.props)}function F(z){return typeof z=="object"&&z!==null&&z.$$typeof===t}function O(z){var G={"=":"=0",":":"=2"};return"$"+z.replace(/[=:]/g,function(ae){return G[ae]})}var X=/\/+/g;function j(z,G){return typeof z=="object"&&z!==null&&z.key!=null?O(""+z.key):G.toString(36)}function W(z){switch(z.status){case"fulfilled":return z.value;case"rejected":throw z.reason;default:switch(typeof z.status=="string"?z.then(C,C):(z.status="pending",z.then(function(G){z.status==="pending"&&(z.status="fulfilled",z.value=G)},function(G){z.status==="pending"&&(z.status="rejected",z.reason=G)})),z.status){case"fulfilled":return z.value;case"rejected":throw z.reason}}throw z}function Y(z,G,ae,se,de){var me=typeof z;(me==="undefined"||me==="boolean")&&(z=null);var ue=!1;if(z===null)ue=!0;else switch(me){case"bigint":case"string":case"number":ue=!0;break;case"object":switch(z.$$typeof){case t:case e:ue=!0;break;case d:return ue=z._init,Y(ue(z._payload),G,ae,se,de)}}if(ue)return de=de(z),ue=se===""?"."+j(z,0):se,T(de)?(ae="",ue!=null&&(ae=ue.replace(X,"$&/")+"/"),Y(de,G,ae,"",function(Me){return Me})):de!=null&&(F(de)&&(de=R(de,ae+(de.key==null||z&&z.key===de.key?"":(""+de.key).replace(X,"$&/")+"/")+ue)),G.push(de)),1;ue=0;var Ee=se===""?".":se+":";if(T(z))for(var pe=0;pe>>1,ne=Y[ee];if(0>>1;eei(ae,Z))sei(de,ae)?(Y[ee]=de,Y[se]=Z,ee=se):(Y[ee]=ae,Y[G]=Z,ee=G);else if(sei(de,Z))Y[ee]=de,Y[se]=Z,ee=se;else break e}}return q}function i(Y,q){var Z=Y.sortIndex-q.sortIndex;return Z!==0?Z:Y.id-q.id}if(t.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var a=performance;t.unstable_now=function(){return a.now()}}else{var o=Date,l=o.now();t.unstable_now=function(){return o.now()-l}}var c=[],u=[],d=1,f=null,m=3,g=!1,x=!1,y=!1,b=!1,v=typeof setTimeout=="function"?setTimeout:null,M=typeof clearTimeout=="function"?clearTimeout:null,S=typeof setImmediate<"u"?setImmediate:null;function A(Y){for(var q=n(u);q!==null;){if(q.callback===null)r(u);else if(q.startTime<=Y)r(u),q.sortIndex=q.expirationTime,e(c,q);else break;q=n(u)}}function T(Y){if(y=!1,A(Y),!x)if(n(c)!==null)x=!0,C||(C=!0,O());else{var q=n(u);q!==null&&W(T,q.startTime-Y)}}var C=!1,E=-1,D=5,k=-1;function R(){return b?!0:!(t.unstable_now()-kY&&R());){var ee=f.callback;if(typeof ee=="function"){f.callback=null,m=f.priorityLevel;var ne=ee(f.expirationTime<=Y);if(Y=t.unstable_now(),typeof ne=="function"){f.callback=ne,A(Y),q=!0;break t}f===n(c)&&r(c),A(Y)}else r(c);f=n(c)}if(f!==null)q=!0;else{var z=n(u);z!==null&&W(T,z.startTime-Y),q=!1}}break e}finally{f=null,m=Z,g=!1}q=void 0}}finally{q?O():C=!1}}}var O;if(typeof S=="function")O=function(){S(F)};else if(typeof MessageChannel<"u"){var X=new MessageChannel,j=X.port2;X.port1.onmessage=F,O=function(){j.postMessage(null)}}else O=function(){v(F,0)};function W(Y,q){E=v(function(){Y(t.unstable_now())},q)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(Y){Y.callback=null},t.unstable_forceFrameRate=function(Y){0>Y||125ee?(Y.sortIndex=Z,e(u,Y),n(c)===null&&Y===n(u)&&(y?(M(E),E=-1):y=!0,W(T,Z-ee))):(Y.sortIndex=ne,e(c,Y),x||g||(x=!0,C||(C=!0,O()))),Y},t.unstable_shouldYield=R,t.unstable_wrapCallback=function(Y){var q=m;return function(){var Z=m;m=q;try{return Y.apply(this,arguments)}finally{m=Z}}}})(NT)),NT}var EU;function Iae(){return EU||(EU=1,MT.exports=Lae()),MT.exports}var ET={exports:{}},Ma={};var TU;function Fae(){if(TU)return Ma;TU=1;var t=Dg();function e(c){var u="https://react.dev/errors/"+c;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(t)}catch(e){console.error(e)}}return t(),ET.exports=Fae(),ET.exports}var CU;function Uae(){if(CU)return Xx;CU=1;var t=Iae(),e=Dg(),n=aW();function r(h){var p="https://react.dev/errors/"+h;if(1ne||(h.current=ee[ne],ee[ne]=null,ne--)}function ae(h,p){ne++,ee[ne]=h.current,h.current=p}var se=z(null),de=z(null),me=z(null),ue=z(null);function Ee(h,p){switch(ae(me,p),ae(de,h),ae(se,null),p.nodeType){case 9:case 11:h=(h=p.documentElement)&&(h=h.namespaceURI)?VF(h):0;break;default:if(h=p.tagName,p=p.namespaceURI)p=VF(p),h=GF(p,h);else switch(h){case"svg":h=1;break;case"math":h=2;break;default:h=0}}G(se),ae(se,h)}function pe(){G(se),G(de),G(me)}function Me(h){h.memoizedState!==null&&ae(ue,h);var p=se.current,_=GF(p,h.type);p!==_&&(ae(de,h),ae(se,_))}function ve(h){de.current===h&&(G(se),G(de)),ue.current===h&&(G(ue),Vx._currentValue=Z)}var oe,Oe;function V(h){if(oe===void 0)try{throw Error()}catch(_){var p=_.stack.trim().match(/\n( *(at )?)/);oe=p&&p[1]||"",Oe=-1<_.stack.indexOf(` at`)?" ()":-1<_.stack.indexOf("@")?"@unknown:0:0":""}return` -`+te+h+ze}var $=!1;function q(h,g){if(!h||$)return"";$=!0;var _=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var N={DetermineComponentFrameRoot:function(){try{if(g){var _t=function(){throw Error()};if(Object.defineProperty(_t.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(_t,[])}catch(ft){var st=ft}Reflect.construct(h,[],_t)}else{try{_t.call()}catch(ft){st=ft}h.call(_t.prototype)}}else{try{throw Error()}catch(ft){st=ft}(_t=h())&&typeof _t.catch=="function"&&_t.catch(function(){})}}catch(ft){if(ft&&st&&typeof ft.stack=="string")return[ft.stack,st.stack]}return[null,null]}};N.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var P=Object.getOwnPropertyDescriptor(N.DetermineComponentFrameRoot,"name");P&&P.configurable&&Object.defineProperty(N.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var I=N.DetermineComponentFrameRoot(),J=I[0],ge=I[1];if(J&&ge){var Le=J.split(` +`+oe+h+Oe}var H=!1;function K(h,p){if(!h||H)return"";H=!0;var _=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var N={DetermineComponentFrameRoot:function(){try{if(p){var _t=function(){throw Error()};if(Object.defineProperty(_t.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(_t,[])}catch(dt){var st=dt}Reflect.construct(h,[],_t)}else{try{_t.call()}catch(dt){st=dt}h.call(_t.prototype)}}else{try{throw Error()}catch(dt){st=dt}(_t=h())&&typeof _t.catch=="function"&&_t.catch(function(){})}}catch(dt){if(dt&&st&&typeof dt.stack=="string")return[dt.stack,st.stack]}return[null,null]}};N.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var P=Object.getOwnPropertyDescriptor(N.DetermineComponentFrameRoot,"name");P&&P.configurable&&Object.defineProperty(N.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var L=N.DetermineComponentFrameRoot(),J=L[0],ge=L[1];if(J&&ge){var Le=J.split(` `),rt=ge.split(` -`);for(P=N=0;NP||Le[N]!==rt[P]){var yt=` -`+Le[N].replace(" at new "," at ");return h.displayName&&yt.includes("")&&(yt=yt.replace("",h.displayName)),yt}while(1<=N&&0<=P);break}}}finally{$=!1,Error.prepareStackTrace=_}return(_=h?h.displayName||h.name:"")?H(_):""}function U(h,g){switch(h.tag){case 26:case 27:case 5:return H(h.type);case 16:return H("Lazy");case 13:return h.child!==g&&g!==null?H("Suspense Fallback"):H("Suspense");case 19:return H("SuspenseList");case 0:case 15:return q(h.type,!1);case 11:return q(h.type.render,!1);case 1:return q(h.type,!0);case 31:return H("Activity");default:return""}}function X(h){try{var g="",_=null;do g+=U(h,_),_=h,h=h.return;while(h);return g}catch(N){return` +`);for(P=N=0;NP||Le[N]!==rt[P]){var bt=` +`+Le[N].replace(" at new "," at ");return h.displayName&&bt.includes("")&&(bt=bt.replace("",h.displayName)),bt}while(1<=N&&0<=P);break}}}finally{H=!1,Error.prepareStackTrace=_}return(_=h?h.displayName||h.name:"")?V(_):""}function U(h,p){switch(h.tag){case 26:case 27:case 5:return V(h.type);case 16:return V("Lazy");case 13:return h.child!==p&&p!==null?V("Suspense Fallback"):V("Suspense");case 19:return V("SuspenseList");case 0:case 15:return K(h.type,!1);case 11:return K(h.type.render,!1);case 1:return K(h.type,!0);case 31:return V("Activity");default:return""}}function Q(h){try{var p="",_=null;do p+=U(h,_),_=h,h=h.return;while(h);return p}catch(N){return` Error generating stack: `+N.message+` -`+N.stack}}var ce=Object.prototype.hasOwnProperty,Ce=t.unstable_scheduleCallback,je=t.unstable_cancelCallback,Te=t.unstable_shouldYield,B=t.unstable_requestPaint,D=t.unstable_now,Q=t.unstable_getCurrentPriorityLevel,ne=t.unstable_ImmediatePriority,ye=t.unstable_UserBlockingPriority,xe=t.unstable_NormalPriority,Qe=t.unstable_LowPriority,Fe=t.unstable_IdlePriority,Ze=t.log,Xe=t.unstable_setDisableYieldValue,qe=null,le=null;function ae(h){if(typeof Ze=="function"&&Xe(h),le&&typeof le.setStrictMode=="function")try{le.setStrictMode(qe,h)}catch{}}var Ne=Math.clz32?Math.clz32:ie,Be=Math.log,it=Math.LN2;function ie(h){return h>>>=0,h===0?32:31-(Be(h)/it|0)|0}var Pe=256,be=262144,Oe=4194304;function Ue(h){var g=h&42;if(g!==0)return g;switch(h&-h){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return h&261888;case 262144:case 524288:case 1048576:case 2097152:return h&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return h&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return h}}function Ae(h,g,_){var N=h.pendingLanes;if(N===0)return 0;var P=0,I=h.suspendedLanes,J=h.pingedLanes;h=h.warmLanes;var ge=N&134217727;return ge!==0?(N=ge&~I,N!==0?P=Ue(N):(J&=ge,J!==0?P=Ue(J):_||(_=ge&~h,_!==0&&(P=Ue(_))))):(ge=N&~I,ge!==0?P=Ue(ge):J!==0?P=Ue(J):_||(_=N&~h,_!==0&&(P=Ue(_)))),P===0?0:g!==0&&g!==P&&(g&I)===0&&(I=P&-P,_=g&-g,I>=_||I===32&&(_&4194048)!==0)?g:P}function Je(h,g){return(h.pendingLanes&~(h.suspendedLanes&~h.pingedLanes)&g)===0}function gt(h,g){switch(h){case 1:case 2:case 4:case 8:case 64:return g+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return g+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Pt(){var h=Oe;return Oe<<=1,(Oe&62914560)===0&&(Oe=4194304),h}function Ot(h){for(var g=[],_=0;31>_;_++)g.push(h);return g}function cn(h,g){h.pendingLanes|=g,g!==268435456&&(h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0)}function It(h,g,_,N,P,I){var J=h.pendingLanes;h.pendingLanes=_,h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0,h.expiredLanes&=_,h.entangledLanes&=_,h.errorRecoveryDisabledLanes&=_,h.shellSuspendCounter=0;var ge=h.entanglements,Le=h.expirationTimes,rt=h.hiddenUpdates;for(_=J&~_;0<_;){var yt=31-Ne(_),_t=1<"u")return null;try{return h.activeElement||h.body}catch{return h.body}}var Yt=/[\n"\\]/g;function rn(h){return h.replace(Yt,function(g){return"\\"+g.charCodeAt(0).toString(16)+" "})}function sr(h,g,_,N,P,I,J,ge){h.name="",J!=null&&typeof J!="function"&&typeof J!="symbol"&&typeof J!="boolean"?h.type=J:h.removeAttribute("type"),g!=null?J==="number"?(g===0&&h.value===""||h.value!=g)&&(h.value=""+_e(g)):h.value!==""+_e(g)&&(h.value=""+_e(g)):J!=="submit"&&J!=="reset"||h.removeAttribute("value"),g!=null?Wt(h,J,_e(g)):_!=null?Wt(h,J,_e(_)):N!=null&&h.removeAttribute("value"),P==null&&I!=null&&(h.defaultChecked=!!I),P!=null&&(h.checked=P&&typeof P!="function"&&typeof P!="symbol"),ge!=null&&typeof ge!="function"&&typeof ge!="symbol"&&typeof ge!="boolean"?h.name=""+_e(ge):h.removeAttribute("name")}function kn(h,g,_,N,P,I,J,ge){if(I!=null&&typeof I!="function"&&typeof I!="symbol"&&typeof I!="boolean"&&(h.type=I),g!=null||_!=null){if(!(I!=="submit"&&I!=="reset"||g!=null)){Nt(h);return}_=_!=null?""+_e(_):"",g=g!=null?""+_e(g):_,ge||g===h.value||(h.value=g),h.defaultValue=g}N=N??P,N=typeof N!="function"&&typeof N!="symbol"&&!!N,h.checked=ge?h.checked:!!N,h.defaultChecked=!!N,J!=null&&typeof J!="function"&&typeof J!="symbol"&&typeof J!="boolean"&&(h.name=J),Nt(h)}function Wt(h,g,_){g==="number"&&Xt(h.ownerDocument)===h||h.defaultValue===""+_||(h.defaultValue=""+_)}function en(h,g,_,N){if(h=h.options,g){g={};for(var P=0;P<_.length;P++)g["$"+_[P]]=!0;for(_=0;_"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),_N=!1;if(rc)try{var lx={};Object.defineProperty(lx,"passive",{get:function(){_N=!0}}),window.addEventListener("test",lx,lx),window.removeEventListener("test",lx,lx)}catch{_N=!1}var wu=null,wN=null,Gy=null;function GL(){if(Gy)return Gy;var h,g=wN,_=g.length,N,P="value"in wu?wu.value:wu.textContent,I=P.length;for(h=0;h<_&&g[h]===P[h];h++);var J=_-h;for(N=1;N<=J&&g[_-N]===P[I-N];N++);return Gy=P.slice(h,1=dx),QL=" ",ZL=!1;function JL(h,g){switch(h){case"keyup":return Jre.indexOf(g.keyCode)!==-1;case"keydown":return g.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function eI(h){return h=h.detail,typeof h=="object"&&"data"in h?h.data:null}var lm=!1;function tie(h,g){switch(h){case"compositionend":return eI(g);case"keypress":return g.which!==32?null:(ZL=!0,QL);case"textInput":return h=g.data,h===QL&&ZL?null:h;default:return null}}function nie(h,g){if(lm)return h==="compositionend"||!TN&&JL(h,g)?(h=GL(),Gy=wN=wu=null,lm=!1,h):null;switch(h){case"paste":return null;case"keypress":if(!(g.ctrlKey||g.altKey||g.metaKey)||g.ctrlKey&&g.altKey){if(g.char&&1=g)return{node:_,offset:g-h};h=N}e:{for(;_;){if(_.nextSibling){_=_.nextSibling;break e}_=_.parentNode}_=void 0}_=lI(_)}}function uI(h,g){return h&&g?h===g?!0:h&&h.nodeType===3?!1:g&&g.nodeType===3?uI(h,g.parentNode):"contains"in h?h.contains(g):h.compareDocumentPosition?!!(h.compareDocumentPosition(g)&16):!1:!1}function dI(h){h=h!=null&&h.ownerDocument!=null&&h.ownerDocument.defaultView!=null?h.ownerDocument.defaultView:window;for(var g=Xt(h.document);g instanceof h.HTMLIFrameElement;){try{var _=typeof g.contentWindow.location.href=="string"}catch{_=!1}if(_)h=g.contentWindow;else break;g=Xt(h.document)}return g}function kN(h){var g=h&&h.nodeName&&h.nodeName.toLowerCase();return g&&(g==="input"&&(h.type==="text"||h.type==="search"||h.type==="tel"||h.type==="url"||h.type==="password")||g==="textarea"||h.contentEditable==="true")}var uie=rc&&"documentMode"in document&&11>=document.documentMode,cm=null,jN=null,px=null,PN=!1;function hI(h,g,_){var N=_.window===_?_.document:_.nodeType===9?_:_.ownerDocument;PN||cm==null||cm!==Xt(N)||(N=cm,"selectionStart"in N&&kN(N)?N={start:N.selectionStart,end:N.selectionEnd}:(N=(N.ownerDocument&&N.ownerDocument.defaultView||window).getSelection(),N={anchorNode:N.anchorNode,anchorOffset:N.anchorOffset,focusNode:N.focusNode,focusOffset:N.focusOffset}),px&&mx(px,N)||(px=N,N=Fv(jN,"onSelect"),0>=J,P-=J,gl=1<<32-Ne(g)+P|_<yn?(In=qt,qt=null):In=qt.sibling;var Wn=st(Ye,qt,tt[yn],vt);if(Wn===null){qt===null&&(qt=In);break}h&&qt&&Wn.alternate===null&&g(Ye,qt),Ve=I(Wn,Ve,yn),Gn===null?Zt=Wn:Gn.sibling=Wn,Gn=Wn,qt=In}if(yn===tt.length)return _(Ye,qt),Un&&ac(Ye,yn),Zt;if(qt===null){for(;ynyn?(In=qt,qt=null):In=qt.sibling;var Vu=st(Ye,qt,Wn.value,vt);if(Vu===null){qt===null&&(qt=In);break}h&&qt&&Vu.alternate===null&&g(Ye,qt),Ve=I(Vu,Ve,yn),Gn===null?Zt=Vu:Gn.sibling=Vu,Gn=Vu,qt=In}if(Wn.done)return _(Ye,qt),Un&&ac(Ye,yn),Zt;if(qt===null){for(;!Wn.done;yn++,Wn=tt.next())Wn=_t(Ye,Wn.value,vt),Wn!==null&&(Ve=I(Wn,Ve,yn),Gn===null?Zt=Wn:Gn.sibling=Wn,Gn=Wn);return Un&&ac(Ye,yn),Zt}for(qt=N(qt);!Wn.done;yn++,Wn=tt.next())Wn=ft(qt,Ye,yn,Wn.value,vt),Wn!==null&&(h&&Wn.alternate!==null&&qt.delete(Wn.key===null?yn:Wn.key),Ve=I(Wn,Ve,yn),Gn===null?Zt=Wn:Gn.sibling=Wn,Gn=Wn);return h&&qt.forEach(function(kae){return g(Ye,kae)}),Un&&ac(Ye,yn),Zt}function ur(Ye,Ve,tt,vt){if(typeof tt=="object"&&tt!==null&&tt.type===y&&tt.key===null&&(tt=tt.props.children),typeof tt=="object"&&tt!==null){switch(tt.$$typeof){case p:e:{for(var Zt=tt.key;Ve!==null;){if(Ve.key===Zt){if(Zt=tt.type,Zt===y){if(Ve.tag===7){_(Ye,Ve.sibling),vt=P(Ve,tt.props.children),vt.return=Ye,Ye=vt;break e}}else if(Ve.elementType===Zt||typeof Zt=="object"&&Zt!==null&&Zt.$$typeof===O&&_h(Zt)===Ve.type){_(Ye,Ve.sibling),vt=P(Ve,tt.props),_x(vt,tt),vt.return=Ye,Ye=vt;break e}_(Ye,Ve);break}else g(Ye,Ve);Ve=Ve.sibling}tt.type===y?(vt=gh(tt.props.children,Ye.mode,vt,tt.key),vt.return=Ye,Ye=vt):(vt=tv(tt.type,tt.key,tt.props,null,Ye.mode,vt),_x(vt,tt),vt.return=Ye,Ye=vt)}return J(Ye);case x:e:{for(Zt=tt.key;Ve!==null;){if(Ve.key===Zt)if(Ve.tag===4&&Ve.stateNode.containerInfo===tt.containerInfo&&Ve.stateNode.implementation===tt.implementation){_(Ye,Ve.sibling),vt=P(Ve,tt.children||[]),vt.return=Ye,Ye=vt;break e}else{_(Ye,Ve);break}else g(Ye,Ve);Ve=Ve.sibling}vt=UN(tt,Ye.mode,vt),vt.return=Ye,Ye=vt}return J(Ye);case O:return tt=_h(tt),ur(Ye,Ve,tt,vt)}if(G(tt))return Vt(Ye,Ve,tt,vt);if(L(tt)){if(Zt=L(tt),typeof Zt!="function")throw Error(r(150));return tt=Zt.call(tt),an(Ye,Ve,tt,vt)}if(typeof tt.then=="function")return ur(Ye,Ve,lv(tt),vt);if(tt.$$typeof===S)return ur(Ye,Ve,iv(Ye,tt),vt);cv(Ye,tt)}return typeof tt=="string"&&tt!==""||typeof tt=="number"||typeof tt=="bigint"?(tt=""+tt,Ve!==null&&Ve.tag===6?(_(Ye,Ve.sibling),vt=P(Ve,tt),vt.return=Ye,Ye=vt):(_(Ye,Ve),vt=FN(tt,Ye.mode,vt),vt.return=Ye,Ye=vt),J(Ye)):_(Ye,Ve)}return function(Ye,Ve,tt,vt){try{vx=0;var Zt=ur(Ye,Ve,tt,vt);return vm=null,Zt}catch(qt){if(qt===ym||qt===sv)throw qt;var Gn=Cs(29,qt,null,Ye.mode);return Gn.lanes=vt,Gn.return=Ye,Gn}finally{}}}var Sh=OI(!0),LI=OI(!1),Tu=!1;function QN(h){h.updateQueue={baseState:h.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function ZN(h,g){h=h.updateQueue,g.updateQueue===h&&(g.updateQueue={baseState:h.baseState,firstBaseUpdate:h.firstBaseUpdate,lastBaseUpdate:h.lastBaseUpdate,shared:h.shared,callbacks:null})}function Au(h){return{lane:h,tag:0,payload:null,callback:null,next:null}}function Cu(h,g,_){var N=h.updateQueue;if(N===null)return null;if(N=N.shared,(Yn&2)!==0){var P=N.pending;return P===null?g.next=g:(g.next=P.next,P.next=g),N.pending=g,g=ev(h),yI(h,null,_),g}return Jy(h,N,g,_),ev(h)}function wx(h,g,_){if(g=g.updateQueue,g!==null&&(g=g.shared,(_&4194048)!==0)){var N=g.lanes;N&=h.pendingLanes,_|=N,g.lanes=_,bt(h,_)}}function JN(h,g){var _=h.updateQueue,N=h.alternate;if(N!==null&&(N=N.updateQueue,_===N)){var P=null,I=null;if(_=_.firstBaseUpdate,_!==null){do{var J={lane:_.lane,tag:_.tag,payload:_.payload,callback:null,next:null};I===null?P=I=J:I=I.next=J,_=_.next}while(_!==null);I===null?P=I=g:I=I.next=g}else P=I=g;_={baseState:N.baseState,firstBaseUpdate:P,lastBaseUpdate:I,shared:N.shared,callbacks:N.callbacks},h.updateQueue=_;return}h=_.lastBaseUpdate,h===null?_.firstBaseUpdate=g:h.next=g,_.lastBaseUpdate=g}var eE=!1;function Sx(){if(eE){var h=bm;if(h!==null)throw h}}function Mx(h,g,_,N){eE=!1;var P=h.updateQueue;Tu=!1;var I=P.firstBaseUpdate,J=P.lastBaseUpdate,ge=P.shared.pending;if(ge!==null){P.shared.pending=null;var Le=ge,rt=Le.next;Le.next=null,J===null?I=rt:J.next=rt,J=Le;var yt=h.alternate;yt!==null&&(yt=yt.updateQueue,ge=yt.lastBaseUpdate,ge!==J&&(ge===null?yt.firstBaseUpdate=rt:ge.next=rt,yt.lastBaseUpdate=Le))}if(I!==null){var _t=P.baseState;J=0,yt=rt=Le=null,ge=I;do{var st=ge.lane&-536870913,ft=st!==ge.lane;if(ft?(Ln&st)===st:(N&st)===st){st!==0&&st===xm&&(eE=!0),yt!==null&&(yt=yt.next={lane:0,tag:ge.tag,payload:ge.payload,callback:null,next:null});e:{var Vt=h,an=ge;st=g;var ur=_;switch(an.tag){case 1:if(Vt=an.payload,typeof Vt=="function"){_t=Vt.call(ur,_t,st);break e}_t=Vt;break e;case 3:Vt.flags=Vt.flags&-65537|128;case 0:if(Vt=an.payload,st=typeof Vt=="function"?Vt.call(ur,_t,st):Vt,st==null)break e;_t=f({},_t,st);break e;case 2:Tu=!0}}st=ge.callback,st!==null&&(h.flags|=64,ft&&(h.flags|=8192),ft=P.callbacks,ft===null?P.callbacks=[st]:ft.push(st))}else ft={lane:st,tag:ge.tag,payload:ge.payload,callback:ge.callback,next:null},yt===null?(rt=yt=ft,Le=_t):yt=yt.next=ft,J|=st;if(ge=ge.next,ge===null){if(ge=P.shared.pending,ge===null)break;ft=ge,ge=ft.next,ft.next=null,P.lastBaseUpdate=ft,P.shared.pending=null}}while(!0);yt===null&&(Le=_t),P.baseState=Le,P.firstBaseUpdate=rt,P.lastBaseUpdate=yt,I===null&&(P.shared.lanes=0),Du|=J,h.lanes=J,h.memoizedState=_t}}function II(h,g){if(typeof h!="function")throw Error(r(191,h));h.call(g)}function FI(h,g){var _=h.callbacks;if(_!==null)for(h.callbacks=null,h=0;h<_.length;h++)II(_[h],g)}var _m=z(null),uv=z(0);function UI(h,g){h=gc,se(uv,h),se(_m,g),gc=h|g.baseLanes}function tE(){se(uv,gc),se(_m,_m.current)}function nE(){gc=uv.current,V(_m),V(uv)}var ks=z(null),ao=null;function ku(h){var g=h.alternate;se(oi,oi.current&1),se(ks,h),ao===null&&(g===null||_m.current!==null||g.memoizedState!==null)&&(ao=h)}function rE(h){se(oi,oi.current),se(ks,h),ao===null&&(ao=h)}function zI(h){h.tag===22?(se(oi,oi.current),se(ks,h),ao===null&&(ao=h)):ju()}function ju(){se(oi,oi.current),se(ks,ks.current)}function js(h){V(ks),ao===h&&(ao=null),V(oi)}var oi=z(0);function dv(h){for(var g=h;g!==null;){if(g.tag===13){var _=g.memoizedState;if(_!==null&&(_=_.dehydrated,_===null||cT(_)||uT(_)))return g}else if(g.tag===19&&(g.memoizedProps.revealOrder==="forwards"||g.memoizedProps.revealOrder==="backwards"||g.memoizedProps.revealOrder==="unstable_legacy-backwards"||g.memoizedProps.revealOrder==="together")){if((g.flags&128)!==0)return g}else if(g.child!==null){g.child.return=g,g=g.child;continue}if(g===h)break;for(;g.sibling===null;){if(g.return===null||g.return===h)return null;g=g.return}g.sibling.return=g.return,g=g.sibling}return null}var lc=0,bn=null,lr=null,mi=null,hv=!1,wm=!1,Mh=!1,fv=0,Nx=0,Sm=null,vie=0;function Yr(){throw Error(r(321))}function iE(h,g){if(g===null)return!1;for(var _=0;_I?I:8;var J=Y.T,ge={};Y.T=ge,yE(h,!1,g,_);try{var Le=P(),rt=Y.S;if(rt!==null&&rt(ge,Le),Le!==null&&typeof Le=="object"&&typeof Le.then=="function"){var yt=yie(Le,N);Tx(h,g,yt,Ds(h))}else Tx(h,g,N,Ds(h))}catch(_t){Tx(h,g,{then:function(){},status:"rejected",reason:_t},Ds())}finally{W.p=I,J!==null&&ge.types!==null&&(J.types=ge.types),Y.T=J}}function Nie(){}function xE(h,g,_,N){if(h.tag!==5)throw Error(r(476));var P=g5(h).queue;p5(h,P,g,Z,_===null?Nie:function(){return x5(h),_(N)})}function g5(h){var g=h.memoizedState;if(g!==null)return g;g={memoizedState:Z,baseState:Z,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:cc,lastRenderedState:Z},next:null};var _={};return g.next={memoizedState:_,baseState:_,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:cc,lastRenderedState:_},next:null},h.memoizedState=g,h=h.alternate,h!==null&&(h.memoizedState=g),g}function x5(h){var g=g5(h);g.next===null&&(g=h.alternate.memoizedState),Tx(h,g.next.queue,{},Ds())}function bE(){return la(Vx)}function b5(){return li().memoizedState}function y5(){return li().memoizedState}function Eie(h){for(var g=h.return;g!==null;){switch(g.tag){case 24:case 3:var _=Ds();h=Au(_);var N=Cu(g,h,_);N!==null&&(cs(N,g,_),wx(N,g,_)),g={cache:qN()},h.payload=g;return}g=g.return}}function Tie(h,g,_){var N=Ds();_={lane:N,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null},yv(h)?_5(g,_):(_=LN(h,g,_,N),_!==null&&(cs(_,h,N),w5(_,g,N)))}function v5(h,g,_){var N=Ds();Tx(h,g,_,N)}function Tx(h,g,_,N){var P={lane:N,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null};if(yv(h))_5(g,P);else{var I=h.alternate;if(h.lanes===0&&(I===null||I.lanes===0)&&(I=g.lastRenderedReducer,I!==null))try{var J=g.lastRenderedState,ge=I(J,_);if(P.hasEagerState=!0,P.eagerState=ge,As(ge,J))return Jy(h,g,P,0),yr===null&&Zy(),!1}catch{}finally{}if(_=LN(h,g,P,N),_!==null)return cs(_,h,N),w5(_,g,N),!0}return!1}function yE(h,g,_,N){if(N={lane:2,revertLane:QE(),gesture:null,action:N,hasEagerState:!1,eagerState:null,next:null},yv(h)){if(g)throw Error(r(479))}else g=LN(h,_,N,2),g!==null&&cs(g,h,2)}function yv(h){var g=h.alternate;return h===bn||g!==null&&g===bn}function _5(h,g){wm=hv=!0;var _=h.pending;_===null?g.next=g:(g.next=_.next,_.next=g),h.pending=g}function w5(h,g,_){if((_&4194048)!==0){var N=g.lanes;N&=h.pendingLanes,_|=N,g.lanes=_,bt(h,_)}}var Ax={readContext:la,use:pv,useCallback:Yr,useContext:Yr,useEffect:Yr,useImperativeHandle:Yr,useLayoutEffect:Yr,useInsertionEffect:Yr,useMemo:Yr,useReducer:Yr,useRef:Yr,useState:Yr,useDebugValue:Yr,useDeferredValue:Yr,useTransition:Yr,useSyncExternalStore:Yr,useId:Yr,useHostTransitionStatus:Yr,useFormState:Yr,useActionState:Yr,useOptimistic:Yr,useMemoCache:Yr,useCacheRefresh:Yr};Ax.useEffectEvent=Yr;var S5={readContext:la,use:pv,useCallback:function(h,g){return za().memoizedState=[h,g===void 0?null:g],h},useContext:la,useEffect:s5,useImperativeHandle:function(h,g,_){_=_!=null?_.concat([h]):null,xv(4194308,4,u5.bind(null,g,h),_)},useLayoutEffect:function(h,g){return xv(4194308,4,h,g)},useInsertionEffect:function(h,g){xv(4,2,h,g)},useMemo:function(h,g){var _=za();g=g===void 0?null:g;var N=h();if(Mh){ae(!0);try{h()}finally{ae(!1)}}return _.memoizedState=[N,g],N},useReducer:function(h,g,_){var N=za();if(_!==void 0){var P=_(g);if(Mh){ae(!0);try{_(g)}finally{ae(!1)}}}else P=g;return N.memoizedState=N.baseState=P,h={pending:null,lanes:0,dispatch:null,lastRenderedReducer:h,lastRenderedState:P},N.queue=h,h=h.dispatch=Tie.bind(null,bn,h),[N.memoizedState,h]},useRef:function(h){var g=za();return h={current:h},g.memoizedState=h},useState:function(h){h=hE(h);var g=h.queue,_=v5.bind(null,bn,g);return g.dispatch=_,[h.memoizedState,_]},useDebugValue:pE,useDeferredValue:function(h,g){var _=za();return gE(_,h,g)},useTransition:function(){var h=hE(!1);return h=p5.bind(null,bn,h.queue,!0,!1),za().memoizedState=h,[!1,h]},useSyncExternalStore:function(h,g,_){var N=bn,P=za();if(Un){if(_===void 0)throw Error(r(407));_=_()}else{if(_=g(),yr===null)throw Error(r(349));(Ln&127)!==0||VI(N,g,_)}P.memoizedState=_;var I={value:_,getSnapshot:g};return P.queue=I,s5(WI.bind(null,N,I,h),[h]),N.flags|=2048,Mm(9,{destroy:void 0},GI.bind(null,N,I,_,g),null),_},useId:function(){var h=za(),g=yr.identifierPrefix;if(Un){var _=xl,N=gl;_=(N&~(1<<32-Ne(N)-1)).toString(32)+_,g="_"+g+"R_"+_,_=fv++,0<_&&(g+="H"+_.toString(32)),g+="_"}else _=vie++,g="_"+g+"r_"+_.toString(32)+"_";return h.memoizedState=g},useHostTransitionStatus:bE,useFormState:t5,useActionState:t5,useOptimistic:function(h){var g=za();g.memoizedState=g.baseState=h;var _={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return g.queue=_,g=yE.bind(null,bn,!0,_),_.dispatch=g,[h,g]},useMemoCache:cE,useCacheRefresh:function(){return za().memoizedState=Eie.bind(null,bn)},useEffectEvent:function(h){var g=za(),_={impl:h};return g.memoizedState=_,function(){if((Yn&2)!==0)throw Error(r(440));return _.impl.apply(void 0,arguments)}}},vE={readContext:la,use:pv,useCallback:h5,useContext:la,useEffect:mE,useImperativeHandle:d5,useInsertionEffect:l5,useLayoutEffect:c5,useMemo:f5,useReducer:gv,useRef:a5,useState:function(){return gv(cc)},useDebugValue:pE,useDeferredValue:function(h,g){var _=li();return m5(_,lr.memoizedState,h,g)},useTransition:function(){var h=gv(cc)[0],g=li().memoizedState;return[typeof h=="boolean"?h:Ex(h),g]},useSyncExternalStore:HI,useId:b5,useHostTransitionStatus:bE,useFormState:n5,useActionState:n5,useOptimistic:function(h,g){var _=li();return XI(_,lr,h,g)},useMemoCache:cE,useCacheRefresh:y5};vE.useEffectEvent=o5;var M5={readContext:la,use:pv,useCallback:h5,useContext:la,useEffect:mE,useImperativeHandle:d5,useInsertionEffect:l5,useLayoutEffect:c5,useMemo:f5,useReducer:dE,useRef:a5,useState:function(){return dE(cc)},useDebugValue:pE,useDeferredValue:function(h,g){var _=li();return lr===null?gE(_,h,g):m5(_,lr.memoizedState,h,g)},useTransition:function(){var h=dE(cc)[0],g=li().memoizedState;return[typeof h=="boolean"?h:Ex(h),g]},useSyncExternalStore:HI,useId:b5,useHostTransitionStatus:bE,useFormState:i5,useActionState:i5,useOptimistic:function(h,g){var _=li();return lr!==null?XI(_,lr,h,g):(_.baseState=h,[h,_.queue.dispatch])},useMemoCache:cE,useCacheRefresh:y5};M5.useEffectEvent=o5;function _E(h,g,_,N){g=h.memoizedState,_=_(N,g),_=_==null?g:f({},g,_),h.memoizedState=_,h.lanes===0&&(h.updateQueue.baseState=_)}var wE={enqueueSetState:function(h,g,_){h=h._reactInternals;var N=Ds(),P=Au(N);P.payload=g,_!=null&&(P.callback=_),g=Cu(h,P,N),g!==null&&(cs(g,h,N),wx(g,h,N))},enqueueReplaceState:function(h,g,_){h=h._reactInternals;var N=Ds(),P=Au(N);P.tag=1,P.payload=g,_!=null&&(P.callback=_),g=Cu(h,P,N),g!==null&&(cs(g,h,N),wx(g,h,N))},enqueueForceUpdate:function(h,g){h=h._reactInternals;var _=Ds(),N=Au(_);N.tag=2,g!=null&&(N.callback=g),g=Cu(h,N,_),g!==null&&(cs(g,h,_),wx(g,h,_))}};function N5(h,g,_,N,P,I,J){return h=h.stateNode,typeof h.shouldComponentUpdate=="function"?h.shouldComponentUpdate(N,I,J):g.prototype&&g.prototype.isPureReactComponent?!mx(_,N)||!mx(P,I):!0}function E5(h,g,_,N){h=g.state,typeof g.componentWillReceiveProps=="function"&&g.componentWillReceiveProps(_,N),typeof g.UNSAFE_componentWillReceiveProps=="function"&&g.UNSAFE_componentWillReceiveProps(_,N),g.state!==h&&wE.enqueueReplaceState(g,g.state,null)}function Nh(h,g){var _=g;if("ref"in g){_={};for(var N in g)N!=="ref"&&(_[N]=g[N])}if(h=h.defaultProps){_===g&&(_=f({},_));for(var P in h)_[P]===void 0&&(_[P]=h[P])}return _}function T5(h){Qy(h)}function A5(h){console.error(h)}function C5(h){Qy(h)}function vv(h,g){try{var _=h.onUncaughtError;_(g.value,{componentStack:g.stack})}catch(N){setTimeout(function(){throw N})}}function k5(h,g,_){try{var N=h.onCaughtError;N(_.value,{componentStack:_.stack,errorBoundary:g.tag===1?g.stateNode:null})}catch(P){setTimeout(function(){throw P})}}function SE(h,g,_){return _=Au(_),_.tag=3,_.payload={element:null},_.callback=function(){vv(h,g)},_}function j5(h){return h=Au(h),h.tag=3,h}function P5(h,g,_,N){var P=_.type.getDerivedStateFromError;if(typeof P=="function"){var I=N.value;h.payload=function(){return P(I)},h.callback=function(){k5(g,_,N)}}var J=_.stateNode;J!==null&&typeof J.componentDidCatch=="function"&&(h.callback=function(){k5(g,_,N),typeof P!="function"&&(Ou===null?Ou=new Set([this]):Ou.add(this));var ge=N.stack;this.componentDidCatch(N.value,{componentStack:ge!==null?ge:""})})}function Aie(h,g,_,N,P){if(_.flags|=32768,N!==null&&typeof N=="object"&&typeof N.then=="function"){if(g=_.alternate,g!==null&&gm(g,_,P,!0),_=ks.current,_!==null){switch(_.tag){case 31:case 13:return ao===null?Pv():_.alternate===null&&Qr===0&&(Qr=3),_.flags&=-257,_.flags|=65536,_.lanes=P,N===ov?_.flags|=16384:(g=_.updateQueue,g===null?_.updateQueue=new Set([N]):g.add(N),KE(h,N,P)),!1;case 22:return _.flags|=65536,N===ov?_.flags|=16384:(g=_.updateQueue,g===null?(g={transitions:null,markerInstances:null,retryQueue:new Set([N])},_.updateQueue=g):(_=g.retryQueue,_===null?g.retryQueue=new Set([N]):_.add(N)),KE(h,N,P)),!1}throw Error(r(435,_.tag))}return KE(h,N,P),Pv(),!1}if(Un)return g=ks.current,g!==null?((g.flags&65536)===0&&(g.flags|=256),g.flags|=65536,g.lanes=P,N!==$N&&(h=Error(r(422),{cause:N}),xx(to(h,_)))):(N!==$N&&(g=Error(r(423),{cause:N}),xx(to(g,_))),h=h.current.alternate,h.flags|=65536,P&=-P,h.lanes|=P,N=to(N,_),P=SE(h.stateNode,N,P),JN(h,P),Qr!==4&&(Qr=2)),!1;var I=Error(r(520),{cause:N});if(I=to(I,_),Lx===null?Lx=[I]:Lx.push(I),Qr!==4&&(Qr=2),g===null)return!0;N=to(N,_),_=g;do{switch(_.tag){case 3:return _.flags|=65536,h=P&-P,_.lanes|=h,h=SE(_.stateNode,N,h),JN(_,h),!1;case 1:if(g=_.type,I=_.stateNode,(_.flags&128)===0&&(typeof g.getDerivedStateFromError=="function"||I!==null&&typeof I.componentDidCatch=="function"&&(Ou===null||!Ou.has(I))))return _.flags|=65536,P&=-P,_.lanes|=P,P=j5(P),P5(P,h,_,N),JN(_,P),!1}_=_.return}while(_!==null);return!1}var ME=Error(r(461)),pi=!1;function ca(h,g,_,N){g.child=h===null?LI(g,null,_,N):Sh(g,h.child,_,N)}function R5(h,g,_,N,P){_=_.render;var I=g.ref;if("ref"in N){var J={};for(var ge in N)ge!=="ref"&&(J[ge]=N[ge])}else J=N;return yh(g),N=aE(h,g,_,J,I,P),ge=sE(),h!==null&&!pi?(oE(h,g,P),uc(h,g,P)):(Un&&ge&&zN(g),g.flags|=1,ca(h,g,N,P),g.child)}function D5(h,g,_,N,P){if(h===null){var I=_.type;return typeof I=="function"&&!IN(I)&&I.defaultProps===void 0&&_.compare===null?(g.tag=15,g.type=I,O5(h,g,I,N,P)):(h=tv(_.type,null,N,g,g.mode,P),h.ref=g.ref,h.return=g,g.child=h)}if(I=h.child,!PE(h,P)){var J=I.memoizedProps;if(_=_.compare,_=_!==null?_:mx,_(J,N)&&h.ref===g.ref)return uc(h,g,P)}return g.flags|=1,h=ic(I,N),h.ref=g.ref,h.return=g,g.child=h}function O5(h,g,_,N,P){if(h!==null){var I=h.memoizedProps;if(mx(I,N)&&h.ref===g.ref)if(pi=!1,g.pendingProps=N=I,PE(h,P))(h.flags&131072)!==0&&(pi=!0);else return g.lanes=h.lanes,uc(h,g,P)}return NE(h,g,_,N,P)}function L5(h,g,_,N){var P=N.children,I=h!==null?h.memoizedState:null;if(h===null&&g.stateNode===null&&(g.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),N.mode==="hidden"){if((g.flags&128)!==0){if(I=I!==null?I.baseLanes|_:_,h!==null){for(N=g.child=h.child,P=0;N!==null;)P=P|N.lanes|N.childLanes,N=N.sibling;N=P&~I}else N=0,g.child=null;return I5(h,g,I,_,N)}if((_&536870912)!==0)g.memoizedState={baseLanes:0,cachePool:null},h!==null&&av(g,I!==null?I.cachePool:null),I!==null?UI(g,I):tE(),zI(g);else return N=g.lanes=536870912,I5(h,g,I!==null?I.baseLanes|_:_,_,N)}else I!==null?(av(g,I.cachePool),UI(g,I),ju(),g.memoizedState=null):(h!==null&&av(g,null),tE(),ju());return ca(h,g,P,_),g.child}function Cx(h,g){return h!==null&&h.tag===22||g.stateNode!==null||(g.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),g.sibling}function I5(h,g,_,N,P){var I=XN();return I=I===null?null:{parent:fi._currentValue,pool:I},g.memoizedState={baseLanes:_,cachePool:I},h!==null&&av(g,null),tE(),zI(g),h!==null&&gm(h,g,N,!0),g.childLanes=P,null}function _v(h,g){return g=Sv({mode:g.mode,children:g.children},h.mode),g.ref=h.ref,h.child=g,g.return=h,g}function F5(h,g,_){return Sh(g,h.child,null,_),h=_v(g,g.pendingProps),h.flags|=2,js(g),g.memoizedState=null,h}function Cie(h,g,_){var N=g.pendingProps,P=(g.flags&128)!==0;if(g.flags&=-129,h===null){if(Un){if(N.mode==="hidden")return h=_v(g,N),g.lanes=536870912,Cx(null,h);if(rE(g),(h=Er)?(h=YF(h,io),h=h!==null&&h.data==="&"?h:null,h!==null&&(g.memoizedState={dehydrated:h,treeContext:Su!==null?{id:gl,overflow:xl}:null,retryLane:536870912,hydrationErrors:null},_=_I(h),_.return=g,g.child=_,oa=g,Er=null)):h=null,h===null)throw Nu(g);return g.lanes=536870912,null}return _v(g,N)}var I=h.memoizedState;if(I!==null){var J=I.dehydrated;if(rE(g),P)if(g.flags&256)g.flags&=-257,g=F5(h,g,_);else if(g.memoizedState!==null)g.child=h.child,g.flags|=128,g=null;else throw Error(r(558));else if(pi||gm(h,g,_,!1),P=(_&h.childLanes)!==0,pi||P){if(N=yr,N!==null&&(J=Gt(N,_),J!==0&&J!==I.retryLane))throw I.retryLane=J,ph(h,J),cs(N,h,J),ME;Pv(),g=F5(h,g,_)}else h=I.treeContext,Er=so(J.nextSibling),oa=g,Un=!0,Mu=null,io=!1,h!==null&&MI(g,h),g=_v(g,N),g.flags|=4096;return g}return h=ic(h.child,{mode:N.mode,children:N.children}),h.ref=g.ref,g.child=h,h.return=g,h}function wv(h,g){var _=g.ref;if(_===null)h!==null&&h.ref!==null&&(g.flags|=4194816);else{if(typeof _!="function"&&typeof _!="object")throw Error(r(284));(h===null||h.ref!==_)&&(g.flags|=4194816)}}function NE(h,g,_,N,P){return yh(g),_=aE(h,g,_,N,void 0,P),N=sE(),h!==null&&!pi?(oE(h,g,P),uc(h,g,P)):(Un&&N&&zN(g),g.flags|=1,ca(h,g,_,P),g.child)}function U5(h,g,_,N,P,I){return yh(g),g.updateQueue=null,_=$I(g,N,_,P),BI(h),N=sE(),h!==null&&!pi?(oE(h,g,I),uc(h,g,I)):(Un&&N&&zN(g),g.flags|=1,ca(h,g,_,I),g.child)}function z5(h,g,_,N,P){if(yh(g),g.stateNode===null){var I=hm,J=_.contextType;typeof J=="object"&&J!==null&&(I=la(J)),I=new _(N,I),g.memoizedState=I.state!==null&&I.state!==void 0?I.state:null,I.updater=wE,g.stateNode=I,I._reactInternals=g,I=g.stateNode,I.props=N,I.state=g.memoizedState,I.refs={},QN(g),J=_.contextType,I.context=typeof J=="object"&&J!==null?la(J):hm,I.state=g.memoizedState,J=_.getDerivedStateFromProps,typeof J=="function"&&(_E(g,_,J,N),I.state=g.memoizedState),typeof _.getDerivedStateFromProps=="function"||typeof I.getSnapshotBeforeUpdate=="function"||typeof I.UNSAFE_componentWillMount!="function"&&typeof I.componentWillMount!="function"||(J=I.state,typeof I.componentWillMount=="function"&&I.componentWillMount(),typeof I.UNSAFE_componentWillMount=="function"&&I.UNSAFE_componentWillMount(),J!==I.state&&wE.enqueueReplaceState(I,I.state,null),Mx(g,N,I,P),Sx(),I.state=g.memoizedState),typeof I.componentDidMount=="function"&&(g.flags|=4194308),N=!0}else if(h===null){I=g.stateNode;var ge=g.memoizedProps,Le=Nh(_,ge);I.props=Le;var rt=I.context,yt=_.contextType;J=hm,typeof yt=="object"&&yt!==null&&(J=la(yt));var _t=_.getDerivedStateFromProps;yt=typeof _t=="function"||typeof I.getSnapshotBeforeUpdate=="function",ge=g.pendingProps!==ge,yt||typeof I.UNSAFE_componentWillReceiveProps!="function"&&typeof I.componentWillReceiveProps!="function"||(ge||rt!==J)&&E5(g,I,N,J),Tu=!1;var st=g.memoizedState;I.state=st,Mx(g,N,I,P),Sx(),rt=g.memoizedState,ge||st!==rt||Tu?(typeof _t=="function"&&(_E(g,_,_t,N),rt=g.memoizedState),(Le=Tu||N5(g,_,Le,N,st,rt,J))?(yt||typeof I.UNSAFE_componentWillMount!="function"&&typeof I.componentWillMount!="function"||(typeof I.componentWillMount=="function"&&I.componentWillMount(),typeof I.UNSAFE_componentWillMount=="function"&&I.UNSAFE_componentWillMount()),typeof I.componentDidMount=="function"&&(g.flags|=4194308)):(typeof I.componentDidMount=="function"&&(g.flags|=4194308),g.memoizedProps=N,g.memoizedState=rt),I.props=N,I.state=rt,I.context=J,N=Le):(typeof I.componentDidMount=="function"&&(g.flags|=4194308),N=!1)}else{I=g.stateNode,ZN(h,g),J=g.memoizedProps,yt=Nh(_,J),I.props=yt,_t=g.pendingProps,st=I.context,rt=_.contextType,Le=hm,typeof rt=="object"&&rt!==null&&(Le=la(rt)),ge=_.getDerivedStateFromProps,(rt=typeof ge=="function"||typeof I.getSnapshotBeforeUpdate=="function")||typeof I.UNSAFE_componentWillReceiveProps!="function"&&typeof I.componentWillReceiveProps!="function"||(J!==_t||st!==Le)&&E5(g,I,N,Le),Tu=!1,st=g.memoizedState,I.state=st,Mx(g,N,I,P),Sx();var ft=g.memoizedState;J!==_t||st!==ft||Tu||h!==null&&h.dependencies!==null&&rv(h.dependencies)?(typeof ge=="function"&&(_E(g,_,ge,N),ft=g.memoizedState),(yt=Tu||N5(g,_,yt,N,st,ft,Le)||h!==null&&h.dependencies!==null&&rv(h.dependencies))?(rt||typeof I.UNSAFE_componentWillUpdate!="function"&&typeof I.componentWillUpdate!="function"||(typeof I.componentWillUpdate=="function"&&I.componentWillUpdate(N,ft,Le),typeof I.UNSAFE_componentWillUpdate=="function"&&I.UNSAFE_componentWillUpdate(N,ft,Le)),typeof I.componentDidUpdate=="function"&&(g.flags|=4),typeof I.getSnapshotBeforeUpdate=="function"&&(g.flags|=1024)):(typeof I.componentDidUpdate!="function"||J===h.memoizedProps&&st===h.memoizedState||(g.flags|=4),typeof I.getSnapshotBeforeUpdate!="function"||J===h.memoizedProps&&st===h.memoizedState||(g.flags|=1024),g.memoizedProps=N,g.memoizedState=ft),I.props=N,I.state=ft,I.context=Le,N=yt):(typeof I.componentDidUpdate!="function"||J===h.memoizedProps&&st===h.memoizedState||(g.flags|=4),typeof I.getSnapshotBeforeUpdate!="function"||J===h.memoizedProps&&st===h.memoizedState||(g.flags|=1024),N=!1)}return I=N,wv(h,g),N=(g.flags&128)!==0,I||N?(I=g.stateNode,_=N&&typeof _.getDerivedStateFromError!="function"?null:I.render(),g.flags|=1,h!==null&&N?(g.child=Sh(g,h.child,null,P),g.child=Sh(g,null,_,P)):ca(h,g,_,P),g.memoizedState=I.state,h=g.child):h=uc(h,g,P),h}function B5(h,g,_,N){return xh(),g.flags|=256,ca(h,g,_,N),g.child}var EE={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function TE(h){return{baseLanes:h,cachePool:kI()}}function AE(h,g,_){return h=h!==null?h.childLanes&~_:0,g&&(h|=Rs),h}function $5(h,g,_){var N=g.pendingProps,P=!1,I=(g.flags&128)!==0,J;if((J=I)||(J=h!==null&&h.memoizedState===null?!1:(oi.current&2)!==0),J&&(P=!0,g.flags&=-129),J=(g.flags&32)!==0,g.flags&=-33,h===null){if(Un){if(P?ku(g):ju(),(h=Er)?(h=YF(h,io),h=h!==null&&h.data!=="&"?h:null,h!==null&&(g.memoizedState={dehydrated:h,treeContext:Su!==null?{id:gl,overflow:xl}:null,retryLane:536870912,hydrationErrors:null},_=_I(h),_.return=g,g.child=_,oa=g,Er=null)):h=null,h===null)throw Nu(g);return uT(h)?g.lanes=32:g.lanes=536870912,null}var ge=N.children;return N=N.fallback,P?(ju(),P=g.mode,ge=Sv({mode:"hidden",children:ge},P),N=gh(N,P,_,null),ge.return=g,N.return=g,ge.sibling=N,g.child=ge,N=g.child,N.memoizedState=TE(_),N.childLanes=AE(h,J,_),g.memoizedState=EE,Cx(null,N)):(ku(g),CE(g,ge))}var Le=h.memoizedState;if(Le!==null&&(ge=Le.dehydrated,ge!==null)){if(I)g.flags&256?(ku(g),g.flags&=-257,g=kE(h,g,_)):g.memoizedState!==null?(ju(),g.child=h.child,g.flags|=128,g=null):(ju(),ge=N.fallback,P=g.mode,N=Sv({mode:"visible",children:N.children},P),ge=gh(ge,P,_,null),ge.flags|=2,N.return=g,ge.return=g,N.sibling=ge,g.child=N,Sh(g,h.child,null,_),N=g.child,N.memoizedState=TE(_),N.childLanes=AE(h,J,_),g.memoizedState=EE,g=Cx(null,N));else if(ku(g),uT(ge)){if(J=ge.nextSibling&&ge.nextSibling.dataset,J)var rt=J.dgst;J=rt,N=Error(r(419)),N.stack="",N.digest=J,xx({value:N,source:null,stack:null}),g=kE(h,g,_)}else if(pi||gm(h,g,_,!1),J=(_&h.childLanes)!==0,pi||J){if(J=yr,J!==null&&(N=Gt(J,_),N!==0&&N!==Le.retryLane))throw Le.retryLane=N,ph(h,N),cs(J,h,N),ME;cT(ge)||Pv(),g=kE(h,g,_)}else cT(ge)?(g.flags|=192,g.child=h.child,g=null):(h=Le.treeContext,Er=so(ge.nextSibling),oa=g,Un=!0,Mu=null,io=!1,h!==null&&MI(g,h),g=CE(g,N.children),g.flags|=4096);return g}return P?(ju(),ge=N.fallback,P=g.mode,Le=h.child,rt=Le.sibling,N=ic(Le,{mode:"hidden",children:N.children}),N.subtreeFlags=Le.subtreeFlags&65011712,rt!==null?ge=ic(rt,ge):(ge=gh(ge,P,_,null),ge.flags|=2),ge.return=g,N.return=g,N.sibling=ge,g.child=N,Cx(null,N),N=g.child,ge=h.child.memoizedState,ge===null?ge=TE(_):(P=ge.cachePool,P!==null?(Le=fi._currentValue,P=P.parent!==Le?{parent:Le,pool:Le}:P):P=kI(),ge={baseLanes:ge.baseLanes|_,cachePool:P}),N.memoizedState=ge,N.childLanes=AE(h,J,_),g.memoizedState=EE,Cx(h.child,N)):(ku(g),_=h.child,h=_.sibling,_=ic(_,{mode:"visible",children:N.children}),_.return=g,_.sibling=null,h!==null&&(J=g.deletions,J===null?(g.deletions=[h],g.flags|=16):J.push(h)),g.child=_,g.memoizedState=null,_)}function CE(h,g){return g=Sv({mode:"visible",children:g},h.mode),g.return=h,h.child=g}function Sv(h,g){return h=Cs(22,h,null,g),h.lanes=0,h}function kE(h,g,_){return Sh(g,h.child,null,_),h=CE(g,g.pendingProps.children),h.flags|=2,g.memoizedState=null,h}function H5(h,g,_){h.lanes|=g;var N=h.alternate;N!==null&&(N.lanes|=g),GN(h.return,g,_)}function jE(h,g,_,N,P,I){var J=h.memoizedState;J===null?h.memoizedState={isBackwards:g,rendering:null,renderingStartTime:0,last:N,tail:_,tailMode:P,treeForkCount:I}:(J.isBackwards=g,J.rendering=null,J.renderingStartTime=0,J.last=N,J.tail=_,J.tailMode=P,J.treeForkCount=I)}function V5(h,g,_){var N=g.pendingProps,P=N.revealOrder,I=N.tail;N=N.children;var J=oi.current,ge=(J&2)!==0;if(ge?(J=J&1|2,g.flags|=128):J&=1,se(oi,J),ca(h,g,N,_),N=Un?gx:0,!ge&&h!==null&&(h.flags&128)!==0)e:for(h=g.child;h!==null;){if(h.tag===13)h.memoizedState!==null&&H5(h,_,g);else if(h.tag===19)H5(h,_,g);else if(h.child!==null){h.child.return=h,h=h.child;continue}if(h===g)break e;for(;h.sibling===null;){if(h.return===null||h.return===g)break e;h=h.return}h.sibling.return=h.return,h=h.sibling}switch(P){case"forwards":for(_=g.child,P=null;_!==null;)h=_.alternate,h!==null&&dv(h)===null&&(P=_),_=_.sibling;_=P,_===null?(P=g.child,g.child=null):(P=_.sibling,_.sibling=null),jE(g,!1,P,_,I,N);break;case"backwards":case"unstable_legacy-backwards":for(_=null,P=g.child,g.child=null;P!==null;){if(h=P.alternate,h!==null&&dv(h)===null){g.child=P;break}h=P.sibling,P.sibling=_,_=P,P=h}jE(g,!0,_,null,I,N);break;case"together":jE(g,!1,null,null,void 0,N);break;default:g.memoizedState=null}return g.child}function uc(h,g,_){if(h!==null&&(g.dependencies=h.dependencies),Du|=g.lanes,(_&g.childLanes)===0)if(h!==null){if(gm(h,g,_,!1),(_&g.childLanes)===0)return null}else return null;if(h!==null&&g.child!==h.child)throw Error(r(153));if(g.child!==null){for(h=g.child,_=ic(h,h.pendingProps),g.child=_,_.return=g;h.sibling!==null;)h=h.sibling,_=_.sibling=ic(h,h.pendingProps),_.return=g;_.sibling=null}return g.child}function PE(h,g){return(h.lanes&g)!==0?!0:(h=h.dependencies,!!(h!==null&&rv(h)))}function kie(h,g,_){switch(g.tag){case 3:Ee(g,g.stateNode.containerInfo),Eu(g,fi,h.memoizedState.cache),xh();break;case 27:case 5:Me(g);break;case 4:Ee(g,g.stateNode.containerInfo);break;case 10:Eu(g,g.type,g.memoizedProps.value);break;case 31:if(g.memoizedState!==null)return g.flags|=128,rE(g),null;break;case 13:var N=g.memoizedState;if(N!==null)return N.dehydrated!==null?(ku(g),g.flags|=128,null):(_&g.child.childLanes)!==0?$5(h,g,_):(ku(g),h=uc(h,g,_),h!==null?h.sibling:null);ku(g);break;case 19:var P=(h.flags&128)!==0;if(N=(_&g.childLanes)!==0,N||(gm(h,g,_,!1),N=(_&g.childLanes)!==0),P){if(N)return V5(h,g,_);g.flags|=128}if(P=g.memoizedState,P!==null&&(P.rendering=null,P.tail=null,P.lastEffect=null),se(oi,oi.current),N)break;return null;case 22:return g.lanes=0,L5(h,g,_,g.pendingProps);case 24:Eu(g,fi,h.memoizedState.cache)}return uc(h,g,_)}function G5(h,g,_){if(h!==null)if(h.memoizedProps!==g.pendingProps)pi=!0;else{if(!PE(h,_)&&(g.flags&128)===0)return pi=!1,kie(h,g,_);pi=(h.flags&131072)!==0}else pi=!1,Un&&(g.flags&1048576)!==0&&SI(g,gx,g.index);switch(g.lanes=0,g.tag){case 16:e:{var N=g.pendingProps;if(h=_h(g.elementType),g.type=h,typeof h=="function")IN(h)?(N=Nh(h,N),g.tag=1,g=z5(null,g,h,N,_)):(g.tag=0,g=NE(null,g,h,N,_));else{if(h!=null){var P=h.$$typeof;if(P===A){g.tag=11,g=R5(null,g,h,N,_);break e}else if(P===E){g.tag=14,g=D5(null,g,h,N,_);break e}}throw g=j(h)||h,Error(r(306,g,""))}}return g;case 0:return NE(h,g,g.type,g.pendingProps,_);case 1:return N=g.type,P=Nh(N,g.pendingProps),z5(h,g,N,P,_);case 3:e:{if(Ee(g,g.stateNode.containerInfo),h===null)throw Error(r(387));N=g.pendingProps;var I=g.memoizedState;P=I.element,ZN(h,g),Mx(g,N,null,_);var J=g.memoizedState;if(N=J.cache,Eu(g,fi,N),N!==I.cache&&WN(g,[fi],_,!0),Sx(),N=J.element,I.isDehydrated)if(I={element:N,isDehydrated:!1,cache:J.cache},g.updateQueue.baseState=I,g.memoizedState=I,g.flags&256){g=B5(h,g,N,_);break e}else if(N!==P){P=to(Error(r(424)),g),xx(P),g=B5(h,g,N,_);break e}else{switch(h=g.stateNode.containerInfo,h.nodeType){case 9:h=h.body;break;default:h=h.nodeName==="HTML"?h.ownerDocument.body:h}for(Er=so(h.firstChild),oa=g,Un=!0,Mu=null,io=!0,_=LI(g,null,N,_),g.child=_;_;)_.flags=_.flags&-3|4096,_=_.sibling}else{if(xh(),N===P){g=uc(h,g,_);break e}ca(h,g,N,_)}g=g.child}return g;case 26:return wv(h,g),h===null?(_=nU(g.type,null,g.pendingProps,null))?g.memoizedState=_:Un||(_=g.type,h=g.pendingProps,N=Uv(me.current).createElement(_),N[sn]=g,N[Sr]=h,ua(N,_,h),$e(N),g.stateNode=N):g.memoizedState=nU(g.type,h.memoizedProps,g.pendingProps,h.memoizedState),null;case 27:return Me(g),h===null&&Un&&(N=g.stateNode=JF(g.type,g.pendingProps,me.current),oa=g,io=!0,P=Er,Uu(g.type)?(dT=P,Er=so(N.firstChild)):Er=P),ca(h,g,g.pendingProps.children,_),wv(h,g),h===null&&(g.flags|=4194304),g.child;case 5:return h===null&&Un&&((P=N=Er)&&(N=sae(N,g.type,g.pendingProps,io),N!==null?(g.stateNode=N,oa=g,Er=so(N.firstChild),io=!1,P=!0):P=!1),P||Nu(g)),Me(g),P=g.type,I=g.pendingProps,J=h!==null?h.memoizedProps:null,N=I.children,sT(P,I)?N=null:J!==null&&sT(P,J)&&(g.flags|=32),g.memoizedState!==null&&(P=aE(h,g,_ie,null,null,_),Vx._currentValue=P),wv(h,g),ca(h,g,N,_),g.child;case 6:return h===null&&Un&&((h=_=Er)&&(_=oae(_,g.pendingProps,io),_!==null?(g.stateNode=_,oa=g,Er=null,h=!0):h=!1),h||Nu(g)),null;case 13:return $5(h,g,_);case 4:return Ee(g,g.stateNode.containerInfo),N=g.pendingProps,h===null?g.child=Sh(g,null,N,_):ca(h,g,N,_),g.child;case 11:return R5(h,g,g.type,g.pendingProps,_);case 7:return ca(h,g,g.pendingProps,_),g.child;case 8:return ca(h,g,g.pendingProps.children,_),g.child;case 12:return ca(h,g,g.pendingProps.children,_),g.child;case 10:return N=g.pendingProps,Eu(g,g.type,N.value),ca(h,g,N.children,_),g.child;case 9:return P=g.type._context,N=g.pendingProps.children,yh(g),P=la(P),N=N(P),g.flags|=1,ca(h,g,N,_),g.child;case 14:return D5(h,g,g.type,g.pendingProps,_);case 15:return O5(h,g,g.type,g.pendingProps,_);case 19:return V5(h,g,_);case 31:return Cie(h,g,_);case 22:return L5(h,g,_,g.pendingProps);case 24:return yh(g),N=la(fi),h===null?(P=XN(),P===null&&(P=yr,I=qN(),P.pooledCache=I,I.refCount++,I!==null&&(P.pooledCacheLanes|=_),P=I),g.memoizedState={parent:N,cache:P},QN(g),Eu(g,fi,P)):((h.lanes&_)!==0&&(ZN(h,g),Mx(g,null,null,_),Sx()),P=h.memoizedState,I=g.memoizedState,P.parent!==N?(P={parent:N,cache:N},g.memoizedState=P,g.lanes===0&&(g.memoizedState=g.updateQueue.baseState=P),Eu(g,fi,N)):(N=I.cache,Eu(g,fi,N),N!==P.cache&&WN(g,[fi],_,!0))),ca(h,g,g.pendingProps.children,_),g.child;case 29:throw g.pendingProps}throw Error(r(156,g.tag))}function dc(h){h.flags|=4}function RE(h,g,_,N,P){if((g=(h.mode&32)!==0)&&(g=!1),g){if(h.flags|=16777216,(P&335544128)===P)if(h.stateNode.complete)h.flags|=8192;else if(bF())h.flags|=8192;else throw wh=ov,YN}else h.flags&=-16777217}function W5(h,g){if(g.type!=="stylesheet"||(g.state.loading&4)!==0)h.flags&=-16777217;else if(h.flags|=16777216,!oU(g))if(bF())h.flags|=8192;else throw wh=ov,YN}function Mv(h,g){g!==null&&(h.flags|=4),h.flags&16384&&(g=h.tag!==22?Pt():536870912,h.lanes|=g,Am|=g)}function kx(h,g){if(!Un)switch(h.tailMode){case"hidden":g=h.tail;for(var _=null;g!==null;)g.alternate!==null&&(_=g),g=g.sibling;_===null?h.tail=null:_.sibling=null;break;case"collapsed":_=h.tail;for(var N=null;_!==null;)_.alternate!==null&&(N=_),_=_.sibling;N===null?g||h.tail===null?h.tail=null:h.tail.sibling=null:N.sibling=null}}function Tr(h){var g=h.alternate!==null&&h.alternate.child===h.child,_=0,N=0;if(g)for(var P=h.child;P!==null;)_|=P.lanes|P.childLanes,N|=P.subtreeFlags&65011712,N|=P.flags&65011712,P.return=h,P=P.sibling;else for(P=h.child;P!==null;)_|=P.lanes|P.childLanes,N|=P.subtreeFlags,N|=P.flags,P.return=h,P=P.sibling;return h.subtreeFlags|=N,h.childLanes=_,g}function jie(h,g,_){var N=g.pendingProps;switch(BN(g),g.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Tr(g),null;case 1:return Tr(g),null;case 3:return _=g.stateNode,N=null,h!==null&&(N=h.memoizedState.cache),g.memoizedState.cache!==N&&(g.flags|=2048),oc(fi),pe(),_.pendingContext&&(_.context=_.pendingContext,_.pendingContext=null),(h===null||h.child===null)&&(pm(g)?dc(g):h===null||h.memoizedState.isDehydrated&&(g.flags&256)===0||(g.flags|=1024,HN())),Tr(g),null;case 26:var P=g.type,I=g.memoizedState;return h===null?(dc(g),I!==null?(Tr(g),W5(g,I)):(Tr(g),RE(g,P,null,N,_))):I?I!==h.memoizedState?(dc(g),Tr(g),W5(g,I)):(Tr(g),g.flags&=-16777217):(h=h.memoizedProps,h!==N&&dc(g),Tr(g),RE(g,P,h,N,_)),null;case 27:if(ve(g),_=me.current,P=g.type,h!==null&&g.stateNode!=null)h.memoizedProps!==N&&dc(g);else{if(!N){if(g.stateNode===null)throw Error(r(166));return Tr(g),null}h=oe.current,pm(g)?NI(g):(h=JF(P,N,_),g.stateNode=h,dc(g))}return Tr(g),null;case 5:if(ve(g),P=g.type,h!==null&&g.stateNode!=null)h.memoizedProps!==N&&dc(g);else{if(!N){if(g.stateNode===null)throw Error(r(166));return Tr(g),null}if(I=oe.current,pm(g))NI(g);else{var J=Uv(me.current);switch(I){case 1:I=J.createElementNS("http://www.w3.org/2000/svg",P);break;case 2:I=J.createElementNS("http://www.w3.org/1998/Math/MathML",P);break;default:switch(P){case"svg":I=J.createElementNS("http://www.w3.org/2000/svg",P);break;case"math":I=J.createElementNS("http://www.w3.org/1998/Math/MathML",P);break;case"script":I=J.createElement("div"),I.innerHTML=" + From e74d5be4b8cf993cc861fe482450412e34c18a37 Mon Sep 17 00:00:00 2001 From: maziggy Date: Thu, 29 Jan 2026 08:17:53 +0100 Subject: [PATCH 16/28] =?UTF-8?q?@renovate=20-=20vitest:=20^2.1.0=20?= =?UTF-8?q?=E2=86=92=20^3.2.4=20-=20@vitest/coverage-v8:=20upgraded=20to?= =?UTF-8?q?=20match?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bambuddy-issue-notes.txt | 83 --- frontend/package-lock.json | 1234 ++++++------------------------------ frontend/package.json | 4 +- 3 files changed, 186 insertions(+), 1135 deletions(-) delete mode 100644 bambuddy-issue-notes.txt diff --git a/bambuddy-issue-notes.txt b/bambuddy-issue-notes.txt deleted file mode 100644 index 71c70f4c0..000000000 --- a/bambuddy-issue-notes.txt +++ /dev/null @@ -1,83 +0,0 @@ -=== BAMBUDDY FILE DELETION ISSUE - Jan 8, 2026 === -=== ROOT CAUSE IDENTIFIED === - -WHAT HAPPENED: -- /opt was COMPLETELY DELETED on TWO containers: - - Container 109 (claude): ~11:22 and ~12:22 - - Container 107 (3dp): ~13:28 -- Container 107 was "untouched" (no SSH, no Claude Code) - just running BamBuddy - -ROOT CAUSE FOUND: -Bug in backend/app/services/archive.py delete_archive() function (lines 914-929): - - file_path = settings.base_dir / archive.file_path - if file_path.exists(): - archive_dir = file_path.parent - shutil.rmtree(archive_dir, ignore_errors=True) # <-- THE BUG - -If archive.file_path is EMPTY or MALFORMED: -- file_path = /opt/bambuddy / "" = /opt/bambuddy -- archive_dir = file_path.parent = /opt -- shutil.rmtree("/opt") --> DELETES ENTIRE /opt DIRECTORY! - -TRIGGER: -- User was deleting archives via BamBuddy web UI on container 107 (3dp) -- One archive had corrupted/empty file_path in database -- Deleting that archive triggered shutil.rmtree("/opt") -- This deleted the entire /opt directory including BamBuddy itself - -TIMELINE FOR CONTAINER 107 (3dp): -- 13:28:19 - Normal operation (WebSocket disconnect) -- 13:28:44 - DELETE /api/v1/archives/* requests failing with 500 - (database already gone because /opt was deleted) -- ls -la / shows root directory modified at 13:28 - -FIX APPLIED (on container 109): -Safety checks added to delete_archive() in archive.py: -1. Check if file_path is not empty -2. Verify archive_dir is inside settings.archive_dir -3. Ensure archive_dir is at least 2 levels deep -4. Log error and refuse to delete if checks fail - -TO INVESTIGATE AFTER ROLLBACK: -On container 107, after rolling back to autodaily260108003006: - - # Find corrupted archive records - sqlite3 /opt/bambuddy/data/bambuddy.db \ - "SELECT id, filename, file_path FROM print_archives - WHERE file_path = '' OR file_path IS NULL - OR file_path NOT LIKE 'archive/%';" - - # Check all file_path values - sqlite3 /opt/bambuddy/data/bambuddy.db \ - "SELECT id, file_path FROM print_archives ORDER BY id;" - -CONTAINER 109 (this host): -- Were you also deleting archives around 11:22 and 12:22? -- Same bug could have been triggered here too - -PROXMOX COMMANDS FOR ROLLBACK: - # Container 107 (3dp) - pct rollback 107 autodaily260108003006 - pct start 107 - - # Container 109 (claude) - already done via UI - # Current snapshot: autodaily260108003004 - -WHAT TO DO NEXT: -1. Rollback container 107 to morning snapshot -2. Run the SQL query above to find corrupted archive -3. Apply the fix from container 109 to container 107 -4. Understand how the file_path got corrupted in the first place - -THE FIX (apply to both containers): -In backend/app/services/archive.py, the delete_archive function now has: -- Empty file_path check -- Path traversal protection (relative_to check) -- Minimum depth check (must be 2+ levels inside archive dir) -- Error logging for refused deletions - -NOT CLAUDE CODE'S FAULT: -This was a bug in BamBuddy's own code that was triggered by: -1. Corrupted database record (unknown how it got corrupted) -2. User action (deleting archives via web UI) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c2278e1be..c6b2bae1d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -44,7 +44,7 @@ "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", - "@vitest/coverage-v8": "^2.1.0", + "@vitest/coverage-v8": "^3.2.4", "autoprefixer": "^10.4.22", "baseline-browser-mapping": "^2.9.18", "eslint": "^9.39.1", @@ -58,7 +58,7 @@ "typescript": "~5.9.3", "typescript-eslint": "^8.46.4", "vite": "^7.2.4", - "vitest": "^2.1.0" + "vitest": "^3.2.4" } }, "node_modules/@adobe/css-tools": { @@ -404,10 +404,13 @@ } }, "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "engines": { + "node": ">=18" + } }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", @@ -2848,6 +2851,16 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", @@ -2911,6 +2924,12 @@ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -3306,30 +3325,31 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", - "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.7", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.12", + "magic-string": "^0.30.17", "magicast": "^0.3.5", - "std-env": "^3.8.0", + "std-env": "^3.9.0", "test-exclude": "^7.0.1", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.9", - "vitest": "2.1.9" + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -3338,80 +3358,108 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", - "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, "dependencies": { - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/pretty-format": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", - "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, "dependencies": { - "tinyrainbow": "^1.2.0" + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "dependencies": { + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", - "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "dependencies": { - "@vitest/utils": "2.1.9", - "pathe": "^1.1.2" + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", - "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.1.9", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", - "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, "dependencies": { - "tinyspy": "^3.0.2" + "tinyspy": "^4.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", - "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.1.9", - "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -3521,6 +3569,23 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", + "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3713,9 +3778,9 @@ } }, "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", "dev": true, "engines": { "node": ">= 16" @@ -6288,9 +6353,9 @@ "dev": true }, "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true }, "node_modules/pathval": { @@ -7161,6 +7226,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7302,18 +7385,18 @@ } }, "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "engines": { "node": ">=14.0.0" } }, "node_modules/tinyspy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, "engines": { "node": ">=14.0.0" @@ -7616,533 +7699,72 @@ } }, "node_modules/vite-node": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", - "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dev": true, "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.7", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", - "vite": "^5.0.0" + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/vite-node/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-node/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/vite-node/node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, "node_modules/vitest": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", - "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "dependencies": { - "@vitest/expect": "2.1.9", - "@vitest/mocker": "2.1.9", - "@vitest/pretty-format": "^2.1.9", - "@vitest/runner": "2.1.9", - "@vitest/snapshot": "2.1.9", - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "debug": "^4.3.7", - "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", - "std-env": "^3.8.0", + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.1.9", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.9", - "@vitest/ui": "2.1.9", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, @@ -8150,6 +7772,9 @@ "@edge-runtime/vm": { "optional": true }, + "@types/debug": { + "optional": true + }, "@types/node": { "optional": true }, @@ -8167,497 +7792,6 @@ } } }, - "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", - "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", - "dev": true, - "dependencies": { - "@vitest/spy": "2.1.9", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/vitest/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/vitest/node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index c3b67f51e..14a4c0903 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,7 +50,7 @@ "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", - "@vitest/coverage-v8": "^2.1.0", + "@vitest/coverage-v8": "^3.2.4", "autoprefixer": "^10.4.22", "baseline-browser-mapping": "^2.9.18", "eslint": "^9.39.1", @@ -64,6 +64,6 @@ "typescript": "~5.9.3", "typescript-eslint": "^8.46.4", "vite": "^7.2.4", - "vitest": "^2.1.0" + "vitest": "^3.2.4" } } From 18fbd9c1560b2dc0afd199a8ff62075388a483eb Mon Sep 17 00:00:00 2001 From: maziggy Date: Thu, 29 Jan 2026 08:27:20 +0100 Subject: [PATCH 17/28] Remove timestamps from GitHub backup files for cleaner diffs - Remove created_at from backup_metadata.json - Remove exported_at from K-profile files - Remove exported_at from cloud profile files (filament, printer, process) - Remove exported_at from app settings backup - Git already tracks file modification history, so these were redundant - Backups now only show changes in git diff when actual data changes --- CHANGELOG.md | 4 ++++ backend/app/services/github_backup.py | 9 +-------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acc5902f9..e6cb5e142 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -130,6 +130,10 @@ All notable changes to Bambuddy will be documented in this file. - Both settings persist in localStorage - Tooltip shows full name on hover - **K-Profiles Backup Status** - Fixed GitHub backup settings showing incorrect printer connection count (e.g., "1/2 connected" when both printers are connected); now fetches status from API instead of relying on WebSocket cache +- **GitHub Backup Timestamps** - Removed volatile timestamps from GitHub backup files so git diffs only show actual data changes + +### Maintenance +- Upgraded vitest from 2.x to 3.x to resolve npm audit security vulnerabilities in dev dependencies ## [0.1.6b11] - 2026-01-22 diff --git a/backend/app/services/github_backup.py b/backend/app/services/github_backup.py index e7e1bfb1f..d499948a7 100644 --- a/backend/app/services/github_backup.py +++ b/backend/app/services/github_backup.py @@ -309,13 +309,11 @@ class GitHubBackupService: } """ files: dict[str, dict | list] = {} - now = datetime.now(UTC) - # Metadata file + # Metadata file (no timestamps - git tracks file history) metadata = { "version": "1.0", "backup_type": "bambuddy_profiles", - "created_at": now.isoformat(), "contents": { "kprofiles": config.backup_kprofiles, "cloud_profiles": config.backup_cloud_profiles, @@ -365,7 +363,6 @@ class GitHubBackupService: "printer_name": printer.name, "printer_serial": serial, "nozzle_diameter": nozzle, - "exported_at": datetime.now(UTC).isoformat(), "profiles": [ { "slot_id": p.slot_id, @@ -425,21 +422,18 @@ class GitHubBackupService: if filament_settings: files["cloud_profiles/filament.json"] = { "version": "1.0", - "exported_at": datetime.now(UTC).isoformat(), "profiles": filament_settings, } if printer_settings: files["cloud_profiles/printer.json"] = { "version": "1.0", - "exported_at": datetime.now(UTC).isoformat(), "profiles": printer_settings, } if process_settings: files["cloud_profiles/process.json"] = { "version": "1.0", - "exported_at": datetime.now(UTC).isoformat(), "profiles": process_settings, } @@ -462,7 +456,6 @@ class GitHubBackupService: files["settings/app_settings.json"] = { "version": "1.0", - "exported_at": datetime.now(UTC).isoformat(), "settings": settings_data, } From 48b897a5a9b1a61e132cbe7f89f0a56a1a5b7eb7 Mon Sep 17 00:00:00 2001 From: maziggy Date: Thu, 29 Jan 2026 13:30:45 +0100 Subject: [PATCH 18/28] Updated CI --- .github/workflows/issue-closed.yml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/issue-closed.yml b/.github/workflows/issue-closed.yml index 17bf9a5fb..31f2e607b 100644 --- a/.github/workflows/issue-closed.yml +++ b/.github/workflows/issue-closed.yml @@ -19,11 +19,19 @@ jobs: const hasLabel = issue.labels.some(l => l.name === 'feedback'); if (hasLabel) { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - name: 'feedback' - }); - console.log(`Removed 'feedback' label from issue #${issue.number}`); + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + name: 'feedback' + }); + console.log(`Removed 'feedback' label from issue #${issue.number}`); + } catch (error) { + if (error.status === 404) { + console.log(`Label 'feedback' already removed from issue #${issue.number}`); + } else { + throw error; + } + } } From 86ad13398aebcebe2b05432f81026733e5870317 Mon Sep 17 00:00:00 2001 From: maziggy Date: Thu, 29 Jan 2026 13:30:45 +0100 Subject: [PATCH 19/28] Updated CI --- .github/workflows/issue-closed.yml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/issue-closed.yml b/.github/workflows/issue-closed.yml index 17bf9a5fb..31f2e607b 100644 --- a/.github/workflows/issue-closed.yml +++ b/.github/workflows/issue-closed.yml @@ -19,11 +19,19 @@ jobs: const hasLabel = issue.labels.some(l => l.name === 'feedback'); if (hasLabel) { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - name: 'feedback' - }); - console.log(`Removed 'feedback' label from issue #${issue.number}`); + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + name: 'feedback' + }); + console.log(`Removed 'feedback' label from issue #${issue.number}`); + } catch (error) { + if (error.status === 404) { + console.log(`Label 'feedback' already removed from issue #${issue.number}`); + } else { + throw error; + } + } } From 375d5238d05309ced8b931e26c2f3699d52071ca Mon Sep 17 00:00:00 2001 From: MisterBeardy Date: Thu, 29 Jan 2026 08:53:49 -0500 Subject: [PATCH 20/28] Add STL thumbnail generation support - Add trimesh and matplotlib dependencies for software-based 3D rendering - Create stl_thumbnail service with generate_stl_thumbnail() function - Handle mesh simplification for large files (>100k vertices) - Auto-generate thumbnails during STL file upload and ZIP extraction - Add POST /library/files/{id}/regenerate-thumbnail endpoint - Add POST /library/generate-stl-thumbnails batch endpoint - Add "Generate Thumbnails" button to file manager toolbar - Add "Regenerate Thumbnail" option to file context menu - Add unit and integration tests for new functionality Co-Authored-By: Claude Opus 4.5 --- .gitignore | 2 +- backend/app/api/routes/library.py | 220 ++++++++++++ backend/app/schemas/library.py | 29 ++ backend/app/services/stl_thumbnail.py | 227 ++++++++++++ backend/tests/integration/test_library_api.py | 123 +++++++ .../tests/unit/services/test_stl_thumbnail.py | 326 ++++++++++++++++++ frontend/src/api/client.ts | 30 ++ frontend/src/pages/FileManagerPage.tsx | 61 +++- requirements.txt | 4 + 9 files changed, 1020 insertions(+), 2 deletions(-) create mode 100644 backend/app/services/stl_thumbnail.py create mode 100644 backend/tests/unit/services/test_stl_thumbnail.py diff --git a/.gitignore b/.gitignore index f5eb2dfd3..470b31f0d 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,4 @@ bambutrack.log.* firmware/ # Node modules -node_modules/ \ No newline at end of file +node_modules/ diff --git a/backend/app/api/routes/library.py b/backend/app/api/routes/library.py index a07e835c8..bbbaa450f 100644 --- a/backend/app/api/routes/library.py +++ b/backend/app/api/routes/library.py @@ -25,6 +25,9 @@ from backend.app.schemas.library import ( AddToQueueRequest, AddToQueueResponse, AddToQueueResult, + BatchThumbnailRequest, + BatchThumbnailResponse, + BatchThumbnailResult, BulkDeleteRequest, BulkDeleteResponse, FileDuplicate, @@ -708,6 +711,18 @@ async def upload_file( except Exception as e: logger.warning(f"Failed to extract gcode thumbnail: {e}") + elif ext == ".stl": + # Generate thumbnail from STL file + try: + from backend.app.services.stl_thumbnail import generate_stl_thumbnail + + thumb_filename = f"{uuid.uuid4().hex}.png" + thumb_path = thumbnails_dir / thumb_filename + if generate_stl_thumbnail(file_path, thumb_path): + thumbnail_path = str(thumb_path) + except Exception as e: + logger.warning(f"Failed to generate STL thumbnail: {e}") + elif ext.lower() in IMAGE_EXTENSIONS: # For image files, create a thumbnail from the image itself thumbnail_path = create_image_thumbnail(file_path, thumbnails_dir) @@ -907,6 +922,17 @@ async def extract_zip_file( except Exception as e: logger.warning(f"Failed to extract gcode thumbnail from ZIP: {e}") + elif ext == ".stl": + try: + from backend.app.services.stl_thumbnail import generate_stl_thumbnail + + thumb_filename = f"{uuid.uuid4().hex}.png" + thumb_path = thumbnails_dir / thumb_filename + if generate_stl_thumbnail(file_path, thumb_path): + thumbnail_path = str(thumb_path) + except Exception as e: + logger.warning(f"Failed to generate STL thumbnail from ZIP: {e}") + elif ext.lower() in IMAGE_EXTENSIONS: thumbnail_path = create_image_thumbnail(file_path, thumbnails_dir) @@ -1888,3 +1914,197 @@ async def get_library_stats(db: AsyncSession = Depends(get_db)): "disk_total_bytes": disk_total_bytes, "disk_used_bytes": disk_used_bytes, } + + +# ============ Thumbnail Generation Endpoints ============ + + +@router.post("/files/{file_id}/regenerate-thumbnail", response_model=FileResponseSchema) +async def regenerate_thumbnail(file_id: int, db: AsyncSession = Depends(get_db)): + """Regenerate thumbnail for a specific file. + + Works for STL, 3MF, gcode, and image files. + """ + result = await db.execute(select(LibraryFile).where(LibraryFile.id == file_id)) + file = result.scalar_one_or_none() + + if not file: + raise HTTPException(status_code=404, detail="File not found") + + if not file.file_path or not os.path.exists(file.file_path): + raise HTTPException(status_code=404, detail="File not found on disk") + + ext = os.path.splitext(file.filename)[1].lower() + thumbnails_dir = get_library_thumbnails_dir() + file_path = Path(file.file_path) + + # Delete old thumbnail if exists + if file.thumbnail_path and os.path.exists(file.thumbnail_path): + try: + os.remove(file.thumbnail_path) + except Exception as e: + logger.warning(f"Failed to delete old thumbnail: {e}") + + thumbnail_path = None + + if ext == ".3mf": + try: + parser = ThreeMFParser(str(file_path)) + raw_metadata = parser.parse() + thumbnail_data = raw_metadata.get("_thumbnail_data") + thumbnail_ext = raw_metadata.get("_thumbnail_ext", ".png") + + if thumbnail_data: + thumb_filename = f"{uuid.uuid4().hex}{thumbnail_ext}" + thumb_path = thumbnails_dir / thumb_filename + with open(thumb_path, "wb") as f: + f.write(thumbnail_data) + thumbnail_path = str(thumb_path) + except Exception as e: + logger.warning(f"Failed to extract 3MF thumbnail: {e}") + + elif ext == ".gcode": + try: + thumbnail_data = extract_gcode_thumbnail(file_path) + if thumbnail_data: + thumb_filename = f"{uuid.uuid4().hex}.png" + thumb_path = thumbnails_dir / thumb_filename + with open(thumb_path, "wb") as f: + f.write(thumbnail_data) + thumbnail_path = str(thumb_path) + except Exception as e: + logger.warning(f"Failed to extract gcode thumbnail: {e}") + + elif ext == ".stl": + try: + from backend.app.services.stl_thumbnail import generate_stl_thumbnail + + thumb_filename = f"{uuid.uuid4().hex}.png" + thumb_path = thumbnails_dir / thumb_filename + if generate_stl_thumbnail(file_path, thumb_path): + thumbnail_path = str(thumb_path) + except Exception as e: + logger.warning(f"Failed to generate STL thumbnail: {e}") + + elif ext.lower() in IMAGE_EXTENSIONS: + thumbnail_path = create_image_thumbnail(file_path, thumbnails_dir) + + # Update database + file.thumbnail_path = thumbnail_path + await db.flush() + await db.refresh(file) + + # Return full response + return await get_file(file_id, db) + + +@router.post("/generate-stl-thumbnails", response_model=BatchThumbnailResponse) +async def batch_generate_stl_thumbnails( + request: BatchThumbnailRequest, + db: AsyncSession = Depends(get_db), +): + """Generate thumbnails for existing STL files. + + Can target specific files, a folder, or all STL files missing thumbnails. + + Args: + request: Batch request specifying which files to process + - file_ids: List of specific file IDs to process + - folder_id: Process all STL files in this folder + - all_missing: Process all STL files without thumbnails + """ + from backend.app.services.stl_thumbnail import generate_stl_thumbnail + + results: list[BatchThumbnailResult] = [] + thumbnails_dir = get_library_thumbnails_dir() + + # Build query based on request parameters + query = select(LibraryFile).where(LibraryFile.file_type == "stl") + + if request.file_ids: + # Specific files requested + query = query.where(LibraryFile.id.in_(request.file_ids)) + elif request.folder_id is not None: + # All STL files in a folder + query = query.where(LibraryFile.folder_id == request.folder_id) + elif request.all_missing: + # All STL files without thumbnails + query = query.where(LibraryFile.thumbnail_path.is_(None)) + else: + # No valid filter specified + raise HTTPException( + status_code=400, + detail="Must specify file_ids, folder_id, or all_missing=true", + ) + + result = await db.execute(query) + files = result.scalars().all() + + succeeded = 0 + failed = 0 + + for file in files: + if not file.file_path or not os.path.exists(file.file_path): + results.append( + BatchThumbnailResult( + file_id=file.id, + filename=file.filename, + success=False, + error="File not found on disk", + ) + ) + failed += 1 + continue + + try: + # Delete old thumbnail if exists + if file.thumbnail_path and os.path.exists(file.thumbnail_path): + try: + os.remove(file.thumbnail_path) + except Exception: + pass + + # Generate new thumbnail + thumb_filename = f"{uuid.uuid4().hex}.png" + thumb_path = thumbnails_dir / thumb_filename + + if generate_stl_thumbnail(file.file_path, thumb_path): + file.thumbnail_path = str(thumb_path) + results.append( + BatchThumbnailResult( + file_id=file.id, + filename=file.filename, + success=True, + ) + ) + succeeded += 1 + else: + results.append( + BatchThumbnailResult( + file_id=file.id, + filename=file.filename, + success=False, + error="Thumbnail generation failed", + ) + ) + failed += 1 + + except Exception as e: + results.append( + BatchThumbnailResult( + file_id=file.id, + filename=file.filename, + success=False, + error=str(e), + ) + ) + failed += 1 + + await db.commit() + + return BatchThumbnailResponse( + processed=len(results), + succeeded=succeeded, + failed=failed, + results=results, + ) diff --git a/backend/app/schemas/library.py b/backend/app/schemas/library.py index dacbcc735..0c52b6b1a 100644 --- a/backend/app/schemas/library.py +++ b/backend/app/schemas/library.py @@ -262,3 +262,32 @@ class ZipExtractResponse(BaseModel): folders_created: int files: list[ZipExtractResult] errors: list[ZipExtractError] + + +# ============ Batch Thumbnail Generation ============ + + +class BatchThumbnailRequest(BaseModel): + """Schema for batch STL thumbnail generation request.""" + + file_ids: list[int] | None = None # Specific file IDs to process + folder_id: int | None = None # Process all STL files in this folder + all_missing: bool = False # Process all STL files without thumbnails + + +class BatchThumbnailResult(BaseModel): + """Result for a single file thumbnail generation.""" + + file_id: int + filename: str + success: bool + error: str | None = None + + +class BatchThumbnailResponse(BaseModel): + """Schema for batch thumbnail generation response.""" + + processed: int + succeeded: int + failed: int + results: list[BatchThumbnailResult] diff --git a/backend/app/services/stl_thumbnail.py b/backend/app/services/stl_thumbnail.py new file mode 100644 index 000000000..2861e45d8 --- /dev/null +++ b/backend/app/services/stl_thumbnail.py @@ -0,0 +1,227 @@ +"""STL thumbnail generation service. + +Generates PNG thumbnails from STL files using trimesh and matplotlib. +Supports both ASCII and binary STL formats, handles large meshes via simplification. +""" + +import io +import logging +from pathlib import Path + +logger = logging.getLogger(__name__) + +# Maximum vertices before simplification is applied +MAX_VERTICES = 100000 + +# Default thumbnail size +DEFAULT_SIZE = (256, 256) + + +def generate_stl_thumbnail( + stl_path: str | Path, + output_path: str | Path, + size: tuple[int, int] = DEFAULT_SIZE, +) -> bool: + """Generate a PNG thumbnail from an STL file. + + Args: + stl_path: Path to the input STL file + output_path: Path where the PNG thumbnail will be saved + size: Tuple of (width, height) for the output image + + Returns: + True if thumbnail was generated successfully, False otherwise + """ + try: + import matplotlib + + matplotlib.use("Agg") # Use non-interactive backend + import matplotlib.pyplot as plt + import numpy as np + import trimesh + + # Load the STL file + mesh = trimesh.load(str(stl_path), file_type="stl") + + if mesh is None or not hasattr(mesh, "vertices") or len(mesh.vertices) == 0: + logger.warning(f"Failed to load STL or empty mesh: {stl_path}") + return False + + # Simplify if mesh is too large + if len(mesh.vertices) > MAX_VERTICES: + logger.info(f"Simplifying mesh with {len(mesh.vertices)} vertices to ~{MAX_VERTICES}") + # Calculate target face count based on vertex ratio + target_faces = int(len(mesh.faces) * (MAX_VERTICES / len(mesh.vertices))) + try: + mesh = mesh.simplify_quadric_decimation(target_faces) + except Exception as e: + logger.warning(f"Mesh simplification failed, using original: {e}") + + # Create figure with transparent background + fig = plt.figure(figsize=(size[0] / 100, size[1] / 100), dpi=100) + ax = fig.add_subplot(111, projection="3d") + + # Get mesh vertices and faces + vertices = mesh.vertices + faces = mesh.faces + + # Center the mesh + center = vertices.mean(axis=0) + vertices = vertices - center + + # Scale to fit in view + max_extent = np.abs(vertices).max() + if max_extent > 0: + vertices = vertices / max_extent + + # Create triangles for plotting + triangles = vertices[faces] + + # Plot the mesh with a nice color scheme + from mpl_toolkits.mplot3d.art3d import Poly3DCollection + + collection = Poly3DCollection( + triangles, + alpha=1.0, + facecolor="#00AE42", # Bambu green + edgecolor="#008833", # Darker green for edges + linewidths=0.1, + ) + ax.add_collection3d(collection) + + # Set axis limits + ax.set_xlim(-1, 1) + ax.set_ylim(-1, 1) + ax.set_zlim(-1, 1) + + # Set viewing angle (isometric-ish) + ax.view_init(elev=25, azim=45) + + # Remove axes for cleaner look + ax.set_axis_off() + + # Set background color + ax.set_facecolor("#1a1a1a") + fig.patch.set_facecolor("#1a1a1a") + + # Tight layout to minimize whitespace + plt.tight_layout(pad=0) + + # Save the figure + plt.savefig( + str(output_path), + format="png", + dpi=100, + facecolor="#1a1a1a", + bbox_inches="tight", + pad_inches=0.05, + ) + plt.close(fig) + + logger.info(f"Generated STL thumbnail: {output_path}") + return True + + except ImportError as e: + logger.error(f"Missing dependency for STL thumbnails: {e}") + return False + except Exception as e: + logger.error(f"Failed to generate STL thumbnail for {stl_path}: {e}") + return False + + +def generate_stl_thumbnail_bytes( + stl_data: bytes, + size: tuple[int, int] = DEFAULT_SIZE, +) -> bytes | None: + """Generate a PNG thumbnail from STL data in memory. + + Args: + stl_data: Raw STL file data (binary or ASCII) + size: Tuple of (width, height) for the output image + + Returns: + PNG image data as bytes, or None on failure + """ + try: + import matplotlib + + matplotlib.use("Agg") + import matplotlib.pyplot as plt + import numpy as np + import trimesh + + # Load from bytes + mesh = trimesh.load( + file_obj=io.BytesIO(stl_data), + file_type="stl", + ) + + if mesh is None or not hasattr(mesh, "vertices") or len(mesh.vertices) == 0: + logger.warning("Failed to load STL from bytes or empty mesh") + return None + + # Simplify if mesh is too large + if len(mesh.vertices) > MAX_VERTICES: + target_faces = int(len(mesh.faces) * (MAX_VERTICES / len(mesh.vertices))) + try: + mesh = mesh.simplify_quadric_decimation(target_faces) + except Exception: + pass # Use original if simplification fails + + # Create figure + fig = plt.figure(figsize=(size[0] / 100, size[1] / 100), dpi=100) + ax = fig.add_subplot(111, projection="3d") + + vertices = mesh.vertices + faces = mesh.faces + + # Center and scale + center = vertices.mean(axis=0) + vertices = vertices - center + max_extent = np.abs(vertices).max() + if max_extent > 0: + vertices = vertices / max_extent + + triangles = vertices[faces] + + from mpl_toolkits.mplot3d.art3d import Poly3DCollection + + collection = Poly3DCollection( + triangles, + alpha=1.0, + facecolor="#00AE42", + edgecolor="#008833", + linewidths=0.1, + ) + ax.add_collection3d(collection) + + ax.set_xlim(-1, 1) + ax.set_ylim(-1, 1) + ax.set_zlim(-1, 1) + ax.view_init(elev=25, azim=45) + ax.set_axis_off() + ax.set_facecolor("#1a1a1a") + fig.patch.set_facecolor("#1a1a1a") + plt.tight_layout(pad=0) + + # Save to bytes buffer + buf = io.BytesIO() + plt.savefig( + buf, + format="png", + dpi=100, + facecolor="#1a1a1a", + bbox_inches="tight", + pad_inches=0.05, + ) + plt.close(fig) + + buf.seek(0) + return buf.read() + + except ImportError as e: + logger.error(f"Missing dependency for STL thumbnails: {e}") + return None + except Exception as e: + logger.error(f"Failed to generate STL thumbnail from bytes: {e}") + return None diff --git a/backend/tests/integration/test_library_api.py b/backend/tests/integration/test_library_api.py index a9ae2e5f3..1113dc189 100644 --- a/backend/tests/integration/test_library_api.py +++ b/backend/tests/integration/test_library_api.py @@ -445,3 +445,126 @@ class TestLibraryZipExtractAPI: result = response.json() assert result["extracted"] == 1 # Only real_file.txt assert result["files"][0]["filename"] == "real_file.txt" + + +class TestSTLThumbnailAPI: + """Integration tests for STL thumbnail generation endpoints.""" + + @pytest.fixture + async def stl_file_factory(self, db_session): + """Factory to create test STL files.""" + _counter = [0] + + async def _create_stl_file(**kwargs): + from backend.app.models.library import LibraryFile + + _counter[0] += 1 + counter = _counter[0] + + defaults = { + "filename": f"test_model_{counter}.stl", + "file_path": f"/test/path/test_model_{counter}.stl", + "file_size": 1024, + "file_type": "stl", + } + defaults.update(kwargs) + + lib_file = LibraryFile(**defaults) + db_session.add(lib_file) + await db_session.commit() + await db_session.refresh(lib_file) + return lib_file + + return _create_stl_file + + @pytest.fixture + async def folder_factory(self, db_session): + """Factory to create test folders.""" + _counter = [0] + + async def _create_folder(**kwargs): + from backend.app.models.library import LibraryFolder + + _counter[0] += 1 + counter = _counter[0] + + defaults = {"name": f"Test Folder {counter}"} + defaults.update(kwargs) + + folder = LibraryFolder(**defaults) + db_session.add(folder) + await db_session.commit() + await db_session.refresh(folder) + return folder + + return _create_folder + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_regenerate_thumbnail_file_not_found(self, async_client: AsyncClient, db_session): + """Verify 404 for non-existent file.""" + response = await async_client.post("/api/v1/library/files/9999/regenerate-thumbnail") + assert response.status_code == 404 + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_regenerate_thumbnail_file_missing_on_disk( + self, async_client: AsyncClient, stl_file_factory, db_session + ): + """Verify error when file exists in DB but not on disk.""" + stl_file = await stl_file_factory() + response = await async_client.post(f"/api/v1/library/files/{stl_file.id}/regenerate-thumbnail") + assert response.status_code == 404 + assert "not found on disk" in response.json()["detail"] + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_batch_generate_no_filter(self, async_client: AsyncClient, db_session): + """Verify error when no filter is specified.""" + data = {} + response = await async_client.post("/api/v1/library/generate-stl-thumbnails", json=data) + assert response.status_code == 400 + assert "Must specify" in response.json()["detail"] + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_batch_generate_all_missing_empty(self, async_client: AsyncClient, db_session): + """Verify batch generation with no matching files.""" + data = {"all_missing": True} + response = await async_client.post("/api/v1/library/generate-stl-thumbnails", json=data) + assert response.status_code == 200 + result = response.json() + assert result["processed"] == 0 + assert result["succeeded"] == 0 + assert result["failed"] == 0 + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_batch_generate_specific_files(self, async_client: AsyncClient, stl_file_factory, db_session): + """Verify batch generation with specific file IDs.""" + stl_file = await stl_file_factory() + data = {"file_ids": [stl_file.id]} + response = await async_client.post("/api/v1/library/generate-stl-thumbnails", json=data) + assert response.status_code == 200 + result = response.json() + assert result["processed"] == 1 + # Will fail because file doesn't exist on disk + assert result["failed"] == 1 + assert result["results"][0]["error"] == "File not found on disk" + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_batch_generate_by_folder( + self, async_client: AsyncClient, stl_file_factory, folder_factory, db_session + ): + """Verify batch generation by folder ID.""" + folder = await folder_factory() + await stl_file_factory(folder_id=folder.id) + await stl_file_factory(folder_id=folder.id) + await stl_file_factory() # File in root, should not be processed + + data = {"folder_id": folder.id} + response = await async_client.post("/api/v1/library/generate-stl-thumbnails", json=data) + assert response.status_code == 200 + result = response.json() + assert result["processed"] == 2 # Only files in the folder diff --git a/backend/tests/unit/services/test_stl_thumbnail.py b/backend/tests/unit/services/test_stl_thumbnail.py new file mode 100644 index 000000000..fe293a74e --- /dev/null +++ b/backend/tests/unit/services/test_stl_thumbnail.py @@ -0,0 +1,326 @@ +"""Unit tests for the STL thumbnail service.""" + +import os +import tempfile +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + + +class TestSTLThumbnailService: + """Tests for STL thumbnail generation.""" + + def test_generate_thumbnail_ascii_stl(self): + """Test generating thumbnail from ASCII STL file.""" + from backend.app.services.stl_thumbnail import generate_stl_thumbnail + + # Create a simple ASCII STL cube + ascii_stl = """solid cube + facet normal 0 0 -1 + outer loop + vertex 0 0 0 + vertex 1 0 0 + vertex 1 1 0 + endloop + endfacet + facet normal 0 0 -1 + outer loop + vertex 0 0 0 + vertex 1 1 0 + vertex 0 1 0 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 0 0 1 + vertex 1 1 1 + vertex 1 0 1 + endloop + endfacet + facet normal 0 0 1 + outer loop + vertex 0 0 1 + vertex 0 1 1 + vertex 1 1 1 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex 0 0 0 + vertex 1 0 1 + vertex 1 0 0 + endloop + endfacet + facet normal 0 -1 0 + outer loop + vertex 0 0 0 + vertex 0 0 1 + vertex 1 0 1 + endloop + endfacet + facet normal 1 0 0 + outer loop + vertex 1 0 0 + vertex 1 1 1 + vertex 1 1 0 + endloop + endfacet + facet normal 1 0 0 + outer loop + vertex 1 0 0 + vertex 1 0 1 + vertex 1 1 1 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex 0 1 0 + vertex 1 1 0 + vertex 1 1 1 + endloop + endfacet + facet normal 0 1 0 + outer loop + vertex 0 1 0 + vertex 1 1 1 + vertex 0 1 1 + endloop + endfacet + facet normal -1 0 0 + outer loop + vertex 0 0 0 + vertex 0 1 0 + vertex 0 1 1 + endloop + endfacet + facet normal -1 0 0 + outer loop + vertex 0 0 0 + vertex 0 1 1 + vertex 0 0 1 + endloop + endfacet +endsolid cube +""" + + with tempfile.NamedTemporaryFile(suffix=".stl", delete=False, mode="w") as stl_file: + stl_file.write(ascii_stl) + stl_path = stl_file.name + + with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as png_file: + png_path = png_file.name + + try: + result = generate_stl_thumbnail(stl_path, png_path) + assert result is True + assert os.path.exists(png_path) + # Check it's a valid PNG (starts with PNG magic bytes) + with open(png_path, "rb") as f: + header = f.read(8) + assert header[:4] == b"\x89PNG" + finally: + os.unlink(stl_path) + if os.path.exists(png_path): + os.unlink(png_path) + + def test_generate_thumbnail_binary_stl(self): + """Test generating thumbnail from binary STL file.""" + import struct + + from backend.app.services.stl_thumbnail import generate_stl_thumbnail + + # Create a simple binary STL cube (minimal version) + # Binary STL format: + # - 80 bytes header + # - 4 bytes number of triangles (uint32) + # - For each triangle: + # - 12 bytes normal (3 floats) + # - 36 bytes vertices (9 floats, 3 vertices x 3 coords) + # - 2 bytes attribute byte count (usually 0) + + header = b"\x00" * 80 # Empty header + num_triangles = 12 # A cube has 12 triangles (2 per face) + + # Define cube vertices + vertices = [ + # Bottom face (z=0) + ((0, 0, -1), [(0, 0, 0), (1, 0, 0), (1, 1, 0)]), + ((0, 0, -1), [(0, 0, 0), (1, 1, 0), (0, 1, 0)]), + # Top face (z=1) + ((0, 0, 1), [(0, 0, 1), (1, 1, 1), (1, 0, 1)]), + ((0, 0, 1), [(0, 0, 1), (0, 1, 1), (1, 1, 1)]), + # Front face (y=0) + ((0, -1, 0), [(0, 0, 0), (1, 0, 1), (1, 0, 0)]), + ((0, -1, 0), [(0, 0, 0), (0, 0, 1), (1, 0, 1)]), + # Back face (y=1) + ((0, 1, 0), [(0, 1, 0), (1, 1, 0), (1, 1, 1)]), + ((0, 1, 0), [(0, 1, 0), (1, 1, 1), (0, 1, 1)]), + # Left face (x=0) + ((-1, 0, 0), [(0, 0, 0), (0, 1, 0), (0, 1, 1)]), + ((-1, 0, 0), [(0, 0, 0), (0, 1, 1), (0, 0, 1)]), + # Right face (x=1) + ((1, 0, 0), [(1, 0, 0), (1, 1, 1), (1, 1, 0)]), + ((1, 0, 0), [(1, 0, 0), (1, 0, 1), (1, 1, 1)]), + ] + + binary_data = header + struct.pack("; }>(`/library/files/${fileId}/filament-requirements${plateId !== undefined ? `?plate_id=${plateId}` : ''}`), + + // STL Thumbnail Generation + regenerateFileThumbnail: (fileId: number) => + request(`/library/files/${fileId}/regenerate-thumbnail`, { + method: 'POST', + }), + batchGenerateStlThumbnails: (options: { + file_ids?: number[]; + folder_id?: number; + all_missing?: boolean; + }) => + request('/library/generate-stl-thumbnails', { + method: 'POST', + body: JSON.stringify(options), + }), }; // AMS History types @@ -3045,6 +3060,21 @@ export interface ZipExtractResponse { errors: ZipExtractError[]; } +// Batch Thumbnail Generation types +export interface BatchThumbnailResult { + file_id: number; + filename: string; + success: boolean; + error: string | null; +} + +export interface BatchThumbnailResponse { + processed: number; + succeeded: number; + failed: number; + results: BatchThumbnailResult[]; +} + // Library Queue types export interface AddToQueueResult { file_id: number; diff --git a/frontend/src/pages/FileManagerPage.tsx b/frontend/src/pages/FileManagerPage.tsx index 7ebe87dc6..74512189e 100644 --- a/frontend/src/pages/FileManagerPage.tsx +++ b/frontend/src/pages/FileManagerPage.tsx @@ -35,6 +35,7 @@ import { Printer, Pencil, Play, + ImageIcon, } from 'lucide-react'; import { api } from '../api/client'; import type { @@ -821,9 +822,10 @@ interface FileCardProps { onAddToQueue?: (id: number) => void; onPrint?: (file: LibraryFileListItem) => void; onRename?: (file: LibraryFileListItem) => void; + onRegenerateThumbnail?: (id: number) => void; } -function FileCard({ file, isSelected, onSelect, onDelete, onDownload, onAddToQueue, onPrint, onRename }: FileCardProps) { +function FileCard({ file, isSelected, onSelect, onDelete, onDownload, onAddToQueue, onPrint, onRename, onRegenerateThumbnail }: FileCardProps) { const [showActions, setShowActions] = useState(false); return ( @@ -924,6 +926,15 @@ function FileCard({ file, isSelected, onSelect, onDelete, onDownload, onAddToQue Rename )} + {onRegenerateThumbnail && ['stl', '3mf', 'gcode'].includes(file.file_type.toLowerCase()) && ( + + )} +
@@ -1604,6 +1652,7 @@ export function FileManagerPage() { onAddToQueue={(id) => addToQueueMutation.mutate([id])} onPrint={setPrintFile} onRename={(f) => setRenameItem({ type: 'file', id: f.id, name: f.filename })} + onRegenerateThumbnail={(id) => regenerateThumbnailMutation.mutate(id)} /> ))}
@@ -1720,6 +1769,16 @@ export function FileManagerPage() { > + {['stl', '3mf', 'gcode'].includes(file.file_type.toLowerCase()) && ( + + )} )} - {onRegenerateThumbnail && ['stl', '3mf', 'gcode'].includes(file.file_type.toLowerCase()) && ( - - )} - @@ -1754,7 +1706,6 @@ export function FileManagerPage() { onAddToQueue={(id) => addToQueueMutation.mutate([id])} onPrint={setPrintFile} onRename={(f) => setRenameItem({ type: 'file', id: f.id, name: f.filename })} - onRegenerateThumbnail={(id) => regenerateThumbnailMutation.mutate(id)} /> ))} @@ -1871,16 +1822,6 @@ export function FileManagerPage() { > - {['stl', '3mf', 'gcode'].includes(file.file_type.toLowerCase()) && ( - - )}