diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 03e2b7824..7bdc599d8 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -18,3 +18,4 @@ jobs: days-before-stale: 21 days-before-close: 7 stale-issue-label: 'stale' + only-labels: 'feedback' diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d64672a1..5121b1a8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ All notable changes to Bambuddy will be documented in this file. - **AMS Slot Configuration — Model Filtering & Pre-Population** — The Configure AMS Slot modal now filters filament presets by the connected printer model. Only presets matching the printer (e.g., "@BBL X1C" presets for X1C printers) and generic presets without a model suffix are shown. Local presets are filtered by their `compatible_printers` field. When re-configuring an already-configured slot, the modal pre-selects the saved preset, pre-populates the color, and auto-selects the active K-profile. The preset list auto-scrolls to the selected item. All modal strings are now fully translated in 5 locales (en, de, fr, it, ja). - **K-Profiles View — Accurate Filament Name Resolution** — K-profile filament names are now resolved from builtin filament tables and user cloud presets (via new `/cloud/filament-id-map` endpoint) instead of showing raw IDs like "GFU99" or "P4d64437". Falls back to extracting names from the profile name field. - **Print Log** — New view mode on the Archives page showing a chronological table of all print activity. Columns include date/time, print name, printer, user, status, duration, and filament. Supports filtering by search text, printer, user, status, and date range. Pagination with configurable page size. A dedicated clear button deletes only log entries without affecting archives. Data is stored in a separate `print_log_entries` database table. +- **Sync Spool Weights from AMS** — New button in Settings → Filament Tracking (built-in inventory mode) to force-sync all inventory spool weights from the live AMS remain% values of connected printers. Overwrites the database weight data with current sensor readings. Useful for recovering from corrupted weight data (e.g., after a power-off event zeroed all fill levels). Requires printers to be online. Includes a confirmation modal. +- **Notification Thumbnails for Telegram & ntfy** ([#372](https://github.com/maziggy/bambuddy/issues/372)) — Print thumbnail images are now attached to Telegram and ntfy notifications (previously only Pushover and Discord). Telegram uses the `sendPhoto` API with the image as caption attachment. ntfy sends the image as a binary PUT with `Filename` and `Message` headers. No configuration needed — images are sent automatically when available. ### Fixed - **Firmware Upload Uses Wrong Filename on Cache Hit** — The firmware update uploader cached downloaded firmware files under a mangled name (e.g., `X1C_01_09_00_10.bin`) instead of the original filename from Bambu Lab's CDN. On the first download the correct filename was uploaded to the SD card, but on subsequent attempts the cached file with the wrong name was used — causing the printer to not recognize the firmware file. Now caches using the original filename so the SD card always receives the correct file. @@ -33,7 +35,12 @@ All notable changes to Bambuddy will be documented in this file. - **Archive List View Not Labeling Failed Prints** ([#365](https://github.com/maziggy/bambuddy/issues/365)) — The archive grid view displayed a red "Failed" / "Cancelled" badge on failed and aborted prints, but the list view had no equivalent indicator. Now shows an inline status badge next to the print name in list view. - **Reprint Fails with SD Card Error for Archives Without 3MF File** ([#376](https://github.com/maziggy/bambuddy/issues/376)) — When a print was sent from an external slicer and Bambuddy couldn't download the 3MF from the printer during auto-archiving, the fallback archive had no file. Attempting to reprint such an archive tried to upload the data directory as a file, causing a confusing "SD card error." The backend now returns a clear error for file-less archives, and the frontend disables Print/Schedule/Open in Slicer buttons with a tooltip explaining that the 3MF file is unavailable. - **Inventory Spool Weight Resets After Print Completes** — After a print, the usage tracker correctly updated `weight_used` (e.g., +1.6g), but periodic AMS status updates recalculated `weight_used` from the AMS remain% sensor and overwrote the precise value. For small prints on large spools (e.g., 1.6g on 1000g), the AMS remain% stays at 100% (integer resolution = 10g steps), resetting `weight_used` back to 0. The AMS weight sync now only increases `weight_used`, never decreases it, preserving precise values from the usage tracker. +- **All Spool Fill Levels Drop to Zero When Printers Power Off** — When a printer powers off, the AMS sensor can report `remain=0` for all trays while `tray_type` is still populated. The weight sync treated 0% remain as "100% consumed," computing `weight_used = label_weight` (e.g., 1000g). The "only increase" guard passed because `label_weight > current_used + 1`, marking every assigned spool as fully consumed. The AMS weight sync now skips `remain=0` entirely — a physically empty spool is tracked by the usage tracker during the print, not by a transient AMS sensor reading. +- **Spool Edit Form Overwrites Usage-Tracked Weight** — Editing any spool field (note, color, material, etc.) sent the full form data back to the server, including `weight_used`. If the frontend cache was stale (e.g., loaded before the last print completed), saving the form would silently reset `weight_used` to the pre-print value, reverting the remaining weight to full. The form now only includes `weight_used` in the update request when the user explicitly changes the weight field. +- **K-Profile Auto-Select Fails for Non-BL Spools on Dual-Nozzle Printers** — When assigning a third-party spool to an AMS slot on dual-nozzle printers (H2D, H2D Pro), the MQTT auto-configure step crashed with `'SpoolKProfile' object has no attribute 'extruder_id'`. The K-profile model uses `extruder` (not `extruder_id`). Fixed the attribute name so K-profile matching correctly filters by nozzle on dual-extruder printers. - **Loose Archive Name Matching Could Cause Wrong Archive Reuse** ([#374](https://github.com/maziggy/bambuddy/issues/374)) — The `on_print_start` callback used `ilike('%{name}%')` to find existing "printing" archives, which meant a print named "Clip" could incorrectly match "Cable Clip" or "Clip Stand". This could cause a new print to reuse the wrong archive or skip creating one. Tightened to exact `print_name` match or exact filename variants (`.3mf`, `.gcode.3mf`). +- **Phantom Prints on Power Cycle** ([#374](https://github.com/maziggy/bambuddy/issues/374)) — The print queue uploaded `.3mf` files to the printer's SD card root (`/`) but never deleted them after the print finished. Some printers (e.g. P1S) auto-start files found in the root directory on power cycle, causing ghost prints on every reboot. Now deletes the uploaded file from the SD card after print completion (best-effort, non-blocking). +- **Spool Form Scrollbar Flicker in Edge** ([#364](https://github.com/maziggy/bambuddy/issues/364)) — The Add/Edit Spool modal's scrollable area used `overflow-y: auto`, which on Windows Edge (where scrollbars take layout space) caused the scrollbar to appear and disappear on hover — making the color picker unusable at certain zoom levels. Added `scrollbar-gutter: stable` to reserve scrollbar space and prevent layout thrashing. - **Archive Duplicate Badge Misses Name-Based Duplicates** ([#315](https://github.com/maziggy/bambuddy/issues/315)) — The duplicate badge on archive cards only matched by file content hash, so re-sliced prints of the same model (different GCODE, same print name) were not flagged as duplicates. Now also matches by print name (case-insensitive), consistent with the detail view's duplicate detection. ### Improved diff --git a/backend/app/api/routes/inventory.py b/backend/app/api/routes/inventory.py index 22f90bd64..fd9195e8e 100644 --- a/backend/app/api/routes/inventory.py +++ b/backend/app/api/routes/inventory.py @@ -748,7 +748,7 @@ async def assign_spool( matching_kp = None for kp in spool.k_profiles: if kp.printer_id == data.printer_id and kp.nozzle_diameter == nozzle_diameter: - if slot_extruder is not None and kp.extruder_id is not None and kp.extruder_id != slot_extruder: + if slot_extruder is not None and kp.extruder is not None and kp.extruder != slot_extruder: continue matching_kp = kp break @@ -954,6 +954,99 @@ async def clear_spool_usage_history( return {"status": "cleared"} +# ── AMS Weight Sync ────────────────────────────────────────────────────────── + + +@router.post("/sync-ams-weights") +async def sync_weights_from_ams( + db: AsyncSession = Depends(get_db), + _: User | None = RequirePermissionIfAuthEnabled(Permission.INVENTORY_UPDATE), +): + """Force-sync spool weight_used from live AMS remain% data. + + Overwrites the database weight_used for every assigned spool using the + current AMS remain% from connected printers. This is a manual recovery + tool — it bypasses the normal "only increase" guard. + """ + from backend.app.services.printer_manager import printer_manager + + result = await db.execute(select(SpoolAssignment).options(selectinload(SpoolAssignment.spool))) + assignments = list(result.scalars().all()) + logger.info("AMS weight sync: found %d assignments", len(assignments)) + + synced = 0 + skipped = 0 + + for assignment in assignments: + spool = assignment.spool + if not spool: + logger.debug("AMS weight sync: assignment %d has no spool", assignment.id) + skipped += 1 + continue + + state = printer_manager.get_status(assignment.printer_id) + if not state or not state.raw_data: + logger.info( + "AMS weight sync: printer %d not connected, skipping spool %d", + assignment.printer_id, + spool.id, + ) + skipped += 1 + continue + + ams_raw = state.raw_data.get("ams", []) + if isinstance(ams_raw, dict): + ams_raw = ams_raw.get("ams", []) + tray = _find_tray_in_ams_data(ams_raw, assignment.ams_id, assignment.tray_id) + if not tray: + logger.info( + "AMS weight sync: no tray data for spool %d (printer %d AMS%d-T%d)", + spool.id, + assignment.printer_id, + assignment.ams_id, + assignment.tray_id, + ) + skipped += 1 + continue + + remain_raw = tray.get("remain") + if remain_raw is None: + logger.debug("AMS weight sync: no remain value for spool %d", spool.id) + skipped += 1 + continue + + try: + remain_val = int(remain_raw) + except (TypeError, ValueError): + skipped += 1 + continue + + if remain_val < 0 or remain_val > 100: + logger.debug("AMS weight sync: invalid remain=%s for spool %d", remain_raw, spool.id) + skipped += 1 + continue + + lw = spool.label_weight or 1000 + new_used = round(lw * (100 - remain_val) / 100.0, 1) + old_used = spool.weight_used or 0 + + if round(old_used, 1) != new_used: + logger.info( + "AMS weight sync: spool %d weight_used %s -> %s (remain=%d%%)", + spool.id, + old_used, + new_used, + remain_val, + ) + spool.weight_used = new_used + synced += 1 + else: + skipped += 1 + + await db.commit() + return {"synced": synced, "skipped": skipped} + + # ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/backend/app/main.py b/backend/app/main.py index d4fafdcab..27fbc66f7 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -689,7 +689,7 @@ async def on_ams_change(printer_id: int, ams_data: list): remain_val = int(remain_raw) except (TypeError, ValueError): remain_val = -1 - if 0 <= remain_val <= 100: + if 1 <= remain_val <= 100: lw = existing_assignment.spool.label_weight or 1000 new_used = round(lw * (100 - remain_val) / 100.0, 1) current_used = existing_assignment.spool.weight_used or 0 @@ -2759,6 +2759,31 @@ async def on_print_complete(printer_id: int, data: dict): logging.getLogger(__name__).warning(f"Queue item update failed: {e}") log_timing("Queue item update") + + # Cleanup: delete uploaded file from printer SD card to prevent phantom prints (Issue #374) + # The print scheduler uploads .3mf files to the SD card root (/). Some printers (e.g. P1S) + # auto-start files found in root on power cycle, causing ghost prints. + try: + printer_info = printer_manager.get_printer(printer_id) + if printer_info and subtask_name: + from backend.app.services.bambu_ftp import delete_file_async + + remote_path = f"/{subtask_name}.3mf" + logger.info("Cleaning up uploaded file from printer SD card: %s", remote_path) + delete_result = await delete_file_async( + printer_info.ip_address, + printer_info.access_code, + remote_path, + printer_model=printer_info.model, + ) + if delete_result: + logger.info("Deleted %s from printer %s SD card", remote_path, printer_info.name) + else: + logger.debug("File delete returned False for %s (may not exist)", remote_path) + except Exception as e: + logger.debug("SD card file cleanup failed for printer %s: %s (non-critical)", printer_id, e) + + log_timing("SD card cleanup") logger.info("[CALLBACK] on_print_complete finished for printer %s, archive %s", printer_id, archive_id) diff --git a/backend/app/services/notification_service.py b/backend/app/services/notification_service.py index 646394ca2..9f98c3010 100644 --- a/backend/app/services/notification_service.py +++ b/backend/app/services/notification_service.py @@ -188,7 +188,9 @@ class NotificationService: else: return False, f"HTTP {response.status_code}: {response.text[:200]}" - async def _send_ntfy(self, config: dict, title: str, message: str) -> tuple[bool, str]: + async def _send_ntfy( + self, config: dict, title: str, message: str, image_data: bytes | None = None + ) -> tuple[bool, str]: """Send notification via ntfy.""" server = config.get("server", "https://ntfy.sh").rstrip("/") topic = config.get("topic", "").strip() @@ -204,7 +206,14 @@ class NotificationService: headers["Authorization"] = f"Bearer {auth_token}" client = await self._get_client() - response = await client.post(url, content=message, headers=headers) + + if image_data: + # ntfy supports image attachments via multipart form-data + headers["Filename"] = "photo.jpg" + headers["Message"] = message + response = await client.put(url, content=image_data, headers=headers) + else: + response = await client.post(url, content=message, headers=headers) if response.status_code in (200, 204): return True, "Message sent successfully" @@ -257,7 +266,7 @@ class NotificationService: except Exception: return False, f"HTTP {response.status_code}: {response.text[:200]}" - async def _send_telegram(self, config: dict, message: str) -> tuple[bool, str]: + async def _send_telegram(self, config: dict, message: str, image_data: bytes | None = None) -> tuple[bool, str]: """Send notification via Telegram bot.""" bot_token = config.get("bot_token", "").strip() chat_id = config.get("chat_id", "").strip() @@ -265,8 +274,6 @@ class NotificationService: if not bot_token or not chat_id: return False, "Bot token and chat ID are required" - url = f"https://api.telegram.org/bot{bot_token}/sendMessage" - # Escape underscores in the message body so Telegram Markdown # parsing doesn't break on job names like "A1_plate_8" or error # codes like "0300_0001". The title is already wrapped in *bold* @@ -276,14 +283,24 @@ class NotificationService: body_part = body_part.replace("_", "\\_") message = f"{title_part}\n{body_part}" - data = { - "chat_id": chat_id, - "text": message, - "parse_mode": "Markdown", - } - client = await self._get_client() - response = await client.post(url, json=data) + + if image_data: + # Use sendPhoto to attach the thumbnail with the caption + url = f"https://api.telegram.org/bot{bot_token}/sendPhoto" + response = await client.post( + url, + data={"chat_id": chat_id, "caption": message, "parse_mode": "Markdown"}, + files={"photo": ("photo.jpg", image_data, "image/jpeg")}, + ) + else: + url = f"https://api.telegram.org/bot{bot_token}/sendMessage" + data = { + "chat_id": chat_id, + "text": message, + "parse_mode": "Markdown", + } + response = await client.post(url, json=data) if response.status_code == 200: result = response.json() @@ -451,11 +468,11 @@ class NotificationService: if provider.provider_type == "callmebot": return await self._send_callmebot(config, f"{title}\n{message}") elif provider.provider_type == "ntfy": - return await self._send_ntfy(config, title, message) + return await self._send_ntfy(config, title, message, image_data=image_data) elif provider.provider_type == "pushover": return await self._send_pushover(config, title, message, image_data=image_data) elif provider.provider_type == "telegram": - return await self._send_telegram(config, f"*{title}*\n{message}") + return await self._send_telegram(config, f"*{title}*\n{message}", image_data=image_data) elif provider.provider_type == "email": return await self._send_email(config, title, message) elif provider.provider_type == "discord": diff --git a/backend/tests/unit/test_sync_ams_weights.py b/backend/tests/unit/test_sync_ams_weights.py new file mode 100644 index 000000000..349a2899f --- /dev/null +++ b/backend/tests/unit/test_sync_ams_weights.py @@ -0,0 +1,242 @@ +"""Unit tests for the AMS weight sync calculation logic. + +Tests the weight_used calculation and remain% validation extracted from +the POST /inventory/sync-ams-weights endpoint, without requiring a database. +""" + +import pytest + +from backend.app.api.routes.inventory import _find_tray_in_ams_data + + +def _calc_weight_used(label_weight: int | None, remain: int) -> float: + """Reproduce the weight calculation from sync_weights_from_ams.""" + lw = label_weight or 1000 + return round(lw * (100 - remain) / 100.0, 1) + + +def _is_valid_remain(remain_raw) -> tuple[bool, int]: + """Reproduce the remain% validation from sync_weights_from_ams. + + Returns (is_valid, parsed_value). parsed_value is only meaningful + when is_valid is True. + """ + if remain_raw is None: + return False, 0 + try: + val = int(remain_raw) + except (TypeError, ValueError): + return False, 0 + if val < 0 or val > 100: + return False, val + return True, val + + +class TestWeightCalculation: + """Test the weight_used = label_weight * (100 - remain) / 100 formula.""" + + def test_remain_100_means_no_usage(self): + """A full spool (remain=100) should have weight_used=0.""" + assert _calc_weight_used(1000, 100) == 0.0 + + def test_remain_50_with_1000g_spool(self): + """Half-used 1000g spool should have weight_used=500.""" + assert _calc_weight_used(1000, 50) == 500.0 + + def test_remain_0_means_fully_used(self): + """An empty spool (remain=0) should have weight_used equal to label_weight. + + Unlike the on_ams_change guard, the sync endpoint processes remain=0 + since it is a manual recovery tool. + """ + assert _calc_weight_used(1000, 0) == 1000.0 + + def test_respects_label_weight_500g(self): + """500g spool at remain=50 should have weight_used=250.""" + assert _calc_weight_used(500, 50) == 250.0 + + def test_respects_label_weight_250g(self): + """250g spool at remain=75 should have weight_used=62.5.""" + assert _calc_weight_used(250, 75) == 62.5 + + def test_none_label_weight_defaults_to_1000(self): + """When label_weight is None, it defaults to 1000g.""" + assert _calc_weight_used(None, 50) == 500.0 + + def test_result_is_rounded_to_one_decimal(self): + """Weight used should be rounded to 1 decimal place. + + For a 1000g spool at remain=33, weight_used = 1000 * 67 / 100 = 670.0 + """ + assert _calc_weight_used(1000, 33) == 670.0 + + def test_odd_fraction_rounds_correctly(self): + """750g spool at remain=33 → 750 * 67/100 = 502.5.""" + assert _calc_weight_used(750, 33) == 502.5 + + def test_small_spool_small_remain(self): + """200g spool at remain=1 → 200 * 99/100 = 198.0.""" + assert _calc_weight_used(200, 1) == 198.0 + + +class TestRemainValidation: + """Test the remain% bounds and type validation.""" + + def test_remain_minus_1_is_invalid(self): + """remain=-1 (firmware 'unknown') should be skipped.""" + valid, _ = _is_valid_remain(-1) + assert valid is False + + def test_remain_101_is_invalid(self): + """remain=101 (out of range) should be skipped.""" + valid, _ = _is_valid_remain(101) + assert valid is False + + def test_remain_negative_large_is_invalid(self): + """Large negative remain values should be skipped.""" + valid, _ = _is_valid_remain(-50) + assert valid is False + + def test_remain_200_is_invalid(self): + """remain=200 should be skipped.""" + valid, _ = _is_valid_remain(200) + assert valid is False + + def test_remain_none_is_invalid(self): + """remain=None (missing from tray data) should be skipped.""" + valid, _ = _is_valid_remain(None) + assert valid is False + + def test_remain_non_numeric_string_is_invalid(self): + """Non-numeric string remain should be skipped.""" + valid, _ = _is_valid_remain("abc") + assert valid is False + + def test_remain_0_is_valid(self): + """remain=0 should be valid (manual recovery handles empty spools).""" + valid, val = _is_valid_remain(0) + assert valid is True + assert val == 0 + + def test_remain_100_is_valid(self): + """remain=100 should be valid.""" + valid, val = _is_valid_remain(100) + assert valid is True + assert val == 100 + + def test_remain_50_is_valid(self): + """remain=50 should be valid.""" + valid, val = _is_valid_remain(50) + assert valid is True + assert val == 50 + + def test_remain_string_number_is_valid(self): + """Numeric string remain (e.g. '75') should be parsed as int.""" + valid, val = _is_valid_remain("75") + assert valid is True + assert val == 75 + + +class TestFindTrayInAmsData: + """Test the _find_tray_in_ams_data helper used by the sync endpoint.""" + + def test_finds_matching_tray(self): + """Should return the matching tray dict.""" + ams_data = [ + { + "id": 0, + "tray": [ + {"id": 0, "remain": 80}, + {"id": 1, "remain": 50}, + ], + }, + ] + tray = _find_tray_in_ams_data(ams_data, ams_id=0, tray_id=1) + assert tray is not None + assert tray["remain"] == 50 + + def test_returns_none_for_missing_ams_unit(self): + """Should return None when the AMS unit ID is not found.""" + ams_data = [{"id": 0, "tray": [{"id": 0, "remain": 80}]}] + assert _find_tray_in_ams_data(ams_data, ams_id=1, tray_id=0) is None + + def test_returns_none_for_missing_tray(self): + """Should return None when the tray ID is not found.""" + ams_data = [{"id": 0, "tray": [{"id": 0, "remain": 80}]}] + assert _find_tray_in_ams_data(ams_data, ams_id=0, tray_id=3) is None + + def test_returns_none_for_empty_data(self): + """Should return None for empty AMS data.""" + assert _find_tray_in_ams_data([], ams_id=0, tray_id=0) is None + + def test_returns_none_for_none_data(self): + """Should return None for None AMS data.""" + assert _find_tray_in_ams_data(None, ams_id=0, tray_id=0) is None + + def test_multi_ams_unit_lookup(self): + """Should find trays across multiple AMS units.""" + ams_data = [ + {"id": 0, "tray": [{"id": 0, "remain": 80}]}, + {"id": 1, "tray": [{"id": 2, "remain": 30}]}, + ] + tray = _find_tray_in_ams_data(ams_data, ams_id=1, tray_id=2) + assert tray is not None + assert tray["remain"] == 30 + + def test_ams_ht_high_id(self): + """Should find trays in AMS-HT units (id >= 128).""" + ams_data = [{"id": 128, "tray": [{"id": 0, "remain": 65}]}] + tray = _find_tray_in_ams_data(ams_data, ams_id=128, tray_id=0) + assert tray is not None + assert tray["remain"] == 65 + + +class TestSyncSkipLogic: + """Test combinations that exercise the sync/skip decision path.""" + + def test_same_value_is_skipped(self): + """When old weight_used matches new, the spool is skipped (no DB write).""" + # Simulating the endpoint logic: if round(old_used, 1) == new_used → skip + label_weight = 1000 + remain = 50 + new_used = _calc_weight_used(label_weight, remain) + old_used = 500.0 # Already matches + assert round(old_used, 1) == new_used # → would be skipped + + def test_different_value_is_synced(self): + """When old weight_used differs from new, the spool is synced.""" + label_weight = 1000 + remain = 50 + new_used = _calc_weight_used(label_weight, remain) + old_used = 300.0 # Different + assert round(old_used, 1) != new_used # → would be synced + + def test_none_old_used_treated_as_zero(self): + """When old weight_used is None (new spool), it defaults to 0.""" + old_used = None + effective_old = old_used or 0 + new_used = _calc_weight_used(1000, 80) # 200.0 + assert effective_old == 0 + assert round(effective_old, 1) != new_used # → would be synced + + def test_remain_0_synced_not_skipped(self): + """remain=0 is valid and produces weight_used=label_weight. + + This is distinct from on_ams_change behavior where remain=0 is + ignored. The sync endpoint processes it as a manual recovery tool. + """ + valid, val = _is_valid_remain(0) + assert valid is True + new_used = _calc_weight_used(1000, val) + assert new_used == 1000.0 + + def test_remain_minus_1_never_reaches_calc(self): + """remain=-1 fails validation before weight calculation.""" + valid, _ = _is_valid_remain(-1) + assert valid is False + # The endpoint would skip += 1 and continue + + def test_remain_101_never_reaches_calc(self): + """remain=101 fails validation before weight calculation.""" + valid, _ = _is_valid_remain(101) + assert valid is False diff --git a/frontend/src/__tests__/components/SpoolFormModal.test.tsx b/frontend/src/__tests__/components/SpoolFormModal.test.tsx new file mode 100644 index 000000000..a13e49b64 --- /dev/null +++ b/frontend/src/__tests__/components/SpoolFormModal.test.tsx @@ -0,0 +1,186 @@ +/** + * Tests for the SpoolFormModal weightTouched behavior. + * + * Verifies that weight_used is only included in the PATCH payload when the user + * explicitly changes the remaining weight field. This prevents stale React Query + * cache values from overwriting usage-tracked weight data on the backend. + */ + +import React from 'react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { screen, waitFor, fireEvent } from '@testing-library/react'; +import { render } from '../utils'; +import { SpoolFormModal } from '../../components/SpoolFormModal'; +import type { InventorySpool } from '../../api/client'; + +// Mock the API client +vi.mock('../../api/client', () => ({ + api: { + getSettings: vi.fn().mockResolvedValue({}), + getAuthStatus: vi.fn().mockResolvedValue({ auth_enabled: false }), + getCloudStatus: vi.fn().mockResolvedValue({ is_authenticated: false }), + getFilamentPresets: vi.fn().mockResolvedValue([]), + getSpoolCatalog: vi.fn().mockResolvedValue([]), + getColorCatalog: vi.fn().mockResolvedValue([]), + getLocalPresets: vi.fn().mockResolvedValue({ filament: [] }), + getPrinters: vi.fn().mockResolvedValue([]), + getSpoolUsageHistory: vi.fn().mockResolvedValue([]), + createSpool: vi.fn().mockResolvedValue({ id: 99 }), + updateSpool: vi.fn().mockResolvedValue({ id: 1 }), + saveSpoolKProfiles: vi.fn().mockResolvedValue([]), + }, +})); + +// Mock validateForm so we can bypass validation for the create-mode test +// (editing tests pass validation naturally since the spool has material + slicer_filament) +vi.mock('../../components/spool-form/types', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + validateForm: vi.fn().mockReturnValue({ isValid: true, errors: {} }), + }; +}); + +// Mock the toast context +const mockShowToast = vi.fn(); +vi.mock('../../contexts/ToastContext', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useToast: () => ({ showToast: mockShowToast }), + }; +}); + +import { api } from '../../api/client'; + +const existingSpool: InventorySpool = { + id: 1, + material: 'PLA', + subtype: 'Basic', + brand: 'Polymaker', + color_name: 'Red', + rgba: 'FF0000FF', + label_weight: 1000, + core_weight: 250, + weight_used: 300, + slicer_filament: 'GFL99', + slicer_filament_name: 'Generic PLA', + nozzle_temp_min: null, + nozzle_temp_max: null, + note: null, + added_full: null, + last_used: null, + encode_time: null, + tag_uid: null, + tray_uuid: null, + data_origin: null, + tag_type: null, + archived_at: null, + created_at: '2025-01-01T00:00:00Z', + updated_at: '2025-01-01T00:00:00Z', + k_profiles: [], +}; + +describe('SpoolFormModal weightTouched', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('excludes weight_used from PATCH when editing without changing weight', async () => { + render( + + ); + + // Wait for the modal to render with the edit title + await waitFor(() => { + expect(screen.getByText('Edit Spool')).toBeInTheDocument(); + }); + + // Click Save without touching the weight field + const saveButton = screen.getByRole('button', { name: /save/i }); + fireEvent.click(saveButton); + + await waitFor(() => { + expect(api.updateSpool).toHaveBeenCalledTimes(1); + }); + + const [spoolId, payload] = vi.mocked(api.updateSpool).mock.calls[0]; + expect(spoolId).toBe(1); + // weight_used must NOT be present in the payload + expect(payload).not.toHaveProperty('weight_used'); + // Other fields should still be present + expect(payload).toHaveProperty('material', 'PLA'); + expect(payload).toHaveProperty('label_weight', 1000); + }); + + it('includes weight_used in PATCH when editing and changing remaining weight', async () => { + render( + + ); + + await waitFor(() => { + expect(screen.getByText('Edit Spool')).toBeInTheDocument(); + }); + + // The remaining weight is (label_weight - weight_used) = 1000 - 300 = 700. + // The input is a number input displaying 700. Find it by its displayed value. + const remainingInput = screen.getByDisplayValue('700'); + expect(remainingInput).toBeInTheDocument(); + + // Change the remaining weight from 700 to 500 (weight_used becomes 1000 - 500 = 500) + fireEvent.change(remainingInput, { target: { value: '500' } }); + + // Click Save + const saveButton = screen.getByRole('button', { name: /save/i }); + fireEvent.click(saveButton); + + await waitFor(() => { + expect(api.updateSpool).toHaveBeenCalledTimes(1); + }); + + const [spoolId, payload] = vi.mocked(api.updateSpool).mock.calls[0]; + expect(spoolId).toBe(1); + // weight_used MUST be present since the user changed the weight + expect(payload).toHaveProperty('weight_used', 500); + }); + + it('includes weight_used when creating a new spool', async () => { + render( + + ); + + // Wait for the modal to render with the create title + await waitFor(() => { + expect(screen.getByRole('heading', { name: 'Add Spool' })).toBeInTheDocument(); + }); + + // Click the submit button (validation is mocked to always pass). + // The default form data has weight_used=0, and for create mode the condition + // if (!isEditing || weightTouched) { data.weight_used = formData.weight_used; } + // always includes weight_used since isEditing is false. + // The submit button also says "Add Spool" — use getAllByText and pick the button. + const addButtons = screen.getAllByRole('button', { name: /add spool/i }); + const submitButton = addButtons.find(btn => btn.tagName === 'BUTTON' && btn.querySelector('svg.lucide-save')); + expect(submitButton).toBeTruthy(); + fireEvent.click(submitButton!); + + await waitFor(() => { + expect(api.createSpool).toHaveBeenCalledTimes(1); + }); + + const [payload] = vi.mocked(api.createSpool).mock.calls[0]; + // weight_used MUST be included for new spools (default value 0) + expect(payload).toHaveProperty('weight_used', 0); + }); +}); diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index e72e14b08..e092bafbc 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -3498,6 +3498,8 @@ export const api = { request(`/inventory/usage?limit=${limit}${printerId ? `&printer_id=${printerId}` : ''}`), clearSpoolUsageHistory: (spoolId: number) => request<{ status: string }>(`/inventory/spools/${spoolId}/usage`, { method: 'DELETE' }), + syncWeightsFromAms: () => + request<{ synced: number; skipped: number }>('/inventory/sync-ams-weights', { method: 'POST' }), getFilamentPresets: () => request('/cloud/filaments'), diff --git a/frontend/src/components/SpoolFormModal.tsx b/frontend/src/components/SpoolFormModal.tsx index b2d5b9710..5b2a8e2eb 100644 --- a/frontend/src/components/SpoolFormModal.tsx +++ b/frontend/src/components/SpoolFormModal.tsx @@ -35,6 +35,7 @@ export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibration const [formData, setFormData] = useState(defaultFormData); const [errors, setErrors] = useState>>({}); const [activeTab, setActiveTab] = useState('filament'); + const [weightTouched, setWeightTouched] = useState(false); // Cloud presets const [cloudAuthenticated, setCloudAuthenticated] = useState(false); @@ -195,6 +196,7 @@ export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibration } setErrors({}); setActiveTab('filament'); + setWeightTouched(false); } }, [isOpen, spool]); @@ -208,6 +210,7 @@ export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibration // Update field helper const updateField = (key: K, value: SpoolFormData[K]) => { setFormData(prev => ({ ...prev, [key]: value })); + if (key === 'weight_used') setWeightTouched(true); if (errors[key]) { setErrors(prev => ({ ...prev, [key]: undefined })); } @@ -333,7 +336,6 @@ export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibration rgba: formData.rgba || null, label_weight: formData.label_weight, core_weight: formData.core_weight, - weight_used: formData.weight_used, slicer_filament: formData.slicer_filament || null, slicer_filament_name: presetName, nozzle_temp_min: null, @@ -341,6 +343,12 @@ export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibration note: formData.note || null, }; + // Only send weight_used when creating or when explicitly changed by the user. + // This prevents stale cached values from overwriting usage-tracker data. + if (!isEditing || weightTouched) { + data.weight_used = formData.weight_used; + } + if (isEditing) { updateMutation.mutate(data); } else { @@ -403,7 +411,7 @@ export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibration {/* Content */} -
+
{activeTab === 'filament' ? (
{/* Filament Info Section */} diff --git a/frontend/src/components/SpoolmanSettings.tsx b/frontend/src/components/SpoolmanSettings.tsx index bc2645f68..3a7ee9b69 100644 --- a/frontend/src/components/SpoolmanSettings.tsx +++ b/frontend/src/components/SpoolmanSettings.tsx @@ -6,6 +6,7 @@ import { api } from '../api/client'; import type { SpoolmanSyncResult, Printer } from '../api/client'; import { Card, CardContent, CardHeader } from './Card'; import { Button } from './Button'; +import { ConfirmModal } from './ConfirmModal'; import { useToast } from '../contexts/ToastContext'; export function SpoolmanSettings() { @@ -20,6 +21,7 @@ export function SpoolmanSettings() { const [selectedPrinterId, setSelectedPrinterId] = useState('all'); const [isInitialized, setIsInitialized] = useState(false); const [showAllSkipped, setShowAllSkipped] = useState(false); + const [showAmsSyncConfirm, setShowAmsSyncConfirm] = useState(false); // Fetch Spoolman settings const { data: settings, isLoading: settingsLoading } = useQuery({ @@ -136,6 +138,21 @@ export function SpoolmanSettings() { } }; + // Inventory AMS weight sync mutation + const amsSyncMutation = useMutation({ + mutationFn: api.syncWeightsFromAms, + onSuccess: (data) => { + queryClient.invalidateQueries({ queryKey: ['spools'] }); + queryClient.invalidateQueries({ queryKey: ['inventory-spools'] }); + showToast(t('settings.amsSyncSuccess', { synced: data.synced, skipped: data.skipped }), 'success'); + setShowAmsSyncConfirm(false); + }, + onError: () => { + showToast(t('settings.amsSyncError'), 'error'); + setShowAmsSyncConfirm(false); + }, + }); + // Combine mutation states const isSyncing = syncAllMutation.isPending || syncPrinterMutation.isPending; const syncResult = selectedPrinterId === 'all' ? syncAllMutation.data : syncPrinterMutation.data; @@ -236,18 +253,34 @@ export function SpoolmanSettings() { {/* Built-in Inventory details */} {!localEnabled && ( -
-
- -
-
    -
  • {t('settings.builtInFeatureRfid')}
  • -
  • {t('settings.builtInFeatureUsage')}
  • -
  • {t('settings.builtInFeatureCatalog')}
  • -
  • {t('settings.builtInFeatureThirdParty')}
  • -
+
+
+
+ +
+
    +
  • {t('settings.builtInFeatureRfid')}
  • +
  • {t('settings.builtInFeatureUsage')}
  • +
  • {t('settings.builtInFeatureCatalog')}
  • +
  • {t('settings.builtInFeatureThirdParty')}
  • +
+
+ +
)} @@ -528,6 +561,19 @@ export function SpoolmanSettings() {
)} + + {showAmsSyncConfirm && ( + amsSyncMutation.mutate()} + onCancel={() => setShowAmsSyncConfirm(false)} + /> + )} ); } diff --git a/frontend/src/i18n/locales/de.ts b/frontend/src/i18n/locales/de.ts index 8986bdfc2..37ab9ca3e 100644 --- a/frontend/src/i18n/locales/de.ts +++ b/frontend/src/i18n/locales/de.ts @@ -1204,6 +1204,12 @@ export default { builtInFeatureUsage: 'Erfasst den Filamentverbrauch pro Druck', builtInFeatureCatalog: 'Spulen, Farben und K-Faktor-Profile verwalten', builtInFeatureThirdParty: 'Drittanbieter-Spulen können Inventarspulen zugewiesen werden', + amsSyncButton: 'Gewichte vom AMS synchronisieren', + amsSyncTitle: 'Spulengewichte vom AMS synchronisieren', + amsSyncMessage: 'Alle Inventar-Spulengewichte werden mit den aktuellen AMS-Restwerten der verbundenen Drucker überschrieben. Verwenden Sie dies zur Wiederherstellung beschädigter Gewichtsdaten. Drucker müssen online sein.', + amsSyncing: 'Synchronisiere...', + amsSyncSuccess: '{{synced}} Spule(n) synchronisiert, {{skipped}} übersprungen', + amsSyncError: 'Synchronisierung der Gewichte vom AMS fehlgeschlagen', // Spoolman settings spoolmanUrl: 'Spoolman URL', spoolmanUrlHint: 'URL Ihres Spoolman-Servers (z.B. http://localhost:7912)', diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 164b9a460..258ef82d0 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -1204,6 +1204,12 @@ export default { builtInFeatureUsage: 'Tracks filament consumption per print', builtInFeatureCatalog: 'Manage spools, colors, and K-factor profiles', builtInFeatureThirdParty: 'Third-party spools can be assigned to inventory spools', + amsSyncButton: 'Sync Weights from AMS', + amsSyncTitle: 'Sync Spool Weights from AMS', + amsSyncMessage: 'This will overwrite all inventory spool weights with the current AMS remain% values from connected printers. Use this to recover from corrupted weight data. Printers must be online.', + amsSyncing: 'Syncing...', + amsSyncSuccess: '{{synced}} spool(s) synced, {{skipped}} skipped', + amsSyncError: 'Failed to sync weights from AMS', // Spoolman settings spoolmanUrl: 'Spoolman URL', spoolmanUrlHint: 'URL of your Spoolman server (e.g., http://localhost:7912)', diff --git a/frontend/src/i18n/locales/fr.ts b/frontend/src/i18n/locales/fr.ts index 4cd9de570..66930631b 100644 --- a/frontend/src/i18n/locales/fr.ts +++ b/frontend/src/i18n/locales/fr.ts @@ -1200,6 +1200,12 @@ export default { builtInFeatureUsage: 'Suit la consommation par impression', builtInFeatureCatalog: 'Gère bobines, couleurs et profils facteur K', builtInFeatureThirdParty: 'Les bobines tierces peuvent être assignées aux bobines d\'inventaire', + amsSyncButton: 'Synchroniser les poids depuis l\'AMS', + amsSyncTitle: 'Synchroniser les poids des bobines depuis l\'AMS', + amsSyncMessage: 'Tous les poids des bobines de l\'inventaire seront écrasés par les valeurs actuelles de l\'AMS des imprimantes connectées. Utilisez ceci pour récupérer des données de poids corrompues. Les imprimantes doivent être en ligne.', + amsSyncing: 'Synchronisation...', + amsSyncSuccess: '{{synced}} bobine(s) synchronisée(s), {{skipped}} ignorée(s)', + amsSyncError: 'Échec de la synchronisation des poids depuis l\'AMS', // Spoolman settings spoolmanUrl: 'URL Spoolman', spoolmanUrlHint: 'URL de votre serveur Spoolman (ex: http://localhost:7912)', diff --git a/frontend/src/i18n/locales/ja.ts b/frontend/src/i18n/locales/ja.ts index ee363aebf..58baebcf7 100644 --- a/frontend/src/i18n/locales/ja.ts +++ b/frontend/src/i18n/locales/ja.ts @@ -1452,6 +1452,12 @@ export default { builtInFeatureUsage: 'プリントごとのフィラメント消費量を追跡', builtInFeatureCatalog: 'スプール、カラー、K値プロファイルを管理', builtInFeatureThirdParty: 'サードパーティ製スプールをインベントリスプールに割り当て可能', + amsSyncButton: 'AMSから重量を同期', + amsSyncTitle: 'AMSからスプール重量を同期', + amsSyncMessage: '接続されたプリンターの現在のAMS残量値で、すべてのインベントリスプール重量を上書きします。破損した重量データの復旧に使用してください。プリンターがオンラインである必要があります。', + amsSyncing: '同期中...', + amsSyncSuccess: '{{synced}}個のスプールを同期、{{skipped}}個をスキップ', + amsSyncError: 'AMSからの重量同期に失敗しました', // Spoolman設定 spoolmanUrl: 'Spoolman URL', spoolmanUrlHint: 'Spoolmanサーバーのurl(例:http://localhost:7912)', diff --git a/static/assets/index-B6_GHLIb.js b/static/assets/index-B6_GHLIb.js new file mode 100644 index 000000000..ea83aef99 --- /dev/null +++ b/static/assets/index-B6_GHLIb.js @@ -0,0 +1,8684 @@ +function Poe(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 s of i)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(i){if(i.ep)return;i.ep=!0;const s=n(i);fetch(i.href,s)}})();var l_=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Jl(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var IT={exports:{}},ry={};var VU;function joe(){if(VU)return ry;VU=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,s){var o=null;if(s!==void 0&&(o=""+s),i.key!==void 0&&(o=""+i.key),"key"in i){s={};for(var l in i)l!=="key"&&(s[l]=i[l])}else s=i;return i=s.ref,{$$typeof:t,type:r,key:o,ref:i!==void 0?i:null,props:s}}return ry.Fragment=e,ry.jsx=n,ry.jsxs=n,ry}var GU;function Doe(){return GU||(GU=1,IT.exports=joe()),IT.exports}var a=Doe(),zT={exports:{}},Nn={};var qU;function Roe(){if(qU)return Nn;qU=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"),s=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"),h=Symbol.for("react.activity"),f=Symbol.iterator;function p(H){return H===null||typeof H!="object"?null:(H=f&&H[f]||H["@@iterator"],typeof H=="function"?H:null)}var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},v=Object.assign,b={};function y(H,X,te){this.props=H,this.context=X,this.refs=b,this.updater=te||x}y.prototype.isReactComponent={},y.prototype.setState=function(H,X){if(typeof H!="object"&&typeof H!="function"&&H!=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,H,X,"setState")},y.prototype.forceUpdate=function(H){this.updater.enqueueForceUpdate(this,H,"forceUpdate")};function S(){}S.prototype=y.prototype;function N(H,X,te){this.props=H,this.context=X,this.refs=b,this.updater=te||x}var A=N.prototype=new S;A.constructor=N,v(A,y.prototype),A.isPureReactComponent=!0;var M=Array.isArray;function T(){}var k={H:null,A:null,T:null,S:null},j=Object.prototype.hasOwnProperty;function E(H,X,te){var J=te.ref;return{$$typeof:t,type:H,key:X,ref:J!==void 0?J:null,props:te}}function P(H,X){return E(H.type,X,H.props)}function U(H){return typeof H=="object"&&H!==null&&H.$$typeof===t}function L(H){var X={"=":"=0",":":"=2"};return"$"+H.replace(/[=:]/g,function(te){return X[te]})}var B=/\/+/g;function R(H,X){return typeof H=="object"&&H!==null&&H.key!=null?L(""+H.key):X.toString(36)}function V(H){switch(H.status){case"fulfilled":return H.value;case"rejected":throw H.reason;default:switch(typeof H.status=="string"?H.then(T,T):(H.status="pending",H.then(function(X){H.status==="pending"&&(H.status="fulfilled",H.value=X)},function(X){H.status==="pending"&&(H.status="rejected",H.reason=X)})),H.status){case"fulfilled":return H.value;case"rejected":throw H.reason}}throw H}function q(H,X,te,J,Se){var fe=typeof H;(fe==="undefined"||fe==="boolean")&&(H=null);var me=!1;if(H===null)me=!0;else switch(fe){case"bigint":case"string":case"number":me=!0;break;case"object":switch(H.$$typeof){case t:case e:me=!0;break;case d:return me=H._init,q(me(H._payload),X,te,J,Se)}}if(me)return Se=Se(H),me=J===""?"."+R(H,0):J,M(Se)?(te="",me!=null&&(te=me.replace(B,"$&/")+"/"),q(Se,X,te,"",function(ce){return ce})):Se!=null&&(U(Se)&&(Se=P(Se,te+(Se.key==null||H&&H.key===Se.key?"":(""+Se.key).replace(B,"$&/")+"/")+me)),X.push(Se)),1;me=0;var De=J===""?".":J+":";if(M(H))for(var de=0;de>>1,le=q[Y];if(0>>1;Yi(te,K))Ji(Se,te)?(q[Y]=Se,q[J]=K,Y=J):(q[Y]=te,q[X]=K,Y=X);else if(Ji(Se,K))q[Y]=Se,q[J]=K,Y=J;else break e}}return I}function i(q,I){var K=q.sortIndex-I.sortIndex;return K!==0?K:q.id-I.id}if(t.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var s=performance;t.unstable_now=function(){return s.now()}}else{var o=Date,l=o.now();t.unstable_now=function(){return o.now()-l}}var c=[],u=[],d=1,h=null,f=3,p=!1,x=!1,v=!1,b=!1,y=typeof setTimeout=="function"?setTimeout:null,S=typeof clearTimeout=="function"?clearTimeout:null,N=typeof setImmediate<"u"?setImmediate:null;function A(q){for(var I=n(u);I!==null;){if(I.callback===null)r(u);else if(I.startTime<=q)r(u),I.sortIndex=I.expirationTime,e(c,I);else break;I=n(u)}}function M(q){if(v=!1,A(q),!x)if(n(c)!==null)x=!0,T||(T=!0,L());else{var I=n(u);I!==null&&V(M,I.startTime-q)}}var T=!1,k=-1,j=5,E=-1;function P(){return b?!0:!(t.unstable_now()-Eq&&P());){var Y=h.callback;if(typeof Y=="function"){h.callback=null,f=h.priorityLevel;var le=Y(h.expirationTime<=q);if(q=t.unstable_now(),typeof le=="function"){h.callback=le,A(q),I=!0;break t}h===n(c)&&r(c),A(q)}else r(c);h=n(c)}if(h!==null)I=!0;else{var H=n(u);H!==null&&V(M,H.startTime-q),I=!1}}break e}finally{h=null,f=K,p=!1}I=void 0}}finally{I?L():T=!1}}}var L;if(typeof N=="function")L=function(){N(U)};else if(typeof MessageChannel<"u"){var B=new MessageChannel,R=B.port2;B.port1.onmessage=U,L=function(){R.postMessage(null)}}else L=function(){y(U,0)};function V(q,I){k=y(function(){q(t.unstable_now())},I)}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(q){q.callback=null},t.unstable_forceFrameRate=function(q){0>q||125Y?(q.sortIndex=K,e(u,q),n(c)===null&&q===n(u)&&(v?(S(k),k=-1):v=!0,V(M,K-Y))):(q.sortIndex=le,e(c,q),x||p||(x=!0,T||(T=!0,L()))),q},t.unstable_shouldYield=P,t.unstable_wrapCallback=function(q){var I=f;return function(){var K=f;f=I;try{return q.apply(this,arguments)}finally{f=K}}}})(HT)),HT}var XU;function Ooe(){return XU||(XU=1,BT.exports=Loe()),BT.exports}var $T={exports:{}},ss={};var YU;function Ioe(){if(YU)return ss;YU=1;var t=Bb();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(),$T.exports=Ioe(),$T.exports}var ZU;function zoe(){if(ZU)return iy;ZU=1;var t=Ooe(),e=Bb(),n=gK();function r(m){var g="https://react.dev/errors/"+m;if(1le||(m.current=Y[le],Y[le]=null,le--)}function te(m,g){le++,Y[le]=m.current,m.current=g}var J=H(null),Se=H(null),fe=H(null),me=H(null);function De(m,g){switch(te(fe,g),te(Se,m),te(J,null),g.nodeType){case 9:case 11:m=(m=g.documentElement)&&(m=m.namespaceURI)?mU(m):0;break;default:if(m=g.tagName,g=g.namespaceURI)g=mU(g),m=fU(g,m);else switch(m){case"svg":m=1;break;case"math":m=2;break;default:m=0}}X(J),te(J,m)}function de(){X(J),X(Se),X(fe)}function ce(m){m.memoizedState!==null&&te(me,m);var g=J.current,_=fU(g,m.type);g!==_&&(te(Se,m),te(J,_))}function Ne(m){Se.current===m&&(X(J),X(Se)),me.current===m&&(X(me),Jx._currentValue=K)}var ie,Le;function $(m){if(ie===void 0)try{throw Error()}catch(_){var g=_.stack.trim().match(/\n( *(at )?)/);ie=g&&g[1]||"",Le=-1<_.stack.indexOf(` + at`)?" ()":-1<_.stack.indexOf("@")?"@unknown:0:0":""}return` +`+ie+m+Le}var W=!1;function ue(m,g){if(!m||W)return"";W=!0;var _=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var C={DetermineComponentFrameRoot:function(){try{if(g){var Ct=function(){throw Error()};if(Object.defineProperty(Ct.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(Ct,[])}catch(gt){var mt=gt}Reflect.construct(m,[],Ct)}else{try{Ct.call()}catch(gt){mt=gt}m.call(Ct.prototype)}}else{try{throw Error()}catch(gt){mt=gt}(Ct=m())&&typeof Ct.catch=="function"&&Ct.catch(function(){})}}catch(gt){if(gt&&mt&&typeof gt.stack=="string")return[gt.stack,mt.stack]}return[null,null]}};C.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var F=Object.getOwnPropertyDescriptor(C.DetermineComponentFrameRoot,"name");F&&F.configurable&&Object.defineProperty(C.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var O=C.DetermineComponentFrameRoot(),ne=O[0],Ce=O[1];if(ne&&Ce){var Be=ne.split(` +`),ht=Ce.split(` +`);for(F=C=0;CF||Be[C]!==ht[F]){var yt=` +`+Be[C].replace(" at new "," at ");return m.displayName&&yt.includes("")&&(yt=yt.replace("",m.displayName)),yt}while(1<=C&&0<=F);break}}}finally{W=!1,Error.prepareStackTrace=_}return(_=m?m.displayName||m.name:"")?$(_):""}function Z(m,g){switch(m.tag){case 26:case 27:case 5:return $(m.type);case 16:return $("Lazy");case 13:return m.child!==g&&g!==null?$("Suspense Fallback"):$("Suspense");case 19:return $("SuspenseList");case 0:case 15:return ue(m.type,!1);case 11:return ue(m.type.render,!1);case 1:return ue(m.type,!0);case 31:return $("Activity");default:return""}}function oe(m){try{var g="",_=null;do g+=Z(m,_),_=m,m=m.return;while(m);return g}catch(C){return` +Error generating stack: `+C.message+` +`+C.stack}}var Q=Object.prototype.hasOwnProperty,he=t.unstable_scheduleCallback,be=t.unstable_cancelCallback,ee=t.unstable_shouldYield,z=t.unstable_requestPaint,D=t.unstable_now,G=t.unstable_getCurrentPriorityLevel,pe=t.unstable_ImmediatePriority,ve=t.unstable_UserBlockingPriority,xe=t.unstable_NormalPriority,ze=t.unstable_LowPriority,_e=t.unstable_IdlePriority,He=t.log,Ze=t.unstable_setDisableYieldValue,we=null,ye=null;function ke(m){if(typeof He=="function"&&Ze(m),ye&&typeof ye.setStrictMode=="function")try{ye.setStrictMode(we,m)}catch{}}var Ue=Math.clz32?Math.clz32:Ae,je=Math.log,Ve=Math.LN2;function Ae(m){return m>>>=0,m===0?32:31-(je(m)/Ve|0)|0}var Je=256,Pe=262144,Ye=4194304;function Re(m){var g=m&42;if(g!==0)return g;switch(m&-m){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 m&261888;case 262144:case 524288:case 1048576:case 2097152:return m&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return m&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return m}}function Fe(m,g,_){var C=m.pendingLanes;if(C===0)return 0;var F=0,O=m.suspendedLanes,ne=m.pingedLanes;m=m.warmLanes;var Ce=C&134217727;return Ce!==0?(C=Ce&~O,C!==0?F=Re(C):(ne&=Ce,ne!==0?F=Re(ne):_||(_=Ce&~m,_!==0&&(F=Re(_))))):(Ce=C&~O,Ce!==0?F=Re(Ce):ne!==0?F=Re(ne):_||(_=C&~m,_!==0&&(F=Re(_)))),F===0?0:g!==0&&g!==F&&(g&O)===0&&(O=F&-F,_=g&-g,O>=_||O===32&&(_&4194048)!==0)?g:F}function We(m,g){return(m.pendingLanes&~(m.suspendedLanes&~m.pingedLanes)&g)===0}function pt(m,g){switch(m){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 At(){var m=Ye;return Ye<<=1,(Ye&62914560)===0&&(Ye=4194304),m}function Rt(m){for(var g=[],_=0;31>_;_++)g.push(m);return g}function Wt(m,g){m.pendingLanes|=g,g!==268435456&&(m.suspendedLanes=0,m.pingedLanes=0,m.warmLanes=0)}function Tt(m,g,_,C,F,O){var ne=m.pendingLanes;m.pendingLanes=_,m.suspendedLanes=0,m.pingedLanes=0,m.warmLanes=0,m.expiredLanes&=_,m.entangledLanes&=_,m.errorRecoveryDisabledLanes&=_,m.shellSuspendCounter=0;var Ce=m.entanglements,Be=m.expirationTimes,ht=m.hiddenUpdates;for(_=ne&~_;0<_;){var yt=31-Ue(_),Ct=1<"u")return null;try{return m.activeElement||m.body}catch{return m.body}}var Qr=/[\n"\\]/g;function gn(m){return m.replace(Qr,function(g){return"\\"+g.charCodeAt(0).toString(16)+" "})}function Zr(m,g,_,C,F,O,ne,Ce){m.name="",ne!=null&&typeof ne!="function"&&typeof ne!="symbol"&&typeof ne!="boolean"?m.type=ne:m.removeAttribute("type"),g!=null?ne==="number"?(g===0&&m.value===""||m.value!=g)&&(m.value=""+Mn(g)):m.value!==""+Mn(g)&&(m.value=""+Mn(g)):ne!=="submit"&&ne!=="reset"||m.removeAttribute("value"),g!=null?vi(m,ne,Mn(g)):_!=null?vi(m,ne,Mn(_)):C!=null&&m.removeAttribute("value"),F==null&&O!=null&&(m.defaultChecked=!!O),F!=null&&(m.checked=F&&typeof F!="function"&&typeof F!="symbol"),Ce!=null&&typeof Ce!="function"&&typeof Ce!="symbol"&&typeof Ce!="boolean"?m.name=""+Mn(Ce):m.removeAttribute("name")}function Wn(m,g,_,C,F,O,ne,Ce){if(O!=null&&typeof O!="function"&&typeof O!="symbol"&&typeof O!="boolean"&&(m.type=O),g!=null||_!=null){if(!(O!=="submit"&&O!=="reset"||g!=null)){sn(m);return}_=_!=null?""+Mn(_):"",g=g!=null?""+Mn(g):_,Ce||g===m.value||(m.value=g),m.defaultValue=g}C=C??F,C=typeof C!="function"&&typeof C!="symbol"&&!!C,m.checked=Ce?m.checked:!!C,m.defaultChecked=!!C,ne!=null&&typeof ne!="function"&&typeof ne!="symbol"&&typeof ne!="boolean"&&(m.name=ne),sn(m)}function vi(m,g,_){g==="number"&&Mr(m.ownerDocument)===m||m.defaultValue===""+_||(m.defaultValue=""+_)}function mi(m,g,_,C){if(m=m.options,g){g={};for(var F=0;F<_.length;F++)g["$"+_[F]]=!0;for(_=0;_"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),vr=!1;if(Ur)try{var kl={};Object.defineProperty(kl,"passive",{get:function(){vr=!0}}),window.addEventListener("test",kl,kl),window.removeEventListener("test",kl,kl)}catch{vr=!1}var Ca=null,oa=null,rc=null;function Br(){if(rc)return rc;var m,g=oa,_=g.length,C,F="value"in Ca?Ca.value:Ca.textContent,O=F.length;for(m=0;m<_&&g[m]===F[m];m++);var ne=_-m;for(C=1;C<=ne&&g[_-C]===F[O-C];C++);return rc=F.slice(m,1=vx),vI=" ",wI=!1;function _I(m,g){switch(m){case"keyup":return Jae.indexOf(g.keyCode)!==-1;case"keydown":return g.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function SI(m){return m=m.detail,typeof m=="object"&&"data"in m?m.data:null}var mp=!1;function tse(m,g){switch(m){case"compositionend":return SI(g);case"keypress":return g.which!==32?null:(wI=!0,vI);case"textInput":return m=g.data,m===vI&&wI?null:m;default:return null}}function nse(m,g){if(mp)return m==="compositionend"||!VM&&_I(m,g)?(m=Br(),rc=oa=Ca=null,mp=!1,m):null;switch(m){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-m};m=C}e:{for(;_;){if(_.nextSibling){_=_.nextSibling;break e}_=_.parentNode}_=void 0}_=PI(_)}}function DI(m,g){return m&&g?m===g?!0:m&&m.nodeType===3?!1:g&&g.nodeType===3?DI(m,g.parentNode):"contains"in m?m.contains(g):m.compareDocumentPosition?!!(m.compareDocumentPosition(g)&16):!1:!1}function RI(m){m=m!=null&&m.ownerDocument!=null&&m.ownerDocument.defaultView!=null?m.ownerDocument.defaultView:window;for(var g=Mr(m.document);g instanceof m.HTMLIFrameElement;){try{var _=typeof g.contentWindow.location.href=="string"}catch{_=!1}if(_)m=g.contentWindow;else break;g=Mr(m.document)}return g}function WM(m){var g=m&&m.nodeName&&m.nodeName.toLowerCase();return g&&(g==="input"&&(m.type==="text"||m.type==="search"||m.type==="tel"||m.type==="url"||m.type==="password")||g==="textarea"||m.contentEditable==="true")}var use=Ur&&"documentMode"in document&&11>=document.documentMode,fp=null,KM=null,Nx=null,XM=!1;function FI(m,g,_){var C=_.window===_?_.document:_.nodeType===9?_:_.ownerDocument;XM||fp==null||fp!==Mr(C)||(C=fp,"selectionStart"in C&&WM(C)?C={start:C.selectionStart,end:C.selectionEnd}:(C=(C.ownerDocument&&C.ownerDocument.defaultView||window).getSelection(),C={anchorNode:C.anchorNode,anchorOffset:C.anchorOffset,focusNode:C.focusNode,focusOffset:C.focusOffset}),Nx&&Sx(Nx,C)||(Nx=C,C=Ww(KM,"onSelect"),0>=ne,F-=ne,ic=1<<32-Ue(g)+F|_<Rn?(tr=Jt,Jt=null):tr=Jt.sibling;var dr=mt(rt,Jt,ut[Rn],St);if(dr===null){Jt===null&&(Jt=tr);break}m&&Jt&&dr.alternate===null&&g(rt,Jt),Ke=O(dr,Ke,Rn),ur===null?an=dr:ur.sibling=dr,ur=dr,Jt=tr}if(Rn===ut.length)return _(rt,Jt),rr&&Qc(rt,Rn),an;if(Jt===null){for(;RnRn?(tr=Jt,Jt=null):tr=Jt.sibling;var $d=mt(rt,Jt,dr.value,St);if($d===null){Jt===null&&(Jt=tr);break}m&&Jt&&$d.alternate===null&&g(rt,Jt),Ke=O($d,Ke,Rn),ur===null?an=$d:ur.sibling=$d,ur=$d,Jt=tr}if(dr.done)return _(rt,Jt),rr&&Qc(rt,Rn),an;if(Jt===null){for(;!dr.done;Rn++,dr=ut.next())dr=Ct(rt,dr.value,St),dr!==null&&(Ke=O(dr,Ke,Rn),ur===null?an=dr:ur.sibling=dr,ur=dr);return rr&&Qc(rt,Rn),an}for(Jt=C(Jt);!dr.done;Rn++,dr=ut.next())dr=gt(Jt,rt,Rn,dr.value,St),dr!==null&&(m&&dr.alternate!==null&&Jt.delete(dr.key===null?Rn:dr.key),Ke=O(dr,Ke,Rn),ur===null?an=dr:ur.sibling=dr,ur=dr);return m&&Jt.forEach(function(Eoe){return g(rt,Eoe)}),rr&&Qc(rt,Rn),an}function Er(rt,Ke,ut,St){if(typeof ut=="object"&&ut!==null&&ut.type===v&&ut.key===null&&(ut=ut.props.children),typeof ut=="object"&&ut!==null){switch(ut.$$typeof){case p:e:{for(var an=ut.key;Ke!==null;){if(Ke.key===an){if(an=ut.type,an===v){if(Ke.tag===7){_(rt,Ke.sibling),St=F(Ke,ut.props.children),St.return=rt,rt=St;break e}}else if(Ke.elementType===an||typeof an=="object"&&an!==null&&an.$$typeof===j&&_m(an)===Ke.type){_(rt,Ke.sibling),St=F(Ke,ut.props),Ex(St,ut),St.return=rt,rt=St;break e}_(rt,Ke);break}else g(rt,Ke);Ke=Ke.sibling}ut.type===v?(St=bm(ut.props.children,rt.mode,St,ut.key),St.return=rt,rt=St):(St=uw(ut.type,ut.key,ut.props,null,rt.mode,St),Ex(St,ut),St.return=rt,rt=St)}return ne(rt);case x:e:{for(an=ut.key;Ke!==null;){if(Ke.key===an)if(Ke.tag===4&&Ke.stateNode.containerInfo===ut.containerInfo&&Ke.stateNode.implementation===ut.implementation){_(rt,Ke.sibling),St=F(Ke,ut.children||[]),St.return=rt,rt=St;break e}else{_(rt,Ke);break}else g(rt,Ke);Ke=Ke.sibling}St=nA(ut,rt.mode,St),St.return=rt,rt=St}return ne(rt);case j:return ut=_m(ut),Er(rt,Ke,ut,St)}if(V(ut))return Qt(rt,Ke,ut,St);if(L(ut)){if(an=L(ut),typeof an!="function")throw Error(r(150));return ut=an.call(ut),hn(rt,Ke,ut,St)}if(typeof ut.then=="function")return Er(rt,Ke,bw(ut),St);if(ut.$$typeof===N)return Er(rt,Ke,mw(rt,ut),St);xw(rt,ut)}return typeof ut=="string"&&ut!==""||typeof ut=="number"||typeof ut=="bigint"?(ut=""+ut,Ke!==null&&Ke.tag===6?(_(rt,Ke.sibling),St=F(Ke,ut),St.return=rt,rt=St):(_(rt,Ke),St=tA(ut,rt.mode,St),St.return=rt,rt=St),ne(rt)):_(rt,Ke)}return function(rt,Ke,ut,St){try{Tx=0;var an=Er(rt,Ke,ut,St);return kp=null,an}catch(Jt){if(Jt===Np||Jt===pw)throw Jt;var ur=ho(29,Jt,null,rt.mode);return ur.lanes=St,ur.return=rt,ur}}}var Nm=i5(!0),a5=i5(!1),Cd=!1;function fA(m){m.updateQueue={baseState:m.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function pA(m,g){m=m.updateQueue,g.updateQueue===m&&(g.updateQueue={baseState:m.baseState,firstBaseUpdate:m.firstBaseUpdate,lastBaseUpdate:m.lastBaseUpdate,shared:m.shared,callbacks:null})}function Md(m){return{lane:m,tag:0,payload:null,callback:null,next:null}}function Ad(m,g,_){var C=m.updateQueue;if(C===null)return null;if(C=C.shared,(pr&2)!==0){var F=C.pending;return F===null?g.next=g:(g.next=F.next,F.next=g),C.pending=g,g=cw(m),HI(m,null,_),g}return lw(m,C,g,_),cw(m)}function Px(m,g,_){if(g=g.updateQueue,g!==null&&(g=g.shared,(_&4194048)!==0)){var C=g.lanes;C&=m.pendingLanes,_|=C,g.lanes=_,xt(m,_)}}function gA(m,g){var _=m.updateQueue,C=m.alternate;if(C!==null&&(C=C.updateQueue,_===C)){var F=null,O=null;if(_=_.firstBaseUpdate,_!==null){do{var ne={lane:_.lane,tag:_.tag,payload:_.payload,callback:null,next:null};O===null?F=O=ne:O=O.next=ne,_=_.next}while(_!==null);O===null?F=O=g:O=O.next=g}else F=O=g;_={baseState:C.baseState,firstBaseUpdate:F,lastBaseUpdate:O,shared:C.shared,callbacks:C.callbacks},m.updateQueue=_;return}m=_.lastBaseUpdate,m===null?_.firstBaseUpdate=g:m.next=g,_.lastBaseUpdate=g}var bA=!1;function jx(){if(bA){var m=Sp;if(m!==null)throw m}}function Dx(m,g,_,C){bA=!1;var F=m.updateQueue;Cd=!1;var O=F.firstBaseUpdate,ne=F.lastBaseUpdate,Ce=F.shared.pending;if(Ce!==null){F.shared.pending=null;var Be=Ce,ht=Be.next;Be.next=null,ne===null?O=ht:ne.next=ht,ne=Be;var yt=m.alternate;yt!==null&&(yt=yt.updateQueue,Ce=yt.lastBaseUpdate,Ce!==ne&&(Ce===null?yt.firstBaseUpdate=ht:Ce.next=ht,yt.lastBaseUpdate=Be))}if(O!==null){var Ct=F.baseState;ne=0,yt=ht=Be=null,Ce=O;do{var mt=Ce.lane&-536870913,gt=mt!==Ce.lane;if(gt?(er&mt)===mt:(C&mt)===mt){mt!==0&&mt===_p&&(bA=!0),yt!==null&&(yt=yt.next={lane:0,tag:Ce.tag,payload:Ce.payload,callback:null,next:null});e:{var Qt=m,hn=Ce;mt=g;var Er=_;switch(hn.tag){case 1:if(Qt=hn.payload,typeof Qt=="function"){Ct=Qt.call(Er,Ct,mt);break e}Ct=Qt;break e;case 3:Qt.flags=Qt.flags&-65537|128;case 0:if(Qt=hn.payload,mt=typeof Qt=="function"?Qt.call(Er,Ct,mt):Qt,mt==null)break e;Ct=h({},Ct,mt);break e;case 2:Cd=!0}}mt=Ce.callback,mt!==null&&(m.flags|=64,gt&&(m.flags|=8192),gt=F.callbacks,gt===null?F.callbacks=[mt]:gt.push(mt))}else gt={lane:mt,tag:Ce.tag,payload:Ce.payload,callback:Ce.callback,next:null},yt===null?(ht=yt=gt,Be=Ct):yt=yt.next=gt,ne|=mt;if(Ce=Ce.next,Ce===null){if(Ce=F.shared.pending,Ce===null)break;gt=Ce,Ce=gt.next,gt.next=null,F.lastBaseUpdate=gt,F.shared.pending=null}}while(!0);yt===null&&(Be=Ct),F.baseState=Be,F.firstBaseUpdate=ht,F.lastBaseUpdate=yt,O===null&&(F.shared.lanes=0),Dd|=ne,m.lanes=ne,m.memoizedState=Ct}}function s5(m,g){if(typeof m!="function")throw Error(r(191,m));m.call(g)}function o5(m,g){var _=m.callbacks;if(_!==null)for(m.callbacks=null,m=0;m<_.length;m++)s5(_[m],g)}var Cp=H(null),yw=H(0);function l5(m,g){m=lu,te(yw,m),te(Cp,g),lu=m|g.baseLanes}function xA(){te(yw,lu),te(Cp,Cp.current)}function yA(){lu=yw.current,X(Cp),X(yw)}var mo=H(null),$o=null;function Td(m){var g=m.alternate;te(Oi,Oi.current&1),te(mo,m),$o===null&&(g===null||Cp.current!==null||g.memoizedState!==null)&&($o=m)}function vA(m){te(Oi,Oi.current),te(mo,m),$o===null&&($o=m)}function c5(m){m.tag===22?(te(Oi,Oi.current),te(mo,m),$o===null&&($o=m)):Ed()}function Ed(){te(Oi,Oi.current),te(mo,mo.current)}function fo(m){X(mo),$o===m&&($o=null),X(Oi)}var Oi=H(0);function vw(m){for(var g=m;g!==null;){if(g.tag===13){var _=g.memoizedState;if(_!==null&&(_=_.dehydrated,_===null||CT(_)||MT(_)))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===m)break;for(;g.sibling===null;){if(g.return===null||g.return===m)return null;g=g.return}g.sibling.return=g.return,g=g.sibling}return null}var eu=0,En=null,Ar=null,qi=null,ww=!1,Mp=!1,km=!1,_w=0,Rx=0,Ap=null,vse=0;function Ni(){throw Error(r(321))}function wA(m,g){if(g===null)return!1;for(var _=0;_O?O:8;var ne=q.T,Ce={};q.T=Ce,LA(m,!1,g,_);try{var Be=F(),ht=q.S;if(ht!==null&&ht(Ce,Be),Be!==null&&typeof Be=="object"&&typeof Be.then=="function"){var yt=yse(Be,C);Lx(m,g,yt,bo(m))}else Lx(m,g,C,bo(m))}catch(Ct){Lx(m,g,{then:function(){},status:"rejected",reason:Ct},bo())}finally{I.p=O,ne!==null&&Ce.types!==null&&(ne.types=Ce.types),q.T=ne}}function kse(){}function RA(m,g,_,C){if(m.tag!==5)throw Error(r(476));var F=z5(m).queue;I5(m,F,g,K,_===null?kse:function(){return U5(m),_(C)})}function z5(m){var g=m.memoizedState;if(g!==null)return g;g={memoizedState:K,baseState:K,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:tu,lastRenderedState:K},next:null};var _={};return g.next={memoizedState:_,baseState:_,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:tu,lastRenderedState:_},next:null},m.memoizedState=g,m=m.alternate,m!==null&&(m.memoizedState=g),g}function U5(m){var g=z5(m);g.next===null&&(g=m.alternate.memoizedState),Lx(m,g.next.queue,{},bo())}function FA(){return Ha(Jx)}function B5(){return Ii().memoizedState}function H5(){return Ii().memoizedState}function Cse(m){for(var g=m.return;g!==null;){switch(g.tag){case 24:case 3:var _=bo();m=Md(_);var C=Ad(g,m,_);C!==null&&(qs(C,g,_),Px(C,g,_)),g={cache:uA()},m.payload=g;return}g=g.return}}function Mse(m,g,_){var C=bo();_={lane:C,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null},Aw(m)?V5(g,_):(_=JM(m,g,_,C),_!==null&&(qs(_,m,C),G5(_,g,C)))}function $5(m,g,_){var C=bo();Lx(m,g,_,C)}function Lx(m,g,_,C){var F={lane:C,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null};if(Aw(m))V5(g,F);else{var O=m.alternate;if(m.lanes===0&&(O===null||O.lanes===0)&&(O=g.lastRenderedReducer,O!==null))try{var ne=g.lastRenderedState,Ce=O(ne,_);if(F.hasEagerState=!0,F.eagerState=Ce,uo(Ce,ne))return lw(m,g,F,0),Hr===null&&ow(),!1}catch{}if(_=JM(m,g,F,C),_!==null)return qs(_,m,C),G5(_,g,C),!0}return!1}function LA(m,g,_,C){if(C={lane:2,revertLane:fT(),gesture:null,action:C,hasEagerState:!1,eagerState:null,next:null},Aw(m)){if(g)throw Error(r(479))}else g=JM(m,_,C,2),g!==null&&qs(g,m,2)}function Aw(m){var g=m.alternate;return m===En||g!==null&&g===En}function V5(m,g){Mp=ww=!0;var _=m.pending;_===null?g.next=g:(g.next=_.next,_.next=g),m.pending=g}function G5(m,g,_){if((_&4194048)!==0){var C=g.lanes;C&=m.pendingLanes,_|=C,g.lanes=_,xt(m,_)}}var Ox={readContext:Ha,use:Nw,useCallback:Ni,useContext:Ni,useEffect:Ni,useImperativeHandle:Ni,useLayoutEffect:Ni,useInsertionEffect:Ni,useMemo:Ni,useReducer:Ni,useRef:Ni,useState:Ni,useDebugValue:Ni,useDeferredValue:Ni,useTransition:Ni,useSyncExternalStore:Ni,useId:Ni,useHostTransitionStatus:Ni,useFormState:Ni,useActionState:Ni,useOptimistic:Ni,useMemoCache:Ni,useCacheRefresh:Ni};Ox.useEffectEvent=Ni;var q5={readContext:Ha,use:Nw,useCallback:function(m,g){return Cs().memoizedState=[m,g===void 0?null:g],m},useContext:Ha,useEffect:T5,useImperativeHandle:function(m,g,_){_=_!=null?_.concat([m]):null,Cw(4194308,4,D5.bind(null,g,m),_)},useLayoutEffect:function(m,g){return Cw(4194308,4,m,g)},useInsertionEffect:function(m,g){Cw(4,2,m,g)},useMemo:function(m,g){var _=Cs();g=g===void 0?null:g;var C=m();if(km){ke(!0);try{m()}finally{ke(!1)}}return _.memoizedState=[C,g],C},useReducer:function(m,g,_){var C=Cs();if(_!==void 0){var F=_(g);if(km){ke(!0);try{_(g)}finally{ke(!1)}}}else F=g;return C.memoizedState=C.baseState=F,m={pending:null,lanes:0,dispatch:null,lastRenderedReducer:m,lastRenderedState:F},C.queue=m,m=m.dispatch=Mse.bind(null,En,m),[C.memoizedState,m]},useRef:function(m){var g=Cs();return m={current:m},g.memoizedState=m},useState:function(m){m=TA(m);var g=m.queue,_=$5.bind(null,En,g);return g.dispatch=_,[m.memoizedState,_]},useDebugValue:jA,useDeferredValue:function(m,g){var _=Cs();return DA(_,m,g)},useTransition:function(){var m=TA(!1);return m=I5.bind(null,En,m.queue,!0,!1),Cs().memoizedState=m,[!1,m]},useSyncExternalStore:function(m,g,_){var C=En,F=Cs();if(rr){if(_===void 0)throw Error(r(407));_=_()}else{if(_=g(),Hr===null)throw Error(r(349));(er&127)!==0||m5(C,g,_)}F.memoizedState=_;var O={value:_,getSnapshot:g};return F.queue=O,T5(p5.bind(null,C,O,m),[m]),C.flags|=2048,Tp(9,{destroy:void 0},f5.bind(null,C,O,_,g),null),_},useId:function(){var m=Cs(),g=Hr.identifierPrefix;if(rr){var _=ac,C=ic;_=(C&~(1<<32-Ue(C)-1)).toString(32)+_,g="_"+g+"R_"+_,_=_w++,0<_&&(g+="H"+_.toString(32)),g+="_"}else _=vse++,g="_"+g+"r_"+_.toString(32)+"_";return m.memoizedState=g},useHostTransitionStatus:FA,useFormState:N5,useActionState:N5,useOptimistic:function(m){var g=Cs();g.memoizedState=g.baseState=m;var _={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return g.queue=_,g=LA.bind(null,En,!0,_),_.dispatch=g,[m,g]},useMemoCache:CA,useCacheRefresh:function(){return Cs().memoizedState=Cse.bind(null,En)},useEffectEvent:function(m){var g=Cs(),_={impl:m};return g.memoizedState=_,function(){if((pr&2)!==0)throw Error(r(440));return _.impl.apply(void 0,arguments)}}},OA={readContext:Ha,use:Nw,useCallback:F5,useContext:Ha,useEffect:PA,useImperativeHandle:R5,useInsertionEffect:P5,useLayoutEffect:j5,useMemo:L5,useReducer:kw,useRef:A5,useState:function(){return kw(tu)},useDebugValue:jA,useDeferredValue:function(m,g){var _=Ii();return O5(_,Ar.memoizedState,m,g)},useTransition:function(){var m=kw(tu)[0],g=Ii().memoizedState;return[typeof m=="boolean"?m:Fx(m),g]},useSyncExternalStore:h5,useId:B5,useHostTransitionStatus:FA,useFormState:k5,useActionState:k5,useOptimistic:function(m,g){var _=Ii();return x5(_,Ar,m,g)},useMemoCache:CA,useCacheRefresh:H5};OA.useEffectEvent=E5;var W5={readContext:Ha,use:Nw,useCallback:F5,useContext:Ha,useEffect:PA,useImperativeHandle:R5,useInsertionEffect:P5,useLayoutEffect:j5,useMemo:L5,useReducer:AA,useRef:A5,useState:function(){return AA(tu)},useDebugValue:jA,useDeferredValue:function(m,g){var _=Ii();return Ar===null?DA(_,m,g):O5(_,Ar.memoizedState,m,g)},useTransition:function(){var m=AA(tu)[0],g=Ii().memoizedState;return[typeof m=="boolean"?m:Fx(m),g]},useSyncExternalStore:h5,useId:B5,useHostTransitionStatus:FA,useFormState:M5,useActionState:M5,useOptimistic:function(m,g){var _=Ii();return Ar!==null?x5(_,Ar,m,g):(_.baseState=m,[m,_.queue.dispatch])},useMemoCache:CA,useCacheRefresh:H5};W5.useEffectEvent=E5;function IA(m,g,_,C){g=m.memoizedState,_=_(C,g),_=_==null?g:h({},g,_),m.memoizedState=_,m.lanes===0&&(m.updateQueue.baseState=_)}var zA={enqueueSetState:function(m,g,_){m=m._reactInternals;var C=bo(),F=Md(C);F.payload=g,_!=null&&(F.callback=_),g=Ad(m,F,C),g!==null&&(qs(g,m,C),Px(g,m,C))},enqueueReplaceState:function(m,g,_){m=m._reactInternals;var C=bo(),F=Md(C);F.tag=1,F.payload=g,_!=null&&(F.callback=_),g=Ad(m,F,C),g!==null&&(qs(g,m,C),Px(g,m,C))},enqueueForceUpdate:function(m,g){m=m._reactInternals;var _=bo(),C=Md(_);C.tag=2,g!=null&&(C.callback=g),g=Ad(m,C,_),g!==null&&(qs(g,m,_),Px(g,m,_))}};function K5(m,g,_,C,F,O,ne){return m=m.stateNode,typeof m.shouldComponentUpdate=="function"?m.shouldComponentUpdate(C,O,ne):g.prototype&&g.prototype.isPureReactComponent?!Sx(_,C)||!Sx(F,O):!0}function X5(m,g,_,C){m=g.state,typeof g.componentWillReceiveProps=="function"&&g.componentWillReceiveProps(_,C),typeof g.UNSAFE_componentWillReceiveProps=="function"&&g.UNSAFE_componentWillReceiveProps(_,C),g.state!==m&&zA.enqueueReplaceState(g,g.state,null)}function Cm(m,g){var _=g;if("ref"in g){_={};for(var C in g)C!=="ref"&&(_[C]=g[C])}if(m=m.defaultProps){_===g&&(_=h({},_));for(var F in m)_[F]===void 0&&(_[F]=m[F])}return _}function Y5(m){sw(m)}function Q5(m){console.error(m)}function Z5(m){sw(m)}function Tw(m,g){try{var _=m.onUncaughtError;_(g.value,{componentStack:g.stack})}catch(C){setTimeout(function(){throw C})}}function J5(m,g,_){try{var C=m.onCaughtError;C(_.value,{componentStack:_.stack,errorBoundary:g.tag===1?g.stateNode:null})}catch(F){setTimeout(function(){throw F})}}function UA(m,g,_){return _=Md(_),_.tag=3,_.payload={element:null},_.callback=function(){Tw(m,g)},_}function ez(m){return m=Md(m),m.tag=3,m}function tz(m,g,_,C){var F=_.type.getDerivedStateFromError;if(typeof F=="function"){var O=C.value;m.payload=function(){return F(O)},m.callback=function(){J5(g,_,C)}}var ne=_.stateNode;ne!==null&&typeof ne.componentDidCatch=="function"&&(m.callback=function(){J5(g,_,C),typeof F!="function"&&(Rd===null?Rd=new Set([this]):Rd.add(this));var Ce=C.stack;this.componentDidCatch(C.value,{componentStack:Ce!==null?Ce:""})})}function Ase(m,g,_,C,F){if(_.flags|=32768,C!==null&&typeof C=="object"&&typeof C.then=="function"){if(g=_.alternate,g!==null&&wp(g,_,F,!0),_=mo.current,_!==null){switch(_.tag){case 31:case 13:return $o===null?Bw():_.alternate===null&&ki===0&&(ki=3),_.flags&=-257,_.flags|=65536,_.lanes=F,C===gw?_.flags|=16384:(g=_.updateQueue,g===null?_.updateQueue=new Set([C]):g.add(C),dT(m,C,F)),!1;case 22:return _.flags|=65536,C===gw?_.flags|=16384:(g=_.updateQueue,g===null?(g={transitions:null,markerInstances:null,retryQueue:new Set([C])},_.updateQueue=g):(_=g.retryQueue,_===null?g.retryQueue=new Set([C]):_.add(C)),dT(m,C,F)),!1}throw Error(r(435,_.tag))}return dT(m,C,F),Bw(),!1}if(rr)return g=mo.current,g!==null?((g.flags&65536)===0&&(g.flags|=256),g.flags|=65536,g.lanes=F,C!==aA&&(m=Error(r(422),{cause:C}),Cx(zo(m,_)))):(C!==aA&&(g=Error(r(423),{cause:C}),Cx(zo(g,_))),m=m.current.alternate,m.flags|=65536,F&=-F,m.lanes|=F,C=zo(C,_),F=UA(m.stateNode,C,F),gA(m,F),ki!==4&&(ki=2)),!1;var O=Error(r(520),{cause:C});if(O=zo(O,_),Gx===null?Gx=[O]:Gx.push(O),ki!==4&&(ki=2),g===null)return!0;C=zo(C,_),_=g;do{switch(_.tag){case 3:return _.flags|=65536,m=F&-F,_.lanes|=m,m=UA(_.stateNode,C,m),gA(_,m),!1;case 1:if(g=_.type,O=_.stateNode,(_.flags&128)===0&&(typeof g.getDerivedStateFromError=="function"||O!==null&&typeof O.componentDidCatch=="function"&&(Rd===null||!Rd.has(O))))return _.flags|=65536,F&=-F,_.lanes|=F,F=ez(F),tz(F,m,_,C),gA(_,F),!1}_=_.return}while(_!==null);return!1}var BA=Error(r(461)),Wi=!1;function $a(m,g,_,C){g.child=m===null?a5(g,null,_,C):Nm(g,m.child,_,C)}function nz(m,g,_,C,F){_=_.render;var O=g.ref;if("ref"in C){var ne={};for(var Ce in C)Ce!=="ref"&&(ne[Ce]=C[Ce])}else ne=C;return vm(g),C=_A(m,g,_,ne,O,F),Ce=SA(),m!==null&&!Wi?(NA(m,g,F),nu(m,g,F)):(rr&&Ce&&rA(g),g.flags|=1,$a(m,g,C,F),g.child)}function rz(m,g,_,C,F){if(m===null){var O=_.type;return typeof O=="function"&&!eA(O)&&O.defaultProps===void 0&&_.compare===null?(g.tag=15,g.type=O,iz(m,g,O,C,F)):(m=uw(_.type,null,C,g,g.mode,F),m.ref=g.ref,m.return=g,g.child=m)}if(O=m.child,!XA(m,F)){var ne=O.memoizedProps;if(_=_.compare,_=_!==null?_:Sx,_(ne,C)&&m.ref===g.ref)return nu(m,g,F)}return g.flags|=1,m=Yc(O,C),m.ref=g.ref,m.return=g,g.child=m}function iz(m,g,_,C,F){if(m!==null){var O=m.memoizedProps;if(Sx(O,C)&&m.ref===g.ref)if(Wi=!1,g.pendingProps=C=O,XA(m,F))(m.flags&131072)!==0&&(Wi=!0);else return g.lanes=m.lanes,nu(m,g,F)}return HA(m,g,_,C,F)}function az(m,g,_,C){var F=C.children,O=m!==null?m.memoizedState:null;if(m===null&&g.stateNode===null&&(g.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),C.mode==="hidden"){if((g.flags&128)!==0){if(O=O!==null?O.baseLanes|_:_,m!==null){for(C=g.child=m.child,F=0;C!==null;)F=F|C.lanes|C.childLanes,C=C.sibling;C=F&~O}else C=0,g.child=null;return sz(m,g,O,_,C)}if((_&536870912)!==0)g.memoizedState={baseLanes:0,cachePool:null},m!==null&&fw(g,O!==null?O.cachePool:null),O!==null?l5(g,O):xA(),c5(g);else return C=g.lanes=536870912,sz(m,g,O!==null?O.baseLanes|_:_,_,C)}else O!==null?(fw(g,O.cachePool),l5(g,O),Ed(),g.memoizedState=null):(m!==null&&fw(g,null),xA(),Ed());return $a(m,g,F,_),g.child}function Ix(m,g){return m!==null&&m.tag===22||g.stateNode!==null||(g.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),g.sibling}function sz(m,g,_,C,F){var O=hA();return O=O===null?null:{parent:Gi._currentValue,pool:O},g.memoizedState={baseLanes:_,cachePool:O},m!==null&&fw(g,null),xA(),c5(g),m!==null&&wp(m,g,C,!0),g.childLanes=F,null}function Ew(m,g){return g=jw({mode:g.mode,children:g.children},m.mode),g.ref=m.ref,m.child=g,g.return=m,g}function oz(m,g,_){return Nm(g,m.child,null,_),m=Ew(g,g.pendingProps),m.flags|=2,fo(g),g.memoizedState=null,m}function Tse(m,g,_){var C=g.pendingProps,F=(g.flags&128)!==0;if(g.flags&=-129,m===null){if(rr){if(C.mode==="hidden")return m=Ew(g,C),g.lanes=536870912,Ix(null,m);if(vA(g),(m=Jr)?(m=yU(m,Ho),m=m!==null&&m.data==="&"?m:null,m!==null&&(g.memoizedState={dehydrated:m,treeContext:_d!==null?{id:ic,overflow:ac}:null,retryLane:536870912,hydrationErrors:null},_=VI(m),_.return=g,g.child=_,Ba=g,Jr=null)):m=null,m===null)throw Nd(g);return g.lanes=536870912,null}return Ew(g,C)}var O=m.memoizedState;if(O!==null){var ne=O.dehydrated;if(vA(g),F)if(g.flags&256)g.flags&=-257,g=oz(m,g,_);else if(g.memoizedState!==null)g.child=m.child,g.flags|=128,g=null;else throw Error(r(558));else if(Wi||wp(m,g,_,!1),F=(_&m.childLanes)!==0,Wi||F){if(C=Hr,C!==null&&(ne=vt(C,_),ne!==0&&ne!==O.retryLane))throw O.retryLane=ne,gm(m,ne),qs(C,m,ne),BA;Bw(),g=oz(m,g,_)}else m=O.treeContext,Jr=Vo(ne.nextSibling),Ba=g,rr=!0,Sd=null,Ho=!1,m!==null&&WI(g,m),g=Ew(g,C),g.flags|=4096;return g}return m=Yc(m.child,{mode:C.mode,children:C.children}),m.ref=g.ref,g.child=m,m.return=g,m}function Pw(m,g){var _=g.ref;if(_===null)m!==null&&m.ref!==null&&(g.flags|=4194816);else{if(typeof _!="function"&&typeof _!="object")throw Error(r(284));(m===null||m.ref!==_)&&(g.flags|=4194816)}}function HA(m,g,_,C,F){return vm(g),_=_A(m,g,_,C,void 0,F),C=SA(),m!==null&&!Wi?(NA(m,g,F),nu(m,g,F)):(rr&&C&&rA(g),g.flags|=1,$a(m,g,_,F),g.child)}function lz(m,g,_,C,F,O){return vm(g),g.updateQueue=null,_=d5(g,C,_,F),u5(m),C=SA(),m!==null&&!Wi?(NA(m,g,O),nu(m,g,O)):(rr&&C&&rA(g),g.flags|=1,$a(m,g,_,O),g.child)}function cz(m,g,_,C,F){if(vm(g),g.stateNode===null){var O=bp,ne=_.contextType;typeof ne=="object"&&ne!==null&&(O=Ha(ne)),O=new _(C,O),g.memoizedState=O.state!==null&&O.state!==void 0?O.state:null,O.updater=zA,g.stateNode=O,O._reactInternals=g,O=g.stateNode,O.props=C,O.state=g.memoizedState,O.refs={},fA(g),ne=_.contextType,O.context=typeof ne=="object"&&ne!==null?Ha(ne):bp,O.state=g.memoizedState,ne=_.getDerivedStateFromProps,typeof ne=="function"&&(IA(g,_,ne,C),O.state=g.memoizedState),typeof _.getDerivedStateFromProps=="function"||typeof O.getSnapshotBeforeUpdate=="function"||typeof O.UNSAFE_componentWillMount!="function"&&typeof O.componentWillMount!="function"||(ne=O.state,typeof O.componentWillMount=="function"&&O.componentWillMount(),typeof O.UNSAFE_componentWillMount=="function"&&O.UNSAFE_componentWillMount(),ne!==O.state&&zA.enqueueReplaceState(O,O.state,null),Dx(g,C,O,F),jx(),O.state=g.memoizedState),typeof O.componentDidMount=="function"&&(g.flags|=4194308),C=!0}else if(m===null){O=g.stateNode;var Ce=g.memoizedProps,Be=Cm(_,Ce);O.props=Be;var ht=O.context,yt=_.contextType;ne=bp,typeof yt=="object"&&yt!==null&&(ne=Ha(yt));var Ct=_.getDerivedStateFromProps;yt=typeof Ct=="function"||typeof O.getSnapshotBeforeUpdate=="function",Ce=g.pendingProps!==Ce,yt||typeof O.UNSAFE_componentWillReceiveProps!="function"&&typeof O.componentWillReceiveProps!="function"||(Ce||ht!==ne)&&X5(g,O,C,ne),Cd=!1;var mt=g.memoizedState;O.state=mt,Dx(g,C,O,F),jx(),ht=g.memoizedState,Ce||mt!==ht||Cd?(typeof Ct=="function"&&(IA(g,_,Ct,C),ht=g.memoizedState),(Be=Cd||K5(g,_,Be,C,mt,ht,ne))?(yt||typeof O.UNSAFE_componentWillMount!="function"&&typeof O.componentWillMount!="function"||(typeof O.componentWillMount=="function"&&O.componentWillMount(),typeof O.UNSAFE_componentWillMount=="function"&&O.UNSAFE_componentWillMount()),typeof O.componentDidMount=="function"&&(g.flags|=4194308)):(typeof O.componentDidMount=="function"&&(g.flags|=4194308),g.memoizedProps=C,g.memoizedState=ht),O.props=C,O.state=ht,O.context=ne,C=Be):(typeof O.componentDidMount=="function"&&(g.flags|=4194308),C=!1)}else{O=g.stateNode,pA(m,g),ne=g.memoizedProps,yt=Cm(_,ne),O.props=yt,Ct=g.pendingProps,mt=O.context,ht=_.contextType,Be=bp,typeof ht=="object"&&ht!==null&&(Be=Ha(ht)),Ce=_.getDerivedStateFromProps,(ht=typeof Ce=="function"||typeof O.getSnapshotBeforeUpdate=="function")||typeof O.UNSAFE_componentWillReceiveProps!="function"&&typeof O.componentWillReceiveProps!="function"||(ne!==Ct||mt!==Be)&&X5(g,O,C,Be),Cd=!1,mt=g.memoizedState,O.state=mt,Dx(g,C,O,F),jx();var gt=g.memoizedState;ne!==Ct||mt!==gt||Cd||m!==null&&m.dependencies!==null&&hw(m.dependencies)?(typeof Ce=="function"&&(IA(g,_,Ce,C),gt=g.memoizedState),(yt=Cd||K5(g,_,yt,C,mt,gt,Be)||m!==null&&m.dependencies!==null&&hw(m.dependencies))?(ht||typeof O.UNSAFE_componentWillUpdate!="function"&&typeof O.componentWillUpdate!="function"||(typeof O.componentWillUpdate=="function"&&O.componentWillUpdate(C,gt,Be),typeof O.UNSAFE_componentWillUpdate=="function"&&O.UNSAFE_componentWillUpdate(C,gt,Be)),typeof O.componentDidUpdate=="function"&&(g.flags|=4),typeof O.getSnapshotBeforeUpdate=="function"&&(g.flags|=1024)):(typeof O.componentDidUpdate!="function"||ne===m.memoizedProps&&mt===m.memoizedState||(g.flags|=4),typeof O.getSnapshotBeforeUpdate!="function"||ne===m.memoizedProps&&mt===m.memoizedState||(g.flags|=1024),g.memoizedProps=C,g.memoizedState=gt),O.props=C,O.state=gt,O.context=Be,C=yt):(typeof O.componentDidUpdate!="function"||ne===m.memoizedProps&&mt===m.memoizedState||(g.flags|=4),typeof O.getSnapshotBeforeUpdate!="function"||ne===m.memoizedProps&&mt===m.memoizedState||(g.flags|=1024),C=!1)}return O=C,Pw(m,g),C=(g.flags&128)!==0,O||C?(O=g.stateNode,_=C&&typeof _.getDerivedStateFromError!="function"?null:O.render(),g.flags|=1,m!==null&&C?(g.child=Nm(g,m.child,null,F),g.child=Nm(g,null,_,F)):$a(m,g,_,F),g.memoizedState=O.state,m=g.child):m=nu(m,g,F),m}function uz(m,g,_,C){return xm(),g.flags|=256,$a(m,g,_,C),g.child}var $A={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function VA(m){return{baseLanes:m,cachePool:JI()}}function GA(m,g,_){return m=m!==null?m.childLanes&~_:0,g&&(m|=go),m}function dz(m,g,_){var C=g.pendingProps,F=!1,O=(g.flags&128)!==0,ne;if((ne=O)||(ne=m!==null&&m.memoizedState===null?!1:(Oi.current&2)!==0),ne&&(F=!0,g.flags&=-129),ne=(g.flags&32)!==0,g.flags&=-33,m===null){if(rr){if(F?Td(g):Ed(),(m=Jr)?(m=yU(m,Ho),m=m!==null&&m.data!=="&"?m:null,m!==null&&(g.memoizedState={dehydrated:m,treeContext:_d!==null?{id:ic,overflow:ac}:null,retryLane:536870912,hydrationErrors:null},_=VI(m),_.return=g,g.child=_,Ba=g,Jr=null)):m=null,m===null)throw Nd(g);return MT(m)?g.lanes=32:g.lanes=536870912,null}var Ce=C.children;return C=C.fallback,F?(Ed(),F=g.mode,Ce=jw({mode:"hidden",children:Ce},F),C=bm(C,F,_,null),Ce.return=g,C.return=g,Ce.sibling=C,g.child=Ce,C=g.child,C.memoizedState=VA(_),C.childLanes=GA(m,ne,_),g.memoizedState=$A,Ix(null,C)):(Td(g),qA(g,Ce))}var Be=m.memoizedState;if(Be!==null&&(Ce=Be.dehydrated,Ce!==null)){if(O)g.flags&256?(Td(g),g.flags&=-257,g=WA(m,g,_)):g.memoizedState!==null?(Ed(),g.child=m.child,g.flags|=128,g=null):(Ed(),Ce=C.fallback,F=g.mode,C=jw({mode:"visible",children:C.children},F),Ce=bm(Ce,F,_,null),Ce.flags|=2,C.return=g,Ce.return=g,C.sibling=Ce,g.child=C,Nm(g,m.child,null,_),C=g.child,C.memoizedState=VA(_),C.childLanes=GA(m,ne,_),g.memoizedState=$A,g=Ix(null,C));else if(Td(g),MT(Ce)){if(ne=Ce.nextSibling&&Ce.nextSibling.dataset,ne)var ht=ne.dgst;ne=ht,C=Error(r(419)),C.stack="",C.digest=ne,Cx({value:C,source:null,stack:null}),g=WA(m,g,_)}else if(Wi||wp(m,g,_,!1),ne=(_&m.childLanes)!==0,Wi||ne){if(ne=Hr,ne!==null&&(C=vt(ne,_),C!==0&&C!==Be.retryLane))throw Be.retryLane=C,gm(m,C),qs(ne,m,C),BA;CT(Ce)||Bw(),g=WA(m,g,_)}else CT(Ce)?(g.flags|=192,g.child=m.child,g=null):(m=Be.treeContext,Jr=Vo(Ce.nextSibling),Ba=g,rr=!0,Sd=null,Ho=!1,m!==null&&WI(g,m),g=qA(g,C.children),g.flags|=4096);return g}return F?(Ed(),Ce=C.fallback,F=g.mode,Be=m.child,ht=Be.sibling,C=Yc(Be,{mode:"hidden",children:C.children}),C.subtreeFlags=Be.subtreeFlags&65011712,ht!==null?Ce=Yc(ht,Ce):(Ce=bm(Ce,F,_,null),Ce.flags|=2),Ce.return=g,C.return=g,C.sibling=Ce,g.child=C,Ix(null,C),C=g.child,Ce=m.child.memoizedState,Ce===null?Ce=VA(_):(F=Ce.cachePool,F!==null?(Be=Gi._currentValue,F=F.parent!==Be?{parent:Be,pool:Be}:F):F=JI(),Ce={baseLanes:Ce.baseLanes|_,cachePool:F}),C.memoizedState=Ce,C.childLanes=GA(m,ne,_),g.memoizedState=$A,Ix(m.child,C)):(Td(g),_=m.child,m=_.sibling,_=Yc(_,{mode:"visible",children:C.children}),_.return=g,_.sibling=null,m!==null&&(ne=g.deletions,ne===null?(g.deletions=[m],g.flags|=16):ne.push(m)),g.child=_,g.memoizedState=null,_)}function qA(m,g){return g=jw({mode:"visible",children:g},m.mode),g.return=m,m.child=g}function jw(m,g){return m=ho(22,m,null,g),m.lanes=0,m}function WA(m,g,_){return Nm(g,m.child,null,_),m=qA(g,g.pendingProps.children),m.flags|=2,g.memoizedState=null,m}function hz(m,g,_){m.lanes|=g;var C=m.alternate;C!==null&&(C.lanes|=g),lA(m.return,g,_)}function KA(m,g,_,C,F,O){var ne=m.memoizedState;ne===null?m.memoizedState={isBackwards:g,rendering:null,renderingStartTime:0,last:C,tail:_,tailMode:F,treeForkCount:O}:(ne.isBackwards=g,ne.rendering=null,ne.renderingStartTime=0,ne.last=C,ne.tail=_,ne.tailMode=F,ne.treeForkCount=O)}function mz(m,g,_){var C=g.pendingProps,F=C.revealOrder,O=C.tail;C=C.children;var ne=Oi.current,Ce=(ne&2)!==0;if(Ce?(ne=ne&1|2,g.flags|=128):ne&=1,te(Oi,ne),$a(m,g,C,_),C=rr?kx:0,!Ce&&m!==null&&(m.flags&128)!==0)e:for(m=g.child;m!==null;){if(m.tag===13)m.memoizedState!==null&&hz(m,_,g);else if(m.tag===19)hz(m,_,g);else if(m.child!==null){m.child.return=m,m=m.child;continue}if(m===g)break e;for(;m.sibling===null;){if(m.return===null||m.return===g)break e;m=m.return}m.sibling.return=m.return,m=m.sibling}switch(F){case"forwards":for(_=g.child,F=null;_!==null;)m=_.alternate,m!==null&&vw(m)===null&&(F=_),_=_.sibling;_=F,_===null?(F=g.child,g.child=null):(F=_.sibling,_.sibling=null),KA(g,!1,F,_,O,C);break;case"backwards":case"unstable_legacy-backwards":for(_=null,F=g.child,g.child=null;F!==null;){if(m=F.alternate,m!==null&&vw(m)===null){g.child=F;break}m=F.sibling,F.sibling=_,_=F,F=m}KA(g,!0,_,null,O,C);break;case"together":KA(g,!1,null,null,void 0,C);break;default:g.memoizedState=null}return g.child}function nu(m,g,_){if(m!==null&&(g.dependencies=m.dependencies),Dd|=g.lanes,(_&g.childLanes)===0)if(m!==null){if(wp(m,g,_,!1),(_&g.childLanes)===0)return null}else return null;if(m!==null&&g.child!==m.child)throw Error(r(153));if(g.child!==null){for(m=g.child,_=Yc(m,m.pendingProps),g.child=_,_.return=g;m.sibling!==null;)m=m.sibling,_=_.sibling=Yc(m,m.pendingProps),_.return=g;_.sibling=null}return g.child}function XA(m,g){return(m.lanes&g)!==0?!0:(m=m.dependencies,!!(m!==null&&hw(m)))}function Ese(m,g,_){switch(g.tag){case 3:De(g,g.stateNode.containerInfo),kd(g,Gi,m.memoizedState.cache),xm();break;case 27:case 5:ce(g);break;case 4:De(g,g.stateNode.containerInfo);break;case 10:kd(g,g.type,g.memoizedProps.value);break;case 31:if(g.memoizedState!==null)return g.flags|=128,vA(g),null;break;case 13:var C=g.memoizedState;if(C!==null)return C.dehydrated!==null?(Td(g),g.flags|=128,null):(_&g.child.childLanes)!==0?dz(m,g,_):(Td(g),m=nu(m,g,_),m!==null?m.sibling:null);Td(g);break;case 19:var F=(m.flags&128)!==0;if(C=(_&g.childLanes)!==0,C||(wp(m,g,_,!1),C=(_&g.childLanes)!==0),F){if(C)return mz(m,g,_);g.flags|=128}if(F=g.memoizedState,F!==null&&(F.rendering=null,F.tail=null,F.lastEffect=null),te(Oi,Oi.current),C)break;return null;case 22:return g.lanes=0,az(m,g,_,g.pendingProps);case 24:kd(g,Gi,m.memoizedState.cache)}return nu(m,g,_)}function fz(m,g,_){if(m!==null)if(m.memoizedProps!==g.pendingProps)Wi=!0;else{if(!XA(m,_)&&(g.flags&128)===0)return Wi=!1,Ese(m,g,_);Wi=(m.flags&131072)!==0}else Wi=!1,rr&&(g.flags&1048576)!==0&&qI(g,kx,g.index);switch(g.lanes=0,g.tag){case 16:e:{var C=g.pendingProps;if(m=_m(g.elementType),g.type=m,typeof m=="function")eA(m)?(C=Cm(m,C),g.tag=1,g=cz(null,g,m,C,_)):(g.tag=0,g=HA(null,g,m,C,_));else{if(m!=null){var F=m.$$typeof;if(F===A){g.tag=11,g=nz(null,g,m,C,_);break e}else if(F===k){g.tag=14,g=rz(null,g,m,C,_);break e}}throw g=R(m)||m,Error(r(306,g,""))}}return g;case 0:return HA(m,g,g.type,g.pendingProps,_);case 1:return C=g.type,F=Cm(C,g.pendingProps),cz(m,g,C,F,_);case 3:e:{if(De(g,g.stateNode.containerInfo),m===null)throw Error(r(387));C=g.pendingProps;var O=g.memoizedState;F=O.element,pA(m,g),Dx(g,C,null,_);var ne=g.memoizedState;if(C=ne.cache,kd(g,Gi,C),C!==O.cache&&cA(g,[Gi],_,!0),jx(),C=ne.element,O.isDehydrated)if(O={element:C,isDehydrated:!1,cache:ne.cache},g.updateQueue.baseState=O,g.memoizedState=O,g.flags&256){g=uz(m,g,C,_);break e}else if(C!==F){F=zo(Error(r(424)),g),Cx(F),g=uz(m,g,C,_);break e}else for(m=g.stateNode.containerInfo,m.nodeType===9?m=m.body:m=m.nodeName==="HTML"?m.ownerDocument.body:m,Jr=Vo(m.firstChild),Ba=g,rr=!0,Sd=null,Ho=!0,_=a5(g,null,C,_),g.child=_;_;)_.flags=_.flags&-3|4096,_=_.sibling;else{if(xm(),C===F){g=nu(m,g,_);break e}$a(m,g,C,_)}g=g.child}return g;case 26:return Pw(m,g),m===null?(_=kU(g.type,null,g.pendingProps,null))?g.memoizedState=_:rr||(_=g.type,m=g.pendingProps,C=Kw(fe.current).createElement(_),C[Pn]=g,C[cr]=m,Va(C,_,m),Ge(C),g.stateNode=C):g.memoizedState=kU(g.type,m.memoizedProps,g.pendingProps,m.memoizedState),null;case 27:return ce(g),m===null&&rr&&(C=g.stateNode=_U(g.type,g.pendingProps,fe.current),Ba=g,Ho=!0,F=Jr,Id(g.type)?(AT=F,Jr=Vo(C.firstChild)):Jr=F),$a(m,g,g.pendingProps.children,_),Pw(m,g),m===null&&(g.flags|=4194304),g.child;case 5:return m===null&&rr&&((F=C=Jr)&&(C=soe(C,g.type,g.pendingProps,Ho),C!==null?(g.stateNode=C,Ba=g,Jr=Vo(C.firstChild),Ho=!1,F=!0):F=!1),F||Nd(g)),ce(g),F=g.type,O=g.pendingProps,ne=m!==null?m.memoizedProps:null,C=O.children,ST(F,O)?C=null:ne!==null&&ST(F,ne)&&(g.flags|=32),g.memoizedState!==null&&(F=_A(m,g,wse,null,null,_),Jx._currentValue=F),Pw(m,g),$a(m,g,C,_),g.child;case 6:return m===null&&rr&&((m=_=Jr)&&(_=ooe(_,g.pendingProps,Ho),_!==null?(g.stateNode=_,Ba=g,Jr=null,m=!0):m=!1),m||Nd(g)),null;case 13:return dz(m,g,_);case 4:return De(g,g.stateNode.containerInfo),C=g.pendingProps,m===null?g.child=Nm(g,null,C,_):$a(m,g,C,_),g.child;case 11:return nz(m,g,g.type,g.pendingProps,_);case 7:return $a(m,g,g.pendingProps,_),g.child;case 8:return $a(m,g,g.pendingProps.children,_),g.child;case 12:return $a(m,g,g.pendingProps.children,_),g.child;case 10:return C=g.pendingProps,kd(g,g.type,C.value),$a(m,g,C.children,_),g.child;case 9:return F=g.type._context,C=g.pendingProps.children,vm(g),F=Ha(F),C=C(F),g.flags|=1,$a(m,g,C,_),g.child;case 14:return rz(m,g,g.type,g.pendingProps,_);case 15:return iz(m,g,g.type,g.pendingProps,_);case 19:return mz(m,g,_);case 31:return Tse(m,g,_);case 22:return az(m,g,_,g.pendingProps);case 24:return vm(g),C=Ha(Gi),m===null?(F=hA(),F===null&&(F=Hr,O=uA(),F.pooledCache=O,O.refCount++,O!==null&&(F.pooledCacheLanes|=_),F=O),g.memoizedState={parent:C,cache:F},fA(g),kd(g,Gi,F)):((m.lanes&_)!==0&&(pA(m,g),Dx(g,null,null,_),jx()),F=m.memoizedState,O=g.memoizedState,F.parent!==C?(F={parent:C,cache:C},g.memoizedState=F,g.lanes===0&&(g.memoizedState=g.updateQueue.baseState=F),kd(g,Gi,C)):(C=O.cache,kd(g,Gi,C),C!==F.cache&&cA(g,[Gi],_,!0))),$a(m,g,g.pendingProps.children,_),g.child;case 29:throw g.pendingProps}throw Error(r(156,g.tag))}function ru(m){m.flags|=4}function YA(m,g,_,C,F){if((g=(m.mode&32)!==0)&&(g=!1),g){if(m.flags|=16777216,(F&335544128)===F)if(m.stateNode.complete)m.flags|=8192;else if(Bz())m.flags|=8192;else throw Sm=gw,mA}else m.flags&=-16777217}function pz(m,g){if(g.type!=="stylesheet"||(g.state.loading&4)!==0)m.flags&=-16777217;else if(m.flags|=16777216,!EU(g))if(Bz())m.flags|=8192;else throw Sm=gw,mA}function Dw(m,g){g!==null&&(m.flags|=4),m.flags&16384&&(g=m.tag!==22?At():536870912,m.lanes|=g,Dp|=g)}function zx(m,g){if(!rr)switch(m.tailMode){case"hidden":g=m.tail;for(var _=null;g!==null;)g.alternate!==null&&(_=g),g=g.sibling;_===null?m.tail=null:_.sibling=null;break;case"collapsed":_=m.tail;for(var C=null;_!==null;)_.alternate!==null&&(C=_),_=_.sibling;C===null?g||m.tail===null?m.tail=null:m.tail.sibling=null:C.sibling=null}}function ei(m){var g=m.alternate!==null&&m.alternate.child===m.child,_=0,C=0;if(g)for(var F=m.child;F!==null;)_|=F.lanes|F.childLanes,C|=F.subtreeFlags&65011712,C|=F.flags&65011712,F.return=m,F=F.sibling;else for(F=m.child;F!==null;)_|=F.lanes|F.childLanes,C|=F.subtreeFlags,C|=F.flags,F.return=m,F=F.sibling;return m.subtreeFlags|=C,m.childLanes=_,g}function Pse(m,g,_){var C=g.pendingProps;switch(iA(g),g.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return ei(g),null;case 1:return ei(g),null;case 3:return _=g.stateNode,C=null,m!==null&&(C=m.memoizedState.cache),g.memoizedState.cache!==C&&(g.flags|=2048),Jc(Gi),de(),_.pendingContext&&(_.context=_.pendingContext,_.pendingContext=null),(m===null||m.child===null)&&(vp(g)?ru(g):m===null||m.memoizedState.isDehydrated&&(g.flags&256)===0||(g.flags|=1024,sA())),ei(g),null;case 26:var F=g.type,O=g.memoizedState;return m===null?(ru(g),O!==null?(ei(g),pz(g,O)):(ei(g),YA(g,F,null,C,_))):O?O!==m.memoizedState?(ru(g),ei(g),pz(g,O)):(ei(g),g.flags&=-16777217):(m=m.memoizedProps,m!==C&&ru(g),ei(g),YA(g,F,m,C,_)),null;case 27:if(Ne(g),_=fe.current,F=g.type,m!==null&&g.stateNode!=null)m.memoizedProps!==C&&ru(g);else{if(!C){if(g.stateNode===null)throw Error(r(166));return ei(g),null}m=J.current,vp(g)?KI(g):(m=_U(F,C,_),g.stateNode=m,ru(g))}return ei(g),null;case 5:if(Ne(g),F=g.type,m!==null&&g.stateNode!=null)m.memoizedProps!==C&&ru(g);else{if(!C){if(g.stateNode===null)throw Error(r(166));return ei(g),null}if(O=J.current,vp(g))KI(g);else{var ne=Kw(fe.current);switch(O){case 1:O=ne.createElementNS("http://www.w3.org/2000/svg",F);break;case 2:O=ne.createElementNS("http://www.w3.org/1998/Math/MathML",F);break;default:switch(F){case"svg":O=ne.createElementNS("http://www.w3.org/2000/svg",F);break;case"math":O=ne.createElementNS("http://www.w3.org/1998/Math/MathML",F);break;case"script":O=ne.createElement("div"),O.innerHTML=" - + +