diff --git a/CHANGELOG.md b/CHANGELOG.md index cf49917..26cf75c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this Make the dashboard follow what each solver can actually do: panels and action buttons are now derived from the solver adapter instead of being shown for every solver. +### Added +- `POST /api/sync_backends`, and a Refresh button that uses it: for a case an execution backend owns, Refresh now asks the server to pull fresh data before rereading. It used to reread local files that only change when the background sync pass runs, so the panels looked frozen between two passes. Throttled to one pass every ten seconds and never failing, so clicking repeatedly costs nothing and a provider outage does not break the button. `csauto status` shares the same throttle +- `ExecutionBackend` boundary (`csauto/backends/`): a case can be launched on a remote execution service instead of a local process or a Slurm job. A backend implements five verbs (`submit`, `poll`, `sync`, `fetch_final`, `cancel`) and translates its own state names into csauto statuses; the core never sees a provider's API. The shipped `fake` backend covers the whole lifecycle in tests with no network, the way the `stub` solver does without a solver. Two new settings, `backend_poll_interval_s` and `backend_sync_interval_s` +- Qarnot cloud execution (`backend = "qarnot"`, optional `[qarnot]` extra): a campaign can run on your own Qarnot account through the generic `docker-batch` profile, with the same docker image used locally. The shared mesh directories travel once per campaign and the case inputs once per case; while a run is in progress only the files the solver adapter declares in `observability_globs` come back, so residuals, probes and logs are live in the dashboard without downloading a multi-gigabyte results directory. The token is read from `QARNOT_TOKEN` and is rejected if found in `csauto.toml`. `csauto doctor --backend qarnot` reports each prerequisite. code_saturne and the stub solver only; code_aster builds no remote command yet +- A **Run on** selector in the dashboard's Run dialog: a launch can be sent to an execution backend instead of the machine hosting the server, chosen per launch rather than per campaign, so one case can be verified locally while the rest go to the cloud. Choosing a backend restates the case count and that the run is billed to your own account before the button is pressed. The list comes from `GET /api/app_config`, so the frontend names no provider; `POST /api/run_case` accepts a matching `backend` field. +- Per-launch execution options: a backend declares what a user may choose and the Run dialog renders it, so choosing Qarnot now offers a scheduling **Priority** (`Flex`, `OnDemand`, `Reserved`) and a **Node type** read from your own account. The core carries the chosen values without interpreting them, which keeps a provider's vocabulary inside its own module, and records them in the case history. The catalogue comes from a dedicated `GET /api/launch_options`, called when the dialog opens rather than on every page load, cached for ten minutes, and degraded rather than failed when the provider is unreachable: a launch never depends on it + ### Changed - Dashboard panels and action buttons are derived from what the solver adapter implements, rather than declared: a panel appears when the adapter provides what feeds it (`find_residuals_files`, `list_probe_files`, or a non-empty `compare_kinds` / `performance_columns` / `control_actions`), and the Restart, Stop, control and Open GUI controls follow the same rule. Solvers other than code_saturne lose the panels and buttons they could never feed: code_aster and the stub solver now show Status, Compare, Log Tail and Recent Errors only. code_saturne is unchanged. Adapters can no longer declare `dashboard_panels`, which now raises `TypeError` at import time - `csauto doctor` reports the panels and capabilities derived for the configured solver ### Fixed +- A run killed by code_saturne's runaway-computation check was reported `DONE`. The solver prints its closing banner before aborting and writes the real message to `error`, not to the log, so the log heuristic read the run as a success; the explicit `run_status.failed` marker beside it was only consulted when the log gave no verdict at all. The marker now decides, and the log is the fallback +- LAST ITER stopped at the iteration a run was at partway through. code_saturne writes a `run_status.running` marker and removes it when the calculation ends, but a copy pulled back by an execution backend's snapshot survives, and `read_progress` kept preferring it: a finished case reported the iteration that snapshot was taken at, for ever. Progress is now read from the log once a case is terminal. A second defect surfaced with it: `extract_last_iteration` did not recognise `TIME STEP NUMBER`, the marker the solver actually announces, and only worked when incidental warnings happened to mention a step +- Selecting a case with no results in the Residuals, Probes or Profiles panel made the panel vanish, taking the case selector with it and leaving no way to pick another one. The controls were rendered inside the same condition as the plot, so an empty result set hid both; they now render whenever the campaign has cases, and the plot area says which of the two is missing. The Probes and Profiles tabs had a second cause: the loader overwrote the flag that enables a tab, which answers "does this campaign have probe data at all", with one scoped to the current selection, so picking a case that had not run disabled the tab itself +- The RESU size column stopped moving. The cached figure was keyed on the results directory's mtime, and some filesystems, WSL2 among them, never bump a directory's mtime when files are created or changed inside it, so the cache never invalidated and the size stayed frozen for the life of the process. It now also expires after ten seconds +- The live residual curve plotted the wrong abscissa. While a run is in progress the points come from the solver log, and each one was numbered by counting the convergence blocks printed so far rather than reading the time step the solver announces. Convergence is printed at the listing frequency, so the Nth block is almost never iteration N: a run at step 5000 showed a curve ending at 44. Affects every run, local ones included; it is simply more visible on a long one +- A case launched on Qarnot could land on a machine with fewer cores than the run asked for. The rank and thread counts only ever reached the solver, inside `DOCKER_CMD`, while the task itself carried no hardware constraint, so Qarnot allocated any available machine and MPI oversubscribed on paid compute. Submission now asks for at least `n` x `nt` cores +- A code_saturne case launched on Qarnot failed in preprocessing with `Mesh file ... not found (no mesh directory given)`. A remote task has one writable directory, so the campaign's shared directories land inside the case rather than beside it, while code_saturne resolves a bare mesh name against `/MESH`. Adapters gain `prepare_remote_case`, called before any backend launch, and code_saturne's adds a `` entry to `setup.xml`; the entry is harmless locally, where the study directory stays a fallback. A task now returns only its results directory, so the shared mesh it was handed is not re-downloaded on every run +- Asking Qarnot for a minimum core count broke every launch with `Some constraints don't exist. Invalid hardware constraints.` Hardware constraints are validated against a per-account catalogue and cannot be invented, so the core constraint is now sent only when the account actually offers it, and dropped otherwise. The node type chosen in the dialog comes from that same catalogue, so it is always sent +- `registry_lock` deadlocked against itself when taken twice in the same thread, freezing the whole process silently and permanently. `REGISTRY_THREAD_LOCK` is an `RLock`, so a nested call passed straight through it, but `flock` applies per file descriptor and the nested call opened a second one, which waited on the first. Launching a case on the Qarnot backend hit exactly this (`_shared_bucket` reads the registry to decide whether the shared mesh still needs uploading) and hung the server before a single request reached the provider. The lock is now genuinely re-entrant within a thread +- Launching a case on an execution backend froze the whole dashboard. The submit uploads the case and talks to a remote API, and it ran inside `registry_transaction`, so every reader of `registry.json` blocked for its full duration: `/api/status` calls `load_registry`, which takes the same lock, and the UI showed nothing changing while the case sat in `PENDING`. The submit now runs with no lock held and writes its outcome back under its own short transaction, the three-phase discipline the synchronisation pass already followed +- Stopping a case on an execution backend that had just finished raised instead of doing nothing. Qarnot refuses to abort a finished task, so `kill_case` returned HTTP 500 on a case that completed between the last status refresh and the button press; a terminal task is now left alone, and the same refusal is tolerated when the task ends mid-call +- `csauto doctor --backend qarnot` reported only that a token was present, so an exhausted bucket or storage quota was discovered as a `QuotaExceeded` in the middle of an upload, after a campaign had been chosen and launched. It now reports the account's bucket count and storage use, and warns before either runs out - A case whose launch failed outright (no docker or `sbatch` on PATH, an unreadable binary, a rejected `sbatch` submission) stayed `PENDING` forever. `_launch_local` and `_launch_slurm` record `status=FAILED` and then raise to report the failure, but `registry_transaction` skipped its save whenever the body raised, so that write was discarded. The save now runs in a `finally`, which also stops `csauto prepare` from discarding the registry entries of cases it already created on disk when it aborts part way - A case launched from the web UI stayed `RUNNING` forever when the run crashed before the solver started (a missing mesh, say). Two causes, both fixed: `is_process_alive` reported a zombie as alive, because `os.kill(pid, 0)` succeeds on a process that exited but was never reaped, which is what every run launched by the long-lived server becomes; and `detect_run_outcome` only read `run_solver.log` and `listing`, so a failure that produces neither went undetected even though code_saturne writes an explicit `run_status.failed` marker next to them. Zombies are now reported as dead (and reaped when we are the parent), and the status markers are read when the logs give no verdict - Opening the solver GUI on a case whose shared dirs are symlinks (the default since `mesh_mode = "symlink"` became the default in 0.4.1) left those symlinks dangling inside the container: `build_gui_command` (docker) and the singularity branch of `build_runtime_gui_command` mounted only the runs dir, unlike their `run` counterparts which also bind the symlink targets. Both now bind them the same way, `MESH` read-only and `POST` writable diff --git a/README.md b/README.md index 207c31e..fc7ba10 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,7 @@ To re-enable: `csauto enable-telemetry` - [Web UI guide](docs/web-ui.md) - [CLI reference](docs/cli.md) - [Configuration](docs/config.md) +- [Running on Qarnot](docs/qarnot.md) - [DOE format](docs/doe-format.md) - [Run lifecycle](docs/run-lifecycle.md) - [HTTP API](docs/api.md) diff --git a/csauto/backend_sync.py b/csauto/backend_sync.py new file mode 100644 index 0000000..81a4711 --- /dev/null +++ b/csauto/backend_sync.py @@ -0,0 +1,173 @@ +"""One synchronisation pass over the cases a remote backend owns. + +Kept out of runner.py, which is already the largest module in the project. The +web server calls this on a timer; `csauto status` calls it once, throttled. + +Follows the three-phase pattern `_count_running_cases` uses: snapshot under a +brief lock, do the network work with no lock held, write back under a new lock. +Holding the registry lock across a network round trip would block the CLI and +every other writer for its duration. +""" + +from __future__ import annotations + +import contextlib +from collections.abc import Callable +from datetime import datetime +from pathlib import Path +from typing import Any + +from .backends import ExecutionBackend, get_backend +from .registry import ( + STATUS_DONE, + STATUS_FAILED, + STATUS_RUNNING, + load_registry, + mutate_registry, + timestamp_now, +) +from .warn import warn + +TERMINAL_STATUSES = (STATUS_DONE, STATUS_FAILED) + +# A user may click Refresh as fast as they like, and `csauto status` may be run +# in a loop. Neither should become a provider round trip each time. +SYNC_THROTTLE_S = 10.0 + + +def sync_backend_cases( + runs_dir: Path, + *, + sync_files: bool = True, + backend_factory: Callable[[str], ExecutionBackend] = get_backend, +) -> int: + """Poll every unfinished backend case once. Returns how many were touched.""" + # Phase 1: snapshot the cases a backend owns, under a brief lock. + snapshot: dict[str, dict[str, Any]] = {} + for case_id, record in load_registry(runs_dir).items(): + if not str(record.get("backend") or "") or not str(record.get("task_id") or ""): + continue + if record.get("status") in TERMINAL_STATUSES and record.get("results_fetched"): + continue + snapshot[case_id] = dict(record) + + # Phase 2: network work, with no lock held. + updates: dict[str, dict[str, Any]] = {} + for case_id, record in snapshot.items(): + backend = backend_factory(str(record["backend"])) + updates[case_id] = _observe(runs_dir, case_id, record, backend, str(record["task_id"]), sync_files) + + # Phase 3: write back under a new lock, skipping any case relaunched meanwhile. + if updates: + + def apply(registry: dict[str, dict[str, Any]]) -> bool: + changed = False + for case_id, fields in updates.items(): + current = registry.get(case_id) + if current is None: + continue + if str(current.get("task_id") or "") != str(snapshot[case_id].get("task_id") or ""): + continue + current.update(fields) + current["last_update"] = timestamp_now() + changed = True + return changed + + mutate_registry(runs_dir, apply) + return len(snapshot) + + +def _observe( + runs_dir: Path, + case_id: str, + record: dict[str, Any], + backend: ExecutionBackend, + task_id: str, + sync_files: bool, +) -> dict[str, Any]: + """Talk to the backend and return the registry fields to write. No lock held.""" + case_dir = Path(record.get("path") or (runs_dir / case_id)) + + try: + state = backend.poll(task_id) + except Exception as exc: + # A transient failure must never change the status: a flaky network + # would otherwise mark a whole campaign as failed. + warn(f"{case_id}: backend poll failed ({exc})") + return {"backend_poll_failures": int(record.get("backend_poll_failures") or 0) + 1} + + fields: dict[str, Any] = {"backend_poll_failures": 0} + if state.progress is not None: + fields["backend_progress"] = state.progress + if state.execution_time_s is not None: + fields["backend_execution_time_s"] = state.execution_time_s + if state.running_core_count is not None: + fields["backend_core_count"] = state.running_core_count + _append(case_dir / "csauto.stdout", state.stdout_delta) + _append(case_dir / "csauto.stderr", state.stderr_delta) + + if sync_files and state.status == STATUS_RUNNING: + try: + backend.sync(task_id, case_dir) + except Exception as exc: + warn(f"{case_id}: backend sync failed ({exc})") + + if state.status not in TERMINAL_STATUSES: + fields["status"] = state.status + return fields + + try: + backend.fetch_final(task_id, case_dir) + except Exception as exc: + # The remote task finished but the results are not local yet. DONE must + # mean the results are on disk, so leave the status alone and retry. + warn(f"{case_id}: downloading results failed, will retry ({exc})") + return fields + + fields["results_fetched"] = True + fields["status"] = state.status + fields["end_time"] = record.get("end_time") or timestamp_now() + return fields + + +def _append(path: Path, text: str) -> None: + if not text: + return + try: + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("a", encoding="utf-8") as handle: + handle.write(text) + except OSError: + return + + +def sync_backend_cases_throttled( + runs_dir: Path, + *, + sync_files: bool = True, + backend_factory: Callable[[str], ExecutionBackend] = get_backend, +) -> bool: + """Run one pass, at most once every 10 s. Returns whether it ran. + + Never raises: the caller is a status command or a Refresh click, and a + provider outage must not fail either. The marker lives under a reserved + `_backend` key at the top of registry.json, which no case id can collide + with: `_resolve_case_id` rejects ids not matching ^[A-Za-z0-9][A-Za-z0-9_.-]*$. + """ + marker = load_registry(runs_dir).get("_backend", {}).get("last_sync") + if marker: + with contextlib.suppress(ValueError): + if (datetime.now() - datetime.fromisoformat(str(marker))).total_seconds() < SYNC_THROTTLE_S: + return False + + def stamp(registry: dict[str, dict[str, Any]]) -> bool: + registry.setdefault("_backend", {})["last_sync"] = timestamp_now() + return True + + mutate_registry(runs_dir, stamp) + with contextlib.suppress(Exception): + sync_backend_cases(runs_dir, sync_files=sync_files, backend_factory=backend_factory) + return True + + +__all__ = ["sync_backend_cases", "sync_backend_cases_throttled"] diff --git a/csauto/backends/__init__.py b/csauto/backends/__init__.py new file mode 100644 index 0000000..624d9fe --- /dev/null +++ b/csauto/backends/__init__.py @@ -0,0 +1,40 @@ +"""Execution backend registry. + +The `backend` field on a case selects which service runs it. Implementation +modules are imported lazily so that loading configuration never pulls in an +optional SDK. +""" + +from __future__ import annotations + +import functools + +from .base import BackendState, ExecutionBackend + +_BACKEND_NAMES = ("fake", "qarnot") + + +def available_backends() -> tuple[str, ...]: + return _BACKEND_NAMES + + +def get_backend(name: str) -> ExecutionBackend: + """Return the memoized backend instance for a name.""" + return _backend_for(str(name or "").strip().lower()) + + +@functools.cache +def _backend_for(normalized: str) -> ExecutionBackend: + if normalized == "fake": + from .fake import FakeBackend + + return FakeBackend() + if normalized == "qarnot": + from .qarnot import QarnotBackend + + return QarnotBackend() + choices = ", ".join(available_backends()) + raise ValueError(f"Unknown backend: {normalized!r}. Choices: {choices}") + + +__all__ = ["BackendState", "ExecutionBackend", "available_backends", "get_backend"] diff --git a/csauto/backends/base.py b/csauto/backends/base.py new file mode 100644 index 0000000..8f0dcbf --- /dev/null +++ b/csauto/backends/base.py @@ -0,0 +1,83 @@ +"""Execution backend boundary. + +`ExecutionBackend` is the contract a remote execution service implements. The +generic core (runner, the sync pass, kill) programs against these five verbs and +never against a provider's API. Translating a provider's own vocabulary into +csauto statuses is the backend's job, the same discipline `SolverAdapter` +applies to solvers. +""" + +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from dataclasses import dataclass +from pathlib import Path +from typing import Protocol, runtime_checkable + + +@dataclass(frozen=True) +class BackendState: + """One observation of a remote task, already in csauto's vocabulary.""" + + status: str # PENDING / RUNNING / DONE / FAILED + progress: float | None = None + stdout_delta: str = "" + stderr_delta: str = "" + execution_time_s: float | None = None + running_core_count: int | None = None + + +@dataclass(frozen=True) +class LaunchOption: + """One choice a backend offers at launch time, declared by the backend. + + The core never interprets these: it renders them and hands the chosen + values back untouched. That is what keeps a provider's vocabulary inside + its own module, the discipline the solver adapters already follow. + """ + + key: str + label: str + choices: tuple[tuple[str, str], ...] + default: str = "" + + +@dataclass(frozen=True) +class LaunchOptions: + """A backend's catalogue, plus whether the provider could be reached. + + `degraded` is reported rather than hidden: a short list because the API is + down looks exactly like a short list because the account is small, and a + user should not have to guess which. + """ + + options: tuple[LaunchOption, ...] = () + degraded: bool = False + + +@runtime_checkable +class ExecutionBackend(Protocol): + """What the core is allowed to know about a remote execution service.""" + + name: str + + def launch_options(self) -> LaunchOptions: ... + + def submit( + self, + case_dir: Path, + argv: Sequence[str], + image: str, + nprocs: int, + nt: int, + observability_globs: Sequence[str] = (), + options: Mapping[str, str] = {}, + ) -> str: ... + + def poll(self, task_id: str) -> BackendState: ... + + def sync(self, task_id: str, case_dir: Path) -> None: ... + + def fetch_final(self, task_id: str, case_dir: Path) -> None: ... + + def cancel(self, task_id: str) -> None: ... diff --git a/csauto/backends/fake.py b/csauto/backends/fake.py new file mode 100644 index 0000000..4cb988e --- /dev/null +++ b/csauto/backends/fake.py @@ -0,0 +1,131 @@ +"""A shipped fake execution backend. + +What `StubAdapter` is to solvers: it satisfies the contract with local files and +a scripted state sequence, so the whole remote lifecycle can be tested without a +network, an account or credit. +""" + +from __future__ import annotations + +import itertools +from collections.abc import Mapping, Sequence +from pathlib import Path + +from .base import BackendState, LaunchOption, LaunchOptions + + +class _FakeTask: + def __init__(self, case_dir: Path) -> None: + self.case_dir = case_dir + self.step = 0 + self.stdout_cursor = 0 + self.cancelled = False + self.polls = 0 + + +class FakeBackend: + """Scripted backend. Every behaviour a test needs is an explicit argument.""" + + name = "fake" + + def __init__( + self, + script: Sequence[str] = ("PENDING", "RUNNING", "DONE"), + stdout_lines: Sequence[str] = (), + stderr_lines: Sequence[str] = (), + sync_files: Mapping[str, str] | None = None, + final_files: Mapping[str, str] | None = None, + fail_polls: int = 0, + fail_fetch_final: int = 0, + ) -> None: + self.script = tuple(script) + self.stdout_lines = tuple(stdout_lines) + self.stderr_lines = tuple(stderr_lines) + self.sync_files = dict(sync_files or {}) + self.final_files = dict(final_files or {}) + self.fail_polls = fail_polls + self.fail_fetch_final = fail_fetch_final + self._tasks: dict[str, _FakeTask] = {} + self._ids = itertools.count(1) + self.submitted_options: dict[str, str] = {} + + def launch_options(self) -> LaunchOptions: + """One trivial option, so the whole path is exercised with no network.""" + return LaunchOptions( + options=( + LaunchOption( + key="speed", + label="Speed", + choices=(("slow", "Slow"), ("fast", "Fast")), + default="slow", + ), + ) + ) + + def submit( + self, + case_dir: Path, + argv: Sequence[str], + image: str, + nprocs: int, + nt: int, + observability_globs: Sequence[str] = (), + options: Mapping[str, str] = {}, + ) -> str: + self.submitted_options = dict(options) + task_id = f"fake-{next(self._ids):04d}" + self._tasks[task_id] = _FakeTask(case_dir) + return task_id + + def poll(self, task_id: str) -> BackendState: + task = self._task(task_id) + task.polls += 1 + if task.polls <= self.fail_polls: + raise RuntimeError("fake poll failure") + if task.cancelled: + return BackendState(status="FAILED") + + index = min(task.step, len(self.script) - 1) + status = self.script[index] + task.step += 1 + + stdout_delta = "" + if task.stdout_cursor < len(self.stdout_lines): + stdout_delta = self.stdout_lines[task.stdout_cursor] + "\n" + task.stdout_cursor += 1 + + stderr_delta = "".join(line + "\n" for line in self.stderr_lines) if task.step == 1 else "" + progress = min(1.0, task.step / max(1, len(self.script))) + return BackendState( + status=status, + progress=progress, + stdout_delta=stdout_delta, + stderr_delta=stderr_delta, + ) + + def sync(self, task_id: str, case_dir: Path) -> None: + self._task(task_id) + self._write(case_dir, self.sync_files) + + def fetch_final(self, task_id: str, case_dir: Path) -> None: + self._task(task_id) + if self.fail_fetch_final > 0: + self.fail_fetch_final -= 1 + raise RuntimeError("fake fetch_final failure") + self._write(case_dir, self.final_files) + + def cancel(self, task_id: str) -> None: + self._task(task_id).cancelled = True + + def _task(self, task_id: str) -> _FakeTask: + try: + return self._tasks[task_id] + except KeyError: + raise KeyError(f"Unknown fake task: {task_id}") from None + + @staticmethod + def _write(case_dir: Path, files: Mapping[str, str]) -> None: + for relative, content in files.items(): + target = case_dir / relative + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(content, encoding="utf-8") diff --git a/csauto/backends/qarnot.py b/csauto/backends/qarnot.py new file mode 100644 index 0000000..75b2a6d --- /dev/null +++ b/csauto/backends/qarnot.py @@ -0,0 +1,370 @@ +"""The Qarnot execution backend. + +Everything Qarnot-specific lives here: the SDK, the buckets, the docker-batch +constants, the state translation and the snapshot policy. The core programs +against the five verbs in `base.py` and never sees a name from this file. + +The SDK is imported inside the methods that need it, so `[qarnot]` stays an +optional extra exactly as `[web]` does for FastAPI. + +Two facts about the SDK that the code below depends on, both checked against +qarnot 2.23.0 rather than assumed: + +- `Task.snapshot(interval, whitelist=...)` called before submission stores only + the interval and silently drops the whitelist, so the whitelist is set as an + attribute before submit (it is serialised into the creation payload) and the + periodic configuration is posted again afterwards, where the explicit path + runs. +- A Qarnot instance is a separate machine, not an MPI rank. `instancecount` is + always 1; the process count is inside `DOCKER_CMD`. +""" + +from __future__ import annotations + +import os +import re +import shlex +import time +from collections.abc import Mapping, Sequence +from pathlib import Path +from typing import Any, ClassVar + +from ..warn import warn +from .base import BackendState, LaunchOption, LaunchOptions +from .qarnot_support import ( + SCHEDULING_CHOICES, + bucket_name, + directory_signature, + ensure_upload_within, + minimum_core_constraint, + scheduling_choice, + snapshot_whitelist, + specific_hardware_constraint, + split_image, + upload_plan, +) + +TOKEN_ENV = "QARNOT_TOKEN" + +# The REST calls have a deadline so a slow provider cannot hang a route. It does +# not apply to the S3 transfers, which are botocore's own business. +CLUSTER_TIMEOUT_S = 30 + +# The node list changes rarely and is read every time the Run dialog opens. +NODE_CACHE_TTL_S = 600 + +# Qarnot's own vocabulary, translated here and nowhere else. Taken from +# qarnot.task.Task.state, not guessed. An unlisted name maps to PENDING: a +# state csauto has never seen must not fail a running campaign. +_STATES = { + "submitted": "PENDING", + "partiallydispatched": "PENDING", + "fullydispatched": "PENDING", + "unsubmitted": "PENDING", + "partiallyexecuting": "RUNNING", + "fullyexecuting": "RUNNING", + "uploadingresults": "RUNNING", + "downloadingresults": "RUNNING", + "pendingcancel": "RUNNING", + "success": "DONE", + "failure": "FAILED", + "cancelled": "FAILED", + "pendingdelete": "FAILED", + "deleted": "FAILED", +} + + +class QarnotBackend: + """Run a case on the Qarnot cloud through its generic docker-batch profile.""" + + name = "qarnot" + + def __init__(self, connection: Any = None) -> None: + self._connection = connection + + def _connect(self) -> Any: + """The SDK connection, built once, from the environment only.""" + if self._connection is not None: + return self._connection + token = os.environ.get(TOKEN_ENV, "").strip() + if not token: + raise RuntimeError( + f"{TOKEN_ENV} is not set. Qarnot runs on your own account: create a token in the " + f"Qarnot console and export it. It must never be written to csauto.toml." + ) + try: + from qarnot.connection import Connection + except ImportError as exc: # pragma: no cover - depends on the extra + raise RuntimeError("The qarnot SDK is not installed. Install csauto with the [qarnot] extra.") from exc + # Never let the token reach a traceback: the SDK raises on a bad token + # with its own message, which does not contain the value. + self._connection = Connection(client_token=token, cluster_timeout=CLUSTER_TIMEOUT_S) + return self._connection + + _catalogue_cache: ClassVar[tuple[float, tuple[dict[str, Any], ...]] | None] = None + + @classmethod + def clear_options_cache(cls) -> None: + """Forget the cached catalogue. Used by tests and after a token change.""" + cls._catalogue_cache = None + + def _catalogue(self) -> tuple[tuple[dict[str, Any], ...], bool]: + """The hardware constraints this account may ask for, as raw payloads. + + Qarnot validates a task's constraints against this catalogue and + rejects anything else with "Some constraints don't exist", so a + constraint csauto invents on its own breaks the launch outright. + """ + cached = type(self)._catalogue_cache + if cached is not None and (time.monotonic() - cached[0]) < NODE_CACHE_TTL_S: + return cached[1], False + try: + found = tuple(constraint.to_json() for constraint in self._connect().all_hardware_constraints()) + except Exception as exc: + # A failure is deliberately not cached: a ten-minute outage would + # otherwise hide the account's hardware long after it recovered. + warn(f"qarnot: could not list hardware constraints ({type(exc).__name__}: {exc})") + return (), True + type(self)._catalogue_cache = (time.monotonic(), found) + return found, False + + def launch_options(self) -> LaunchOptions: + """What a user may choose, with the node list fetched from the account. + + Never raises: the Run dialog must open, and a launch must remain + possible, whatever the provider is doing. When the node list cannot be + fetched the catalogue still offers the scheduling classes, "Any node" + stands alone, and `degraded` says so rather than letting a short list + pass for the truth. + """ + nodes, degraded = self._node_choices() + return LaunchOptions( + options=( + LaunchOption( + key="scheduling", + label="Priority", + choices=SCHEDULING_CHOICES, + default=SCHEDULING_CHOICES[0][0], + ), + LaunchOption( + key="node", + label="Node type", + choices=(("", "Any node"), *nodes), + default="", + ), + ), + degraded=degraded, + ) + + def _node_choices(self) -> tuple[tuple[tuple[str, str], ...], bool]: + """The account's node specifications, as (key, label) pairs.""" + catalogue, degraded = self._catalogue() + choices: list[tuple[str, str]] = [] + for payload in catalogue: + if payload.get("discriminator") != "SpecificHardwareConstraint": + continue + key = str(payload.get("specificationKey") or "") + if key: + choices.append((key, key)) + return tuple(choices), degraded + + def _accepted_constraints(self, candidates: Sequence[Any]) -> list[Any]: + """Keep only the constraints this account actually offers. + + Anything else makes Qarnot refuse the whole submission, so an + unverifiable constraint is dropped rather than risked: running on a + machine csauto did not get to choose beats not running at all. + """ + catalogue, _degraded = self._catalogue() + allowed = [dict(payload) for payload in catalogue] + return [candidate for candidate in candidates if candidate.to_json() in allowed] + + def submit( + self, + case_dir: Path, + argv: Sequence[str], + image: str, + nprocs: int, + nt: int, + observability_globs: Sequence[str] = (), + options: Mapping[str, str] = {}, + ) -> str: + from ..solvers import get_solver_adapter + + case_dir = Path(case_dir).resolve() + runs_dir = case_dir.parent + config = _campaign_config(runs_dir) + adapter = get_solver_adapter(config.solver) + + command = [str(part) for part in argv if str(part) != ""] + if not command: + raise ValueError( + f"Solver {adapter.name} does not build a remote command: its run_argv is empty " + f"because it composes the whole launch line locally. It cannot run on {self.name} yet." + ) + + plan = upload_plan( + case_dir, + shared_dir_names=adapter.shared_dir_names, + results_dirname=adapter.results_dirname, + ) + ensure_upload_within(plan, config.qarnot_max_upload_mb) + + connection = self._connect() + campaign = bucket_name(runs_dir) + + # The remote working directory *is* the case: it is the only place a + # task may write, so the campaign's shared directories land inside it + # rather than beside it. `adapter.prepare_remote_case` has already + # adjusted the case for that, whatever its solver needs. + case_bucket = connection.retrieve_or_create_bucket(f"{campaign}-{case_dir.name}") + for relative in plan.paths: + case_bucket.add_file(str(case_dir / relative), relative) + + resources = [case_bucket] + shared_bucket = self._shared_bucket(connection, runs_dir, adapter, campaign) + if shared_bucket is not None: + resources.append(shared_bucket) + + repo, tag = split_image(image or adapter.default_docker_image) + task = connection.create_task(f"csauto-{runs_dir.name}-{case_dir.name}", config.qarnot_profile, 1) + task.constants["DOCKER_REPO"] = repo + task.constants["DOCKER_TAG"] = tag + task.constants["DOCKER_CMD"] = shlex.join(command) + task.resources = resources + task.results = connection.retrieve_or_create_bucket(f"{campaign}-{case_dir.name}-out") + # The rank and thread counts only reach the solver, inside DOCKER_CMD. + # Without a constraint Qarnot allocates any available machine and the + # run can land on fewer cores than it asks for, oversubscribing MPI on + # paid compute. Both setters below raise once the task is launched, so + # they run before submit(). + # The core constraint is synthesised by csauto, so it is kept only when + # the account offers it. The node came out of that same catalogue, so it + # is valid by construction and always travels. + constraints: list[Any] = self._accepted_constraints([minimum_core_constraint(int(nprocs) * int(nt))]) + node = str(options.get("node") or "").strip() + if node: + constraints.append(specific_hardware_constraint(node)) + task.hardware_constraints = constraints + task.scheduling_type = scheduling_choice(str(options.get("scheduling") or "Flex")) + + # Only the results directory comes back. Without this the task would + # also return the shared directories it was given, re-downloading a + # mesh of several gigabytes into the case on every run. + whitelist = snapshot_whitelist(observability_globs) + if whitelist: + task.snapshot_whitelist = whitelist + task.results_whitelist = f"{re.escape(adapter.results_dirname)}/.*" + task.submit() + if whitelist: + task.snapshot(config.qarnot_snapshot_interval_s, whitelist=whitelist) + return str(task.uuid) + + def _shared_bucket(self, connection: Any, runs_dir: Path, adapter: Any, campaign: str) -> Any: + """Upload MESH/POST once per campaign, not once per case. + + With mesh_mode = "symlink" these are shared directories of several + gigabytes. Re-uploading them per case would mean hundreds of gigabytes + for a hundred-case campaign, so a fingerprint in the registry records + what was last sent. + """ + from ..registry import load_registry, mutate_registry + + targets = [runs_dir / name for name in adapter.shared_dir_names] + targets = [path for path in targets if path.is_dir()] + if not targets: + return None + + bucket = connection.retrieve_or_create_bucket(f"{campaign}-shared") + signature = directory_signature(targets) + marker = load_registry(runs_dir).get("_backend", {}).get("shared_upload") or {} + if marker.get("bucket") == bucket.uuid and marker.get("signature") == signature: + return bucket + + for path in targets: + bucket.sync_directory(str(path), remote=path.name) + + def stamp(registry: dict[str, Any]) -> bool: + registry.setdefault("_backend", {})["shared_upload"] = { + "bucket": str(bucket.uuid), + "signature": signature, + } + return True + + mutate_registry(runs_dir, stamp) + return bucket + + def poll(self, task_id: str) -> BackendState: + task = self._task(task_id) + progress = getattr(task, "progress", None) + return BackendState( + status=_STATES.get(str(getattr(task, "state", "")).strip().lower(), "PENDING"), + progress=None if progress is None else float(progress) / 100.0, + stdout_delta=task.fresh_stdout() or "", + stderr_delta=task.fresh_stderr() or "", + execution_time_s=_as_float(getattr(task, "execution_time", None)), + running_core_count=_as_int(getattr(task, "running_core_count", None)), + ) + + def sync(self, task_id: str, case_dir: Path) -> None: + """Pull whatever the snapshot whitelist has captured so far. + + download_results only transfers when the task is dirty, so calling it + on a quiet task costs one request. The results bucket only ever holds + whitelisted paths under the results directory, so the case inputs are + never overwritten. + """ + Path(case_dir).mkdir(parents=True, exist_ok=True) + self._task(task_id).download_results(str(case_dir)) + + def fetch_final(self, task_id: str, case_dir: Path) -> None: + """Pull the complete results, once, unconditionally. + + Not download_results: that one skips the transfer when the SDK thinks + nothing changed, and DONE must mean the results really are on disk. + """ + Path(case_dir).mkdir(parents=True, exist_ok=True) + self._task(task_id).results.get_all_files(str(case_dir)) + + def cancel(self, task_id: str) -> None: + """Abort the task, unless it has already finished. + + Qarnot refuses to abort a finished task ("Invalid operation on + non-running task"). Stopping something that already stopped is not an + error for a user pressing Stop, so a terminal task is left alone, and + the same refusal is tolerated when the task ends between the state read + and the abort. Any other failure is still reported. + """ + task = self._task(task_id) + if _STATES.get(str(getattr(task, "state", "")).strip().lower(), "PENDING") in ("DONE", "FAILED"): + return + try: + task.abort() + except Exception as exc: + if "non-running" not in str(exc).lower(): + raise + + def _task(self, task_id: str) -> Any: + return self._connect().retrieve_task(str(task_id)) + + +def _campaign_config(runs_dir: Path) -> Any: + """The campaign's own csauto.toml when it has one, the ambient config otherwise.""" + from ..config import load_config + + candidate = runs_dir / "csauto.toml" + return load_config(candidate if candidate.is_file() else None) + + +def _as_float(value: Any) -> float | None: + try: + return None if value is None else float(value) + except (TypeError, ValueError): + return None + + +def _as_int(value: Any) -> int | None: + try: + return None if value is None else int(value) + except (TypeError, ValueError): + return None diff --git a/csauto/backends/qarnot_support.py b/csauto/backends/qarnot_support.py new file mode 100644 index 0000000..c08ad1a --- /dev/null +++ b/csauto/backends/qarnot_support.py @@ -0,0 +1,235 @@ +"""Qarnot decisions that are not API calls. + +Kept apart from `qarnot.py` so every rule here is unit-testable with no SDK +installed, no token and no network, and so the backend module stays short +enough to read in one sitting. +""" + +from __future__ import annotations + +import hashlib +import re +from collections.abc import Sequence +from dataclasses import dataclass +from pathlib import Path + +# S3 bucket names: 3 to 63 characters, lowercase letters, digits, dots and +# hyphens, starting and ending with a letter or a digit. +_UNSAFE = re.compile(r"[^a-z0-9]+") +_SLUG_MAX = 20 + + +def bucket_name(runs_dir: Path, suffix: str = "") -> str: + """An S3-legal bucket name for a campaign, stable and collision free. + + The readable part comes from the campaign directory name, so a bucket is + recognisable in the Qarnot console; the digest comes from its absolute + path, so two campaigns that normalise to the same slug never collide. + """ + resolved = Path(runs_dir).resolve() + digest = hashlib.sha256(str(resolved).encode("utf-8")).hexdigest()[:8] + slug = _UNSAFE.sub("-", resolved.name.lower()).strip("-")[:_SLUG_MAX].strip("-") or "runs" + parts = ["csauto", slug, digest] + if suffix: + clean = _UNSAFE.sub("-", str(suffix).lower()).strip("-") + if clean: + parts.append(clean) + return "-".join(parts) + + +def snapshot_whitelist(globs: Sequence[str]) -> str: + """Translate adapter globs into the regex Qarnot's snapshot filter expects. + + Qarnot documents whitelist and blacklist as regular expressions, so the + patterns cannot be passed through unchanged. `*` stops at a slash, `**` + crosses one, and everything else is escaped. + """ + parts = [_translate(str(pattern).strip()) for pattern in globs if str(pattern).strip()] + return "|".join(parts) + + +def _translate(pattern: str) -> str: + out: list[str] = [] + index = 0 + while index < len(pattern): + char = pattern[index] + if char == "*": + if pattern[index + 1 : index + 2] == "*": + out.append(".*") + index += 2 + else: + out.append("[^/]*") + index += 1 + continue + if char == "?": + out.append("[^/]") + index += 1 + continue + out.append(re.escape(char)) + index += 1 + return "".join(out) + + +@dataclass(frozen=True) +class UploadPlan: + """What a single case would upload: relative paths, and how many bytes.""" + + paths: tuple[str, ...] + total_bytes: int + + +def upload_plan(case_dir: Path, *, shared_dir_names: Sequence[str], results_dirname: str) -> UploadPlan: + """The case's own input files: everything that is neither shared nor output. + + The shared directories are campaign-wide (and usually symlinks to a mesh of + several gigabytes), so they travel once per campaign in their own bucket, + not once per case. The results directory is what comes back, not what goes. + """ + case_dir = Path(case_dir) + skip = {str(name) for name in shared_dir_names} | {str(results_dirname)} + paths: list[str] = [] + total = 0 + for path in sorted(case_dir.rglob("*")): + if not path.is_file() or path.is_symlink(): + continue + relative = path.relative_to(case_dir) + if relative.parts[0] in skip or any(part.startswith(".") for part in relative.parts): + continue + paths.append(relative.as_posix()) + total += path.stat().st_size + return UploadPlan(paths=tuple(paths), total_bytes=total) + + +def ensure_upload_within(plan: UploadPlan, max_mb: int) -> None: + """Refuse a case whose inputs are too large to upload per case. + + A hundred-case campaign multiplies this by a hundred, so an accidental + gigabyte in DATA/ is worth stopping before it is paid for. + """ + limit = int(max_mb) * 1024 * 1024 + if plan.total_bytes <= limit: + return + raise ValueError( + f"Case inputs are {plan.total_bytes / 1024 / 1024:.1f} MB, over the " + f"{max_mb} MB limit ({len(plan.paths)} files). Move bulk data into a " + f"shared directory, or raise qarnot_max_upload_mb." + ) + + +def directory_signature(paths: Sequence[Path]) -> str: + """A cheap fingerprint of the shared directories: names, sizes, mtimes. + + Only stat calls, so fingerprinting a multi-gigabyte mesh costs nothing. + Used to skip re-uploading the shared bucket when nothing changed. + """ + digest = hashlib.sha256() + for root in sorted(Path(p) for p in paths): + for path in sorted(root.rglob("*")): + if not path.is_file(): + continue + stat = path.stat() + digest.update(f"{path.relative_to(root).as_posix()}:{stat.st_size}:{stat.st_mtime_ns}\n".encode()) + return digest.hexdigest() + + +@dataclass(frozen=True) +class MinimumCoreConstraint: + """Ask Qarnot for a machine with at least this many cores. + + Built by hand rather than imported from `qarnot.hardware_constraint`, so + the rest of this module, and the tests, stay runnable without the optional + extra. The SDK only ever calls `to_json()` on a constraint, so a plain + object of the same shape is interchangeable with its own class. + `test_minimum_core_constraint_matches_the_sdk` guards the discriminator + against a rename whenever the SDK happens to be installed. + """ + + core_count: int + + def to_json(self) -> dict[str, object]: + return {"discriminator": "MinimumCoreHardwareConstraint", "coreCount": self.core_count} + + +def minimum_core_constraint(cores: int) -> MinimumCoreConstraint: + """The hardware constraint matching a run's total core count.""" + return MinimumCoreConstraint(core_count=max(1, int(cores))) + + +# Qarnot's scheduling classes, from qarnot.scheduling_type. A closed set, so it +# is declared rather than fetched. Labels say what the choice costs, because +# that is the part a user is actually deciding. +SCHEDULING_CHOICES: tuple[tuple[str, str], ...] = ( + ("Flex", "Flex - cheaper, may wait for spare capacity"), + ("OnDemand", "OnDemand - starts sooner, costs more"), + ("Reserved", "Reserved - uses capacity reserved on your account"), +) + + +@dataclass(frozen=True) +class SchedulingChoice: + """A scheduling class, in the shape `Task._to_json` reads. + + Built by hand rather than imported from `qarnot.scheduling_type`, so this + module stays usable without the optional extra. The SDK only ever reads the + `schedulingType` attribute off it (`task.py:2169`), so a plain object of + the same shape is interchangeable with its own classes. + """ + + # Not snake_case on purpose: the SDK reads this exact attribute name. + schedulingType: str + + +def scheduling_choice(value: str) -> SchedulingChoice: + """Validate a scheduling value and wrap it for the SDK.""" + text = str(value or "").strip() + known = [known_value for known_value, _label in SCHEDULING_CHOICES] + if text not in known: + raise ValueError(f"Unknown scheduling {text!r}. Choices: {', '.join(known)}") + return SchedulingChoice(schedulingType=text) + + +@dataclass(frozen=True) +class SpecificHardwareConstraint: + """Pin a task to one hardware specification, Qarnot's notion of a node.""" + + specification_key: str + + def to_json(self) -> dict[str, object]: + return {"discriminator": "SpecificHardwareConstraint", "specificationKey": self.specification_key} + + +def specific_hardware_constraint(key: str) -> SpecificHardwareConstraint: + """The hardware constraint pinning a run to one node type.""" + return SpecificHardwareConstraint(specification_key=str(key).strip()) + + +def split_image(image: str) -> tuple[str, str]: + """Split `repo[:tag]` into the DOCKER_REPO and DOCKER_TAG constants.""" + text = str(image or "").strip() + if not text: + raise ValueError( + "Qarnot runs docker images, but no docker image is configured. " + "Set docker_image in csauto.toml even when the local runtime is native." + ) + repo, separator, tag = text.rpartition(":") + if not separator or "/" in tag: + return text, "latest" + return repo, tag or "latest" + + +__all__ = [ + "SCHEDULING_CHOICES", + "MinimumCoreConstraint", + "SchedulingChoice", + "SpecificHardwareConstraint", + "UploadPlan", + "bucket_name", + "directory_signature", + "ensure_upload_within", + "minimum_core_constraint", + "scheduling_choice", + "snapshot_whitelist", + "specific_hardware_constraint", + "split_image", + "upload_plan", +] diff --git a/csauto/cli.py b/csauto/cli.py index 6aac9c8..842d452 100644 --- a/csauto/cli.py +++ b/csauto/cli.py @@ -329,6 +329,11 @@ def _add_export_args(parser: argparse.ArgumentParser) -> None: doctor_parser = subparsers.add_parser("doctor", help="Check configuration and cases.") doctor_parser.add_argument("runs_dir", type=Path, help="Directory containing generated cases") + doctor_parser.add_argument( + "--backend", + default=None, + help="Also check the prerequisites of an execution backend (for example: qarnot).", + ) cleanup_parser = subparsers.add_parser("cleanup", help="Clean up runs (results/logs/cache).") cleanup_parser.add_argument("runs_dir", type=Path, help="Directory containing generated cases") @@ -509,6 +514,9 @@ def main(argv: Sequence[str] | None = None) -> int: adapter=adapter, ) elif args.command == "status": + from .backend_sync import sync_backend_cases_throttled + + sync_backend_cases_throttled(args.runs_dir) rows = refresh_status(args.runs_dir, adapter=adapter) print_status_table(rows) elif args.command == "residuals": @@ -558,6 +566,7 @@ def main(argv: Sequence[str] | None = None) -> int: singularity_image=config.singularity_image, singularity_bin=config.singularity_bin, adapter=adapter, + backend=args.backend, ) if _print_doctor(items): return 1 diff --git a/csauto/config.py b/csauto/config.py index 509a1e8..4ded5d5 100644 --- a/csauto/config.py +++ b/csauto/config.py @@ -22,6 +22,11 @@ class Config: use_slurm: bool | None = None mpi_exec_options: str | None = None max_parallel: int = 1 + backend_poll_interval_s: int = 15 + backend_sync_interval_s: int = 60 + qarnot_profile: str = "docker-batch" + qarnot_snapshot_interval_s: int = 60 + qarnot_max_upload_mb: int = 512 mesh_mode: str = "symlink" host: str = "127.0.0.1" port: int = 8000 @@ -179,6 +184,18 @@ def load_config(path: Path | None = None) -> Config: config_path=config_path, min_value=1, ) + for interval_field in ("backend_poll_interval_s", "backend_sync_interval_s"): + if interval_field in data: + setattr( + config, + interval_field, + _parse_required_int( + data.get(interval_field), + field=interval_field, + config_path=config_path, + min_value=1, + ), + ) if "mesh_mode" in data: config.mesh_mode = _parse_mesh_mode(data.get("mesh_mode"), config_path=config_path) if "host" in data: @@ -200,6 +217,38 @@ def load_config(path: Path | None = None) -> Config: token = api_section.get("token") if isinstance(api_section, dict) else None if token is not None: config.api_token = _coerce_str(token, "").strip() or None + + qarnot_section = data.get("qarnot") + if qarnot_section is not None and not isinstance(qarnot_section, dict): + raise ValueError(f"Invalid [qarnot] section in {config_path}: expected table") + if isinstance(qarnot_section, dict): + # The token is deliberately not a setting: this file is shared, + # committed and sometimes archived. Fail loudly rather than read it. + for secret_key in ("token", "client_token", "api_token"): + if secret_key in qarnot_section: + raise ValueError( + f"Invalid [qarnot] section in {config_path}: {secret_key!r} must not be " + f"stored in a config file. Set the QARNOT_TOKEN environment variable instead." + ) + if "profile" in qarnot_section: + profile = _coerce_str(qarnot_section.get("profile"), "").strip() + if not profile: + raise ValueError(f"Invalid [qarnot].profile in {config_path}: value must not be empty") + config.qarnot_profile = profile + if "snapshot_interval_s" in qarnot_section: + config.qarnot_snapshot_interval_s = _parse_required_int( + qarnot_section.get("snapshot_interval_s"), + field="[qarnot].snapshot_interval_s", + config_path=config_path, + min_value=1, + ) + if "max_upload_mb" in qarnot_section: + config.qarnot_max_upload_mb = _parse_required_int( + qarnot_section.get("max_upload_mb"), + field="[qarnot].max_upload_mb", + config_path=config_path, + min_value=1, + ) return config diff --git a/csauto/fastapi_app.py b/csauto/fastapi_app.py index 1ff419d..a0cf219 100644 --- a/csauto/fastapi_app.py +++ b/csauto/fastapi_app.py @@ -65,6 +65,8 @@ def create_fastapi_app( singularity_bin: str | None = None, use_slurm: bool | None = None, mpi_exec_options: str | None = None, + backend_poll_interval_s: int = 15, + backend_sync_interval_s: int = 60, ) -> Any: components = _import_fastapi_components() FastAPI = components["FastAPI"] @@ -113,13 +115,39 @@ async def _heartbeat_loop() -> None: except Exception: pass + async def _backend_sync_loop() -> None: + """Poll the cases a remote backend owns. + + Deliberately not inside refresh_status, which runs about twice a second + because the dashboard polls every second and the server caches for half + of one. A network round trip there would abuse the provider's API and + make the dashboard lag. + """ + from . import backend_sync + + elapsed_since_file_sync = 0.0 + while True: + await asyncio.sleep(backend_poll_interval_s) + elapsed_since_file_sync += backend_poll_interval_s + pull_files = elapsed_since_file_sync >= backend_sync_interval_s + if pull_files: + elapsed_since_file_sync = 0.0 + try: + touched = await asyncio.to_thread(backend_sync.sync_backend_cases, runs_dir, sync_files=pull_files) + except Exception: + continue + if touched: + ctx.invalidate_status_cache() + @contextlib.asynccontextmanager async def _lifespan(_app: Any) -> AsyncIterator[None]: - task = asyncio.create_task(_heartbeat_loop()) + tasks = [asyncio.create_task(_heartbeat_loop()), asyncio.create_task(_backend_sync_loop())] yield - task.cancel() - with contextlib.suppress(asyncio.CancelledError): - await task + for task in tasks: + task.cancel() + for task in tasks: + with contextlib.suppress(asyncio.CancelledError): + await task app = FastAPI(title="csauto", lifespan=_lifespan) diff --git a/csauto/fastapi_routes/actions.py b/csauto/fastapi_routes/actions.py index 7f7d35b..dd04686 100644 --- a/csauto/fastapi_routes/actions.py +++ b/csauto/fastapi_routes/actions.py @@ -63,6 +63,8 @@ class RunCasePayload(BaseModel): n: int | None = None nt: int | None = None max_parallel: int | None = None + backend: str | None = None + options: dict[str, str] | None = None restart: bool = False restart_mode: str = "" restart_value: int | float | None = None @@ -422,6 +424,34 @@ def api_open_gui( ) return {"status": "ok"} + class SyncBackendsResponse(BaseModel): + status: str + synced: bool + + @app.post("/api/sync_backends", response_model=SyncBackendsResponse) + def api_sync_backends( + x_csauto_token: str | None = Header(default=None), + authorization: str | None = Header(default=None), + ) -> dict[str, Any]: + """Pull fresh data for the cases an execution backend owns, on demand. + + The background loop already does this on a timer, but pressing Refresh + rereads local files, and for a cloud case those only change when a sync + pass runs. This is what makes the button mean something there. + + Throttled to one pass every ten seconds and never raising, so clicking + it repeatedly costs nothing and a provider outage does not fail the + click. Deliberately not part of /api/status, which the dashboard polls + about once a second. + """ + ctx.require_auth(x_csauto_token, authorization) + from ..backend_sync import sync_backend_cases_throttled + + synced = sync_backend_cases_throttled(ctx.runs_dir) + if synced: + ctx.invalidate_status_cache() + return {"status": "ok", "synced": synced} + @app.post("/api/run_case", response_model=SuccessResponse) def api_run_case( payload: RunCasePayload, @@ -446,6 +476,16 @@ def api_run_case( else: max_parallel = payload.max_parallel + backend = (payload.backend or "").strip().lower() or None + if backend is not None: + from ..backends import available_backends + + if backend not in available_backends(): + raise ctx.http_exception_cls( + status_code=400, + detail=f"Unknown execution backend: {backend!r}. Choices: {', '.join(available_backends())}", + ) + restart, restart_mode, restart_value, restart_path = _parse_restart_params( payload, ctx.http_exception_cls, @@ -486,6 +526,8 @@ def api_run_case( mpi_exec_options=ctx.mpi_exec_options, source="web", adapter=ctx.adapter, + backend=backend, + options=payload.options, ) except ctx.http_exception_cls: raise diff --git a/csauto/fastapi_routes/observability.py b/csauto/fastapi_routes/observability.py index d4ffab1..226193b 100644 --- a/csauto/fastapi_routes/observability.py +++ b/csauto/fastapi_routes/observability.py @@ -1,6 +1,7 @@ import math from typing import Annotated, Any +from ..backends import available_backends from ..logs import ( ANOMALY_CONTEXT_DEFAULT, ANOMALY_SEVERITY, @@ -87,6 +88,21 @@ class AppConfigModel(BaseModel): compare_kinds: list[CompareKindModel] error_files: list[str] control_actions: list[str] + backends: list[str] + + class LaunchOptionModel(BaseModel): + key: str + label: str + choices: list[tuple[str, str]] + default: str + + class LaunchOptionsModel(BaseModel): + backend: str + degraded: bool + options: list[LaunchOptionModel] + + class LaunchOptionsQuery(BaseModel): + backend: str class RecentErrorItemModel(BaseModel): case_id: str @@ -150,6 +166,51 @@ def api_app_config( "compare_kinds": [{"value": kind.value, "label": kind.label} for kind in ctx.adapter.compare_kinds], "error_files": list(ctx.adapter.anomaly_file_names), "control_actions": sorted(ctx.adapter.control_actions), + "backends": list(available_backends()), + } + + @app.get("/api/launch_options", response_model=LaunchOptionsModel) + def api_launch_options( + query: Annotated[LaunchOptionsQuery, Query()], + x_csauto_token: str | None = Header(default=None), + authorization: str | None = Header(default=None), + ) -> dict[str, Any]: + """What a backend lets a user choose, for the Run dialog. + + Deliberately not part of /api/app_config: that route is polled on every + page load, and a provider call there is what froze the server before. + This one is called when the dialog opens, and never fails: a provider + that is down yields an empty, degraded catalogue so the dialog still + opens and the launch still works with defaults. + """ + ctx.require_auth(x_csauto_token, authorization) + from ..backends import get_backend + + name = str(query.backend or "").strip().lower() + if name not in available_backends(): + raise ctx.http_exception_cls( + status_code=400, + detail=f"Unknown execution backend: {name!r}. Choices: {', '.join(available_backends())}", + ) + try: + catalogue = get_backend(name).launch_options() + except Exception: + # Never surface the provider's own error here: it can carry + # connection details, and the dialog only needs to know it is + # working without a catalogue. + return {"backend": name, "degraded": True, "options": []} + return { + "backend": name, + "degraded": catalogue.degraded, + "options": [ + { + "key": option.key, + "label": option.label, + "choices": [list(choice) for choice in option.choices], + "default": option.default, + } + for option in catalogue.options + ], } @app.get("/api/restart_origin", response_model=RestartOriginResponse) diff --git a/csauto/logs.py b/csauto/logs.py index bf69332..d486f98 100644 --- a/csauto/logs.py +++ b/csauto/logs.py @@ -429,6 +429,9 @@ def extract_restart_origin(case_dir: Path) -> dict[str, int | float]: def extract_last_iteration(log_path: Path) -> int | None: """Extract the last iteration number from a log file, if possible.""" patterns = [ + # The solver announces each step it starts; the rest are incidental + # mentions in warnings, which a log need not contain at all. + re.compile(r"TIME\s+STEP\s+NUMBER\s+(\d+)", re.IGNORECASE), re.compile(r"[Ii]teration\s+(\d+)"), re.compile(r"[Tt]ime\s+step\s+(\d+)"), re.compile(r"Iter\s*=\s*(\d+)"), diff --git a/csauto/maintenance.py b/csauto/maintenance.py index 5c20943..316ee4d 100644 --- a/csauto/maintenance.py +++ b/csauto/maintenance.py @@ -41,6 +41,76 @@ def _check_web_deps(add: object) -> None: add("ok", "web dependencies available (fastapi, uvicorn, pydantic)") +def _qarnot_connection(): + """The Qarnot connection, as its own function so doctor tests can replace it.""" + from .backends.qarnot import QarnotBackend + + return QarnotBackend()._connect() + + +def _check_qarnot_account(add: object) -> None: + """Report what the account still has room for, before a launch spends it. + + An exhausted bucket or storage quota surfaces as a QuotaExceeded in the + middle of an upload, which is far too late: by then the user has chosen a + campaign and pressed the button. Never print the token, only what it buys. + """ + try: + user = _qarnot_connection().user_info + except Exception as exc: + # The token itself must never reach this message; str(exc) from the SDK + # carries a status and a reason, not the credential. + add("fail", f"qarnot API not reachable ({type(exc).__name__}: {exc})") + return + + used = int(getattr(user, "used_quota_bytes_bucket", 0) or 0) + total = int(getattr(user, "quota_bytes_bucket", 0) or 0) + count = getattr(user, "bucket_count", None) + limit = getattr(user, "max_bucket", None) + gb = 1024**3 + + if limit: + level = "warn" if count is not None and count >= limit else "ok" + add(level, f"qarnot buckets {count}/{limit}") + if total: + level = "warn" if used / total >= 0.9 else "ok" + add(level, f"qarnot storage {used / gb:.1f}/{total / gb:.1f} GB used") + else: + add("warn", "qarnot storage quota is 0: the account cannot hold a campaign's inputs") + + +def _check_qarnot(add: object, adapter: object) -> None: + """Report each Qarnot prerequisite separately, and never the token itself.""" + import importlib + + try: + importlib.import_module("qarnot") + except ImportError: + add("fail", "qarnot SDK missing: install csauto with the [qarnot] extra") + else: + add("ok", "qarnot SDK available") + + if os.environ.get("QARNOT_TOKEN", "").strip(): + add("ok", "QARNOT_TOKEN is set") + _check_qarnot_account(add) + else: + add("fail", "QARNOT_TOKEN is not set (the token belongs in the environment, never in csauto.toml)") + + image = getattr(adapter, "default_docker_image", "") + if image: + add("ok", f"cloud image: {image}") + else: + add("fail", f"solver {adapter.name}: no docker image configured, and qarnot only runs docker images") + + if adapter.run_argv(".", 1, 1): + add("ok", f"solver {adapter.name}: builds a remote command") + else: + add( + "fail", + f"solver {adapter.name}: builds no remote command (run_argv is empty), so it cannot run on qarnot yet", + ) + + def run_doctor( runs_dir: Path, require_docker: bool = False, @@ -53,6 +123,7 @@ def run_doctor( singularity_image: str | None = None, singularity_bin: str | None = None, adapter=None, + backend: str | None = None, ) -> list[DoctorItem]: adapter = adapter or _default_adapter() items: list[DoctorItem] = [] @@ -162,6 +233,9 @@ def add(level: str, message: str) -> None: else: add("warn", "DISPLAY is set but /tmp/.X11-unix is missing") + if str(backend or "").strip().lower() == "qarnot": + _check_qarnot(add, adapter) + _check_web_deps(add) return items diff --git a/csauto/registry.py b/csauto/registry.py index 85e7442..865f971 100644 --- a/csauto/registry.py +++ b/csauto/registry.py @@ -18,6 +18,17 @@ STATUS_DONE = "DONE" STATUS_FAILED = "FAILED" +# Keys at the top of registry.json that hold campaign state rather than a case. +# Everything that iterates the registry as a list of cases must go through +# case_records, or a marker is reported as a phantom case. +RESERVED_REGISTRY_KEYS = frozenset({"_backend"}) + + +def case_records(registry: dict[str, Any]) -> dict[str, Any]: + """The registry entries that are cases, with the reserved markers removed.""" + return {key: value for key, value in registry.items() if key not in RESERVED_REGISTRY_KEYS} + + REGISTRY_FILENAME = "registry.json" REGISTRY_LOCKFILE = f"{REGISTRY_FILENAME}.lock" REGISTRY_THREAD_LOCK = threading.RLock() @@ -29,21 +40,49 @@ fcntl = None +# Which lock files this thread already holds. REGISTRY_THREAD_LOCK is an RLock, +# so a thread re-entering the lock passes straight through it, but flock applies +# per file descriptor: reopening the lock file would make the second descriptor +# wait on the first, held by this very thread. That deadlock is silent and +# permanent, and it takes the whole process with it, so a re-entry is tracked +# here and simply passes through. +_HELD_LOCKS = threading.local() + + +def _held_lock_paths() -> set[str]: + paths = getattr(_HELD_LOCKS, "paths", None) + if paths is None: + paths = set() + _HELD_LOCKS.paths = paths + return paths + + @contextmanager def registry_lock(runs_dir: Path) -> Iterable[None]: - """Serialize registry access across threads and processes.""" + """Serialize registry access across threads and processes. + + Re-entrant within a thread: a nested call passes through instead of + deadlocking on a second descriptor of the same lock file. + """ if not runs_dir.is_dir(): with REGISTRY_THREAD_LOCK: yield return lock_path = runs_dir / REGISTRY_LOCKFILE + key = str(lock_path.parent.resolve() / lock_path.name) with REGISTRY_THREAD_LOCK: + held = _held_lock_paths() + if key in held: + yield + return handle = lock_path.open("a+") + held.add(key) try: if fcntl: fcntl.flock(handle.fileno(), fcntl.LOCK_EX) yield finally: + held.discard(key) if fcntl: fcntl.flock(handle.fileno(), fcntl.LOCK_UN) handle.close() diff --git a/csauto/residuals.py b/csauto/residuals.py index 42b0275..1db6ffe 100644 --- a/csauto/residuals.py +++ b/csauto/residuals.py @@ -243,16 +243,27 @@ def parse_residuals_from_log(log_path: Path) -> tuple[list[str], list[dict[str, fields: list[str] = ["iteration"] header_re = re.compile(r"Variable\s+Rhs norm", re.IGNORECASE) sep_re = re.compile(r"^-{3,}") + # The solver announces each time step before printing its convergence + # block. Counting blocks instead would put the curve on a wrong axis: + # convergence is printed at the listing frequency, so the Nth block is + # almost never iteration N. + step_re = re.compile(r"TIME STEP NUMBER\s+(\d+)", re.IGNORECASE) + last_step: str | None = None try: with log_path.open("r", encoding="utf-8", errors="ignore") as handle: for line in handle: + step_match = step_re.search(line) + if step_match: + last_step = step_match.group(1) if header_re.search(line): if current: rows.append(current) current = None block_has_data = False block_idx += 1 - current = {"iteration": str(block_idx)} + # Fall back to the block index for a log that does not + # announce its steps, so such a run still plots. + current = {"iteration": last_step or str(block_idx)} continue if current is not None: if sep_re.match(line) and not block_has_data: diff --git a/csauto/runner.py b/csauto/runner.py index e6e60f2..9ca07ba 100644 --- a/csauto/runner.py +++ b/csauto/runner.py @@ -32,6 +32,7 @@ STATUS_PREPARED, STATUS_RUNNING, append_history, + case_records, load_registry, mutate_registry, registry_transaction, @@ -293,6 +294,8 @@ def _start_case( restart_path: str | None, mpi_exec_options: str | None, source: str, + backend_name: str | None = None, + options: Mapping[str, str] | None = None, ) -> bool: """Launch a single case. Returns True if launched, False if skipped.""" case_id = case_dir.name @@ -305,6 +308,7 @@ def _start_case( restart_value, restart_path, ) + backend_argv: list[str] | None = None with registry_transaction(runs_dir) as registry: record = registry.get(case_id, {}) if record.get("status") == STATUS_RUNNING: @@ -350,7 +354,16 @@ def _start_case( env_vars=container_env, ) - if use_slurm_scheduler: + if backend_name: + # Only decide here. Submitting uploads the case and talks to a + # remote API, and doing that under the registry lock freezes every + # reader for its duration, the dashboard's own status route + # included. The submit happens below, with no lock held. + # A relative path: the remote working directory *is* the case, the + # only directory a task can write to, and the host path means + # nothing over there. + backend_argv = adapter.run_argv(".", nprocs, nt, restart_args) + elif use_slurm_scheduler: _launch_slurm( case_dir, case_id, @@ -369,21 +382,44 @@ def _start_case( else: _launch_local(case_dir, case_id, cmd, cidfile, registry, base_update) - history_details: dict[str, Any] = {"n": nprocs, "nt": nt, "runtime": selection.runtime} - history_details.update(restart_details) - if scheduler: - history_details["scheduler"] = scheduler - history_details["job_id"] = registry.get(case_id, {}).get("job_id") - if mpi_exec_options: - history_details["mpi_exec_options"] = str(mpi_exec_options).strip() - if selection.runtime == RUNTIME_DOCKER: - history_details["docker_image"] = selection.docker_image - elif selection.runtime == "native": - history_details["saturne_bin"] = selection.saturne_bin - elif selection.runtime == "singularity": - history_details["singularity_bin"] = selection.singularity_bin - history_details["singularity_image"] = selection.singularity_image - append_history(case_dir, "run", details=history_details, source=source) + scheduler_job_id = registry.get(case_id, {}).get("job_id") + + if backend_argv is not None: + # The backend puts the campaign's shared directories inside the case, + # because a remote task has one working directory and no parent. Only + # the adapter knows what its solver needs in order to cope. + adapter.prepare_remote_case(case_dir) + _launch_backend( + case_dir, + case_id, + backend_argv, + selection.docker_image, + nprocs, + nt, + backend_name or "", + adapter.observability_globs, + dict(options or {}), + runs_dir, + base_update, + ) + + history_details: dict[str, Any] = {"n": nprocs, "nt": nt, "runtime": selection.runtime} + history_details.update(restart_details) + if backend_name and options: + history_details["options"] = dict(options) + if scheduler: + history_details["scheduler"] = scheduler + history_details["job_id"] = scheduler_job_id + if mpi_exec_options: + history_details["mpi_exec_options"] = str(mpi_exec_options).strip() + if selection.runtime == RUNTIME_DOCKER: + history_details["docker_image"] = selection.docker_image + elif selection.runtime == "native": + history_details["saturne_bin"] = selection.saturne_bin + elif selection.runtime == "singularity": + history_details["singularity_bin"] = selection.singularity_bin + history_details["singularity_image"] = selection.singularity_image + append_history(case_dir, "run", details=history_details, source=source) return True @@ -458,6 +494,74 @@ def _launch_slurm( ) +def _launch_backend( + case_dir: Path, + case_id: str, + argv: Sequence[str], + image: str, + nprocs: int, + nt: int, + backend_name: str, + observability_globs: Sequence[str], + options: Mapping[str, str], + runs_dir: Path, + base_update: dict[str, Any], +) -> None: + """Submit the case to a remote execution backend. + + Called with **no registry lock held**: submitting uploads the case and + talks to a remote API, which can take minutes, and every reader of + registry.json would block for that whole time. The outcome is written back + under its own short transaction, the same three-phase discipline + `backend_sync` follows. + + No PID and no scheduler job: the case is identified by backend and task_id, + and the synchronisation pass owns its status from here on. + """ + from .backends import get_backend + + backend = get_backend(backend_name) + try: + print(f"Submitting {case_id} to backend {backend_name}.") + task_id = backend.submit(case_dir, argv, image, nprocs, nt, observability_globs, options) + except Exception as exc: + + def mark_failed(registry: dict[str, Any]) -> bool: + update_case( + registry, + case_id, + **base_update, + status=STATUS_FAILED, + start_time=None, + end_time=timestamp_now(), + pid=None, + job_id=None, + backend=backend_name, + task_id=None, + ) + return True + + mutate_registry(runs_dir, mark_failed) + raise RuntimeError(f"Failed to submit {case_id}: {exc}") from exc + + def mark_running(registry: dict[str, Any]) -> bool: + update_case( + registry, + case_id, + **base_update, + status=STATUS_RUNNING, + start_time=timestamp_now(), + end_time=None, + pid=None, + job_id=None, + backend=backend_name, + task_id=task_id, + ) + return True + + mutate_registry(runs_dir, mark_running) + + def _launch_local( case_dir: Path, case_id: str, @@ -541,6 +645,8 @@ def run_cases( mpi_exec_options: str | None = None, source: str = "cli", adapter: SolverAdapter | None = None, + backend: str | None = None, + options: Mapping[str, str] | None = None, ) -> None: """Launch solver runs for each case directory.""" if nprocs <= 0 or nt <= 0: @@ -613,6 +719,8 @@ def _do_start(selected_case: Path = case_dir) -> None: restart_path=restart_path, mpi_exec_options=mpi_exec_options, source=source, + backend_name=backend, + options=options, ): launched += 1 @@ -773,7 +881,7 @@ def refresh_status( results: list[RefreshResult] = [] doe_columns: list[str] = [] seen_doe_columns: set[str] = set() - for case_id in sorted(registry): + for case_id in sorted(case_records(registry)): result = _compute_refresh_result( runs_dir, case_id, @@ -811,8 +919,12 @@ def _compute_refresh_result( end_time = record.get("end_time") has_started = bool(start_time) job_id = _normalize_job_id(record.get("job_id")) + # A case a remote backend owns has neither a PID nor a scheduler job, so the + # finalisation below would mark it FAILED on the first refresh, before the + # service had even started it. The synchronisation pass owns its status. + backend_name = str(record.get("backend") or "") - if status == STATUS_RUNNING: + if status == STATUS_RUNNING and not backend_name: pid_alive = False if pid is not None: try: @@ -829,7 +941,7 @@ def _compute_refresh_result( end_time = end_time or timestamp_now() pid = None job_id = None - if status == STATUS_RUNNING and has_started: + if status == STATUS_RUNNING and has_started and not backend_name: # Even if the PID is stale, trust the log end markers. outcome = adapter.detect_outcome(case_dir, start_time) if outcome: @@ -838,7 +950,7 @@ def _compute_refresh_result( end_time = timestamp_now() pid = None job_id = None - elif status != STATUS_DONE and has_started: + elif status != STATUS_DONE and has_started and not backend_name: # Only check for completion if a run was actually started. outcome = adapter.detect_outcome(case_dir, start_time) if outcome: @@ -848,7 +960,9 @@ def _compute_refresh_result( pid = None job_id = None - last_iter = adapter.read_progress(case_dir, start_time) + # A terminal case reads its progress from the log: a live marker left + # behind by a run that is over would pin the figure to its last capture. + last_iter = adapter.read_progress(case_dir, start_time, running=status not in (STATUS_DONE, STATUS_FAILED)) duration_record = dict(record) duration_record["end_time"] = end_time @@ -869,6 +983,13 @@ def _compute_refresh_result( "duration": duration, "last_mod": last_mod, "resu_size_mb": resu_size_mb, + # A case a remote backend owns has no local duration to speak of, so + # the dashboard shows what the backend reports instead. Empty and None + # for every local case, which is every case today. + "backend": backend_name, + "backend_progress": record.get("backend_progress"), + "backend_execution_time_s": record.get("backend_execution_time_s"), + "backend_core_count": record.get("backend_core_count"), } doe_cols: tuple[str, ...] = () @@ -990,26 +1111,39 @@ def _query_slurm_job_activity(job_ids: Sequence[str | None]) -> dict[str, bool | return states +# Some filesystems, WSL2 among them, never bump a directory's mtime when files +# are created or changed inside it, so a cache keyed on that mtime alone would +# never invalidate. The size is therefore also given a short life of its own. +RESU_SIZE_CACHE_TTL_S = 10.0 + + def _cached_resu_size_mb(case_dir: Path, adapter: SolverAdapter) -> tuple[float | None, float | None]: resu_root = adapter.results_root(case_dir) cache_key = str(case_dir.resolve()) resu_mtime = None if resu_root.is_dir(): try: - resu_mtime = resu_root.stat().st_mtime + # The run directories too, not just the root: a backend sync drops + # files into an existing run, which leaves the root's mtime alone + # and would freeze the cached size for the rest of the campaign. + resu_mtime = max( + [resu_root.stat().st_mtime] + [child.stat().st_mtime for child in resu_root.iterdir() if child.is_dir()] + ) except OSError: resu_mtime = None if resu_mtime is None: with RESU_SIZE_CACHE_LOCK: RESU_SIZE_CACHE.pop(cache_key, None) return None, None + now = time.monotonic() with RESU_SIZE_CACHE_LOCK: cached = RESU_SIZE_CACHE.get(cache_key) - if cached and cached.get("mtime") == resu_mtime: + fresh = cached is not None and (now - float(cached.get("at") or 0.0)) < RESU_SIZE_CACHE_TTL_S + if cached and fresh and cached.get("mtime") == resu_mtime: return resu_mtime, cached.get("size") size = _resu_size_mb(case_dir, adapter) with RESU_SIZE_CACHE_LOCK: - RESU_SIZE_CACHE[cache_key] = {"mtime": resu_mtime, "size": size} + RESU_SIZE_CACHE[cache_key] = {"mtime": resu_mtime, "size": size, "at": now} return resu_mtime, size diff --git a/csauto/solvers/base.py b/csauto/solvers/base.py index 8b7d83f..e86f3be 100644 --- a/csauto/solvers/base.py +++ b/csauto/solvers/base.py @@ -63,6 +63,7 @@ class SolverAdapter(Protocol): shared_dir_names: tuple[str, ...] template_input_names: frozenset[str] anomaly_file_names: tuple[str, ...] + observability_globs: tuple[str, ...] cleanup_log_names: frozenset[str] performance_fields: tuple[str, ...] performance_columns: tuple[PerfColumn, ...] @@ -117,7 +118,7 @@ def preflight(self, runs_dir: Path, runtime: str) -> None: ... def detect_outcome(self, case_dir: Path, start_time: str | None = None) -> str | None: ... - def read_progress(self, case_dir: Path, start_time: str | None = None) -> int | None: ... + def read_progress(self, case_dir: Path, start_time: str | None = None, *, running: bool = True) -> int | None: ... def read_restart_origin(self, case_dir: Path) -> dict[str, int | float]: ... @@ -169,6 +170,8 @@ def apply_control( start_time: str | None = None, ) -> dict[str, Any]: ... + def prepare_remote_case(self, case_dir: Path) -> None: ... + class SolverAdapterBase(ABC): """Shared composition and safe defaults for solver adapters.""" @@ -182,6 +185,12 @@ class SolverAdapterBase(ABC): shared_dir_names: ClassVar[tuple[str, ...]] = () template_input_names: ClassVar[frozenset[str]] = frozenset() anomaly_file_names: ClassVar[tuple[str, ...]] = ("csauto.stderr", "csauto.stdout") + # Patterns, relative to the case directory, naming the few files a remote + # execution backend should pull back *while the run is in progress*. They + # exist because find_residuals_files and friends inspect a local directory + # and so cannot describe a task running on someone else's machine. + # Keep the list narrow: Qarnot advises a snapshot stays under 1 GB. + observability_globs: ClassVar[tuple[str, ...]] = () cleanup_log_names: ClassVar[frozenset[str]] = frozenset({"csauto.stdout", "csauto.stderr"}) performance_columns: ClassVar[tuple[PerfColumn, ...]] = () compare_kinds: ClassVar[tuple[CompareKind, ...]] = () @@ -314,8 +323,14 @@ def preflight(self, runs_dir: Path, runtime: str) -> None: def detect_outcome(self, case_dir: Path, start_time: str | None = None) -> str | None: """Read solver output to decide STATUS_DONE / STATUS_FAILED / None (still unknown).""" - def read_progress(self, case_dir: Path, start_time: str | None = None) -> int | None: - """Best-effort current iteration / time step of a run.""" + def read_progress(self, case_dir: Path, start_time: str | None = None, *, running: bool = True) -> int | None: + """Best-effort current iteration / time step of a run. + + `running` says whether the run is still going. A solver that reads a + live progress marker must ignore it once the run is over: such a marker + can outlive the run, and then reports forever the iteration it was last + written at. + """ return None def read_restart_origin(self, case_dir: Path) -> dict[str, int | float]: @@ -407,6 +422,16 @@ def find_performance_log(self, case_dir: Path) -> Path | None: def parse_performance(self, path: Path) -> dict[str, str | None]: return {} + def prepare_remote_case(self, case_dir: Path) -> None: # noqa: B027 - optional hook, not abstract + """Adjust a case about to run on an execution backend. Does nothing by default. + + A backend lays the campaign's shared directories *inside* the case + directory, because a remote task has a single working directory and no + parent to put them in. A solver that expects them somewhere else has to + be told, and only the adapter knows how to tell it. Must never raise: + preparing is not allowed to be the thing that fails a launch. + """ + def doctor_checks( self, runs_dir: Path, diff --git a/csauto/solvers/code_aster.py b/csauto/solvers/code_aster.py index f06278d..1332496 100644 --- a/csauto/solvers/code_aster.py +++ b/csauto/solvers/code_aster.py @@ -34,6 +34,13 @@ class CodeAsterAdapter(SolverAdapterBase): "MESH", "RESU", ) + # Declared for completeness. code_aster does not run on an execution + # backend yet: run_argv returns [] because build_run_command composes the + # whole launch line itself, so there is no argv to hand to a remote task. + observability_globs: ClassVar[tuple[str, ...]] = ( + "RESU/LOGS/run_solver.log", + "RESU/LOGS/*.mess", + ) # doe_row.csv is written into every generated case by the generic DOE code, # so it is comparable whatever the solver. The .export file would be a better # candidate but its name varies per case (find_setup_file globs *.export) diff --git a/csauto/solvers/code_saturne.py b/csauto/solvers/code_saturne.py index db70989..296c305 100644 --- a/csauto/solvers/code_saturne.py +++ b/csauto/solvers/code_saturne.py @@ -2,6 +2,7 @@ from __future__ import annotations +import contextlib import math import re import shlex @@ -82,6 +83,14 @@ class CodeSaturneAdapter(SolverAdapterBase): default_docker_image: ClassVar[str] = "simvia/code_saturne" results_dirname: ClassVar[str] = "RESU" shared_dir_names: ClassVar[tuple[str, ...]] = ("MESH", "POST") + observability_globs: ClassVar[tuple[str, ...]] = ( + "RESU/*/residuals.csv", + "RESU/*/listing", + "RESU/*/run_solver.log", + "RESU/*/performance.log", + "RESU/*/monitoring/*.csv", + "RESU/*/run_status.*", + ) template_input_names: ClassVar[frozenset[str]] = frozenset({"setup.xml", "run.cfg"}) anomaly_file_names: ClassVar[tuple[str, ...]] = ANOMALY_FILES_DEFAULT cleanup_log_names: ClassVar[frozenset[str]] = frozenset( @@ -346,25 +355,30 @@ def _extract_restart_checkpoint_state(self, case_dir: Path, run_id: str) -> tupl return None, None def detect_outcome(self, case_dir: Path, start_time: str | None = None) -> str | None: - outcome = detect_run_outcome(case_dir, start_time) - if outcome: - return outcome - # The logs gave no verdict. A run that fails before the solver starts (a - # missing mesh, say) writes no run_solver.log at all, only a status - # marker beside it. Restricted to the current run, so a marker left by a - # previous run never overrides the log of this one. + # The marker the solver writes comes first, because the log is only a + # heuristic and it can be read wrong in both directions: a run that + # fails before the solver starts writes no log at all, and a run killed + # by the runaway-computation check prints its closing banner first and + # puts the real message in `error`, so the log reads as a success. + # Restricted to the current run, so a marker left by a previous one + # never fails a good run. start_ts = _parse_start_time(start_time) for run_dir in self.list_run_dirs(case_dir): for name in RUN_STATUS_FAILURE_NAMES: marker = run_dir / name if marker.is_file() and _is_recent(marker, start_ts): return STATUS_FAILED - return None - - def read_progress(self, case_dir: Path, start_time: str | None = None) -> int | None: - run_status_path = locate_run_status_file(case_dir, start_time) - if run_status_path: - return extract_run_status_iteration(run_status_path) + return detect_run_outcome(case_dir, start_time) + + def read_progress(self, case_dir: Path, start_time: str | None = None, *, running: bool = True) -> int | None: + # run_status.running is only trustworthy while the run is running. + # code_saturne removes it when the calculation ends, but a copy pulled + # back by an execution backend's snapshot survives, and would report + # the iteration it was captured at for the rest of the campaign. + if running: + run_status_path = locate_run_status_file(case_dir, start_time) + if run_status_path: + return extract_run_status_iteration(run_status_path) log_path = locate_log_file(case_dir, start_time) return extract_last_iteration(log_path) if log_path else None @@ -374,6 +388,36 @@ def read_restart_origin(self, case_dir: Path) -> dict[str, int | float]: def locate_case_file(self, case_dir: Path, name: str) -> Path | None: return locate_case_file(case_dir, name) + def prepare_remote_case(self, case_dir: Path) -> None: + """Point setup.xml at the MESH directory a backend places inside the case. + + code_saturne resolves a bare mesh name against `/MESH`, the + parent of the case directory, which a remote task does not have. It + also honours `` in setup.xml, resolved against the case + directory itself (`cs_case_domain.py:1229`), and it keeps the study + directory as a fallback, so the entry is harmless for a local run. + """ + import xml.etree.ElementTree as ElementTree + + try: + setup = self.find_setup_file(case_dir) + tree = ElementTree.parse(setup) + except Exception: + # A case with no readable setup fails later, with a better message. + return + + meshes = tree.getroot().find(".//solution_domain/meshes_list") + if meshes is None or meshes.find("meshdir") is not None: + return + shared = self.shared_dir_names[0] if self.shared_dir_names else "" + if not shared: + return + node = ElementTree.Element("meshdir") + node.set("name", shared) + meshes.insert(0, node) + with contextlib.suppress(OSError): + tree.write(setup, encoding="utf-8", xml_declaration=True) + def find_setup_file(self, template_dir: Path) -> Path: return find_setup_file(template_dir) diff --git a/csauto/solvers/stub.py b/csauto/solvers/stub.py index be5c581..196638b 100644 --- a/csauto/solvers/stub.py +++ b/csauto/solvers/stub.py @@ -40,6 +40,7 @@ class StubAdapter(SolverAdapterBase): container_root: ClassVar[str] = "/stub" default_docker_image: ClassVar[str] = "" results_dirname: ClassVar[str] = "OUT" + observability_globs: ClassVar[tuple[str, ...]] = (f"OUT/*/{STUB_LOG_FILENAME}",) template_input_names: ClassVar[frozenset[str]] = frozenset({STUB_SETUP_FILENAME}) anomaly_file_names: ClassVar[tuple[str, ...]] = ("csauto.stderr", "csauto.stdout", STUB_LOG_FILENAME) cleanup_log_names: ClassVar[frozenset[str]] = frozenset({STUB_LOG_FILENAME, "csauto.stdout", "csauto.stderr"}) @@ -99,7 +100,7 @@ def apply_control( (run_dir / "stub_control").write_text("stop\n", encoding="utf-8") return {"action": "stop"} - def read_progress(self, case_dir: Path, start_time: str | None = None) -> int | None: + def read_progress(self, case_dir: Path, start_time: str | None = None, *, running: bool = True) -> int | None: log_path = self.locate_case_file(case_dir, STUB_LOG_FILENAME) if not log_path: return None diff --git a/csauto/web_support.py b/csauto/web_support.py index 1ec7daf..2e5f276 100644 --- a/csauto/web_support.py +++ b/csauto/web_support.py @@ -314,15 +314,29 @@ def kill_case( start_time = record.get("start_time") previous_end_time = record.get("end_time") pid_raw = record.get("pid") + backend_name = str(record.get("backend") or "") + task_id = str(record.get("task_id") or "") - if pid_raw is None and not container_id and not job_id: - raise ValueError(f"No PID, container_id or job_id for {case_id}") + if pid_raw is None and not container_id and not job_id and not task_id: + raise ValueError(f"No PID, container_id, job_id or task_id for {case_id}") details: dict[str, Any] = { "pid": pid_raw, "container_id": container_id, "job_id": job_id, } + if task_id: + details["task_id"] = task_id + + backend_error: Exception | None = None + if backend_name and task_id: + try: + from .backends import get_backend + + get_backend(backend_name).cancel(task_id) + details["backend_cancelled"] = True + except Exception as exc: + backend_error = exc container_error: Exception | None = None if container_id: @@ -352,6 +366,8 @@ def kill_case( except Exception as exc: job_error = exc + if backend_error and pid_raw is None and not container_id and not job_id: + raise backend_error if container_error and pid_raw is None and job_id is None: raise container_error if job_error and pid_raw is None and not container_id: diff --git a/docs/adding-a-solver.md b/docs/adding-a-solver.md index 371c5a6..6d2e343 100644 --- a/docs/adding-a-solver.md +++ b/docs/adding-a-solver.md @@ -159,3 +159,17 @@ ships a header logo today; adding one means editing the Simvia mark and switches to `/favicon-.svg` automatically when that asset exists — drop a `favicon-.svg` into `frontend/static/` and rebuild the frontend to ship one. + +## Running on an execution backend + +A backend's remote working directory is the case directory, and the campaign's +shared directories (whatever `shared_dir_names` declares) sit **inside** it, +because a remote task has a single writable directory and no parent. + +If your solver resolves those directories relative to something else, override +`prepare_remote_case(case_dir)` to tell it. It is called once, before the case +is uploaded, and must never raise: preparing is not allowed to be the thing +that fails a launch. `CodeSaturneAdapter` is the worked example, adding a +`` entry to `setup.xml`. + +Solvers that need nothing leave it alone; the default does nothing. diff --git a/docs/api.md b/docs/api.md index 17d469f..3c17e19 100644 --- a/docs/api.md +++ b/docs/api.md @@ -99,6 +99,41 @@ Response: - `{ "records": [...] }` +## `GET /api/launch_options` + +Purpose: + +- list what an execution backend lets a user choose at launch time, for the Run + dialog + +Query parameters: + +- `backend` (required): a name from the `backends` list in `GET /api/app_config` + +Response: + +```json +{ + "backend": "qarnot", + "degraded": false, + "options": [ + {"key": "scheduling", "label": "Priority", "default": "Flex", + "choices": [["Flex", "Flex - cheaper, may wait for spare capacity"]]} + ] +} +``` + +Rules: + +- an unknown backend is rejected with HTTP 400 +- the route never fails otherwise: when the provider cannot be reached it + returns `degraded: true` with whatever it could build, so the dialog still + opens and a launch remains possible with the defaults +- it never returns a credential of any kind +- call it when the dialog opens, not on page load: it may talk to the provider + +--- + ## `GET /api/residuals` Purpose: @@ -525,6 +560,32 @@ Response: } ``` +## `POST /api/sync_backends` + +Purpose: + +- pull fresh data for the cases an execution backend owns, on demand + +Payload: none. + +Response: + +```json +{ "status": "ok", "synced": true } +``` + +Rules: + +- throttled to one pass every ten seconds; `synced` says whether this call ran + one or found the window still open +- never fails: a provider outage returns `synced: true` with nothing fetched, + because the caller is a Refresh click and must not be broken by it +- the background loop in `csauto serve` already syncs on a timer; this exists so + pressing Refresh means something for a cloud case, whose local files only + change when a pass runs + +--- + ## `POST /api/kill_case` Purpose: @@ -640,6 +701,11 @@ Rules: - `cases`, `n`, `nt` are required - `n`, `nt`, `max_parallel` must be integers `> 0` +- `backend` is optional: the name of an execution backend to run on, taken from + the `backends` list returned by `GET /api/app_config`. Omit it, or send an + empty string, to run on the machine hosting the server. An unknown name is + rejected with HTTP 400. `GET /api/app_config` never returns a credential of + any kind: how a backend authenticates is not the dashboard's business - `restart_mode` accepted values: `iterations`, `physical_time` - `restart_value` must match selected mode constraints diff --git a/docs/architecture.md b/docs/architecture.md index a50bf97..360a703 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -109,6 +109,50 @@ Adding a solver means writing one adapter class and registering it in `csauto/solvers/__init__.py`; the core does not change. See [Adding a new solver](./adding-a-solver.md) for the step-by-step guide. +## Execution backend boundary + +Where `SolverAdapter` answers "what does this solver do", `ExecutionBackend` +(`csauto/backends/base.py`) answers "where does this case run". A backend +implements five verbs: `submit`, `poll`, `sync`, `fetch_final`, `cancel`. +`BackendState.status` already speaks csauto's vocabulary, so translating a +provider's own state names is the backend's job, never the runner's. + +A case a backend owns carries `backend` and `task_id` in the registry instead of +a `pid` or a `job_id`, and `_compute_refresh_result` does not finalise it: the +synchronisation pass in `csauto/backend_sync.py` owns its status. That pass runs +on a timer in `csauto serve` and once, throttled, in `csauto status`. + +Two invariants the implementation depends on: + +- **`DONE` means the results are on local disk.** Compare reads files, Clean + removes directories, `resu_size_mb` measures a size. The pass marks a case + `DONE` only after `fetch_final` succeeded. +- **A failed poll never changes a status.** A flaky network must not mark a + whole campaign as failed; consecutive failures are counted instead. + +`QarnotBackend` (`backend = "qarnot"`) is the first real implementation. It runs +any docker image through Qarnot's generic `docker-batch` profile, uploads the +shared directories once per campaign and the case inputs once per case, and +turns `adapter.observability_globs` into the regex whitelist Qarnot's periodic +snapshot expects. Everything Qarnot-specific (the SDK, the buckets, the +`DOCKER_*` constants, the state table) lives in `csauto/backends/qarnot.py`; +`csauto/backends/qarnot_support.py` holds the parts that are decisions rather +than API calls, so they are testable with no SDK. See +[Running a campaign on Qarnot](./qarnot.md). + +A backend's remote working directory **is** the case directory, and the +campaign's shared directories sit inside it: a remote task has one writable +directory and no parent to put them in. A solver that expects them elsewhere is +adjusted by its own adapter, through `SolverAdapter.prepare_remote_case`, which +the runner calls before any backend launch. That keeps the backend ignorant of +solvers and the adapter ignorant of providers. + +`local` and Slurm are not behind this contract. They predate it and work; porting +them is a later, mechanical refactor. + +`FakeBackend` (`backend = "fake"`) is a shipped fake, what `StubAdapter` is to +solvers: it covers the whole lifecycle in tests with no network and no account. + ## Persistent data model - `RUNS/registry.json` - global case state diff --git a/docs/cli.md b/docs/cli.md index f87a61f..be2a3c3 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -374,6 +374,10 @@ Checks performed: - Runtime binary / image is accessible (reads `csauto.toml` — native binary, Singularity image, or Docker) - X11 display availability (for GUI launch) +`--backend ` also checks the prerequisites of an execution backend, for +example `csauto doctor RUNS --backend qarnot` (SDK installed, `QARNOT_TOKEN` +set, an image to run, and a solver that can produce a remote command). + Each check prints `[OK]` or `[FAIL]`. Fix all failures before running. --- diff --git a/docs/config.md b/docs/config.md index 20b8c60..932d9f6 100644 --- a/docs/config.md +++ b/docs/config.md @@ -118,6 +118,11 @@ token = "your-secret-token" | `use_slurm` | (auto-detected) | `true` to force Slurm submission, `false` to force local | | `mpi_exec_options` | (none) | Options injected as `CS_MPIEXEC_OPTIONS` in Slurm jobs | | `max_parallel` | `1` | Maximum simultaneous case launches | +| `backend_poll_interval_s` | `15` | Seconds between two execution-backend polls | +| `backend_sync_interval_s` | `60` | Seconds between two observability file syncs | +| `[qarnot].profile` | `docker-batch` | Qarnot profile used for cloud runs | +| `[qarnot].snapshot_interval_s` | `60` | Seconds between two remote captures of the observability files | +| `[qarnot].max_upload_mb` | `512` | Refuse to submit a case whose own inputs exceed this | | `mesh_mode` | `symlink` | How `prepare` places the shared `MESH`/`POST` dirs into `RUNS/`: `symlink` or `copy` (see [concepts.md](./concepts.md#shared-meshpost-directories)) | | `host` | `127.0.0.1` | Bind address for `serve` | | `port` | `8000` | Bind port for `serve` | diff --git a/docs/contributing.md b/docs/contributing.md index 3a075e7..8b4ce3b 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -22,6 +22,10 @@ pytest tests/runner -q pytest tests/web -q ``` +`tests/integration/test_qarnot_live.py` talks to the real Qarnot API and is +skipped unless `CSAUTO_QARNOT_LIVE=1` is set, alongside a `QARNOT_TOKEN`. It +costs money, so it never runs in CI. + ## Coding style - simple Python, standard library first diff --git a/docs/qarnot.md b/docs/qarnot.md new file mode 100644 index 0000000..b1f6b2e --- /dev/null +++ b/docs/qarnot.md @@ -0,0 +1,170 @@ +# Running a campaign on Qarnot + +csauto can send cases to the [Qarnot](https://qarnot.com) cloud instead of running +them on this machine. You run them on **your own Qarnot account**: csauto submits +on your behalf, and Simvia hosts nothing, bills nothing and sees nothing. + +Supported solvers: code_saturne and the `stub` test solver. code_aster is not +supported yet, because its adapter composes its whole launch line locally +instead of producing a command a remote container can run; `csauto doctor +--backend qarnot` says so. + +## Setup + +```bash +pip install "csauto[qarnot]" +export QARNOT_TOKEN="" +csauto doctor RUNS --backend qarnot +``` + +`doctor` reports the SDK, the token, the image, whether the configured solver +can build a remote command, and what the account still has room for: + +``` +[OK] qarnot SDK available +[OK] QARNOT_TOKEN is set +[OK] qarnot buckets 4/100 +[OK] qarnot storage 1.2/40.0 GB used +``` + +The quota lines matter. An exhausted bucket or storage quota surfaces as a +`QuotaExceeded` in the middle of an upload, after you have chosen a campaign and +pressed the button. `doctor` warns before that, and never prints the token +itself, only what it buys. + +**The token goes in the environment, never in `csauto.toml`.** That file lives in +the campaign directory, which gets shared, committed and archived. csauto +refuses to start if it finds a token in there. + +## Launching from the dashboard + +Start `csauto serve`, select the cases, press **Run**, and pick `qarnot` in the +**Run on** selector. The dialog restates how many cases are about to be +submitted before anything is sent. + +The selector only appears once the server can offer a backend, and the choice +applies to that launch alone, so a campaign can be split between this machine +and the cloud. + +## Settings + +`csauto.toml` carries the non-sensitive settings only: + +```toml +[qarnot] +profile = "docker-batch" # the Qarnot profile +snapshot_interval_s = 60 # how often the remote task captures the watched files +max_upload_mb = 512 # refuse a case whose own inputs exceed this +``` + +Two more settings govern how often csauto looks: + +```toml +backend_poll_interval_s = 15 # status, progress, stdout and stderr +backend_sync_interval_s = 60 # pull the captured files +``` + +Pulling more often than the task captures only wastes requests, so keep +`backend_sync_interval_s` at or above `snapshot_interval_s`. + +## Which machine a case runs on + +csauto asks Qarnot for a machine with **at least `n` x `nt` cores**, derived from +the MPI ranks and threads you enter in the Run dialog, **when your account offers +that exact constraint**. Qarnot validates hardware constraints against a +catalogue and refuses a whole submission for one it does not recognise, so a +core count the account does not list is dropped rather than risked: running on a +machine csauto did not get to choose beats not running at all. + +`csauto doctor --backend qarnot` and the Node type list both read that same +catalogue. + +The Run dialog also lets you pick, per launch: + +- **Priority**: `Flex` (cheaper, waits for spare capacity), `OnDemand` (starts + sooner, costs more) or `Reserved` (uses capacity reserved on your account). + `Reserved` fails unless your account actually has a reservation. +- **Node type**: one hardware specification from your account, or any node. The + list is read from Qarnot when the dialog opens and cached for ten minutes; if + it cannot be read, the dialog says so and the run uses any node. + +Memory floors, GPUs and CPU models are not exposed yet. + +## What is uploaded, and what comes back + +The remote working directory **is** the case: it is the only place a task may +write, so the campaign's shared directories land inside it rather than beside +it, unlike the local `RUNS/` layout. + +``` +/job/DATA/setup.xml the case +/job/MESH/mesh1.med the campaign's shared directories, inside it +``` + +A solver that expects them elsewhere is told by its own adapter, through +`prepare_remote_case`. code_saturne resolves a bare mesh name against +`/MESH`, which a remote task has no parent for, so its adapter adds a +`` entry to `setup.xml`. The entry is harmless locally: code_saturne +keeps the study directory as a fallback. + +Only the results directory comes back. Without that restriction a task would +also return the shared directories it was handed, re-downloading a mesh of +several gigabytes on every run. + + +| Bucket | Content | Uploaded | +|---|---|---| +| `csauto---shared` | the shared directories (`MESH`, `POST`) | once per campaign, and again only if they change | +| `csauto---` | the case's own inputs (`DATA/`, `SRC/`, `doe_row.csv`, ...) | once per case | +| `csauto----out` | snapshots and results | written by the task | + +While a case runs, only the files the solver adapter declares as observability +files come back (for code_saturne: the listing, the residuals, the monitoring +probes and the run status markers). That is a few hundred kB, which keeps the +snapshot well under the 1 GB Qarnot advises. The complete results directory is +downloaded once, when the task finishes. + +Because those files genuinely land in `RUNS/caseXXXX/`, the dashboard's +residuals, probes, log tail and error panels work during a cloud run exactly as +they do locally. + +## Monitoring + +Live monitoring goes through `csauto serve`: the polling loop lives in the +server. From the CLI, `csauto status RUNS` performs one poll and one sync, at +most once every ten seconds. + +## What the statuses mean + +- `PENDING`: submitted, waiting for machines. +- `RUNNING`: executing, or uploading and downloading its results. +- `DONE`: **the results are on local disk.** csauto never reports `DONE` before + the download succeeded, because Compare, Clean and the size figures all read + local files. +- `FAILED`: the task failed, or was cancelled. + +A failed poll never changes a status. A flaky network must not mark a hundred +cases as failed, so consecutive failures are counted and reported instead. + +## Costs + +csauto shows no price estimate: it has no pricing figure it can vouch for, and a +wrong number on a launch button would be worse than none. What it does do is +refuse an oversized upload before submission, and record the execution time and +core count of each case afterwards. + +## Verifying against the real API + +Every other Qarnot test runs against a fake connection, which proves what csauto +asks Qarnot to do. One test proves what Qarnot does with it, and it is the only +one that costs money, so it is skipped unless you ask for it: + +```bash +CSAUTO_QARNOT_LIVE=1 QARNOT_TOKEN=... .venv/bin/python -m pytest \ + tests/integration/test_qarnot_live.py -q -s +``` + +It submits one `alpine` task of about a minute, and asserts the three claims the +design rests on: that the case inputs arrive in the task's working directory, +that a partial result comes back while the task is still running, and that the +full results land on local disk. It cancels the task even when it fails. diff --git a/docs/web-ui.md b/docs/web-ui.md index 056e16c..ff300d8 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -65,6 +65,12 @@ The toolbar provides: Each row also has an **Open GUI** button to launch the code_saturne GUI for that case. +For a case running on an execution backend, **Refresh** first asks the server to +pull fresh data from the provider, then rereads. Without that it would reread +local files that only change when the background sync pass runs, so the panels +would look frozen between two passes. The request is throttled to one every ten +seconds, so clicking repeatedly costs nothing. + ### Selecting cases - Click a row to select it @@ -82,6 +88,22 @@ With one or more cases selected, the action buttons activate: **Run**: launch the selected cases (`PREPARED`, `DONE`, or `FAILED`). A popup asks for `--n` (MPI ranks), `--nt` (OMP threads), and `--max-parallel`. +When the server offers an execution backend, the popup gains a **Run on** +selector. It defaults to **This machine**, which is the behaviour csauto has +always had. Choosing a cloud backend restates, before you press the button, how +many cases are about to be submitted and that they run on your own account and +bill you for the compute. + +The choice is per launch, not per campaign: verify one case locally, then send +the other ninety-nine to the cloud. See +[Running a campaign on Qarnot](./qarnot.md). + +Choosing a backend also reveals whatever that backend lets you choose. For +Qarnot that is the scheduling **Priority**, which trades price against waiting +time, and the **Node type**, listed from your own account. If the provider +cannot be reached the selects still appear with their defaults and a line says +so; the run goes ahead either way. + **Restart**: restart from the latest checkpoint. A popup asks for the stop criterion: - `Iterations`: add N more iterations beyond the current checkpoint diff --git a/frontend/dist/_app/immutable/assets/2.BvWkSkaN.css b/frontend/dist/_app/immutable/assets/2.BvWkSkaN.css new file mode 100644 index 0000000..d329bac --- /dev/null +++ b/frontend/dist/_app/immutable/assets/2.BvWkSkaN.css @@ -0,0 +1 @@ +/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-mono:"JetBrains Mono", "Fira Code", "Cascadia Code", "Courier New", monospace;--color-red-600:oklch(57.7% .245 27.325);--color-amber-400:oklch(82.8% .189 84.429);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-light:300;--font-weight-normal:400;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:"edf-2020";--default-mono-font-family:var(--font-mono);--color-edf-orange-fonce:#d6430a;--color-edf-orange-moyen:#ff861d;--color-edf-orange-clair:#ffb210;--color-edf-vert-fonce:#307a10;--color-edf-bleu-fonce:#001a70;--color-edf-bleu-moyen:#1057c8;--color-edf-bleu-clair:#1089ff;--color-edf-noir:#333;--color-edf-slate:#f8f7f7;--color-edf-gris-moyen:#dedede;--color-edf-gris-clair:#f8f7f7;--color-edf-blanc:#fff;--color-bg:var(--color-edf-slate);--color-ink:var(--color-edf-noir);--color-muted:#787878;--color-card:var(--color-edf-blanc);--color-border:var(--color-edf-gris-moyen);--color-table-head:var(--color-edf-gris-clair);--color-table-row:var(--color-edf-blanc);--color-table-hover:#f5f5f4;--color-table-selected:#e8eff8;--color-table-selected-hover:#dce7f4;--animate-rise:rise .35s ease both}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.sticky\!{position:sticky!important}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.end\!{inset-inline-end:var(--spacing)!important}.top-0{top:calc(var(--spacing) * 0)}.top-1\/2{top:50%}.right-0{right:calc(var(--spacing) * 0)}.right-2{right:calc(var(--spacing) * 2)}.right-4{right:calc(var(--spacing) * 4)}.bottom-4{bottom:calc(var(--spacing) * 4)}.left-0{left:calc(var(--spacing) * 0)}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.z-80{z-index:80}.z-\[1\]{z-index:1}.z-\[1000\]{z-index:1000}.col-span-6{grid-column:span 6/span 6}.col-span-12{grid-column:span 12/span 12}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing) * 0)}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mr-auto{margin-right:auto}.mb-0{margin-bottom:calc(var(--spacing) * 0)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-2\.5{margin-bottom:calc(var(--spacing) * 2.5)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-3\.5{margin-bottom:calc(var(--spacing) * 3.5)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-4\.5{margin-bottom:calc(var(--spacing) * 4.5)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-auto{margin-left:auto}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.aspect-\[900\/500\]{aspect-ratio:900/500}.h-7{height:calc(var(--spacing) * 7)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-14{height:calc(var(--spacing) * 14)}.h-\[3px\]{height:3px}.h-\[10px\]{height:10px}.h-\[14px\]{height:14px}.h-\[30px\]{height:30px}.h-\[34px\]{height:34px}.h-full{height:100%}.max-h-\[240px\]{max-height:240px}.max-h-\[260px\]{max-height:260px}.max-h-\[500px\]{max-height:500px}.\!min-h-\[80px\]{min-height:80px!important}.w-9{width:calc(var(--spacing) * 9)}.w-32\.5{width:calc(var(--spacing) * 32.5)}.w-37\.5{width:calc(var(--spacing) * 37.5)}.w-\[10px\]{width:10px}.w-\[14px\]{width:14px}.w-\[30px\]{width:30px}.w-\[40px\]{width:40px}.w-\[70px\]{width:70px}.w-\[72px\]{width:72px}.w-\[80px\]{width:80px}.w-\[90px\]{width:90px}.w-\[110px\]{width:110px}.w-\[130px\]{width:130px}.w-\[140px\]{width:140px}.w-\[150px\]{width:150px}.w-\[160px\]{width:160px}.w-\[180px\]{width:180px}.w-\[200px\]{width:200px}.w-\[min\(420px\,96vw\)\]{width:min(420px,96vw)}.w-\[min\(1200px\,94vw\)\]{width:min(1200px,94vw)}.w-\[min\(\{maxWidth\}\,96vw\)\]{width:min({maxWidth},96vw)}.w-auto{width:auto}.w-full{width:100%}.w-max{width:max-content}.max-w-\[140px\]{max-width:140px}.max-w-\[320px\]{max-width:320px}.max-w-\[920px\]{max-width:920px}.\!min-w-0{min-width:calc(var(--spacing) * 0)!important}.min-w-\[36px\]{min-width:36px}.min-w-\[40px\]{min-width:40px}.min-w-\[72px\]{min-width:72px}.min-w-\[170px\]{min-width:170px}.min-w-\[200px\]{min-width:200px}.min-w-full{min-width:100%}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-180{rotate:180deg}.animate-pulse{animation:var(--animate-pulse)}.animate-rise{animation:var(--animate-rise)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-1\.5{row-gap:calc(var(--spacing) * 1.5)}.self-end{align-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-clip{overflow:clip}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[10px\]{border-radius:10px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-none{--tw-border-style:none;border-style:none}.border-\[rgba\(51\,51\,51\,0\.08\)\]{border-color:#33333314}.border-amber-400\/50{border-color:#fcbb0080}@supports (color:color-mix(in lab,red,red)){.border-amber-400\/50{border-color:color-mix(in oklab,var(--color-amber-400) 50%,transparent)}}.border-border{border-color:var(--color-border)}.border-edf-bleu-moyen{border-color:var(--color-edf-bleu-moyen)}.border-edf-gris-moyen{border-color:var(--color-edf-gris-moyen)}.border-transparent{border-color:#0000}.bg-\[rgba\(0\,26\,112\,0\.3\)\]{background-color:#001a704d}.bg-\[rgba\(16\,87\,200\,0\.06\)\]{background-color:#1057c80f}.bg-\[rgba\(48\,122\,16\,0\.04\)\]{background-color:#307a100a}.bg-\[rgba\(48\,122\,16\,0\.06\)\]{background-color:#307a100f}.bg-\[rgba\(48\,122\,16\,0\.08\)\]{background-color:#307a1014}.bg-\[rgba\(214\,67\,10\,0\.04\)\]{background-color:#d6430a0a}.bg-\[rgba\(214\,67\,10\,0\.06\)\]{background-color:#d6430a0f}.bg-\[rgba\(214\,67\,10\,0\.08\)\]{background-color:#d6430a14}.bg-\[rgba\(214\,67\,10\,0\.12\)\]{background-color:#d6430a1f}.bg-\[rgba\(255\,178\,16\,0\.1\)\]{background-color:#ffb2101a}.bg-\[rgba\(255\,178\,16\,0\.3\)\]{background-color:#ffb2104d}.bg-\[rgba\(255\,178\,16\,0\.6\)\]{background-color:#ffb21099}.bg-amber-400\/10{background-color:#fcbb001a}@supports (color:color-mix(in lab,red,red)){.bg-amber-400\/10{background-color:color-mix(in oklab,var(--color-amber-400) 10%,transparent)}}.bg-card{background-color:var(--color-card)}.bg-edf-blanc{background-color:var(--color-edf-blanc)}.bg-edf-bleu-moyen{background-color:var(--color-edf-bleu-moyen)}.bg-edf-gris-clair{background-color:var(--color-edf-gris-clair)}.bg-edf-orange-fonce{background-color:var(--color-edf-orange-fonce)}.bg-edf-orange-moyen{background-color:var(--color-edf-orange-moyen)}.bg-edf-vert-fonce{background-color:var(--color-edf-vert-fonce)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/60{background-color:#fff9}@supports (color:color-mix(in lab,red,red)){.bg-white\/60{background-color:color-mix(in oklab,var(--color-white) 60%,transparent)}}.p-0{padding:calc(var(--spacing) * 0)}.p-1{padding:calc(var(--spacing) * 1)}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.p-4{padding:calc(var(--spacing) * 4)}.p-4\.5{padding:calc(var(--spacing) * 4.5)}.p-5{padding:calc(var(--spacing) * 5)}.p-\[16px_18px_18px\]{padding:16px 18px 18px}.\!px-2\.5{padding-inline:calc(var(--spacing) * 2.5)!important}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-\[1px\]{padding-inline:1px}.px-\[10px\]{padding-inline:10px}.\!py-0{padding-block:calc(var(--spacing) * 0)!important}.\!py-1{padding-block:calc(var(--spacing) * 1)!important}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-\[7px\]{padding-block:7px}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-7{padding-right:calc(var(--spacing) * 7)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-12{padding-bottom:calc(var(--spacing) * 12)}.pl-2\.5{padding-left:calc(var(--spacing) * 2.5)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-\[edf-2020-soft\]{font-family:edf-2020-soft}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[11px\]{font-size:11px}.text-\[12\.5px\]{font-size:12.5px}.text-\[13px\]{font-size:13px}.leading-\[1\.6\]{--tw-leading:1.6;line-height:1.6}.leading-\[1\.45\]{--tw-leading:1.45;line-height:1.45}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-edf-bleu-clair{color:var(--color-edf-bleu-clair)}.text-edf-bleu-fonce{color:var(--color-edf-bleu-fonce)}.text-edf-bleu-moyen{color:var(--color-edf-bleu-moyen)}.text-edf-gris-moyen{color:var(--color-edf-gris-moyen)}.text-edf-noir{color:var(--color-edf-noir)}.text-edf-orange-fonce{color:var(--color-edf-orange-fonce)}.text-edf-vert-fonce{color:var(--color-edf-vert-fonce)}.text-ink{color:var(--color-ink)}.text-muted{color:var(--color-muted)}.text-red-600{color:var(--color-red-600)}.text-white{color:var(--color-white)}.italic{font-style:italic}.opacity-40{opacity:.4}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-\[background\]{transition-property:background;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-\[120ms\]{--tw-duration:.12s;transition-duration:.12s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.select-all{-webkit-user-select:all;user-select:all}.select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.hover\:border-border:hover{border-color:var(--color-border)}.hover\:border-edf-bleu-clair:hover{border-color:var(--color-edf-bleu-clair)}.hover\:border-edf-gris-moyen:hover{border-color:var(--color-edf-gris-moyen)}.hover\:bg-\[rgb\(12\,72\,170\)\]:hover{background-color:#0c48aa}.hover\:bg-\[rgb\(38\,98\,12\)\]:hover{background-color:#26620c}.hover\:bg-\[rgb\(180\,56\,8\)\]:hover{background-color:#b43808}.hover\:bg-\[rgb\(230\,118\,20\)\]:hover{background-color:#e67614}.hover\:bg-edf-gris-clair:hover{background-color:var(--color-edf-gris-clair)}.hover\:text-muted:hover{color:var(--color-muted)}.hover\:underline:hover{text-decoration-line:underline}.hover\:decoration-dotted:hover{text-decoration-style:dotted}.hover\:underline-offset-2:hover{text-underline-offset:2px}}.focus\:border-edf-bleu-moyen:focus{border-color:var(--color-edf-bleu-moyen)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-40:disabled{opacity:.4}@media not all and (min-width:64rem){.max-lg\:order-3{order:3}.max-lg\:h-auto{height:auto}.max-lg\:w-full{width:100%}.max-lg\:flex-wrap{flex-wrap:wrap}.max-lg\:gap-2{gap:calc(var(--spacing) * 2)}.max-lg\:gap-3{gap:calc(var(--spacing) * 3)}}}@font-face{font-family:edf-2020;src:url(./EDF2020W01-Regular.DEtAzL8F.woff2)format("woff2");font-weight:400;font-style:normal;font-display:swap}@font-face{font-family:edf-2020;src:url(./EDF2020W01-Bold.Bub472Sg.woff2)format("woff2");font-weight:700;font-style:normal;font-display:swap}@font-face{font-family:edf-2020;src:url(./EDF2020W01-Light.D7COPh2m.woff2)format("woff2");font-weight:300;font-style:normal;font-display:swap}@font-face{font-family:edf-2020;src:url(./EDF2020W01-Italic.CtMn0wF8.woff2)format("woff2");font-weight:400;font-style:italic;font-display:swap}@font-face{font-family:edf-2020-soft;src:url(./EDF2020Soft-BoldItalic.y4KHcb-c.woff2)format("woff2");font-weight:700;font-style:italic;font-display:swap}@font-face{font-family:edf-2020-soft;src:url(./EDF2020Soft-Italic.CTdZcEjD.woff2)format("woff2");font-weight:400;font-style:italic;font-display:swap}@font-face{font-family:edf-2020-soft;src:url(./EDF2020Soft-LightItalic.DlUXaATs.woff2)format("woff2");font-weight:300;font-style:italic;font-display:swap}html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{color:var(--color-ink);background:var(--color-bg);min-height:100vh;margin:0}input,select,textarea{border:1px solid var(--color-border);height:30px;color:var(--color-ink);background:#fff;border-radius:6px;padding:5px 10px;font-family:inherit;font-size:13px;transition:border-color .15s}textarea,select[multiple]{height:auto}input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}input[type=number]{-moz-appearance:textfield}input:focus,select:focus,textarea:focus{border-color:var(--color-edf-bleu-moyen);outline:none}select{min-width:160px}select[multiple]{min-width:180px}textarea{width:100%;min-height:160px;font-family:var(--font-mono);font-size:12px}[data-icon]:before{content:attr(data-icon);opacity:.8;justify-content:center;align-items:center;font-size:12px;display:inline-flex}.table-wrap{border:1px solid var(--color-border);background:var(--color-table-row);border-radius:8px;overflow-x:auto}#status-table{border-collapse:separate;border-spacing:0;width:max-content;min-width:100%}table{border-collapse:collapse;width:100%;min-width:760px}th,td{text-align:left;border-bottom:1px solid var(--color-edf-gris-clair);padding:8px 10px;font-size:13px}th{text-transform:uppercase;letter-spacing:.12em;color:var(--color-muted);background:var(--color-table-head);z-index:1;font-size:11px;font-weight:700;position:sticky;top:0}th.status-col-doe,th.status-col-calc{white-space:nowrap}td.status-col-doe{font-family:var(--font-mono);font-size:12px}td.case-id{font-weight:700}tbody tr{background:var(--color-table-row)}tbody tr:hover{background:var(--color-table-hover)}tbody tr.row-selected{background:var(--color-table-selected)}tbody tr.row-selected:hover{background:var(--color-table-selected-hover)}#status-body tr,#status-body td{-webkit-user-select:none;user-select:none}th.status-sortable{cursor:pointer;-webkit-user-select:none;user-select:none}th.status-sortable:hover{color:var(--color-edf-bleu-moyen)}#status-doe-select{min-width:220px;max-width:360px}#status-table thead tr,#status-table tbody tr{height:34px}#status-table-wrap{max-height:374px;overflow-y:auto}#status-table-wrap.sticky-enabled th.sticky-left,#status-table-wrap.sticky-enabled th.sticky-right{background:var(--color-table-head);z-index:4;position:sticky}#status-table-wrap.sticky-enabled td.sticky-left,#status-table-wrap.sticky-enabled td.sticky-right{background:var(--color-table-row);z-index:3;position:sticky}#status-table-wrap.sticky-enabled .sticky-right{right:0}#status-table-wrap.sticky-enabled .sticky-divider{border-right:1px solid var(--color-border)}#status-table-wrap.sticky-enabled .sticky-right.sticky-divider{border-left:1px solid var(--color-border);border-right:none}#status-table-wrap.sticky-enabled tbody tr:hover td.sticky-left,#status-table-wrap.sticky-enabled tbody tr:hover td.sticky-right{background:var(--color-table-hover)}#status-table-wrap.sticky-enabled tbody tr.row-selected td.sticky-left,#status-table-wrap.sticky-enabled tbody tr.row-selected td.sticky-right{background:var(--color-table-selected)}#status-table-wrap.sticky-enabled tbody tr.row-selected:hover td.sticky-left,#status-table-wrap.sticky-enabled tbody tr.row-selected:hover td.sticky-right{background:var(--color-table-selected-hover)}.status-pill{letter-spacing:.1em;text-transform:uppercase;border:1px solid #0000;border-radius:4px;align-items:center;padding:3px 8px;font-size:10px;font-weight:700;display:inline-flex}.status-running{color:var(--color-edf-bleu-fonce);background:#1089ff1a;border-color:#1089ff40}.status-done{color:var(--color-edf-vert-fonce);background:#307a1014;border-color:#307a1033}.status-failed{color:var(--color-edf-orange-fonce);background:#d6430a14;border-color:#d6430a40}.status-converged{color:var(--color-edf-vert-fonce);background:#307a101f;border-color:#307a104d}.status-not-converged{color:var(--color-edf-orange-fonce);background:#d6430a1a;border-color:#d6430a40}.status-prepared,.status-unknown{background:var(--color-edf-gris-clair);color:var(--color-muted);border-color:var(--color-border)}.is-active{color:var(--color-edf-bleu-fonce);background:#1057c814;border-color:#1057c833;font-weight:700}#status-card:focus{outline:none}.link-btn{color:var(--color-edf-bleu-moyen);cursor:pointer;background:0 0;border:none;font-size:12px}.link-btn:hover{text-decoration:underline}#perf-table{min-width:920px}#perf-table tbody tr:hover{background:inherit}pre,.code-box{color:var(--color-edf-gris-clair);max-height:320px;font-size:12px;font-family:var(--font-mono);background:#1e1e2d;border-radius:8px;padding:10px 12px;overflow:auto}.code-box{white-space:pre}.tail-pane{white-space:normal;max-height:320px;padding:8px 10px;overflow:auto}.tail-meta{margin:2px 0 8px}.tail-line{white-space:pre;border-radius:3px;margin:0 -4px;padding:0 4px;line-height:1.45}.tail-sev-error{color:var(--color-edf-orange-clair);background:#d6430a33}.tail-sev-warn{color:var(--color-edf-orange-clair);background:#ffb21026}.tail-sev-info{color:var(--color-edf-gris-moyen)}.tail-query-hit{color:var(--color-edf-noir);background:#ffb21080;border-radius:2px;padding:0 2px}.tail-filter-error{color:var(--color-edf-orange-clair);margin-bottom:6px;font-weight:600}.tail-new{animation:2.6s ease-out tailFlash}@keyframes tailFlash{0%{background-color:#1089ff33}to{background-color:#0000}}code{background:#ff861d14;border-radius:4px;padding:2px 6px;font-size:12px}.error-list{border-radius:8px;flex-direction:column;gap:8px;max-height:320px;display:flex;overflow:auto}.error-item{background:#d6430a0a;border:1px solid #d6430a33;border-radius:8px;padding:10px 12px}.error-meta{color:var(--color-edf-orange-fonce);margin-bottom:0;font-size:11px;font-weight:700}.error-meta-row{justify-content:space-between;align-items:center;gap:10px;margin-bottom:0;display:flex}.error-meta-row+.error-line{margin-top:6px}.error-meta-row .error-meta{text-align:left;flex:1;margin:0}.error-badges{flex-wrap:wrap;align-items:center;gap:6px;display:inline-flex}.error-badge{color:var(--color-edf-orange-fonce);background:#d6430a14;border:1px solid #0000;border-radius:4px;align-items:center;padding:2px 7px;font-size:10px;font-weight:700;display:inline-flex}.error-badge-new{color:var(--color-edf-bleu-fonce);background:#1057c814;border-color:#1057c833}.error-line{font-family:var(--font-mono);white-space:pre-wrap;color:var(--color-edf-noir);font-size:12px}.error-empty{color:var(--color-muted);padding:6px 0;font-size:12px}.error-sev-warn{background:#ffb2100a;border-color:#ffb2104d}.error-sev-warn .error-meta,.error-sev-warn .error-line{color:var(--color-edf-orange-fonce)}.error-sev-info{background:#1089ff08;border-color:#1089ff33}.error-sev-info .error-meta,.error-sev-info .error-line{color:var(--color-edf-bleu-fonce)}.err-hit{background:#d6430a40;border-radius:2px;padding:0 2px}.error-sev-warn .err-hit{background:#ffb21059}.error-sev-info .err-hit{background:#1089ff33}.error-item-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;width:100%;font:inherit;color:inherit;text-align:left;background:0 0;border:none;padding:0}.error-expand-icon{color:var(--color-muted);align-items:center;margin-right:4px;display:inline-flex}@keyframes rise{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@media(max-width:980px){main>section{grid-column:span 12!important}table{min-width:640px}}@media(prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}.icon{vertical-align:-2px;display:inline-block}div.svelte-1kfua4v svg{display:block;max-width:100%;height:auto} diff --git a/frontend/dist/_app/immutable/assets/2.DKE6ochW.css b/frontend/dist/_app/immutable/assets/2.DKE6ochW.css deleted file mode 100644 index 3454f86..0000000 --- a/frontend/dist/_app/immutable/assets/2.DKE6ochW.css +++ /dev/null @@ -1 +0,0 @@ -/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-mono:"JetBrains Mono", "Fira Code", "Cascadia Code", "Courier New", monospace;--color-red-600:oklch(57.7% .245 27.325);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-light:300;--font-weight-normal:400;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:"edf-2020";--default-mono-font-family:var(--font-mono);--color-edf-orange-fonce:#d6430a;--color-edf-orange-moyen:#ff861d;--color-edf-orange-clair:#ffb210;--color-edf-vert-fonce:#307a10;--color-edf-bleu-fonce:#001a70;--color-edf-bleu-moyen:#1057c8;--color-edf-bleu-clair:#1089ff;--color-edf-noir:#333;--color-edf-slate:#f8f7f7;--color-edf-gris-moyen:#dedede;--color-edf-gris-clair:#f8f7f7;--color-edf-blanc:#fff;--color-bg:var(--color-edf-slate);--color-ink:var(--color-edf-noir);--color-muted:#787878;--color-card:var(--color-edf-blanc);--color-border:var(--color-edf-gris-moyen);--color-table-head:var(--color-edf-gris-clair);--color-table-row:var(--color-edf-blanc);--color-table-hover:#f5f5f4;--color-table-selected:#e8eff8;--color-table-selected-hover:#dce7f4;--animate-rise:rise .35s ease both}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.sticky\!{position:sticky!important}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.end\!{inset-inline-end:var(--spacing)!important}.top-0{top:calc(var(--spacing) * 0)}.top-1\/2{top:50%}.right-0{right:calc(var(--spacing) * 0)}.right-2{right:calc(var(--spacing) * 2)}.right-4{right:calc(var(--spacing) * 4)}.bottom-4{bottom:calc(var(--spacing) * 4)}.left-0{left:calc(var(--spacing) * 0)}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.z-80{z-index:80}.z-\[1\]{z-index:1}.z-\[1000\]{z-index:1000}.col-span-6{grid-column:span 6/span 6}.col-span-12{grid-column:span 12/span 12}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing) * 0)}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mr-auto{margin-right:auto}.mb-0{margin-bottom:calc(var(--spacing) * 0)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-2\.5{margin-bottom:calc(var(--spacing) * 2.5)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-3\.5{margin-bottom:calc(var(--spacing) * 3.5)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-4\.5{margin-bottom:calc(var(--spacing) * 4.5)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-auto{margin-left:auto}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.aspect-\[900\/500\]{aspect-ratio:900/500}.h-7{height:calc(var(--spacing) * 7)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-14{height:calc(var(--spacing) * 14)}.h-\[3px\]{height:3px}.h-\[10px\]{height:10px}.h-\[14px\]{height:14px}.h-\[30px\]{height:30px}.h-\[34px\]{height:34px}.h-full{height:100%}.max-h-\[240px\]{max-height:240px}.max-h-\[260px\]{max-height:260px}.max-h-\[500px\]{max-height:500px}.\!min-h-\[80px\]{min-height:80px!important}.w-9{width:calc(var(--spacing) * 9)}.w-32\.5{width:calc(var(--spacing) * 32.5)}.w-37\.5{width:calc(var(--spacing) * 37.5)}.w-\[10px\]{width:10px}.w-\[14px\]{width:14px}.w-\[30px\]{width:30px}.w-\[40px\]{width:40px}.w-\[70px\]{width:70px}.w-\[72px\]{width:72px}.w-\[80px\]{width:80px}.w-\[90px\]{width:90px}.w-\[110px\]{width:110px}.w-\[130px\]{width:130px}.w-\[140px\]{width:140px}.w-\[150px\]{width:150px}.w-\[160px\]{width:160px}.w-\[180px\]{width:180px}.w-\[200px\]{width:200px}.w-\[min\(420px\,96vw\)\]{width:min(420px,96vw)}.w-\[min\(1200px\,94vw\)\]{width:min(1200px,94vw)}.w-\[min\(\{maxWidth\}\,96vw\)\]{width:min({maxWidth},96vw)}.w-auto{width:auto}.w-full{width:100%}.w-max{width:max-content}.max-w-\[140px\]{max-width:140px}.max-w-\[320px\]{max-width:320px}.max-w-\[920px\]{max-width:920px}.\!min-w-0{min-width:calc(var(--spacing) * 0)!important}.min-w-\[36px\]{min-width:36px}.min-w-\[40px\]{min-width:40px}.min-w-\[72px\]{min-width:72px}.min-w-\[170px\]{min-width:170px}.min-w-\[200px\]{min-width:200px}.min-w-full{min-width:100%}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-180{rotate:180deg}.animate-pulse{animation:var(--animate-pulse)}.animate-rise{animation:var(--animate-rise)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-1\.5{row-gap:calc(var(--spacing) * 1.5)}.self-end{align-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-clip{overflow:clip}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[10px\]{border-radius:10px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-none{--tw-border-style:none;border-style:none}.border-\[rgba\(51\,51\,51\,0\.08\)\]{border-color:#33333314}.border-border{border-color:var(--color-border)}.border-edf-bleu-moyen{border-color:var(--color-edf-bleu-moyen)}.border-edf-gris-moyen{border-color:var(--color-edf-gris-moyen)}.border-transparent{border-color:#0000}.bg-\[rgba\(0\,26\,112\,0\.3\)\]{background-color:#001a704d}.bg-\[rgba\(16\,87\,200\,0\.06\)\]{background-color:#1057c80f}.bg-\[rgba\(48\,122\,16\,0\.04\)\]{background-color:#307a100a}.bg-\[rgba\(48\,122\,16\,0\.06\)\]{background-color:#307a100f}.bg-\[rgba\(48\,122\,16\,0\.08\)\]{background-color:#307a1014}.bg-\[rgba\(214\,67\,10\,0\.04\)\]{background-color:#d6430a0a}.bg-\[rgba\(214\,67\,10\,0\.06\)\]{background-color:#d6430a0f}.bg-\[rgba\(214\,67\,10\,0\.08\)\]{background-color:#d6430a14}.bg-\[rgba\(214\,67\,10\,0\.12\)\]{background-color:#d6430a1f}.bg-\[rgba\(255\,178\,16\,0\.1\)\]{background-color:#ffb2101a}.bg-\[rgba\(255\,178\,16\,0\.3\)\]{background-color:#ffb2104d}.bg-\[rgba\(255\,178\,16\,0\.6\)\]{background-color:#ffb21099}.bg-card{background-color:var(--color-card)}.bg-edf-blanc{background-color:var(--color-edf-blanc)}.bg-edf-bleu-moyen{background-color:var(--color-edf-bleu-moyen)}.bg-edf-gris-clair{background-color:var(--color-edf-gris-clair)}.bg-edf-orange-fonce{background-color:var(--color-edf-orange-fonce)}.bg-edf-orange-moyen{background-color:var(--color-edf-orange-moyen)}.bg-edf-vert-fonce{background-color:var(--color-edf-vert-fonce)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/60{background-color:#fff9}@supports (color:color-mix(in lab,red,red)){.bg-white\/60{background-color:color-mix(in oklab,var(--color-white) 60%,transparent)}}.p-0{padding:calc(var(--spacing) * 0)}.p-1{padding:calc(var(--spacing) * 1)}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.p-4{padding:calc(var(--spacing) * 4)}.p-4\.5{padding:calc(var(--spacing) * 4.5)}.p-5{padding:calc(var(--spacing) * 5)}.p-\[16px_18px_18px\]{padding:16px 18px 18px}.\!px-2\.5{padding-inline:calc(var(--spacing) * 2.5)!important}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-\[1px\]{padding-inline:1px}.px-\[10px\]{padding-inline:10px}.\!py-0{padding-block:calc(var(--spacing) * 0)!important}.\!py-1{padding-block:calc(var(--spacing) * 1)!important}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-\[7px\]{padding-block:7px}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-7{padding-right:calc(var(--spacing) * 7)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-12{padding-bottom:calc(var(--spacing) * 12)}.pl-2\.5{padding-left:calc(var(--spacing) * 2.5)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-\[edf-2020-soft\]{font-family:edf-2020-soft}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[11px\]{font-size:11px}.text-\[12\.5px\]{font-size:12.5px}.text-\[13px\]{font-size:13px}.leading-\[1\.6\]{--tw-leading:1.6;line-height:1.6}.leading-\[1\.45\]{--tw-leading:1.45;line-height:1.45}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-edf-bleu-clair{color:var(--color-edf-bleu-clair)}.text-edf-bleu-fonce{color:var(--color-edf-bleu-fonce)}.text-edf-bleu-moyen{color:var(--color-edf-bleu-moyen)}.text-edf-gris-moyen{color:var(--color-edf-gris-moyen)}.text-edf-noir{color:var(--color-edf-noir)}.text-edf-orange-fonce{color:var(--color-edf-orange-fonce)}.text-edf-vert-fonce{color:var(--color-edf-vert-fonce)}.text-ink{color:var(--color-ink)}.text-muted{color:var(--color-muted)}.text-red-600{color:var(--color-red-600)}.text-white{color:var(--color-white)}.italic{font-style:italic}.opacity-40{opacity:.4}.opacity-50{opacity:.5}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-\[background\]{transition-property:background;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-\[120ms\]{--tw-duration:.12s;transition-duration:.12s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.select-all{-webkit-user-select:all;user-select:all}.select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.hover\:border-border:hover{border-color:var(--color-border)}.hover\:border-edf-bleu-clair:hover{border-color:var(--color-edf-bleu-clair)}.hover\:border-edf-gris-moyen:hover{border-color:var(--color-edf-gris-moyen)}.hover\:bg-\[rgb\(12\,72\,170\)\]:hover{background-color:#0c48aa}.hover\:bg-\[rgb\(38\,98\,12\)\]:hover{background-color:#26620c}.hover\:bg-\[rgb\(180\,56\,8\)\]:hover{background-color:#b43808}.hover\:bg-\[rgb\(230\,118\,20\)\]:hover{background-color:#e67614}.hover\:bg-edf-gris-clair:hover{background-color:var(--color-edf-gris-clair)}.hover\:text-muted:hover{color:var(--color-muted)}.hover\:underline:hover{text-decoration-line:underline}.hover\:decoration-dotted:hover{text-decoration-style:dotted}.hover\:underline-offset-2:hover{text-underline-offset:2px}}.focus\:border-edf-bleu-moyen:focus{border-color:var(--color-edf-bleu-moyen)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-40:disabled{opacity:.4}@media not all and (min-width:64rem){.max-lg\:order-3{order:3}.max-lg\:h-auto{height:auto}.max-lg\:w-full{width:100%}.max-lg\:flex-wrap{flex-wrap:wrap}.max-lg\:gap-2{gap:calc(var(--spacing) * 2)}.max-lg\:gap-3{gap:calc(var(--spacing) * 3)}}}@font-face{font-family:edf-2020;src:url(./EDF2020W01-Regular.DEtAzL8F.woff2)format("woff2");font-weight:400;font-style:normal;font-display:swap}@font-face{font-family:edf-2020;src:url(./EDF2020W01-Bold.Bub472Sg.woff2)format("woff2");font-weight:700;font-style:normal;font-display:swap}@font-face{font-family:edf-2020;src:url(./EDF2020W01-Light.D7COPh2m.woff2)format("woff2");font-weight:300;font-style:normal;font-display:swap}@font-face{font-family:edf-2020;src:url(./EDF2020W01-Italic.CtMn0wF8.woff2)format("woff2");font-weight:400;font-style:italic;font-display:swap}@font-face{font-family:edf-2020-soft;src:url(./EDF2020Soft-BoldItalic.y4KHcb-c.woff2)format("woff2");font-weight:700;font-style:italic;font-display:swap}@font-face{font-family:edf-2020-soft;src:url(./EDF2020Soft-Italic.CTdZcEjD.woff2)format("woff2");font-weight:400;font-style:italic;font-display:swap}@font-face{font-family:edf-2020-soft;src:url(./EDF2020Soft-LightItalic.DlUXaATs.woff2)format("woff2");font-weight:300;font-style:italic;font-display:swap}html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{color:var(--color-ink);background:var(--color-bg);min-height:100vh;margin:0}input,select,textarea{border:1px solid var(--color-border);height:30px;color:var(--color-ink);background:#fff;border-radius:6px;padding:5px 10px;font-family:inherit;font-size:13px;transition:border-color .15s}textarea,select[multiple]{height:auto}input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}input[type=number]{-moz-appearance:textfield}input:focus,select:focus,textarea:focus{border-color:var(--color-edf-bleu-moyen);outline:none}select{min-width:160px}select[multiple]{min-width:180px}textarea{width:100%;min-height:160px;font-family:var(--font-mono);font-size:12px}[data-icon]:before{content:attr(data-icon);opacity:.8;justify-content:center;align-items:center;font-size:12px;display:inline-flex}.table-wrap{border:1px solid var(--color-border);background:var(--color-table-row);border-radius:8px;overflow-x:auto}#status-table{border-collapse:separate;border-spacing:0;width:max-content;min-width:100%}table{border-collapse:collapse;width:100%;min-width:760px}th,td{text-align:left;border-bottom:1px solid var(--color-edf-gris-clair);padding:8px 10px;font-size:13px}th{text-transform:uppercase;letter-spacing:.12em;color:var(--color-muted);background:var(--color-table-head);z-index:1;font-size:11px;font-weight:700;position:sticky;top:0}th.status-col-doe,th.status-col-calc{white-space:nowrap}td.status-col-doe{font-family:var(--font-mono);font-size:12px}td.case-id{font-weight:700}tbody tr{background:var(--color-table-row)}tbody tr:hover{background:var(--color-table-hover)}tbody tr.row-selected{background:var(--color-table-selected)}tbody tr.row-selected:hover{background:var(--color-table-selected-hover)}#status-body tr,#status-body td{-webkit-user-select:none;user-select:none}th.status-sortable{cursor:pointer;-webkit-user-select:none;user-select:none}th.status-sortable:hover{color:var(--color-edf-bleu-moyen)}#status-doe-select{min-width:220px;max-width:360px}#status-table thead tr,#status-table tbody tr{height:34px}#status-table-wrap{max-height:374px;overflow-y:auto}#status-table-wrap.sticky-enabled th.sticky-left,#status-table-wrap.sticky-enabled th.sticky-right{background:var(--color-table-head);z-index:4;position:sticky}#status-table-wrap.sticky-enabled td.sticky-left,#status-table-wrap.sticky-enabled td.sticky-right{background:var(--color-table-row);z-index:3;position:sticky}#status-table-wrap.sticky-enabled .sticky-right{right:0}#status-table-wrap.sticky-enabled .sticky-divider{border-right:1px solid var(--color-border)}#status-table-wrap.sticky-enabled .sticky-right.sticky-divider{border-left:1px solid var(--color-border);border-right:none}#status-table-wrap.sticky-enabled tbody tr:hover td.sticky-left,#status-table-wrap.sticky-enabled tbody tr:hover td.sticky-right{background:var(--color-table-hover)}#status-table-wrap.sticky-enabled tbody tr.row-selected td.sticky-left,#status-table-wrap.sticky-enabled tbody tr.row-selected td.sticky-right{background:var(--color-table-selected)}#status-table-wrap.sticky-enabled tbody tr.row-selected:hover td.sticky-left,#status-table-wrap.sticky-enabled tbody tr.row-selected:hover td.sticky-right{background:var(--color-table-selected-hover)}.status-pill{letter-spacing:.1em;text-transform:uppercase;border:1px solid #0000;border-radius:4px;align-items:center;padding:3px 8px;font-size:10px;font-weight:700;display:inline-flex}.status-running{color:var(--color-edf-bleu-fonce);background:#1089ff1a;border-color:#1089ff40}.status-done{color:var(--color-edf-vert-fonce);background:#307a1014;border-color:#307a1033}.status-failed{color:var(--color-edf-orange-fonce);background:#d6430a14;border-color:#d6430a40}.status-converged{color:var(--color-edf-vert-fonce);background:#307a101f;border-color:#307a104d}.status-not-converged{color:var(--color-edf-orange-fonce);background:#d6430a1a;border-color:#d6430a40}.status-prepared,.status-unknown{background:var(--color-edf-gris-clair);color:var(--color-muted);border-color:var(--color-border)}.is-active{color:var(--color-edf-bleu-fonce);background:#1057c814;border-color:#1057c833;font-weight:700}#status-card:focus{outline:none}.link-btn{color:var(--color-edf-bleu-moyen);cursor:pointer;background:0 0;border:none;font-size:12px}.link-btn:hover{text-decoration:underline}#perf-table{min-width:920px}#perf-table tbody tr:hover{background:inherit}pre,.code-box{color:var(--color-edf-gris-clair);max-height:320px;font-size:12px;font-family:var(--font-mono);background:#1e1e2d;border-radius:8px;padding:10px 12px;overflow:auto}.code-box{white-space:pre}.tail-pane{white-space:normal;max-height:320px;padding:8px 10px;overflow:auto}.tail-meta{margin:2px 0 8px}.tail-line{white-space:pre;border-radius:3px;margin:0 -4px;padding:0 4px;line-height:1.45}.tail-sev-error{color:var(--color-edf-orange-clair);background:#d6430a33}.tail-sev-warn{color:var(--color-edf-orange-clair);background:#ffb21026}.tail-sev-info{color:var(--color-edf-gris-moyen)}.tail-query-hit{color:var(--color-edf-noir);background:#ffb21080;border-radius:2px;padding:0 2px}.tail-filter-error{color:var(--color-edf-orange-clair);margin-bottom:6px;font-weight:600}.tail-new{animation:2.6s ease-out tailFlash}@keyframes tailFlash{0%{background-color:#1089ff33}to{background-color:#0000}}code{background:#ff861d14;border-radius:4px;padding:2px 6px;font-size:12px}.error-list{border-radius:8px;flex-direction:column;gap:8px;max-height:320px;display:flex;overflow:auto}.error-item{background:#d6430a0a;border:1px solid #d6430a33;border-radius:8px;padding:10px 12px}.error-meta{color:var(--color-edf-orange-fonce);margin-bottom:0;font-size:11px;font-weight:700}.error-meta-row{justify-content:space-between;align-items:center;gap:10px;margin-bottom:0;display:flex}.error-meta-row+.error-line{margin-top:6px}.error-meta-row .error-meta{text-align:left;flex:1;margin:0}.error-badges{flex-wrap:wrap;align-items:center;gap:6px;display:inline-flex}.error-badge{color:var(--color-edf-orange-fonce);background:#d6430a14;border:1px solid #0000;border-radius:4px;align-items:center;padding:2px 7px;font-size:10px;font-weight:700;display:inline-flex}.error-badge-new{color:var(--color-edf-bleu-fonce);background:#1057c814;border-color:#1057c833}.error-line{font-family:var(--font-mono);white-space:pre-wrap;color:var(--color-edf-noir);font-size:12px}.error-empty{color:var(--color-muted);padding:6px 0;font-size:12px}.error-sev-warn{background:#ffb2100a;border-color:#ffb2104d}.error-sev-warn .error-meta,.error-sev-warn .error-line{color:var(--color-edf-orange-fonce)}.error-sev-info{background:#1089ff08;border-color:#1089ff33}.error-sev-info .error-meta,.error-sev-info .error-line{color:var(--color-edf-bleu-fonce)}.err-hit{background:#d6430a40;border-radius:2px;padding:0 2px}.error-sev-warn .err-hit{background:#ffb21059}.error-sev-info .err-hit{background:#1089ff33}.error-item-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;width:100%;font:inherit;color:inherit;text-align:left;background:0 0;border:none;padding:0}.error-expand-icon{color:var(--color-muted);align-items:center;margin-right:4px;display:inline-flex}@keyframes rise{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@media(max-width:980px){main>section{grid-column:span 12!important}table{min-width:640px}}@media(prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}.icon{vertical-align:-2px;display:inline-block}div.svelte-1kfua4v svg{display:block;max-width:100%;height:auto} diff --git a/frontend/dist/_app/immutable/chunks/BI00lc_u.js b/frontend/dist/_app/immutable/chunks/5xxJZag6.js similarity index 99% rename from frontend/dist/_app/immutable/chunks/BI00lc_u.js rename to frontend/dist/_app/immutable/chunks/5xxJZag6.js index 4b090d1..6f962eb 100644 --- a/frontend/dist/_app/immutable/chunks/BI00lc_u.js +++ b/frontend/dist/_app/immutable/chunks/5xxJZag6.js @@ -1 +1 @@ -var te=t=>{throw TypeError(t)};var je=(t,e,n)=>e.has(t)||te("Cannot "+n);var v=(t,e,n)=>(je(t,e,"read from private field"),n?n.call(t):e.get(t)),U=(t,e,n)=>e.has(t)?te("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,n);import{bf as Ut,bg as Ne,aK as T,o as I,$ as O,aJ as mt,bh as qe}from"./VJhVTb1A.js";import{o as ee}from"./Cz6m6K8p.js";const B=[];function Dt(t,e=Ut){let n=null;const a=new Set;function r(o){if(Ne(t,o)&&(t=o,n)){const l=!B.length;for(const c of a)c[1](),B.push(c,t);if(l){for(let c=0;c{a.delete(c),a.size===0&&n&&(n(),n=null)}}return{set:r,update:i,subscribe:s}}class Vt{constructor(e,n){this.status=e,typeof n=="string"?this.body={message:n}:n?this.body=n:this.body={message:`Error: ${e}`}}toString(){return JSON.stringify(this.body)}}class Kt{constructor(e,n){this.status=e,this.location=n}}class Bt extends Error{constructor(e,n,a){super(a),this.status=e,this.text=n}}new URL("sveltekit-internal://");function De(t,e){return t==="/"||e==="ignore"?t:e==="never"?t.endsWith("/")?t.slice(0,-1):t:e==="always"&&!t.endsWith("/")?t+"/":t}function Ve(t){return t.split("%25").map(decodeURI).join("%25")}function Ke(t){for(const e in t)t[e]=decodeURIComponent(t[e]);return t}function Tt({href:t}){return t.split("#")[0]}function Be(...t){let e=5381;for(const n of t)if(typeof n=="string"){let a=n.length;for(;a;)e=e*33^n.charCodeAt(--a)}else if(ArrayBuffer.isView(n)){const a=new Uint8Array(n.buffer,n.byteOffset,n.byteLength);let r=a.length;for(;r;)e=e*33^a[--r]}else throw new TypeError("value must be a string or TypedArray");return(e>>>0).toString(36)}new TextEncoder;new TextDecoder;function Me(t){const e=atob(t),n=new Uint8Array(e.length);for(let a=0;a((t instanceof Request?t.method:(e==null?void 0:e.method)||"GET")!=="GET"&&H.delete(Mt(t)),ze(t,e));const H=new Map;function Fe(t,e){const n=Mt(t,e),a=document.querySelector(n);if(a!=null&&a.textContent){a.remove();let{body:r,...i}=JSON.parse(a.textContent);const s=a.getAttribute("data-ttl");return s&&H.set(n,{body:r,init:i,ttl:1e3*Number(s)}),a.getAttribute("data-b64")!==null&&(r=Me(r)),Promise.resolve(new Response(r,i))}return window.fetch(t,e)}function Ge(t,e,n){if(H.size>0){const a=Mt(t,n),r=H.get(a);if(r){if(performance.now(){const r=/^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(a);if(r)return e.push({name:r[1],matcher:r[2],optional:!1,rest:!0,chained:!0}),"(?:/([^]*))?";const i=/^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(a);if(i)return e.push({name:i[1],matcher:i[2],optional:!0,rest:!1,chained:!0}),"(?:/([^/]+))?";if(!a)return;const s=a.split(/\[(.+?)\](?!\])/);return"/"+s.map((l,c)=>{if(c%2){if(l.startsWith("x+"))return It(String.fromCharCode(parseInt(l.slice(2),16)));if(l.startsWith("u+"))return It(String.fromCharCode(...l.slice(2).split("-").map(m=>parseInt(m,16))));const d=We.exec(l),[,u,w,p,f]=d;return e.push({name:p,matcher:f,optional:!!u,rest:!!w,chained:w?c===1&&s[0]==="":!1}),w?"([^]*?)":u?"([^/]*)?":"([^/]+?)"}return It(l)}).join("")}).join("")}/?$`),params:e}}function Ye(t){return t!==""&&!/^\([^)]+\)$/.test(t)}function He(t){return t.slice(1).split("/").filter(Ye)}function Xe(t,e,n){const a={},r=t.slice(1),i=r.filter(o=>o!==void 0);let s=0;for(let o=0;od).join("/"),s=0),c===void 0)if(l.rest)c="";else continue;if(!l.matcher||n[l.matcher](c)){a[l.name]=c;const d=e[o+1],u=r[o+1];d&&!d.rest&&d.optional&&u&&l.chained&&(s=0),!d&&!u&&Object.keys(a).length===i.length&&(s=0);continue}if(l.optional&&l.chained){s++;continue}return}if(!s)return a}function It(t){return t.normalize().replace(/[[\]]/g,"\\$&").replace(/%/g,"%25").replace(/\//g,"%2[Ff]").replace(/\?/g,"%3[Ff]").replace(/#/g,"%23").replace(/[.*+?^${}()|\\]/g,"\\$&")}function Qe({nodes:t,server_loads:e,dictionary:n,matchers:a}){const r=new Set(e);return Object.entries(n).map(([o,[l,c,d]])=>{const{pattern:u,params:w}=Je(o),p={id:o,exec:f=>{const m=u.exec(f);if(m)return Xe(m,w,a)},errors:[1,...d||[]].map(f=>t[f]),layouts:[0,...c||[]].map(s),leaf:i(l)};return p.errors.length=p.layouts.length=Math.max(p.errors.length,p.layouts.length),p});function i(o){const l=o<0;return l&&(o=~o),[l,t[o]]}function s(o){return o===void 0?o:[r.has(o),t[o]]}}function ge(t,e=JSON.parse){try{return e(sessionStorage[t])}catch{}}function ne(t,e,n=JSON.stringify){const a=n(e);try{sessionStorage[t]=a}catch{}}var fe;const A=((fe=globalThis.__sveltekit_cs5p4g)==null?void 0:fe.base)??"";var ue;const Ze=((ue=globalThis.__sveltekit_cs5p4g)==null?void 0:ue.assets)??A??"",tn="1789130391568",me="sveltekit:snapshot",_e="sveltekit:scroll",we="sveltekit:states",en="sveltekit:pageurl",z="sveltekit:history",Q="sveltekit:navigation",q={tap:1,hover:2,viewport:3,eager:4,off:-1,false:-1},zt=location.origin;function ve(t){if(t instanceof URL)return t;let e=document.baseURI;if(!e){const n=document.getElementsByTagName("base");e=n.length?n[0].href:document.URL}return new URL(t,e)}function V(){return{x:pageXOffset,y:pageYOffset}}function M(t,e){return t.getAttribute(`data-sveltekit-${e}`)}const re={...q,"":q.hover};function ye(t){let e=t.assignedSlot??t.parentNode;return(e==null?void 0:e.nodeType)===11&&(e=e.host),e}function be(t,e){for(;t&&t!==e;){if(t.nodeName.toUpperCase()==="A"&&t.hasAttribute("href"))return t;t=ye(t)}}function $t(t,e,n){let a;try{if(a=new URL(t instanceof SVGAElement?t.href.baseVal:t.href,document.baseURI),n&&a.hash.match(/^#[^/]/)){const o=location.hash.split("#")[1]||"/";a.hash=`#${o}${a.hash}`}}catch{}const r=t instanceof SVGAElement?t.target.baseVal:t.target,i=!a||!!r||Rt(a,e,n)||(t.getAttribute("rel")||"").split(/\s+/).includes("external"),s=(a==null?void 0:a.origin)===zt&&t.hasAttribute("download");return{url:a,external:i,target:r,download:s}}function _t(t){let e=null,n=null,a=null,r=null,i=null,s=null,o=t;for(;o&&o!==document.documentElement;)a===null&&(a=M(o,"preload-code")),r===null&&(r=M(o,"preload-data")),e===null&&(e=M(o,"keepfocus")),n===null&&(n=M(o,"noscroll")),i===null&&(i=M(o,"reload")),s===null&&(s=M(o,"replacestate")),o=ye(o);function l(c){switch(c){case"":case"true":return!0;case"off":case"false":return!1;default:return}}return{preload_code:re[a??"off"],preload_data:re[r??"off"],keepfocus:l(e),noscroll:l(n),reload:l(i),replace_state:l(s)}}function ae(t){const e=Dt(t);let n=!0;function a(){n=!0,e.update(s=>s)}function r(s){n=!1,e.set(s)}function i(s){let o;return e.subscribe(l=>{(o===void 0||n&&l!==o)&&s(o=l)})}return{notify:a,set:r,subscribe:i}}const ke={v:()=>{}};function nn(){const{set:t,subscribe:e}=Dt(!1);let n;async function a(){clearTimeout(n);try{const r=await fetch(`${Ze}/_app/version.json`,{headers:{pragma:"no-cache","cache-control":"no-cache"}});if(!r.ok)return!1;const s=(await r.json()).version!==tn;return s&&(t(!0),ke.v(),clearTimeout(n)),s}catch{return!1}}return{subscribe:e,check:a}}function Rt(t,e,n){return t.origin!==zt||!t.pathname.startsWith(e)?!0:n?t.pathname!==location.pathname:!1}function On(t){}const Se=new Set(["load","prerender","csr","ssr","trailingSlash","config"]);[...Se];const rn=new Set([...Se]);[...rn];function an(t){return t.filter(e=>e!=null)}function Ft(t){return t instanceof Vt||t instanceof Bt?t.status:500}function on(t){return t instanceof Bt?t.text:"Internal Error"}let R,Z,Ot;const sn=ee.toString().includes("$$")||/function \w+\(\) \{\}/.test(ee.toString()),oe="a:";var at,ot,st,it,lt,ct,ft,ut,de,dt,he,ht,pe;sn?(R={data:{},form:null,error:null,params:{},route:{id:null},state:{},status:-1,url:new URL(oe)},Z={current:null},Ot={current:!1}):(R=new(de=class{constructor(){U(this,at,T({}));U(this,ot,T(null));U(this,st,T(null));U(this,it,T({}));U(this,lt,T({id:null}));U(this,ct,T({}));U(this,ft,T(-1));U(this,ut,T(new URL(oe)))}get data(){return I(v(this,at))}set data(e){O(v(this,at),e)}get form(){return I(v(this,ot))}set form(e){O(v(this,ot),e)}get error(){return I(v(this,st))}set error(e){O(v(this,st),e)}get params(){return I(v(this,it))}set params(e){O(v(this,it),e)}get route(){return I(v(this,lt))}set route(e){O(v(this,lt),e)}get state(){return I(v(this,ct))}set state(e){O(v(this,ct),e)}get status(){return I(v(this,ft))}set status(e){O(v(this,ft),e)}get url(){return I(v(this,ut))}set url(e){O(v(this,ut),e)}},at=new WeakMap,ot=new WeakMap,st=new WeakMap,it=new WeakMap,lt=new WeakMap,ct=new WeakMap,ft=new WeakMap,ut=new WeakMap,de),Z=new(he=class{constructor(){U(this,dt,T(null))}get current(){return I(v(this,dt))}set current(e){O(v(this,dt),e)}},dt=new WeakMap,he),Ot=new(pe=class{constructor(){U(this,ht,T(!1))}get current(){return I(v(this,ht))}set current(e){O(v(this,ht),e)}},ht=new WeakMap,pe),ke.v=()=>Ot.current=!0);function ln(t){Object.assign(R,t)}const cn=new Set(["icon","shortcut icon","apple-touch-icon"]);let J=null;const j=ge(_e)??{},tt=ge(me)??{},C={url:ae({}),page:ae({}),navigating:Dt(null),updated:nn()};function Gt(t){j[t]=V()}function fn(t,e){let n=t+1;for(;j[n];)delete j[n],n+=1;for(n=e+1;tt[n];)delete tt[n],n+=1}function et(t,e=!1){return e?location.replace(t.href):location.href=t.href,new Promise(()=>{})}async function Ee(){if("serviceWorker"in navigator){const t=await navigator.serviceWorker.getRegistration(A||"/");t&&await t.update()}}function se(){}let Wt,Ct,wt,P,jt,S;const vt=[],yt=[];let y=null;function Nt(){var t;(t=y==null?void 0:y.fork)==null||t.then(e=>e==null?void 0:e.discard()),y=null}const gt=new Map,Re=new Set,un=new Set,X=new Set;let _={branch:[],error:null,url:null},xe=!1,bt=!1,ie=!0,nt=!1,Y=!1,Le=!1,Jt=!1,Ae,k,L,D;const kt=new Set,le=new Map;async function jn(t,e,n){var i,s,o,l,c;(i=globalThis.__sveltekit_cs5p4g)!=null&&i.data&&globalThis.__sveltekit_cs5p4g.data,document.URL!==location.href&&(location.href=location.href),S=t,await((o=(s=t.hooks).init)==null?void 0:o.call(s)),Wt=Qe(t),P=document.documentElement,jt=e,Ct=t.nodes[0],wt=t.nodes[1],Ct(),wt(),k=(l=history.state)==null?void 0:l[z],L=(c=history.state)==null?void 0:c[Q],k||(k=L=Date.now(),history.replaceState({...history.state,[z]:k,[Q]:L},""));const a=j[k];function r(){a&&(history.scrollRestoration="manual",scrollTo(a.x,a.y))}n?(r(),await Rn(jt,n)):(await F({type:"enter",url:ve(S.hash?An(new URL(location.href)):location.href),replace_state:!0}),r()),En()}function dn(){vt.length=0,Jt=!1}function Ue(t){yt.some(e=>e==null?void 0:e.snapshot)&&(tt[t]=yt.map(e=>{var n;return(n=e==null?void 0:e.snapshot)==null?void 0:n.capture()}))}function Te(t){var e;(e=tt[t])==null||e.forEach((n,a)=>{var r,i;(i=(r=yt[a])==null?void 0:r.snapshot)==null||i.restore(n)})}function ce(){Gt(k),ne(_e,j),Ue(L),ne(me,tt)}async function hn(t,e,n,a){let r;e.invalidateAll&&Nt(),await F({type:"goto",url:ve(t),keepfocus:e.keepFocus,noscroll:e.noScroll,replace_state:e.replaceState,state:e.state,redirect_count:n,nav_token:a,accept:()=>{e.invalidateAll&&(Jt=!0,r=[...le.keys()]),e.invalidate&&e.invalidate.forEach(Sn)}}),e.invalidateAll&&mt().then(mt).then(()=>{le.forEach(({resource:i},s)=>{var o;r!=null&&r.includes(s)&&((o=i.refresh)==null||o.call(i))})})}async function pn(t){if(t.id!==(y==null?void 0:y.id)){Nt();const e={};kt.add(e),y={id:t.id,token:e,promise:Oe({...t,preload:e}).then(n=>(kt.delete(e),n.type==="loaded"&&n.state.error&&Nt(),n)),fork:null}}return y.promise}async function Pt(t){var n;const e=(n=await xt(t,!1))==null?void 0:n.route;e&&await Promise.all([...e.layouts,e.leaf].filter(Boolean).map(a=>a[1]()))}async function Ie(t,e,n){var i;const a={params:_.params,route:{id:((i=_.route)==null?void 0:i.id)??null},url:new URL(location.href)};_={...t.state,nav:a};const r=document.querySelector("style[data-sveltekit]");if(r&&r.remove(),Object.assign(R,t.props.page),Ae=new S.root({target:e,props:{...t.props,stores:C,components:yt},hydrate:n,sync:!1,transformError:void 0}),await Promise.resolve(),Te(L),n){const s={from:null,to:{...a,scroll:j[k]??V()},willUnload:!1,type:"enter",complete:Promise.resolve()};X.forEach(o=>o(s))}bt=!0}async function St({url:t,params:e,branch:n,errors:a,status:r,error:i,route:s,form:o}){let l="never";if(A&&(t.pathname===A||t.pathname===A+"/"))l="always";else for(const f of n)(f==null?void 0:f.slash)!==void 0&&(l=f.slash);t.pathname=De(t.pathname,l),t.search=t.search;const c={type:"loaded",state:{url:t,params:e,branch:n,error:i,route:s},props:{constructors:an(n).map(f=>f.node.component),page:Zt(R)}};o!==void 0&&(c.props.form=o);let d={},u=!R,w=0;for(let f=0;fo(new URL(s))))return!0;return!1}function Ht(t,e){return(t==null?void 0:t.type)==="data"?t:(t==null?void 0:t.type)==="skip"?e??null:null}function _n(t,e){if(!t)return new Set(e.searchParams.keys());const n=new Set([...t.searchParams.keys(),...e.searchParams.keys()]);for(const a of n){const r=t.searchParams.getAll(a),i=e.searchParams.getAll(a);r.every(s=>i.includes(s))&&i.every(s=>r.includes(s))&&n.delete(a)}return n}function wn({error:t,url:e,route:n,params:a}){return{type:"loaded",state:{error:t,url:e,route:n,params:a,branch:[]},props:{page:Zt(R),constructors:[]}}}async function Oe({id:t,invalidating:e,url:n,params:a,route:r,preload:i}){if((y==null?void 0:y.id)===t)return kt.delete(y.token),y.promise;const{errors:s,layouts:o,leaf:l}=r,c=[...o,l];s.forEach(h=>h==null?void 0:h().catch(()=>{})),c.forEach(h=>h==null?void 0:h[1]().catch(()=>{}));const d=_.url?t!==Et(_.url):!1,u=_.route?r.id!==_.route.id:!1,w=_n(_.url,n);let p=!1;const f=c.map(async(h,g)=>{var $;if(!h)return;const b=_.branch[g];return h[1]===(b==null?void 0:b.loader)&&!mn(p,u,d,w,($=b.universal)==null?void 0:$.uses,a)?b:(p=!0,Yt({loader:h[1],url:n,params:a,route:r,parent:async()=>{var pt;const N={};for(let K=0;K{});const m=[];for(let h=0;hPromise.resolve({}),server_data_node:Ht(i)}),o={node:await wt(),loader:wt,universal:null,server:null,data:null};return St({url:n,params:r,branch:[s,o],status:t,error:e,errors:[],route:null})}catch(s){if(s instanceof Kt)return hn(new URL(s.location,location.href),{},0);throw s}}async function yn(t){const e=t.href;if(gt.has(e))return gt.get(e);let n;try{const a=(async()=>{let r=await S.hooks.reroute({url:new URL(t),fetch:async(i,s)=>gn(i,s,t).promise})??t;if(typeof r=="string"){const i=new URL(t);S.hash?i.hash=r:i.pathname=r,r=i}return r})();gt.set(e,a),n=await a}catch{gt.delete(e);return}return n}async function xt(t,e){if(t&&!Rt(t,A,S.hash)){const n=await yn(t);if(!n)return;const a=bn(n);for(const r of Wt){const i=r.exec(a);if(i)return{id:Et(t),invalidating:e,route:r,params:Ke(i),url:t}}}}function bn(t){return Ve(S.hash?t.hash.replace(/^#/,"").replace(/[?#].+/,""):t.pathname.slice(A.length))||"/"}function Et(t){return(S.hash?t.hash.replace(/^#/,""):t.pathname)+t.search}function Pe({url:t,type:e,intent:n,delta:a,event:r,scroll:i}){let s=!1;const o=Qt(_,n,t,e,i??null);a!==void 0&&(o.navigation.delta=a),r!==void 0&&(o.navigation.event=r);const l={...o.navigation,cancel:()=>{s=!0,o.reject(new Error("navigation cancelled"))}};return nt||Re.forEach(c=>c(l)),s?null:o}async function F({type:t,url:e,popped:n,keepfocus:a,noscroll:r,replace_state:i,state:s={},redirect_count:o=0,nav_token:l={},accept:c=se,block:d=se,event:u}){var K;const w=D;D=l;const p=await xt(e,!1),f=t==="enter"?Qt(_,p,e,t):Pe({url:e,type:t,delta:n==null?void 0:n.delta,intent:p,scroll:n==null?void 0:n.scroll,event:u});if(!f){d(),D===l&&(D=w);return}const m=k,h=L;c(),nt=!0,bt&&f.navigation.type!=="enter"&&C.navigating.set(Z.current=f.navigation);let g=p&&await Oe(p);if(!g){if(Rt(e,A,S.hash))return await et(e,i);g=await $e(e,{id:null},await rt(new Bt(404,"Not Found",`Not found: ${e.pathname}`),{url:e,params:{},route:{id:null}}),404,i)}if(e=(p==null?void 0:p.url)||e,D!==l)return f.reject(new Error("navigation aborted")),!1;if(g.type==="redirect"){if(o<20){await F({type:t,url:new URL(g.location,e),popped:n,keepfocus:a,noscroll:r,replace_state:i,state:s,redirect_count:o+1,nav_token:l}),f.fulfil(void 0);return}g=await Xt({status:500,error:await rt(new Error("Redirect loop"),{url:e,params:{},route:{id:null}}),url:e,route:{id:null}})}else g.props.page.status>=400&&await C.updated.check()&&(await Ee(),await et(e,i));if(dn(),Gt(m),Ue(h),g.props.page.url.pathname!==e.pathname&&(e.pathname=g.props.page.url.pathname),s=n?n.state:s,!n){const E=i?0:1,G={[z]:k+=E,[Q]:L+=E,[we]:s};(i?history.replaceState:history.pushState).call(history,G,"",e),i||fn(k,L)}const b=p&&(y==null?void 0:y.id)===p.id?y.fork:null;y=null,g.props.page.state=s;let x;if(bt){const E=(await Promise.all(Array.from(un,W=>W(f.navigation)))).filter(W=>typeof W=="function");if(E.length>0){let W=function(){E.forEach(At=>{X.delete(At)})};E.push(W),E.forEach(At=>{X.add(At)})}const G=f.navigation.to;_={...g.state,nav:{params:G.params,route:G.route,url:G.url}},g.props.page&&(g.props.page.url=e);const Lt=b&&await b;Lt?x=Lt.commit():(J=null,Ae.$set(g.props),J&&Object.assign(g.props.page,J),ln(g.props.page),x=(K=qe)==null?void 0:K()),Le=!0}else await Ie(g,jt,!1);const{activeElement:$}=document;await x,await mt(),await mt();let N=null;if(ie){const E=n?n.scroll:r?V():null;E?scrollTo(E.x,E.y):(N=e.hash&&document.getElementById(Ce(e)))?N.scrollIntoView():scrollTo(0,0)}const pt=document.activeElement!==$&&document.activeElement!==document.body;!a&&!pt&&Ln(e,!N),ie=!0,g.props.page&&(J&&Object.assign(g.props.page,J),Object.assign(R,g.props.page)),nt=!1,t==="popstate"&&Te(L),f.fulfil(void 0),f.navigation.to&&(f.navigation.to.scroll=V()),X.forEach(E=>E(f.navigation)),C.navigating.set(Z.current=null)}async function $e(t,e,n,a,r){return t.origin===zt&&t.pathname===location.pathname&&!xe?await Xt({status:a,error:n,url:t,route:e}):await et(t,r)}function kn(){let t,e={element:void 0,href:void 0},n;P.addEventListener("mousemove",o=>{const l=o.target;clearTimeout(t),t=setTimeout(()=>{i(l,q.hover)},20)});function a(o){o.defaultPrevented||i(o.composedPath()[0],q.tap)}P.addEventListener("mousedown",a),P.addEventListener("touchstart",a,{passive:!0});const r=new IntersectionObserver(o=>{for(const l of o)l.isIntersecting&&(Pt(new URL(l.target.href)),r.unobserve(l.target))},{threshold:0});async function i(o,l){const c=be(o,P),d=c===e.element&&(c==null?void 0:c.href)===e.href&&l>=n;if(!c||d)return;const{url:u,external:w,download:p}=$t(c,A,S.hash);if(w||p)return;const f=_t(c),m=u&&Et(_.url)===Et(u);if(!(f.reload||m))if(l<=f.preload_data){e={element:c,href:c.href},n=q.tap;const h=await xt(u,!1);if(!h)return;pn(h)}else l<=f.preload_code&&(e={element:c,href:c.href},n=l,Pt(u))}function s(){r.disconnect();for(const o of P.querySelectorAll("a")){const{url:l,external:c,download:d}=$t(o,A,S.hash);if(c||d)continue;const u=_t(o);u.reload||(u.preload_code===q.viewport&&r.observe(o),u.preload_code===q.eager&&Pt(l))}}X.add(s),s()}function rt(t,e){if(t instanceof Vt)return t.body;const n=Ft(t),a=on(t);return S.hooks.handleError({error:t,event:e,status:n,message:a})??{message:a}}function Sn(t){if(typeof t=="function")vt.push(t);else{const{href:e}=new URL(t,location.href);vt.push(n=>n.href===e)}}function En(){var e;history.scrollRestoration="manual",addEventListener("beforeunload",n=>{let a=!1;if(ce(),!nt){const r=Qt(_,void 0,null,"leave"),i={...r.navigation,cancel:()=>{a=!0,r.reject(new Error("navigation cancelled"))}};Re.forEach(s=>s(i))}a?(n.preventDefault(),n.returnValue=""):history.scrollRestoration="auto"}),addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&ce()}),(e=navigator.connection)!=null&&e.saveData||kn(),P.addEventListener("click",async n=>{if(n.button||n.which!==1||n.metaKey||n.ctrlKey||n.shiftKey||n.altKey||n.defaultPrevented)return;const a=be(n.composedPath()[0],P);if(!a)return;const{url:r,external:i,target:s,download:o}=$t(a,A,S.hash);if(!r)return;if(s==="_parent"||s==="_top"){if(window.parent!==window)return}else if(s&&s!=="_self")return;const l=_t(a);if(!(a instanceof SVGAElement)&&r.protocol!==location.protocol&&!(r.protocol==="https:"||r.protocol==="http:")||o)return;const[d,u]=(S.hash?r.hash.replace(/^#/,""):r.href).split("#"),w=d===Tt(location);if(i||l.reload&&(!w||!u)){Pe({url:r,type:"link",event:n})?nt=!0:n.preventDefault();return}if(u!==void 0&&w){const[,p]=_.url.href.split("#");if(p===u){if(n.preventDefault(),u===""||u==="top"&&a.ownerDocument.getElementById("top")===null)scrollTo({top:0});else{const f=a.ownerDocument.getElementById(decodeURIComponent(u));f&&(f.scrollIntoView(),f.focus())}return}if(Y=!0,Gt(k),t(r),!l.replace_state)return;Y=!1}n.preventDefault(),await new Promise(p=>{requestAnimationFrame(()=>{setTimeout(p,0)}),setTimeout(p,100)}),await F({type:"link",url:r,keepfocus:l.keepfocus,noscroll:l.noscroll,replace_state:l.replace_state??r.href===location.href,event:n})}),P.addEventListener("submit",n=>{if(n.defaultPrevented)return;const a=HTMLFormElement.prototype.cloneNode.call(n.target),r=n.submitter;if(((r==null?void 0:r.formTarget)||a.target)==="_blank"||((r==null?void 0:r.formMethod)||a.method)!=="get")return;const o=new URL((r==null?void 0:r.hasAttribute("formaction"))&&(r==null?void 0:r.formAction)||a.action);if(Rt(o,A,!1))return;const l=n.target,c=_t(l);if(c.reload)return;n.preventDefault(),n.stopPropagation();const d=new FormData(l,r);o.search=new URLSearchParams(d).toString(),F({type:"form",url:o,keepfocus:c.keepfocus,noscroll:c.noscroll,replace_state:c.replace_state??o.href===location.href,event:n})}),addEventListener("popstate",async n=>{var a;if(!qt){if((a=n.state)!=null&&a[z]){const r=n.state[z];if(D={},r===k)return;const i=j[r],s=n.state[we]??{},o=new URL(n.state[en]??location.href),l=n.state[Q],c=_.url?Tt(location)===Tt(_.url):!1;if(l===L&&(Le||c)){s!==R.state&&(R.state=s),t(o),j[k]=V(),i&&scrollTo(i.x,i.y),k=r;return}const u=r-k;await F({type:"popstate",url:o,popped:{state:s,scroll:i,delta:u},accept:()=>{k=r,L=l},block:()=>{history.go(-u)},nav_token:D,event:n})}else if(!Y){const r=new URL(location.href);t(r),S.hash&&location.reload()}}}),addEventListener("hashchange",()=>{Y&&(Y=!1,history.replaceState({...history.state,[z]:++k,[Q]:L},"",location.href))});for(const n of document.querySelectorAll("link"))cn.has(n.rel)&&(n.href=n.href);addEventListener("pageshow",n=>{n.persisted&&C.navigating.set(Z.current=null)});function t(n){_.url=R.url=n,C.page.set(Zt(R)),C.page.notify()}}async function Rn(t,{status:e=200,error:n,node_ids:a,params:r,route:i,server_route:s,data:o,form:l}){xe=!0;const c=new URL(location.href);let d;({params:r={},route:i={id:null}}=await xt(c,!1)||{}),d=Wt.find(({id:p})=>p===i.id);let u,w=!0;try{const p=a.map(async(m,h)=>{const g=o[h];return g!=null&&g.uses&&(g.uses=xn(g.uses)),Yt({loader:S.nodes[m],url:c,params:r,route:i,parent:async()=>{const b={};for(let x=0;x{const o=history.state;qt=!0,location.replace(new URL(`#${a}`,location.href)),history.replaceState(o,"",t),e&&scrollTo(i,s),qt=!1})}else{const i=document.body,s=i.getAttribute("tabindex");i.tabIndex=-1,i.focus({preventScroll:!0,focusVisible:!1}),s!==null?i.setAttribute("tabindex",s):i.removeAttribute("tabindex")}const r=getSelection();if(r&&r.type!=="None"){const i=[];for(let s=0;s{if(r.rangeCount===i.length){for(let s=0;s{i=u,s=w});return o.catch(()=>{}),{navigation:{from:{params:t.params,route:{id:((c=t.route)==null?void 0:c.id)??null},url:t.url,scroll:V()},to:n&&{params:(e==null?void 0:e.params)??null,route:{id:((d=e==null?void 0:e.route)==null?void 0:d.id)??null},url:n,scroll:r},willUnload:!e,type:a,complete:o},fulfil:i,reject:s}}function Zt(t){return{data:t.data,error:t.error,form:t.form,params:t.params,route:t.route,state:t.state,status:t.status,url:t.url}}function An(t){const e=new URL(t);return e.hash=decodeURIComponent(t.hash),e}function Ce(t){let e;if(S.hash){const[,,n]=t.hash.split("#",3);e=n??""}else e=t.hash.slice(1);return decodeURIComponent(e)}export{jn as a,On as l,R as p,C as s}; +var te=t=>{throw TypeError(t)};var je=(t,e,n)=>e.has(t)||te("Cannot "+n);var v=(t,e,n)=>(je(t,e,"read from private field"),n?n.call(t):e.get(t)),U=(t,e,n)=>e.has(t)?te("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,n);import{bf as Ut,bg as Ne,aK as T,o as I,$ as O,aJ as mt,bh as qe}from"./VJhVTb1A.js";import{o as ee}from"./Cz6m6K8p.js";const B=[];function Dt(t,e=Ut){let n=null;const a=new Set;function r(o){if(Ne(t,o)&&(t=o,n)){const l=!B.length;for(const c of a)c[1](),B.push(c,t);if(l){for(let c=0;c{a.delete(c),a.size===0&&n&&(n(),n=null)}}return{set:r,update:i,subscribe:s}}class Vt{constructor(e,n){this.status=e,typeof n=="string"?this.body={message:n}:n?this.body=n:this.body={message:`Error: ${e}`}}toString(){return JSON.stringify(this.body)}}class Kt{constructor(e,n){this.status=e,this.location=n}}class Bt extends Error{constructor(e,n,a){super(a),this.status=e,this.text=n}}new URL("sveltekit-internal://");function De(t,e){return t==="/"||e==="ignore"?t:e==="never"?t.endsWith("/")?t.slice(0,-1):t:e==="always"&&!t.endsWith("/")?t+"/":t}function Ve(t){return t.split("%25").map(decodeURI).join("%25")}function Ke(t){for(const e in t)t[e]=decodeURIComponent(t[e]);return t}function Tt({href:t}){return t.split("#")[0]}function Be(...t){let e=5381;for(const n of t)if(typeof n=="string"){let a=n.length;for(;a;)e=e*33^n.charCodeAt(--a)}else if(ArrayBuffer.isView(n)){const a=new Uint8Array(n.buffer,n.byteOffset,n.byteLength);let r=a.length;for(;r;)e=e*33^a[--r]}else throw new TypeError("value must be a string or TypedArray");return(e>>>0).toString(36)}new TextEncoder;new TextDecoder;function Me(t){const e=atob(t),n=new Uint8Array(e.length);for(let a=0;a((t instanceof Request?t.method:(e==null?void 0:e.method)||"GET")!=="GET"&&H.delete(Mt(t)),ze(t,e));const H=new Map;function Fe(t,e){const n=Mt(t,e),a=document.querySelector(n);if(a!=null&&a.textContent){a.remove();let{body:r,...i}=JSON.parse(a.textContent);const s=a.getAttribute("data-ttl");return s&&H.set(n,{body:r,init:i,ttl:1e3*Number(s)}),a.getAttribute("data-b64")!==null&&(r=Me(r)),Promise.resolve(new Response(r,i))}return window.fetch(t,e)}function Ge(t,e,n){if(H.size>0){const a=Mt(t,n),r=H.get(a);if(r){if(performance.now(){const r=/^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(a);if(r)return e.push({name:r[1],matcher:r[2],optional:!1,rest:!0,chained:!0}),"(?:/([^]*))?";const i=/^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(a);if(i)return e.push({name:i[1],matcher:i[2],optional:!0,rest:!1,chained:!0}),"(?:/([^/]+))?";if(!a)return;const s=a.split(/\[(.+?)\](?!\])/);return"/"+s.map((l,c)=>{if(c%2){if(l.startsWith("x+"))return It(String.fromCharCode(parseInt(l.slice(2),16)));if(l.startsWith("u+"))return It(String.fromCharCode(...l.slice(2).split("-").map(m=>parseInt(m,16))));const d=We.exec(l),[,u,w,p,f]=d;return e.push({name:p,matcher:f,optional:!!u,rest:!!w,chained:w?c===1&&s[0]==="":!1}),w?"([^]*?)":u?"([^/]*)?":"([^/]+?)"}return It(l)}).join("")}).join("")}/?$`),params:e}}function Ye(t){return t!==""&&!/^\([^)]+\)$/.test(t)}function He(t){return t.slice(1).split("/").filter(Ye)}function Xe(t,e,n){const a={},r=t.slice(1),i=r.filter(o=>o!==void 0);let s=0;for(let o=0;od).join("/"),s=0),c===void 0)if(l.rest)c="";else continue;if(!l.matcher||n[l.matcher](c)){a[l.name]=c;const d=e[o+1],u=r[o+1];d&&!d.rest&&d.optional&&u&&l.chained&&(s=0),!d&&!u&&Object.keys(a).length===i.length&&(s=0);continue}if(l.optional&&l.chained){s++;continue}return}if(!s)return a}function It(t){return t.normalize().replace(/[[\]]/g,"\\$&").replace(/%/g,"%25").replace(/\//g,"%2[Ff]").replace(/\?/g,"%3[Ff]").replace(/#/g,"%23").replace(/[.*+?^${}()|\\]/g,"\\$&")}function Qe({nodes:t,server_loads:e,dictionary:n,matchers:a}){const r=new Set(e);return Object.entries(n).map(([o,[l,c,d]])=>{const{pattern:u,params:w}=Je(o),p={id:o,exec:f=>{const m=u.exec(f);if(m)return Xe(m,w,a)},errors:[1,...d||[]].map(f=>t[f]),layouts:[0,...c||[]].map(s),leaf:i(l)};return p.errors.length=p.layouts.length=Math.max(p.errors.length,p.layouts.length),p});function i(o){const l=o<0;return l&&(o=~o),[l,t[o]]}function s(o){return o===void 0?o:[r.has(o),t[o]]}}function ge(t,e=JSON.parse){try{return e(sessionStorage[t])}catch{}}function ne(t,e,n=JSON.stringify){const a=n(e);try{sessionStorage[t]=a}catch{}}var fe;const A=((fe=globalThis.__sveltekit_vrmx5m)==null?void 0:fe.base)??"";var ue;const Ze=((ue=globalThis.__sveltekit_vrmx5m)==null?void 0:ue.assets)??A??"",tn="1789462962656",me="sveltekit:snapshot",_e="sveltekit:scroll",we="sveltekit:states",en="sveltekit:pageurl",z="sveltekit:history",Q="sveltekit:navigation",q={tap:1,hover:2,viewport:3,eager:4,off:-1,false:-1},zt=location.origin;function ve(t){if(t instanceof URL)return t;let e=document.baseURI;if(!e){const n=document.getElementsByTagName("base");e=n.length?n[0].href:document.URL}return new URL(t,e)}function V(){return{x:pageXOffset,y:pageYOffset}}function M(t,e){return t.getAttribute(`data-sveltekit-${e}`)}const re={...q,"":q.hover};function ye(t){let e=t.assignedSlot??t.parentNode;return(e==null?void 0:e.nodeType)===11&&(e=e.host),e}function be(t,e){for(;t&&t!==e;){if(t.nodeName.toUpperCase()==="A"&&t.hasAttribute("href"))return t;t=ye(t)}}function $t(t,e,n){let a;try{if(a=new URL(t instanceof SVGAElement?t.href.baseVal:t.href,document.baseURI),n&&a.hash.match(/^#[^/]/)){const o=location.hash.split("#")[1]||"/";a.hash=`#${o}${a.hash}`}}catch{}const r=t instanceof SVGAElement?t.target.baseVal:t.target,i=!a||!!r||Rt(a,e,n)||(t.getAttribute("rel")||"").split(/\s+/).includes("external"),s=(a==null?void 0:a.origin)===zt&&t.hasAttribute("download");return{url:a,external:i,target:r,download:s}}function _t(t){let e=null,n=null,a=null,r=null,i=null,s=null,o=t;for(;o&&o!==document.documentElement;)a===null&&(a=M(o,"preload-code")),r===null&&(r=M(o,"preload-data")),e===null&&(e=M(o,"keepfocus")),n===null&&(n=M(o,"noscroll")),i===null&&(i=M(o,"reload")),s===null&&(s=M(o,"replacestate")),o=ye(o);function l(c){switch(c){case"":case"true":return!0;case"off":case"false":return!1;default:return}}return{preload_code:re[a??"off"],preload_data:re[r??"off"],keepfocus:l(e),noscroll:l(n),reload:l(i),replace_state:l(s)}}function ae(t){const e=Dt(t);let n=!0;function a(){n=!0,e.update(s=>s)}function r(s){n=!1,e.set(s)}function i(s){let o;return e.subscribe(l=>{(o===void 0||n&&l!==o)&&s(o=l)})}return{notify:a,set:r,subscribe:i}}const ke={v:()=>{}};function nn(){const{set:t,subscribe:e}=Dt(!1);let n;async function a(){clearTimeout(n);try{const r=await fetch(`${Ze}/_app/version.json`,{headers:{pragma:"no-cache","cache-control":"no-cache"}});if(!r.ok)return!1;const s=(await r.json()).version!==tn;return s&&(t(!0),ke.v(),clearTimeout(n)),s}catch{return!1}}return{subscribe:e,check:a}}function Rt(t,e,n){return t.origin!==zt||!t.pathname.startsWith(e)?!0:n?t.pathname!==location.pathname:!1}function On(t){}const Se=new Set(["load","prerender","csr","ssr","trailingSlash","config"]);[...Se];const rn=new Set([...Se]);[...rn];function an(t){return t.filter(e=>e!=null)}function Ft(t){return t instanceof Vt||t instanceof Bt?t.status:500}function on(t){return t instanceof Bt?t.text:"Internal Error"}let R,Z,Ot;const sn=ee.toString().includes("$$")||/function \w+\(\) \{\}/.test(ee.toString()),oe="a:";var at,ot,st,it,lt,ct,ft,ut,de,dt,he,ht,pe;sn?(R={data:{},form:null,error:null,params:{},route:{id:null},state:{},status:-1,url:new URL(oe)},Z={current:null},Ot={current:!1}):(R=new(de=class{constructor(){U(this,at,T({}));U(this,ot,T(null));U(this,st,T(null));U(this,it,T({}));U(this,lt,T({id:null}));U(this,ct,T({}));U(this,ft,T(-1));U(this,ut,T(new URL(oe)))}get data(){return I(v(this,at))}set data(e){O(v(this,at),e)}get form(){return I(v(this,ot))}set form(e){O(v(this,ot),e)}get error(){return I(v(this,st))}set error(e){O(v(this,st),e)}get params(){return I(v(this,it))}set params(e){O(v(this,it),e)}get route(){return I(v(this,lt))}set route(e){O(v(this,lt),e)}get state(){return I(v(this,ct))}set state(e){O(v(this,ct),e)}get status(){return I(v(this,ft))}set status(e){O(v(this,ft),e)}get url(){return I(v(this,ut))}set url(e){O(v(this,ut),e)}},at=new WeakMap,ot=new WeakMap,st=new WeakMap,it=new WeakMap,lt=new WeakMap,ct=new WeakMap,ft=new WeakMap,ut=new WeakMap,de),Z=new(he=class{constructor(){U(this,dt,T(null))}get current(){return I(v(this,dt))}set current(e){O(v(this,dt),e)}},dt=new WeakMap,he),Ot=new(pe=class{constructor(){U(this,ht,T(!1))}get current(){return I(v(this,ht))}set current(e){O(v(this,ht),e)}},ht=new WeakMap,pe),ke.v=()=>Ot.current=!0);function ln(t){Object.assign(R,t)}const cn=new Set(["icon","shortcut icon","apple-touch-icon"]);let J=null;const j=ge(_e)??{},tt=ge(me)??{},C={url:ae({}),page:ae({}),navigating:Dt(null),updated:nn()};function Gt(t){j[t]=V()}function fn(t,e){let n=t+1;for(;j[n];)delete j[n],n+=1;for(n=e+1;tt[n];)delete tt[n],n+=1}function et(t,e=!1){return e?location.replace(t.href):location.href=t.href,new Promise(()=>{})}async function Ee(){if("serviceWorker"in navigator){const t=await navigator.serviceWorker.getRegistration(A||"/");t&&await t.update()}}function se(){}let Wt,Ct,wt,P,jt,S;const vt=[],yt=[];let y=null;function Nt(){var t;(t=y==null?void 0:y.fork)==null||t.then(e=>e==null?void 0:e.discard()),y=null}const gt=new Map,Re=new Set,un=new Set,X=new Set;let _={branch:[],error:null,url:null},xe=!1,bt=!1,ie=!0,nt=!1,Y=!1,Le=!1,Jt=!1,Ae,k,L,D;const kt=new Set,le=new Map;async function jn(t,e,n){var i,s,o,l,c;(i=globalThis.__sveltekit_vrmx5m)!=null&&i.data&&globalThis.__sveltekit_vrmx5m.data,document.URL!==location.href&&(location.href=location.href),S=t,await((o=(s=t.hooks).init)==null?void 0:o.call(s)),Wt=Qe(t),P=document.documentElement,jt=e,Ct=t.nodes[0],wt=t.nodes[1],Ct(),wt(),k=(l=history.state)==null?void 0:l[z],L=(c=history.state)==null?void 0:c[Q],k||(k=L=Date.now(),history.replaceState({...history.state,[z]:k,[Q]:L},""));const a=j[k];function r(){a&&(history.scrollRestoration="manual",scrollTo(a.x,a.y))}n?(r(),await Rn(jt,n)):(await F({type:"enter",url:ve(S.hash?An(new URL(location.href)):location.href),replace_state:!0}),r()),En()}function dn(){vt.length=0,Jt=!1}function Ue(t){yt.some(e=>e==null?void 0:e.snapshot)&&(tt[t]=yt.map(e=>{var n;return(n=e==null?void 0:e.snapshot)==null?void 0:n.capture()}))}function Te(t){var e;(e=tt[t])==null||e.forEach((n,a)=>{var r,i;(i=(r=yt[a])==null?void 0:r.snapshot)==null||i.restore(n)})}function ce(){Gt(k),ne(_e,j),Ue(L),ne(me,tt)}async function hn(t,e,n,a){let r;e.invalidateAll&&Nt(),await F({type:"goto",url:ve(t),keepfocus:e.keepFocus,noscroll:e.noScroll,replace_state:e.replaceState,state:e.state,redirect_count:n,nav_token:a,accept:()=>{e.invalidateAll&&(Jt=!0,r=[...le.keys()]),e.invalidate&&e.invalidate.forEach(Sn)}}),e.invalidateAll&&mt().then(mt).then(()=>{le.forEach(({resource:i},s)=>{var o;r!=null&&r.includes(s)&&((o=i.refresh)==null||o.call(i))})})}async function pn(t){if(t.id!==(y==null?void 0:y.id)){Nt();const e={};kt.add(e),y={id:t.id,token:e,promise:Oe({...t,preload:e}).then(n=>(kt.delete(e),n.type==="loaded"&&n.state.error&&Nt(),n)),fork:null}}return y.promise}async function Pt(t){var n;const e=(n=await xt(t,!1))==null?void 0:n.route;e&&await Promise.all([...e.layouts,e.leaf].filter(Boolean).map(a=>a[1]()))}async function Ie(t,e,n){var i;const a={params:_.params,route:{id:((i=_.route)==null?void 0:i.id)??null},url:new URL(location.href)};_={...t.state,nav:a};const r=document.querySelector("style[data-sveltekit]");if(r&&r.remove(),Object.assign(R,t.props.page),Ae=new S.root({target:e,props:{...t.props,stores:C,components:yt},hydrate:n,sync:!1,transformError:void 0}),await Promise.resolve(),Te(L),n){const s={from:null,to:{...a,scroll:j[k]??V()},willUnload:!1,type:"enter",complete:Promise.resolve()};X.forEach(o=>o(s))}bt=!0}async function St({url:t,params:e,branch:n,errors:a,status:r,error:i,route:s,form:o}){let l="never";if(A&&(t.pathname===A||t.pathname===A+"/"))l="always";else for(const f of n)(f==null?void 0:f.slash)!==void 0&&(l=f.slash);t.pathname=De(t.pathname,l),t.search=t.search;const c={type:"loaded",state:{url:t,params:e,branch:n,error:i,route:s},props:{constructors:an(n).map(f=>f.node.component),page:Zt(R)}};o!==void 0&&(c.props.form=o);let d={},u=!R,w=0;for(let f=0;fo(new URL(s))))return!0;return!1}function Ht(t,e){return(t==null?void 0:t.type)==="data"?t:(t==null?void 0:t.type)==="skip"?e??null:null}function _n(t,e){if(!t)return new Set(e.searchParams.keys());const n=new Set([...t.searchParams.keys(),...e.searchParams.keys()]);for(const a of n){const r=t.searchParams.getAll(a),i=e.searchParams.getAll(a);r.every(s=>i.includes(s))&&i.every(s=>r.includes(s))&&n.delete(a)}return n}function wn({error:t,url:e,route:n,params:a}){return{type:"loaded",state:{error:t,url:e,route:n,params:a,branch:[]},props:{page:Zt(R),constructors:[]}}}async function Oe({id:t,invalidating:e,url:n,params:a,route:r,preload:i}){if((y==null?void 0:y.id)===t)return kt.delete(y.token),y.promise;const{errors:s,layouts:o,leaf:l}=r,c=[...o,l];s.forEach(h=>h==null?void 0:h().catch(()=>{})),c.forEach(h=>h==null?void 0:h[1]().catch(()=>{}));const d=_.url?t!==Et(_.url):!1,u=_.route?r.id!==_.route.id:!1,w=_n(_.url,n);let p=!1;const f=c.map(async(h,g)=>{var $;if(!h)return;const b=_.branch[g];return h[1]===(b==null?void 0:b.loader)&&!mn(p,u,d,w,($=b.universal)==null?void 0:$.uses,a)?b:(p=!0,Yt({loader:h[1],url:n,params:a,route:r,parent:async()=>{var pt;const N={};for(let K=0;K{});const m=[];for(let h=0;hPromise.resolve({}),server_data_node:Ht(i)}),o={node:await wt(),loader:wt,universal:null,server:null,data:null};return St({url:n,params:r,branch:[s,o],status:t,error:e,errors:[],route:null})}catch(s){if(s instanceof Kt)return hn(new URL(s.location,location.href),{},0);throw s}}async function yn(t){const e=t.href;if(gt.has(e))return gt.get(e);let n;try{const a=(async()=>{let r=await S.hooks.reroute({url:new URL(t),fetch:async(i,s)=>gn(i,s,t).promise})??t;if(typeof r=="string"){const i=new URL(t);S.hash?i.hash=r:i.pathname=r,r=i}return r})();gt.set(e,a),n=await a}catch{gt.delete(e);return}return n}async function xt(t,e){if(t&&!Rt(t,A,S.hash)){const n=await yn(t);if(!n)return;const a=bn(n);for(const r of Wt){const i=r.exec(a);if(i)return{id:Et(t),invalidating:e,route:r,params:Ke(i),url:t}}}}function bn(t){return Ve(S.hash?t.hash.replace(/^#/,"").replace(/[?#].+/,""):t.pathname.slice(A.length))||"/"}function Et(t){return(S.hash?t.hash.replace(/^#/,""):t.pathname)+t.search}function Pe({url:t,type:e,intent:n,delta:a,event:r,scroll:i}){let s=!1;const o=Qt(_,n,t,e,i??null);a!==void 0&&(o.navigation.delta=a),r!==void 0&&(o.navigation.event=r);const l={...o.navigation,cancel:()=>{s=!0,o.reject(new Error("navigation cancelled"))}};return nt||Re.forEach(c=>c(l)),s?null:o}async function F({type:t,url:e,popped:n,keepfocus:a,noscroll:r,replace_state:i,state:s={},redirect_count:o=0,nav_token:l={},accept:c=se,block:d=se,event:u}){var K;const w=D;D=l;const p=await xt(e,!1),f=t==="enter"?Qt(_,p,e,t):Pe({url:e,type:t,delta:n==null?void 0:n.delta,intent:p,scroll:n==null?void 0:n.scroll,event:u});if(!f){d(),D===l&&(D=w);return}const m=k,h=L;c(),nt=!0,bt&&f.navigation.type!=="enter"&&C.navigating.set(Z.current=f.navigation);let g=p&&await Oe(p);if(!g){if(Rt(e,A,S.hash))return await et(e,i);g=await $e(e,{id:null},await rt(new Bt(404,"Not Found",`Not found: ${e.pathname}`),{url:e,params:{},route:{id:null}}),404,i)}if(e=(p==null?void 0:p.url)||e,D!==l)return f.reject(new Error("navigation aborted")),!1;if(g.type==="redirect"){if(o<20){await F({type:t,url:new URL(g.location,e),popped:n,keepfocus:a,noscroll:r,replace_state:i,state:s,redirect_count:o+1,nav_token:l}),f.fulfil(void 0);return}g=await Xt({status:500,error:await rt(new Error("Redirect loop"),{url:e,params:{},route:{id:null}}),url:e,route:{id:null}})}else g.props.page.status>=400&&await C.updated.check()&&(await Ee(),await et(e,i));if(dn(),Gt(m),Ue(h),g.props.page.url.pathname!==e.pathname&&(e.pathname=g.props.page.url.pathname),s=n?n.state:s,!n){const E=i?0:1,G={[z]:k+=E,[Q]:L+=E,[we]:s};(i?history.replaceState:history.pushState).call(history,G,"",e),i||fn(k,L)}const b=p&&(y==null?void 0:y.id)===p.id?y.fork:null;y=null,g.props.page.state=s;let x;if(bt){const E=(await Promise.all(Array.from(un,W=>W(f.navigation)))).filter(W=>typeof W=="function");if(E.length>0){let W=function(){E.forEach(At=>{X.delete(At)})};E.push(W),E.forEach(At=>{X.add(At)})}const G=f.navigation.to;_={...g.state,nav:{params:G.params,route:G.route,url:G.url}},g.props.page&&(g.props.page.url=e);const Lt=b&&await b;Lt?x=Lt.commit():(J=null,Ae.$set(g.props),J&&Object.assign(g.props.page,J),ln(g.props.page),x=(K=qe)==null?void 0:K()),Le=!0}else await Ie(g,jt,!1);const{activeElement:$}=document;await x,await mt(),await mt();let N=null;if(ie){const E=n?n.scroll:r?V():null;E?scrollTo(E.x,E.y):(N=e.hash&&document.getElementById(Ce(e)))?N.scrollIntoView():scrollTo(0,0)}const pt=document.activeElement!==$&&document.activeElement!==document.body;!a&&!pt&&Ln(e,!N),ie=!0,g.props.page&&(J&&Object.assign(g.props.page,J),Object.assign(R,g.props.page)),nt=!1,t==="popstate"&&Te(L),f.fulfil(void 0),f.navigation.to&&(f.navigation.to.scroll=V()),X.forEach(E=>E(f.navigation)),C.navigating.set(Z.current=null)}async function $e(t,e,n,a,r){return t.origin===zt&&t.pathname===location.pathname&&!xe?await Xt({status:a,error:n,url:t,route:e}):await et(t,r)}function kn(){let t,e={element:void 0,href:void 0},n;P.addEventListener("mousemove",o=>{const l=o.target;clearTimeout(t),t=setTimeout(()=>{i(l,q.hover)},20)});function a(o){o.defaultPrevented||i(o.composedPath()[0],q.tap)}P.addEventListener("mousedown",a),P.addEventListener("touchstart",a,{passive:!0});const r=new IntersectionObserver(o=>{for(const l of o)l.isIntersecting&&(Pt(new URL(l.target.href)),r.unobserve(l.target))},{threshold:0});async function i(o,l){const c=be(o,P),d=c===e.element&&(c==null?void 0:c.href)===e.href&&l>=n;if(!c||d)return;const{url:u,external:w,download:p}=$t(c,A,S.hash);if(w||p)return;const f=_t(c),m=u&&Et(_.url)===Et(u);if(!(f.reload||m))if(l<=f.preload_data){e={element:c,href:c.href},n=q.tap;const h=await xt(u,!1);if(!h)return;pn(h)}else l<=f.preload_code&&(e={element:c,href:c.href},n=l,Pt(u))}function s(){r.disconnect();for(const o of P.querySelectorAll("a")){const{url:l,external:c,download:d}=$t(o,A,S.hash);if(c||d)continue;const u=_t(o);u.reload||(u.preload_code===q.viewport&&r.observe(o),u.preload_code===q.eager&&Pt(l))}}X.add(s),s()}function rt(t,e){if(t instanceof Vt)return t.body;const n=Ft(t),a=on(t);return S.hooks.handleError({error:t,event:e,status:n,message:a})??{message:a}}function Sn(t){if(typeof t=="function")vt.push(t);else{const{href:e}=new URL(t,location.href);vt.push(n=>n.href===e)}}function En(){var e;history.scrollRestoration="manual",addEventListener("beforeunload",n=>{let a=!1;if(ce(),!nt){const r=Qt(_,void 0,null,"leave"),i={...r.navigation,cancel:()=>{a=!0,r.reject(new Error("navigation cancelled"))}};Re.forEach(s=>s(i))}a?(n.preventDefault(),n.returnValue=""):history.scrollRestoration="auto"}),addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&ce()}),(e=navigator.connection)!=null&&e.saveData||kn(),P.addEventListener("click",async n=>{if(n.button||n.which!==1||n.metaKey||n.ctrlKey||n.shiftKey||n.altKey||n.defaultPrevented)return;const a=be(n.composedPath()[0],P);if(!a)return;const{url:r,external:i,target:s,download:o}=$t(a,A,S.hash);if(!r)return;if(s==="_parent"||s==="_top"){if(window.parent!==window)return}else if(s&&s!=="_self")return;const l=_t(a);if(!(a instanceof SVGAElement)&&r.protocol!==location.protocol&&!(r.protocol==="https:"||r.protocol==="http:")||o)return;const[d,u]=(S.hash?r.hash.replace(/^#/,""):r.href).split("#"),w=d===Tt(location);if(i||l.reload&&(!w||!u)){Pe({url:r,type:"link",event:n})?nt=!0:n.preventDefault();return}if(u!==void 0&&w){const[,p]=_.url.href.split("#");if(p===u){if(n.preventDefault(),u===""||u==="top"&&a.ownerDocument.getElementById("top")===null)scrollTo({top:0});else{const f=a.ownerDocument.getElementById(decodeURIComponent(u));f&&(f.scrollIntoView(),f.focus())}return}if(Y=!0,Gt(k),t(r),!l.replace_state)return;Y=!1}n.preventDefault(),await new Promise(p=>{requestAnimationFrame(()=>{setTimeout(p,0)}),setTimeout(p,100)}),await F({type:"link",url:r,keepfocus:l.keepfocus,noscroll:l.noscroll,replace_state:l.replace_state??r.href===location.href,event:n})}),P.addEventListener("submit",n=>{if(n.defaultPrevented)return;const a=HTMLFormElement.prototype.cloneNode.call(n.target),r=n.submitter;if(((r==null?void 0:r.formTarget)||a.target)==="_blank"||((r==null?void 0:r.formMethod)||a.method)!=="get")return;const o=new URL((r==null?void 0:r.hasAttribute("formaction"))&&(r==null?void 0:r.formAction)||a.action);if(Rt(o,A,!1))return;const l=n.target,c=_t(l);if(c.reload)return;n.preventDefault(),n.stopPropagation();const d=new FormData(l,r);o.search=new URLSearchParams(d).toString(),F({type:"form",url:o,keepfocus:c.keepfocus,noscroll:c.noscroll,replace_state:c.replace_state??o.href===location.href,event:n})}),addEventListener("popstate",async n=>{var a;if(!qt){if((a=n.state)!=null&&a[z]){const r=n.state[z];if(D={},r===k)return;const i=j[r],s=n.state[we]??{},o=new URL(n.state[en]??location.href),l=n.state[Q],c=_.url?Tt(location)===Tt(_.url):!1;if(l===L&&(Le||c)){s!==R.state&&(R.state=s),t(o),j[k]=V(),i&&scrollTo(i.x,i.y),k=r;return}const u=r-k;await F({type:"popstate",url:o,popped:{state:s,scroll:i,delta:u},accept:()=>{k=r,L=l},block:()=>{history.go(-u)},nav_token:D,event:n})}else if(!Y){const r=new URL(location.href);t(r),S.hash&&location.reload()}}}),addEventListener("hashchange",()=>{Y&&(Y=!1,history.replaceState({...history.state,[z]:++k,[Q]:L},"",location.href))});for(const n of document.querySelectorAll("link"))cn.has(n.rel)&&(n.href=n.href);addEventListener("pageshow",n=>{n.persisted&&C.navigating.set(Z.current=null)});function t(n){_.url=R.url=n,C.page.set(Zt(R)),C.page.notify()}}async function Rn(t,{status:e=200,error:n,node_ids:a,params:r,route:i,server_route:s,data:o,form:l}){xe=!0;const c=new URL(location.href);let d;({params:r={},route:i={id:null}}=await xt(c,!1)||{}),d=Wt.find(({id:p})=>p===i.id);let u,w=!0;try{const p=a.map(async(m,h)=>{const g=o[h];return g!=null&&g.uses&&(g.uses=xn(g.uses)),Yt({loader:S.nodes[m],url:c,params:r,route:i,parent:async()=>{const b={};for(let x=0;x{const o=history.state;qt=!0,location.replace(new URL(`#${a}`,location.href)),history.replaceState(o,"",t),e&&scrollTo(i,s),qt=!1})}else{const i=document.body,s=i.getAttribute("tabindex");i.tabIndex=-1,i.focus({preventScroll:!0,focusVisible:!1}),s!==null?i.setAttribute("tabindex",s):i.removeAttribute("tabindex")}const r=getSelection();if(r&&r.type!=="None"){const i=[];for(let s=0;s{if(r.rangeCount===i.length){for(let s=0;s{i=u,s=w});return o.catch(()=>{}),{navigation:{from:{params:t.params,route:{id:((c=t.route)==null?void 0:c.id)??null},url:t.url,scroll:V()},to:n&&{params:(e==null?void 0:e.params)??null,route:{id:((d=e==null?void 0:e.route)==null?void 0:d.id)??null},url:n,scroll:r},willUnload:!e,type:a,complete:o},fulfil:i,reject:s}}function Zt(t){return{data:t.data,error:t.error,form:t.form,params:t.params,route:t.route,state:t.state,status:t.status,url:t.url}}function An(t){const e=new URL(t);return e.hash=decodeURIComponent(t.hash),e}function Ce(t){let e;if(S.hash){const[,,n]=t.hash.split("#",3);e=n??""}else e=t.hash.slice(1);return decodeURIComponent(e)}export{jn as a,On as l,R as p,C as s}; diff --git a/frontend/dist/_app/immutable/entry/app.Cn2ZzZiT.js b/frontend/dist/_app/immutable/entry/app.BiXph2fc.js similarity index 93% rename from frontend/dist/_app/immutable/entry/app.Cn2ZzZiT.js rename to frontend/dist/_app/immutable/entry/app.BiXph2fc.js index 5c2d84c..c46a97f 100644 --- a/frontend/dist/_app/immutable/entry/app.Cn2ZzZiT.js +++ b/frontend/dist/_app/immutable/entry/app.BiXph2fc.js @@ -1,2 +1,2 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["../nodes/0.DecrOZwe.js","../chunks/C14P8jaG.js","../chunks/VJhVTb1A.js","../chunks/CiUbu-1F.js","../chunks/U5yjBaoU.js","../nodes/1.DU6Ijmpv.js","../chunks/DmjnTBOd.js","../chunks/BeON-0X8.js","../chunks/BI00lc_u.js","../chunks/Cz6m6K8p.js","../nodes/2.DQxkgq4x.js","../chunks/zf4GFcxs.js","../assets/2.DKE6ochW.css"])))=>i.map(i=>d[i]); -var H=t=>{throw TypeError(t)};var J=(t,e,s)=>e.has(t)||H("Cannot "+s);var l=(t,e,s)=>(J(t,e,"read from private field"),s?s.call(t):e.get(t)),S=(t,e,s)=>e.has(t)?H("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,s),C=(t,e,s,o)=>(J(t,e,"write to private field"),o?o.call(t,s):e.set(t,s),s);import{$ as L,ab as X,o as g,aH as Z,au as M,aI as N,x as $,u as ee,j as te,aJ as re,g as j,C as se,z as ae,aK as A,A as ne,B as oe,aL as B,y as ce}from"../chunks/VJhVTb1A.js";import{h as ie,m as le,u as ue,s as fe}from"../chunks/BeON-0X8.js";import{a as w,c as I,f as W,t as me}from"../chunks/C14P8jaG.js";import{o as de}from"../chunks/Cz6m6K8p.js";import{p as T,i as D,c as V,b as q}from"../chunks/zf4GFcxs.js";function he(t){return class extends _e{constructor(e){super({component:t,...e})}}}var y,m;class _e{constructor(e){S(this,y);S(this,m);var _;var s=new Map,o=(a,r)=>{var c=N(r,!1,!1);return s.set(a,c),c};const u=new Proxy({...e.props||{},$$events:{}},{get(a,r){return g(s.get(r)??o(r,Reflect.get(a,r)))},has(a,r){return r===X?!0:(g(s.get(r)??o(r,Reflect.get(a,r))),Reflect.has(a,r))},set(a,r,c){return L(s.get(r)??o(r,c),c),Reflect.set(a,r,c)}});C(this,m,(e.hydrate?ie:le)(e.component,{target:e.target,anchor:e.anchor,props:u,context:e.context,intro:e.intro??!1,recover:e.recover,transformError:e.transformError})),(!((_=e==null?void 0:e.props)!=null&&_.$$host)||e.sync===!1)&&Z(),C(this,y,u.$$events);for(const a of Object.keys(l(this,m)))a==="$set"||a==="$destroy"||a==="$on"||M(this,a,{get(){return l(this,m)[a]},set(r){l(this,m)[a]=r},enumerable:!0});l(this,m).$set=a=>{Object.assign(u,a)},l(this,m).$destroy=()=>{ue(l(this,m))}}$set(e){l(this,m).$set(e)}$on(e,s){l(this,y)[e]=l(this,y)[e]||[];const o=(...u)=>s.call(this,...u);return l(this,y)[e].push(o),()=>{l(this,y)[e]=l(this,y)[e].filter(u=>u!==o)}}$destroy(){l(this,m).$destroy()}}y=new WeakMap,m=new WeakMap;const ve="modulepreload",ge=function(t,e){return new URL(t,e).href},K={},U=function(e,s,o){let u=Promise.resolve();if(s&&s.length>0){let a=function(i){return Promise.all(i.map(v=>Promise.resolve(v).then(b=>({status:"fulfilled",value:b}),b=>({status:"rejected",reason:b}))))};const r=document.getElementsByTagName("link"),c=document.querySelector("meta[property=csp-nonce]"),k=(c==null?void 0:c.nonce)||(c==null?void 0:c.getAttribute("nonce"));u=a(s.map(i=>{if(i=ge(i,o),i in K)return;K[i]=!0;const v=i.endsWith(".css"),b=v?'[rel="stylesheet"]':"";if(!!o)for(let n=r.length-1;n>=0;n--){const f=r[n];if(f.href===i&&(!v||f.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${i}"]${b}`))return;const h=document.createElement("link");if(h.rel=v?"stylesheet":ve,v||(h.as="script"),h.crossOrigin="",h.href=i,k&&h.setAttribute("nonce",k),document.head.appendChild(h),v)return new Promise((n,f)=>{h.addEventListener("load",n),h.addEventListener("error",()=>f(new Error(`Unable to preload CSS for ${i}`)))})}))}function _(a){const r=new Event("vite:preloadError",{cancelable:!0});if(r.payload=a,window.dispatchEvent(r),!r.defaultPrevented)throw a}return u.then(a=>{for(const r of a||[])r.status==="rejected"&&_(r.reason);return e().catch(_)})},Se={};var ye=W('
'),be=W(" ",1);function Ee(t,e){$(e,!0);let s=T(e,"components",23,()=>[]),o=T(e,"data_0",3,null),u=T(e,"data_1",3,null);ee(()=>e.stores.page.set(e.page)),te(()=>{e.stores,e.page,e.constructors,s(),e.form,o(),u(),e.stores.page.notify()});let _=A(!1),a=A(!1),r=A(null);de(()=>{const n=e.stores.page.subscribe(()=>{g(_)&&(L(a,!0),re().then(()=>{L(r,document.title||"untitled page",!0)}))});return L(_,!0),n});const c=B(()=>e.constructors[1]);var k=be(),i=j(k);{var v=n=>{const f=B(()=>e.constructors[0]);var E=I(),x=j(E);V(x,()=>g(f),(P,R)=>{q(R(P,{get data(){return o()},get form(){return e.form},get params(){return e.page.params},children:(d,Re)=>{var G=I(),p=j(G);V(p,()=>g(c),(F,Q)=>{q(Q(F,{get data(){return u()},get form(){return e.form},get params(){return e.page.params}}),O=>s()[1]=O,()=>{var O;return(O=s())==null?void 0:O[1]})}),w(d,G)},$$slots:{default:!0}}),d=>s()[0]=d,()=>{var d;return(d=s())==null?void 0:d[0]})}),w(n,E)},b=n=>{const f=B(()=>e.constructors[0]);var E=I(),x=j(E);V(x,()=>g(f),(P,R)=>{q(R(P,{get data(){return o()},get form(){return e.form},get params(){return e.page.params}}),d=>s()[0]=d,()=>{var d;return(d=s())==null?void 0:d[0]})}),w(n,E)};D(i,n=>{e.constructors[1]?n(v):n(b,-1)})}var z=se(i,2);{var h=n=>{var f=ye(),E=ne(f);{var x=P=>{var R=me();ce(()=>fe(R,g(r))),w(P,R)};D(E,P=>{g(a)&&P(x)})}oe(f),w(n,f)};D(z,n=>{g(_)&&n(h)})}w(t,k),ae()}const Ce=he(Ee),Ae=[()=>U(()=>import("../nodes/0.DecrOZwe.js"),__vite__mapDeps([0,1,2,3,4]),import.meta.url),()=>U(()=>import("../nodes/1.DU6Ijmpv.js"),__vite__mapDeps([5,1,2,6,7,8,9]),import.meta.url),()=>U(()=>import("../nodes/2.DQxkgq4x.js"),__vite__mapDeps([10,1,2,9,11,4,7,3,6,12]),import.meta.url)],Be=[],Ie={"/":[2]},Y={handleError:(({error:t})=>{console.error(t)}),reroute:(()=>{}),transport:{}},Pe=Object.fromEntries(Object.entries(Y.transport).map(([t,e])=>[t,e.decode])),Te=Object.fromEntries(Object.entries(Y.transport).map(([t,e])=>[t,e.encode])),De=!1,Ve=(t,e)=>Pe[t](e);export{Ve as decode,Pe as decoders,Ie as dictionary,Te as encoders,De as hash,Y as hooks,Se as matchers,Ae as nodes,Ce as root,Be as server_loads}; +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["../nodes/0.DecrOZwe.js","../chunks/C14P8jaG.js","../chunks/VJhVTb1A.js","../chunks/CiUbu-1F.js","../chunks/U5yjBaoU.js","../nodes/1.CCoIP03N.js","../chunks/DmjnTBOd.js","../chunks/BeON-0X8.js","../chunks/5xxJZag6.js","../chunks/Cz6m6K8p.js","../nodes/2.Db1dJWID.js","../chunks/zf4GFcxs.js","../assets/2.BvWkSkaN.css"])))=>i.map(i=>d[i]); +var H=t=>{throw TypeError(t)};var J=(t,e,s)=>e.has(t)||H("Cannot "+s);var l=(t,e,s)=>(J(t,e,"read from private field"),s?s.call(t):e.get(t)),S=(t,e,s)=>e.has(t)?H("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,s),C=(t,e,s,o)=>(J(t,e,"write to private field"),o?o.call(t,s):e.set(t,s),s);import{$ as L,ab as X,o as g,aH as Z,au as M,aI as N,x as $,u as ee,j as te,aJ as re,g as j,C as se,z as ae,aK as A,A as ne,B as oe,aL as B,y as ce}from"../chunks/VJhVTb1A.js";import{h as ie,m as le,u as ue,s as fe}from"../chunks/BeON-0X8.js";import{a as w,c as I,f as W,t as me}from"../chunks/C14P8jaG.js";import{o as de}from"../chunks/Cz6m6K8p.js";import{p as T,i as D,c as V,b as q}from"../chunks/zf4GFcxs.js";function he(t){return class extends _e{constructor(e){super({component:t,...e})}}}var y,m;class _e{constructor(e){S(this,y);S(this,m);var _;var s=new Map,o=(a,r)=>{var c=N(r,!1,!1);return s.set(a,c),c};const u=new Proxy({...e.props||{},$$events:{}},{get(a,r){return g(s.get(r)??o(r,Reflect.get(a,r)))},has(a,r){return r===X?!0:(g(s.get(r)??o(r,Reflect.get(a,r))),Reflect.has(a,r))},set(a,r,c){return L(s.get(r)??o(r,c),c),Reflect.set(a,r,c)}});C(this,m,(e.hydrate?ie:le)(e.component,{target:e.target,anchor:e.anchor,props:u,context:e.context,intro:e.intro??!1,recover:e.recover,transformError:e.transformError})),(!((_=e==null?void 0:e.props)!=null&&_.$$host)||e.sync===!1)&&Z(),C(this,y,u.$$events);for(const a of Object.keys(l(this,m)))a==="$set"||a==="$destroy"||a==="$on"||M(this,a,{get(){return l(this,m)[a]},set(r){l(this,m)[a]=r},enumerable:!0});l(this,m).$set=a=>{Object.assign(u,a)},l(this,m).$destroy=()=>{ue(l(this,m))}}$set(e){l(this,m).$set(e)}$on(e,s){l(this,y)[e]=l(this,y)[e]||[];const o=(...u)=>s.call(this,...u);return l(this,y)[e].push(o),()=>{l(this,y)[e]=l(this,y)[e].filter(u=>u!==o)}}$destroy(){l(this,m).$destroy()}}y=new WeakMap,m=new WeakMap;const ve="modulepreload",ge=function(t,e){return new URL(t,e).href},K={},U=function(e,s,o){let u=Promise.resolve();if(s&&s.length>0){let a=function(i){return Promise.all(i.map(v=>Promise.resolve(v).then(b=>({status:"fulfilled",value:b}),b=>({status:"rejected",reason:b}))))};const r=document.getElementsByTagName("link"),c=document.querySelector("meta[property=csp-nonce]"),k=(c==null?void 0:c.nonce)||(c==null?void 0:c.getAttribute("nonce"));u=a(s.map(i=>{if(i=ge(i,o),i in K)return;K[i]=!0;const v=i.endsWith(".css"),b=v?'[rel="stylesheet"]':"";if(!!o)for(let n=r.length-1;n>=0;n--){const f=r[n];if(f.href===i&&(!v||f.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${i}"]${b}`))return;const h=document.createElement("link");if(h.rel=v?"stylesheet":ve,v||(h.as="script"),h.crossOrigin="",h.href=i,k&&h.setAttribute("nonce",k),document.head.appendChild(h),v)return new Promise((n,f)=>{h.addEventListener("load",n),h.addEventListener("error",()=>f(new Error(`Unable to preload CSS for ${i}`)))})}))}function _(a){const r=new Event("vite:preloadError",{cancelable:!0});if(r.payload=a,window.dispatchEvent(r),!r.defaultPrevented)throw a}return u.then(a=>{for(const r of a||[])r.status==="rejected"&&_(r.reason);return e().catch(_)})},Se={};var ye=W('
'),be=W(" ",1);function Ee(t,e){$(e,!0);let s=T(e,"components",23,()=>[]),o=T(e,"data_0",3,null),u=T(e,"data_1",3,null);ee(()=>e.stores.page.set(e.page)),te(()=>{e.stores,e.page,e.constructors,s(),e.form,o(),u(),e.stores.page.notify()});let _=A(!1),a=A(!1),r=A(null);de(()=>{const n=e.stores.page.subscribe(()=>{g(_)&&(L(a,!0),re().then(()=>{L(r,document.title||"untitled page",!0)}))});return L(_,!0),n});const c=B(()=>e.constructors[1]);var k=be(),i=j(k);{var v=n=>{const f=B(()=>e.constructors[0]);var E=I(),x=j(E);V(x,()=>g(f),(P,R)=>{q(R(P,{get data(){return o()},get form(){return e.form},get params(){return e.page.params},children:(d,Re)=>{var G=I(),p=j(G);V(p,()=>g(c),(F,Q)=>{q(Q(F,{get data(){return u()},get form(){return e.form},get params(){return e.page.params}}),O=>s()[1]=O,()=>{var O;return(O=s())==null?void 0:O[1]})}),w(d,G)},$$slots:{default:!0}}),d=>s()[0]=d,()=>{var d;return(d=s())==null?void 0:d[0]})}),w(n,E)},b=n=>{const f=B(()=>e.constructors[0]);var E=I(),x=j(E);V(x,()=>g(f),(P,R)=>{q(R(P,{get data(){return o()},get form(){return e.form},get params(){return e.page.params}}),d=>s()[0]=d,()=>{var d;return(d=s())==null?void 0:d[0]})}),w(n,E)};D(i,n=>{e.constructors[1]?n(v):n(b,-1)})}var z=se(i,2);{var h=n=>{var f=ye(),E=ne(f);{var x=P=>{var R=me();ce(()=>fe(R,g(r))),w(P,R)};D(E,P=>{g(a)&&P(x)})}oe(f),w(n,f)};D(z,n=>{g(_)&&n(h)})}w(t,k),ae()}const Ce=he(Ee),Ae=[()=>U(()=>import("../nodes/0.DecrOZwe.js"),__vite__mapDeps([0,1,2,3,4]),import.meta.url),()=>U(()=>import("../nodes/1.CCoIP03N.js"),__vite__mapDeps([5,1,2,6,7,8,9]),import.meta.url),()=>U(()=>import("../nodes/2.Db1dJWID.js"),__vite__mapDeps([10,1,2,9,11,4,7,3,6,12]),import.meta.url)],Be=[],Ie={"/":[2]},Y={handleError:(({error:t})=>{console.error(t)}),reroute:(()=>{}),transport:{}},Pe=Object.fromEntries(Object.entries(Y.transport).map(([t,e])=>[t,e.decode])),Te=Object.fromEntries(Object.entries(Y.transport).map(([t,e])=>[t,e.encode])),De=!1,Ve=(t,e)=>Pe[t](e);export{Ve as decode,Pe as decoders,Ie as dictionary,Te as encoders,De as hash,Y as hooks,Se as matchers,Ae as nodes,Ce as root,Be as server_loads}; diff --git a/frontend/dist/_app/immutable/entry/start.C6epiIK6.js b/frontend/dist/_app/immutable/entry/start.C6epiIK6.js new file mode 100644 index 0000000..754951f --- /dev/null +++ b/frontend/dist/_app/immutable/entry/start.C6epiIK6.js @@ -0,0 +1 @@ +import{l as o,a as r}from"../chunks/5xxJZag6.js";export{o as load_css,r as start}; diff --git a/frontend/dist/_app/immutable/entry/start.Ca27w5R7.js b/frontend/dist/_app/immutable/entry/start.Ca27w5R7.js deleted file mode 100644 index 2f7d149..0000000 --- a/frontend/dist/_app/immutable/entry/start.Ca27w5R7.js +++ /dev/null @@ -1 +0,0 @@ -import{l as o,a as r}from"../chunks/BI00lc_u.js";export{o as load_css,r as start}; diff --git a/frontend/dist/_app/immutable/nodes/1.DU6Ijmpv.js b/frontend/dist/_app/immutable/nodes/1.CCoIP03N.js similarity index 78% rename from frontend/dist/_app/immutable/nodes/1.DU6Ijmpv.js rename to frontend/dist/_app/immutable/nodes/1.CCoIP03N.js index 6885a46..4a0947a 100644 --- a/frontend/dist/_app/immutable/nodes/1.DU6Ijmpv.js +++ b/frontend/dist/_app/immutable/nodes/1.CCoIP03N.js @@ -1 +1 @@ -import{a as g,f as h}from"../chunks/C14P8jaG.js";import{i as l}from"../chunks/DmjnTBOd.js";import{x as v,g as d,y as x,z as _,A as e,B as o,C as $}from"../chunks/VJhVTb1A.js";import{s as p}from"../chunks/BeON-0X8.js";import{s as k,p as m}from"../chunks/BI00lc_u.js";const b={get error(){return m.error},get status(){return m.status}};k.updated.check;const i=b;var y=h("

",1);function j(f,n){v(n,!1),l();var t=y(),r=d(t),c=e(r,!0);o(r);var s=$(r,2),u=e(s,!0);o(s),x(()=>{var a;p(c,i.status),p(u,(a=i.error)==null?void 0:a.message)}),g(f,t),_()}export{j as component}; +import{a as g,f as h}from"../chunks/C14P8jaG.js";import{i as l}from"../chunks/DmjnTBOd.js";import{x as v,g as d,y as x,z as _,A as e,B as o,C as $}from"../chunks/VJhVTb1A.js";import{s as p}from"../chunks/BeON-0X8.js";import{s as k,p as m}from"../chunks/5xxJZag6.js";const b={get error(){return m.error},get status(){return m.status}};k.updated.check;const i=b;var y=h("

",1);function j(f,n){v(n,!1),l();var t=y(),r=d(t),c=e(r,!0);o(r);var s=$(r,2),u=e(s,!0);o(s),x(()=>{var a;p(c,i.status),p(u,(a=i.error)==null?void 0:a.message)}),g(f,t),_()}export{j as component}; diff --git a/frontend/dist/_app/immutable/nodes/2.DQxkgq4x.js b/frontend/dist/_app/immutable/nodes/2.DQxkgq4x.js deleted file mode 100644 index 763b315..0000000 --- a/frontend/dist/_app/immutable/nodes/2.DQxkgq4x.js +++ /dev/null @@ -1,1190 +0,0 @@ -import{b as Pr,a as u,f as S,c as be,t as Ve,d as Ot}from"../chunks/C14P8jaG.js";import{o as Zn,a as vr}from"../chunks/Cz6m6K8p.js";import{h as De,L as $t,G as Lt,c as Er,J as Kt,b as Jn,o as e,N as ja,ag as Va,O as En,P as zt,f as _t,ay as ea,aF as Ua,ao as Rn,e as mt,a as dr,aM as Ba,s as qa,a5 as Ka,aN as ta,aE as dn,aO as Ga,a7 as Nn,aP as Wa,aQ as Xa,aI as Ya,aR as ht,aS as ra,a1 as Qa,r as na,p as aa,aT as Ur,ae as sa,aU as Za,aV as Ja,aC as es,m as ts,d as Nr,az as oa,y as J,F as ia,aW as rs,aG as ns,aA as as,D as la,aX as ca,aY as ss,aZ as os,E as is,aw as ua,S as Dr,k as Mr,a_ as da,a$ as ls,b0 as cs,b1 as fa,b2 as us,b3 as ds,b4 as gr,b5 as fs,b6 as vs,b7 as gs,b8 as hs,b9 as ps,ba as ms,bb as _s,aJ as Pt,U as bs,aK as z,_ as ie,$ as f,A as k,B as p,x as Me,g as K,z as Ae,C as $,j as Re,ap as ke,aL as j,bc as xs,q as Vt,bd as ys,be as it,bf as ws}from"../chunks/VJhVTb1A.js";import{p as me,i as Q,b as nt,c as ks,l as Oe,s as Ue}from"../chunks/zf4GFcxs.js";import{i as Cs,a as Ss,d as le,b as Ke,c as $s,n as Ps,e as Es,s as ne,f as tt}from"../chunks/BeON-0X8.js";import{s as ft}from"../chunks/CiUbu-1F.js";import{i as Rs}from"../chunks/DmjnTBOd.js";import{B as Ns}from"../chunks/U5yjBaoU.js";function fn(t,r){return r}function Ms(t,r,a){for(var n=[],s=r.length,i,o=r.length,l=0;l{if(i){if(i.pending.delete(A),i.done.add(A),i.pending.size===0){var m=t.outrogroups;Qr(t,dn(i.done)),m.delete(i),m.size===0&&(t.outrogroups=null)}}else o-=1},!1)}if(o===0){var d=n.length===0&&a!==null;if(d){var b=a,x=b.parentNode;es(x),x.append(b),t.items.clear()}Qr(t,r,!d)}else i={pending:new Set(r),done:new Set},(t.outrogroups??(t.outrogroups=new Set)).add(i)}function Qr(t,r,a=!0){var n;if(t.pending.size>0){n=new Set;for(const o of t.pending.values())for(const l of o)n.add(t.items.get(l).e)}for(var s=0;s{var h=a();return ta(h)?h:h==null?[]:dn(h)}),m,N=new Map,L=!0;function g(h){(P.effect.f&Qa)===0&&(P.pending.delete(h),P.fallback=x,As(P,m,o,r,n),x!==null&&(m.length===0?(x.f&ht)===0?na(x):(x.f^=ht,lr(x,null,o)):aa(x,()=>{x=null})))}function c(h){P.pending.delete(h)}var w=Jn(()=>{m=e(A);var h=m.length;let y=!1;if(De){var C=ja(o)===Va;C!==(h===0)&&(o=En(),$t(o),zt(!1),y=!0)}for(var _=new Set,M=mt,F=qa(),T=0;Ti(o)):(x=dr(()=>i(Mn??(Mn=Er()))),x.f|=ht)),h>_.size&&Ba(),De&&h>0&&$t(En()),!L)if(N.set(M,_),F){for(const[V,G]of l)_.has(V)||M.skip_effect(G.e);M.oncommit(g),M.ondiscard(c)}else g(M);y&&zt(!0),e(A)}),P={effect:w,items:l,pending:N,outrogroups:null,fallback:x};L=!1,De&&(o=_t)}function ar(t){for(;t!==null&&(t.f&Za)===0;)t=t.next;return t}function As(t,r,a,n,s){var q,I,O,V,G,Z,ae,E,R;var i=(n&Ja)!==0,o=r.length,l=t.items,d=ar(t.effect.first),b,x=null,A,m=[],N=[],L,g,c,w;if(i)for(w=0;w0){var T=(n&ra)!==0&&o===0?a:null;if(i){for(w=0;w{var H,D;if(A!==void 0)for(c of A)(D=(H=c.nodes)==null?void 0:H.a)==null||D.apply()})}function Ls(t,r,a,n,s,i,o,l){var d=(o&Wa)!==0?(o&Xa)===0?Ya(a,!1,!1):Nn(a):null,b=(o&Ga)!==0?Nn(s):null;return{v:d,i:b,e:dr(()=>(i(r,d??a,b??s,l),()=>{t.delete(n)}))}}function lr(t,r,a){if(t.nodes)for(var n=t.nodes.start,s=t.nodes.end,i=r&&(r.f&ht)===0?r.nodes.start:a;n!==null;){var o=oa(n);if(i.before(n),n===s)return;n=o}}function wt(t,r,a){r===null?t.effect.first=a:r.next=a,a===null?t.effect.last=r:a.prev=r}function zr(t,r,a=!1,n=!1,s=!1,i=!1){var o=t,l="";if(a){var d=t;De&&(o=$t(Lt(d)))}J(()=>{var b=ia;if(l===(l=r()??"")){De&&Kt();return}if(a&&!De){b.nodes=null,d.innerHTML=l,l!==""&&Pr(Lt(d),d.lastChild);return}if(b.nodes!==null&&(rs(b.nodes.start,b.nodes.end),b.nodes=null),l!==""){if(De){_t.data;for(var x=Kt(),A=x;x!==null&&(x.nodeType!==ea||x.data!=="");)A=x,x=oa(x);if(x===null)throw ns(),as;Pr(_t,A),o=$t(x);return}var m=n?ca:s?ss:void 0,N=la(n?"svg":s?"math":"template",m);N.innerHTML=l;var L=n||s?N:N.content;if(Pr(Lt(L),L.lastChild),n||s)for(;Lt(L);)o.before(Lt(L));else o.before(L)}})}function He(t,r,a,n,s){var l;De&&Kt();var i=(l=r.$$slots)==null?void 0:l[a],o=!1;i===!0&&(i=r.children,o=!0),i===void 0||i(t,o?()=>n:n)}function Ts(t,r,a,n,s,i){let o=De;De&&Kt();var l=null;De&&_t.nodeType===os&&(l=_t,Kt());var d=De?_t:t,b=new Ns(d,!1);Jn(()=>{const x=r()||null;var A=ca;if(x===null){b.ensure(null,null);return}return b.ensure(x,m=>{if(x){if(l=De?l:la(x,A),Pr(l,l),n){De&&Cs(x)&&l.append(document.createComment(""));var N=De?Lt(l):l.appendChild(Er());De&&(N===null?zt(!1):$t(N)),n(l,N)}ia.nodes.end=l,m.before(l)}De&&$t(m)}),()=>{}},is),ua(()=>{}),o&&(zt(!0),$t(d))}function Fs(t,r,a){Dr(()=>{var n=Mr(()=>r(t,a==null?void 0:a())||{});if(n!=null&&n.destroy)return()=>n.destroy()})}function Ds(t,r){var a=void 0,n;da(()=>{a!==(a=r())&&(n&&(Nr(n),n=null),a&&(n=dr(()=>{Dr(()=>a(t))})))})}function va(t){var r,a,n="";if(typeof t=="string"||typeof t=="number")n+=t;else if(typeof t=="object")if(Array.isArray(t)){var s=t.length;for(r=0;r=0;){var l=o+i;(o===0||An.includes(n[o-1]))&&(l===n.length||An.includes(n[l]))?n=(o===0?"":n.substring(0,o))+n.substring(l+1):o=l}}return n===""?null:n}function Ln(t,r=!1){var a=r?" !important;":";",n="";for(var s of Object.keys(t)){var i=t[s];i!=null&&i!==""&&(n+=" "+s+": "+i+a)}return n}function Br(t){return t[0]!=="-"||t[1]!=="-"?t.toLowerCase():t}function Os(t,r){if(r){var a="",n,s;if(Array.isArray(r)?(n=r[0],s=r[1]):n=r,t){t=String(t).replaceAll(/\s*\/\*.*?\*\/\s*/g,"").trim();var i=!1,o=0,l=!1,d=[];n&&d.push(...Object.keys(n).map(Br)),s&&d.push(...Object.keys(s).map(Br));var b=0,x=-1;const g=t.length;for(var A=0;A{Ar(t,t.__value)});r.observe(t,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["value"]}),ua(()=>{r.disconnect()})}function Hs(t,r,a=r){var n=new WeakSet,s=!0;fa(t,"change",i=>{var o=i?"[selected]":":checked",l;if(t.multiple)l=[].map.call(t.querySelectorAll(o),ur);else{var d=t.querySelector(o)??t.querySelector("option:not([disabled])");l=d&&ur(d)}a(l),t.__value=l,mt!==null&&n.add(mt)}),Dr(()=>{var i=r();if(t===document.activeElement){var o=mt;if(n.has(o))return}if(Ar(t,i,s),s&&i===void 0){var l=t.querySelector(":checked");l!==null&&(i=ur(l),a(i))}t.__value=i,s=!1}),ga(t)}function ur(t){return"__value"in t?t.__value:t.value}const sr=Symbol("class"),or=Symbol("style"),ha=Symbol("is custom element"),pa=Symbol("is html"),js=gr?"link":"LINK",Vs=gr?"input":"INPUT",Us=gr?"option":"OPTION",Bs=gr?"select":"SELECT",qs=gr?"progress":"PROGRESS";function Xe(t){if(De){var r=!1,a=()=>{if(!r){if(r=!0,t.hasAttribute("value")){var n=t.value;we(t,"value",null),t.value=n}if(t.hasAttribute("checked")){var s=t.checked;we(t,"checked",null),t.checked=s}}};t.__on_r=a,sa(a),gs()}}function vn(t,r){var a=gn(t);a.value===(a.value=r??void 0)||t.value===r&&(r!==0||t.nodeName!==qs)||(t.value=r??"")}function Ks(t,r){r?t.hasAttribute("selected")||t.setAttribute("selected",""):t.removeAttribute("selected")}function we(t,r,a,n){var s=gn(t);De&&(s[r]=t.getAttribute(r),r==="src"||r==="srcset"||r==="href"&&t.nodeName===js)||s[r]!==(s[r]=a)&&(r==="loading"&&(t[ms]=a),a==null?t.removeAttribute(r):typeof a!="string"&&ma(t).includes(r)?t[r]=a:t.setAttribute(r,a))}function Gs(t,r,a,n,s=!1,i=!1){if(De&&s&&t.nodeName===Vs){var o=t,l=o.type==="checkbox"?"defaultChecked":"defaultValue";l in a||Xe(o)}var d=gn(t),b=d[ha],x=!d[pa];let A=De&&b;A&&zt(!1);var m=r||{},N=t.nodeName===Us;for(var L in r)L in a||(a[L]=null);a.class?a.class=dt(a.class):a[sr]&&(a.class=null),a[or]&&(a.style??(a.style=null));var g=ma(t);for(const _ in a){let M=a[_];if(N&&_==="value"&&M==null){t.value=t.__value="",m[_]=M;continue}if(_==="class"){var c=t.namespaceURI==="http://www.w3.org/1999/xhtml";Se(t,c,M,n,r==null?void 0:r[sr],a[sr]),m[_]=M,m[sr]=a[sr];continue}if(_==="style"){Ze(t,M,r==null?void 0:r[or],a[or]),m[_]=M,m[or]=a[or];continue}var w=m[_];if(!(M===w&&!(M===void 0&&t.hasAttribute(_)))){m[_]=M;var P=_[0]+_[1];if(P!=="$$")if(P==="on"){const F={},T="$$"+_;let q=_.slice(2);var h=Es(q);if(Ss(q)&&(q=q.slice(0,-7),F.capture=!0),!h&&w){if(M!=null)continue;t.removeEventListener(q,m[T],F),m[T]=null}if(h)le(q,t,M),Ke([q]);else if(M!=null){let I=function(O){m[_].call(this,O)};m[T]=$s(q,t,I,F)}}else if(_==="style")we(t,_,M);else if(_==="autofocus")fs(t,!!M);else if(!b&&(_==="__value"||_==="value"&&M!=null))t.value=t.__value=M;else if(_==="selected"&&N)Ks(t,M);else{var y=_;x||(y=Ps(y));var C=y==="defaultValue"||y==="defaultChecked";if(M==null&&!b&&!C)if(d[_]=null,y==="value"||y==="checked"){let F=t;const T=r===void 0;if(y==="value"){let q=F.defaultValue;F.removeAttribute(y),F.defaultValue=q,F.value=F.__value=T?q:null}else{let q=F.defaultChecked;F.removeAttribute(y),F.defaultChecked=q,F.checked=T?q:!1}}else t.removeAttribute(_);else C||g.includes(y)&&(b||typeof M!="string")?(t[y]=M,y in d&&(d[y]=vs)):typeof M!="function"&&we(t,y,M)}}}return A&&zt(!0),m}function Tn(t,r,a=[],n=[],s=[],i,o=!1,l=!1){us(s,a,n,d=>{var b=void 0,x={},A=t.nodeName===Bs,m=!1;if(da(()=>{var L=r(...d.map(e)),g=Gs(t,b,L,i,o,l);m&&A&&"value"in L&&Ar(t,L.value);for(let w of Object.getOwnPropertySymbols(x))L[w]||Nr(x[w]);for(let w of Object.getOwnPropertySymbols(L)){var c=L[w];w.description===ds&&(!b||c!==b[w])&&(x[w]&&Nr(x[w]),x[w]=dr(()=>Ds(t,()=>c))),g[w]=c}b=g}),A){var N=t;Dr(()=>{Ar(N,b.value,!0),ga(N)})}m=!0})}function gn(t){return t.__attributes??(t.__attributes={[ha]:t.nodeName.includes("-"),[pa]:t.namespaceURI===hs})}var Fn=new Map;function ma(t){var r=t.getAttribute("is")||t.nodeName,a=Fn.get(r);if(a)return a;Fn.set(r,a=[]);for(var n,s=t,i=Element.prototype;i!==s;){n=_s(s);for(var o in n)n[o].set&&a.push(o);s=ps(s)}return a}function et(t,r,a=r){var n=new WeakSet;fa(t,"input",async s=>{var i=s?t.defaultValue:t.value;if(i=Kr(t)?Gr(i):i,a(i),mt!==null&&n.add(mt),await Pt(),i!==(i=r())){var o=t.selectionStart,l=t.selectionEnd,d=t.value.length;if(t.value=i??"",l!==null){var b=t.value.length;o===l&&l===d&&b>d?(t.selectionStart=b,t.selectionEnd=b):(t.selectionStart=o,t.selectionEnd=Math.min(l,b))}}}),(De&&t.defaultValue!==t.value||Mr(r)==null&&t.value)&&(a(Kr(t)?Gr(t.value):t.value),mt!==null&&n.add(mt)),bs(()=>{var s=r();if(t===document.activeElement){var i=mt;if(n.has(i))return}Kr(t)&&s===Gr(t.value)||t.type==="date"&&!s&&!t.value||s!==t.value&&(t.value=s??"")})}function Kr(t){var r=t.type;return r==="number"||r==="range"}function Gr(t){return t===""?null:+t}let xt=z(ie({type:"none",props:{},resolve:null}));function Ws(){return e(xt)}function ot(t=void 0){const{resolve:r}=e(xt);f(xt,{type:"none",props:{},resolve:null},!0),r==null||r(t)}function Xs(t){return new Promise(r=>{f(xt,{type:"run",props:{cases:t},resolve:r},!0)})}function Ys(t){return new Promise(r=>{f(xt,{type:"restart",props:{cases:t},resolve:r},!0)})}function Qs(t){return new Promise(r=>{f(xt,{type:"clean",props:{cases:t},resolve:r},!0)})}function We(t,r="Alert"){return new Promise(a=>{f(xt,{type:"alert",props:{title:r,message:t},resolve:()=>a()},!0)})}function Dn(t,r="Confirm",a="OK",n="primary"){return new Promise(s=>{f(xt,{type:"confirm",props:{title:r,message:t,confirmLabel:a,confirmVariant:n},resolve:s},!0)})}function hn(t,r="",a="Input",n="",s=!1){return new Promise(i=>{f(xt,{type:"prompt",props:{title:a,message:t,value:r,placeholder:n,multiline:s},resolve:i},!0)})}var Zs=S('
');function Jt(t,r){function a(i){return document.body.appendChild(i),{destroy(){i.remove()}}}var n=Zs(),s=k(n);ft(s,()=>r.children),p(n),Fs(n,i=>a==null?void 0:a(i)),u(t,n)}var Js=S('
'),eo=S('
');function hr(t,r){Me(r,!0);const a=L=>{var g=eo(),c=k(g),w=k(c),P=k(w),h=k(P,!0);p(P);var y=$(P,2);{var C=T=>{var q=Js(),I=k(q,!0);p(q),J(()=>ne(I,r.subtitle)),u(T,q)};Q(y,T=>{r.subtitle&&T(C)})}p(w);var _=$(w,2);ft(_,()=>r.children);var M=$(_,2),F=k(M);ft(F,()=>r.footer),p(M),p(c),p(g),J(()=>{Se(c,1,`w-[min(${n()??""},96vw)] bg-white border border-border rounded-[10px] p-4.5`),we(c,"aria-labelledby",r.titleId),we(P,"id",r.titleId),ne(h,r.title)}),le("keydown",g,o),le("mousedown",g,d),le("click",g,b),u(L,g)};let n=me(r,"maxWidth",3,"520px"),s=me(r,"portal",3,!1);function i(){r.onCancel?r.onCancel():ot(null)}function o(L){L.key==="Escape"?i():L.key==="Enter"&&r.onConfirm&&r.onConfirm()}let l=!1;function d(L){l=L.target.dataset.backdrop!==void 0}function b(L){const g=L.target.dataset.backdrop!==void 0;l&&g&&i(),l=!1}var x=be(),A=K(x);{var m=L=>{Jt(L,{children:(g,c)=>{a(g)}})},N=L=>{a(L)};Q(A,L=>{s()?L(m):L(N,-1)})}u(t,x),Ae()}Ke(["keydown","mousedown","click"]);var to=S("");function $e(t,r){let a=me(r,"variant",3,"primary"),n=me(r,"size",3,"default"),s=me(r,"disabled",3,!1);const i={default:"h-[34px] rounded-md px-3.5 leading-none text-[13px] tracking-wide",sm:"h-[30px] rounded-md px-2.5 leading-none text-xs"},o={primary:"bg-edf-bleu-moyen text-white border-none hover:bg-[rgb(12,72,170)]",secondary:"bg-white text-ink border border-border hover:bg-edf-gris-clair",run:"bg-edf-vert-fonce text-white border-none hover:bg-[rgb(38,98,12)]",warning:"bg-edf-orange-moyen text-white border-none hover:bg-[rgb(230,118,20)]",danger:"bg-edf-orange-fonce text-white border-none hover:bg-[rgb(180,56,8)]"};var l=to(),d=k(l);ft(d,()=>r.children),p(l),J(()=>{Se(l,1,`inline-flex items-center justify-center gap-1.5 font-bold cursor-pointer transition-colors duration-150 disabled:opacity-40 disabled:pointer-events-none ${i[n()]??""} ${o[a()]??""}`),l.disabled=s()}),le("click",l,function(...b){var x;(x=r.onclick)==null||x.apply(this,b)}),u(t,l)}Ke(["click"]);var ro=S(" ",1),no=S(''),ao=S(''),so=S('
'),oo=S('

',1);function io(t,r){Me(r,!0);let a=me(r,"confirmLabel",3,"OK"),n=me(r,"confirmVariant",3,"primary"),s=me(r,"value",3,""),i=me(r,"placeholder",3,""),o=me(r,"multiline",3,!1);const l=s();let d=z(ie(l)),b=z(void 0),x=z(void 0);Re(()=>{var N;r.mode==="prompt"&&e(b)?(e(b).focus(),"select"in e(b)&&e(b).select()):e(x)&&((N=e(x).querySelector("button"))==null||N.focus())});function A(){r.mode==="prompt"?ot(e(d)):r.mode==="confirm"?ot(!0):ot()}function m(){r.mode==="confirm"?ot(!1):r.mode==="prompt"?ot(null):ot()}{const N=g=>{var c=ro(),w=K(c);{var P=C=>{$e(C,{variant:"secondary",onclick:m,children:(_,M)=>{ke();var F=Ve("Cancel");u(_,F)},$$slots:{default:!0}})};Q(w,C=>{r.mode!=="alert"&&C(P)})}var h=$(w,2),y=k(h);$e(y,{get variant(){return n()},onclick:A,children:(C,_)=>{ke();var M=Ve();J(()=>ne(M,a())),u(C,M)},$$slots:{default:!0}}),p(h),nt(h,C=>f(x,C),()=>e(x)),u(g,c)};let L=j(()=>o()?void 0:A);hr(t,{get title(){return r.title},titleId:"app-dialog-title",get onConfirm(){return e(L)},onCancel:m,footer:N,children:(g,c)=>{var w=oo(),P=K(w),h=k(P,!0);p(P);var y=$(P,2);{var C=_=>{var M=so(),F=k(M);{var T=I=>{var O=no();xs(O),nt(O,V=>f(b,V),()=>e(b)),J(()=>we(O,"placeholder",i())),et(O,()=>e(d),V=>f(d,V)),u(I,O)},q=I=>{var O=ao();Xe(O),nt(O,V=>f(b,V),()=>e(b)),J(()=>we(O,"placeholder",i())),et(O,()=>e(d),V=>f(d,V)),u(I,O)};Q(F,I=>{o()?I(T):I(q,-1)})}p(M),u(_,M)};Q(y,_=>{r.mode==="prompt"&&_(C)})}J(()=>ne(h,r.message)),u(g,w)},$$slots:{footer:!0,default:!0}})}Ae()}var lo=S('');function Ne(t,r){var a=lo(),n=k(a),s=$(n);ft(s,()=>r.children),p(a),J(()=>ne(n,`${r.text??""} `)),u(t,a)}function Ct(t,r){const a=localStorage.getItem(t);if(a===null)return r;const n=Number(a);return Number.isFinite(n)?n:r}function St(t,r){Number.isFinite(r)&&localStorage.setItem(t,String(r))}function co(t,r){return localStorage.getItem(t)??r}function uo(){return{n:Ct("csauto_run_n",1),nt:Ct("csauto_run_nt",1),maxParallel:Ct("csauto_run_max_parallel",0)||null}}function fo(t){St("csauto_run_n",t.n),St("csauto_run_nt",t.nt),t.maxParallel&&St("csauto_run_max_parallel",t.maxParallel)}function vo(){return{n:Ct("csauto_restart_n",1),nt:Ct("csauto_restart_nt",1),maxParallel:Ct("csauto_restart_max_parallel",0)||null,mode:co("csauto_restart_mode","iterations"),value:Ct("csauto_restart_value",100)}}function go(t){St("csauto_restart_n",t.n),St("csauto_restart_nt",t.nt),t.maxParallel&&St("csauto_restart_max_parallel",t.maxParallel),localStorage.setItem("csauto_restart_mode",t.mode),St("csauto_restart_value",t.value)}function ho(){return{keepLast:Ct("csauto_clean_keep_last",1)}}function po(t){St("csauto_clean_keep_last",t.keepLast)}var mo=S(" ",1),_o=S(''),bo=S(''),xo=S(''),yo=S('
');function wo(t,r){Me(r,!0);const a=uo(),n=a.maxParallel??(r.cases.length>1?r.cases.length:0);let s=z(ie(a.n)),i=z(ie(a.nt)),o=z(ie(n));async function l(){if(!Number.isFinite(e(s))||e(s)<=0){await We("MPI ranks must be an integer > 0.","Invalid value");return}if(!Number.isFinite(e(i))||e(i)<=0){await We("Threads must be an integer > 0.","Invalid value");return}if(e(o)&&(!Number.isFinite(e(o))||e(o)<=0)){await We("Max parallel must be empty or > 0.","Invalid value");return}const d={n:e(s),nt:e(i),maxParallel:e(o)||null};fo(d),ot(d)}{const d=A=>{var m=mo(),N=K(m);$e(N,{variant:"secondary",onclick:()=>ot(null),children:(g,c)=>{ke();var w=Ve("Cancel");u(g,w)},$$slots:{default:!0}});var L=$(N,2);$e(L,{variant:"run",onclick:l,children:(g,c)=>{ke();var w=Ve("Run");u(g,w)},$$slots:{default:!0}}),u(A,m)};let b=j(()=>r.cases.length),x=j(()=>r.cases.length>1?"s":"");hr(t,{title:"Run Cases",titleId:"run-dialog-title",get subtitle(){return`${e(b)??""} case${e(x)??""} selected`},onConfirm:l,footer:d,children:(A,m)=>{var N=yo(),L=k(N);Ne(L,{text:"MPI Ranks (n)",children:(w,P)=>{var h=_o();Xe(h),et(h,()=>e(s),y=>f(s,y)),u(w,h)}});var g=$(L,2);Ne(g,{text:"OMP Threads (nt)",children:(w,P)=>{var h=bo();Xe(h),et(h,()=>e(i),y=>f(i,y)),u(w,h)}});var c=$(g,2);Ne(c,{text:"Max Parallel",children:(w,P)=>{var h=xo();Xe(h),et(h,()=>e(o),y=>f(o,y)),u(w,h)}}),p(N),u(A,N)},$$slots:{footer:!0,default:!0}})}Ae()}var ko=S(" ",1),Co=S(''),So=S(''),$o=S(''),Po=S(""),Eo=S(''),Ro=S('
',1);function No(t,r){Me(r,!0);const a=vo(),n=a.maxParallel??(r.cases.length>1?r.cases.length:0);let s=z(ie(a.n)),i=z(ie(a.nt)),o=z(ie(n)),l=z(ie(a.mode)),d=z(ie(a.value)),b=j(()=>e(l)==="iterations"?"Additional iterations":"Additional physical time"),x=j(()=>e(l)==="iterations"?"1":"any"),A=j(()=>e(l)==="iterations"?"1":"0");async function m(){if(!Number.isFinite(e(s))||e(s)<=0){await We("MPI ranks must be an integer > 0.","Invalid value");return}if(!Number.isFinite(e(i))||e(i)<=0){await We("Threads must be an integer > 0.","Invalid value");return}if(e(o)&&(!Number.isFinite(e(o))||e(o)<=0)){await We("Max parallel must be empty or > 0.","Invalid value");return}if(!Number.isFinite(e(d))||e(d)<=0){await We("Value must be > 0.","Invalid value");return}if(e(l)==="iterations"&&!Number.isInteger(e(d))){await We("Iterations must be an integer.","Invalid value");return}const N={n:e(s),nt:e(i),maxParallel:e(o)||null,restartMode:e(l),restartValue:e(d)};go({n:e(s),nt:e(i),maxParallel:e(o)||null,mode:e(l),value:e(d)}),ot(N)}{const N=c=>{var w=ko(),P=K(w);$e(P,{variant:"secondary",onclick:()=>ot(null),children:(y,C)=>{ke();var _=Ve("Cancel");u(y,_)},$$slots:{default:!0}});var h=$(P,2);$e(h,{variant:"warning",onclick:m,children:(y,C)=>{ke();var _=Ve("Restart");u(y,_)},$$slots:{default:!0}}),u(c,w)};let L=j(()=>r.cases.length),g=j(()=>r.cases.length>1?"s":"");hr(t,{title:"Restart Cases",titleId:"restart-dialog-title",get subtitle(){return`${e(L)??""} case${e(g)??""} selected`},onConfirm:m,footer:N,children:(c,w)=>{var P=Ro(),h=K(P),y=k(h);Ne(y,{text:"MPI Ranks (n)",children:(q,I)=>{var O=Co();Xe(O),et(O,()=>e(s),V=>f(s,V)),u(q,O)}});var C=$(y,2);Ne(C,{text:"OMP Threads (nt)",children:(q,I)=>{var O=So();Xe(O),et(O,()=>e(i),V=>f(i,V)),u(q,O)}});var _=$(C,2);Ne(_,{text:"Max Parallel",children:(q,I)=>{var O=$o();Xe(O),et(O,()=>e(o),V=>f(o,V)),u(q,O)}}),p(h);var M=$(h,2),F=k(M);Ne(F,{text:"Stop criterion",children:(q,I)=>{var O=Po(),V=k(O);V.value=V.__value="iterations";var G=$(V);G.value=G.__value="physical_time",p(O),Hs(O,()=>e(l),Z=>f(l,Z)),u(q,O)}});var T=$(F,2);Ne(T,{get text(){return e(b)},children:(q,I)=>{var O=Eo();Xe(O),J(()=>{we(O,"min",e(A)),we(O,"step",e(x))}),et(O,()=>e(d),V=>f(d,V)),u(q,O)}}),p(M),u(c,P)},$$slots:{footer:!0,default:!0}})}Ae()}function Te(t,r){let a=me(r,"size",3,14);var n=be(),s=K(n);ks(s,()=>r.icon,(i,o)=>{o(i,{get size(){return a()},class:"icon"})}),u(t,n)}/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const Mo={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"};/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const Ao=t=>{for(const r in t)if(r.startsWith("aria-")||r==="role"||r==="title")return!0;return!1};/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const zn=(...t)=>t.filter((r,a,n)=>!!r&&r.trim()!==""&&n.indexOf(r)===a).join(" ").trim();var Lo=Ot("");function Be(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]),n=Oe(a,["name","color","size","strokeWidth","absoluteStrokeWidth","iconNode"]);Me(r,!1);let s=me(r,"name",8,void 0),i=me(r,"color",8,"currentColor"),o=me(r,"size",8,24),l=me(r,"strokeWidth",8,2),d=me(r,"absoluteStrokeWidth",8,!1),b=me(r,"iconNode",24,()=>[]);Rs();var x=Lo();Tn(x,(N,L,g)=>({...Mo,...N,...n,width:o(),height:o(),stroke:i(),"stroke-width":L,class:g}),[()=>Ao(n)?void 0:{"aria-hidden":"true"},()=>(Vt(d()),Vt(l()),Vt(o()),Mr(()=>d()?Number(l())*24/Number(o()):l())),()=>(Vt(zn),Vt(s()),Vt(a),Mr(()=>zn("lucide-icon","lucide",s()?`lucide-${s()}`:"",a.class)))]);var A=k(x);Ye(A,1,b,fn,(N,L)=>{var g=j(()=>ys(e(L),2));let c=()=>e(g)[0],w=()=>e(g)[1];var P=be(),h=K(P);Ts(h,c,!0,(y,C)=>{Tn(y,()=>({...w()}))}),u(N,P)});var m=$(A);He(m,r,"default",{}),p(x),u(t,x),Ae()}function To(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M12 5v14"}],["path",{d:"m19 12-7 7-7-7"}]];Be(t,Ue({name:"arrow-down"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Fo(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M8 3 4 7l4 4"}],["path",{d:"M4 7h16"}],["path",{d:"m16 21 4-4-4-4"}],["path",{d:"M20 17H4"}]];Be(t,Ue({name:"arrow-left-right"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Do(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"m5 12 7-7 7 7"}],["path",{d:"M12 19V5"}]];Be(t,Ue({name:"arrow-up"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function pn(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"m6 9 6 6 6-6"}]];Be(t,Ue({name:"chevron-down"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function zo(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"m9 18 6-6-6-6"}]];Be(t,Ue({name:"chevron-right"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Io(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["circle",{cx:"12",cy:"12",r:"10"}],["rect",{x:"9",y:"9",width:"6",height:"6",rx:"1"}]];Be(t,Ue({name:"circle-stop"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Oo(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m15 9-6 6"}],["path",{d:"m9 9 6 6"}]];Be(t,Ue({name:"circle-x"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function pr(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M12 15V3"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"}],["path",{d:"m7 10 5 5 5-5"}]];Be(t,Ue({name:"download"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Ho(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M7 16.3c2.2 0 4-1.83 4-4.05 0-1.16-.57-2.26-1.71-3.19S7.29 6.75 7 5.3c-.29 1.45-1.14 2.84-2.29 3.76S3 11.1 3 12.25c0 2.22 1.8 4.05 4 4.05z"}],["path",{d:"M12.56 6.6A10.97 10.97 0 0 0 14 3.02c.5 2.5 2 4.9 4 6.5s3 3.5 3 5.5a6.98 6.98 0 0 1-11.91 4.97"}]];Be(t,Ue({name:"droplets"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function jo(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["circle",{cx:"12",cy:"12",r:"1"}],["circle",{cx:"19",cy:"12",r:"1"}],["circle",{cx:"5",cy:"12",r:"1"}]];Be(t,Ue({name:"ellipsis"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Vo(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M15 3h6v6"}],["path",{d:"M10 14 21 3"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"}]];Be(t,Ue({name:"external-link"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Uo(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M12 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 12 18z"}],["path",{d:"M2 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 2 18z"}]];Be(t,Ue({name:"fast-forward"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Bo(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["rect",{x:"14",y:"3",width:"5",height:"18",rx:"1"}],["rect",{x:"5",y:"3",width:"5",height:"18",rx:"1"}]];Be(t,Ue({name:"pause"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function qo(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M13 21h8"}],["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"}]];Be(t,Ue({name:"pen-line"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Ko(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"}],["path",{d:"m15 5 4 4"}]];Be(t,Ue({name:"pencil"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function _a(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"}]];Be(t,Ue({name:"play"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Go(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M5 12h14"}],["path",{d:"M12 5v14"}]];Be(t,Ue({name:"plus"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function er(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"}],["path",{d:"M21 3v5h-5"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"}],["path",{d:"M8 16H3v5"}]];Be(t,Ue({name:"refresh-cw"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Wo(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"}],["path",{d:"M3 3v5h5"}]];Be(t,Ue({name:"rotate-ccw"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Xo(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"}],["path",{d:"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7"}],["path",{d:"M7 3v4a1 1 0 0 0 1 1h7"}]];Be(t,Ue({name:"save"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Yo(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"}],["circle",{cx:"12",cy:"12",r:"3"}]];Be(t,Ue({name:"settings"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Qo(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** - * @license lucide-svelte v1.0.1 - ISC - * - * ISC License - * - * Copyright (c) 2026 Lucide Icons and Contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * --- - * - * The following Lucide icons are derived from the Feather project: - * - * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out - * - * The MIT License (MIT) (for the icons listed above) - * - * Copyright (c) 2013-present Cole Bemis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */const n=[["path",{d:"M10 11v6"}],["path",{d:"M14 11v6"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"}],["path",{d:"M3 6h18"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"}]];Be(t,Ue({name:"trash-2"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=K(o);He(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}var Zo=S('
  • '),Jo=S('
      '),ei=S('
      ',1);function lt(t,r){Me(r,!0);let a=me(r,"value",3,""),n=me(r,"placeholder",3,"Select..."),s=me(r,"class",3,""),i=me(r,"buttonClass",3,""),o=z(!1),l=z(void 0),d=z(void 0),b=z(""),x=j(()=>{var T;return((T=r.options.find(q=>q.value===a()))==null?void 0:T.label)??n()});function A(T){var q;f(o,!1),(q=r.onchange)==null||q.call(r,T)}function m(){if(!e(l)||!e(d))return;const T=e(l).getBoundingClientRect(),q=e(d).offsetHeight,I=4,O=window.innerHeight-T.bottom-I,G=OO?T.top-I-q:T.bottom+I,Z=Math.min(T.left,window.innerWidth-T.width);f(b,`position:fixed; top:${G}px; left:${Z}px; width:${T.width}px; z-index:9999;`)}async function N(){f(o,!e(o)),e(o)&&(await Pt(),m())}function L(T){T.key==="Escape"&&f(o,!1)}function g(T){e(o)&&e(l)&&!e(l).contains(T.target)&&e(d)&&!e(d).contains(T.target)&&f(o,!1)}var c=ei();tt("mousedown",it,g),tt("keydown",it,L),tt("scroll",it,()=>{e(o)&&f(o,!1)});var w=K(c),P=k(w),h=k(P),y=k(h,!0);p(h);var C=$(h,2),_=k(C);Te(_,{get icon(){return pn},size:14}),p(C),p(P),nt(P,T=>f(l,T),()=>e(l)),p(w);var M=$(w,2);{var F=T=>{Jt(T,{children:(q,I)=>{var O=Jo();Ye(O,21,()=>r.options,V=>V.value,(V,G)=>{var Z=Zo();we(Z,"tabindex",0);var ae=k(Z,!0);p(Z),J(()=>{we(Z,"aria-selected",e(G).value===a()),Se(Z,1,`px-2.5 py-1.5 text-[13px] cursor-pointer transition-colors duration-100 - ${e(G).value===a()?"text-edf-bleu-fonce font-bold bg-[rgba(16,87,200,0.06)]":"text-ink hover:bg-edf-gris-clair"}`),ne(ae,e(G).label)}),le("mousedown",Z,E=>{E.stopPropagation(),A(e(G).value)}),u(V,Z)}),p(O),nt(O,V=>f(d,V),()=>e(d)),J(()=>Ze(O,e(b))),u(q,O)}})};Q(M,T=>{e(o)&&T(F)})}J(()=>{Se(w,1,`relative inline-flex ${s()??""}`),Se(P,1,dt(i()||"flex items-center justify-between gap-1.5 w-full h-[30px] cursor-pointer bg-white border border-border rounded-md text-[13px] text-ink font-normal pl-2.5 pr-2 transition-colors duration-150 hover:bg-edf-gris-clair focus:outline-none focus:border-edf-bleu-moyen")),we(P,"aria-expanded",e(o)),ne(y,e(x)),Se(C,1,`text-muted shrink-0 transition-transform duration-150 ${e(o)?"rotate-180":""}`)}),le("mousedown",P,T=>{T.stopPropagation(),N()}),u(t,c),Ae()}Ke(["mousedown"]);var ti=Ot(''),ri=S('
      '),ni=S('
      '),ai=S('
      ',1);function Gt(t,r){Me(r,!0);let a=me(r,"selected",19,()=>[]),n=me(r,"placeholder",3,"Select..."),s=me(r,"class",3,""),i=z(!1),o=z(void 0),l=z(void 0),d=z(""),b=j(()=>new Set(a())),x=j(()=>a().length===0?n():a().length===r.options.length?`All (${r.options.length})`:a().length<=2?a().map(I=>{var O;return((O=r.options.find(V=>V.value===I))==null?void 0:O.label)??I}).join(", "):`${a().length} selected`);function A(I){var V;const O=new Set(e(b));O.has(I)?O.delete(I):O.add(I),(V=r.onchange)==null||V.call(r,[...O])}function m(){var I;(I=r.onchange)==null||I.call(r,r.options.map(O=>O.value))}function N(){var I;(I=r.onchange)==null||I.call(r,[])}function L(){if(!e(o)||!e(l))return;const I=e(o).getBoundingClientRect(),O=e(l).offsetHeight,V=4,G=window.innerHeight-I.bottom-V,ae=GG?I.top-V-O:I.bottom+V,E=Math.min(I.left,window.innerWidth-I.width);f(d,`position:fixed; top:${ae}px; left:${E}px; min-width:${I.width}px; z-index:9999;`)}async function g(){f(i,!e(i)),e(i)&&(await Pt(),L())}function c(I){e(i)&&e(o)&&!e(o).contains(I.target)&&e(l)&&!e(l).contains(I.target)&&f(i,!1)}function w(I){I.key==="Escape"&&f(i,!1)}var P=ai();tt("mousedown",it,c),tt("keydown",it,w),tt("scroll",it,()=>{e(i)&&f(i,!1)});var h=K(P),y=k(h),C=k(y),_=k(C,!0);p(C);var M=$(C,2),F=k(M);Te(F,{get icon(){return pn},size:14}),p(M),p(y),nt(y,I=>f(o,I),()=>e(o)),p(h);var T=$(h,2);{var q=I=>{Jt(I,{children:(O,V)=>{var G=ni(),Z=k(G),ae=k(Z),E=$(ae,2);p(Z);var R=$(Z,2);Ye(R,17,()=>r.options,H=>H.value,(H,D)=>{var v=ri(),B=k(v),X=k(B);{var ue=se=>{var de=ti();u(se,de)},fe=j(()=>e(b).has(e(D).value));Q(X,se=>{e(fe)&&se(ue)})}p(B);var W=$(B,2),te=k(W,!0);p(W),p(v),J(se=>{Se(B,1,`inline-flex items-center justify-center shrink-0 w-[14px] h-[14px] rounded border-2 transition-colors duration-100 - ${se??""}`),ne(te,e(D).label)},[()=>e(b).has(e(D).value)?"bg-edf-bleu-moyen border-edf-bleu-moyen text-white":"bg-white border-edf-gris-moyen"]),le("mousedown",v,se=>{se.stopPropagation(),A(e(D).value)}),u(H,v)}),p(G),nt(G,H=>f(l,H),()=>e(l)),J(()=>Ze(G,e(d))),le("mousedown",ae,H=>{H.stopPropagation(),m()}),le("mousedown",E,H=>{H.stopPropagation(),N()}),u(O,G)}})};Q(T,I=>{e(i)&&I(q)})}J(()=>{Se(h,1,`relative inline-flex ${s()??""}`),we(y,"aria-expanded",e(i)),ne(_,e(x)),Se(M,1,`text-muted shrink-0 transition-transform duration-150 ${e(i)?"rotate-180":""}`)}),le("mousedown",y,I=>{I.stopPropagation(),g()}),u(t,P),Ae()}Ke(["mousedown"]);const Zr="csauto_token";let ba=z(ie(localStorage.getItem(Zr)??""));function xa(){return e(ba)}function ya(t){f(ba,t,!0),t?localStorage.setItem(Zr,t):localStorage.removeItem(Zr)}let wr=null;async function mn(t,r={}){const a=new Headers(r.headers),n=xa();n&&a.set("X-CSAUTO-TOKEN",n);const s=await fetch(t,{...r,headers:a});if(s.status===401){wr||(wr=hn("API token required:",n,"Authentication"));const i=await wr;return wr=null,i===null?s:(ya(i),a.set("X-CSAUTO-TOKEN",i),fetch(t,{...r,headers:a}))}return s}async function ct(t){const r=await mn(t);if(!r.ok)throw new Error(`GET ${t} failed: ${r.status}`);return r.json()}async function Ir(t){const r=await mn(t);if(!r.ok)throw new Error(`GET ${t} failed: ${r.status}`);return r.text()}async function Rt(t,r){const a=await mn(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!a.ok){const n=await a.text().catch(()=>a.statusText);throw new Error(`POST ${t} failed: ${a.status} — ${n}`)}return a.json()}function Or(t){return t.map(r=>`case=${encodeURIComponent(r)}`).join("&")}function si(t=!1){return ct(`/api/status${t?"?log=1":""}`)}function In(t){return ct(`/api/perf?${Or(t)}`)}function oi(){return ct("/api/app_config")}function ii(t){return ct(`/api/residual_columns?${Or(t)}`).then(r=>r.columns??[])}function li(t,r,a={}){const n=new URLSearchParams;return t.forEach(s=>n.append("case",s)),n.set("columns",r.join(",")),a.width&&n.set("width",String(a.width)),a.height&&n.set("height",String(a.height)),a.xMin!==void 0&&n.set("x_min",String(a.xMin)),a.includeHistory&&n.set("include_history","true"),Ir(`/api/residuals_svg?${n}`)}function wa(t){return ct(`/api/restart_origin?${Or(t)}`)}function ci(t){var a;const r=new URLSearchParams;return t.cases.forEach(n=>r.append("case",n)),(a=t.files)!=null&&a.length&&r.set("files",t.files.join(",")),t.maxHits&&r.set("max_hits",String(t.maxHits)),t.context!==void 0&&r.set("context",String(t.context)),t.sev&&r.set("sev",t.sev),t.q&&r.set("q",t.q),ct(`/api/recent_errors?${r}`)}function ui(t,r,a){const n=new URLSearchParams({case:t,file:r,n:String(a)});return Ir(`/api/tail?${n}`)}function di(t){return ct(`/api/resu_files?case=${encodeURIComponent(t)}`).then(r=>r.files??[])}function fi(t){return ct(`/api/resu_dirs?${Or(t)}`).then(r=>r.dirs??[])}function Jr(t,r){const a=new URLSearchParams({scope:r});return t.forEach(n=>a.append("case",n)),ct(`/api/probes?${a}`).then(n=>n.files??[])}function vi(t,r){const a=new URLSearchParams;return t.forEach(n=>a.append("case",n)),r.forEach(n=>a.append("probe",n)),ct(`/api/probe_columns?${a}`).then(n=>n.columns??[])}function gi(t,r,a){const n=new URLSearchParams({case:t,probe:r});return a.forEach(s=>n.append("column",s)),ct(`/api/probe_position?${n}`)}function hi(t){const r=new URLSearchParams;return t.cases.forEach(a=>r.append("case",a)),t.probes.forEach(a=>r.append("probe",a)),r.set("columns",t.columns.join(",")),t.axis&&r.set("axis",t.axis),t.xMin!==void 0&&r.set("x_min",String(t.xMin)),t.timeMin!==void 0&&r.set("time_min",String(t.timeMin)),r.set("include_history","true"),t.width&&r.set("width",String(t.width)),t.height&&r.set("height",String(t.height)),Ir(`/api/probe_svg?${r}`)}function pi(t){const r=new URLSearchParams;return t.cases.forEach(a=>r.append("case",a)),t.base&&r.set("base",t.base),t.kind&&r.set("kind",t.kind),t.filter&&r.set("filter",t.filter),Ir(`/api/compare_runs?${r}`)}function On(t){return Rt("/api/run_case",{cases:t.cases,n:t.n,nt:t.nt,max_parallel:t.maxParallel??void 0,restart:t.restart??!1,restart_mode:t.restartMode??"",restart_value:t.restartValue??void 0}).then(()=>{})}function mi(t){return Rt("/api/kill_case",{cases:t}).then(()=>{})}function kr(t){return Rt("/api/control_case",{cases:t.cases,action:t.action,value:t.value??void 0}).then(()=>{})}function _i(t){return Rt("/api/cleanup_cases",{cases:t.cases,keep_last:t.keepLast??1,prune_resu:t.pruneResu??!0,keep_resu:t.keepResu??[],delete_resu:t.deleteResu??[],max_log_mb:t.maxLogMb??50,clear_cid:!0,clear_pyc:!1})}function bi(t,r){return Rt("/api/case_note",{case:t,note:r}).then(()=>{})}function xi(t,r){return Rt("/api/case_convergence",{case:t,convergence:r}).then(()=>{})}function yi(t){return Rt("/api/open_gui",{case:t}).then(()=>{})}function wi(){return ct("/api/settings/telemetry")}function ki(t){return Rt("/api/settings/telemetry",{enabled:t})}var Ci=S(" ",1),Si=S(''),$i=S('Loading...'),Pi=S('No folders found'),Ei=S('
      '),Ri=S('
      ',1);function Ni(t,r){Me(r,!0);const a=ho();let n=z("keep_latest"),s=z(ie(a.keepLast)),i=z(ie([])),o=z(ie([])),l=z(!0),d=j(()=>e(n)==="keep_folder"||e(n)==="delete_folder");const b=[{value:"keep_latest",label:"Keep latest N"},{value:"delete_all",label:"Delete all RESU"},{value:"keep_folder",label:"Keep specific folders"},{value:"delete_folder",label:"Delete specific folders"}];let x=j(()=>e(i).map(m=>({value:m,label:m})));Zn(async()=>{try{f(i,await fi(r.cases),!0)}catch{f(i,[],!0)}f(l,!1)});async function A(){if(e(d)&&e(o).length===0){await We("Please select at least one RESU folder.","Missing selection");return}const m={action:e(n)};e(n)==="keep_latest"?(m.keepLast=e(s),po({keepLast:e(s)})):e(n)==="delete_all"?m.keepLast=0:e(n)==="keep_folder"?m.keepResu=e(o):e(n)==="delete_folder"&&(m.deleteResu=e(o)),ot(m)}{const m=g=>{var c=Ci(),w=K(c);$e(w,{variant:"secondary",onclick:()=>ot(null),children:(h,y)=>{ke();var C=Ve("Cancel");u(h,C)},$$slots:{default:!0}});var P=$(w,2);$e(P,{variant:"primary",onclick:A,children:(h,y)=>{ke();var C=Ve("Clean");u(h,C)},$$slots:{default:!0}}),u(g,c)};let N=j(()=>r.cases.length),L=j(()=>r.cases.length>1?"s":"");hr(t,{title:"Cleanup Cases",titleId:"clean-dialog-title",get subtitle(){return`${e(N)??""} case${e(L)??""} selected`},footer:m,children:(g,c)=>{var w=Ri(),P=K(w),h=k(P);Ne(h,{text:"Action",children:(F,T)=>{lt(F,{class:"w-[200px]",get options(){return b},get value(){return e(n)},onchange:q=>f(n,q,!0)})}});var y=$(h,2);{var C=F=>{Ne(F,{text:"Keep last",children:(T,q)=>{var I=Si();Xe(I),et(I,()=>e(s),O=>f(s,O)),u(T,I)}})};Q(y,F=>{e(n)==="keep_latest"&&F(C)})}p(P);var _=$(P,2);{var M=F=>{var T=Ei(),q=k(T);Ne(q,{text:"RESU folders",children:(I,O)=>{var V=be(),G=K(V);{var Z=R=>{var H=$i();u(R,H)},ae=R=>{var H=Pi();u(R,H)},E=R=>{Gt(R,{class:"w-full",get options(){return e(x)},get selected(){return e(o)},onchange:H=>f(o,H,!0),placeholder:"Select folders..."})};Q(G,R=>{e(l)?R(Z):e(i).length===0?R(ae,1):R(E,-1)})}u(I,V)}}),p(T),u(F,T)};Q(_,F=>{e(d)&&F(M)})}u(g,w)},$$slots:{footer:!0,default:!0}})}Ae()}function Mi(t,r){Me(r,!0);let a=j(Ws);var n=be(),s=K(n);{var i=b=>{wo(b,{get cases(){return e(a).props.cases}})},o=b=>{No(b,{get cases(){return e(a).props.cases}})},l=b=>{Ni(b,{get cases(){return e(a).props.cases}})},d=b=>{io(b,{get mode(){return e(a).type},get title(){return e(a).props.title},get message(){return e(a).props.message},get confirmLabel(){return e(a).props.confirmLabel},get confirmVariant(){return e(a).props.confirmVariant},get value(){return e(a).props.value},get placeholder(){return e(a).props.placeholder},get multiline(){return e(a).props.multiline}})};Q(s,b=>{e(a).type==="run"?b(i):e(a).type==="restart"?b(o,1):e(a).type==="clean"?b(l,2):(e(a).type==="alert"||e(a).type==="confirm"||e(a).type==="prompt")&&b(d,3)})}u(t,n),Ae()}let fr=z(ie([])),Ai=0;function Li(){return e(fr)}function Cr(t,r="success",a=3500){const n=++Ai;f(fr,[...e(fr),{id:n,message:t,variant:r}],!0),setTimeout(()=>ka(n),a)}function ka(t){f(fr,e(fr).filter(r=>r.id!==t),!0)}var Ti=S('
      '),Fi=S('
      ');function Di(t,r){Me(r,!0);let a=j(Li);Jt(t,{children:(n,s)=>{var i=Fi();Ye(i,21,()=>e(a),o=>o.id,(o,l)=>{var d=Ti(),b=k(d,!0);p(d),J(()=>{Se(d,1,`pointer-events-auto max-w-[320px] px-4 py-2.5 rounded-md shadow-lg text-sm font-semibold text-white cursor-pointer ${e(l).variant==="success"?"bg-edf-vert-fonce":"bg-edf-orange-fonce"}`),ne(b,e(l).message)}),le("click",d,()=>ka(e(l).id)),u(o,d)}),p(i),u(n,i)}}),Ae()}Ke(["click"]);var zi=Ot(''),Ii=Ot(''),Oi=S(''),Hi=S(" ",1),ji=S(" ",1),Vi=S('');function It(t,r){Me(r,!0);const a=g=>{var c=Oi(),w=k(c);{var P=y=>{var C=zi();J(()=>Ze(C,`width: ${o()-4}px; height: ${o()-4}px;`)),u(y,C)},h=y=>{var C=Ii();J(()=>Ze(C,`width: ${o()-4}px; height: ${o()-4}px;`)),u(y,C)};Q(w,y=>{n()&&!s()?y(P):s()&&y(h,1)})}p(c),J(()=>{Se(c,1,`inline-flex items-center justify-center rounded border-2 cursor-pointer transition-colors duration-100 shrink-0 - ${i()?"opacity-40 cursor-not-allowed":""} - ${n()||s()?"bg-edf-bleu-moyen border-edf-bleu-moyen text-white":"bg-white border-edf-gris-moyen hover:border-edf-bleu-clair"}`),Ze(c,`width: ${o()??""}px; height: ${o()??""}px;`),we(c,"aria-checked",s()?"mixed":n()),we(c,"aria-disabled",i()),we(c,"tabindex",i()?-1:0)}),le("click",c,b),le("keydown",c,x),u(g,c)};let n=me(r,"checked",3,!1),s=me(r,"indeterminate",3,!1),i=me(r,"disabled",3,!1),o=me(r,"size",3,16),l=me(r,"label",3,""),d=me(r,"labelFirst",3,!1);function b(g){var w;if(g.stopPropagation(),i())return;const c=s()?!0:!n();(w=r.onchange)==null||w.call(r,c)}function x(g){(g.key===" "||g.key==="Enter")&&(g.preventDefault(),g.stopPropagation(),b(g))}var A=be(),m=K(A);{var N=g=>{var c=Vi(),w=k(c);{var P=y=>{var C=Hi(),_=K(C),M=k(_,!0);p(_);var F=$(_);a(F),J(()=>ne(M,l())),u(y,C)},h=y=>{var C=ji(),_=K(C);a(_);var M=$(_),F=k(M,!0);p(M),J(()=>ne(F,l())),u(y,C)};Q(w,y=>{d()?y(P):y(h,-1)})}p(c),le("click",c,b),le("keydown",c,x),u(g,c)},L=g=>{a(g)};Q(m,g=>{l()?g(N):g(L,-1)})}u(t,A),Ae()}Ke(["click","keydown"]);function _n(t,r){try{const a=localStorage.getItem(t);return a===null?r:JSON.parse(a)}catch{return r}}function Hr(t,r){localStorage.setItem(t,JSON.stringify(r))}const en=new Map;function mr(t,r,a){vt(t),en.set(t,setInterval(r,a))}function vt(t){const r=en.get(t);r!==void 0&&(clearInterval(r),en.delete(t))}const Tt={status:1e3,tail:1e3,plot:3e3,probe:3e3,errors:5e3},Ui=["status","plot","probe","tail","errors"],Wr={status:{label:"Status table",min:500},plot:{label:"Residual plots",min:1e3},probe:{label:"Probe plots",min:1e3},tail:{label:"Log tail",min:500},errors:{label:"Recent errors",min:2e3}},ir=_n("csauto_refresh_rates",{});let bt=z(ie({status:ir.status??Tt.status,tail:ir.tail??Tt.tail,plot:ir.plot??Tt.plot,probe:ir.probe??Tt.probe,errors:ir.errors??Tt.errors}));function Bi(){return e(bt)}function qi(t,r){f(bt,{...e(bt),[t]:r},!0),Hr("csauto_refresh_rates",e(bt))}function Ki(){return e(bt).status}function Hn(){return e(bt).tail}function jn(){return e(bt).plot}function Gi(){return e(bt).probe}function Vn(){return e(bt).errors}const tn=new Set;function _r(t){return tn.add(t),()=>tn.delete(t)}function Wi(){tn.forEach(t=>t())}const rn=_n("csauto_autorefresh_enabled",{});function tr(t){return rn[t]??!0}function rr(t,r){rn[t]=r,Hr("csauto_autorefresh_enabled",rn)}var Xi=S(" ",1),Yi=S('
      ms
      '),Qi=S('
      Telemetry
      '),Zi=S('
      API token
      Auto-refresh intervals
      ',1);function Ji(t,r){Me(r,!0);let a=z(ie(xa())),n=z(ie({...Bi()})),s=z(!0),i=z(!1);Re(()=>{wi().then(b=>{f(s,b.enabled,!0),f(i,!0)}).catch(()=>{f(i,!1)})});function o(b){const{min:x}=Wr[b];let A=e(n)[b];(typeof A!="number"||isNaN(A))&&(A=Tt[b]),f(n,{...e(n),[b]:Math.max(x,Math.round(A))},!0)}function l(b,x){const A=x.target,m=A.value.replace(/[^0-9]/g,""),N=parseInt(m,10);isNaN(N)?m===""&&f(n,{...e(n),[b]:Wr[b].min},!0):f(n,{...e(n),[b]:N},!0),A.value=String(e(n)[b])}function d(){ya(e(a));for(const b of Object.keys(e(n)))o(b),qi(b,e(n)[b]);e(i)&&ki(e(s)).catch(()=>{}),r.onClose()}hr(t,{title:"Settings",titleId:"settings-dialog-title",get onCancel(){return r.onClose},onConfirm:d,maxWidth:"400px",portal:!0,footer:x=>{var A=Xi(),m=K(A);$e(m,{variant:"secondary",get onclick(){return r.onClose},children:(L,g)=>{ke();var c=Ve("Cancel");u(L,c)},$$slots:{default:!0}});var N=$(m,2);$e(N,{variant:"primary",onclick:d,children:(L,g)=>{ke();var c=Ve("Save");u(L,c)},$$slots:{default:!0}}),u(x,A)},children:(x,A)=>{var m=Zi(),N=K(m),L=$(k(N),2);Xe(L),p(N);var g=$(N,2),c=k(g),w=$(k(c),2);p(c);var P=$(c,2);Ye(P,20,()=>Ui,C=>C,(C,_)=>{const M=j(()=>Wr[_]);var F=Yi(),T=k(F),q=k(T),I=k(q,!0);p(q);var O=$(q,2),V=k(O);p(O),p(T);var G=$(T,2),Z=k(G);Xe(Z),ke(2),p(G),p(F),J(()=>{ne(I,e(M).label),ne(V,`min ${e(M).min??""}ms`),vn(Z,e(n)[_])}),le("input",Z,ae=>l(_,ae)),tt("blur",Z,()=>o(_)),u(C,F)}),p(P),p(g);var h=$(g,2);{var y=C=>{var _=Qi(),M=$(k(_),2);It(M,{get checked(){return e(s)},onchange:F=>f(s,F,!0),label:"Send anonymous usage statistics"}),p(_),u(C,_)};Q(h,C=>{e(i)&&C(y)})}et(L,()=>e(a),C=>f(a,C)),le("click",w,()=>f(n,{...Tt},!0)),u(x,m)},$$slots:{footer:!0,default:!0}}),Ae()}Ke(["click","input"]);const el=""+new URL("../assets/code-saturne.BHojVttu.svg",import.meta.url).href,tl="data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='UTF-8'?%3e%3csvg%20id='Calque_1'%20xmlns='http://www.w3.org/2000/svg'%20version='1.1'%20height='400px'%20viewBox='0%200%20397.89%2095.85'%3e%3c!--%20Generator:%20Adobe%20Illustrator%2029.8.1,%20SVG%20Export%20Plug-In%20.%20SVG%20Version:%202.1.1%20Build%202)%20--%3e%3cdefs%3e%3cstyle%3e%20.st0%20{%20fill:%20%23ff861d;%20}%20.st1%20{%20fill:%20%23ffb210;%20}%20.st2%20{%20fill:%20%231057c8;%20}%20.st3%20{%20fill:%20%23d6430a;%20}%20%3c/style%3e%3c/defs%3e%3cpath%20class='st2'%20d='M26.25,61.73c-12.02,0-20.11-8.16-20.11-21.37s8.01-21.37,20.33-21.37c8.83,0,15.06,3.78,17.36,11.28l-5.64,2.52c-1.48-5.94-4.97-8.69-11.87-8.69-8.46,0-14.25,6.01-14.25,16.25s5.93,16.25,13.95,16.25,11.73-2.82,12.84-8.61l5.71,1.93c-2,7.79-9.13,11.8-18.33,11.8'/%3e%3cpath%20class='st2'%20d='M89.96,40.36c0,13.21-7.87,21.37-20.48,21.37s-20.48-8.16-20.48-21.37,7.86-21.37,20.48-21.37,20.48,8.16,20.48,21.37M54.94,40.36c0,10.24,5.64,16.25,14.54,16.25s14.54-6.01,14.54-16.25-5.64-16.25-14.54-16.25-14.54,6.01-14.54,16.25'/%3e%3cpath%20class='st2'%20d='M134.3,40.29c0,13.43-8.01,20.85-21.81,20.85h-14.84V19.58h14.84c13.8,0,21.81,7.42,21.81,20.7M128.36,40.29c0-9.94-5.57-15.58-15.44-15.58h-9.35v31.32h9.35c9.87,0,15.44-5.71,15.44-15.73'/%3e%3cpolygon%20class='st2'%20points='174.11%2056.02%20174.11%2061.14%20141.98%2061.14%20141.98%2019.58%20173.14%2019.58%20173.14%2024.7%20147.91%2024.7%20147.91%2037.54%20167.65%2037.54%20167.65%2042.66%20147.91%2042.66%20147.91%2056.02%20174.11%2056.02'/%3e%3cpath%20class='st2'%20d='M213.81,22.13c4.16-1.92,9.75-3.11,14.3-3.11,12.55,0,17.74,5.19,17.74,17.34v5.27c0,4.16.08,7.28.16,10.31.08,3.12.24,5.99.48,9.19h-9.43c-.4-2.16-.4-4.88-.48-6.15h-.16c-2.48,4.56-7.83,7.11-12.71,7.11-7.28,0-14.39-4.4-14.39-12.23,0-6.15,2.96-9.75,7.03-11.75,4.08-2,9.35-2.4,13.83-2.4h5.91c0-6.63-2.96-8.87-9.27-8.87-4.56,0-9.11,1.76-12.71,4.48l-.32-9.19ZM226.36,54.26c3.27,0,5.83-1.44,7.51-3.68,1.76-2.32,2.24-5.27,2.24-8.47h-4.63c-4.8,0-11.91.8-11.91,7.11,0,3.52,2.96,5.04,6.79,5.04'/%3e%3cpath%20class='st2'%20d='M280.98,28.61c-3.28-1.12-5.67-1.76-9.59-1.76-2.88,0-6.31,1.04-6.31,4.56,0,6.56,18.62,2.4,18.62,17.27,0,9.59-8.55,13.43-17.27,13.43-4.08,0-8.23-.72-12.15-1.76l.64-8.79c3.36,1.68,6.87,2.72,10.55,2.72,2.72,0,7.03-1.04,7.03-5.04,0-8.07-18.62-2.56-18.62-17.42,0-8.87,7.75-12.79,16.14-12.79,5.03,0,8.31.8,11.67,1.52l-.72,8.07Z'/%3e%3cpath%20class='st2'%20d='M295.25,27.81h-7.91v-7.83h7.91v-8.23l10.71-3.44v11.67h9.51v7.83h-9.51v19.18c0,3.52.96,6.79,5.04,6.79,1.92,0,3.76-.4,4.88-1.12l.32,8.47c-2.24.64-4.72.96-7.91.96-8.39,0-13.03-5.2-13.03-13.35v-20.94Z'/%3e%3cpath%20class='st2'%20d='M355.22,58.82c-3.92,2.16-8.39,3.28-14.15,3.28-13.59,0-21.5-7.83-21.5-21.34,0-11.91,6.31-21.74,19.1-21.74,15.27,0,19.58,10.47,19.58,24.85h-28.45c.48,6.63,5.11,10.39,11.75,10.39,5.2,0,9.67-1.92,13.67-4.15v8.71ZM348.03,36.52c-.32-5.19-2.72-9.67-8.71-9.67s-9.03,4.16-9.51,9.67h18.22Z'/%3e%3cpath%20class='st2'%20d='M366.57,19.98h9.51v9.35h.16c.48-3.84,4.87-10.31,11.27-10.31,1.04,0,2.16,0,3.27.32v10.79c-.96-.56-2.88-.88-4.79-.88-8.71,0-8.71,10.87-8.71,16.78v15.11h-10.71V19.98Z'/%3e%3cpath%20class='st0'%20d='M229.73,80.9c0,3.67-2.97,6.64-6.64,6.64s-6.64-2.97-6.64-6.64,2.97-6.64,6.64-6.64,6.64,2.97,6.64,6.64'/%3e%3cpath%20class='st0'%20d='M203.17,80.9c0,3.67-2.97,6.64-6.64,6.64s-6.64-2.97-6.64-6.64,2.97-6.64,6.64-6.64,6.64,2.97,6.64,6.64'/%3e%3cpath%20class='st1'%20d='M296.14,80.9c0,3.67-2.97,6.64-6.64,6.64s-6.64-2.97-6.64-6.64,2.97-6.64,6.64-6.64,6.64,2.97,6.64,6.64'/%3e%3cpath%20class='st1'%20d='M378.47,80.9c0,3.67-2.97,6.64-6.64,6.64s-6.64-2.97-6.64-6.64,2.97-6.64,6.64-6.64,6.64,2.97,6.64,6.64'/%3e%3cpath%20class='st1'%20d='M269.57,80.9c0,3.67-2.97,6.64-6.64,6.64s-6.64-2.97-6.64-6.64,2.97-6.64,6.64-6.64,6.64,2.97,6.64,6.64'/%3e%3cpath%20class='st3'%20d='M107.55,80.9c0,3.67-2.97,6.64-6.64,6.64s-6.64-2.97-6.64-6.64,2.97-6.64,6.64-6.64,6.64,2.97,6.64,6.64'/%3e%3crect%20class='st0'%20x='134.12'%20y='75.59'%20width='42.5'%20height='10.62'/%3e%3crect%20class='st1'%20x='309.42'%20y='75.59'%20width='42.5'%20height='10.62'/%3e%3c/svg%3e";let Wt=z(null);function rl(t){f(Wt,t,!0)}function Ft(){return e(Wt)}function cr(t){return e(Wt)===null||e(Wt).capabilities.includes(t)}function nl(t){return e(Wt)===null||e(Wt).control_actions.includes(t)}var al=S('CODE_SATURNE'),sl=S('CODE_ASTER'),ol=S(' '),il=S('
      ',1);function ll(t,r){Me(r,!0);let a=j(()=>r.shownCases!==r.totalCases),n=z(!1);var s=il(),i=K(s),o=k(i),l=k(o);{var d=D=>{var v=al();J(()=>we(v,"src",el)),u(D,v)},b=j(()=>{var D;return!Ft()||((D=Ft())==null?void 0:D.solver)==="code_saturne"}),x=D=>{var v=sl();J(()=>we(v,"src",tl)),u(D,v)},A=j(()=>{var D;return((D=Ft())==null?void 0:D.solver)==="code_aster"}),m=D=>{var v=ol(),B=k(v,!0);p(v),J(X=>ne(B,X),[()=>{var X;return(X=Ft())==null?void 0:X.solver}]),u(D,v)};Q(l,D=>{e(b)?D(d):e(A)?D(x,1):D(m,-1)})}p(o);var N=$(o,2),L=k(N),g=k(L),c=k(g);{var w=D=>{var v=Ve();J(()=>ne(v,`${r.shownCases??""}/`)),u(D,v)};Q(c,D=>{e(a)&&D(w)})}var P=$(c,1,!0);p(g);var h=$(g,2),y=k(h,!0);p(h),p(L);var C=$(L,2),_=k(C),M=k(_);{var F=D=>{var v=Ve();J(()=>ne(v,`${r.shownRunning??""}/`)),u(D,v)};Q(M,D=>{e(a)&&D(F)})}var T=$(M,1,!0);p(_),ke(2),p(C);var q=$(C,2),I=k(q),O=k(I);{var V=D=>{var v=Ve();J(()=>ne(v,`${r.shownConverged??""}/`)),u(D,v)};Q(O,D=>{e(a)&&D(V)})}var G=$(O,1,!0);p(I),ke(2),p(q),p(N);var Z=$(N,2),ae=k(Z),E=k(ae);Te(E,{get icon(){return Yo},size:18}),p(ae),p(Z),p(i);var R=$(i,2);{var H=D=>{Ji(D,{onClose:()=>f(n,!1)})};Q(R,D=>{e(n)&&D(H)})}J(()=>{ne(P,r.totalCases),ne(y,r.totalCases===1?"case":"cases"),ne(T,r.totalRunning),ne(G,r.totalConverged)}),le("click",ae,()=>f(n,!0)),u(t,s),Ae()}Ke(["click"]);const cl=""+new URL("../assets/simvia-logo.kLDq7Uoj.svg",import.meta.url).href;var ul=S('
      '),dl=S('

      '),fl=S("
      "),vl=S('
      '),gl=S('
      '),hl=S('
      ');function Ht(t,r){let a=me(r,"eyebrow",3,""),n=me(r,"title",3,""),s=me(r,"wide",3,!1);var i=hl(),o=$(k(i),2);{var l=b=>{var x=gl(),A=k(x);{var m=c=>{var w=be(),P=K(w);ft(P,()=>r.titleSlot),u(c,w)},N=c=>{var w=fl(),P=k(w);{var h=_=>{var M=ul(),F=k(M,!0);p(M),J(()=>ne(F,a())),u(_,M)};Q(P,_=>{a()&&_(h)})}var y=$(P,2);{var C=_=>{var M=dl(),F=k(M,!0);p(M),J(()=>ne(F,n())),u(_,M)};Q(y,_=>{n()&&_(C)})}p(w),u(c,w)};Q(A,c=>{r.titleSlot?c(m):c(N,-1)})}var L=$(A,2);{var g=c=>{var w=vl(),P=k(w);ft(P,()=>r.actions),p(w),u(c,w)};Q(L,c=>{r.actions&&c(g)})}p(x),u(b,x)};Q(o,b=>{(a()||n()||r.titleSlot||r.actions)&&b(l)})}var d=$(o,2);ft(d,()=>r.children),p(i),J(()=>{Se(i,1,`${s()?"col-span-12":"col-span-6"} bg-card border border-border rounded-[10px] p-[16px_18px_18px] relative overflow-clip animate-rise`),we(i,"id",r.id)}),u(t,i)}function nr(t,r){Me(r,!0);let a=me(r,"checked",15,!0);Re(()=>{a()?mr(r.name,r.onRefresh,r.intervalMs):vt(r.name)}),vr(()=>vt(r.name)),It(t,{get checked(){return a()},onchange:n=>a(n),size:14,label:"Auto-refresh",labelFirst:!0}),Ae()}var pl=S(" ",1),ml=S('
    • '),_l=S(''),bl=S(' ',1);function xl(t,r){Me(r,!0);let a=me(r,"label",3,"More"),n=z(!1),s=z(void 0),i=z(void 0),o=z("");function l(){if(!e(s)||!e(i))return;const c=e(s).getBoundingClientRect(),w=e(i).offsetHeight,P=4,h=window.innerHeight-c.bottom-P,C=hh?c.top-P-w:c.bottom+P,_=Math.min(c.left,window.innerWidth-180);f(o,`position:fixed; top:${C}px; left:${_}px; min-width:170px; z-index:9999;`)}async function d(){f(n,!e(n)),e(n)&&(await Pt(),l())}function b(c){c.disabled||(f(n,!1),c.onClick())}function x(c){e(n)&&e(s)&&!e(s).contains(c.target)&&e(i)&&!e(i).contains(c.target)&&f(n,!1)}var A=bl();tt("mousedown",it,x),tt("keydown",it,c=>{c.key==="Escape"&&f(n,!1)}),tt("scroll",it,()=>{e(n)&&f(n,!1)});var m=K(A),N=k(m);$e(N,{variant:"secondary",size:"sm",onclick:d,children:(c,w)=>{var P=pl(),h=K(P);Te(h,{get icon(){return jo}});var y=$(h);J(()=>ne(y,` ${a()??""}`)),u(c,P)},$$slots:{default:!0}}),p(m),nt(m,c=>f(s,c),()=>e(s));var L=$(m,2);{var g=c=>{Jt(c,{children:(w,P)=>{var h=_l();Ye(h,21,()=>r.items,y=>y.label,(y,C)=>{var _=ml(),M=k(_),F=k(M);Te(F,{get icon(){return e(C).icon},size:14});var T=$(F);p(M),p(_),J(()=>{M.disabled=e(C).disabled,we(M,"aria-disabled",e(C).disabled),Se(M,1,`w-full flex items-center gap-2 text-left py-[7px] px-[10px] bg-transparent border border-transparent rounded-md text-[12.5px] text-ink transition-[background] duration-[120ms] ease-in-out disabled:opacity-40 disabled:pointer-events-none ${e(C).disabled?"":"cursor-pointer hover:bg-edf-gris-clair hover:border-border"}`),ne(T,` ${e(C).label??""}`)}),le("mousedown",M,q=>{q.stopPropagation(),b(e(C))}),u(y,_)}),p(h),nt(h,y=>f(i,y),()=>e(i)),J(()=>Ze(h,e(o))),u(w,h)}})};Q(L,c=>{e(n)&&c(g)})}u(t,A),Ae()}Ke(["mousedown"]);const Dt=[{key:"note",label:"Note"},{key:"nprocs",label:"MPI Ranks"},{key:"nt",label:"Thread Count"},{key:"last_iter",label:"Last Iter"},{key:"duration",label:"Duration"},{key:"last_mod",label:"Last Modified"},{key:"resu_size_mb",label:"RESU Size (MB)"}];let Xt=z(ie([])),Lr=z(ie([])),pt=z(ie(new Set)),br=z(""),bn=z(""),Et=z(ie([])),st=z(ie([])),Yt=z(""),Ca=z(!0),yl=z(!0),qt=z(ie(_n("csauto_status_views",{}))),xn=z(ie(localStorage.getItem("csauto_status_view_selected")??""));function yn(){return e(Xt)}function wn(){return e(Lr)}function Ut(){return e(pt)}function Un(){return e(br)}function nn(){return e(bn)}function wl(){return e(Et)}function kl(){return e(Et).filter(t=>e(Lr).includes(t))}function Cl(){return e(Et).filter(t=>Dt.some(r=>r.key===t))}function Sl(){return e(st)}function $l(){return e(Yt)}function Pl(){return e(Ca)}function Bn(){return e(qt)}function El(){return e(xn)}function Rl(t){f(Xt,t,!0)}function Nl(t){const r=e(Lr).length===0&&t.length>0;f(Lr,t,!0),r&&e(Et).length===0&&f(Et,[...t,...Dt.map(a=>a.key)],!0)}function Ml(t){f(br,t,!0)}function qn(t){f(Et,t,!0)}function Al(t){f(Yt,t,!0)}function Ll(t){f(Ca,t,!0)}function Tl(t){f(yl,t,!0)}function kn(t){f(xn,t,!0),localStorage.setItem("csauto_status_view_selected",t)}function an(t){const r=new Set(e(pt));r.has(t)?r.delete(t):r.add(t),f(pt,r,!0),f(br,t,!0),f(bn,t,!0)}function Sa(t){f(pt,new Set([t]),!0),f(br,t,!0),f(bn,t,!0)}function sn(t,r,a){const n=a.indexOf(t),s=a.indexOf(r);if(n<0||s<0)return;const[i,o]=n{const a=Fl(r);return t.every(n=>a.includes(n))})}function Dl(t){if(t==null||t==="")return"";const r=Number(t);return Number.isFinite(r)?r:String(t).toLowerCase()}function Wn(t,r){var n;if(r==="case_id")return t.case_id;if(r==="status")return t.status??"";if(r==="note")return t.note??"";if(r==="nprocs")return t.nprocs??0;if(r==="nt")return t.nt??0;if(r==="last_iter")return t.last_iter??0;if(r==="duration")return t.duration_s??0;if(r==="last_mod")return t.last_mod??"";if(r==="resu_size_mb")return t.resu_size_mb??0;const a=(n=t.doe)==null?void 0:n[r];return a!==void 0?Dl(a):""}function zl(t,r){return t===""&&r===""?0:t===""?1:r===""?-1:typeof t=="number"&&typeof r=="number"?t-r:String(t).localeCompare(String(r))}function Pa(){const t=$a();return e(st).length===0?t:[...t].sort((r,a)=>{for(const n of e(st)){const s=Wn(r,n.key),i=Wn(a,n.key),o=zl(s,i);if(o!==0)return n.dir==="asc"?o:-o}return 0})}function Rr(){return Pa().map(t=>t.case_id)}function Il(t,r){const a=e(st).findIndex(n=>n.key===t);if(r)if(a>=0){const n=[...e(st)];n[a]={key:t,dir:n[a].dir==="asc"?"desc":"asc"},f(st,n,!0)}else f(st,[...e(st),{key:t,dir:"asc"}],!0);else a>=0&&e(st).length===1?f(st,[{key:t,dir:e(st)[0].dir==="asc"?"desc":"asc"}],!0):f(st,[{key:t,dir:"asc"}],!0)}function Ol(){return{visibleColumns:[...e(Et)],search:e(Yt),sorts:e(st).map(t=>({...t}))}}function Hl(t){f(Et,[...t.visibleColumns],!0),f(Yt,t.search,!0),f(st,t.sorts.map(r=>({...r})),!0)}function jl(t){f(qt,{...e(qt),[t]:Ol()},!0),Hr("csauto_status_views",e(qt)),kn(t)}function Vl(t){const r={...e(qt)};delete r[t],f(qt,r,!0),Hr("csauto_status_views",r),e(xn)===t&&kn("")}function Sr(){const t=new Set;for(const r of e(Xt))e(pt).has(r.case_id)&&t.add((r.status??"").toUpperCase());return t}function Xn(){for(const t of e(Xt))if(e(pt).has(t.case_id)&&t.resu_size_mb!=null&&t.resu_size_mb>0)return!0;return!1}function Ul(t,r){const a=r.length,n=t.length,s=r.filter(d=>d.status==="RUNNING").length,i=t.filter(d=>d.status==="RUNNING").length,o=r.filter(d=>d.convergence==="converged").length,l=t.filter(d=>d.convergence==="converged").length;return{totalCases:a,shownCases:n,totalRunning:s,shownRunning:i,totalConverged:o,shownConverged:l}}var Bl=S('
      DOE columns
      '),ql=S('
      ');function Kl(t,r){Me(r,!0);let a=me(r,"initialName",3,""),n=j(wn),s=z(ie(a())),i=z(ie(new Set(r.initialColumns??[...e(n),...Dt.map(V=>V.key)])));function o(V){const G=new Set(e(i));G.has(V)?G.delete(V):G.add(V),f(i,G,!0)}function l(V,G){const Z=new Set(e(i));V.forEach(ae=>G?Z.add(ae):Z.delete(ae)),f(i,Z,!0)}function d(){const V=e(s).trim();V&&r.onSave(V,[...e(i)])}function b(V){V.key==="Escape"&&r.onCancel()}function x(V){V.target.dataset.backdrop!==void 0&&r.onCancel()}let A=z(void 0);Re(()=>{var V,G;(V=e(A))==null||V.focus(),(G=e(A))==null||G.select()});var m=ql(),N=k(m),L=k(N),g=k(L,!0);p(L);var c=$(L,2),w=$(k(c),2);Xe(w),nt(w,V=>f(A,V),()=>e(A)),p(c);var P=$(c,2);{var h=V=>{var G=Bl(),Z=k(G),ae=$(k(Z),2),E=k(ae),R=$(E,2);p(ae),p(Z);var H=$(Z,2);Ye(H,20,()=>e(n),D=>D,(D,v)=>{{let B=j(()=>e(i).has(v));It(D,{get checked(){return e(B)},onchange:()=>o(v),size:14,get label(){return v}})}}),p(H),p(G),le("click",E,()=>l(e(n),!0)),le("click",R,()=>l(e(n),!1)),u(V,G)};Q(P,V=>{e(n).length>0&&V(h)})}var y=$(P,2),C=k(y),_=$(k(C),2),M=k(_),F=$(M,2);p(_),p(C);var T=$(C,2);Ye(T,21,()=>Dt,V=>V.key,(V,G)=>{{let Z=j(()=>e(i).has(e(G).key));It(V,{get checked(){return e(Z)},onchange:()=>o(e(G).key),size:14,get label(){return e(G).label}})}}),p(T),p(y);var q=$(y,2),I=k(q);$e(I,{variant:"secondary",get onclick(){return r.onCancel},children:(V,G)=>{ke();var Z=Ve("Cancel");u(V,Z)},$$slots:{default:!0}});var O=$(I,2);{let V=j(()=>!e(s).trim());$e(O,{variant:"primary",onclick:d,get disabled(){return e(V)},children:(G,Z)=>{ke();var ae=Ve("Save");u(G,ae)},$$slots:{default:!0}})}p(q),p(N),p(m),J(()=>ne(g,r.mode==="create"?"New view":"Edit view")),le("keydown",m,b),le("click",m,x),et(w,()=>e(s),V=>f(s,V)),le("click",M,()=>l(Dt.map(V=>V.key),!0)),le("click",F,()=>l(Dt.map(V=>V.key),!1)),u(t,m),Ae()}Ke(["keydown","click"]);var Gl=S('
      ',1);function Wl(t,r){Me(r,!0);let a=j(()=>Object.keys(Bn())),n=z(ie(El())),s=z(!1),i=z("create"),o=j(()=>[{value:"",label:"All columns"},...e(a).map(C=>({value:C,label:C}))]);function l(C){if(f(n,C,!0),kn(C),C==="")qn([...wn(),...Dt.map(_=>_.key)]);else{const _=Bn();_[C]&&Hl(_[C])}}function d(){f(i,"edit"),f(s,!0)}function b(){f(i,"create"),f(s,!0)}function x(C,_){qn(_),e(i)==="edit"&&e(n)&&C!==e(n)&&Vl(e(n)),jl(C),f(n,C,!0),f(s,!1)}function A(){f(s,!1)}var m=Gl(),N=K(m),L=k(N);lt(L,{class:"w-[140px]",get options(){return e(o)},get value(){return e(n)},onchange:l});var g=$(L,2),c=k(g);Te(c,{get icon(){return Ko},size:14}),p(g);var w=$(g,2),P=k(w);Te(P,{get icon(){return Go},size:14}),p(w),p(N);var h=$(N,2);{var y=C=>{Jt(C,{children:(_,M)=>{{let F=j(()=>e(i)==="edit"?e(n):""),T=j(wl);Kl(_,{get mode(){return e(i)},get initialName(){return e(F)},get initialColumns(){return e(T)},onSave:x,onCancel:A})}}})};Q(h,C=>{e(s)&&C(y)})}le("click",g,d),le("click",w,b),u(t,m),Ae()}Ke(["click"]);var Xl=S('
      '),Yl=S(""),Ql=S(' '),Zl=S(' '),Jl=S('
      '),ec=S(" "),tc=S(''),rc=S(''),nc=S(" Open GUI",1),ac=S(""),sc=S(" "),oc=S(""),ic=S('
      ');function lc(t,r){Me(r,!0);let a=j(()=>r.rows.length>0&&r.rows.every(E=>e(b).has(E.case_id))),n=j(()=>r.rows.some(E=>e(b).has(E.case_id))),s=j(()=>e(n)&&!e(a)),i=j(kl),o=j(()=>new Set(Cl())),l=j(Sl),d=j(Pl),b=j(Ut),x=j(()=>{const E=[{key:"_select",label:"",kind:"meta",sticky:"left"},{key:"case_id",label:"Case",kind:"meta",sticky:"left"},{key:"status",label:"Status",kind:"meta",sticky:"left"},...e(o).has("note")?[{key:"note",label:"Note",kind:"meta"}]:[]],R=e(i).map(B=>({key:B,label:B,kind:"doe"})),D=[{key:"nprocs",label:"MPI Ranks",kind:"calc"},{key:"nt",label:"Thread Count",kind:"calc"},{key:"last_iter",label:"Last Iter",kind:"calc"},{key:"duration",label:"Duration",kind:"calc"},{key:"last_mod",label:"Last Modified",kind:"calc"},{key:"resu_size_mb",label:"RESU Size (MB)",kind:"calc"}].filter(B=>e(o).has(B.key)),v=cr("gui")?[{key:"_actions",label:"",kind:"meta",sticky:"right"}]:[];return[...E,...R,...D,...v]});function A(E){const R=e(l).findIndex(D=>D.key===E);if(R<0)return"";const H=e(l)[R].dir==="asc"?"▲":"▼";return e(l).length>1?`${H}${R+1}`:H}function m(E,R){E!=="_actions"&&Il(E,R.ctrlKey||R.metaKey)}function N(E,R){R.shiftKey?sn(nn()||E,E,Rr()):R.ctrlKey||R.metaKey?an(E):Sa(E)}function L(E,R){var D;const H=((D=E.status)==null?void 0:D.toUpperCase())??"";H!=="DONE"&&H!=="FAILED"||(R.preventDefault(),r.onContextMenu(E.case_id,R.clientX,R.clientY,E.convergence??""))}async function g(E){const R=E.note??"",H=await hn("Case note:",R,`Note — ${E.case_id}`,"",!0);if(H!==null)try{await bi(E.case_id,H)}catch(D){await We(`Failed to set note: ${D instanceof Error?D.message:D}`,"Error")}}async function c(E){try{await yi(E)}catch(R){await We(`Failed to open GUI: ${R instanceof Error?R.message:R}`,"Error")}}function w(E,R){const H=(E==null?void 0:E.toUpperCase())??"";return(H==="DONE"||H==="FAILED")&&R==="converged"?"CONVERGED":(H==="DONE"||H==="FAILED")&&R==="not_converged"?"NOT CONVERGED":H}function P(E,R){const H=(E==null?void 0:E.toUpperCase())??"";return(H==="DONE"||H==="FAILED")&&R==="converged"?"status-converged":(H==="DONE"||H==="FAILED")&&R==="not_converged"?"status-not-converged":H==="RUNNING"?"status-running":H==="DONE"?"status-done":H==="FAILED"?"status-failed":H==="PREPARED"?"status-prepared":"status-unknown"}function h(E){return e(b).has(E.case_id)?"row-selected":""}function y(E,R){var H;return R.key==="case_id"?E.case_id:R.key==="nprocs"?E.nprocs!=null?String(E.nprocs):"":R.key==="nt"?E.nt!=null?String(E.nt):"":R.key==="last_iter"?E.last_iter!=null?String(E.last_iter):"":R.key==="duration"?E.duration??"":R.key==="last_mod"?C(E.last_mod):R.key==="resu_size_mb"?E.resu_size_mb!=null?String(E.resu_size_mb):"":R.kind==="doe"?((H=E.doe)==null?void 0:H[R.key])!=null&&String(E.doe[R.key])!==""?String(E.doe[R.key]):"—":""}function C(E){if(!E)return"";try{const R=new Date(E),H=new Date,D=R.toDateString()===H.toDateString(),v=new Date(H);v.setDate(v.getDate()-1);const B=R.toDateString()===v.toDateString(),X=R.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});return D?`Today ${X}`:B?`Yesterday ${X}`:R.toLocaleDateString([],{month:"short",day:"numeric"})+` ${X}`}catch{return E}}let _=z(ie({})),M=z(void 0);Re(()=>{e(d),e(x),r.rows,!(!e(M)||!e(d))&&Pt().then(()=>{if(!e(M))return;const E=Array.from(e(M).querySelectorAll("thead th.sticky-left")),R={};let H=0;E.forEach(D=>{const v=D.dataset.colKey??"";R[v]=H,H+=D.getBoundingClientRect().width}),f(_,R,!0)})});function F(E,R,H){const D=H&&E.key!=="_select"&&E.key!=="_actions"?["status-sortable"]:[];if(!H&&E.key==="case_id"&&D.push("case-id"),E.kind==="doe"&&D.push("status-col-doe"),!H&&E.key==="note"&&D.push("max-w-[140px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer hover:underline hover:decoration-dotted hover:underline-offset-2"),e(d)&&E.sticky==="left"&&D.push("sticky-left"),e(d)&&E.sticky==="right"&&D.push("sticky-right"),e(d)&&E.sticky==="left"){const v=e(x)[R+1];(!v||v.sticky!=="left")&&D.push("sticky-divider")}if(e(d)&&E.sticky==="right"){const v=e(x)[R-1];(!v||v.sticky!=="right")&&D.push("sticky-divider")}return D.join(" ")}function T(E,R){return F(E,R,!0)}function q(E,R){return F(E,R,!1)}function I(E){return!e(d)||!E.sticky?"":E.sticky==="right"?"position:sticky;right:0;":E.sticky==="left"&&E.key in e(_)?`position:sticky;left:${e(_)[E.key]}px;`:""}var O=ic(),V=k(O),G=k(V),Z=k(G);Ye(Z,23,()=>e(x),E=>E.key,(E,R,H)=>{var D=be(),v=K(D);{var B=fe=>{var W=Xl(),te=k(W),se=k(te);It(se,{get checked(){return e(a)},get indeterminate(){return e(s)},get onchange(){return r.onToggleAll},size:14}),p(te),p(W),J((de,ce)=>{Se(W,1,`${de??""} !px-2.5 !py-0 w-9`),we(W,"data-col-key",e(R).key),Ze(W,ce)},[()=>T(e(R),e(H)),()=>I(e(R))]),u(fe,W)},X=fe=>{var W=Yl();J((te,se)=>{Se(W,1,te),we(W,"data-col-key",e(R).key),Ze(W,se)},[()=>dt(T(e(R),e(H))),()=>I(e(R))]),u(fe,W)},ue=fe=>{var W=Zl(),te=k(W),se=$(te);{var de=re=>{var xe=Ql(),he=k(xe,!0);p(xe),J(Ee=>ne(he,Ee),[()=>A(e(R).key)]),u(re,xe)},ce=j(()=>A(e(R).key));Q(se,re=>{e(ce)&&re(de)})}p(W),J((re,xe)=>{Se(W,1,re),we(W,"data-col-key",e(R).key),Ze(W,xe),ne(te,`${e(R).label??""} `)},[()=>dt(T(e(R),e(H))),()=>I(e(R))]),le("click",W,re=>m(e(R).key,re)),le("keydown",W,re=>{(re.key==="Enter"||re.key===" ")&&(re.preventDefault(),m(e(R).key,re))}),u(fe,W)};Q(v,fe=>{e(R).key==="_select"?fe(B):e(R).key==="_actions"?fe(X,1):fe(ue,-1)})}u(E,D)}),p(Z),p(G);var ae=$(G);Ye(ae,21,()=>r.rows,E=>E.case_id,(E,R)=>{var H=oc();Ye(H,23,()=>e(x),D=>D.key,(D,v,B)=>{var X=be(),ue=K(X);{var fe=ce=>{var re=Jl(),xe=k(re),he=k(xe);{let Ee=j(()=>e(b).has(e(R).case_id));It(he,{get checked(){return e(Ee)},onchange:()=>r.onToggleRow(e(R).case_id),size:14})}p(xe),p(re),J((Ee,pe)=>{Se(re,1,`${Ee??""} !px-2.5 !py-0 w-9`),we(re,"data-col-key",e(v).key),Ze(re,pe)},[()=>q(e(v),e(B)),()=>I(e(v))]),u(ce,re)},W=ce=>{var re=ec(),xe=k(re),he=k(xe,!0);p(xe),p(re),J((Ee,pe,Fe,ze)=>{Se(re,1,Ee),we(re,"data-col-key",e(v).key),Ze(re,pe),Se(xe,1,`status-pill ${Fe??""}`),ne(he,ze)},[()=>dt(q(e(v),e(B))),()=>I(e(v)),()=>P(e(R).status,e(R).convergence),()=>w(e(R).status,e(R).convergence)]),u(ce,re)},te=ce=>{var re=rc(),xe=k(re);{var he=pe=>{var Fe=Ve();J(()=>ne(Fe,e(R).note)),u(pe,Fe)},Ee=pe=>{var Fe=tc(),ze=k(Fe);Te(ze,{get icon(){return qo},size:12}),p(Fe),u(pe,Fe)};Q(xe,pe=>{e(R).note?pe(he):pe(Ee,-1)})}p(re),J((pe,Fe)=>{Se(re,1,pe),we(re,"data-col-key",e(v).key),Ze(re,Fe),we(re,"title",e(R).note||"Add note")},[()=>dt(q(e(v),e(B))),()=>I(e(v))]),le("click",re,pe=>{pe.stopPropagation(),g(e(R))}),le("keydown",re,pe=>{(pe.key==="Enter"||pe.key===" ")&&(pe.preventDefault(),pe.stopPropagation(),g(e(R)))}),u(ce,re)},se=ce=>{var re=ac(),xe=k(re);$e(xe,{variant:"primary",size:"sm",onclick:he=>{he.stopPropagation(),c(e(R).case_id)},children:(he,Ee)=>{var pe=nc(),Fe=K(pe);Te(Fe,{get icon(){return Vo},size:12}),ke(),u(he,pe)},$$slots:{default:!0}}),p(re),J((he,Ee)=>{Se(re,1,he),we(re,"data-col-key",e(v).key),Ze(re,Ee)},[()=>dt(q(e(v),e(B))),()=>I(e(v))]),u(ce,re)},de=ce=>{var re=sc(),xe=k(re,!0);p(re),J((he,Ee,pe)=>{Se(re,1,he),we(re,"data-col-key",e(v).key),Ze(re,Ee),ne(xe,pe)},[()=>dt(q(e(v),e(B))),()=>I(e(v)),()=>y(e(R),e(v))]),u(ce,re)};Q(ue,ce=>{e(v).key==="_select"?ce(fe):e(v).key==="status"?ce(W,1):e(v).key==="note"?ce(te,2):e(v).key==="_actions"?ce(se,3):ce(de,-1)})}u(D,X)}),p(H),J(D=>Se(H,1,D),[()=>dt(h(e(R)))]),le("click",H,D=>N(e(R).case_id,D)),le("contextmenu",H,D=>L(e(R),D)),u(E,H)}),p(ae),p(V),nt(V,E=>f(M,E),()=>e(M)),p(O),J(()=>Se(O,1,`table-wrap ${e(d)?"sticky-enabled":""}`)),u(t,O),Ae()}Ke(["click","keydown","contextmenu"]);var cc=S('
      ');function uc(t,r){Me(r,!0);let a=j(()=>{const m=Math.min(r.x,window.innerWidth-180),N=Math.min(r.y,window.innerHeight-120);return`left: ${m}px; top: ${N}px;`});async function n(m){try{for(const N of r.cases)await xi(N,m)}catch(N){console.error("Failed to set convergence:",N)}r.onClose()}const s=r.cases.length>1?` (${r.cases.length})`:"";var i=cc();tt("keydown",it,m=>{m.key==="Escape"&&r.onClose()});var o=k(i),l=k(o);p(o);var d=$(o,2),b=k(d);p(d);var x=$(d,2),A=k(x);p(x),p(i),J(()=>{Ze(i,e(a)),Se(o,1,`w-full text-left py-[7px] px-[10px] bg-transparent border border-transparent rounded-md text-[12.5px] cursor-pointer transition-[background] duration-[120ms] ease-in-out hover:bg-edf-gris-clair hover:border-border ${r.currentValue==="converged"?"is-active":""}`),ne(l,`Mark Converged${s}`),Se(d,1,`w-full text-left py-[7px] px-[10px] bg-transparent border border-transparent rounded-md text-[12.5px] cursor-pointer transition-[background] duration-[120ms] ease-in-out hover:bg-edf-gris-clair hover:border-border ${r.currentValue==="not_converged"?"is-active":""}`),ne(b,`Mark Not Converged${s}`),Se(x,1,`w-full text-left py-[7px] px-[10px] bg-transparent border border-transparent rounded-md text-[12.5px] cursor-pointer transition-[background] duration-[120ms] ease-in-out hover:bg-edf-gris-clair hover:border-border ${r.currentValue===""?"is-active":""}`),ne(A,`Clear Mark${s}`)}),le("click",o,()=>n("converged")),le("click",d,()=>n("not_converged")),le("click",x,()=>n("")),u(t,i),Ae()}Ke(["click"]);var dc=S(" Refresh",1),fc=S(" ",1),vc=S(" Run",1),gc=S(" Restart",1),hc=S(" Stop",1),pc=S(" Kill",1),mc=S(" Clean",1),_c=S('
      ',1),bc=S('
      ',1);function xc(t,r){Me(r,!0);function a(){return[...Ut()]}async function n(){const v=a();if(!v.length)return;const B=await Xs(v);if(B)try{await On({cases:v,n:B.n,nt:B.nt,maxParallel:B.maxParallel}),r.onRefresh()}catch(X){await We(`Run failed: ${X instanceof Error?X.message:X}`,"Error")}}async function s(){const v=a();if(!v.length)return;const B=await Ys(v);if(B)try{await On({cases:v,n:B.n,nt:B.nt,maxParallel:B.maxParallel,restart:!0,restartMode:B.restartMode,restartValue:B.restartValue}),r.onRefresh()}catch(X){await We(`Restart failed: ${X instanceof Error?X.message:X}`,"Error")}}async function i(){const v=a();if(!(!v.length||!await Dn(`Kill ${v.length} case${v.length>1?"s":""}?`,"Confirm Kill","Kill","danger")))try{await mi(v),r.onRefresh()}catch(X){await We(`Kill failed: ${X instanceof Error?X.message:X}`,"Error")}}async function o(){const v=a();if(!(!v.length||!await Dn(`Stop ${v.length} case${v.length>1?"s":""} gracefully (checkpoint + exit)?`,"Confirm Stop","Stop","danger")))try{await kr({cases:v,action:"stop"}),r.onRefresh(),Cr(`Stop requested for ${v.length} case${v.length>1?"s":""} — will checkpoint and exit`)}catch(X){await We(`Stop failed: ${X instanceof Error?X.message:X}`,"Error")}}async function l(){const v=a();if(v.length)try{await kr({cases:v,action:"checkpoint"}),r.onRefresh(),Cr(`Checkpoint requested for ${v.length} case${v.length>1?"s":""}`)}catch(B){await We(`Checkpoint failed: ${B instanceof Error?B.message:B}`,"Error")}}async function d(){const v=a();if(!v.length)return;const B=await hn("Extend by how many additional time steps?","500","Extend Run");if(B===null)return;const X=parseInt(B,10);if(!Number.isFinite(X)||X<=0){await We("Enter a positive integer number of time steps.","Error");return}try{await kr({cases:v,action:"extend",value:X}),r.onRefresh(),Cr(`Extended ${v.length} case${v.length>1?"s":""} by ${X} time steps`)}catch(ue){await We(`Extend failed: ${ue instanceof Error?ue.message:ue}`,"Error")}}async function b(){const v=a();if(v.length)try{await kr({cases:v,action:"flush"}),Cr(`Flush requested for ${v.length} case${v.length>1?"s":""}`)}catch(B){await We(`Flush failed: ${B instanceof Error?B.message:B}`,"Error")}}async function x(){const v=a();if(!v.length)return;const B=await Qs(v);if(B)try{await _i({cases:v,keepLast:B.keepLast,keepResu:B.keepResu,deleteResu:B.deleteResu,pruneResu:!0}),r.onRefresh(),Wi()}catch(X){await We(`Cleanup failed: ${X instanceof Error?X.message:X}`,"Error")}}function A(){const v=Rr();v.length>0&&v.every(X=>Ut().has(X))?Gn():Kn(v)}function m(v){an(v)}let N=z(ie(tr("status")));Re(()=>{rr("status",e(N))}),Ll(!0);let L=j(()=>{const v=Sr();return v.size>0&&[...v].some(B=>B==="PREPARED"||B==="DONE"||B==="FAILED")}),g=j(()=>{if(!cr("restart"))return!1;const v=Sr();return v.size>0&&[...v].some(B=>B==="DONE"||B==="FAILED")&&Xn()}),c=j(()=>{const v=Sr();return v.size>0&&v.has("RUNNING")}),w=j(()=>{if(!cr("control"))return!1;const v=Sr();return v.size>0&&v.has("RUNNING")});const P=[{label:"Extend",icon:Uo,onClick:d,action:"extend"},{label:"Checkpoint",icon:Xo,onClick:l,action:"checkpoint"},{label:"Flush",icon:Ho,onClick:b,action:"flush"}];let h=j(()=>P.filter(v=>nl(v.action)).map(v=>({label:v.label,icon:v.icon,onClick:v.onClick,disabled:!e(w)}))),y=j(Xn),C=z(ie($l())),_=null;function M(){_&&clearTimeout(_),_=setTimeout(()=>{Al(e(C))},200)}Re(()=>Tl(e(N)));let F=z(!1),T=z(0),q=z(0),I=z(ie([])),O=z("");function V(v,B,X,ue){f(I,Ut().has(v)?[...Ut()]:[v],!0),f(O,ue,!0),f(T,B,!0),f(q,X,!0),f(F,!0)}function G(){f(F,!1)}function Z(v){const B=Rr();if(B.length){if(v.key==="Escape"){Gn(),G();return}if((v.ctrlKey||v.metaKey)&&v.key==="a"){v.preventDefault(),Kn(B);return}if(v.key==="ArrowDown"||v.key==="ArrowUp"){v.preventDefault();const X=v.key==="ArrowDown"?1:-1,ue=Un(),fe=B.indexOf(ue),W=Math.max(0,Math.min(B.length-1,fe+X)),te=B[W];v.shiftKey?sn(nn()||B[0],te,B):Sa(te),Ml(te);return}if(v.key===" "){v.preventDefault();const X=Un();X&&(v.shiftKey?sn(nn()||X,X,Rr()):an(X))}}}var ae=bc();tt("click",it,G),tt("scroll",it,G);var E=K(ae),R=k(E);Ht(R,{eyebrow:"Overview",title:"Status",wide:!0,id:"status-card-shell",actions:B=>{var X=fc(),ue=K(X);{let te=j(Ki);nr(ue,{name:"status",get intervalMs(){return e(te)},get onRefresh(){return r.onRefresh},get checked(){return e(N)},set checked(se){f(N,se,!0)}})}var fe=$(ue,2);{var W=te=>{$e(te,{variant:"primary",onclick:()=>r.onRefresh(),children:(se,de)=>{var ce=dc(),re=K(ce);Te(re,{get icon(){return er}}),ke(),u(se,ce)},$$slots:{default:!0}})};Q(fe,te=>{e(N)||te(W)})}u(B,X)},children:(B,X)=>{var ue=_c(),fe=K(ue),W=k(fe),te=k(W);Xe(te);var se=$(te,2);Wl(se,{}),p(W);var de=$(W,2),ce=k(de),re=k(ce);p(ce);var xe=$(ce,2);{let U=j(()=>!e(L));$e(xe,{variant:"run",size:"sm",onclick:n,get disabled(){return e(U)},children:(ee,oe)=>{var ve=vc(),ye=K(ve);Te(ye,{get icon(){return _a}}),ke(),u(ee,ve)},$$slots:{default:!0}})}var he=$(xe,2);{var Ee=U=>{{let ee=j(()=>!e(g));$e(U,{variant:"warning",size:"sm",onclick:s,get disabled(){return e(ee)},children:(oe,ve)=>{var ye=gc(),ge=K(ye);Te(ge,{get icon(){return Wo}}),ke(),u(oe,ye)},$$slots:{default:!0}})}},pe=j(()=>cr("restart"));Q(he,U=>{e(pe)&&U(Ee)})}var Fe=$(he,2);{var ze=U=>{{let ee=j(()=>!e(w));$e(U,{variant:"warning",size:"sm",onclick:o,get disabled(){return e(ee)},children:(oe,ve)=>{var ye=hc(),ge=K(ye);Te(ge,{get icon(){return Io}}),ke(),u(oe,ye)},$$slots:{default:!0}})}},Qe=j(()=>cr("control"));Q(Fe,U=>{e(Qe)&&U(ze)})}var je=$(Fe,2);{var rt=U=>{xl(U,{get items(){return e(h)}})};Q(je,U=>{e(h).length>0&&U(rt)})}var Le=$(je,2);{let U=j(()=>!e(c));$e(Le,{variant:"danger",size:"sm",onclick:i,get disabled(){return e(U)},children:(ee,oe)=>{var ve=pc(),ye=K(ve);Te(ye,{get icon(){return Oo}}),ke(),u(ee,ve)},$$slots:{default:!0}})}var Pe=$(Le,2);{let U=j(()=>!e(y));$e(Pe,{variant:"secondary",size:"sm",onclick:x,get disabled(){return e(U)},children:(ee,oe)=>{var ve=mc(),ye=K(ve);Te(ye,{get icon(){return Qo}}),ke(),u(ee,ve)},$$slots:{default:!0}})}p(de),p(fe);var Y=$(fe,2);{let U=j(Pa);lc(Y,{get rows(){return e(U)},onContextMenu:V,onToggleAll:A,onToggleRow:m})}J(U=>ne(re,`${U??""} selected`),[()=>Ut().size]),le("input",te,M),et(te,()=>e(C),U=>f(C,U)),u(B,ue)},$$slots:{actions:!0,default:!0}}),p(E);var H=$(E,2);{var D=v=>{uc(v,{get cases(){return e(I)},get x(){return e(T)},get y(){return e(q)},get currentValue(){return e(O)},onClose:G})};Q(H,v=>{e(F)&&v(D)})}le("keydown",E,Z),u(t,ae),Ae()}Ke(["keydown","input"]);var yc=S('
      ');function Qt(t,r){var a=yc(),n=k(a);ft(n,()=>r.children),p(a),u(t,a)}function Cn(t){return t.map(r=>({value:r,label:r}))}var wc=S(''),kc=S(" ",1);function Sn(t,r){Me(r,!0);let a=me(r,"columnLabel",3,"Columns"),n=me(r,"xMinLabel",3,"Iter min"),s=j(()=>Cn(r.allCases)),i=j(()=>r.columns.map(d=>({value:d,label:d}))),o=[{value:"zero",label:"Zero"},{value:"restart",label:"Restart start"},{value:"custom",label:"Custom"}];function l(d){r.onXMinChange(Number(d.target.value)||0)}Qt(t,{children:(d,b)=>{var x=kc(),A=K(x);Ne(A,{text:"Cases",children:(y,C)=>{Gt(y,{class:"w-[160px]",get options(){return e(s)},get selected(){return r.selectedCases},get onchange(){return r.onCasesChange},placeholder:"Select cases..."})}});var m=$(A,2);{var N=y=>{var C=be(),_=K(C);ft(_,()=>r.middleSlot),u(y,C)};Q(m,y=>{r.middleSlot&&y(N)})}var L=$(m,2);Ne(L,{get text(){return a()},children:(y,C)=>{Gt(y,{class:"w-[160px]",get options(){return e(i)},get selected(){return r.selectedColumns},get onchange(){return r.onColumnsChange},placeholder:"Select..."})}});var g=$(L,2);Ne(g,{text:"Start from",children:(y,C)=>{lt(y,{class:"w-[140px]",get options(){return o},get value(){return r.startFrom},onchange:_=>r.onStartFromChange(_)})}});var c=$(g,2);{var w=y=>{Ne(y,{get text(){return n()},children:(C,_)=>{var M=wc();Xe(M),J(()=>vn(M,r.xMin)),le("input",M,l),u(C,M)}})};Q(c,y=>{r.startFrom==="custom"&&y(w)})}var P=$(c,2);{var h=y=>{var C=be(),_=K(C);ft(_,()=>r.extraSlot),u(y,C)};Q(P,y=>{r.extraSlot&&y(h)})}u(d,x)}}),Ae()}Ke(["input"]);var Cc=S('
      '),Sc=S('
      ');function Zt(t,r){let a=me(r,"emptyMessage",3,"");var n=Sc(),s=k(n);{var i=l=>{var d=be(),b=K(d);zr(b,()=>r.svgHtml),u(l,d)},o=l=>{var d=Cc(),b=k(d),x=k(b,!0);p(b),p(d),J(()=>ne(x,a()||"No data to display.")),u(l,d)};Q(s,l=>{r.svgHtml?l(i):l(o,-1)})}p(n),J(()=>we(n,"id",r.id)),u(t,n)}async function $n(t,r="plot.png"){const a=document.getElementById(t),n=a==null?void 0:a.querySelector("svg");if(!n)return;const{width:s,height:i}=$c(n),o=window.devicePixelRatio||1,l=document.createElement("canvas");l.width=s*o,l.height=i*o;const d=l.getContext("2d");if(!d)return;d.scale(o,o);const b=new XMLSerializer().serializeToString(n),x=new Blob([b],{type:"image/svg+xml;charset=utf-8"}),A=URL.createObjectURL(x),m=new Image;m.width=s,m.height=i,await new Promise((L,g)=>{m.onload=()=>{d.drawImage(m,0,0,s,i),URL.revokeObjectURL(A),L()},m.onerror=g,m.src=A});const N=await new Promise(L=>l.toBlob(L,"image/png"));N&&await Ra(N,Pc(r))}function xr(t,r,a="png"){const n=r.length<=3?r.join("_"):`${r.length}_cases`,s=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19);return Ec(`${t}_${n}_${s}.${a}`)}async function Ea(t,r){const a=new Blob([t],{type:"text/csv;charset=utf-8"});await Ra(a,r)}function $c(t){const r=t.getAttribute("viewBox");if(r){const a=r.split(/[\s,]+/).map(Number);if(a.length===4)return{width:a[2],height:a[3]}}return{width:t.width.baseVal.value||900,height:t.height.baseVal.value||500}}async function Ra(t,r){if("showSaveFilePicker"in window)try{const i=await(await window.showSaveFilePicker({suggestedName:r,types:[{description:t.type.startsWith("image/")?"Image":"File",accept:{[t.type]:[`.${r.split(".").pop()}`]}}]})).createWritable();await i.write(t),await i.close();return}catch(s){if((s==null?void 0:s.name)==="AbortError")return}const a=URL.createObjectURL(t),n=document.createElement("a");n.href=a,n.download=r,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(a)}function Pc(t){return t.endsWith(".png")?t:`${t}.png`}function Ec(t){return t.replace(/[^a-zA-Z0-9._-]/g,"_")}let Na=z(ie([])),Ma=z(ie([])),Aa=z(ie([])),La=z("zero"),Ta=z(0),Fa=z(""),Rc=z(!0);function Nc(){return{selectedCases:e(Na),columns:e(Ma),selectedColumns:e(Aa),startFrom:e(La),iterMin:e(Ta),svgHtml:e(Fa),autoRefresh:e(Rc)}}function Yn(t){f(Na,t,!0)}function Mc(t){f(Ma,t,!0)}function Xr(t){f(Aa,t,!0)}function Ac(t){f(La,t,!0)}function Lc(t){f(Ta,t,!0)}function $r(t){f(Fa,t,!0)}var Tc=S(" Refresh",1),Fc=S(" ",1),Dc=S(" Download as PNG",1),zc=S('
      '),Ic=S('
      ',1),Oc=S('
      '),Hc=S('

      '),jc=S(" ",1);function Vc(t,r){Me(r,!0);let a=j(Nc),n=z("");const s=new Set(["iteration","wall_distance","walldistance"]),i=["velocity","pressure"];async function o(){if(e(a).selectedCases.length)try{const g=await ii(e(a).selectedCases);f(n,"");const c=g.filter(w=>!s.has(w.toLowerCase().replace(/\s+/g,"_")));if(Mc(c),c.length===0){Xr([]),$r("");return}if(e(a).selectedColumns.length===0){const w=c.filter(P=>i.some(h=>P.toLowerCase().includes(h)));Xr(w.length>0?w:[c[0]])}}catch(g){f(n,"Failed to load residual columns"),console.error("Failed to load residual columns:",g)}}async function l(){if(e(a).selectedCases.length&&!((e(a).columns.length===0||e(a).selectedColumns.length===0)&&(await o(),!e(a).selectedColumns.length)))try{let g=0,c=!0;if(e(a).startFrom==="restart"){const P=await wa(e(a).selectedCases),h=Object.values(P.origins).map(y=>y.iteration).filter(y=>y!==void 0&&Number.isFinite(y));g=h.length>0?Math.min(...h):0}else e(a).startFrom==="custom"&&(g=e(a).iterMin);const w=await li(e(a).selectedCases,e(a).selectedColumns,{xMin:g,includeHistory:c});f(n,""),w&&$r(w)}catch(g){f(n,"Failed to load residual plot"),console.error("Failed to load residual plot:",g)}}function d(g){if(Yn(g),g.length===0){$r("");return}o(),l()}function b(g){if(Xr(g),g.length===0){$r("");return}l()}function x(g){Ac(g),l()}function A(g){Lc(g),l()}let m=z(ie(tr("plot")));Re(()=>{rr("plot",e(m))}),Re(()=>{e(m)?mr("residualPlot",l,jn()):vt("residualPlot")});let N=!1;Re(()=>{r.allCases.length>0&&!N&&(N=!0,Yn([...r.allCases]),o().then(()=>l()))});const L=_r(()=>{N&&o().then(()=>l())});vr(()=>{vt("residualPlot"),L()}),Ht(t,{eyebrow:"Diagnostics",title:"Residuals Plot",wide:!0,actions:c=>{var w=Fc(),P=K(w);{let C=j(jn);nr(P,{name:"residualPlot",get intervalMs(){return e(C)},onRefresh:l,get checked(){return e(m)},set checked(_){f(m,_,!0)}})}var h=$(P,2);{var y=C=>{$e(C,{variant:"primary",onclick:l,children:(_,M)=>{var F=Tc(),T=K(F);Te(T,{get icon(){return er}}),ke(),u(_,F)},$$slots:{default:!0}})};Q(h,C=>{e(m)||C(y)})}u(c,w)},children:(c,w)=>{var P=jc(),h=K(P);{var y=F=>{const T=G=>{var Z=zc(),ae=k(Z);{let E=j(()=>!e(a).svgHtml);$e(ae,{variant:"secondary",size:"sm",onclick:()=>$n("plot-holder",xr("residuals",e(a).selectedCases)),get disabled(){return e(E)},children:(R,H)=>{var D=Dc(),v=K(D);Te(v,{get icon(){return pr}}),ke(),u(R,D)},$$slots:{default:!0}})}p(Z),u(G,Z)};var q=Ic(),I=K(q);Sn(I,{prefix:"plot",get allCases(){return r.allCases},get selectedCases(){return e(a).selectedCases},onCasesChange:d,columnLabel:"Variables",get columns(){return e(a).columns},get selectedColumns(){return e(a).selectedColumns},onColumnsChange:b,get startFrom(){return e(a).startFrom},onStartFromChange:x,xMinLabel:"Iter min",get xMin(){return e(a).iterMin},onXMinChange:A,get extraSlot(){return T}});var O=$(I,2),V=k(O);{let G=j(()=>e(a).selectedCases.length===0?"Please select at least one case.":e(a).selectedColumns.length===0?"Please select at least one variable.":"No data to display.");Zt(V,{id:"plot-holder",get svgHtml(){return e(a).svgHtml},get emptyMessage(){return e(G)}})}p(O),u(F,q)},C=F=>{var T=Oc(),q=k(T);Zt(q,{id:"plot-holder",svgHtml:"",emptyMessage:"No data available. Please run a simulation first."}),p(T),u(F,T)};Q(h,F=>{e(a).columns.length>0?F(y):F(C,-1)})}var _=$(h,2);{var M=F=>{var T=Hc(),q=k(T,!0);p(T),J(()=>ne(q,e(n))),u(F,T)};Q(_,F=>{e(n)&&F(M)})}u(c,P)},$$slots:{actions:!0,default:!0}}),Ae()}function Da(t){const{scope:r,getState:a,setState:n,setHasData:s,getAxis:i}=t;async function o(){const m=a();if(m.selectedCases.length)try{let N=await Jr(m.selectedCases,r);if(t.filterFiles&&(N=t.filterFiles(N)),n({files:N}),s(N.length>0),N.length===0){n({file:"",columns:[],selectedColumns:[],positionText:"",svgHtml:""});return}(!m.file||!N.includes(m.file))&&(n({file:N[0]}),await l())}catch(N){console.error(`Failed to load ${r} files:`,N)}}async function l(){const m=a();if(!(!m.selectedCases.length||!m.file))try{const N=await vi(m.selectedCases,[m.file]),L=t.filterColumns?t.filterColumns(N):N;t.onColumnsLoaded?t.onColumnsLoaded(N):n({columns:L});const g=a();if(g.columns.length===0){n({selectedColumns:[],positionText:"",svgHtml:""});return}g.selectedColumns.length===0&&n({selectedColumns:[g.columns[0]]})}catch(N){console.error(`Failed to load ${r} columns:`,N)}}async function d(){let m=a();if(m.selectedCases.length){if(await o(),m=a(),!m.file){n({positionText:"",svgHtml:""});return}if((m.columns.length===0||m.selectedColumns.length===0)&&(await l(),m=a(),!m.selectedColumns.length)){n({positionText:"",svgHtml:""});return}try{let N;const L=a();if(L.startFrom==="restart"){const c=await wa(L.selectedCases),w=i().toLowerCase(),P=w==="t"||w==="time"?"time":"iteration",h=Object.values(c.origins).map(y=>y[P]).filter(y=>y!==void 0&&Number.isFinite(y));N=h.length>0?Math.min(...h):void 0}else L.startFrom==="custom"&&(N=L.xMin);const g=await hi({cases:L.selectedCases,probes:[L.file],columns:L.selectedColumns,axis:i(),timeMin:typeof N=="number"?N:void 0,xMin:N,includeHistory:!0});g&&n({svgHtml:g})}catch(N){console.error(`Failed to load ${r} plot:`,N)}}}async function b(m){if(n({selectedCases:m}),m.length===0){n({svgHtml:""});return}await o(),r==="monitoring"&&await d()}function x(m){if(n({selectedColumns:m}),m.length===0){n({svgHtml:""});return}d()}async function A(m){n({file:m}),await l(),await d()}return{loadFiles:o,loadColumns:l,loadPlot:d,handleCasesChange:b,handleColumnsChange:x,handleFileChange:A}}function za(){return{selectedCases:[],file:"",files:[],columns:[],selectedColumns:[],axis:"time",startFrom:"zero",xMin:0,positionText:"",svgHtml:"",autoRefresh:!0}}let Bt=z("time"),on=z(ie(za())),ln=z(ie({...za(),axis:""})),Uc=z(!0),Tr=z(!1),Fr=z(!1);function Bc(){return e(Bt)}function qc(t){f(Bt,t,!0)}function Kc(){return e(Tr)}function cn(t){f(Tr,t,!0)}function Gc(){return e(Fr)}function un(t){f(Fr,t,!0)}function Wc(){e(Bt)==="time"&&!e(Tr)&&e(Fr)?f(Bt,"profile"):e(Bt)==="profile"&&!e(Fr)&&e(Tr)&&f(Bt,"time")}function Qn(){return e(on)}function Yr(){return e(ln)}function kt(t){f(on,{...e(on),...t},!0)}function At(t){f(ln,{...e(ln),...t},!0)}function Xc(){return e(Uc)}var Yc=S(" Download as PNG",1),Qc=S('
      '),Zc=S("
      "),Jc=S('
      '),eu=S('
      ',1),tu=S('
      ');function ru(t,r){Me(r,!0);let a=me(r,"onRefresh",15),n=j(Qn);const s=new Set(["time","t","iteration","iter"]),i=Da({scope:"monitoring",getState:Qn,setState:kt,setHasData:cn,filterFiles:g=>g.filter(c=>{const w=c.replace(/\.csv$/i,"").toLowerCase();return w!=="coords"&&!w.includes("coordinates")}),filterColumns:g=>g.filter(c=>!s.has(c.toLowerCase())),onColumnsLoaded:g=>{const c=g.filter(w=>!s.has(w.toLowerCase()));kt({columns:c}),o()},getAxis:()=>"time"});async function o(){if(!(!e(n).selectedCases.length||!e(n).file||!e(n).selectedColumns.length))try{const g=[];for(const C of e(n).selectedColumns){const _=await gi(e(n).selectedCases[0],e(n).file,[C]);_.found&&g.push({col:C,x:_.x,y:_.y,z:_.z})}if(!g.length){kt({positionText:""});return}const c=C=>{let _=0;for(const M of C){if(M===void 0)continue;const F=String(M),T=F.indexOf(".");T>=0&&(_=Math.max(_,F.length-T-1))}return _},w=c(g.map(C=>C.x)),P=c(g.map(C=>C.y)),h=c(g.map(C=>C.z)),y=g.map(C=>{const _=[];return C.x!==void 0&&_.push(`x=${C.x.toFixed(w)}`),C.y!==void 0&&_.push(`y=${C.y.toFixed(P)}`),C.z!==void 0&&_.push(`z=${C.z.toFixed(h)}`),`Probe position [${C.col}] : ${_.join(" · ")}`});kt({positionText:y.join(` -`)})}catch{kt({positionText:""})}}function l(g){i.handleColumnsChange(g),g.length>0&&o()}let d=j(()=>e(n).files.map(g=>({value:g,label:g.replace(/\.csv$/i,"").replace(/^probes?_/i,"")}))),b=!1;Re(()=>{r.allCases.length>0&&!b&&(b=!0,kt({selectedCases:[...r.allCases]}),i.loadFiles().then(()=>i.loadPlot()))}),Re(()=>{a(i.loadPlot)});let x=j(()=>e(n).selectedCases.length===0?"Please select at least one case.":e(n).selectedColumns.length===0?"Please select at least one probe.":"No data to display.");var A=be(),m=K(A);{var N=g=>{const c=F=>{Ne(F,{text:"Quantity",children:(T,q)=>{lt(T,{class:"w-[160px]",get options(){return e(d)},get value(){return e(n).file},get onchange(){return i.handleFileChange}})}})},w=F=>{var T=Qc(),q=k(T);{let I=j(()=>!e(n).svgHtml);$e(q,{variant:"secondary",size:"sm",onclick:()=>$n("probe-plot-holder",xr("probe",e(n).selectedCases)),get disabled(){return e(I)},children:(O,V)=>{var G=Yc(),Z=K(G);Te(Z,{get icon(){return pr}}),ke(),u(O,G)},$$slots:{default:!0}})}p(T),u(F,T)};var P=eu(),h=K(P);Sn(h,{prefix:"probe",get allCases(){return r.allCases},get selectedCases(){return e(n).selectedCases},get onCasesChange(){return i.handleCasesChange},get middleSlot(){return c},columnLabel:"Probes",get columns(){return e(n).columns},get selectedColumns(){return e(n).selectedColumns},onColumnsChange:l,get startFrom(){return e(n).startFrom},onStartFromChange:F=>{kt({startFrom:F}),i.loadPlot()},xMinLabel:"Time min",get xMin(){return e(n).xMin},onXMinChange:F=>{kt({xMin:F}),i.loadPlot()},get extraSlot(){return w}});var y=$(h,2);{var C=F=>{var T=Jc();Ye(T,21,()=>e(n).positionText.split(` -`).sort(),fn,(q,I)=>{var O=Zc(),V=k(O,!0);p(O),J(()=>ne(V,e(I))),u(q,O)}),p(T),u(F,T)};Q(y,F=>{e(n).positionText&&F(C)})}var _=$(y,2),M=k(_);Zt(M,{id:"probe-plot-holder",get svgHtml(){return e(n).svgHtml},get emptyMessage(){return e(x)}}),p(_),u(g,P)},L=g=>{var c=tu(),w=k(c);Zt(w,{id:"probe-plot-holder",svgHtml:"",emptyMessage:"No probe data available. Please run a simulation first."}),p(c),u(g,c)};Q(m,g=>{e(n).files.length>0?g(N):g(L,-1)})}u(t,A),Ae()}var nu=S(" ",1),au=S(" Download as PNG",1),su=S('
      '),ou=S('
      ',1),iu=S('
      ');function lu(t,r){Me(r,!0);let a=me(r,"onRefresh",15),n=j(Yr);const s=["s","x","abscissa","distance","arclength","arc_length","curvilinear","length","r","y","z","coord","position"];function i(c){const w=c.map(P=>P.toLowerCase());for(const P of s){const h=w.indexOf(P);if(h>=0)return c[h]}return c[0]??""}const o=Da({scope:"profiles",getState:Yr,setState:At,setHasData:un,onColumnsLoaded:c=>{const w=e(n).axis||i(c),P=c.filter(h=>h!==w).toSorted((h,y)=>h.localeCompare(y));At({columns:P,axis:w,selectedColumns:e(n).selectedColumns.length?e(n).selectedColumns:P.length?[P[0]]:[]})},getAxis:()=>Yr().axis});function l(c){const w=e(n).axis;At({axis:c});const h=[...e(n).columns,...w?[w]:[]].filter(y=>y!==c).toSorted((y,C)=>y.localeCompare(C));At({columns:h}),o.loadPlot()}let d=j(()=>e(n).files.map(c=>({value:c,label:c.replace(/\.csv$/i,"").replace(/^profiles\//i,"").replace(/_/g," ")}))),b=j(()=>[...e(n).axis?[e(n).axis]:[],...e(n).columns].toSorted((c,w)=>c.localeCompare(w)).map(c=>({value:c,label:c}))),x=!1;Re(()=>{r.allCases.length>0&&!x&&(x=!0,At({selectedCases:[...r.allCases]}),o.loadFiles().then(()=>o.loadPlot()))}),Re(()=>{a(o.loadPlot)});let A=j(()=>e(n).selectedCases.length===0?"Please select at least one case.":e(n).selectedColumns.length===0?"Please select at least one value.":"No data to display.");var m=be(),N=K(m);{var L=c=>{const w=M=>{var F=nu(),T=K(F);Ne(T,{text:"Profile",children:(I,O)=>{lt(I,{class:"w-[160px]",get options(){return e(d)},get value(){return e(n).file},get onchange(){return o.handleFileChange}})}});var q=$(T,2);Ne(q,{text:"X axis",children:(I,O)=>{lt(I,{class:"w-[140px]",get options(){return e(b)},get value(){return e(n).axis},onchange:l})}}),u(M,F)},P=M=>{var F=su(),T=k(F);{let q=j(()=>!e(n).svgHtml);$e(T,{variant:"secondary",size:"sm",onclick:()=>$n("profile-plot-holder",xr("profile",e(n).selectedCases)),get disabled(){return e(q)},children:(I,O)=>{var V=au(),G=K(V);Te(G,{get icon(){return pr}}),ke(),u(I,V)},$$slots:{default:!0}})}p(F),u(M,F)};var h=ou(),y=K(h);Sn(y,{prefix:"profile",get allCases(){return r.allCases},get selectedCases(){return e(n).selectedCases},get onCasesChange(){return o.handleCasesChange},get middleSlot(){return w},columnLabel:"Values",get columns(){return e(n).columns},get selectedColumns(){return e(n).selectedColumns},get onColumnsChange(){return o.handleColumnsChange},get startFrom(){return e(n).startFrom},onStartFromChange:M=>{At({startFrom:M}),o.loadPlot()},xMinLabel:"X min",get xMin(){return e(n).xMin},onXMinChange:M=>{At({xMin:M}),o.loadPlot()},get extraSlot(){return P}});var C=$(y,2),_=k(C);Zt(_,{id:"profile-plot-holder",get svgHtml(){return e(n).svgHtml},get emptyMessage(){return e(A)}}),p(C),u(c,h)},g=c=>{var w=iu(),P=k(w);Zt(P,{id:"profile-plot-holder",svgHtml:"",emptyMessage:"No profile data available. Please run a simulation first."}),p(w),u(c,w)};Q(N,c=>{e(n).files.length>0?c(L):c(g,-1)})}u(t,m),Ae()}var cu=S('
      Diagnostics
      '),uu=S(" Refresh",1),du=S(" ",1);function fu(t,r){Me(r,!0);let a=j(Bc),n=j(Xc),s=j(Kc),i=j(Gc),o=z(ie(tr("probe")));Re(()=>{rr("probe",e(o))});let l=z(void 0);function d(){var L;(L=e(l))==null||L()}async function b(){if(r.allCases.length){try{const g=(await Jr(r.allCases,"monitoring")).filter(c=>{const w=c.replace(/\.csv$/i,"").toLowerCase();return w!=="coords"&&!w.includes("coordinates")});cn(g.length>0)}catch{cn(!1)}try{const L=await Jr(r.allCases,"profiles");un(L.length>0)}catch{un(!1)}}}Re(()=>{r.allCases.length>0&&b()}),_r(()=>{b(),d()}),Re(()=>{e(s),e(i),Wc()});function x(L){L==="time"&&!e(s)||L==="profile"&&!e(i)||qc(L)}var A=be(),m=K(A);{var N=L=>{{const g=w=>{var P=cu(),h=$(k(P),2),y=k(h),C=$(y,2);p(h),p(P),J(()=>{Se(y,1,`bg-transparent border-none p-0 m-0 text-lg font-bold tracking-tight transition-colors duration-150 - ${e(s)?e(a)==="time"?"text-edf-bleu-fonce cursor-pointer":"text-edf-gris-moyen cursor-pointer hover:text-muted":"text-edf-gris-moyen cursor-not-allowed opacity-50"}`),y.disabled=!e(s),we(y,"title",e(s)?"":"No probe data available. Run a simulation first."),Se(C,1,`bg-transparent border-none p-0 m-0 text-lg font-bold tracking-tight transition-colors duration-150 - ${e(i)?e(a)==="profile"?"text-edf-bleu-fonce cursor-pointer":"text-edf-gris-moyen cursor-pointer hover:text-muted":"text-edf-gris-moyen cursor-not-allowed opacity-50"}`),C.disabled=!e(i),we(C,"title",e(i)?"":"No profile data available. Run a simulation first.")}),le("click",y,()=>x("time")),le("click",C,()=>x("profile")),u(w,P)};Ht(L,{wide:!0,titleSlot:g,tabs:g,actions:w=>{var P=du(),h=K(P);{let _=j(()=>e(a)==="time"?"probePlot":"profilePlot"),M=j(Gi);nr(h,{get name(){return e(_)},get intervalMs(){return e(M)},onRefresh:d,get checked(){return e(o)},set checked(F){f(o,F,!0)}})}var y=$(h,2);{var C=_=>{$e(_,{variant:"primary",onclick:d,children:(M,F)=>{var T=uu(),q=K(T);Te(q,{get icon(){return er}}),ke(),u(M,T)},$$slots:{default:!0}})};Q(y,_=>{e(o)||_(C)})}u(w,P)},children:(w,P)=>{var h=be(),y=K(h);{var C=M=>{ru(M,{get allCases(){return r.allCases},get onRefresh(){return e(l)},set onRefresh(F){f(l,F,!0)}})},_=M=>{lu(M,{get allCases(){return r.allCases},get onRefresh(){return e(l)},set onRefresh(F){f(l,F,!0)}})};Q(y,M=>{e(a)==="time"?M(C):M(_,-1)})}u(w,h)},$$slots:{tabs:!0,actions:!0,default:!0}})}};Q(m,L=>{e(n)&&L(N)})}u(t,A),Ae()}Ke(["click"]);var vu=S(" Refresh",1),gu=S(" ",1),hu=S(" Download as CSV",1),pu=S('
      ',1),mu=S(" "),_u=S(' '),bu=S(' '),xu=S('
      Case
      '),yu=S('

      '),wu=S(" ",1);function ku(t,r){Me(r,!0);let a=z(ie([])),n=z(ie([])),s=z(!1),i=z(!1),o=z(ie(tr("perf")));Re(()=>{rr("perf",e(o))});const l=5e3;let b=z(ie([{key:"elapsed_time",label:"Elapsed (s)",kind:"time"},{key:"io_time",label:"I/O (s)",kind:"time"},{key:"linear_solver_time",label:"Linear Solver (s)",kind:"time"},{key:"gradients_time",label:"Gradients (s)",kind:"time"},{key:"balances_time",label:"Balances (s)",kind:"time"},{key:"mpi_ranks",label:"MPI Ranks",kind:"int"},{key:"threads",label:"Threads",kind:"int"}])),x=j(()=>r.allCases.map(P=>({value:P,label:P}))),A=j(()=>e(n).length>0);async function m(){var P;if(e(a).length){f(i,!0);try{const h=await In(e(a));(P=h.columns)!=null&&P.length&&f(b,h.columns,!0),f(n,h.records,!0),e(n).length>0&&f(s,!0)}catch(h){console.error("Failed to load perf:",h)}f(i,!1)}}function N(P){if(f(a,P,!0),P.length===0){f(n,[],!0);return}m()}function L(P,h){if(h==null||h==="")return"-";if(P==="text")return h;const y=Number(h);return Number.isFinite(y)?P==="int"?String(Math.round(y)):y.toFixed(3):h}async function g(){if(!e(n).length)return;const P=["case_id",...e(b).map(_=>_.key)],h=e(n).map(_=>[_.case_id,...e(b).map(M=>_[M.key]??"")].join(",")),y=[P.join(","),...h].join(` -`),C=xr("timing_snapshot",e(a),"csv");await Ea(y,C)}let c=!1;Re(()=>{r.allCases.length>0&&!c&&(c=!0,f(a,[...r.allCases],!0),In(r.allCases).then(P=>{var h;(h=P.columns)!=null&&h.length&&f(b,P.columns,!0),f(s,P.records.length>0),f(n,P.records,!0)}).catch(()=>{}))}),Re(()=>{e(o)?mr("perf",m,l):vt("perf")});const w=_r(()=>{c&&m()});vr(()=>{vt("perf"),w()}),Ht(t,{eyebrow:"Performance",title:"Timing Snapshot",wide:!0,actions:h=>{var y=gu(),C=K(y);nr(C,{name:"perf",intervalMs:l,onRefresh:m,get checked(){return e(o)},set checked(F){f(o,F,!0)}});var _=$(C,2);{var M=F=>{$e(F,{variant:"primary",onclick:m,children:(T,q)=>{var I=vu(),O=K(I);Te(O,{get icon(){return er}}),ke(),u(T,I)},$$slots:{default:!0}})};Q(_,F=>{e(o)||F(M)})}u(h,y)},children:(h,y)=>{var C=wu(),_=K(C);{var M=I=>{Qt(I,{children:(O,V)=>{var G=pu(),Z=K(G);Ne(Z,{text:"Cases",children:(R,H)=>{Gt(R,{class:"w-[160px]",get options(){return e(x)},get selected(){return e(a)},onchange:N,placeholder:"Select cases..."})}});var ae=$(Z,2),E=k(ae);{let R=j(()=>!e(A));$e(E,{variant:"secondary",size:"sm",onclick:g,get disabled(){return e(R)},children:(H,D)=>{var v=hu(),B=K(v);Te(B,{get icon(){return pr}}),ke(),u(H,v)},$$slots:{default:!0}})}p(ae),u(O,G)}})};Q(_,I=>{e(s)&&I(M)})}var F=$(_,2);{var T=I=>{var O=xu(),V=k(O),G=k(V),Z=k(G),ae=$(k(Z));Ye(ae,17,()=>e(b),R=>R.key,(R,H)=>{var D=mu(),v=k(D,!0);p(D),J(()=>ne(v,e(H).label)),u(R,D)}),p(Z),p(G);var E=$(G);Ye(E,21,()=>e(n),R=>R.case_id,(R,H)=>{var D=bu(),v=k(D),B=k(v,!0);p(v);var X=$(v);Ye(X,17,()=>e(b),ue=>ue.key,(ue,fe)=>{var W=_u(),te=k(W,!0);p(W),J(se=>ne(te,se),[()=>L(e(fe).kind,e(H)[e(fe).key])]),u(ue,W)}),p(D),J(()=>ne(B,e(H).case_id)),u(R,D)}),p(E),p(V),p(O),u(I,O)},q=I=>{var O=yu(),V=k(O);{var G=ae=>{var E=Ve("Select cases above to view timing information.");u(ae,E)},Z=ae=>{var E=Ve("No performance data available. Please run a simulation first.");u(ae,E)};Q(V,ae=>{e(s)?ae(G):ae(Z,-1)})}p(O),u(I,O)};Q(F,I=>{e(A)?I(T):I(q,-1)})}u(h,C)},$$slots:{actions:!0,default:!0}}),Ae()}const Cu={"&":"&","<":"<",">":">",'"':""","'":"'"};function Ia(t){return t.replace(/[&<>"']/g,r=>Cu[r]??r)}var Su=S("
      "),$u=S('
      ');function Pu(t,r){Me(r,!0);let a=z(void 0);function n(b){const x=Ia(b);if(!r.searchQuery.trim())return x;try{const A=new RegExp(`(${r.searchQuery})`,"gi");return x.replace(A,'$1')}catch{return x}}function s(b){return b==="error"?"tail-sev-error":b==="warn"?"tail-sev-warn":b==="info"?"tail-sev-info":""}function i(){return e(a)?e(a).scrollHeight-e(a).scrollTop-e(a).clientHeight<=24:!0}let o=z(!0);function l(){f(o,i(),!0)}Re(()=>{r.autoScroll&&r.lines.length>0&&e(a)&&e(o)&&Pt().then(()=>{e(a)&&(e(a).scrollTop=e(a).scrollHeight)})});var d=$u();Ye(d,21,()=>r.lines,b=>b.index,(b,x)=>{var A=Su();zr(A,()=>n(e(x).text),!0),p(A),J(m=>Se(A,1,`tail-line ${m??""} ${e(x).isNew?"tail-new":""}`),[()=>s(e(x).severity)]),u(b,A)}),p(d),nt(d,b=>f(a,b),()=>e(a)),tt("scroll",d,l),u(t,d),Ae()}const Eu=/(fatal error|error detected|error reading|error writing|segmentation fault|sigterm|sigsegv|sigkill|abort|killed|terminated|core dumped|traceback|exception|errno)/i,Ru=/(warning|divergence|non-convergence|clipping|negative|nan detected|overflow|underflow)/i;function Nu(t){return Eu.test(t)?"error":Ru.test(t)?"warn":"none"}function Mu(t,r){return r==="all"||r===""?!0:r==="info"?t!=="none":t===r}var Au=S(" Refresh",1),Lu=S(" ",1),Tu=S(''),Fu=S(''),Du=S("    ",1),zu=Ot(''),Iu=Ot(''),Ou=Ot(''),Hu=S(" Resume",1),ju=S(" Pause",1),Vu=S('Case not running'),Uu=S(' 
      ',1),Bu=S('

      No log data available. Please run a simulation first.

      '),qu=S('

      '),Ku=S(" ",1);function Gu(t,r){Me(r,!0);const a=80;let n=z(""),s=z(""),i=z(!1),o=z(ie([])),l=z(a),d=z(ie([])),b=z(ie(new Set)),x=z(!1),A=z(ie(tr("tail")));Re(()=>{rr("tail",e(A))});let m=z(!0),N=z("all"),L=z(""),g=z(""),c=z(void 0),w=j(()=>Cn(r.allCases)),P=j(()=>e(o).map(E=>{const R=E.split("/").pop()??E;return{value:E,label:R}})),h=[{value:"all",label:"All"},{value:"error",label:"Error"},{value:"warn",label:"Warn"}],y=j(()=>e(o).length>0),C=j(()=>yn().some(E=>{var R;return E.case_id===e(n)&&((R=E.status)==null?void 0:R.toUpperCase())==="RUNNING"}));const _={"run_solver.log":1,listing:2,"run_status.running":3,"csauto.stdout":4,"csauto.stderr":5,"performance.log":6};function M(E){const R=E.split("/").pop()??E;return _[R]??99}async function F(){if(e(n))try{const H=(await di(e(n))).filter(D=>{const v=D.split("/").pop()??D;return D.endsWith(".log")||D.endsWith("/summary")||v in _}).sort((D,v)=>M(D)-M(v));if(f(o,H,!0),H.length>0){const D=H[0];!e(s)||!H.includes(e(s))?(f(s,D,!0),f(i,!1)):!e(i)&&M(D)0&&H[H.length-1]===""?H.slice(0,-1):H;if(e(d).length>0){const v=q(e(d),D),B=new Set;for(let X=v;X{let E=e(d).map((R,H)=>({text:R,index:H,severity:Nu(R),isNew:e(b).has(H)}));if(e(N)!=="all"&&(E=E.filter(R=>Mu(R.severity,e(N)))),e(L).trim())try{const R=new RegExp(e(L),"i");E=E.filter(H=>R.test(H.text))}catch{}return E});function O(E){f(n,E,!0),f(s,""),f(i,!1),f(d,[],!0),F().then(()=>T(!0))}function V(E){f(s,E,!0),f(i,!0),f(d,[],!0),T(!0)}function G(){f(x,!e(x)),e(x)||T(!0)}function Z(E){(E.ctrlKey||E.metaKey)&&E.key==="f"&&e(c)&&(E.preventDefault(),e(c).focus())}Re(()=>{r.allCases.length>0&&!e(n)&&(f(n,r.allCases[0],!0),F().then(()=>T(!0)))}),Re(()=>{e(A)&&!e(x)&&e(n)?mr("tail",()=>T(!1),Hn()):vt("tail")});const ae=_r(()=>{e(n)&&F().then(()=>T(!0))});vr(()=>{vt("tail"),ae()}),tt("keydown",it,Z),Ht(t,{eyebrow:"Live",title:"Log Tail",wide:!0,actions:R=>{var H=Lu(),D=K(H);{let X=j(Hn);nr(D,{name:"tail",get intervalMs(){return e(X)},onRefresh:()=>T(!1),get checked(){return e(A)},set checked(ue){f(A,ue,!0)}})}var v=$(D,2);{var B=X=>{$e(X,{variant:"primary",onclick:()=>T(!0),children:(ue,fe)=>{var W=Au(),te=K(W);Te(te,{get icon(){return er}}),ke(),u(ue,W)},$$slots:{default:!0}})};Q(v,X=>{e(A)||X(B)})}u(R,H)},children:(R,H)=>{var D=Ku(),v=K(D);{var B=se=>{Qt(se,{children:(de,ce)=>{var re=Du(),xe=K(re);Ne(xe,{text:"Case",children:(ze,Qe)=>{lt(ze,{class:"w-[130px]",get options(){return e(w)},get value(){return e(n)},onchange:O})}});var he=$(xe,2);Ne(he,{text:"File",children:(ze,Qe)=>{lt(ze,{class:"w-[150px]",get options(){return e(P)},get value(){return e(s)},onchange:V})}});var Ee=$(he,2);Ne(Ee,{text:"Lines",children:(ze,Qe)=>{var je=Tu();Xe(je),le("change",je,()=>T(!0)),et(je,()=>e(l),rt=>f(l,rt)),u(ze,je)}});var pe=$(Ee,2);Ne(pe,{text:"Filter",children:(ze,Qe)=>{var je=Fu();Xe(je),nt(je,rt=>f(c,rt),()=>e(c)),et(je,()=>e(L),rt=>f(L,rt)),u(ze,je)}});var Fe=$(pe,2);Ne(Fe,{text:"Severity",children:(ze,Qe)=>{lt(ze,{class:"w-[90px]",get options(){return h},get value(){return e(N)},onchange:je=>f(N,je,!0)})}}),u(de,re)}})};Q(v,se=>{e(y)&&se(B)})}var X=$(v,2);{var ue=se=>{var de=Uu(),ce=K(de);Pu(ce,{get lines(){return e(I)},get searchQuery(){return e(L)},get autoScroll(){return e(m)}});var re=$(ce,2),xe=k(re),he=k(xe);{var Ee=Y=>{var U=zu();u(Y,U)},pe=Y=>{var U=Iu();u(Y,U)},Fe=Y=>{var U=Ou();u(Y,U)};Q(he,Y=>{e(x)?Y(Ee):e(C)?Y(pe,1):Y(Fe,-1)})}var ze=$(he);p(xe);var Qe=$(xe,2);{var je=Y=>{$e(Y,{variant:"secondary",size:"sm",onclick:G,children:(U,ee)=>{var oe=be(),ve=K(oe);{var ye=_e=>{var Ce=Hu(),Ie=K(Ce);Te(Ie,{get icon(){return _a}}),ke(),u(_e,Ce)},ge=_e=>{var Ce=ju(),Ie=K(Ce);Te(Ie,{get icon(){return Bo}}),ke(),u(_e,Ce)};Q(ve,_e=>{e(x)?_e(ye):_e(ge,-1)})}u(U,oe)},$$slots:{default:!0}})},rt=Y=>{var U=Vu();u(Y,U)};Q(Qe,Y=>{e(C)?Y(je):Y(rt,-1)})}var Le=$(Qe,2),Pe=k(Le);It(Pe,{get checked(){return e(m)},onchange:Y=>f(m,Y,!0),size:14,label:"Auto-scroll",labelFirst:!0}),p(Le),p(re),J(()=>ne(ze,` ${e(I).length??""} / ${e(d).length??""}`)),u(se,de)},fe=se=>{var de=Bu();u(se,de)};Q(X,se=>{e(d).length>0?se(ue):se(fe,-1)})}var W=$(X,2);{var te=se=>{var de=qu(),ce=k(de,!0);p(de),J(()=>ne(ce,e(g))),u(se,de)};Q(W,se=>{e(g)&&se(te)})}u(R,D)},$$slots:{actions:!0,default:!0}}),Ae()}Ke(["change"]);function Wu(t,r=3){var N,L;const a=t.split(` -`),n=[];let s=0,i=0,o=!1;for(const g of a){if(g.startsWith("===")||g.startsWith("---")||g.startsWith("+++"))continue;const c=g.match(/^@@ -(\d+),?\d* \+(\d+),?\d* @@/);if(c){s=parseInt(c[1],10)-1,i=parseInt(c[2],10)-1,o=!0;continue}if(o)if(g.startsWith("-"))s++,n.push({type:"del",leftNum:s,rightNum:null,leftContent:g.slice(1),rightContent:""});else if(g.startsWith("+"))i++,n.push({type:"add",leftNum:null,rightNum:i,leftContent:"",rightContent:g.slice(1)});else{s++,i++;const w=g.startsWith(" ")?g.slice(1):g;n.push({type:"equal",leftNum:s,rightNum:i,leftContent:w,rightContent:w})}}const l=[];let d=0;for(;d=0&&g-A>1&&x.push({kind:"separator",skipped:g-A-1});const c=l[g].type!=="equal",w=c&&!m;x.push({kind:"line",row:l[g],hunkStart:w}),m=c,A=g}if(A=0&&x.push({kind:"separator",skipped:l.length-1-A}),x.length>0&&x[0].kind==="line"){const g=((N=x[0].row)==null?void 0:N.leftNum)??0,c=((L=x[0].row)==null?void 0:L.rightNum)??0,w=Math.max(g,c)-1;w>0&&x.unshift({kind:"separator",skipped:w})}return x}var Xu=S('
      '),Yu=S('
      '),Qu=S(' ',1),Zu=S('No matches'),Ju=S('
      '),ed=S('

      Files are identical.

      '),td=S('
      Loading...
      '),rd=S('
      ');function nd(t,r){Me(r,!0);let a=me(r,"loading",3,!1),n=z(void 0),s=j(()=>Wu(r.diffText)),i=j(()=>{const Y=[];return e(s).forEach((U,ee)=>{U.hunkStart&&Y.push(ee)}),Y}),o=j(()=>e(i).length>0),l=j(()=>{var U;const Y=new Array(e(s).length).fill(-1);for(let ee=0;ee{r.diffText!==b&&(b=r.diffText,f(d,-1))});let x=j(()=>{if(!r.search.trim())return[];try{const Y=new RegExp(r.search,"i"),U=[];return e(s).forEach((ee,oe)=>{ee.kind==="line"&&ee.row&&(Y.test(ee.row.leftContent)||Y.test(ee.row.rightContent))&&U.push(oe)}),U}catch{return[]}}),A=z(-1),m="";Re(()=>{r.search!==m&&(m=r.search,f(A,e(x).length>0?0:-1,!0))});async function N(Y){var oe;f(A,Y,!0),await Pt();const U=e(x)[Y],ee=(oe=e(h))==null?void 0:oe.querySelector(`[data-item-idx="${U}"]`);if(ee&&e(h)&&e(y)){const ve=e(h).getBoundingClientRect(),ge=ee.getBoundingClientRect().top-ve.top+e(h).scrollTop-e(h).clientHeight/3;_=!0,e(h).scrollTop=ge,e(y).scrollTop=ge,requestAnimationFrame(()=>{_=!1})}}function L(){e(A)>0&&N(e(A)-1)}function g(){e(A)<0&&e(x).length>0?N(0):e(A)$1`)}catch{return ee}}function w(Y,U){return Y==="equal"?"":Y==="del"&&U==="left"?"bg-[rgba(214,67,10,0.08)] text-edf-orange-fonce":Y==="del"&&U==="right"?"bg-[rgba(48,122,16,0.08)] text-edf-vert-fonce":Y==="add"&&U==="left"?"bg-edf-gris-clair":Y==="add"&&U==="right"?"bg-[rgba(48,122,16,0.08)] text-edf-vert-fonce":""}function P(Y,U){return Y==="del"&&U==="left"?"rgb(251,237,233)":Y==="del"&&U==="right"?"rgb(238,247,236)":Y==="add"&&U==="left"?"var(--color-edf-gris-clair)":Y==="add"&&U==="right"?"rgb(238,247,236)":"white"}let h=z(void 0),y=z(void 0),C=!1,_=!1;function M(Y){C||_||!e(h)||!e(y)||(C=!0,Y==="left"?(e(y).scrollTop=e(h).scrollTop,e(y).scrollLeft=e(h).scrollLeft):(e(h).scrollTop=e(y).scrollTop,e(h).scrollLeft=e(y).scrollLeft),requestAnimationFrame(()=>{C=!1}))}async function F(Y){var ee;f(d,Y,!0),await Pt();const U=(ee=e(h))==null?void 0:ee.querySelector(`[data-hunk-start="${Y}"]`);if(U&&e(h)&&e(y)){_=!0;const oe=e(h).getBoundingClientRect(),ye=U.getBoundingClientRect().top-oe.top+e(h).scrollTop-e(h).clientHeight/3;e(h).scrollTop=ye,e(y).scrollTop=ye,requestAnimationFrame(()=>{_=!1})}}function T(){e(d)>0&&F(e(d)-1)}function q(){e(d)<0?F(0):e(d)=0&&f(d,U,!0)}function O(Y){Y.target instanceof HTMLInputElement||(Y.key==="n"&&!Y.shiftKey?(Y.preventDefault(),q()):Y.key==="N"||Y.key==="n"&&Y.shiftKey?(Y.preventDefault(),T()):Y.key==="j"?(Y.preventDefault(),g()):(Y.key==="J"||Y.key==="j"&&Y.shiftKey)&&(Y.preventDefault(),L()))}var V=rd();{const Y=(U,ee=ws)=>{var oe=be(),ve=K(oe);Ye(ve,17,()=>e(s),fn,(ye,ge,_e)=>{const Ce=j(()=>e(i).indexOf(_e)),Ie=j(()=>e(l)[_e]>=0&&e(l)[_e]===e(d)),Ge=j(()=>{var at,qe;return ee()==="left"?(at=e(ge).row)==null?void 0:at.leftNum:(qe=e(ge).row)==null?void 0:qe.rightNum}),gt=j(()=>{var at,qe;return ee()==="left"?((at=e(ge).row)==null?void 0:at.leftContent)??"":((qe=e(ge).row)==null?void 0:qe.rightContent)??""});var jt=be(),jr=K(jt);{var Je=at=>{var qe=Xu(),Nt=$(k(qe),2),Vr=k(Nt);p(Nt),p(qe),J(()=>ne(Vr,`... ${e(ge).skipped??""} lines hidden ...`)),u(at,qe)},ut=at=>{var qe=Yu(),Nt=k(qe),Vr=k(Nt,!0);p(Nt);var Pn=$(Nt,2);zr(Pn,()=>c(e(gt),e(A)>=0&&e(x)[e(A)]===_e),!0),p(Pn),p(qe),J((yt,Mt,yr,Oa,Ha)=>{Se(qe,1,`flex w-full ${yt??""} ${Mt??""}`),Ze(qe,e(Ie)?"background-color: rgba(16,87,200,0.12); color: rgb(16,87,200);":""),we(qe,"role",yr),we(qe,"tabindex",Oa),we(qe,"data-hunk-start",ee()==="left"&&e(Ce)>=0?e(Ce):void 0),we(qe,"data-item-idx",ee()==="left"?_e:void 0),Ze(Nt,`background: ${Ha??""}; color: ${e(Ie)?"white":"var(--color-muted)"};`),ne(Vr,e(Ge)??"")},[()=>w(e(ge).row.type,ee()),()=>e(ge).row.type!=="equal"||e(x).includes(_e)?"cursor-pointer":"",()=>e(ge).row.type!=="equal"||e(x).includes(_e)?"button":void 0,()=>e(ge).row.type!=="equal"||e(x).includes(_e)?0:void 0,()=>e(Ie)?"rgb(16,87,200)":P(e(ge).row.type,ee())]),le("click",qe,()=>{var Mt;((Mt=e(ge).row)==null?void 0:Mt.type)!=="equal"&&I(_e);const yt=e(x).indexOf(_e);yt>=0&&f(A,yt,!0)}),le("keydown",qe,yt=>{var Mt;if(yt.key==="Enter"||yt.key===" "){yt.preventDefault(),((Mt=e(ge).row)==null?void 0:Mt.type)!=="equal"&&I(_e);const yr=e(x).indexOf(_e);yr>=0&&f(A,yr,!0)}}),u(at,qe)};Q(jr,at=>{e(ge).kind==="separator"?at(Je):e(ge).row&&at(ut,1)})}u(ye,jt)}),u(U,oe)};var G=k(V),Z=k(G),ae=k(Z),E=k(ae,!0);p(ae);var R=$(ae,2),H=k(R,!0);p(R),p(Z);var D=$(Z,2),v=k(D),B=k(v,!0);p(v);var X=$(v,2),ue=k(X,!0);p(X),p(D),p(G);var fe=$(G,2),W=k(fe),te=k(W);Xe(te);var se=$(te,2);{var de=U=>{var ee=Qu(),oe=K(ee);{let _e=j(()=>e(A)<=0);$e(oe,{variant:"secondary",size:"sm",onclick:L,get disabled(){return e(_e)},children:(Ce,Ie)=>{ke();var Ge=Ve("Prev");u(Ce,Ge)},$$slots:{default:!0}})}var ve=$(oe,2),ye=k(ve);p(ve);var ge=$(ve,2);{let _e=j(()=>e(A)>=e(x).length-1);$e(ge,{variant:"secondary",size:"sm",onclick:g,get disabled(){return e(_e)},children:(Ce,Ie)=>{ke();var Ge=Ve("Next");u(Ce,Ge)},$$slots:{default:!0}})}J(()=>ne(ye,`${e(A)>=0?e(A)+1:"–"} / ${e(x).length??""} occurrence${e(x).length!==1?"s":""}`)),u(U,ee)},ce=j(()=>r.search.trim()&&e(x).length>0),re=U=>{var ee=Zu();u(U,ee)},xe=j(()=>r.search.trim());Q(se,U=>{e(ce)?U(de):e(xe)&&U(re,1)})}p(W);var he=$(W,2),Ee=k(he);{let U=j(()=>e(i).length===0||e(d)<=0);$e(Ee,{variant:"secondary",size:"sm",onclick:T,get disabled(){return e(U)},children:(ee,oe)=>{ke();var ve=Ve("Prev");u(ee,ve)},$$slots:{default:!0}})}var pe=$(Ee,2),Fe=k(pe);p(pe);var ze=$(pe,2);{let U=j(()=>e(i).length===0||e(d)>=e(i).length-1);$e(ze,{variant:"secondary",size:"sm",onclick:q,get disabled(){return e(U)},children:(ee,oe)=>{ke();var ve=Ve("Next");u(ee,ve)},$$slots:{default:!0}})}p(he),p(fe);var Qe=$(fe,2);{var je=U=>{var ee=Ju(),oe=k(ee),ve=k(oe),ye=k(ve);Y(ye,()=>"left"),p(ve),p(oe),nt(oe,Ie=>f(h,Ie),()=>e(h));var ge=$(oe,2),_e=k(ge),Ce=k(_e);Y(Ce,()=>"right"),p(_e),p(ge),nt(ge,Ie=>f(y,Ie),()=>e(y)),p(ee),tt("scroll",oe,()=>M("left")),tt("scroll",ge,()=>M("right")),u(U,ee)},rt=U=>{var ee=ed();u(U,ee)};Q(Qe,U=>{e(o)?U(je):U(rt,-1)})}var Le=$(Qe,2);{var Pe=U=>{var ee=td();u(U,ee)};Q(Le,U=>{a()&&U(Pe)})}p(V),nt(V,U=>f(n,U),()=>e(n)),J(()=>{ne(E,r.leftLabel),ne(H,r.kind),ne(B,r.rightLabel),ne(ue,r.kind),vn(te,r.search),ne(Fe,`${e(d)>=0?e(d)+1:"–"} / ${e(i).length??""} diff${e(i).length!==1?"s":""}`)}),le("input",te,U=>r.onSearchChange(U.target.value)),le("keydown",te,U=>{U.key==="Enter"&&(U.preventDefault(),g())})}le("keydown",V,O),u(t,V),Ae()}Ke(["keydown","click","input"]);var ad=S(" ",1),sd=S('

      Please select two different cases to compare.

      '),od=S('

      Select two cases to compare.

      '),id=S(' '),ld=S('
      Parameter
      '),cd=S('

      All parameters are identical.

      '),ud=S('
      ',1),dd=S('

      '),fd=S('

      Loading...

      '),vd=S(" ",1),gd=S(" ",1);function hd(t,r){Me(r,!0);let a=z(""),n=z(""),s=z(""),i=z(""),o=z(""),l=z(!1),d=z(!1),b=z(""),x=j(()=>r.allCases.map(_=>({value:_,label:_})));const A=[{value:"setup.xml",label:"setup.xml"},{value:"doe_row.csv",label:"doe_row.csv"},{value:"run_solver.log",label:"run_solver.log"},{value:"performance.log",label:"performance.log"}];let m=j(()=>{var _;return((_=Ft())==null?void 0:_.compare_kinds)??A});Re(()=>{var _;e(m).some(M=>M.value===e(s))||f(s,((_=e(m)[0])==null?void 0:_.value)??"",!0)});let N=z(!1),L=j(()=>e(a)&&e(n)&&e(a)!==e(n));function g(){const _=e(a);f(a,e(n),!0),f(n,_,!0)}async function c(){if(e(L)){f(l,!0),f(b,"");try{const _=await pi({cases:[e(a),e(n)],base:e(a),kind:e(s)});f(o,_,!0),f(d,!0)}catch(_){console.error("Failed to load diff:",_),f(o,""),f(d,!1);const M=_ instanceof Error?_.message:String(_);f(b,M.includes("404")?`File "${e(s)}" not found for one of the selected cases. It may not have been run yet.`:"Failed to load comparison.",!0)}f(l,!1)}}Re(()=>{e(a)&&e(n)&&e(a)!==e(n)&&e(s)?c():e(a)&&e(n)&&e(a)===e(n)&&(f(o,""),f(d,!0))});let w=!1;Re(()=>{r.allCases.length>=2&&!w?(w=!0,f(a,r.allCases[0],!0),f(n,r.allCases[1],!0)):r.allCases.length===1&&!w&&(w=!0,f(a,r.allCases[0],!0))});let P=j(wn),h=j(()=>{if(!e(a)||!e(n)||e(a)===e(n)||e(P).length===0)return[];const _=yn(),M=_.find(T=>T.case_id===e(a)),F=_.find(T=>T.case_id===e(n));return!M&&!F?[]:e(P).map(T=>{var O,V;const q=((O=M==null?void 0:M.doe)==null?void 0:O[T])!=null&&String(M.doe[T])!==""?String(M.doe[T]):"—",I=((V=F==null?void 0:F.doe)==null?void 0:V[T])!=null&&String(F.doe[T])!==""?String(F.doe[T]):"—";return{param:T,left:q,right:I,differs:q!==I}})}),y=j(()=>e(N)?e(h):e(h).filter(_=>_.differs)),C=j(()=>e(h).filter(_=>_.differs).length);Ht(t,{eyebrow:"Compare",title:"Side-by-Side Comparison",wide:!0,children:(_,M)=>{var F=gd(),T=K(F);Qt(T,{children:(G,Z)=>{var ae=ad(),E=K(ae);Ne(E,{text:"First case",children:(D,v)=>{lt(D,{class:"w-32.5",get options(){return e(x)},get value(){return e(a)},onchange:B=>f(a,B,!0),placeholder:"Select..."})}});var R=$(E,2);{let D=j(()=>!e(a)&&!e(n));$e(R,{variant:"secondary",size:"sm",onclick:g,get disabled(){return e(D)},children:(v,B)=>{Te(v,{get icon(){return Fo}})},$$slots:{default:!0}})}var H=$(R,2);Ne(H,{text:"Second case",children:(D,v)=>{lt(D,{class:"w-32.5",get options(){return e(x)},get value(){return e(n)},onchange:B=>f(n,B,!0),placeholder:"Select..."})}}),u(G,ae)}});var q=$(T,2);{var I=G=>{var Z=sd();u(G,Z)},O=G=>{var Z=od();u(G,Z)},V=G=>{var Z=vd(),ae=K(Z);{var E=X=>{var ue=ud(),fe=K(ue),W=k(fe),te=k(W);p(W);var se=$(W,2),de=k(se,!0);p(se),p(fe);var ce=$(fe,2);{var re=he=>{var Ee=ld(),pe=k(Ee),Fe=k(pe),ze=k(Fe),Qe=k(ze),je=k(Qe,!0);p(Qe);var rt=$(Qe,2),Le=k(rt,!0);p(rt),p(ze),p(Fe);var Pe=$(Fe);Ye(Pe,21,()=>e(y),Y=>Y.param,(Y,U)=>{var ee=id(),oe=k(ee),ve=k(oe,!0);p(oe);var ye=$(oe),ge=k(ye,!0);p(ye);var _e=$(ye),Ce=k(_e,!0);p(_e),p(ee),J(()=>{Se(oe,1,dt(e(U).differs?"text-edf-orange-fonce bg-[rgba(214,67,10,0.04)]":"")),ne(ve,e(U).left),ne(ge,e(U).param),Se(_e,1,dt(e(U).differs?"text-edf-vert-fonce bg-[rgba(48,122,16,0.04)]":"")),ne(Ce,e(U).right)}),u(Y,ee)}),p(Pe),p(pe),p(Ee),J(()=>{ne(je,e(a)),ne(Le,e(n))}),u(he,Ee)},xe=he=>{var Ee=cd();u(he,Ee)};Q(ce,he=>{e(y).length>0?he(re):he(xe,-1)})}J(()=>{ne(te,`${e(C)??""} difference${e(C)!==1?"s":""} out of ${e(h).length??""} - parameters`),ne(de,e(N)?"Show differences only":"Show all parameters")}),le("click",se,()=>f(N,!e(N))),u(X,ue)};Q(ae,X=>{e(h).length>0&&X(E)})}var R=$(ae,2);Qt(R,{children:(X,ue)=>{Ne(X,{text:"File",children:(fe,W)=>{lt(fe,{class:"w-37.5",get options(){return e(m)},get value(){return e(s)},onchange:te=>f(s,te,!0)})}})}});var H=$(R,2);{var D=X=>{var ue=dd(),fe=k(ue,!0);p(ue),J(()=>ne(fe,e(b))),u(X,ue)},v=X=>{nd(X,{get diffText(){return e(o)},get leftLabel(){return e(a)},get rightLabel(){return e(n)},get kind(){return e(s)},get search(){return e(i)},get loading(){return e(l)},onSearchChange:ue=>f(i,ue,!0)})},B=X=>{var ue=fd();u(X,ue)};Q(H,X=>{e(b)?X(D):e(d)?X(v,1):e(l)&&X(B,2)})}u(G,Z)};Q(q,G=>{e(a)&&e(n)&&e(a)===e(n)?G(I):!e(a)||!e(n)?G(O,1):G(V,-1)})}u(_,F)},$$slots:{default:!0}}),Ae()}Ke(["click"]);var pd=S(" Refresh",1),md=S(" ",1),_d=S(''),bd=S(''),xd=S(" Download as CSV",1),yd=S('
      ',1),wd=S(" ",1),kd=S(""),Cd=S('
      Sort by:
      '),Sd=S('

      No errors found. Select cases and adjust filters above.

      '),$d=S(' '),Pd=S('NEW'),Ed=S('
      '),Rd=S('
      '),Nd=S(' '),Md=S('
      '),Ad=S('

      '),Ld=S("
      ",1);function Td(t,r){Me(r,!0);const a=6,n=["csauto.stderr","run_solver.log","listing"];let s=z(ie([])),i=z(ie([...n])),o=!1;Re(()=>{var te;const W=(te=Ft())==null?void 0:te.error_files;W!=null&&W.length&&!o&&f(i,[...W],!0)});let l=z("all"),d=z(""),b=z(a),x=z(!1),A=z(""),m=z(ie(tr("errors")));Re(()=>{rr("errors",e(m))});let N=z(ie([])),L=z(ie(new Set));function g(W){return W.toLowerCase().replace(/[0-9]+/g,"#").replace(/0x[0-9a-f]+/gi,"#").replace(/\s+/g," ").trim()}function c(W){const te=W.line_html.replace(/<[^>]*>/g,"").slice(0,120);return`${W.case_id}|${W.file}|${W.severity}|${g(te)}`}function w(W){const te=new Map;for(const se of W){const de=c(se),ce=te.get(de);ce?ce.count++:te.set(de,{...se,count:1,fingerprint:de,isNew:!e(L).has(de)})}return Array.from(te.values())}async function P(){if(!(!e(s).length||!e(i).length)){f(x,!0);try{const W=await ci({cases:e(s),files:e(i),context:e(b),sev:e(l)==="all"?"":e(l),q:e(d)});f(A,"");const te=w(W.items);f(N,W.items,!0);const se=new Set(te.map(de=>de.fingerprint));f(L,se,!0),f(h,te,!0)}catch(W){f(A,"Failed to load errors"),console.error("Failed to load errors:",W)}finally{f(x,!1)}}}let h=z(ie([])),y=null;function C(){y&&clearTimeout(y),y=setTimeout(P,250)}function _(W){return W==="error"?"error-sev-error":W==="warn"?"error-sev-warn":"error-sev-info"}let M=j(()=>Cn(r.allCases));const F=["csauto.stderr","run_solver.log","listing","csauto.stdout"];let T=j(()=>{var W;return(((W=Ft())==null?void 0:W.error_files)??F).map(te=>({value:te,label:te}))}),q=[{value:"all",label:"All"},{value:"error",label:"Error"},{value:"warn",label:"Warn"},{value:"info",label:"Info"}];function I(W){if(f(s,W,!0),W.length===0){f(h,[],!0),f(N,[],!0);return}P()}function O(W){if(o=!0,f(i,W,!0),W.length===0){f(h,[],!0),f(N,[],!0);return}P()}function V(W){f(l,W,!0),P()}function G(){P()}let Z=!1;Re(()=>{r.allCases.length>0&&!Z&&(Z=!0,f(s,[...r.allCases],!0),P())}),Re(()=>{e(m)?mr("errors",P,Vn()):vt("errors")});const ae=_r(()=>{Z&&P()});vr(()=>{vt("errors"),ae()});let E=z(ie(new Set));function R(W){const te=new Set(e(E));te.has(W)?te.delete(W):te.add(W),f(E,te,!0)}let H=z("severity"),D=z("desc");const v={info:0,warn:1,error:2};let B=j(()=>{const W=[...e(h)];return W.sort((te,se)=>{let de=0;return e(H)==="severity"?de=(v[te.severity]??9)-(v[se.severity]??9):e(H)==="count"?de=te.count-se.count:de=te[e(H)].localeCompare(se[e(H)]),e(D)==="asc"?de:-de}),W});const X={severity:"desc",count:"desc",case_id:"asc",file:"asc"};function ue(W){e(H)===W?f(D,e(D)==="asc"?"desc":"asc",!0):(f(H,W,!0),f(D,X[W],!0))}function fe(){if(!e(h).length)return;const W=["case_id","file","severity","count","is_new","line_text"],te=e(B).map(ce=>[ce.case_id,ce.file,ce.severity,ce.count,ce.isNew?"yes":"no",`"${ce.line_html.replace(/<[^>]*>/g,"").replace(/"/g,'""')}"`].join(",")),se=[W.join(","),...te].join(` -`),de=xr("errors",e(s),"csv");Ea(se,de)}Ht(t,{eyebrow:"Diagnostics",title:"Recent Errors",wide:!0,actions:te=>{var se=md(),de=K(se);{let xe=j(Vn);nr(de,{name:"errors",get intervalMs(){return e(xe)},onRefresh:P,get checked(){return e(m)},set checked(he){f(m,he,!0)}})}var ce=$(de,2);{var re=xe=>{$e(xe,{variant:"primary",onclick:P,children:(he,Ee)=>{var pe=pd(),Fe=K(pe);Te(Fe,{get icon(){return er}}),ke(),u(he,pe)},$$slots:{default:!0}})};Q(ce,xe=>{e(m)||xe(re)})}u(te,se)},children:(te,se)=>{var de=Ld(),ce=K(de);Qt(ce,{children:(Le,Pe)=>{var Y=yd(),U=K(Y);Ne(U,{text:"Cases",children:(Ce,Ie)=>{Gt(Ce,{class:"w-[160px]",get options(){return e(M)},get selected(){return e(s)},onchange:I,placeholder:"Select cases..."})}});var ee=$(U,2);Ne(ee,{text:"Files",children:(Ce,Ie)=>{Gt(Ce,{class:"w-[160px]",get options(){return e(T)},get selected(){return e(i)},onchange:O,placeholder:"Select files..."})}});var oe=$(ee,2);Ne(oe,{text:"Severity",children:(Ce,Ie)=>{lt(Ce,{class:"w-[90px]",get options(){return q},get value(){return e(l)},onchange:V})}});var ve=$(oe,2);Ne(ve,{text:"Search",children:(Ce,Ie)=>{var Ge=_d();Xe(Ge),le("input",Ge,C),et(Ge,()=>e(d),gt=>f(d,gt)),u(Ce,Ge)}});var ye=$(ve,2);Ne(ye,{text:"Context",children:(Ce,Ie)=>{var Ge=bd();Xe(Ge),le("change",Ge,G),et(Ge,()=>e(b),gt=>f(b,gt)),u(Ce,Ge)}});var ge=$(ye,2),_e=k(ge);{let Ce=j(()=>e(h).length===0);$e(_e,{variant:"secondary",size:"sm",onclick:fe,get disabled(){return e(Ce)},children:(Ie,Ge)=>{var gt=xd(),jt=K(gt);Te(jt,{get icon(){return pr}}),ke(),u(Ie,gt)},$$slots:{default:!0}})}p(ge),u(Le,Y)}});var re=$(ce,2);{var xe=Le=>{var Pe=Cd(),Y=$(k(Pe),2);Ye(Y,16,()=>[{key:"severity",label:"Severity"},{key:"count",label:"Count"},{key:"case_id",label:"Case"},{key:"file",label:"File"}],U=>U.key,(U,ee)=>{var oe=kd(),ve=k(oe,!0),ye=$(ve);{var ge=_e=>{var Ce=wd(),Ie=$(K(Ce));{let Ge=j(()=>e(D)==="asc"?Do:To);Te(Ie,{get icon(){return e(Ge)},size:12})}u(_e,Ce)};Q(ye,_e=>{e(H)===ee.key&&_e(ge)})}p(oe),J(()=>{Se(oe,1,`cursor-pointer bg-transparent border-none text-xs hover:underline ${e(H)===ee.key?"text-edf-bleu-fonce font-bold":"text-muted"}`),ne(ve,ee.label)}),le("click",oe,()=>ue(ee.key)),u(U,oe)}),p(Pe),u(Le,Pe)};Q(re,Le=>{e(B).length>0&&Le(xe)})}var he=$(re,2),Ee=k(he);{var pe=Le=>{var Pe=Sd();u(Le,Pe)};Q(Ee,Le=>{e(B).length===0&&Le(pe)})}var Fe=$(Ee,2);Ye(Fe,17,()=>e(B),Le=>Le.fingerprint,(Le,Pe)=>{var Y=Rd(),U=k(Y),ee=k(U),oe=k(ee);{let Je=j(()=>e(E).has(e(Pe).fingerprint)?pn:zo);Te(oe,{get icon(){return e(Je)},size:14})}p(ee);var ve=$(ee,2),ye=k(ve);p(ve);var ge=$(ve,2),_e=k(ge);{var Ce=Je=>{var ut=$d(),at=k(ut);p(ut),J(()=>ne(at,`${e(Pe).count??""}x`)),u(Je,ut)};Q(_e,Je=>{e(Pe).count>1&&Je(Ce)})}var Ie=$(_e,2);{var Ge=Je=>{var ut=Pd();u(Je,ut)};Q(Ie,Je=>{e(Pe).isNew&&Je(Ge)})}p(ge),p(U);var gt=$(U,2);{var jt=Je=>{var ut=Ed();zr(ut,()=>e(Pe).line_html,!0),p(ut),u(Je,ut)},jr=j(()=>e(E).has(e(Pe).fingerprint));Q(gt,Je=>{e(jr)&&Je(jt)})}p(Y),J(Je=>{Se(Y,1,`error-item ${Je??""}`),ne(ye,`${e(Pe).case_id??""} / ${e(Pe).file??""} : ${e(Pe).severity??""}`)},[()=>_(e(Pe).severity)]),le("click",U,()=>R(e(Pe).fingerprint)),u(Le,Y)}),p(he);var ze=$(he,2);{var Qe=Le=>{var Pe=Md(),Y=k(Pe),U=k(Y);p(Y);var ee=$(Y,2);{var oe=ye=>{var ge=Nd(),_e=k(ge);p(ge),J(Ce=>ne(_e,`${Ce??""} new`),[()=>e(h).filter(Ce=>Ce.isNew).length]),u(ye,ge)},ve=j(()=>e(h).some(ye=>ye.isNew));Q(ee,ye=>{e(ve)&&ye(oe)})}p(Pe),J(()=>ne(U,`${e(h).length??""} unique / ${e(N).length??""} total`)),u(Le,Pe)};Q(ze,Le=>{e(B).length>0&&Le(Qe)})}var je=$(ze,2);{var rt=Le=>{var Pe=Ad(),Y=k(Pe,!0);p(Pe),J(()=>ne(Y,e(A))),u(Le,Pe)};Q(je,Le=>{e(A)&&Le(rt)})}J(()=>Se(he,1,`error-list ${e(x)?"opacity-50 pointer-events-none":""}`)),u(te,de)},$$slots:{actions:!0,default:!0}}),Ae()}Ke(["input","change","click"]);var Fd=S('
      Developed by Simvia
      ',1);function Bd(t,r){Me(r,!0);let a=z(ie({totalCases:0,shownCases:0,totalRunning:0,shownRunning:0,totalConverged:0,shownConverged:0})),n=z(ie([])),s=z(null);const i=v=>e(s)===null||e(s).includes(v);async function o(v){var ue;const B=`/favicon-${v}.svg`;try{const fe=await fetch(B);if(!fe.ok||!((ue=fe.headers.get("content-type"))!=null&&ue.includes("svg")))return}catch{return}const X=document.querySelector('link[rel="icon"]');X&&(X.href=B)}async function l(){try{const v=await si();Rl(v.rows),Nl(v.doe_columns),f(n,v.rows.map(B=>B.case_id),!0),f(a,Ul($a(),yn()),!0)}catch(v){console.error("Failed to load status:",v)}}Zn(()=>{l(),oi().then(v=>{rl(v),f(s,v.panels,!0),o(v.solver)}).catch(()=>{})});var d=Fd(),b=K(d);Mi(b,{});var x=$(b,2);Di(x,{});var A=$(x,2);ll(A,{get totalCases(){return e(a).totalCases},get shownCases(){return e(a).shownCases},get totalRunning(){return e(a).totalRunning},get shownRunning(){return e(a).shownRunning},get totalConverged(){return e(a).totalConverged},get shownConverged(){return e(a).shownConverged}});var m=$(A,2),N=k(m);{var L=v=>{xc(v,{onRefresh:l})},g=j(()=>i("status"));Q(N,v=>{e(g)&&v(L)})}var c=$(N,2);{var w=v=>{Vc(v,{get allCases(){return e(n)}})},P=j(()=>i("residuals"));Q(c,v=>{e(P)&&v(w)})}var h=$(c,2);{var y=v=>{fu(v,{get allCases(){return e(n)}})},C=j(()=>i("probes"));Q(h,v=>{e(C)&&v(y)})}var _=$(h,2);{var M=v=>{ku(v,{get allCases(){return e(n)}})},F=j(()=>i("performance"));Q(_,v=>{e(F)&&v(M)})}var T=$(_,2);{var q=v=>{hd(v,{get allCases(){return e(n)}})},I=j(()=>i("compare"));Q(T,v=>{e(I)&&v(q)})}var O=$(T,2);{var V=v=>{Gu(v,{get allCases(){return e(n)}})},G=j(()=>i("tail"));Q(O,v=>{e(G)&&v(V)})}var Z=$(O,2);{var ae=v=>{Td(v,{get allCases(){return e(n)}})},E=j(()=>i("errors"));Q(Z,v=>{e(E)&&v(ae)})}p(m);var R=$(m,2),H=$(k(R),2),D=k(H);p(H),p(R),J(()=>we(D,"src",cl)),u(t,d),Ae()}export{Bd as component}; diff --git a/frontend/dist/_app/immutable/nodes/2.Db1dJWID.js b/frontend/dist/_app/immutable/nodes/2.Db1dJWID.js new file mode 100644 index 0000000..5af38a8 --- /dev/null +++ b/frontend/dist/_app/immutable/nodes/2.Db1dJWID.js @@ -0,0 +1,1191 @@ +import{b as Pr,a as u,f as P,c as be,t as Ve,d as Ht}from"../chunks/C14P8jaG.js";import{o as Jn,a as vr}from"../chunks/Cz6m6K8p.js";import{h as Fe,L as Pt,G as Lt,c as Er,J as Gt,b as ea,o as e,N as Ba,ag as qa,O as Pn,P as zt,f as _t,ay as ta,aF as Ka,ao as En,e as mt,a as dr,aM as Ga,s as Wa,a5 as Xa,aN as ra,aE as un,aO as Ya,a7 as Rn,aP as Qa,aQ as Za,aI as Ja,aR as ht,aS as na,a1 as es,r as aa,p as sa,aT as Ur,ae as oa,aU as ts,aV as rs,aC as ns,m as as,d as Nr,az as ia,y as ee,F as la,aW as ss,aG as os,aA as is,D as ca,aX as ua,aY as ls,aZ as cs,E as us,aw as da,S as Ir,k as Mr,a_ as fa,a$ as ds,b0 as fs,b1 as va,b2 as vs,b3 as gs,b4 as gr,b5 as hs,b6 as ps,b7 as ms,b8 as _s,b9 as bs,ba as xs,bb as ys,aJ as Et,U as ws,aK as D,_ as ce,$ as f,A as _,B as v,x as Me,g as G,z as Ae,C as E,j as Ne,ap as we,aL as V,bc as ks,bd as ga,q as Ut,be as it,bf as Cs}from"../chunks/VJhVTb1A.js";import{p as me,i as Y,b as nt,c as Ss,l as Oe,s as Ue}from"../chunks/zf4GFcxs.js";import{i as $s,a as Ps,d as ue,b as Ge,c as Es,n as Rs,e as Ns,s as ae,f as rt}from"../chunks/BeON-0X8.js";import{s as ft}from"../chunks/CiUbu-1F.js";import{i as Ms}from"../chunks/DmjnTBOd.js";import{B as As}from"../chunks/U5yjBaoU.js";function dn(t,r){return r}function Ls(t,r,a){for(var n=[],s=r.length,i,o=r.length,l=0;l{if(i){if(i.pending.delete(k),i.done.add(k),i.pending.size===0){var b=t.outrogroups;Qr(t,un(i.done)),b.delete(i),b.size===0&&(t.outrogroups=null)}}else o-=1},!1)}if(o===0){var d=n.length===0&&a!==null;if(d){var m=a,x=m.parentNode;ns(x),x.append(m),t.items.clear()}Qr(t,r,!d)}else i={pending:new Set(r),done:new Set},(t.outrogroups??(t.outrogroups=new Set)).add(i)}function Qr(t,r,a=!0){var n;if(t.pending.size>0){n=new Set;for(const o of t.pending.values())for(const l of o)n.add(t.items.get(l).e)}for(var s=0;s{var p=a();return ra(p)?p:p==null?[]:un(p)}),b,z=new Map,A=!0;function h(p){(R.effect.f&es)===0&&(R.pending.delete(p),R.fallback=x,Ts(R,b,o,r,n),x!==null&&(b.length===0?(x.f&ht)===0?aa(x):(x.f^=ht,lr(x,null,o)):sa(x,()=>{x=null})))}function c(p){R.pending.delete(p)}var C=ea(()=>{b=e(k);var p=b.length;let y=!1;if(Fe){var $=Ba(o)===qa;$!==(p===0)&&(o=Pn(),Pt(o),zt(!1),y=!0)}for(var g=new Set,N=mt,I=Wa(),L=0;Li(o)):(x=dr(()=>i(Nn??(Nn=Er()))),x.f|=ht)),p>g.size&&Ga(),Fe&&p>0&&Pt(Pn()),!A)if(z.set(N,g),I){for(const[U,X]of l)g.has(U)||N.skip_effect(X.e);N.oncommit(h),N.ondiscard(c)}else h(N);y&&zt(!0),e(k)}),R={effect:C,items:l,pending:z,outrogroups:null,fallback:x};A=!1,Fe&&(o=_t)}function ar(t){for(;t!==null&&(t.f&ts)===0;)t=t.next;return t}function Ts(t,r,a,n,s){var q,H,j,U,X,K,re,w,S;var i=(n&rs)!==0,o=r.length,l=t.items,d=ar(t.effect.first),m,x=null,k,b=[],z=[],A,h,c,C;if(i)for(C=0;C0){var L=(n&na)!==0&&o===0?a:null;if(i){for(C=0;C{var O,F;if(k!==void 0)for(c of k)(F=(O=c.nodes)==null?void 0:O.a)==null||F.apply()})}function Fs(t,r,a,n,s,i,o,l){var d=(o&Qa)!==0?(o&Za)===0?Ja(a,!1,!1):Rn(a):null,m=(o&Ya)!==0?Rn(s):null;return{v:d,i:m,e:dr(()=>(i(r,d??a,m??s,l),()=>{t.delete(n)}))}}function lr(t,r,a){if(t.nodes)for(var n=t.nodes.start,s=t.nodes.end,i=r&&(r.f&ht)===0?r.nodes.start:a;n!==null;){var o=ia(n);if(i.before(n),n===s)return;n=o}}function kt(t,r,a){r===null?t.effect.first=a:r.next=a,a===null?t.effect.last=r:a.prev=r}function zr(t,r,a=!1,n=!1,s=!1,i=!1){var o=t,l="";if(a){var d=t;Fe&&(o=Pt(Lt(d)))}ee(()=>{var m=la;if(l===(l=r()??"")){Fe&&Gt();return}if(a&&!Fe){m.nodes=null,d.innerHTML=l,l!==""&&Pr(Lt(d),d.lastChild);return}if(m.nodes!==null&&(ss(m.nodes.start,m.nodes.end),m.nodes=null),l!==""){if(Fe){_t.data;for(var x=Gt(),k=x;x!==null&&(x.nodeType!==ta||x.data!=="");)k=x,x=ia(x);if(x===null)throw os(),is;Pr(_t,k),o=Pt(x);return}var b=n?ua:s?ls:void 0,z=ca(n?"svg":s?"math":"template",b);z.innerHTML=l;var A=n||s?z:z.content;if(Pr(Lt(A),A.lastChild),n||s)for(;Lt(A);)o.before(Lt(A));else o.before(A)}})}function je(t,r,a,n,s){var l;Fe&&Gt();var i=(l=r.$$slots)==null?void 0:l[a],o=!1;i===!0&&(i=r.children,o=!0),i===void 0||i(t,o?()=>n:n)}function Is(t,r,a,n,s,i){let o=Fe;Fe&&Gt();var l=null;Fe&&_t.nodeType===cs&&(l=_t,Gt());var d=Fe?_t:t,m=new As(d,!1);ea(()=>{const x=r()||null;var k=ua;if(x===null){m.ensure(null,null);return}return m.ensure(x,b=>{if(x){if(l=Fe?l:ca(x,k),Pr(l,l),n){Fe&&$s(x)&&l.append(document.createComment(""));var z=Fe?Lt(l):l.appendChild(Er());Fe&&(z===null?zt(!1):Pt(z)),n(l,z)}la.nodes.end=l,b.before(l)}Fe&&Pt(b)}),()=>{}},us),da(()=>{}),o&&(zt(!0),Pt(d))}function zs(t,r,a){Ir(()=>{var n=Mr(()=>r(t,a==null?void 0:a())||{});if(n!=null&&n.destroy)return()=>n.destroy()})}function Ds(t,r){var a=void 0,n;fa(()=>{a!==(a=r())&&(n&&(Nr(n),n=null),a&&(n=dr(()=>{Ir(()=>a(t))})))})}function ha(t){var r,a,n="";if(typeof t=="string"||typeof t=="number")n+=t;else if(typeof t=="object")if(Array.isArray(t)){var s=t.length;for(r=0;r=0;){var l=o+i;(o===0||Mn.includes(n[o-1]))&&(l===n.length||Mn.includes(n[l]))?n=(o===0?"":n.substring(0,o))+n.substring(l+1):o=l}}return n===""?null:n}function An(t,r=!1){var a=r?" !important;":";",n="";for(var s of Object.keys(t)){var i=t[s];i!=null&&i!==""&&(n+=" "+s+": "+i+a)}return n}function Br(t){return t[0]!=="-"||t[1]!=="-"?t.toLowerCase():t}function js(t,r){if(r){var a="",n,s;if(Array.isArray(r)?(n=r[0],s=r[1]):n=r,t){t=String(t).replaceAll(/\s*\/\*.*?\*\/\s*/g,"").trim();var i=!1,o=0,l=!1,d=[];n&&d.push(...Object.keys(n).map(Br)),s&&d.push(...Object.keys(s).map(Br));var m=0,x=-1;const h=t.length;for(var k=0;k{Ar(t,t.__value)});r.observe(t,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["value"]}),da(()=>{r.disconnect()})}function Zr(t,r,a=r){var n=new WeakSet,s=!0;va(t,"change",i=>{var o=i?"[selected]":":checked",l;if(t.multiple)l=[].map.call(t.querySelectorAll(o),ur);else{var d=t.querySelector(o)??t.querySelector("option:not([disabled])");l=d&&ur(d)}a(l),t.__value=l,mt!==null&&n.add(mt)}),Ir(()=>{var i=r();if(t===document.activeElement){var o=mt;if(n.has(o))return}if(Ar(t,i,s),s&&i===void 0){var l=t.querySelector(":checked");l!==null&&(i=ur(l),a(i))}t.__value=i,s=!1}),pa(t)}function ur(t){return"__value"in t?t.__value:t.value}const sr=Symbol("class"),or=Symbol("style"),ma=Symbol("is custom element"),_a=Symbol("is html"),Vs=gr?"link":"LINK",Us=gr?"input":"INPUT",Bs=gr?"option":"OPTION",qs=gr?"select":"SELECT",Ks=gr?"progress":"PROGRESS";function Ye(t){if(Fe){var r=!1,a=()=>{if(!r){if(r=!0,t.hasAttribute("value")){var n=t.value;ke(t,"value",null),t.value=n}if(t.hasAttribute("checked")){var s=t.checked;ke(t,"checked",null),t.checked=s}}};t.__on_r=a,oa(a),ms()}}function fn(t,r){var a=vn(t);a.value===(a.value=r??void 0)||t.value===r&&(r!==0||t.nodeName!==Ks)||(t.value=r??"")}function Gs(t,r){r?t.hasAttribute("selected")||t.setAttribute("selected",""):t.removeAttribute("selected")}function ke(t,r,a,n){var s=vn(t);Fe&&(s[r]=t.getAttribute(r),r==="src"||r==="srcset"||r==="href"&&t.nodeName===Vs)||s[r]!==(s[r]=a)&&(r==="loading"&&(t[xs]=a),a==null?t.removeAttribute(r):typeof a!="string"&&ba(t).includes(r)?t[r]=a:t.setAttribute(r,a))}function Ws(t,r,a,n,s=!1,i=!1){if(Fe&&s&&t.nodeName===Us){var o=t,l=o.type==="checkbox"?"defaultChecked":"defaultValue";l in a||Ye(o)}var d=vn(t),m=d[ma],x=!d[_a];let k=Fe&&m;k&&zt(!1);var b=r||{},z=t.nodeName===Bs;for(var A in r)A in a||(a[A]=null);a.class?a.class=dt(a.class):a[sr]&&(a.class=null),a[or]&&(a.style??(a.style=null));var h=ba(t);for(const g in a){let N=a[g];if(z&&g==="value"&&N==null){t.value=t.__value="",b[g]=N;continue}if(g==="class"){var c=t.namespaceURI==="http://www.w3.org/1999/xhtml";Se(t,c,N,n,r==null?void 0:r[sr],a[sr]),b[g]=N,b[sr]=a[sr];continue}if(g==="style"){Ze(t,N,r==null?void 0:r[or],a[or]),b[g]=N,b[or]=a[or];continue}var C=b[g];if(!(N===C&&!(N===void 0&&t.hasAttribute(g)))){b[g]=N;var R=g[0]+g[1];if(R!=="$$")if(R==="on"){const I={},L="$$"+g;let q=g.slice(2);var p=Ns(q);if(Ps(q)&&(q=q.slice(0,-7),I.capture=!0),!p&&C){if(N!=null)continue;t.removeEventListener(q,b[L],I),b[L]=null}if(p)ue(q,t,N),Ge([q]);else if(N!=null){let H=function(j){b[g].call(this,j)};b[L]=Es(q,t,H,I)}}else if(g==="style")ke(t,g,N);else if(g==="autofocus")hs(t,!!N);else if(!m&&(g==="__value"||g==="value"&&N!=null))t.value=t.__value=N;else if(g==="selected"&&z)Gs(t,N);else{var y=g;x||(y=Rs(y));var $=y==="defaultValue"||y==="defaultChecked";if(N==null&&!m&&!$)if(d[g]=null,y==="value"||y==="checked"){let I=t;const L=r===void 0;if(y==="value"){let q=I.defaultValue;I.removeAttribute(y),I.defaultValue=q,I.value=I.__value=L?q:null}else{let q=I.defaultChecked;I.removeAttribute(y),I.defaultChecked=q,I.checked=L?q:!1}}else t.removeAttribute(g);else $||h.includes(y)&&(m||typeof N!="string")?(t[y]=N,y in d&&(d[y]=ps)):typeof N!="function"&&ke(t,y,N)}}}return k&&zt(!0),b}function Ln(t,r,a=[],n=[],s=[],i,o=!1,l=!1){vs(s,a,n,d=>{var m=void 0,x={},k=t.nodeName===qs,b=!1;if(fa(()=>{var A=r(...d.map(e)),h=Ws(t,m,A,i,o,l);b&&k&&"value"in A&&Ar(t,A.value);for(let C of Object.getOwnPropertySymbols(x))A[C]||Nr(x[C]);for(let C of Object.getOwnPropertySymbols(A)){var c=A[C];C.description===gs&&(!m||c!==m[C])&&(x[C]&&Nr(x[C]),x[C]=dr(()=>Ds(t,()=>c))),h[C]=c}m=h}),k){var z=t;Ir(()=>{Ar(z,m.value,!0),pa(z)})}b=!0})}function vn(t){return t.__attributes??(t.__attributes={[ma]:t.nodeName.includes("-"),[_a]:t.namespaceURI===_s})}var Tn=new Map;function ba(t){var r=t.getAttribute("is")||t.nodeName,a=Tn.get(r);if(a)return a;Tn.set(r,a=[]);for(var n,s=t,i=Element.prototype;i!==s;){n=ys(s);for(var o in n)n[o].set&&a.push(o);s=bs(s)}return a}function tt(t,r,a=r){var n=new WeakSet;va(t,"input",async s=>{var i=s?t.defaultValue:t.value;if(i=Kr(t)?Gr(i):i,a(i),mt!==null&&n.add(mt),await Et(),i!==(i=r())){var o=t.selectionStart,l=t.selectionEnd,d=t.value.length;if(t.value=i??"",l!==null){var m=t.value.length;o===l&&l===d&&m>d?(t.selectionStart=m,t.selectionEnd=m):(t.selectionStart=o,t.selectionEnd=Math.min(l,m))}}}),(Fe&&t.defaultValue!==t.value||Mr(r)==null&&t.value)&&(a(Kr(t)?Gr(t.value):t.value),mt!==null&&n.add(mt)),ws(()=>{var s=r();if(t===document.activeElement){var i=mt;if(n.has(i))return}Kr(t)&&s===Gr(t.value)||t.type==="date"&&!s&&!t.value||s!==t.value&&(t.value=s??"")})}function Kr(t){var r=t.type;return r==="number"||r==="range"}function Gr(t){return t===""?null:+t}let xt=D(ce({type:"none",props:{},resolve:null}));function Xs(){return e(xt)}function ot(t=void 0){const{resolve:r}=e(xt);f(xt,{type:"none",props:{},resolve:null},!0),r==null||r(t)}function Ys(t){return new Promise(r=>{f(xt,{type:"run",props:{cases:t},resolve:r},!0)})}function Qs(t){return new Promise(r=>{f(xt,{type:"restart",props:{cases:t},resolve:r},!0)})}function Zs(t){return new Promise(r=>{f(xt,{type:"clean",props:{cases:t},resolve:r},!0)})}function Xe(t,r="Alert"){return new Promise(a=>{f(xt,{type:"alert",props:{title:r,message:t},resolve:()=>a()},!0)})}function Fn(t,r="Confirm",a="OK",n="primary"){return new Promise(s=>{f(xt,{type:"confirm",props:{title:r,message:t,confirmLabel:a,confirmVariant:n},resolve:s},!0)})}function gn(t,r="",a="Input",n="",s=!1){return new Promise(i=>{f(xt,{type:"prompt",props:{title:a,message:t,value:r,placeholder:n,multiline:s},resolve:i},!0)})}var Js=P('
      ');function Jt(t,r){function a(i){return document.body.appendChild(i),{destroy(){i.remove()}}}var n=Js(),s=_(n);ft(s,()=>r.children),v(n),zs(n,i=>a==null?void 0:a(i)),u(t,n)}var eo=P('
      '),to=P('
      ');function hr(t,r){Me(r,!0);const a=A=>{var h=to(),c=_(h),C=_(c),R=_(C),p=_(R,!0);v(R);var y=E(R,2);{var $=L=>{var q=eo(),H=_(q,!0);v(q),ee(()=>ae(H,r.subtitle)),u(L,q)};Y(y,L=>{r.subtitle&&L($)})}v(C);var g=E(C,2);ft(g,()=>r.children);var N=E(g,2),I=_(N);ft(I,()=>r.footer),v(N),v(c),v(h),ee(()=>{Se(c,1,`w-[min(${n()??""},96vw)] bg-white border border-border rounded-[10px] p-4.5`),ke(c,"aria-labelledby",r.titleId),ke(R,"id",r.titleId),ae(p,r.title)}),ue("keydown",h,o),ue("mousedown",h,d),ue("click",h,m),u(A,h)};let n=me(r,"maxWidth",3,"520px"),s=me(r,"portal",3,!1);function i(){r.onCancel?r.onCancel():ot(null)}function o(A){A.key==="Escape"?i():A.key==="Enter"&&r.onConfirm&&r.onConfirm()}let l=!1;function d(A){l=A.target.dataset.backdrop!==void 0}function m(A){const h=A.target.dataset.backdrop!==void 0;l&&h&&i(),l=!1}var x=be(),k=G(x);{var b=A=>{Jt(A,{children:(h,c)=>{a(h)}})},z=A=>{a(A)};Y(k,A=>{s()?A(b):A(z,-1)})}u(t,x),Ae()}Ge(["keydown","mousedown","click"]);var ro=P("");function Pe(t,r){let a=me(r,"variant",3,"primary"),n=me(r,"size",3,"default"),s=me(r,"disabled",3,!1);const i={default:"h-[34px] rounded-md px-3.5 leading-none text-[13px] tracking-wide",sm:"h-[30px] rounded-md px-2.5 leading-none text-xs"},o={primary:"bg-edf-bleu-moyen text-white border-none hover:bg-[rgb(12,72,170)]",secondary:"bg-white text-ink border border-border hover:bg-edf-gris-clair",run:"bg-edf-vert-fonce text-white border-none hover:bg-[rgb(38,98,12)]",warning:"bg-edf-orange-moyen text-white border-none hover:bg-[rgb(230,118,20)]",danger:"bg-edf-orange-fonce text-white border-none hover:bg-[rgb(180,56,8)]"};var l=ro(),d=_(l);ft(d,()=>r.children),v(l),ee(()=>{Se(l,1,`inline-flex items-center justify-center gap-1.5 font-bold cursor-pointer transition-colors duration-150 disabled:opacity-40 disabled:pointer-events-none ${i[n()]??""} ${o[a()]??""}`),l.disabled=s()}),ue("click",l,function(...m){var x;(x=r.onclick)==null||x.apply(this,m)}),u(t,l)}Ge(["click"]);var no=P(" ",1),ao=P(''),so=P(''),oo=P('
      '),io=P('

      ',1);function lo(t,r){Me(r,!0);let a=me(r,"confirmLabel",3,"OK"),n=me(r,"confirmVariant",3,"primary"),s=me(r,"value",3,""),i=me(r,"placeholder",3,""),o=me(r,"multiline",3,!1);const l=s();let d=D(ce(l)),m=D(void 0),x=D(void 0);Ne(()=>{var z;r.mode==="prompt"&&e(m)?(e(m).focus(),"select"in e(m)&&e(m).select()):e(x)&&((z=e(x).querySelector("button"))==null||z.focus())});function k(){r.mode==="prompt"?ot(e(d)):r.mode==="confirm"?ot(!0):ot()}function b(){r.mode==="confirm"?ot(!1):r.mode==="prompt"?ot(null):ot()}{const z=h=>{var c=no(),C=G(c);{var R=$=>{Pe($,{variant:"secondary",onclick:b,children:(g,N)=>{we();var I=Ve("Cancel");u(g,I)},$$slots:{default:!0}})};Y(C,$=>{r.mode!=="alert"&&$(R)})}var p=E(C,2),y=_(p);Pe(y,{get variant(){return n()},onclick:k,children:($,g)=>{we();var N=Ve();ee(()=>ae(N,a())),u($,N)},$$slots:{default:!0}}),v(p),nt(p,$=>f(x,$),()=>e(x)),u(h,c)};let A=V(()=>o()?void 0:k);hr(t,{get title(){return r.title},titleId:"app-dialog-title",get onConfirm(){return e(A)},onCancel:b,footer:z,children:(h,c)=>{var C=io(),R=G(C),p=_(R,!0);v(R);var y=E(R,2);{var $=g=>{var N=oo(),I=_(N);{var L=H=>{var j=ao();ks(j),nt(j,U=>f(m,U),()=>e(m)),ee(()=>ke(j,"placeholder",i())),tt(j,()=>e(d),U=>f(d,U)),u(H,j)},q=H=>{var j=so();Ye(j),nt(j,U=>f(m,U),()=>e(m)),ee(()=>ke(j,"placeholder",i())),tt(j,()=>e(d),U=>f(d,U)),u(H,j)};Y(I,H=>{o()?H(L):H(q,-1)})}v(N),u(g,N)};Y(y,g=>{r.mode==="prompt"&&g($)})}ee(()=>ae(p,r.message)),u(h,C)},$$slots:{footer:!0,default:!0}})}Ae()}var co=P('');function Re(t,r){var a=co(),n=_(a),s=E(n);ft(s,()=>r.children),v(a),ee(()=>ae(n,`${r.text??""} `)),u(t,a)}function St(t,r){const a=localStorage.getItem(t);if(a===null)return r;const n=Number(a);return Number.isFinite(n)?n:r}function $t(t,r){Number.isFinite(r)&&localStorage.setItem(t,String(r))}function xa(t,r){return localStorage.getItem(t)??r}function uo(t,r){r?localStorage.setItem(t,r):localStorage.removeItem(t)}function fo(){return{n:St("csauto_run_n",1),nt:St("csauto_run_nt",1),maxParallel:St("csauto_run_max_parallel",0)||null,backend:xa("csauto_run_backend","")||null}}function vo(t){$t("csauto_run_n",t.n),$t("csauto_run_nt",t.nt),t.maxParallel&&$t("csauto_run_max_parallel",t.maxParallel),uo("csauto_run_backend",t.backend??"")}function go(){return{n:St("csauto_restart_n",1),nt:St("csauto_restart_nt",1),maxParallel:St("csauto_restart_max_parallel",0)||null,mode:xa("csauto_restart_mode","iterations"),value:St("csauto_restart_value",100)}}function ho(t){$t("csauto_restart_n",t.n),$t("csauto_restart_nt",t.nt),t.maxParallel&&$t("csauto_restart_max_parallel",t.maxParallel),localStorage.setItem("csauto_restart_mode",t.mode),$t("csauto_restart_value",t.value)}function po(){return{keepLast:St("csauto_clean_keep_last",1)}}function mo(t){$t("csauto_clean_keep_last",t.keepLast)}let Dt=D(null);function _o(t){f(Dt,t,!0)}function Ft(){return e(Dt)}function cr(t){return e(Dt)===null||e(Dt).capabilities.includes(t)}function bo(t){return e(Dt)===null||e(Dt).control_actions.includes(t)}function xo(){var t;return((t=e(Dt))==null?void 0:t.backends)??[]}const Jr="csauto_token";let ya=D(ce(localStorage.getItem(Jr)??""));function wa(){return e(ya)}function ka(t){f(ya,t,!0),t?localStorage.setItem(Jr,t):localStorage.removeItem(Jr)}let wr=null;async function hn(t,r={}){const a=new Headers(r.headers),n=wa();n&&a.set("X-CSAUTO-TOKEN",n);const s=await fetch(t,{...r,headers:a});if(s.status===401){wr||(wr=gn("API token required:",n,"Authentication"));const i=await wr;return wr=null,i===null?s:(ka(i),a.set("X-CSAUTO-TOKEN",i),fetch(t,{...r,headers:a}))}return s}async function ct(t){const r=await hn(t);if(!r.ok)throw new Error(`GET ${t} failed: ${r.status}`);return r.json()}async function Dr(t){const r=await hn(t);if(!r.ok)throw new Error(`GET ${t} failed: ${r.status}`);return r.text()}async function yt(t,r){const a=await hn(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!a.ok){const n=await a.text().catch(()=>a.statusText);throw new Error(`POST ${t} failed: ${a.status} — ${n}`)}return a.json()}function Or(t){return t.map(r=>`case=${encodeURIComponent(r)}`).join("&")}function yo(t){return ct(`/api/launch_options?backend=${encodeURIComponent(t)}`)}function wo(t=!1){return ct(`/api/status${t?"?log=1":""}`)}function In(t){return ct(`/api/perf?${Or(t)}`)}function ko(){return ct("/api/app_config")}function Co(t){return ct(`/api/residual_columns?${Or(t)}`).then(r=>r.columns??[])}function So(t,r,a={}){const n=new URLSearchParams;return t.forEach(s=>n.append("case",s)),n.set("columns",r.join(",")),a.width&&n.set("width",String(a.width)),a.height&&n.set("height",String(a.height)),a.xMin!==void 0&&n.set("x_min",String(a.xMin)),a.includeHistory&&n.set("include_history","true"),Dr(`/api/residuals_svg?${n}`)}function Ca(t){return ct(`/api/restart_origin?${Or(t)}`)}function $o(t){var a;const r=new URLSearchParams;return t.cases.forEach(n=>r.append("case",n)),(a=t.files)!=null&&a.length&&r.set("files",t.files.join(",")),t.maxHits&&r.set("max_hits",String(t.maxHits)),t.context!==void 0&&r.set("context",String(t.context)),t.sev&&r.set("sev",t.sev),t.q&&r.set("q",t.q),ct(`/api/recent_errors?${r}`)}function Po(t,r,a){const n=new URLSearchParams({case:t,file:r,n:String(a)});return Dr(`/api/tail?${n}`)}function Eo(t){return ct(`/api/resu_files?case=${encodeURIComponent(t)}`).then(r=>r.files??[])}function Ro(t){return ct(`/api/resu_dirs?${Or(t)}`).then(r=>r.dirs??[])}function en(t,r){const a=new URLSearchParams({scope:r});return t.forEach(n=>a.append("case",n)),ct(`/api/probes?${a}`).then(n=>n.files??[])}function No(t,r){const a=new URLSearchParams;return t.forEach(n=>a.append("case",n)),r.forEach(n=>a.append("probe",n)),ct(`/api/probe_columns?${a}`).then(n=>n.columns??[])}function Mo(t,r,a){const n=new URLSearchParams({case:t,probe:r});return a.forEach(s=>n.append("column",s)),ct(`/api/probe_position?${n}`)}function Ao(t){const r=new URLSearchParams;return t.cases.forEach(a=>r.append("case",a)),t.probes.forEach(a=>r.append("probe",a)),r.set("columns",t.columns.join(",")),t.axis&&r.set("axis",t.axis),t.xMin!==void 0&&r.set("x_min",String(t.xMin)),t.timeMin!==void 0&&r.set("time_min",String(t.timeMin)),r.set("include_history","true"),t.width&&r.set("width",String(t.width)),t.height&&r.set("height",String(t.height)),Dr(`/api/probe_svg?${r}`)}function Lo(t){const r=new URLSearchParams;return t.cases.forEach(a=>r.append("case",a)),t.base&&r.set("base",t.base),t.kind&&r.set("kind",t.kind),t.filter&&r.set("filter",t.filter),Dr(`/api/compare_runs?${r}`)}function To(){return yt("/api/sync_backends",{}).then(()=>{}).catch(()=>{})}function zn(t){return yt("/api/run_case",{cases:t.cases,n:t.n,nt:t.nt,max_parallel:t.maxParallel??void 0,backend:t.backend??void 0,options:t.options??void 0,restart:t.restart??!1,restart_mode:t.restartMode??"",restart_value:t.restartValue??void 0}).then(()=>{})}function Fo(t){return yt("/api/kill_case",{cases:t}).then(()=>{})}function kr(t){return yt("/api/control_case",{cases:t.cases,action:t.action,value:t.value??void 0}).then(()=>{})}function Io(t){return yt("/api/cleanup_cases",{cases:t.cases,keep_last:t.keepLast??1,prune_resu:t.pruneResu??!0,keep_resu:t.keepResu??[],delete_resu:t.deleteResu??[],max_log_mb:t.maxLogMb??50,clear_cid:!0,clear_pyc:!1})}function zo(t,r){return yt("/api/case_note",{case:t,note:r}).then(()=>{})}function Do(t,r){return yt("/api/case_convergence",{case:t,convergence:r}).then(()=>{})}function Oo(t){return yt("/api/open_gui",{case:t}).then(()=>{})}function Ho(){return ct("/api/settings/telemetry")}function jo(t){return yt("/api/settings/telemetry",{enabled:t})}var Vo=P(" ",1),Uo=P(''),Bo=P(''),qo=P(''),Ko=P(""),Go=P(""),Wo=P('
      '),Xo=P(`

      About to submit , which runs on your own account and + bills you for the compute. Results come back automatically.

      `),Yo=P(""),Qo=P(""),Zo=P('
      '),Jo=P('

      '),ei=P('
      ',1);function ti(t,r){Me(r,!0);const a=fo(),n=a.maxParallel??(r.cases.length>1?r.cases.length:0);let s=D(ce(a.n)),i=D(ce(a.nt)),o=D(ce(n));const l=xo().filter(z=>z!=="fake");let d=D(ce(l.includes(a.backend??"")?a.backend:"")),m=D(ce([])),x=D(!1),k=D(ce({}));Ne(()=>{const z=e(d);if(!z){f(m,[],!0),f(x,!1),f(k,{},!0);return}let A=!1;return yo(z).then(h=>{A||(f(m,h.options,!0),f(x,h.degraded,!0),f(k,Object.fromEntries(h.options.map(c=>[c.key,c.default])),!0))}).catch(()=>{A||(f(m,[],!0),f(x,!0),f(k,{},!0))}),()=>{A=!0}});async function b(){if(!Number.isFinite(e(s))||e(s)<=0){await Xe("MPI ranks must be an integer > 0.","Invalid value");return}if(!Number.isFinite(e(i))||e(i)<=0){await Xe("Threads must be an integer > 0.","Invalid value");return}if(e(o)&&(!Number.isFinite(e(o))||e(o)<=0)){await Xe("Max parallel must be empty or > 0.","Invalid value");return}const z={n:e(s),nt:e(i),maxParallel:e(o)||null,backend:e(d)||null,options:e(d)?e(k):void 0};vo(z),ot(z)}{const z=c=>{var C=Vo(),R=G(C);Pe(R,{variant:"secondary",onclick:()=>ot(null),children:(y,$)=>{we();var g=Ve("Cancel");u(y,g)},$$slots:{default:!0}});var p=E(R,2);Pe(p,{variant:"run",onclick:b,children:(y,$)=>{we();var g=Ve();ee(()=>ae(g,e(d)?`Run on ${e(d)}`:"Run")),u(y,g)},$$slots:{default:!0}}),u(c,C)};let A=V(()=>r.cases.length),h=V(()=>r.cases.length>1?"s":"");hr(t,{title:"Run Cases",titleId:"run-dialog-title",get subtitle(){return`${e(A)??""} case${e(h)??""} selected`},onConfirm:b,footer:z,children:(c,C)=>{var R=ei(),p=G(R),y=_(p);Re(y,{text:"MPI Ranks (n)",children:(K,re)=>{var w=Uo();Ye(w),tt(w,()=>e(s),S=>f(s,S)),u(K,w)}});var $=E(y,2);Re($,{text:"OMP Threads (nt)",children:(K,re)=>{var w=Bo();Ye(w),tt(w,()=>e(i),S=>f(i,S)),u(K,w)}});var g=E($,2);Re(g,{text:"Max Parallel",children:(K,re)=>{var w=qo();Ye(w),tt(w,()=>e(o),S=>f(o,S)),u(K,w)}}),v(p);var N=E(p,2);{var I=K=>{var re=Wo(),w=_(re);Re(w,{text:"Run on",children:(S,O)=>{var F=Go(),M=_(F);M.value=M.__value="";var T=E(M);He(T,16,()=>l,B=>B,(B,te)=>{var ie=Ko(),W=_(ie,!0);v(ie);var ne={};ee(()=>{ae(W,te),ne!==(ne=te)&&(ie.value=(ie.__value=te)??"")}),u(B,ie)}),v(F),Zr(F,()=>e(d),B=>f(d,B)),u(S,F)}}),v(re),u(K,re)};Y(N,K=>{l.length&&K(I)})}var L=E(N,2);{var q=K=>{var re=Xo(),w=E(_(re)),S=_(w,!0);v(w);var O=E(w),F=E(O),M=_(F,!0);v(F),we(),v(re),ee(()=>{ae(S,r.cases.length),ae(O,` case${r.cases.length>1?"s":""} to `),ae(M,e(d))}),u(K,re)};Y(L,K=>{e(d)&&K(q)})}var H=E(L,2);{var j=K=>{var re=Zo();He(re,21,()=>e(m),w=>w.key,(w,S)=>{Re(w,{get text(){return e(S).label},children:(O,F)=>{var M=Qo();He(M,21,()=>e(S).choices,([T,B])=>T,(T,B)=>{var te=V(()=>ga(e(B),2));let ie=()=>e(te)[0],W=()=>e(te)[1];var ne=Yo(),oe=_(ne,!0);v(ne);var de={};ee(()=>{ae(oe,W()),de!==(de=ie())&&(ne.value=(ne.__value=ie())??"")}),u(T,ne)}),v(M),Zr(M,()=>e(k)[e(S).key],T=>e(k)[e(S).key]=T),u(O,M)}})}),v(re),u(K,re)};Y(H,K=>{e(d)&&e(m).length&&K(j)})}var U=E(H,2);{var X=K=>{var re=Jo(),w=_(re);v(re),ee(()=>ae(w,`Could not reach ${e(d)??""} to list what is available. The run will use the defaults.`)),u(K,re)};Y(U,K=>{e(d)&&e(x)&&K(X)})}u(c,R)},$$slots:{footer:!0,default:!0}})}Ae()}var ri=P(" ",1),ni=P(''),ai=P(''),si=P(''),oi=P(""),ii=P(''),li=P('
      ',1);function ci(t,r){Me(r,!0);const a=go(),n=a.maxParallel??(r.cases.length>1?r.cases.length:0);let s=D(ce(a.n)),i=D(ce(a.nt)),o=D(ce(n)),l=D(ce(a.mode)),d=D(ce(a.value)),m=V(()=>e(l)==="iterations"?"Additional iterations":"Additional physical time"),x=V(()=>e(l)==="iterations"?"1":"any"),k=V(()=>e(l)==="iterations"?"1":"0");async function b(){if(!Number.isFinite(e(s))||e(s)<=0){await Xe("MPI ranks must be an integer > 0.","Invalid value");return}if(!Number.isFinite(e(i))||e(i)<=0){await Xe("Threads must be an integer > 0.","Invalid value");return}if(e(o)&&(!Number.isFinite(e(o))||e(o)<=0)){await Xe("Max parallel must be empty or > 0.","Invalid value");return}if(!Number.isFinite(e(d))||e(d)<=0){await Xe("Value must be > 0.","Invalid value");return}if(e(l)==="iterations"&&!Number.isInteger(e(d))){await Xe("Iterations must be an integer.","Invalid value");return}const z={n:e(s),nt:e(i),maxParallel:e(o)||null,restartMode:e(l),restartValue:e(d)};ho({n:e(s),nt:e(i),maxParallel:e(o)||null,mode:e(l),value:e(d)}),ot(z)}{const z=c=>{var C=ri(),R=G(C);Pe(R,{variant:"secondary",onclick:()=>ot(null),children:(y,$)=>{we();var g=Ve("Cancel");u(y,g)},$$slots:{default:!0}});var p=E(R,2);Pe(p,{variant:"warning",onclick:b,children:(y,$)=>{we();var g=Ve("Restart");u(y,g)},$$slots:{default:!0}}),u(c,C)};let A=V(()=>r.cases.length),h=V(()=>r.cases.length>1?"s":"");hr(t,{title:"Restart Cases",titleId:"restart-dialog-title",get subtitle(){return`${e(A)??""} case${e(h)??""} selected`},onConfirm:b,footer:z,children:(c,C)=>{var R=li(),p=G(R),y=_(p);Re(y,{text:"MPI Ranks (n)",children:(q,H)=>{var j=ni();Ye(j),tt(j,()=>e(s),U=>f(s,U)),u(q,j)}});var $=E(y,2);Re($,{text:"OMP Threads (nt)",children:(q,H)=>{var j=ai();Ye(j),tt(j,()=>e(i),U=>f(i,U)),u(q,j)}});var g=E($,2);Re(g,{text:"Max Parallel",children:(q,H)=>{var j=si();Ye(j),tt(j,()=>e(o),U=>f(o,U)),u(q,j)}}),v(p);var N=E(p,2),I=_(N);Re(I,{text:"Stop criterion",children:(q,H)=>{var j=oi(),U=_(j);U.value=U.__value="iterations";var X=E(U);X.value=X.__value="physical_time",v(j),Zr(j,()=>e(l),K=>f(l,K)),u(q,j)}});var L=E(I,2);Re(L,{get text(){return e(m)},children:(q,H)=>{var j=ii();Ye(j),ee(()=>{ke(j,"min",e(k)),ke(j,"step",e(x))}),tt(j,()=>e(d),U=>f(d,U)),u(q,j)}}),v(N),u(c,R)},$$slots:{footer:!0,default:!0}})}Ae()}function Te(t,r){let a=me(r,"size",3,14);var n=be(),s=G(n);Ss(s,()=>r.icon,(i,o)=>{o(i,{get size(){return a()},class:"icon"})}),u(t,n)}/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const ui={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"};/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const di=t=>{for(const r in t)if(r.startsWith("aria-")||r==="role"||r==="title")return!0;return!1};/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const Dn=(...t)=>t.filter((r,a,n)=>!!r&&r.trim()!==""&&n.indexOf(r)===a).join(" ").trim();var fi=Ht("");function Be(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]),n=Oe(a,["name","color","size","strokeWidth","absoluteStrokeWidth","iconNode"]);Me(r,!1);let s=me(r,"name",8,void 0),i=me(r,"color",8,"currentColor"),o=me(r,"size",8,24),l=me(r,"strokeWidth",8,2),d=me(r,"absoluteStrokeWidth",8,!1),m=me(r,"iconNode",24,()=>[]);Ms();var x=fi();Ln(x,(z,A,h)=>({...ui,...z,...n,width:o(),height:o(),stroke:i(),"stroke-width":A,class:h}),[()=>di(n)?void 0:{"aria-hidden":"true"},()=>(Ut(d()),Ut(l()),Ut(o()),Mr(()=>d()?Number(l())*24/Number(o()):l())),()=>(Ut(Dn),Ut(s()),Ut(a),Mr(()=>Dn("lucide-icon","lucide",s()?`lucide-${s()}`:"",a.class)))]);var k=_(x);He(k,1,m,dn,(z,A)=>{var h=V(()=>ga(e(A),2));let c=()=>e(h)[0],C=()=>e(h)[1];var R=be(),p=G(R);Is(p,c,!0,(y,$)=>{Ln(y,()=>({...C()}))}),u(z,R)});var b=E(k);je(b,r,"default",{}),v(x),u(t,x),Ae()}function vi(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M12 5v14"}],["path",{d:"m19 12-7 7-7-7"}]];Be(t,Ue({name:"arrow-down"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function gi(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M8 3 4 7l4 4"}],["path",{d:"M4 7h16"}],["path",{d:"m16 21 4-4-4-4"}],["path",{d:"M20 17H4"}]];Be(t,Ue({name:"arrow-left-right"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function hi(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"m5 12 7-7 7 7"}],["path",{d:"M12 19V5"}]];Be(t,Ue({name:"arrow-up"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function pn(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"m6 9 6 6 6-6"}]];Be(t,Ue({name:"chevron-down"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function pi(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"m9 18 6-6-6-6"}]];Be(t,Ue({name:"chevron-right"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function mi(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["circle",{cx:"12",cy:"12",r:"10"}],["rect",{x:"9",y:"9",width:"6",height:"6",rx:"1"}]];Be(t,Ue({name:"circle-stop"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function _i(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m15 9-6 6"}],["path",{d:"m9 9 6 6"}]];Be(t,Ue({name:"circle-x"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function pr(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M12 15V3"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"}],["path",{d:"m7 10 5 5 5-5"}]];Be(t,Ue({name:"download"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function bi(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M7 16.3c2.2 0 4-1.83 4-4.05 0-1.16-.57-2.26-1.71-3.19S7.29 6.75 7 5.3c-.29 1.45-1.14 2.84-2.29 3.76S3 11.1 3 12.25c0 2.22 1.8 4.05 4 4.05z"}],["path",{d:"M12.56 6.6A10.97 10.97 0 0 0 14 3.02c.5 2.5 2 4.9 4 6.5s3 3.5 3 5.5a6.98 6.98 0 0 1-11.91 4.97"}]];Be(t,Ue({name:"droplets"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function xi(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["circle",{cx:"12",cy:"12",r:"1"}],["circle",{cx:"19",cy:"12",r:"1"}],["circle",{cx:"5",cy:"12",r:"1"}]];Be(t,Ue({name:"ellipsis"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function yi(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M15 3h6v6"}],["path",{d:"M10 14 21 3"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"}]];Be(t,Ue({name:"external-link"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function wi(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M12 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 12 18z"}],["path",{d:"M2 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 2 18z"}]];Be(t,Ue({name:"fast-forward"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function ki(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["rect",{x:"14",y:"3",width:"5",height:"18",rx:"1"}],["rect",{x:"5",y:"3",width:"5",height:"18",rx:"1"}]];Be(t,Ue({name:"pause"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Ci(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M13 21h8"}],["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"}]];Be(t,Ue({name:"pen-line"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Si(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"}],["path",{d:"m15 5 4 4"}]];Be(t,Ue({name:"pencil"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Sa(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"}]];Be(t,Ue({name:"play"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function $i(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M5 12h14"}],["path",{d:"M12 5v14"}]];Be(t,Ue({name:"plus"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function er(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"}],["path",{d:"M21 3v5h-5"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"}],["path",{d:"M8 16H3v5"}]];Be(t,Ue({name:"refresh-cw"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Pi(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"}],["path",{d:"M3 3v5h5"}]];Be(t,Ue({name:"rotate-ccw"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Ei(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"}],["path",{d:"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7"}],["path",{d:"M7 3v4a1 1 0 0 0 1 1h7"}]];Be(t,Ue({name:"save"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Ri(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"}],["circle",{cx:"12",cy:"12",r:"3"}]];Be(t,Ue({name:"settings"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}function Ni(t,r){const a=Oe(r,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v1.0.1 - ISC + * + * ISC License + * + * Copyright (c) 2026 Lucide Icons and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The following Lucide icons are derived from the Feather project: + * + * airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + * + * The MIT License (MIT) (for the icons listed above) + * + * Copyright (c) 2013-present Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M10 11v6"}],["path",{d:"M14 11v6"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"}],["path",{d:"M3 6h18"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"}]];Be(t,Ue({name:"trash-2"},()=>a,{get iconNode(){return n},children:(s,i)=>{var o=be(),l=G(o);je(l,r,"default",{}),u(s,o)},$$slots:{default:!0}}))}var Mi=P('
    • '),Ai=P('
        '),Li=P('
        ',1);function lt(t,r){Me(r,!0);let a=me(r,"value",3,""),n=me(r,"placeholder",3,"Select..."),s=me(r,"class",3,""),i=me(r,"buttonClass",3,""),o=D(!1),l=D(void 0),d=D(void 0),m=D(""),x=V(()=>{var L;return((L=r.options.find(q=>q.value===a()))==null?void 0:L.label)??n()});function k(L){var q;f(o,!1),(q=r.onchange)==null||q.call(r,L)}function b(){if(!e(l)||!e(d))return;const L=e(l).getBoundingClientRect(),q=e(d).offsetHeight,H=4,j=window.innerHeight-L.bottom-H,X=jj?L.top-H-q:L.bottom+H,K=Math.min(L.left,window.innerWidth-L.width);f(m,`position:fixed; top:${X}px; left:${K}px; width:${L.width}px; z-index:9999;`)}async function z(){f(o,!e(o)),e(o)&&(await Et(),b())}function A(L){L.key==="Escape"&&f(o,!1)}function h(L){e(o)&&e(l)&&!e(l).contains(L.target)&&e(d)&&!e(d).contains(L.target)&&f(o,!1)}var c=Li();rt("mousedown",it,h),rt("keydown",it,A),rt("scroll",it,()=>{e(o)&&f(o,!1)});var C=G(c),R=_(C),p=_(R),y=_(p,!0);v(p);var $=E(p,2),g=_($);Te(g,{get icon(){return pn},size:14}),v($),v(R),nt(R,L=>f(l,L),()=>e(l)),v(C);var N=E(C,2);{var I=L=>{Jt(L,{children:(q,H)=>{var j=Ai();He(j,21,()=>r.options,U=>U.value,(U,X)=>{var K=Mi();ke(K,"tabindex",0);var re=_(K,!0);v(K),ee(()=>{ke(K,"aria-selected",e(X).value===a()),Se(K,1,`px-2.5 py-1.5 text-[13px] cursor-pointer transition-colors duration-100 + ${e(X).value===a()?"text-edf-bleu-fonce font-bold bg-[rgba(16,87,200,0.06)]":"text-ink hover:bg-edf-gris-clair"}`),ae(re,e(X).label)}),ue("mousedown",K,w=>{w.stopPropagation(),k(e(X).value)}),u(U,K)}),v(j),nt(j,U=>f(d,U),()=>e(d)),ee(()=>Ze(j,e(m))),u(q,j)}})};Y(N,L=>{e(o)&&L(I)})}ee(()=>{Se(C,1,`relative inline-flex ${s()??""}`),Se(R,1,dt(i()||"flex items-center justify-between gap-1.5 w-full h-[30px] cursor-pointer bg-white border border-border rounded-md text-[13px] text-ink font-normal pl-2.5 pr-2 transition-colors duration-150 hover:bg-edf-gris-clair focus:outline-none focus:border-edf-bleu-moyen")),ke(R,"aria-expanded",e(o)),ae(y,e(x)),Se($,1,`text-muted shrink-0 transition-transform duration-150 ${e(o)?"rotate-180":""}`)}),ue("mousedown",R,L=>{L.stopPropagation(),z()}),u(t,c),Ae()}Ge(["mousedown"]);var Ti=Ht(''),Fi=P('
        '),Ii=P('
        '),zi=P('
        ',1);function Wt(t,r){Me(r,!0);let a=me(r,"selected",19,()=>[]),n=me(r,"placeholder",3,"Select..."),s=me(r,"class",3,""),i=D(!1),o=D(void 0),l=D(void 0),d=D(""),m=V(()=>new Set(a())),x=V(()=>a().length===0?n():a().length===r.options.length?`All (${r.options.length})`:a().length<=2?a().map(H=>{var j;return((j=r.options.find(U=>U.value===H))==null?void 0:j.label)??H}).join(", "):`${a().length} selected`);function k(H){var U;const j=new Set(e(m));j.has(H)?j.delete(H):j.add(H),(U=r.onchange)==null||U.call(r,[...j])}function b(){var H;(H=r.onchange)==null||H.call(r,r.options.map(j=>j.value))}function z(){var H;(H=r.onchange)==null||H.call(r,[])}function A(){if(!e(o)||!e(l))return;const H=e(o).getBoundingClientRect(),j=e(l).offsetHeight,U=4,X=window.innerHeight-H.bottom-U,re=XX?H.top-U-j:H.bottom+U,w=Math.min(H.left,window.innerWidth-H.width);f(d,`position:fixed; top:${re}px; left:${w}px; min-width:${H.width}px; z-index:9999;`)}async function h(){f(i,!e(i)),e(i)&&(await Et(),A())}function c(H){e(i)&&e(o)&&!e(o).contains(H.target)&&e(l)&&!e(l).contains(H.target)&&f(i,!1)}function C(H){H.key==="Escape"&&f(i,!1)}var R=zi();rt("mousedown",it,c),rt("keydown",it,C),rt("scroll",it,()=>{e(i)&&f(i,!1)});var p=G(R),y=_(p),$=_(y),g=_($,!0);v($);var N=E($,2),I=_(N);Te(I,{get icon(){return pn},size:14}),v(N),v(y),nt(y,H=>f(o,H),()=>e(o)),v(p);var L=E(p,2);{var q=H=>{Jt(H,{children:(j,U)=>{var X=Ii(),K=_(X),re=_(K),w=E(re,2);v(K);var S=E(K,2);He(S,17,()=>r.options,O=>O.value,(O,F)=>{var M=Fi(),T=_(M),B=_(T);{var te=oe=>{var de=Ti();u(oe,de)},ie=V(()=>e(m).has(e(F).value));Y(B,oe=>{e(ie)&&oe(te)})}v(T);var W=E(T,2),ne=_(W,!0);v(W),v(M),ee(oe=>{Se(T,1,`inline-flex items-center justify-center shrink-0 w-[14px] h-[14px] rounded border-2 transition-colors duration-100 + ${oe??""}`),ae(ne,e(F).label)},[()=>e(m).has(e(F).value)?"bg-edf-bleu-moyen border-edf-bleu-moyen text-white":"bg-white border-edf-gris-moyen"]),ue("mousedown",M,oe=>{oe.stopPropagation(),k(e(F).value)}),u(O,M)}),v(X),nt(X,O=>f(l,O),()=>e(l)),ee(()=>Ze(X,e(d))),ue("mousedown",re,O=>{O.stopPropagation(),b()}),ue("mousedown",w,O=>{O.stopPropagation(),z()}),u(j,X)}})};Y(L,H=>{e(i)&&H(q)})}ee(()=>{Se(p,1,`relative inline-flex ${s()??""}`),ke(y,"aria-expanded",e(i)),ae(g,e(x)),Se(N,1,`text-muted shrink-0 transition-transform duration-150 ${e(i)?"rotate-180":""}`)}),ue("mousedown",y,H=>{H.stopPropagation(),h()}),u(t,R),Ae()}Ge(["mousedown"]);var Di=P(" ",1),Oi=P(''),Hi=P('Loading...'),ji=P('No folders found'),Vi=P('
        '),Ui=P('
        ',1);function Bi(t,r){Me(r,!0);const a=po();let n=D("keep_latest"),s=D(ce(a.keepLast)),i=D(ce([])),o=D(ce([])),l=D(!0),d=V(()=>e(n)==="keep_folder"||e(n)==="delete_folder");const m=[{value:"keep_latest",label:"Keep latest N"},{value:"delete_all",label:"Delete all RESU"},{value:"keep_folder",label:"Keep specific folders"},{value:"delete_folder",label:"Delete specific folders"}];let x=V(()=>e(i).map(b=>({value:b,label:b})));Jn(async()=>{try{f(i,await Ro(r.cases),!0)}catch{f(i,[],!0)}f(l,!1)});async function k(){if(e(d)&&e(o).length===0){await Xe("Please select at least one RESU folder.","Missing selection");return}const b={action:e(n)};e(n)==="keep_latest"?(b.keepLast=e(s),mo({keepLast:e(s)})):e(n)==="delete_all"?b.keepLast=0:e(n)==="keep_folder"?b.keepResu=e(o):e(n)==="delete_folder"&&(b.deleteResu=e(o)),ot(b)}{const b=h=>{var c=Di(),C=G(c);Pe(C,{variant:"secondary",onclick:()=>ot(null),children:(p,y)=>{we();var $=Ve("Cancel");u(p,$)},$$slots:{default:!0}});var R=E(C,2);Pe(R,{variant:"primary",onclick:k,children:(p,y)=>{we();var $=Ve("Clean");u(p,$)},$$slots:{default:!0}}),u(h,c)};let z=V(()=>r.cases.length),A=V(()=>r.cases.length>1?"s":"");hr(t,{title:"Cleanup Cases",titleId:"clean-dialog-title",get subtitle(){return`${e(z)??""} case${e(A)??""} selected`},footer:b,children:(h,c)=>{var C=Ui(),R=G(C),p=_(R);Re(p,{text:"Action",children:(I,L)=>{lt(I,{class:"w-[200px]",get options(){return m},get value(){return e(n)},onchange:q=>f(n,q,!0)})}});var y=E(p,2);{var $=I=>{Re(I,{text:"Keep last",children:(L,q)=>{var H=Oi();Ye(H),tt(H,()=>e(s),j=>f(s,j)),u(L,H)}})};Y(y,I=>{e(n)==="keep_latest"&&I($)})}v(R);var g=E(R,2);{var N=I=>{var L=Vi(),q=_(L);Re(q,{text:"RESU folders",children:(H,j)=>{var U=be(),X=G(U);{var K=S=>{var O=Hi();u(S,O)},re=S=>{var O=ji();u(S,O)},w=S=>{Wt(S,{class:"w-full",get options(){return e(x)},get selected(){return e(o)},onchange:O=>f(o,O,!0),placeholder:"Select folders..."})};Y(X,S=>{e(l)?S(K):e(i).length===0?S(re,1):S(w,-1)})}u(H,U)}}),v(L),u(I,L)};Y(g,I=>{e(d)&&I(N)})}u(h,C)},$$slots:{footer:!0,default:!0}})}Ae()}function qi(t,r){Me(r,!0);let a=V(Xs);var n=be(),s=G(n);{var i=m=>{ti(m,{get cases(){return e(a).props.cases}})},o=m=>{ci(m,{get cases(){return e(a).props.cases}})},l=m=>{Bi(m,{get cases(){return e(a).props.cases}})},d=m=>{lo(m,{get mode(){return e(a).type},get title(){return e(a).props.title},get message(){return e(a).props.message},get confirmLabel(){return e(a).props.confirmLabel},get confirmVariant(){return e(a).props.confirmVariant},get value(){return e(a).props.value},get placeholder(){return e(a).props.placeholder},get multiline(){return e(a).props.multiline}})};Y(s,m=>{e(a).type==="run"?m(i):e(a).type==="restart"?m(o,1):e(a).type==="clean"?m(l,2):(e(a).type==="alert"||e(a).type==="confirm"||e(a).type==="prompt")&&m(d,3)})}u(t,n),Ae()}let fr=D(ce([])),Ki=0;function Gi(){return e(fr)}function Cr(t,r="success",a=3500){const n=++Ki;f(fr,[...e(fr),{id:n,message:t,variant:r}],!0),setTimeout(()=>$a(n),a)}function $a(t){f(fr,e(fr).filter(r=>r.id!==t),!0)}var Wi=P('
        '),Xi=P('
        ');function Yi(t,r){Me(r,!0);let a=V(Gi);Jt(t,{children:(n,s)=>{var i=Xi();He(i,21,()=>e(a),o=>o.id,(o,l)=>{var d=Wi(),m=_(d,!0);v(d),ee(()=>{Se(d,1,`pointer-events-auto max-w-[320px] px-4 py-2.5 rounded-md shadow-lg text-sm font-semibold text-white cursor-pointer ${e(l).variant==="success"?"bg-edf-vert-fonce":"bg-edf-orange-fonce"}`),ae(m,e(l).message)}),ue("click",d,()=>$a(e(l).id)),u(o,d)}),v(i),u(n,i)}}),Ae()}Ge(["click"]);var Qi=Ht(''),Zi=Ht(''),Ji=P(''),el=P(" ",1),tl=P(" ",1),rl=P('');function Ot(t,r){Me(r,!0);const a=h=>{var c=Ji(),C=_(c);{var R=y=>{var $=Qi();ee(()=>Ze($,`width: ${o()-4}px; height: ${o()-4}px;`)),u(y,$)},p=y=>{var $=Zi();ee(()=>Ze($,`width: ${o()-4}px; height: ${o()-4}px;`)),u(y,$)};Y(C,y=>{n()&&!s()?y(R):s()&&y(p,1)})}v(c),ee(()=>{Se(c,1,`inline-flex items-center justify-center rounded border-2 cursor-pointer transition-colors duration-100 shrink-0 + ${i()?"opacity-40 cursor-not-allowed":""} + ${n()||s()?"bg-edf-bleu-moyen border-edf-bleu-moyen text-white":"bg-white border-edf-gris-moyen hover:border-edf-bleu-clair"}`),Ze(c,`width: ${o()??""}px; height: ${o()??""}px;`),ke(c,"aria-checked",s()?"mixed":n()),ke(c,"aria-disabled",i()),ke(c,"tabindex",i()?-1:0)}),ue("click",c,m),ue("keydown",c,x),u(h,c)};let n=me(r,"checked",3,!1),s=me(r,"indeterminate",3,!1),i=me(r,"disabled",3,!1),o=me(r,"size",3,16),l=me(r,"label",3,""),d=me(r,"labelFirst",3,!1);function m(h){var C;if(h.stopPropagation(),i())return;const c=s()?!0:!n();(C=r.onchange)==null||C.call(r,c)}function x(h){(h.key===" "||h.key==="Enter")&&(h.preventDefault(),h.stopPropagation(),m(h))}var k=be(),b=G(k);{var z=h=>{var c=rl(),C=_(c);{var R=y=>{var $=el(),g=G($),N=_(g,!0);v(g);var I=E(g);a(I),ee(()=>ae(N,l())),u(y,$)},p=y=>{var $=tl(),g=G($);a(g);var N=E(g),I=_(N,!0);v(N),ee(()=>ae(I,l())),u(y,$)};Y(C,y=>{d()?y(R):y(p,-1)})}v(c),ue("click",c,m),ue("keydown",c,x),u(h,c)},A=h=>{a(h)};Y(b,h=>{l()?h(z):h(A,-1)})}u(t,k),Ae()}Ge(["click","keydown"]);function mn(t,r){try{const a=localStorage.getItem(t);return a===null?r:JSON.parse(a)}catch{return r}}function Hr(t,r){localStorage.setItem(t,JSON.stringify(r))}const tn=new Map;function mr(t,r,a){vt(t),tn.set(t,setInterval(r,a))}function vt(t){const r=tn.get(t);r!==void 0&&(clearInterval(r),tn.delete(t))}const Tt={status:1e3,tail:1e3,plot:3e3,probe:3e3,errors:5e3},nl=["status","plot","probe","tail","errors"],Wr={status:{label:"Status table",min:500},plot:{label:"Residual plots",min:1e3},probe:{label:"Probe plots",min:1e3},tail:{label:"Log tail",min:500},errors:{label:"Recent errors",min:2e3}},ir=mn("csauto_refresh_rates",{});let bt=D(ce({status:ir.status??Tt.status,tail:ir.tail??Tt.tail,plot:ir.plot??Tt.plot,probe:ir.probe??Tt.probe,errors:ir.errors??Tt.errors}));function al(){return e(bt)}function sl(t,r){f(bt,{...e(bt),[t]:r},!0),Hr("csauto_refresh_rates",e(bt))}function ol(){return e(bt).status}function On(){return e(bt).tail}function Hn(){return e(bt).plot}function il(){return e(bt).probe}function jn(){return e(bt).errors}const rn=new Set;function _r(t){return rn.add(t),()=>rn.delete(t)}function ll(){rn.forEach(t=>t())}const nn=mn("csauto_autorefresh_enabled",{});function tr(t){return nn[t]??!0}function rr(t,r){nn[t]=r,Hr("csauto_autorefresh_enabled",nn)}var cl=P(" ",1),ul=P('
        ms
        '),dl=P('
        Telemetry
        '),fl=P('
        API token
        Auto-refresh intervals
        ',1);function vl(t,r){Me(r,!0);let a=D(ce(wa())),n=D(ce({...al()})),s=D(!0),i=D(!1);Ne(()=>{Ho().then(m=>{f(s,m.enabled,!0),f(i,!0)}).catch(()=>{f(i,!1)})});function o(m){const{min:x}=Wr[m];let k=e(n)[m];(typeof k!="number"||isNaN(k))&&(k=Tt[m]),f(n,{...e(n),[m]:Math.max(x,Math.round(k))},!0)}function l(m,x){const k=x.target,b=k.value.replace(/[^0-9]/g,""),z=parseInt(b,10);isNaN(z)?b===""&&f(n,{...e(n),[m]:Wr[m].min},!0):f(n,{...e(n),[m]:z},!0),k.value=String(e(n)[m])}function d(){ka(e(a));for(const m of Object.keys(e(n)))o(m),sl(m,e(n)[m]);e(i)&&jo(e(s)).catch(()=>{}),r.onClose()}hr(t,{title:"Settings",titleId:"settings-dialog-title",get onCancel(){return r.onClose},onConfirm:d,maxWidth:"400px",portal:!0,footer:x=>{var k=cl(),b=G(k);Pe(b,{variant:"secondary",get onclick(){return r.onClose},children:(A,h)=>{we();var c=Ve("Cancel");u(A,c)},$$slots:{default:!0}});var z=E(b,2);Pe(z,{variant:"primary",onclick:d,children:(A,h)=>{we();var c=Ve("Save");u(A,c)},$$slots:{default:!0}}),u(x,k)},children:(x,k)=>{var b=fl(),z=G(b),A=E(_(z),2);Ye(A),v(z);var h=E(z,2),c=_(h),C=E(_(c),2);v(c);var R=E(c,2);He(R,20,()=>nl,$=>$,($,g)=>{const N=V(()=>Wr[g]);var I=ul(),L=_(I),q=_(L),H=_(q,!0);v(q);var j=E(q,2),U=_(j);v(j),v(L);var X=E(L,2),K=_(X);Ye(K),we(2),v(X),v(I),ee(()=>{ae(H,e(N).label),ae(U,`min ${e(N).min??""}ms`),fn(K,e(n)[g])}),ue("input",K,re=>l(g,re)),rt("blur",K,()=>o(g)),u($,I)}),v(R),v(h);var p=E(h,2);{var y=$=>{var g=dl(),N=E(_(g),2);Ot(N,{get checked(){return e(s)},onchange:I=>f(s,I,!0),label:"Send anonymous usage statistics"}),v(g),u($,g)};Y(p,$=>{e(i)&&$(y)})}tt(A,()=>e(a),$=>f(a,$)),ue("click",C,()=>f(n,{...Tt},!0)),u(x,b)},$$slots:{footer:!0,default:!0}}),Ae()}Ge(["click","input"]);const gl=""+new URL("../assets/code-saturne.BHojVttu.svg",import.meta.url).href,hl="data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='UTF-8'?%3e%3csvg%20id='Calque_1'%20xmlns='http://www.w3.org/2000/svg'%20version='1.1'%20height='400px'%20viewBox='0%200%20397.89%2095.85'%3e%3c!--%20Generator:%20Adobe%20Illustrator%2029.8.1,%20SVG%20Export%20Plug-In%20.%20SVG%20Version:%202.1.1%20Build%202)%20--%3e%3cdefs%3e%3cstyle%3e%20.st0%20{%20fill:%20%23ff861d;%20}%20.st1%20{%20fill:%20%23ffb210;%20}%20.st2%20{%20fill:%20%231057c8;%20}%20.st3%20{%20fill:%20%23d6430a;%20}%20%3c/style%3e%3c/defs%3e%3cpath%20class='st2'%20d='M26.25,61.73c-12.02,0-20.11-8.16-20.11-21.37s8.01-21.37,20.33-21.37c8.83,0,15.06,3.78,17.36,11.28l-5.64,2.52c-1.48-5.94-4.97-8.69-11.87-8.69-8.46,0-14.25,6.01-14.25,16.25s5.93,16.25,13.95,16.25,11.73-2.82,12.84-8.61l5.71,1.93c-2,7.79-9.13,11.8-18.33,11.8'/%3e%3cpath%20class='st2'%20d='M89.96,40.36c0,13.21-7.87,21.37-20.48,21.37s-20.48-8.16-20.48-21.37,7.86-21.37,20.48-21.37,20.48,8.16,20.48,21.37M54.94,40.36c0,10.24,5.64,16.25,14.54,16.25s14.54-6.01,14.54-16.25-5.64-16.25-14.54-16.25-14.54,6.01-14.54,16.25'/%3e%3cpath%20class='st2'%20d='M134.3,40.29c0,13.43-8.01,20.85-21.81,20.85h-14.84V19.58h14.84c13.8,0,21.81,7.42,21.81,20.7M128.36,40.29c0-9.94-5.57-15.58-15.44-15.58h-9.35v31.32h9.35c9.87,0,15.44-5.71,15.44-15.73'/%3e%3cpolygon%20class='st2'%20points='174.11%2056.02%20174.11%2061.14%20141.98%2061.14%20141.98%2019.58%20173.14%2019.58%20173.14%2024.7%20147.91%2024.7%20147.91%2037.54%20167.65%2037.54%20167.65%2042.66%20147.91%2042.66%20147.91%2056.02%20174.11%2056.02'/%3e%3cpath%20class='st2'%20d='M213.81,22.13c4.16-1.92,9.75-3.11,14.3-3.11,12.55,0,17.74,5.19,17.74,17.34v5.27c0,4.16.08,7.28.16,10.31.08,3.12.24,5.99.48,9.19h-9.43c-.4-2.16-.4-4.88-.48-6.15h-.16c-2.48,4.56-7.83,7.11-12.71,7.11-7.28,0-14.39-4.4-14.39-12.23,0-6.15,2.96-9.75,7.03-11.75,4.08-2,9.35-2.4,13.83-2.4h5.91c0-6.63-2.96-8.87-9.27-8.87-4.56,0-9.11,1.76-12.71,4.48l-.32-9.19ZM226.36,54.26c3.27,0,5.83-1.44,7.51-3.68,1.76-2.32,2.24-5.27,2.24-8.47h-4.63c-4.8,0-11.91.8-11.91,7.11,0,3.52,2.96,5.04,6.79,5.04'/%3e%3cpath%20class='st2'%20d='M280.98,28.61c-3.28-1.12-5.67-1.76-9.59-1.76-2.88,0-6.31,1.04-6.31,4.56,0,6.56,18.62,2.4,18.62,17.27,0,9.59-8.55,13.43-17.27,13.43-4.08,0-8.23-.72-12.15-1.76l.64-8.79c3.36,1.68,6.87,2.72,10.55,2.72,2.72,0,7.03-1.04,7.03-5.04,0-8.07-18.62-2.56-18.62-17.42,0-8.87,7.75-12.79,16.14-12.79,5.03,0,8.31.8,11.67,1.52l-.72,8.07Z'/%3e%3cpath%20class='st2'%20d='M295.25,27.81h-7.91v-7.83h7.91v-8.23l10.71-3.44v11.67h9.51v7.83h-9.51v19.18c0,3.52.96,6.79,5.04,6.79,1.92,0,3.76-.4,4.88-1.12l.32,8.47c-2.24.64-4.72.96-7.91.96-8.39,0-13.03-5.2-13.03-13.35v-20.94Z'/%3e%3cpath%20class='st2'%20d='M355.22,58.82c-3.92,2.16-8.39,3.28-14.15,3.28-13.59,0-21.5-7.83-21.5-21.34,0-11.91,6.31-21.74,19.1-21.74,15.27,0,19.58,10.47,19.58,24.85h-28.45c.48,6.63,5.11,10.39,11.75,10.39,5.2,0,9.67-1.92,13.67-4.15v8.71ZM348.03,36.52c-.32-5.19-2.72-9.67-8.71-9.67s-9.03,4.16-9.51,9.67h18.22Z'/%3e%3cpath%20class='st2'%20d='M366.57,19.98h9.51v9.35h.16c.48-3.84,4.87-10.31,11.27-10.31,1.04,0,2.16,0,3.27.32v10.79c-.96-.56-2.88-.88-4.79-.88-8.71,0-8.71,10.87-8.71,16.78v15.11h-10.71V19.98Z'/%3e%3cpath%20class='st0'%20d='M229.73,80.9c0,3.67-2.97,6.64-6.64,6.64s-6.64-2.97-6.64-6.64,2.97-6.64,6.64-6.64,6.64,2.97,6.64,6.64'/%3e%3cpath%20class='st0'%20d='M203.17,80.9c0,3.67-2.97,6.64-6.64,6.64s-6.64-2.97-6.64-6.64,2.97-6.64,6.64-6.64,6.64,2.97,6.64,6.64'/%3e%3cpath%20class='st1'%20d='M296.14,80.9c0,3.67-2.97,6.64-6.64,6.64s-6.64-2.97-6.64-6.64,2.97-6.64,6.64-6.64,6.64,2.97,6.64,6.64'/%3e%3cpath%20class='st1'%20d='M378.47,80.9c0,3.67-2.97,6.64-6.64,6.64s-6.64-2.97-6.64-6.64,2.97-6.64,6.64-6.64,6.64,2.97,6.64,6.64'/%3e%3cpath%20class='st1'%20d='M269.57,80.9c0,3.67-2.97,6.64-6.64,6.64s-6.64-2.97-6.64-6.64,2.97-6.64,6.64-6.64,6.64,2.97,6.64,6.64'/%3e%3cpath%20class='st3'%20d='M107.55,80.9c0,3.67-2.97,6.64-6.64,6.64s-6.64-2.97-6.64-6.64,2.97-6.64,6.64-6.64,6.64,2.97,6.64,6.64'/%3e%3crect%20class='st0'%20x='134.12'%20y='75.59'%20width='42.5'%20height='10.62'/%3e%3crect%20class='st1'%20x='309.42'%20y='75.59'%20width='42.5'%20height='10.62'/%3e%3c/svg%3e";var pl=P('CODE_SATURNE'),ml=P('CODE_ASTER'),_l=P(' '),bl=P('
        ',1);function xl(t,r){Me(r,!0);let a=V(()=>r.shownCases!==r.totalCases),n=D(!1);var s=bl(),i=G(s),o=_(i),l=_(o);{var d=F=>{var M=pl();ee(()=>ke(M,"src",gl)),u(F,M)},m=V(()=>{var F;return!Ft()||((F=Ft())==null?void 0:F.solver)==="code_saturne"}),x=F=>{var M=ml();ee(()=>ke(M,"src",hl)),u(F,M)},k=V(()=>{var F;return((F=Ft())==null?void 0:F.solver)==="code_aster"}),b=F=>{var M=_l(),T=_(M,!0);v(M),ee(B=>ae(T,B),[()=>{var B;return(B=Ft())==null?void 0:B.solver}]),u(F,M)};Y(l,F=>{e(m)?F(d):e(k)?F(x,1):F(b,-1)})}v(o);var z=E(o,2),A=_(z),h=_(A),c=_(h);{var C=F=>{var M=Ve();ee(()=>ae(M,`${r.shownCases??""}/`)),u(F,M)};Y(c,F=>{e(a)&&F(C)})}var R=E(c,1,!0);v(h);var p=E(h,2),y=_(p,!0);v(p),v(A);var $=E(A,2),g=_($),N=_(g);{var I=F=>{var M=Ve();ee(()=>ae(M,`${r.shownRunning??""}/`)),u(F,M)};Y(N,F=>{e(a)&&F(I)})}var L=E(N,1,!0);v(g),we(2),v($);var q=E($,2),H=_(q),j=_(H);{var U=F=>{var M=Ve();ee(()=>ae(M,`${r.shownConverged??""}/`)),u(F,M)};Y(j,F=>{e(a)&&F(U)})}var X=E(j,1,!0);v(H),we(2),v(q),v(z);var K=E(z,2),re=_(K),w=_(re);Te(w,{get icon(){return Ri},size:18}),v(re),v(K),v(i);var S=E(i,2);{var O=F=>{vl(F,{onClose:()=>f(n,!1)})};Y(S,F=>{e(n)&&F(O)})}ee(()=>{ae(R,r.totalCases),ae(y,r.totalCases===1?"case":"cases"),ae(L,r.totalRunning),ae(X,r.totalConverged)}),ue("click",re,()=>f(n,!0)),u(t,s),Ae()}Ge(["click"]);const yl=""+new URL("../assets/simvia-logo.kLDq7Uoj.svg",import.meta.url).href;var wl=P('
        '),kl=P('

        '),Cl=P("
        "),Sl=P('
        '),$l=P('
        '),Pl=P('
        ');function jt(t,r){let a=me(r,"eyebrow",3,""),n=me(r,"title",3,""),s=me(r,"wide",3,!1);var i=Pl(),o=E(_(i),2);{var l=m=>{var x=$l(),k=_(x);{var b=c=>{var C=be(),R=G(C);ft(R,()=>r.titleSlot),u(c,C)},z=c=>{var C=Cl(),R=_(C);{var p=g=>{var N=wl(),I=_(N,!0);v(N),ee(()=>ae(I,a())),u(g,N)};Y(R,g=>{a()&&g(p)})}var y=E(R,2);{var $=g=>{var N=kl(),I=_(N,!0);v(N),ee(()=>ae(I,n())),u(g,N)};Y(y,g=>{n()&&g($)})}v(C),u(c,C)};Y(k,c=>{r.titleSlot?c(b):c(z,-1)})}var A=E(k,2);{var h=c=>{var C=Sl(),R=_(C);ft(R,()=>r.actions),v(C),u(c,C)};Y(A,c=>{r.actions&&c(h)})}v(x),u(m,x)};Y(o,m=>{(a()||n()||r.titleSlot||r.actions)&&m(l)})}var d=E(o,2);ft(d,()=>r.children),v(i),ee(()=>{Se(i,1,`${s()?"col-span-12":"col-span-6"} bg-card border border-border rounded-[10px] p-[16px_18px_18px] relative overflow-clip animate-rise`),ke(i,"id",r.id)}),u(t,i)}function nr(t,r){Me(r,!0);let a=me(r,"checked",15,!0);Ne(()=>{a()?mr(r.name,r.onRefresh,r.intervalMs):vt(r.name)}),vr(()=>vt(r.name)),Ot(t,{get checked(){return a()},onchange:n=>a(n),size:14,label:"Auto-refresh",labelFirst:!0}),Ae()}var El=P(" ",1),Rl=P('
      • '),Nl=P(''),Ml=P(' ',1);function Al(t,r){Me(r,!0);let a=me(r,"label",3,"More"),n=D(!1),s=D(void 0),i=D(void 0),o=D("");function l(){if(!e(s)||!e(i))return;const c=e(s).getBoundingClientRect(),C=e(i).offsetHeight,R=4,p=window.innerHeight-c.bottom-R,$=pp?c.top-R-C:c.bottom+R,g=Math.min(c.left,window.innerWidth-180);f(o,`position:fixed; top:${$}px; left:${g}px; min-width:170px; z-index:9999;`)}async function d(){f(n,!e(n)),e(n)&&(await Et(),l())}function m(c){c.disabled||(f(n,!1),c.onClick())}function x(c){e(n)&&e(s)&&!e(s).contains(c.target)&&e(i)&&!e(i).contains(c.target)&&f(n,!1)}var k=Ml();rt("mousedown",it,x),rt("keydown",it,c=>{c.key==="Escape"&&f(n,!1)}),rt("scroll",it,()=>{e(n)&&f(n,!1)});var b=G(k),z=_(b);Pe(z,{variant:"secondary",size:"sm",onclick:d,children:(c,C)=>{var R=El(),p=G(R);Te(p,{get icon(){return xi}});var y=E(p);ee(()=>ae(y,` ${a()??""}`)),u(c,R)},$$slots:{default:!0}}),v(b),nt(b,c=>f(s,c),()=>e(s));var A=E(b,2);{var h=c=>{Jt(c,{children:(C,R)=>{var p=Nl();He(p,21,()=>r.items,y=>y.label,(y,$)=>{var g=Rl(),N=_(g),I=_(N);Te(I,{get icon(){return e($).icon},size:14});var L=E(I);v(N),v(g),ee(()=>{N.disabled=e($).disabled,ke(N,"aria-disabled",e($).disabled),Se(N,1,`w-full flex items-center gap-2 text-left py-[7px] px-[10px] bg-transparent border border-transparent rounded-md text-[12.5px] text-ink transition-[background] duration-[120ms] ease-in-out disabled:opacity-40 disabled:pointer-events-none ${e($).disabled?"":"cursor-pointer hover:bg-edf-gris-clair hover:border-border"}`),ae(L,` ${e($).label??""}`)}),ue("mousedown",N,q=>{q.stopPropagation(),m(e($))}),u(y,g)}),v(p),nt(p,y=>f(i,y),()=>e(i)),ee(()=>Ze(p,e(o))),u(C,p)}})};Y(A,c=>{e(n)&&c(h)})}u(t,k),Ae()}Ge(["mousedown"]);const It=[{key:"note",label:"Note"},{key:"nprocs",label:"MPI Ranks"},{key:"nt",label:"Thread Count"},{key:"last_iter",label:"Last Iter"},{key:"duration",label:"Duration"},{key:"last_mod",label:"Last Modified"},{key:"resu_size_mb",label:"RESU Size (MB)"}];let Xt=D(ce([])),Lr=D(ce([])),pt=D(ce(new Set)),br=D(""),_n=D(""),Rt=D(ce([])),st=D(ce([])),Yt=D(""),Pa=D(!0),Ll=D(!0),Kt=D(ce(mn("csauto_status_views",{}))),bn=D(ce(localStorage.getItem("csauto_status_view_selected")??""));function xn(){return e(Xt)}function yn(){return e(Lr)}function Bt(){return e(pt)}function Vn(){return e(br)}function an(){return e(_n)}function Tl(){return e(Rt)}function Fl(){return e(Rt).filter(t=>e(Lr).includes(t))}function Il(){return e(Rt).filter(t=>It.some(r=>r.key===t))}function zl(){return e(st)}function Dl(){return e(Yt)}function Ol(){return e(Pa)}function Un(){return e(Kt)}function Hl(){return e(bn)}function jl(t){f(Xt,t,!0)}function Vl(t){const r=e(Lr).length===0&&t.length>0;f(Lr,t,!0),r&&e(Rt).length===0&&f(Rt,[...t,...It.map(a=>a.key)],!0)}function Ul(t){f(br,t,!0)}function Bn(t){f(Rt,t,!0)}function Bl(t){f(Yt,t,!0)}function ql(t){f(Pa,t,!0)}function Kl(t){f(Ll,t,!0)}function wn(t){f(bn,t,!0),localStorage.setItem("csauto_status_view_selected",t)}function sn(t){const r=new Set(e(pt));r.has(t)?r.delete(t):r.add(t),f(pt,r,!0),f(br,t,!0),f(_n,t,!0)}function Ea(t){f(pt,new Set([t]),!0),f(br,t,!0),f(_n,t,!0)}function on(t,r,a){const n=a.indexOf(t),s=a.indexOf(r);if(n<0||s<0)return;const[i,o]=n{const a=Gl(r);return t.every(n=>a.includes(n))})}function Wl(t){if(t==null||t==="")return"";const r=Number(t);return Number.isFinite(r)?r:String(t).toLowerCase()}function Gn(t,r){var n;if(r==="case_id")return t.case_id;if(r==="status")return t.status??"";if(r==="note")return t.note??"";if(r==="nprocs")return t.nprocs??0;if(r==="nt")return t.nt??0;if(r==="last_iter")return t.last_iter??0;if(r==="duration")return t.duration_s??0;if(r==="last_mod")return t.last_mod??"";if(r==="resu_size_mb")return t.resu_size_mb??0;const a=(n=t.doe)==null?void 0:n[r];return a!==void 0?Wl(a):""}function Xl(t,r){return t===""&&r===""?0:t===""?1:r===""?-1:typeof t=="number"&&typeof r=="number"?t-r:String(t).localeCompare(String(r))}function Na(){const t=Ra();return e(st).length===0?t:[...t].sort((r,a)=>{for(const n of e(st)){const s=Gn(r,n.key),i=Gn(a,n.key),o=Xl(s,i);if(o!==0)return n.dir==="asc"?o:-o}return 0})}function Rr(){return Na().map(t=>t.case_id)}function Yl(t,r){const a=e(st).findIndex(n=>n.key===t);if(r)if(a>=0){const n=[...e(st)];n[a]={key:t,dir:n[a].dir==="asc"?"desc":"asc"},f(st,n,!0)}else f(st,[...e(st),{key:t,dir:"asc"}],!0);else a>=0&&e(st).length===1?f(st,[{key:t,dir:e(st)[0].dir==="asc"?"desc":"asc"}],!0):f(st,[{key:t,dir:"asc"}],!0)}function Ql(){return{visibleColumns:[...e(Rt)],search:e(Yt),sorts:e(st).map(t=>({...t}))}}function Zl(t){f(Rt,[...t.visibleColumns],!0),f(Yt,t.search,!0),f(st,t.sorts.map(r=>({...r})),!0)}function Jl(t){f(Kt,{...e(Kt),[t]:Ql()},!0),Hr("csauto_status_views",e(Kt)),wn(t)}function ec(t){const r={...e(Kt)};delete r[t],f(Kt,r,!0),Hr("csauto_status_views",r),e(bn)===t&&wn("")}function Sr(){const t=new Set;for(const r of e(Xt))e(pt).has(r.case_id)&&t.add((r.status??"").toUpperCase());return t}function Wn(){for(const t of e(Xt))if(e(pt).has(t.case_id)&&t.resu_size_mb!=null&&t.resu_size_mb>0)return!0;return!1}function tc(t,r){const a=r.length,n=t.length,s=r.filter(d=>d.status==="RUNNING").length,i=t.filter(d=>d.status==="RUNNING").length,o=r.filter(d=>d.convergence==="converged").length,l=t.filter(d=>d.convergence==="converged").length;return{totalCases:a,shownCases:n,totalRunning:s,shownRunning:i,totalConverged:o,shownConverged:l}}var rc=P('
        DOE columns
        '),nc=P('
        ');function ac(t,r){Me(r,!0);let a=me(r,"initialName",3,""),n=V(yn),s=D(ce(a())),i=D(ce(new Set(r.initialColumns??[...e(n),...It.map(U=>U.key)])));function o(U){const X=new Set(e(i));X.has(U)?X.delete(U):X.add(U),f(i,X,!0)}function l(U,X){const K=new Set(e(i));U.forEach(re=>X?K.add(re):K.delete(re)),f(i,K,!0)}function d(){const U=e(s).trim();U&&r.onSave(U,[...e(i)])}function m(U){U.key==="Escape"&&r.onCancel()}function x(U){U.target.dataset.backdrop!==void 0&&r.onCancel()}let k=D(void 0);Ne(()=>{var U,X;(U=e(k))==null||U.focus(),(X=e(k))==null||X.select()});var b=nc(),z=_(b),A=_(z),h=_(A,!0);v(A);var c=E(A,2),C=E(_(c),2);Ye(C),nt(C,U=>f(k,U),()=>e(k)),v(c);var R=E(c,2);{var p=U=>{var X=rc(),K=_(X),re=E(_(K),2),w=_(re),S=E(w,2);v(re),v(K);var O=E(K,2);He(O,20,()=>e(n),F=>F,(F,M)=>{{let T=V(()=>e(i).has(M));Ot(F,{get checked(){return e(T)},onchange:()=>o(M),size:14,get label(){return M}})}}),v(O),v(X),ue("click",w,()=>l(e(n),!0)),ue("click",S,()=>l(e(n),!1)),u(U,X)};Y(R,U=>{e(n).length>0&&U(p)})}var y=E(R,2),$=_(y),g=E(_($),2),N=_(g),I=E(N,2);v(g),v($);var L=E($,2);He(L,21,()=>It,U=>U.key,(U,X)=>{{let K=V(()=>e(i).has(e(X).key));Ot(U,{get checked(){return e(K)},onchange:()=>o(e(X).key),size:14,get label(){return e(X).label}})}}),v(L),v(y);var q=E(y,2),H=_(q);Pe(H,{variant:"secondary",get onclick(){return r.onCancel},children:(U,X)=>{we();var K=Ve("Cancel");u(U,K)},$$slots:{default:!0}});var j=E(H,2);{let U=V(()=>!e(s).trim());Pe(j,{variant:"primary",onclick:d,get disabled(){return e(U)},children:(X,K)=>{we();var re=Ve("Save");u(X,re)},$$slots:{default:!0}})}v(q),v(z),v(b),ee(()=>ae(h,r.mode==="create"?"New view":"Edit view")),ue("keydown",b,m),ue("click",b,x),tt(C,()=>e(s),U=>f(s,U)),ue("click",N,()=>l(It.map(U=>U.key),!0)),ue("click",I,()=>l(It.map(U=>U.key),!1)),u(t,b),Ae()}Ge(["keydown","click"]);var sc=P('
        ',1);function oc(t,r){Me(r,!0);let a=V(()=>Object.keys(Un())),n=D(ce(Hl())),s=D(!1),i=D("create"),o=V(()=>[{value:"",label:"All columns"},...e(a).map($=>({value:$,label:$}))]);function l($){if(f(n,$,!0),wn($),$==="")Bn([...yn(),...It.map(g=>g.key)]);else{const g=Un();g[$]&&Zl(g[$])}}function d(){f(i,"edit"),f(s,!0)}function m(){f(i,"create"),f(s,!0)}function x($,g){Bn(g),e(i)==="edit"&&e(n)&&$!==e(n)&&ec(e(n)),Jl($),f(n,$,!0),f(s,!1)}function k(){f(s,!1)}var b=sc(),z=G(b),A=_(z);lt(A,{class:"w-[140px]",get options(){return e(o)},get value(){return e(n)},onchange:l});var h=E(A,2),c=_(h);Te(c,{get icon(){return Si},size:14}),v(h);var C=E(h,2),R=_(C);Te(R,{get icon(){return $i},size:14}),v(C),v(z);var p=E(z,2);{var y=$=>{Jt($,{children:(g,N)=>{{let I=V(()=>e(i)==="edit"?e(n):""),L=V(Tl);ac(g,{get mode(){return e(i)},get initialName(){return e(I)},get initialColumns(){return e(L)},onSave:x,onCancel:k})}}})};Y(p,$=>{e(s)&&$(y)})}ue("click",h,d),ue("click",C,m),u(t,b),Ae()}Ge(["click"]);var ic=P('
        '),lc=P(""),cc=P(' '),uc=P(' '),dc=P('
        '),fc=P(" "),vc=P(''),gc=P(''),hc=P(" Open GUI",1),pc=P(""),mc=P(" "),_c=P(""),bc=P('
        ');function xc(t,r){Me(r,!0);let a=V(()=>r.rows.length>0&&r.rows.every(w=>e(m).has(w.case_id))),n=V(()=>r.rows.some(w=>e(m).has(w.case_id))),s=V(()=>e(n)&&!e(a)),i=V(Fl),o=V(()=>new Set(Il())),l=V(zl),d=V(Ol),m=V(Bt),x=V(()=>{const w=[{key:"_select",label:"",kind:"meta",sticky:"left"},{key:"case_id",label:"Case",kind:"meta",sticky:"left"},{key:"status",label:"Status",kind:"meta",sticky:"left"},...e(o).has("note")?[{key:"note",label:"Note",kind:"meta"}]:[]],S=e(i).map(T=>({key:T,label:T,kind:"doe"})),F=[{key:"nprocs",label:"MPI Ranks",kind:"calc"},{key:"nt",label:"Thread Count",kind:"calc"},{key:"last_iter",label:"Last Iter",kind:"calc"},{key:"duration",label:"Duration",kind:"calc"},{key:"last_mod",label:"Last Modified",kind:"calc"},{key:"resu_size_mb",label:"RESU Size (MB)",kind:"calc"}].filter(T=>e(o).has(T.key)),M=cr("gui")?[{key:"_actions",label:"",kind:"meta",sticky:"right"}]:[];return[...w,...S,...F,...M]});function k(w){const S=e(l).findIndex(F=>F.key===w);if(S<0)return"";const O=e(l)[S].dir==="asc"?"▲":"▼";return e(l).length>1?`${O}${S+1}`:O}function b(w,S){w!=="_actions"&&Yl(w,S.ctrlKey||S.metaKey)}function z(w,S){S.shiftKey?on(an()||w,w,Rr()):S.ctrlKey||S.metaKey?sn(w):Ea(w)}function A(w,S){var F;const O=((F=w.status)==null?void 0:F.toUpperCase())??"";O!=="DONE"&&O!=="FAILED"||(S.preventDefault(),r.onContextMenu(w.case_id,S.clientX,S.clientY,w.convergence??""))}async function h(w){const S=w.note??"",O=await gn("Case note:",S,`Note — ${w.case_id}`,"",!0);if(O!==null)try{await zo(w.case_id,O)}catch(F){await Xe(`Failed to set note: ${F instanceof Error?F.message:F}`,"Error")}}async function c(w){try{await Oo(w)}catch(S){await Xe(`Failed to open GUI: ${S instanceof Error?S.message:S}`,"Error")}}function C(w,S){const O=(w==null?void 0:w.toUpperCase())??"";return(O==="DONE"||O==="FAILED")&&S==="converged"?"CONVERGED":(O==="DONE"||O==="FAILED")&&S==="not_converged"?"NOT CONVERGED":O}function R(w,S){const O=(w==null?void 0:w.toUpperCase())??"";return(O==="DONE"||O==="FAILED")&&S==="converged"?"status-converged":(O==="DONE"||O==="FAILED")&&S==="not_converged"?"status-not-converged":O==="RUNNING"?"status-running":O==="DONE"?"status-done":O==="FAILED"?"status-failed":O==="PREPARED"?"status-prepared":"status-unknown"}function p(w){return e(m).has(w.case_id)?"row-selected":""}function y(w,S){var O;return S.key==="case_id"?w.case_id:S.key==="nprocs"?w.nprocs!=null?String(w.nprocs):"":S.key==="nt"?w.nt!=null?String(w.nt):"":S.key==="last_iter"?w.last_iter!=null?String(w.last_iter):"":S.key==="duration"?w.duration??"":S.key==="last_mod"?$(w.last_mod):S.key==="resu_size_mb"?w.resu_size_mb!=null?String(w.resu_size_mb):"":S.kind==="doe"?((O=w.doe)==null?void 0:O[S.key])!=null&&String(w.doe[S.key])!==""?String(w.doe[S.key]):"—":""}function $(w){if(!w)return"";try{const S=new Date(w),O=new Date,F=S.toDateString()===O.toDateString(),M=new Date(O);M.setDate(M.getDate()-1);const T=S.toDateString()===M.toDateString(),B=S.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});return F?`Today ${B}`:T?`Yesterday ${B}`:S.toLocaleDateString([],{month:"short",day:"numeric"})+` ${B}`}catch{return w}}let g=D(ce({})),N=D(void 0);Ne(()=>{e(d),e(x),r.rows,!(!e(N)||!e(d))&&Et().then(()=>{if(!e(N))return;const w=Array.from(e(N).querySelectorAll("thead th.sticky-left")),S={};let O=0;w.forEach(F=>{const M=F.dataset.colKey??"";S[M]=O,O+=F.getBoundingClientRect().width}),f(g,S,!0)})});function I(w,S,O){const F=O&&w.key!=="_select"&&w.key!=="_actions"?["status-sortable"]:[];if(!O&&w.key==="case_id"&&F.push("case-id"),w.kind==="doe"&&F.push("status-col-doe"),!O&&w.key==="note"&&F.push("max-w-[140px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer hover:underline hover:decoration-dotted hover:underline-offset-2"),e(d)&&w.sticky==="left"&&F.push("sticky-left"),e(d)&&w.sticky==="right"&&F.push("sticky-right"),e(d)&&w.sticky==="left"){const M=e(x)[S+1];(!M||M.sticky!=="left")&&F.push("sticky-divider")}if(e(d)&&w.sticky==="right"){const M=e(x)[S-1];(!M||M.sticky!=="right")&&F.push("sticky-divider")}return F.join(" ")}function L(w,S){return I(w,S,!0)}function q(w,S){return I(w,S,!1)}function H(w){return!e(d)||!w.sticky?"":w.sticky==="right"?"position:sticky;right:0;":w.sticky==="left"&&w.key in e(g)?`position:sticky;left:${e(g)[w.key]}px;`:""}var j=bc(),U=_(j),X=_(U),K=_(X);He(K,23,()=>e(x),w=>w.key,(w,S,O)=>{var F=be(),M=G(F);{var T=ie=>{var W=ic(),ne=_(W),oe=_(ne);Ot(oe,{get checked(){return e(a)},get indeterminate(){return e(s)},get onchange(){return r.onToggleAll},size:14}),v(ne),v(W),ee((de,ve)=>{Se(W,1,`${de??""} !px-2.5 !py-0 w-9`),ke(W,"data-col-key",e(S).key),Ze(W,ve)},[()=>L(e(S),e(O)),()=>H(e(S))]),u(ie,W)},B=ie=>{var W=lc();ee((ne,oe)=>{Se(W,1,ne),ke(W,"data-col-key",e(S).key),Ze(W,oe)},[()=>dt(L(e(S),e(O))),()=>H(e(S))]),u(ie,W)},te=ie=>{var W=uc(),ne=_(W),oe=E(ne);{var de=se=>{var _e=cc(),he=_(_e,!0);v(_e),ee(Ee=>ae(he,Ee),[()=>k(e(S).key)]),u(se,_e)},ve=V(()=>k(e(S).key));Y(oe,se=>{e(ve)&&se(de)})}v(W),ee((se,_e)=>{Se(W,1,se),ke(W,"data-col-key",e(S).key),Ze(W,_e),ae(ne,`${e(S).label??""} `)},[()=>dt(L(e(S),e(O))),()=>H(e(S))]),ue("click",W,se=>b(e(S).key,se)),ue("keydown",W,se=>{(se.key==="Enter"||se.key===" ")&&(se.preventDefault(),b(e(S).key,se))}),u(ie,W)};Y(M,ie=>{e(S).key==="_select"?ie(T):e(S).key==="_actions"?ie(B,1):ie(te,-1)})}u(w,F)}),v(K),v(X);var re=E(X);He(re,21,()=>r.rows,w=>w.case_id,(w,S)=>{var O=_c();He(O,23,()=>e(x),F=>F.key,(F,M,T)=>{var B=be(),te=G(B);{var ie=ve=>{var se=dc(),_e=_(se),he=_(_e);{let Ee=V(()=>e(m).has(e(S).case_id));Ot(he,{get checked(){return e(Ee)},onchange:()=>r.onToggleRow(e(S).case_id),size:14})}v(_e),v(se),ee((Ee,pe)=>{Se(se,1,`${Ee??""} !px-2.5 !py-0 w-9`),ke(se,"data-col-key",e(M).key),Ze(se,pe)},[()=>q(e(M),e(T)),()=>H(e(M))]),u(ve,se)},W=ve=>{var se=fc(),_e=_(se),he=_(_e,!0);v(_e),v(se),ee((Ee,pe,Ie,ze)=>{Se(se,1,Ee),ke(se,"data-col-key",e(M).key),Ze(se,pe),Se(_e,1,`status-pill ${Ie??""}`),ae(he,ze)},[()=>dt(q(e(M),e(T))),()=>H(e(M)),()=>R(e(S).status,e(S).convergence),()=>C(e(S).status,e(S).convergence)]),u(ve,se)},ne=ve=>{var se=gc(),_e=_(se);{var he=pe=>{var Ie=Ve();ee(()=>ae(Ie,e(S).note)),u(pe,Ie)},Ee=pe=>{var Ie=vc(),ze=_(Ie);Te(ze,{get icon(){return Ci},size:12}),v(Ie),u(pe,Ie)};Y(_e,pe=>{e(S).note?pe(he):pe(Ee,-1)})}v(se),ee((pe,Ie)=>{Se(se,1,pe),ke(se,"data-col-key",e(M).key),Ze(se,Ie),ke(se,"title",e(S).note||"Add note")},[()=>dt(q(e(M),e(T))),()=>H(e(M))]),ue("click",se,pe=>{pe.stopPropagation(),h(e(S))}),ue("keydown",se,pe=>{(pe.key==="Enter"||pe.key===" ")&&(pe.preventDefault(),pe.stopPropagation(),h(e(S)))}),u(ve,se)},oe=ve=>{var se=pc(),_e=_(se);Pe(_e,{variant:"primary",size:"sm",onclick:he=>{he.stopPropagation(),c(e(S).case_id)},children:(he,Ee)=>{var pe=hc(),Ie=G(pe);Te(Ie,{get icon(){return yi},size:12}),we(),u(he,pe)},$$slots:{default:!0}}),v(se),ee((he,Ee)=>{Se(se,1,he),ke(se,"data-col-key",e(M).key),Ze(se,Ee)},[()=>dt(q(e(M),e(T))),()=>H(e(M))]),u(ve,se)},de=ve=>{var se=mc(),_e=_(se,!0);v(se),ee((he,Ee,pe)=>{Se(se,1,he),ke(se,"data-col-key",e(M).key),Ze(se,Ee),ae(_e,pe)},[()=>dt(q(e(M),e(T))),()=>H(e(M)),()=>y(e(S),e(M))]),u(ve,se)};Y(te,ve=>{e(M).key==="_select"?ve(ie):e(M).key==="status"?ve(W,1):e(M).key==="note"?ve(ne,2):e(M).key==="_actions"?ve(oe,3):ve(de,-1)})}u(F,B)}),v(O),ee(F=>Se(O,1,F),[()=>dt(p(e(S)))]),ue("click",O,F=>z(e(S).case_id,F)),ue("contextmenu",O,F=>A(e(S),F)),u(w,O)}),v(re),v(U),nt(U,w=>f(N,w),()=>e(N)),v(j),ee(()=>Se(j,1,`table-wrap ${e(d)?"sticky-enabled":""}`)),u(t,j),Ae()}Ge(["click","keydown","contextmenu"]);var yc=P('
        ');function wc(t,r){Me(r,!0);let a=V(()=>{const b=Math.min(r.x,window.innerWidth-180),z=Math.min(r.y,window.innerHeight-120);return`left: ${b}px; top: ${z}px;`});async function n(b){try{for(const z of r.cases)await Do(z,b)}catch(z){console.error("Failed to set convergence:",z)}r.onClose()}const s=r.cases.length>1?` (${r.cases.length})`:"";var i=yc();rt("keydown",it,b=>{b.key==="Escape"&&r.onClose()});var o=_(i),l=_(o);v(o);var d=E(o,2),m=_(d);v(d);var x=E(d,2),k=_(x);v(x),v(i),ee(()=>{Ze(i,e(a)),Se(o,1,`w-full text-left py-[7px] px-[10px] bg-transparent border border-transparent rounded-md text-[12.5px] cursor-pointer transition-[background] duration-[120ms] ease-in-out hover:bg-edf-gris-clair hover:border-border ${r.currentValue==="converged"?"is-active":""}`),ae(l,`Mark Converged${s}`),Se(d,1,`w-full text-left py-[7px] px-[10px] bg-transparent border border-transparent rounded-md text-[12.5px] cursor-pointer transition-[background] duration-[120ms] ease-in-out hover:bg-edf-gris-clair hover:border-border ${r.currentValue==="not_converged"?"is-active":""}`),ae(m,`Mark Not Converged${s}`),Se(x,1,`w-full text-left py-[7px] px-[10px] bg-transparent border border-transparent rounded-md text-[12.5px] cursor-pointer transition-[background] duration-[120ms] ease-in-out hover:bg-edf-gris-clair hover:border-border ${r.currentValue===""?"is-active":""}`),ae(k,`Clear Mark${s}`)}),ue("click",o,()=>n("converged")),ue("click",d,()=>n("not_converged")),ue("click",x,()=>n("")),u(t,i),Ae()}Ge(["click"]);var kc=P(" Refresh",1),Cc=P(" ",1),Sc=P(" Run",1),$c=P(" Restart",1),Pc=P(" Stop",1),Ec=P(" Kill",1),Rc=P(" Clean",1),Nc=P('
        ',1),Mc=P('
        ',1);function Ac(t,r){Me(r,!0);async function a(){await To(),r.onRefresh()}function n(){return[...Bt()]}async function s(){const T=n();if(!T.length)return;const B=await Ys(T);if(B)try{await zn({cases:T,n:B.n,nt:B.nt,maxParallel:B.maxParallel,backend:B.backend,options:B.options}),r.onRefresh()}catch(te){await Xe(`Run failed: ${te instanceof Error?te.message:te}`,"Error")}}async function i(){const T=n();if(!T.length)return;const B=await Qs(T);if(B)try{await zn({cases:T,n:B.n,nt:B.nt,maxParallel:B.maxParallel,restart:!0,restartMode:B.restartMode,restartValue:B.restartValue}),r.onRefresh()}catch(te){await Xe(`Restart failed: ${te instanceof Error?te.message:te}`,"Error")}}async function o(){const T=n();if(!(!T.length||!await Fn(`Kill ${T.length} case${T.length>1?"s":""}?`,"Confirm Kill","Kill","danger")))try{await Fo(T),r.onRefresh()}catch(te){await Xe(`Kill failed: ${te instanceof Error?te.message:te}`,"Error")}}async function l(){const T=n();if(!(!T.length||!await Fn(`Stop ${T.length} case${T.length>1?"s":""} gracefully (checkpoint + exit)?`,"Confirm Stop","Stop","danger")))try{await kr({cases:T,action:"stop"}),r.onRefresh(),Cr(`Stop requested for ${T.length} case${T.length>1?"s":""} — will checkpoint and exit`)}catch(te){await Xe(`Stop failed: ${te instanceof Error?te.message:te}`,"Error")}}async function d(){const T=n();if(T.length)try{await kr({cases:T,action:"checkpoint"}),r.onRefresh(),Cr(`Checkpoint requested for ${T.length} case${T.length>1?"s":""}`)}catch(B){await Xe(`Checkpoint failed: ${B instanceof Error?B.message:B}`,"Error")}}async function m(){const T=n();if(!T.length)return;const B=await gn("Extend by how many additional time steps?","500","Extend Run");if(B===null)return;const te=parseInt(B,10);if(!Number.isFinite(te)||te<=0){await Xe("Enter a positive integer number of time steps.","Error");return}try{await kr({cases:T,action:"extend",value:te}),r.onRefresh(),Cr(`Extended ${T.length} case${T.length>1?"s":""} by ${te} time steps`)}catch(ie){await Xe(`Extend failed: ${ie instanceof Error?ie.message:ie}`,"Error")}}async function x(){const T=n();if(T.length)try{await kr({cases:T,action:"flush"}),Cr(`Flush requested for ${T.length} case${T.length>1?"s":""}`)}catch(B){await Xe(`Flush failed: ${B instanceof Error?B.message:B}`,"Error")}}async function k(){const T=n();if(!T.length)return;const B=await Zs(T);if(B)try{await Io({cases:T,keepLast:B.keepLast,keepResu:B.keepResu,deleteResu:B.deleteResu,pruneResu:!0}),r.onRefresh(),ll()}catch(te){await Xe(`Cleanup failed: ${te instanceof Error?te.message:te}`,"Error")}}function b(){const T=Rr();T.length>0&&T.every(te=>Bt().has(te))?Kn():qn(T)}function z(T){sn(T)}let A=D(ce(tr("status")));Ne(()=>{rr("status",e(A))}),ql(!0);let h=V(()=>{const T=Sr();return T.size>0&&[...T].some(B=>B==="PREPARED"||B==="DONE"||B==="FAILED")}),c=V(()=>{if(!cr("restart"))return!1;const T=Sr();return T.size>0&&[...T].some(B=>B==="DONE"||B==="FAILED")&&Wn()}),C=V(()=>{const T=Sr();return T.size>0&&T.has("RUNNING")}),R=V(()=>{if(!cr("control"))return!1;const T=Sr();return T.size>0&&T.has("RUNNING")});const p=[{label:"Extend",icon:wi,onClick:m,action:"extend"},{label:"Checkpoint",icon:Ei,onClick:d,action:"checkpoint"},{label:"Flush",icon:bi,onClick:x,action:"flush"}];let y=V(()=>p.filter(T=>bo(T.action)).map(T=>({label:T.label,icon:T.icon,onClick:T.onClick,disabled:!e(R)}))),$=V(Wn),g=D(ce(Dl())),N=null;function I(){N&&clearTimeout(N),N=setTimeout(()=>{Bl(e(g))},200)}Ne(()=>Kl(e(A)));let L=D(!1),q=D(0),H=D(0),j=D(ce([])),U=D("");function X(T,B,te,ie){f(j,Bt().has(T)?[...Bt()]:[T],!0),f(U,ie,!0),f(q,B,!0),f(H,te,!0),f(L,!0)}function K(){f(L,!1)}function re(T){const B=Rr();if(B.length){if(T.key==="Escape"){Kn(),K();return}if((T.ctrlKey||T.metaKey)&&T.key==="a"){T.preventDefault(),qn(B);return}if(T.key==="ArrowDown"||T.key==="ArrowUp"){T.preventDefault();const te=T.key==="ArrowDown"?1:-1,ie=Vn(),W=B.indexOf(ie),ne=Math.max(0,Math.min(B.length-1,W+te)),oe=B[ne];T.shiftKey?on(an()||B[0],oe,B):Ea(oe),Ul(oe);return}if(T.key===" "){T.preventDefault();const te=Vn();te&&(T.shiftKey?on(an()||te,te,Rr()):sn(te))}}}var w=Mc();rt("click",it,K),rt("scroll",it,K);var S=G(w),O=_(S);jt(O,{eyebrow:"Overview",title:"Status",wide:!0,id:"status-card-shell",actions:B=>{var te=Cc(),ie=G(te);{let oe=V(ol);nr(ie,{name:"status",get intervalMs(){return e(oe)},get onRefresh(){return r.onRefresh},get checked(){return e(A)},set checked(de){f(A,de,!0)}})}var W=E(ie,2);{var ne=oe=>{Pe(oe,{variant:"primary",onclick:a,children:(de,ve)=>{var se=kc(),_e=G(se);Te(_e,{get icon(){return er}}),we(),u(de,se)},$$slots:{default:!0}})};Y(W,oe=>{e(A)||oe(ne)})}u(B,te)},children:(B,te)=>{var ie=Nc(),W=G(ie),ne=_(W),oe=_(ne);Ye(oe);var de=E(oe,2);oc(de,{}),v(ne);var ve=E(ne,2),se=_(ve),_e=_(se);v(se);var he=E(se,2);{let J=V(()=>!e(h));Pe(he,{variant:"run",size:"sm",onclick:s,get disabled(){return e(J)},children:(le,xe)=>{var ye=Sc(),fe=G(ye);Te(fe,{get icon(){return Sa}}),we(),u(le,ye)},$$slots:{default:!0}})}var Ee=E(he,2);{var pe=J=>{{let le=V(()=>!e(c));Pe(J,{variant:"warning",size:"sm",onclick:i,get disabled(){return e(le)},children:(xe,ye)=>{var fe=$c(),ge=G(fe);Te(ge,{get icon(){return Pi}}),we(),u(xe,fe)},$$slots:{default:!0}})}},Ie=V(()=>cr("restart"));Y(Ee,J=>{e(Ie)&&J(pe)})}var ze=E(Ee,2);{var Qe=J=>{{let le=V(()=>!e(R));Pe(J,{variant:"warning",size:"sm",onclick:l,get disabled(){return e(le)},children:(xe,ye)=>{var fe=Pc(),ge=G(fe);Te(ge,{get icon(){return mi}}),we(),u(xe,fe)},$$slots:{default:!0}})}},qe=V(()=>cr("control"));Y(ze,J=>{e(qe)&&J(Qe)})}var Je=E(ze,2);{var Le=J=>{Al(J,{get items(){return e(y)}})};Y(Je,J=>{e(y).length>0&&J(Le)})}var $e=E(Je,2);{let J=V(()=>!e(C));Pe($e,{variant:"danger",size:"sm",onclick:o,get disabled(){return e(J)},children:(le,xe)=>{var ye=Ec(),fe=G(ye);Te(fe,{get icon(){return _i}}),we(),u(le,ye)},$$slots:{default:!0}})}var Q=E($e,2);{let J=V(()=>!e($));Pe(Q,{variant:"secondary",size:"sm",onclick:k,get disabled(){return e(J)},children:(le,xe)=>{var ye=Rc(),fe=G(ye);Te(fe,{get icon(){return Ni}}),we(),u(le,ye)},$$slots:{default:!0}})}v(ve),v(W);var Z=E(W,2);{let J=V(Na);xc(Z,{get rows(){return e(J)},onContextMenu:X,onToggleAll:b,onToggleRow:z})}ee(J=>ae(_e,`${J??""} selected`),[()=>Bt().size]),ue("input",oe,I),tt(oe,()=>e(g),J=>f(g,J)),u(B,ie)},$$slots:{actions:!0,default:!0}}),v(S);var F=E(S,2);{var M=T=>{wc(T,{get cases(){return e(j)},get x(){return e(q)},get y(){return e(H)},get currentValue(){return e(U)},onClose:K})};Y(F,T=>{e(L)&&T(M)})}ue("keydown",S,re),u(t,w),Ae()}Ge(["keydown","input"]);var Lc=P('
        ');function Qt(t,r){var a=Lc(),n=_(a);ft(n,()=>r.children),v(a),u(t,a)}function kn(t){return t.map(r=>({value:r,label:r}))}var Tc=P(''),Fc=P(" ",1);function Cn(t,r){Me(r,!0);let a=me(r,"columnLabel",3,"Columns"),n=me(r,"xMinLabel",3,"Iter min"),s=V(()=>kn(r.allCases)),i=V(()=>r.columns.map(d=>({value:d,label:d}))),o=[{value:"zero",label:"Zero"},{value:"restart",label:"Restart start"},{value:"custom",label:"Custom"}];function l(d){r.onXMinChange(Number(d.target.value)||0)}Qt(t,{children:(d,m)=>{var x=Fc(),k=G(x);Re(k,{text:"Cases",children:(y,$)=>{Wt(y,{class:"w-[160px]",get options(){return e(s)},get selected(){return r.selectedCases},get onchange(){return r.onCasesChange},placeholder:"Select cases..."})}});var b=E(k,2);{var z=y=>{var $=be(),g=G($);ft(g,()=>r.middleSlot),u(y,$)};Y(b,y=>{r.middleSlot&&y(z)})}var A=E(b,2);Re(A,{get text(){return a()},children:(y,$)=>{Wt(y,{class:"w-[160px]",get options(){return e(i)},get selected(){return r.selectedColumns},get onchange(){return r.onColumnsChange},placeholder:"Select..."})}});var h=E(A,2);Re(h,{text:"Start from",children:(y,$)=>{lt(y,{class:"w-[140px]",get options(){return o},get value(){return r.startFrom},onchange:g=>r.onStartFromChange(g)})}});var c=E(h,2);{var C=y=>{Re(y,{get text(){return n()},children:($,g)=>{var N=Tc();Ye(N),ee(()=>fn(N,r.xMin)),ue("input",N,l),u($,N)}})};Y(c,y=>{r.startFrom==="custom"&&y(C)})}var R=E(c,2);{var p=y=>{var $=be(),g=G($);ft(g,()=>r.extraSlot),u(y,$)};Y(R,y=>{r.extraSlot&&y(p)})}u(d,x)}}),Ae()}Ge(["input"]);var Ic=P('
        '),zc=P('
        ');function Zt(t,r){let a=me(r,"emptyMessage",3,"");var n=zc(),s=_(n);{var i=l=>{var d=be(),m=G(d);zr(m,()=>r.svgHtml),u(l,d)},o=l=>{var d=Ic(),m=_(d),x=_(m,!0);v(m),v(d),ee(()=>ae(x,a()||"No data to display.")),u(l,d)};Y(s,l=>{r.svgHtml?l(i):l(o,-1)})}v(n),ee(()=>ke(n,"id",r.id)),u(t,n)}async function Sn(t,r="plot.png"){const a=document.getElementById(t),n=a==null?void 0:a.querySelector("svg");if(!n)return;const{width:s,height:i}=Dc(n),o=window.devicePixelRatio||1,l=document.createElement("canvas");l.width=s*o,l.height=i*o;const d=l.getContext("2d");if(!d)return;d.scale(o,o);const m=new XMLSerializer().serializeToString(n),x=new Blob([m],{type:"image/svg+xml;charset=utf-8"}),k=URL.createObjectURL(x),b=new Image;b.width=s,b.height=i,await new Promise((A,h)=>{b.onload=()=>{d.drawImage(b,0,0,s,i),URL.revokeObjectURL(k),A()},b.onerror=h,b.src=k});const z=await new Promise(A=>l.toBlob(A,"image/png"));z&&await Aa(z,Oc(r))}function xr(t,r,a="png"){const n=r.length<=3?r.join("_"):`${r.length}_cases`,s=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19);return Hc(`${t}_${n}_${s}.${a}`)}async function Ma(t,r){const a=new Blob([t],{type:"text/csv;charset=utf-8"});await Aa(a,r)}function Dc(t){const r=t.getAttribute("viewBox");if(r){const a=r.split(/[\s,]+/).map(Number);if(a.length===4)return{width:a[2],height:a[3]}}return{width:t.width.baseVal.value||900,height:t.height.baseVal.value||500}}async function Aa(t,r){if("showSaveFilePicker"in window)try{const i=await(await window.showSaveFilePicker({suggestedName:r,types:[{description:t.type.startsWith("image/")?"Image":"File",accept:{[t.type]:[`.${r.split(".").pop()}`]}}]})).createWritable();await i.write(t),await i.close();return}catch(s){if((s==null?void 0:s.name)==="AbortError")return}const a=URL.createObjectURL(t),n=document.createElement("a");n.href=a,n.download=r,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(a)}function Oc(t){return t.endsWith(".png")?t:`${t}.png`}function Hc(t){return t.replace(/[^a-zA-Z0-9._-]/g,"_")}let La=D(ce([])),Ta=D(ce([])),Fa=D(ce([])),Ia=D("zero"),za=D(0),Da=D(""),jc=D(!0);function Vc(){return{selectedCases:e(La),columns:e(Ta),selectedColumns:e(Fa),startFrom:e(Ia),iterMin:e(za),svgHtml:e(Da),autoRefresh:e(jc)}}function Xn(t){f(La,t,!0)}function Uc(t){f(Ta,t,!0)}function Xr(t){f(Fa,t,!0)}function Bc(t){f(Ia,t,!0)}function qc(t){f(za,t,!0)}function $r(t){f(Da,t,!0)}var Kc=P(" Refresh",1),Gc=P(" ",1),Wc=P(" Download as PNG",1),Xc=P('
        '),Yc=P('
        ',1),Qc=P('
        '),Zc=P('

        '),Jc=P(" ",1);function eu(t,r){Me(r,!0);let a=V(Vc),n=D("");const s=new Set(["iteration","wall_distance","walldistance"]),i=["velocity","pressure"];async function o(){if(e(a).selectedCases.length)try{const h=await Co(e(a).selectedCases);f(n,"");const c=h.filter(C=>!s.has(C.toLowerCase().replace(/\s+/g,"_")));if(Uc(c),c.length===0){Xr([]),$r("");return}if(e(a).selectedColumns.length===0){const C=c.filter(R=>i.some(p=>R.toLowerCase().includes(p)));Xr(C.length>0?C:[c[0]])}}catch(h){f(n,"Failed to load residual columns"),console.error("Failed to load residual columns:",h)}}async function l(){if(e(a).selectedCases.length&&!((e(a).columns.length===0||e(a).selectedColumns.length===0)&&(await o(),!e(a).selectedColumns.length)))try{let h=0,c=!0;if(e(a).startFrom==="restart"){const R=await Ca(e(a).selectedCases),p=Object.values(R.origins).map(y=>y.iteration).filter(y=>y!==void 0&&Number.isFinite(y));h=p.length>0?Math.min(...p):0}else e(a).startFrom==="custom"&&(h=e(a).iterMin);const C=await So(e(a).selectedCases,e(a).selectedColumns,{xMin:h,includeHistory:c});f(n,""),C&&$r(C)}catch(h){f(n,"Failed to load residual plot"),console.error("Failed to load residual plot:",h)}}function d(h){if(Xn(h),h.length===0){$r("");return}o(),l()}function m(h){if(Xr(h),h.length===0){$r("");return}l()}function x(h){Bc(h),l()}function k(h){qc(h),l()}let b=D(ce(tr("plot")));Ne(()=>{rr("plot",e(b))}),Ne(()=>{e(b)?mr("residualPlot",l,Hn()):vt("residualPlot")});let z=!1;Ne(()=>{r.allCases.length>0&&!z&&(z=!0,Xn([...r.allCases]),o().then(()=>l()))});const A=_r(()=>{z&&o().then(()=>l())});vr(()=>{vt("residualPlot"),A()}),jt(t,{eyebrow:"Diagnostics",title:"Residuals Plot",wide:!0,actions:c=>{var C=Gc(),R=G(C);{let $=V(Hn);nr(R,{name:"residualPlot",get intervalMs(){return e($)},onRefresh:l,get checked(){return e(b)},set checked(g){f(b,g,!0)}})}var p=E(R,2);{var y=$=>{Pe($,{variant:"primary",onclick:l,children:(g,N)=>{var I=Kc(),L=G(I);Te(L,{get icon(){return er}}),we(),u(g,I)},$$slots:{default:!0}})};Y(p,$=>{e(b)||$(y)})}u(c,C)},children:(c,C)=>{var R=Jc(),p=G(R);{var y=I=>{const L=X=>{var K=Xc(),re=_(K);{let w=V(()=>!e(a).svgHtml);Pe(re,{variant:"secondary",size:"sm",onclick:()=>Sn("plot-holder",xr("residuals",e(a).selectedCases)),get disabled(){return e(w)},children:(S,O)=>{var F=Wc(),M=G(F);Te(M,{get icon(){return pr}}),we(),u(S,F)},$$slots:{default:!0}})}v(K),u(X,K)};var q=Yc(),H=G(q);Cn(H,{prefix:"plot",get allCases(){return r.allCases},get selectedCases(){return e(a).selectedCases},onCasesChange:d,columnLabel:"Variables",get columns(){return e(a).columns},get selectedColumns(){return e(a).selectedColumns},onColumnsChange:m,get startFrom(){return e(a).startFrom},onStartFromChange:x,xMinLabel:"Iter min",get xMin(){return e(a).iterMin},onXMinChange:k,get extraSlot(){return L}});var j=E(H,2),U=_(j);{let X=V(()=>e(a).selectedCases.length===0?"Please select at least one case.":e(a).columns.length===0?"No residuals for the selected cases yet.":e(a).selectedColumns.length===0?"Please select at least one variable.":"No data to display.");Zt(U,{id:"plot-holder",get svgHtml(){return e(a).svgHtml},get emptyMessage(){return e(X)}})}v(j),u(I,q)},$=I=>{var L=Qc(),q=_(L);Zt(q,{id:"plot-holder",svgHtml:"",emptyMessage:"No data available. Please run a simulation first."}),v(L),u(I,L)};Y(p,I=>{r.allCases.length>0?I(y):I($,-1)})}var g=E(p,2);{var N=I=>{var L=Zc(),q=_(L,!0);v(L),ee(()=>ae(q,e(n))),u(I,L)};Y(g,I=>{e(n)&&I(N)})}u(c,R)},$$slots:{actions:!0,default:!0}}),Ae()}function Oa(t){const{scope:r,getState:a,setState:n,getAxis:s}=t;async function i(){const k=a();if(k.selectedCases.length)try{let b=await en(k.selectedCases,r);if(t.filterFiles&&(b=t.filterFiles(b)),n({files:b}),b.length===0){n({file:"",columns:[],selectedColumns:[],positionText:"",svgHtml:""});return}(!k.file||!b.includes(k.file))&&(n({file:b[0]}),await o())}catch(b){console.error(`Failed to load ${r} files:`,b)}}async function o(){const k=a();if(!(!k.selectedCases.length||!k.file))try{const b=await No(k.selectedCases,[k.file]),z=t.filterColumns?t.filterColumns(b):b;t.onColumnsLoaded?t.onColumnsLoaded(b):n({columns:z});const A=a();if(A.columns.length===0){n({selectedColumns:[],positionText:"",svgHtml:""});return}A.selectedColumns.length===0&&n({selectedColumns:[A.columns[0]]})}catch(b){console.error(`Failed to load ${r} columns:`,b)}}async function l(){let k=a();if(k.selectedCases.length){if(await i(),k=a(),!k.file){n({positionText:"",svgHtml:""});return}if((k.columns.length===0||k.selectedColumns.length===0)&&(await o(),k=a(),!k.selectedColumns.length)){n({positionText:"",svgHtml:""});return}try{let b;const z=a();if(z.startFrom==="restart"){const h=await Ca(z.selectedCases),c=s().toLowerCase(),C=c==="t"||c==="time"?"time":"iteration",R=Object.values(h.origins).map(p=>p[C]).filter(p=>p!==void 0&&Number.isFinite(p));b=R.length>0?Math.min(...R):void 0}else z.startFrom==="custom"&&(b=z.xMin);const A=await Ao({cases:z.selectedCases,probes:[z.file],columns:z.selectedColumns,axis:s(),timeMin:typeof b=="number"?b:void 0,xMin:b,includeHistory:!0});A&&n({svgHtml:A})}catch(b){console.error(`Failed to load ${r} plot:`,b)}}}async function d(k){if(n({selectedCases:k}),k.length===0){n({svgHtml:""});return}await i(),r==="monitoring"&&await l()}function m(k){if(n({selectedColumns:k}),k.length===0){n({svgHtml:""});return}l()}async function x(k){n({file:k}),await o(),await l()}return{loadFiles:i,loadColumns:o,loadPlot:l,handleCasesChange:d,handleColumnsChange:m,handleFileChange:x}}function Ha(){return{selectedCases:[],file:"",files:[],columns:[],selectedColumns:[],axis:"time",startFrom:"zero",xMin:0,positionText:"",svgHtml:"",autoRefresh:!0}}let qt=D("time"),ln=D(ce(Ha())),cn=D(ce({...Ha(),axis:""})),tu=D(!0),Tr=D(!1),Fr=D(!1);function ru(){return e(qt)}function nu(t){f(qt,t,!0)}function au(){return e(Tr)}function Yn(t){f(Tr,t,!0)}function su(){return e(Fr)}function Qn(t){f(Fr,t,!0)}function ou(){e(qt)==="time"&&!e(Tr)&&e(Fr)?f(qt,"profile"):e(qt)==="profile"&&!e(Fr)&&e(Tr)&&f(qt,"time")}function Zn(){return e(ln)}function Yr(){return e(cn)}function Ct(t){f(ln,{...e(ln),...t},!0)}function At(t){f(cn,{...e(cn),...t},!0)}function iu(){return e(tu)}var lu=P(" Download as PNG",1),cu=P('
        '),uu=P("
        "),du=P('
        '),fu=P('
        ',1),vu=P('
        ');function gu(t,r){Me(r,!0);let a=me(r,"onRefresh",15),n=V(Zn);const s=new Set(["time","t","iteration","iter"]),i=Oa({scope:"monitoring",getState:Zn,setState:Ct,filterFiles:h=>h.filter(c=>{const C=c.replace(/\.csv$/i,"").toLowerCase();return C!=="coords"&&!C.includes("coordinates")}),filterColumns:h=>h.filter(c=>!s.has(c.toLowerCase())),onColumnsLoaded:h=>{const c=h.filter(C=>!s.has(C.toLowerCase()));Ct({columns:c}),o()},getAxis:()=>"time"});async function o(){if(!(!e(n).selectedCases.length||!e(n).file||!e(n).selectedColumns.length))try{const h=[];for(const $ of e(n).selectedColumns){const g=await Mo(e(n).selectedCases[0],e(n).file,[$]);g.found&&h.push({col:$,x:g.x,y:g.y,z:g.z})}if(!h.length){Ct({positionText:""});return}const c=$=>{let g=0;for(const N of $){if(N===void 0)continue;const I=String(N),L=I.indexOf(".");L>=0&&(g=Math.max(g,I.length-L-1))}return g},C=c(h.map($=>$.x)),R=c(h.map($=>$.y)),p=c(h.map($=>$.z)),y=h.map($=>{const g=[];return $.x!==void 0&&g.push(`x=${$.x.toFixed(C)}`),$.y!==void 0&&g.push(`y=${$.y.toFixed(R)}`),$.z!==void 0&&g.push(`z=${$.z.toFixed(p)}`),`Probe position [${$.col}] : ${g.join(" · ")}`});Ct({positionText:y.join(` +`)})}catch{Ct({positionText:""})}}function l(h){i.handleColumnsChange(h),h.length>0&&o()}let d=V(()=>e(n).files.map(h=>({value:h,label:h.replace(/\.csv$/i,"").replace(/^probes?_/i,"")}))),m=!1;Ne(()=>{r.allCases.length>0&&!m&&(m=!0,Ct({selectedCases:[...r.allCases]}),i.loadFiles().then(()=>i.loadPlot()))}),Ne(()=>{a(i.loadPlot)});let x=V(()=>e(n).selectedCases.length===0?"Please select at least one case.":e(n).files.length===0?"No probe data for the selected cases yet.":e(n).selectedColumns.length===0?"Please select at least one probe.":"No data to display.");var k=be(),b=G(k);{var z=h=>{const c=I=>{Re(I,{text:"Quantity",children:(L,q)=>{lt(L,{class:"w-[160px]",get options(){return e(d)},get value(){return e(n).file},get onchange(){return i.handleFileChange}})}})},C=I=>{var L=cu(),q=_(L);{let H=V(()=>!e(n).svgHtml);Pe(q,{variant:"secondary",size:"sm",onclick:()=>Sn("probe-plot-holder",xr("probe",e(n).selectedCases)),get disabled(){return e(H)},children:(j,U)=>{var X=lu(),K=G(X);Te(K,{get icon(){return pr}}),we(),u(j,X)},$$slots:{default:!0}})}v(L),u(I,L)};var R=fu(),p=G(R);Cn(p,{prefix:"probe",get allCases(){return r.allCases},get selectedCases(){return e(n).selectedCases},get onCasesChange(){return i.handleCasesChange},get middleSlot(){return c},columnLabel:"Probes",get columns(){return e(n).columns},get selectedColumns(){return e(n).selectedColumns},onColumnsChange:l,get startFrom(){return e(n).startFrom},onStartFromChange:I=>{Ct({startFrom:I}),i.loadPlot()},xMinLabel:"Time min",get xMin(){return e(n).xMin},onXMinChange:I=>{Ct({xMin:I}),i.loadPlot()},get extraSlot(){return C}});var y=E(p,2);{var $=I=>{var L=du();He(L,21,()=>e(n).positionText.split(` +`).sort(),dn,(q,H)=>{var j=uu(),U=_(j,!0);v(j),ee(()=>ae(U,e(H))),u(q,j)}),v(L),u(I,L)};Y(y,I=>{e(n).positionText&&I($)})}var g=E(y,2),N=_(g);Zt(N,{id:"probe-plot-holder",get svgHtml(){return e(n).svgHtml},get emptyMessage(){return e(x)}}),v(g),u(h,R)},A=h=>{var c=vu(),C=_(c);Zt(C,{id:"probe-plot-holder",svgHtml:"",emptyMessage:"No probe data available. Please run a simulation first."}),v(c),u(h,c)};Y(b,h=>{r.allCases.length>0?h(z):h(A,-1)})}u(t,k),Ae()}var hu=P(" ",1),pu=P(" Download as PNG",1),mu=P('
        '),_u=P('
        ',1),bu=P('
        ');function xu(t,r){Me(r,!0);let a=me(r,"onRefresh",15),n=V(Yr);const s=["s","x","abscissa","distance","arclength","arc_length","curvilinear","length","r","y","z","coord","position"];function i(c){const C=c.map(R=>R.toLowerCase());for(const R of s){const p=C.indexOf(R);if(p>=0)return c[p]}return c[0]??""}const o=Oa({scope:"profiles",getState:Yr,setState:At,onColumnsLoaded:c=>{const C=e(n).axis||i(c),R=c.filter(p=>p!==C).toSorted((p,y)=>p.localeCompare(y));At({columns:R,axis:C,selectedColumns:e(n).selectedColumns.length?e(n).selectedColumns:R.length?[R[0]]:[]})},getAxis:()=>Yr().axis});function l(c){const C=e(n).axis;At({axis:c});const p=[...e(n).columns,...C?[C]:[]].filter(y=>y!==c).toSorted((y,$)=>y.localeCompare($));At({columns:p}),o.loadPlot()}let d=V(()=>e(n).files.map(c=>({value:c,label:c.replace(/\.csv$/i,"").replace(/^profiles\//i,"").replace(/_/g," ")}))),m=V(()=>[...e(n).axis?[e(n).axis]:[],...e(n).columns].toSorted((c,C)=>c.localeCompare(C)).map(c=>({value:c,label:c}))),x=!1;Ne(()=>{r.allCases.length>0&&!x&&(x=!0,At({selectedCases:[...r.allCases]}),o.loadFiles().then(()=>o.loadPlot()))}),Ne(()=>{a(o.loadPlot)});let k=V(()=>e(n).selectedCases.length===0?"Please select at least one case.":e(n).files.length===0?"No profile data for the selected cases yet.":e(n).selectedColumns.length===0?"Please select at least one value.":"No data to display.");var b=be(),z=G(b);{var A=c=>{const C=N=>{var I=hu(),L=G(I);Re(L,{text:"Profile",children:(H,j)=>{lt(H,{class:"w-[160px]",get options(){return e(d)},get value(){return e(n).file},get onchange(){return o.handleFileChange}})}});var q=E(L,2);Re(q,{text:"X axis",children:(H,j)=>{lt(H,{class:"w-[140px]",get options(){return e(m)},get value(){return e(n).axis},onchange:l})}}),u(N,I)},R=N=>{var I=mu(),L=_(I);{let q=V(()=>!e(n).svgHtml);Pe(L,{variant:"secondary",size:"sm",onclick:()=>Sn("profile-plot-holder",xr("profile",e(n).selectedCases)),get disabled(){return e(q)},children:(H,j)=>{var U=pu(),X=G(U);Te(X,{get icon(){return pr}}),we(),u(H,U)},$$slots:{default:!0}})}v(I),u(N,I)};var p=_u(),y=G(p);Cn(y,{prefix:"profile",get allCases(){return r.allCases},get selectedCases(){return e(n).selectedCases},get onCasesChange(){return o.handleCasesChange},get middleSlot(){return C},columnLabel:"Values",get columns(){return e(n).columns},get selectedColumns(){return e(n).selectedColumns},get onColumnsChange(){return o.handleColumnsChange},get startFrom(){return e(n).startFrom},onStartFromChange:N=>{At({startFrom:N}),o.loadPlot()},xMinLabel:"X min",get xMin(){return e(n).xMin},onXMinChange:N=>{At({xMin:N}),o.loadPlot()},get extraSlot(){return R}});var $=E(y,2),g=_($);Zt(g,{id:"profile-plot-holder",get svgHtml(){return e(n).svgHtml},get emptyMessage(){return e(k)}}),v($),u(c,p)},h=c=>{var C=bu(),R=_(C);Zt(R,{id:"profile-plot-holder",svgHtml:"",emptyMessage:"No profile data available. Please run a simulation first."}),v(C),u(c,C)};Y(z,c=>{r.allCases.length>0?c(A):c(h,-1)})}u(t,b),Ae()}var yu=P('
        Diagnostics
        '),wu=P(" Refresh",1),ku=P(" ",1);function Cu(t,r){Me(r,!0);let a=V(ru),n=V(iu),s=V(au),i=V(su),o=D(ce(tr("probe")));Ne(()=>{rr("probe",e(o))});let l=D(void 0);function d(){var A;(A=e(l))==null||A()}async function m(){if(r.allCases.length){try{const h=(await en(r.allCases,"monitoring")).filter(c=>{const C=c.replace(/\.csv$/i,"").toLowerCase();return C!=="coords"&&!C.includes("coordinates")});Yn(h.length>0)}catch{Yn(!1)}try{const A=await en(r.allCases,"profiles");Qn(A.length>0)}catch{Qn(!1)}}}Ne(()=>{r.allCases.length>0&&m()}),_r(()=>{m(),d()}),Ne(()=>{e(s),e(i),ou()});function x(A){A==="time"&&!e(s)||A==="profile"&&!e(i)||nu(A)}var k=be(),b=G(k);{var z=A=>{{const h=C=>{var R=yu(),p=E(_(R),2),y=_(p),$=E(y,2);v(p),v(R),ee(()=>{Se(y,1,`bg-transparent border-none p-0 m-0 text-lg font-bold tracking-tight transition-colors duration-150 + ${e(s)?e(a)==="time"?"text-edf-bleu-fonce cursor-pointer":"text-edf-gris-moyen cursor-pointer hover:text-muted":"text-edf-gris-moyen cursor-not-allowed opacity-50"}`),y.disabled=!e(s),ke(y,"title",e(s)?"":"No probe data available. Run a simulation first."),Se($,1,`bg-transparent border-none p-0 m-0 text-lg font-bold tracking-tight transition-colors duration-150 + ${e(i)?e(a)==="profile"?"text-edf-bleu-fonce cursor-pointer":"text-edf-gris-moyen cursor-pointer hover:text-muted":"text-edf-gris-moyen cursor-not-allowed opacity-50"}`),$.disabled=!e(i),ke($,"title",e(i)?"":"No profile data available. Run a simulation first.")}),ue("click",y,()=>x("time")),ue("click",$,()=>x("profile")),u(C,R)};jt(A,{wide:!0,titleSlot:h,tabs:h,actions:C=>{var R=ku(),p=G(R);{let g=V(()=>e(a)==="time"?"probePlot":"profilePlot"),N=V(il);nr(p,{get name(){return e(g)},get intervalMs(){return e(N)},onRefresh:d,get checked(){return e(o)},set checked(I){f(o,I,!0)}})}var y=E(p,2);{var $=g=>{Pe(g,{variant:"primary",onclick:d,children:(N,I)=>{var L=wu(),q=G(L);Te(q,{get icon(){return er}}),we(),u(N,L)},$$slots:{default:!0}})};Y(y,g=>{e(o)||g($)})}u(C,R)},children:(C,R)=>{var p=be(),y=G(p);{var $=N=>{gu(N,{get allCases(){return r.allCases},get onRefresh(){return e(l)},set onRefresh(I){f(l,I,!0)}})},g=N=>{xu(N,{get allCases(){return r.allCases},get onRefresh(){return e(l)},set onRefresh(I){f(l,I,!0)}})};Y(y,N=>{e(a)==="time"?N($):N(g,-1)})}u(C,p)},$$slots:{tabs:!0,actions:!0,default:!0}})}};Y(b,A=>{e(n)&&A(z)})}u(t,k),Ae()}Ge(["click"]);var Su=P(" Refresh",1),$u=P(" ",1),Pu=P(" Download as CSV",1),Eu=P('
        ',1),Ru=P(" "),Nu=P(' '),Mu=P(' '),Au=P('
        Case
        '),Lu=P('

        '),Tu=P(" ",1);function Fu(t,r){Me(r,!0);let a=D(ce([])),n=D(ce([])),s=D(!1),i=D(!1),o=D(ce(tr("perf")));Ne(()=>{rr("perf",e(o))});const l=5e3;let m=D(ce([{key:"elapsed_time",label:"Elapsed (s)",kind:"time"},{key:"io_time",label:"I/O (s)",kind:"time"},{key:"linear_solver_time",label:"Linear Solver (s)",kind:"time"},{key:"gradients_time",label:"Gradients (s)",kind:"time"},{key:"balances_time",label:"Balances (s)",kind:"time"},{key:"mpi_ranks",label:"MPI Ranks",kind:"int"},{key:"threads",label:"Threads",kind:"int"}])),x=V(()=>r.allCases.map(R=>({value:R,label:R}))),k=V(()=>e(n).length>0);async function b(){var R;if(e(a).length){f(i,!0);try{const p=await In(e(a));(R=p.columns)!=null&&R.length&&f(m,p.columns,!0),f(n,p.records,!0),e(n).length>0&&f(s,!0)}catch(p){console.error("Failed to load perf:",p)}f(i,!1)}}function z(R){if(f(a,R,!0),R.length===0){f(n,[],!0);return}b()}function A(R,p){if(p==null||p==="")return"-";if(R==="text")return p;const y=Number(p);return Number.isFinite(y)?R==="int"?String(Math.round(y)):y.toFixed(3):p}async function h(){if(!e(n).length)return;const R=["case_id",...e(m).map(g=>g.key)],p=e(n).map(g=>[g.case_id,...e(m).map(N=>g[N.key]??"")].join(",")),y=[R.join(","),...p].join(` +`),$=xr("timing_snapshot",e(a),"csv");await Ma(y,$)}let c=!1;Ne(()=>{r.allCases.length>0&&!c&&(c=!0,f(a,[...r.allCases],!0),In(r.allCases).then(R=>{var p;(p=R.columns)!=null&&p.length&&f(m,R.columns,!0),f(s,R.records.length>0),f(n,R.records,!0)}).catch(()=>{}))}),Ne(()=>{e(o)?mr("perf",b,l):vt("perf")});const C=_r(()=>{c&&b()});vr(()=>{vt("perf"),C()}),jt(t,{eyebrow:"Performance",title:"Timing Snapshot",wide:!0,actions:p=>{var y=$u(),$=G(y);nr($,{name:"perf",intervalMs:l,onRefresh:b,get checked(){return e(o)},set checked(I){f(o,I,!0)}});var g=E($,2);{var N=I=>{Pe(I,{variant:"primary",onclick:b,children:(L,q)=>{var H=Su(),j=G(H);Te(j,{get icon(){return er}}),we(),u(L,H)},$$slots:{default:!0}})};Y(g,I=>{e(o)||I(N)})}u(p,y)},children:(p,y)=>{var $=Tu(),g=G($);{var N=H=>{Qt(H,{children:(j,U)=>{var X=Eu(),K=G(X);Re(K,{text:"Cases",children:(S,O)=>{Wt(S,{class:"w-[160px]",get options(){return e(x)},get selected(){return e(a)},onchange:z,placeholder:"Select cases..."})}});var re=E(K,2),w=_(re);{let S=V(()=>!e(k));Pe(w,{variant:"secondary",size:"sm",onclick:h,get disabled(){return e(S)},children:(O,F)=>{var M=Pu(),T=G(M);Te(T,{get icon(){return pr}}),we(),u(O,M)},$$slots:{default:!0}})}v(re),u(j,X)}})};Y(g,H=>{e(s)&&H(N)})}var I=E(g,2);{var L=H=>{var j=Au(),U=_(j),X=_(U),K=_(X),re=E(_(K));He(re,17,()=>e(m),S=>S.key,(S,O)=>{var F=Ru(),M=_(F,!0);v(F),ee(()=>ae(M,e(O).label)),u(S,F)}),v(K),v(X);var w=E(X);He(w,21,()=>e(n),S=>S.case_id,(S,O)=>{var F=Mu(),M=_(F),T=_(M,!0);v(M);var B=E(M);He(B,17,()=>e(m),te=>te.key,(te,ie)=>{var W=Nu(),ne=_(W,!0);v(W),ee(oe=>ae(ne,oe),[()=>A(e(ie).kind,e(O)[e(ie).key])]),u(te,W)}),v(F),ee(()=>ae(T,e(O).case_id)),u(S,F)}),v(w),v(U),v(j),u(H,j)},q=H=>{var j=Lu(),U=_(j);{var X=re=>{var w=Ve("Select cases above to view timing information.");u(re,w)},K=re=>{var w=Ve("No performance data available. Please run a simulation first.");u(re,w)};Y(U,re=>{e(s)?re(X):re(K,-1)})}v(j),u(H,j)};Y(I,H=>{e(k)?H(L):H(q,-1)})}u(p,$)},$$slots:{actions:!0,default:!0}}),Ae()}const Iu={"&":"&","<":"<",">":">",'"':""","'":"'"};function ja(t){return t.replace(/[&<>"']/g,r=>Iu[r]??r)}var zu=P("
        "),Du=P('
        ');function Ou(t,r){Me(r,!0);let a=D(void 0);function n(m){const x=ja(m);if(!r.searchQuery.trim())return x;try{const k=new RegExp(`(${r.searchQuery})`,"gi");return x.replace(k,'$1')}catch{return x}}function s(m){return m==="error"?"tail-sev-error":m==="warn"?"tail-sev-warn":m==="info"?"tail-sev-info":""}function i(){return e(a)?e(a).scrollHeight-e(a).scrollTop-e(a).clientHeight<=24:!0}let o=D(!0);function l(){f(o,i(),!0)}Ne(()=>{r.autoScroll&&r.lines.length>0&&e(a)&&e(o)&&Et().then(()=>{e(a)&&(e(a).scrollTop=e(a).scrollHeight)})});var d=Du();He(d,21,()=>r.lines,m=>m.index,(m,x)=>{var k=zu();zr(k,()=>n(e(x).text),!0),v(k),ee(b=>Se(k,1,`tail-line ${b??""} ${e(x).isNew?"tail-new":""}`),[()=>s(e(x).severity)]),u(m,k)}),v(d),nt(d,m=>f(a,m),()=>e(a)),rt("scroll",d,l),u(t,d),Ae()}const Hu=/(fatal error|error detected|error reading|error writing|segmentation fault|sigterm|sigsegv|sigkill|abort|killed|terminated|core dumped|traceback|exception|errno)/i,ju=/(warning|divergence|non-convergence|clipping|negative|nan detected|overflow|underflow)/i;function Vu(t){return Hu.test(t)?"error":ju.test(t)?"warn":"none"}function Uu(t,r){return r==="all"||r===""?!0:r==="info"?t!=="none":t===r}var Bu=P(" Refresh",1),qu=P(" ",1),Ku=P(''),Gu=P(''),Wu=P("    ",1),Xu=Ht(''),Yu=Ht(''),Qu=Ht(''),Zu=P(" Resume",1),Ju=P(" Pause",1),ed=P('Case not running'),td=P(' 
        ',1),rd=P('

        No log data available. Please run a simulation first.

        '),nd=P('

        '),ad=P(" ",1);function sd(t,r){Me(r,!0);const a=80;let n=D(""),s=D(""),i=D(!1),o=D(ce([])),l=D(a),d=D(ce([])),m=D(ce(new Set)),x=D(!1),k=D(ce(tr("tail")));Ne(()=>{rr("tail",e(k))});let b=D(!0),z=D("all"),A=D(""),h=D(""),c=D(void 0),C=V(()=>kn(r.allCases)),R=V(()=>e(o).map(w=>{const S=w.split("/").pop()??w;return{value:w,label:S}})),p=[{value:"all",label:"All"},{value:"error",label:"Error"},{value:"warn",label:"Warn"}],y=V(()=>e(o).length>0),$=V(()=>xn().some(w=>{var S;return w.case_id===e(n)&&((S=w.status)==null?void 0:S.toUpperCase())==="RUNNING"}));const g={"run_solver.log":1,listing:2,"run_status.running":3,"csauto.stdout":4,"csauto.stderr":5,"performance.log":6};function N(w){const S=w.split("/").pop()??w;return g[S]??99}async function I(){if(e(n))try{const O=(await Eo(e(n))).filter(F=>{const M=F.split("/").pop()??F;return F.endsWith(".log")||F.endsWith("/summary")||M in g}).sort((F,M)=>N(F)-N(M));if(f(o,O,!0),O.length>0){const F=O[0];!e(s)||!O.includes(e(s))?(f(s,F,!0),f(i,!1)):!e(i)&&N(F)0&&O[O.length-1]===""?O.slice(0,-1):O;if(e(d).length>0){const M=q(e(d),F),T=new Set;for(let B=M;B{let w=e(d).map((S,O)=>({text:S,index:O,severity:Vu(S),isNew:e(m).has(O)}));if(e(z)!=="all"&&(w=w.filter(S=>Uu(S.severity,e(z)))),e(A).trim())try{const S=new RegExp(e(A),"i");w=w.filter(O=>S.test(O.text))}catch{}return w});function j(w){f(n,w,!0),f(s,""),f(i,!1),f(d,[],!0),I().then(()=>L(!0))}function U(w){f(s,w,!0),f(i,!0),f(d,[],!0),L(!0)}function X(){f(x,!e(x)),e(x)||L(!0)}function K(w){(w.ctrlKey||w.metaKey)&&w.key==="f"&&e(c)&&(w.preventDefault(),e(c).focus())}Ne(()=>{r.allCases.length>0&&!e(n)&&(f(n,r.allCases[0],!0),I().then(()=>L(!0)))}),Ne(()=>{e(k)&&!e(x)&&e(n)?mr("tail",()=>L(!1),On()):vt("tail")});const re=_r(()=>{e(n)&&I().then(()=>L(!0))});vr(()=>{vt("tail"),re()}),rt("keydown",it,K),jt(t,{eyebrow:"Live",title:"Log Tail",wide:!0,actions:S=>{var O=qu(),F=G(O);{let B=V(On);nr(F,{name:"tail",get intervalMs(){return e(B)},onRefresh:()=>L(!1),get checked(){return e(k)},set checked(te){f(k,te,!0)}})}var M=E(F,2);{var T=B=>{Pe(B,{variant:"primary",onclick:()=>L(!0),children:(te,ie)=>{var W=Bu(),ne=G(W);Te(ne,{get icon(){return er}}),we(),u(te,W)},$$slots:{default:!0}})};Y(M,B=>{e(k)||B(T)})}u(S,O)},children:(S,O)=>{var F=ad(),M=G(F);{var T=oe=>{Qt(oe,{children:(de,ve)=>{var se=Wu(),_e=G(se);Re(_e,{text:"Case",children:(ze,Qe)=>{lt(ze,{class:"w-[130px]",get options(){return e(C)},get value(){return e(n)},onchange:j})}});var he=E(_e,2);Re(he,{text:"File",children:(ze,Qe)=>{lt(ze,{class:"w-[150px]",get options(){return e(R)},get value(){return e(s)},onchange:U})}});var Ee=E(he,2);Re(Ee,{text:"Lines",children:(ze,Qe)=>{var qe=Ku();Ye(qe),ue("change",qe,()=>L(!0)),tt(qe,()=>e(l),Je=>f(l,Je)),u(ze,qe)}});var pe=E(Ee,2);Re(pe,{text:"Filter",children:(ze,Qe)=>{var qe=Gu();Ye(qe),nt(qe,Je=>f(c,Je),()=>e(c)),tt(qe,()=>e(A),Je=>f(A,Je)),u(ze,qe)}});var Ie=E(pe,2);Re(Ie,{text:"Severity",children:(ze,Qe)=>{lt(ze,{class:"w-[90px]",get options(){return p},get value(){return e(z)},onchange:qe=>f(z,qe,!0)})}}),u(de,se)}})};Y(M,oe=>{e(y)&&oe(T)})}var B=E(M,2);{var te=oe=>{var de=td(),ve=G(de);Ou(ve,{get lines(){return e(H)},get searchQuery(){return e(A)},get autoScroll(){return e(b)}});var se=E(ve,2),_e=_(se),he=_(_e);{var Ee=Q=>{var Z=Xu();u(Q,Z)},pe=Q=>{var Z=Yu();u(Q,Z)},Ie=Q=>{var Z=Qu();u(Q,Z)};Y(he,Q=>{e(x)?Q(Ee):e($)?Q(pe,1):Q(Ie,-1)})}var ze=E(he);v(_e);var Qe=E(_e,2);{var qe=Q=>{Pe(Q,{variant:"secondary",size:"sm",onclick:X,children:(Z,J)=>{var le=be(),xe=G(le);{var ye=ge=>{var Ce=Zu(),De=G(Ce);Te(De,{get icon(){return Sa}}),we(),u(ge,Ce)},fe=ge=>{var Ce=Ju(),De=G(Ce);Te(De,{get icon(){return ki}}),we(),u(ge,Ce)};Y(xe,ge=>{e(x)?ge(ye):ge(fe,-1)})}u(Z,le)},$$slots:{default:!0}})},Je=Q=>{var Z=ed();u(Q,Z)};Y(Qe,Q=>{e($)?Q(qe):Q(Je,-1)})}var Le=E(Qe,2),$e=_(Le);Ot($e,{get checked(){return e(b)},onchange:Q=>f(b,Q,!0),size:14,label:"Auto-scroll",labelFirst:!0}),v(Le),v(se),ee(()=>ae(ze,` ${e(H).length??""} / ${e(d).length??""}`)),u(oe,de)},ie=oe=>{var de=rd();u(oe,de)};Y(B,oe=>{e(d).length>0?oe(te):oe(ie,-1)})}var W=E(B,2);{var ne=oe=>{var de=nd(),ve=_(de,!0);v(de),ee(()=>ae(ve,e(h))),u(oe,de)};Y(W,oe=>{e(h)&&oe(ne)})}u(S,F)},$$slots:{actions:!0,default:!0}}),Ae()}Ge(["change"]);function od(t,r=3){var z,A;const a=t.split(` +`),n=[];let s=0,i=0,o=!1;for(const h of a){if(h.startsWith("===")||h.startsWith("---")||h.startsWith("+++"))continue;const c=h.match(/^@@ -(\d+),?\d* \+(\d+),?\d* @@/);if(c){s=parseInt(c[1],10)-1,i=parseInt(c[2],10)-1,o=!0;continue}if(o)if(h.startsWith("-"))s++,n.push({type:"del",leftNum:s,rightNum:null,leftContent:h.slice(1),rightContent:""});else if(h.startsWith("+"))i++,n.push({type:"add",leftNum:null,rightNum:i,leftContent:"",rightContent:h.slice(1)});else{s++,i++;const C=h.startsWith(" ")?h.slice(1):h;n.push({type:"equal",leftNum:s,rightNum:i,leftContent:C,rightContent:C})}}const l=[];let d=0;for(;d=0&&h-k>1&&x.push({kind:"separator",skipped:h-k-1});const c=l[h].type!=="equal",C=c&&!b;x.push({kind:"line",row:l[h],hunkStart:C}),b=c,k=h}if(k=0&&x.push({kind:"separator",skipped:l.length-1-k}),x.length>0&&x[0].kind==="line"){const h=((z=x[0].row)==null?void 0:z.leftNum)??0,c=((A=x[0].row)==null?void 0:A.rightNum)??0,C=Math.max(h,c)-1;C>0&&x.unshift({kind:"separator",skipped:C})}return x}var id=P('
        '),ld=P('
        '),cd=P(' ',1),ud=P('No matches'),dd=P('
        '),fd=P('

        Files are identical.

        '),vd=P('
        Loading...
        '),gd=P('
        ');function hd(t,r){Me(r,!0);let a=me(r,"loading",3,!1),n=D(void 0),s=V(()=>od(r.diffText)),i=V(()=>{const Q=[];return e(s).forEach((Z,J)=>{Z.hunkStart&&Q.push(J)}),Q}),o=V(()=>e(i).length>0),l=V(()=>{var Z;const Q=new Array(e(s).length).fill(-1);for(let J=0;J{r.diffText!==m&&(m=r.diffText,f(d,-1))});let x=V(()=>{if(!r.search.trim())return[];try{const Q=new RegExp(r.search,"i"),Z=[];return e(s).forEach((J,le)=>{J.kind==="line"&&J.row&&(Q.test(J.row.leftContent)||Q.test(J.row.rightContent))&&Z.push(le)}),Z}catch{return[]}}),k=D(-1),b="";Ne(()=>{r.search!==b&&(b=r.search,f(k,e(x).length>0?0:-1,!0))});async function z(Q){var le;f(k,Q,!0),await Et();const Z=e(x)[Q],J=(le=e(p))==null?void 0:le.querySelector(`[data-item-idx="${Z}"]`);if(J&&e(p)&&e(y)){const xe=e(p).getBoundingClientRect(),fe=J.getBoundingClientRect().top-xe.top+e(p).scrollTop-e(p).clientHeight/3;g=!0,e(p).scrollTop=fe,e(y).scrollTop=fe,requestAnimationFrame(()=>{g=!1})}}function A(){e(k)>0&&z(e(k)-1)}function h(){e(k)<0&&e(x).length>0?z(0):e(k)$1`)}catch{return J}}function C(Q,Z){return Q==="equal"?"":Q==="del"&&Z==="left"?"bg-[rgba(214,67,10,0.08)] text-edf-orange-fonce":Q==="del"&&Z==="right"?"bg-[rgba(48,122,16,0.08)] text-edf-vert-fonce":Q==="add"&&Z==="left"?"bg-edf-gris-clair":Q==="add"&&Z==="right"?"bg-[rgba(48,122,16,0.08)] text-edf-vert-fonce":""}function R(Q,Z){return Q==="del"&&Z==="left"?"rgb(251,237,233)":Q==="del"&&Z==="right"?"rgb(238,247,236)":Q==="add"&&Z==="left"?"var(--color-edf-gris-clair)":Q==="add"&&Z==="right"?"rgb(238,247,236)":"white"}let p=D(void 0),y=D(void 0),$=!1,g=!1;function N(Q){$||g||!e(p)||!e(y)||($=!0,Q==="left"?(e(y).scrollTop=e(p).scrollTop,e(y).scrollLeft=e(p).scrollLeft):(e(p).scrollTop=e(y).scrollTop,e(p).scrollLeft=e(y).scrollLeft),requestAnimationFrame(()=>{$=!1}))}async function I(Q){var J;f(d,Q,!0),await Et();const Z=(J=e(p))==null?void 0:J.querySelector(`[data-hunk-start="${Q}"]`);if(Z&&e(p)&&e(y)){g=!0;const le=e(p).getBoundingClientRect(),ye=Z.getBoundingClientRect().top-le.top+e(p).scrollTop-e(p).clientHeight/3;e(p).scrollTop=ye,e(y).scrollTop=ye,requestAnimationFrame(()=>{g=!1})}}function L(){e(d)>0&&I(e(d)-1)}function q(){e(d)<0?I(0):e(d)=0&&f(d,Z,!0)}function j(Q){Q.target instanceof HTMLInputElement||(Q.key==="n"&&!Q.shiftKey?(Q.preventDefault(),q()):Q.key==="N"||Q.key==="n"&&Q.shiftKey?(Q.preventDefault(),L()):Q.key==="j"?(Q.preventDefault(),h()):(Q.key==="J"||Q.key==="j"&&Q.shiftKey)&&(Q.preventDefault(),A()))}var U=gd();{const Q=(Z,J=Cs)=>{var le=be(),xe=G(le);He(xe,17,()=>e(s),dn,(ye,fe,ge)=>{const Ce=V(()=>e(i).indexOf(ge)),De=V(()=>e(l)[ge]>=0&&e(l)[ge]===e(d)),We=V(()=>{var at,Ke;return J()==="left"?(at=e(fe).row)==null?void 0:at.leftNum:(Ke=e(fe).row)==null?void 0:Ke.rightNum}),gt=V(()=>{var at,Ke;return J()==="left"?((at=e(fe).row)==null?void 0:at.leftContent)??"":((Ke=e(fe).row)==null?void 0:Ke.rightContent)??""});var Vt=be(),jr=G(Vt);{var et=at=>{var Ke=id(),Nt=E(_(Ke),2),Vr=_(Nt);v(Nt),v(Ke),ee(()=>ae(Vr,`... ${e(fe).skipped??""} lines hidden ...`)),u(at,Ke)},ut=at=>{var Ke=ld(),Nt=_(Ke),Vr=_(Nt,!0);v(Nt);var $n=E(Nt,2);zr($n,()=>c(e(gt),e(k)>=0&&e(x)[e(k)]===ge),!0),v($n),v(Ke),ee((wt,Mt,yr,Va,Ua)=>{Se(Ke,1,`flex w-full ${wt??""} ${Mt??""}`),Ze(Ke,e(De)?"background-color: rgba(16,87,200,0.12); color: rgb(16,87,200);":""),ke(Ke,"role",yr),ke(Ke,"tabindex",Va),ke(Ke,"data-hunk-start",J()==="left"&&e(Ce)>=0?e(Ce):void 0),ke(Ke,"data-item-idx",J()==="left"?ge:void 0),Ze(Nt,`background: ${Ua??""}; color: ${e(De)?"white":"var(--color-muted)"};`),ae(Vr,e(We)??"")},[()=>C(e(fe).row.type,J()),()=>e(fe).row.type!=="equal"||e(x).includes(ge)?"cursor-pointer":"",()=>e(fe).row.type!=="equal"||e(x).includes(ge)?"button":void 0,()=>e(fe).row.type!=="equal"||e(x).includes(ge)?0:void 0,()=>e(De)?"rgb(16,87,200)":R(e(fe).row.type,J())]),ue("click",Ke,()=>{var Mt;((Mt=e(fe).row)==null?void 0:Mt.type)!=="equal"&&H(ge);const wt=e(x).indexOf(ge);wt>=0&&f(k,wt,!0)}),ue("keydown",Ke,wt=>{var Mt;if(wt.key==="Enter"||wt.key===" "){wt.preventDefault(),((Mt=e(fe).row)==null?void 0:Mt.type)!=="equal"&&H(ge);const yr=e(x).indexOf(ge);yr>=0&&f(k,yr,!0)}}),u(at,Ke)};Y(jr,at=>{e(fe).kind==="separator"?at(et):e(fe).row&&at(ut,1)})}u(ye,Vt)}),u(Z,le)};var X=_(U),K=_(X),re=_(K),w=_(re,!0);v(re);var S=E(re,2),O=_(S,!0);v(S),v(K);var F=E(K,2),M=_(F),T=_(M,!0);v(M);var B=E(M,2),te=_(B,!0);v(B),v(F),v(X);var ie=E(X,2),W=_(ie),ne=_(W);Ye(ne);var oe=E(ne,2);{var de=Z=>{var J=cd(),le=G(J);{let ge=V(()=>e(k)<=0);Pe(le,{variant:"secondary",size:"sm",onclick:A,get disabled(){return e(ge)},children:(Ce,De)=>{we();var We=Ve("Prev");u(Ce,We)},$$slots:{default:!0}})}var xe=E(le,2),ye=_(xe);v(xe);var fe=E(xe,2);{let ge=V(()=>e(k)>=e(x).length-1);Pe(fe,{variant:"secondary",size:"sm",onclick:h,get disabled(){return e(ge)},children:(Ce,De)=>{we();var We=Ve("Next");u(Ce,We)},$$slots:{default:!0}})}ee(()=>ae(ye,`${e(k)>=0?e(k)+1:"–"} / ${e(x).length??""} occurrence${e(x).length!==1?"s":""}`)),u(Z,J)},ve=V(()=>r.search.trim()&&e(x).length>0),se=Z=>{var J=ud();u(Z,J)},_e=V(()=>r.search.trim());Y(oe,Z=>{e(ve)?Z(de):e(_e)&&Z(se,1)})}v(W);var he=E(W,2),Ee=_(he);{let Z=V(()=>e(i).length===0||e(d)<=0);Pe(Ee,{variant:"secondary",size:"sm",onclick:L,get disabled(){return e(Z)},children:(J,le)=>{we();var xe=Ve("Prev");u(J,xe)},$$slots:{default:!0}})}var pe=E(Ee,2),Ie=_(pe);v(pe);var ze=E(pe,2);{let Z=V(()=>e(i).length===0||e(d)>=e(i).length-1);Pe(ze,{variant:"secondary",size:"sm",onclick:q,get disabled(){return e(Z)},children:(J,le)=>{we();var xe=Ve("Next");u(J,xe)},$$slots:{default:!0}})}v(he),v(ie);var Qe=E(ie,2);{var qe=Z=>{var J=dd(),le=_(J),xe=_(le),ye=_(xe);Q(ye,()=>"left"),v(xe),v(le),nt(le,De=>f(p,De),()=>e(p));var fe=E(le,2),ge=_(fe),Ce=_(ge);Q(Ce,()=>"right"),v(ge),v(fe),nt(fe,De=>f(y,De),()=>e(y)),v(J),rt("scroll",le,()=>N("left")),rt("scroll",fe,()=>N("right")),u(Z,J)},Je=Z=>{var J=fd();u(Z,J)};Y(Qe,Z=>{e(o)?Z(qe):Z(Je,-1)})}var Le=E(Qe,2);{var $e=Z=>{var J=vd();u(Z,J)};Y(Le,Z=>{a()&&Z($e)})}v(U),nt(U,Z=>f(n,Z),()=>e(n)),ee(()=>{ae(w,r.leftLabel),ae(O,r.kind),ae(T,r.rightLabel),ae(te,r.kind),fn(ne,r.search),ae(Ie,`${e(d)>=0?e(d)+1:"–"} / ${e(i).length??""} diff${e(i).length!==1?"s":""}`)}),ue("input",ne,Z=>r.onSearchChange(Z.target.value)),ue("keydown",ne,Z=>{Z.key==="Enter"&&(Z.preventDefault(),h())})}ue("keydown",U,j),u(t,U),Ae()}Ge(["keydown","click","input"]);var pd=P(" ",1),md=P('

        Please select two different cases to compare.

        '),_d=P('

        Select two cases to compare.

        '),bd=P(' '),xd=P('
        Parameter
        '),yd=P('

        All parameters are identical.

        '),wd=P('
        ',1),kd=P('

        '),Cd=P('

        Loading...

        '),Sd=P(" ",1),$d=P(" ",1);function Pd(t,r){Me(r,!0);let a=D(""),n=D(""),s=D(""),i=D(""),o=D(""),l=D(!1),d=D(!1),m=D(""),x=V(()=>r.allCases.map(g=>({value:g,label:g})));const k=[{value:"setup.xml",label:"setup.xml"},{value:"doe_row.csv",label:"doe_row.csv"},{value:"run_solver.log",label:"run_solver.log"},{value:"performance.log",label:"performance.log"}];let b=V(()=>{var g;return((g=Ft())==null?void 0:g.compare_kinds)??k});Ne(()=>{var g;e(b).some(N=>N.value===e(s))||f(s,((g=e(b)[0])==null?void 0:g.value)??"",!0)});let z=D(!1),A=V(()=>e(a)&&e(n)&&e(a)!==e(n));function h(){const g=e(a);f(a,e(n),!0),f(n,g,!0)}async function c(){if(e(A)){f(l,!0),f(m,"");try{const g=await Lo({cases:[e(a),e(n)],base:e(a),kind:e(s)});f(o,g,!0),f(d,!0)}catch(g){console.error("Failed to load diff:",g),f(o,""),f(d,!1);const N=g instanceof Error?g.message:String(g);f(m,N.includes("404")?`File "${e(s)}" not found for one of the selected cases. It may not have been run yet.`:"Failed to load comparison.",!0)}f(l,!1)}}Ne(()=>{e(a)&&e(n)&&e(a)!==e(n)&&e(s)?c():e(a)&&e(n)&&e(a)===e(n)&&(f(o,""),f(d,!0))});let C=!1;Ne(()=>{r.allCases.length>=2&&!C?(C=!0,f(a,r.allCases[0],!0),f(n,r.allCases[1],!0)):r.allCases.length===1&&!C&&(C=!0,f(a,r.allCases[0],!0))});let R=V(yn),p=V(()=>{if(!e(a)||!e(n)||e(a)===e(n)||e(R).length===0)return[];const g=xn(),N=g.find(L=>L.case_id===e(a)),I=g.find(L=>L.case_id===e(n));return!N&&!I?[]:e(R).map(L=>{var j,U;const q=((j=N==null?void 0:N.doe)==null?void 0:j[L])!=null&&String(N.doe[L])!==""?String(N.doe[L]):"—",H=((U=I==null?void 0:I.doe)==null?void 0:U[L])!=null&&String(I.doe[L])!==""?String(I.doe[L]):"—";return{param:L,left:q,right:H,differs:q!==H}})}),y=V(()=>e(z)?e(p):e(p).filter(g=>g.differs)),$=V(()=>e(p).filter(g=>g.differs).length);jt(t,{eyebrow:"Compare",title:"Side-by-Side Comparison",wide:!0,children:(g,N)=>{var I=$d(),L=G(I);Qt(L,{children:(X,K)=>{var re=pd(),w=G(re);Re(w,{text:"First case",children:(F,M)=>{lt(F,{class:"w-32.5",get options(){return e(x)},get value(){return e(a)},onchange:T=>f(a,T,!0),placeholder:"Select..."})}});var S=E(w,2);{let F=V(()=>!e(a)&&!e(n));Pe(S,{variant:"secondary",size:"sm",onclick:h,get disabled(){return e(F)},children:(M,T)=>{Te(M,{get icon(){return gi}})},$$slots:{default:!0}})}var O=E(S,2);Re(O,{text:"Second case",children:(F,M)=>{lt(F,{class:"w-32.5",get options(){return e(x)},get value(){return e(n)},onchange:T=>f(n,T,!0),placeholder:"Select..."})}}),u(X,re)}});var q=E(L,2);{var H=X=>{var K=md();u(X,K)},j=X=>{var K=_d();u(X,K)},U=X=>{var K=Sd(),re=G(K);{var w=B=>{var te=wd(),ie=G(te),W=_(ie),ne=_(W);v(W);var oe=E(W,2),de=_(oe,!0);v(oe),v(ie);var ve=E(ie,2);{var se=he=>{var Ee=xd(),pe=_(Ee),Ie=_(pe),ze=_(Ie),Qe=_(ze),qe=_(Qe,!0);v(Qe);var Je=E(Qe,2),Le=_(Je,!0);v(Je),v(ze),v(Ie);var $e=E(Ie);He($e,21,()=>e(y),Q=>Q.param,(Q,Z)=>{var J=bd(),le=_(J),xe=_(le,!0);v(le);var ye=E(le),fe=_(ye,!0);v(ye);var ge=E(ye),Ce=_(ge,!0);v(ge),v(J),ee(()=>{Se(le,1,dt(e(Z).differs?"text-edf-orange-fonce bg-[rgba(214,67,10,0.04)]":"")),ae(xe,e(Z).left),ae(fe,e(Z).param),Se(ge,1,dt(e(Z).differs?"text-edf-vert-fonce bg-[rgba(48,122,16,0.04)]":"")),ae(Ce,e(Z).right)}),u(Q,J)}),v($e),v(pe),v(Ee),ee(()=>{ae(qe,e(a)),ae(Le,e(n))}),u(he,Ee)},_e=he=>{var Ee=yd();u(he,Ee)};Y(ve,he=>{e(y).length>0?he(se):he(_e,-1)})}ee(()=>{ae(ne,`${e($)??""} difference${e($)!==1?"s":""} out of ${e(p).length??""} + parameters`),ae(de,e(z)?"Show differences only":"Show all parameters")}),ue("click",oe,()=>f(z,!e(z))),u(B,te)};Y(re,B=>{e(p).length>0&&B(w)})}var S=E(re,2);Qt(S,{children:(B,te)=>{Re(B,{text:"File",children:(ie,W)=>{lt(ie,{class:"w-37.5",get options(){return e(b)},get value(){return e(s)},onchange:ne=>f(s,ne,!0)})}})}});var O=E(S,2);{var F=B=>{var te=kd(),ie=_(te,!0);v(te),ee(()=>ae(ie,e(m))),u(B,te)},M=B=>{hd(B,{get diffText(){return e(o)},get leftLabel(){return e(a)},get rightLabel(){return e(n)},get kind(){return e(s)},get search(){return e(i)},get loading(){return e(l)},onSearchChange:te=>f(i,te,!0)})},T=B=>{var te=Cd();u(B,te)};Y(O,B=>{e(m)?B(F):e(d)?B(M,1):e(l)&&B(T,2)})}u(X,K)};Y(q,X=>{e(a)&&e(n)&&e(a)===e(n)?X(H):!e(a)||!e(n)?X(j,1):X(U,-1)})}u(g,I)},$$slots:{default:!0}}),Ae()}Ge(["click"]);var Ed=P(" Refresh",1),Rd=P(" ",1),Nd=P(''),Md=P(''),Ad=P(" Download as CSV",1),Ld=P('
        ',1),Td=P(" ",1),Fd=P(""),Id=P('
        Sort by:
        '),zd=P('

        No errors found. Select cases and adjust filters above.

        '),Dd=P(' '),Od=P('NEW'),Hd=P('
        '),jd=P('
        '),Vd=P(' '),Ud=P('
        '),Bd=P('

        '),qd=P("
        ",1);function Kd(t,r){Me(r,!0);const a=6,n=["csauto.stderr","run_solver.log","listing"];let s=D(ce([])),i=D(ce([...n])),o=!1;Ne(()=>{var ne;const W=(ne=Ft())==null?void 0:ne.error_files;W!=null&&W.length&&!o&&f(i,[...W],!0)});let l=D("all"),d=D(""),m=D(a),x=D(!1),k=D(""),b=D(ce(tr("errors")));Ne(()=>{rr("errors",e(b))});let z=D(ce([])),A=D(ce(new Set));function h(W){return W.toLowerCase().replace(/[0-9]+/g,"#").replace(/0x[0-9a-f]+/gi,"#").replace(/\s+/g," ").trim()}function c(W){const ne=W.line_html.replace(/<[^>]*>/g,"").slice(0,120);return`${W.case_id}|${W.file}|${W.severity}|${h(ne)}`}function C(W){const ne=new Map;for(const oe of W){const de=c(oe),ve=ne.get(de);ve?ve.count++:ne.set(de,{...oe,count:1,fingerprint:de,isNew:!e(A).has(de)})}return Array.from(ne.values())}async function R(){if(!(!e(s).length||!e(i).length)){f(x,!0);try{const W=await $o({cases:e(s),files:e(i),context:e(m),sev:e(l)==="all"?"":e(l),q:e(d)});f(k,"");const ne=C(W.items);f(z,W.items,!0);const oe=new Set(ne.map(de=>de.fingerprint));f(A,oe,!0),f(p,ne,!0)}catch(W){f(k,"Failed to load errors"),console.error("Failed to load errors:",W)}finally{f(x,!1)}}}let p=D(ce([])),y=null;function $(){y&&clearTimeout(y),y=setTimeout(R,250)}function g(W){return W==="error"?"error-sev-error":W==="warn"?"error-sev-warn":"error-sev-info"}let N=V(()=>kn(r.allCases));const I=["csauto.stderr","run_solver.log","listing","csauto.stdout"];let L=V(()=>{var W;return(((W=Ft())==null?void 0:W.error_files)??I).map(ne=>({value:ne,label:ne}))}),q=[{value:"all",label:"All"},{value:"error",label:"Error"},{value:"warn",label:"Warn"},{value:"info",label:"Info"}];function H(W){if(f(s,W,!0),W.length===0){f(p,[],!0),f(z,[],!0);return}R()}function j(W){if(o=!0,f(i,W,!0),W.length===0){f(p,[],!0),f(z,[],!0);return}R()}function U(W){f(l,W,!0),R()}function X(){R()}let K=!1;Ne(()=>{r.allCases.length>0&&!K&&(K=!0,f(s,[...r.allCases],!0),R())}),Ne(()=>{e(b)?mr("errors",R,jn()):vt("errors")});const re=_r(()=>{K&&R()});vr(()=>{vt("errors"),re()});let w=D(ce(new Set));function S(W){const ne=new Set(e(w));ne.has(W)?ne.delete(W):ne.add(W),f(w,ne,!0)}let O=D("severity"),F=D("desc");const M={info:0,warn:1,error:2};let T=V(()=>{const W=[...e(p)];return W.sort((ne,oe)=>{let de=0;return e(O)==="severity"?de=(M[ne.severity]??9)-(M[oe.severity]??9):e(O)==="count"?de=ne.count-oe.count:de=ne[e(O)].localeCompare(oe[e(O)]),e(F)==="asc"?de:-de}),W});const B={severity:"desc",count:"desc",case_id:"asc",file:"asc"};function te(W){e(O)===W?f(F,e(F)==="asc"?"desc":"asc",!0):(f(O,W,!0),f(F,B[W],!0))}function ie(){if(!e(p).length)return;const W=["case_id","file","severity","count","is_new","line_text"],ne=e(T).map(ve=>[ve.case_id,ve.file,ve.severity,ve.count,ve.isNew?"yes":"no",`"${ve.line_html.replace(/<[^>]*>/g,"").replace(/"/g,'""')}"`].join(",")),oe=[W.join(","),...ne].join(` +`),de=xr("errors",e(s),"csv");Ma(oe,de)}jt(t,{eyebrow:"Diagnostics",title:"Recent Errors",wide:!0,actions:ne=>{var oe=Rd(),de=G(oe);{let _e=V(jn);nr(de,{name:"errors",get intervalMs(){return e(_e)},onRefresh:R,get checked(){return e(b)},set checked(he){f(b,he,!0)}})}var ve=E(de,2);{var se=_e=>{Pe(_e,{variant:"primary",onclick:R,children:(he,Ee)=>{var pe=Ed(),Ie=G(pe);Te(Ie,{get icon(){return er}}),we(),u(he,pe)},$$slots:{default:!0}})};Y(ve,_e=>{e(b)||_e(se)})}u(ne,oe)},children:(ne,oe)=>{var de=qd(),ve=G(de);Qt(ve,{children:(Le,$e)=>{var Q=Ld(),Z=G(Q);Re(Z,{text:"Cases",children:(Ce,De)=>{Wt(Ce,{class:"w-[160px]",get options(){return e(N)},get selected(){return e(s)},onchange:H,placeholder:"Select cases..."})}});var J=E(Z,2);Re(J,{text:"Files",children:(Ce,De)=>{Wt(Ce,{class:"w-[160px]",get options(){return e(L)},get selected(){return e(i)},onchange:j,placeholder:"Select files..."})}});var le=E(J,2);Re(le,{text:"Severity",children:(Ce,De)=>{lt(Ce,{class:"w-[90px]",get options(){return q},get value(){return e(l)},onchange:U})}});var xe=E(le,2);Re(xe,{text:"Search",children:(Ce,De)=>{var We=Nd();Ye(We),ue("input",We,$),tt(We,()=>e(d),gt=>f(d,gt)),u(Ce,We)}});var ye=E(xe,2);Re(ye,{text:"Context",children:(Ce,De)=>{var We=Md();Ye(We),ue("change",We,X),tt(We,()=>e(m),gt=>f(m,gt)),u(Ce,We)}});var fe=E(ye,2),ge=_(fe);{let Ce=V(()=>e(p).length===0);Pe(ge,{variant:"secondary",size:"sm",onclick:ie,get disabled(){return e(Ce)},children:(De,We)=>{var gt=Ad(),Vt=G(gt);Te(Vt,{get icon(){return pr}}),we(),u(De,gt)},$$slots:{default:!0}})}v(fe),u(Le,Q)}});var se=E(ve,2);{var _e=Le=>{var $e=Id(),Q=E(_($e),2);He(Q,16,()=>[{key:"severity",label:"Severity"},{key:"count",label:"Count"},{key:"case_id",label:"Case"},{key:"file",label:"File"}],Z=>Z.key,(Z,J)=>{var le=Fd(),xe=_(le,!0),ye=E(xe);{var fe=ge=>{var Ce=Td(),De=E(G(Ce));{let We=V(()=>e(F)==="asc"?hi:vi);Te(De,{get icon(){return e(We)},size:12})}u(ge,Ce)};Y(ye,ge=>{e(O)===J.key&&ge(fe)})}v(le),ee(()=>{Se(le,1,`cursor-pointer bg-transparent border-none text-xs hover:underline ${e(O)===J.key?"text-edf-bleu-fonce font-bold":"text-muted"}`),ae(xe,J.label)}),ue("click",le,()=>te(J.key)),u(Z,le)}),v($e),u(Le,$e)};Y(se,Le=>{e(T).length>0&&Le(_e)})}var he=E(se,2),Ee=_(he);{var pe=Le=>{var $e=zd();u(Le,$e)};Y(Ee,Le=>{e(T).length===0&&Le(pe)})}var Ie=E(Ee,2);He(Ie,17,()=>e(T),Le=>Le.fingerprint,(Le,$e)=>{var Q=jd(),Z=_(Q),J=_(Z),le=_(J);{let et=V(()=>e(w).has(e($e).fingerprint)?pn:pi);Te(le,{get icon(){return e(et)},size:14})}v(J);var xe=E(J,2),ye=_(xe);v(xe);var fe=E(xe,2),ge=_(fe);{var Ce=et=>{var ut=Dd(),at=_(ut);v(ut),ee(()=>ae(at,`${e($e).count??""}x`)),u(et,ut)};Y(ge,et=>{e($e).count>1&&et(Ce)})}var De=E(ge,2);{var We=et=>{var ut=Od();u(et,ut)};Y(De,et=>{e($e).isNew&&et(We)})}v(fe),v(Z);var gt=E(Z,2);{var Vt=et=>{var ut=Hd();zr(ut,()=>e($e).line_html,!0),v(ut),u(et,ut)},jr=V(()=>e(w).has(e($e).fingerprint));Y(gt,et=>{e(jr)&&et(Vt)})}v(Q),ee(et=>{Se(Q,1,`error-item ${et??""}`),ae(ye,`${e($e).case_id??""} / ${e($e).file??""} : ${e($e).severity??""}`)},[()=>g(e($e).severity)]),ue("click",Z,()=>S(e($e).fingerprint)),u(Le,Q)}),v(he);var ze=E(he,2);{var Qe=Le=>{var $e=Ud(),Q=_($e),Z=_(Q);v(Q);var J=E(Q,2);{var le=ye=>{var fe=Vd(),ge=_(fe);v(fe),ee(Ce=>ae(ge,`${Ce??""} new`),[()=>e(p).filter(Ce=>Ce.isNew).length]),u(ye,fe)},xe=V(()=>e(p).some(ye=>ye.isNew));Y(J,ye=>{e(xe)&&ye(le)})}v($e),ee(()=>ae(Z,`${e(p).length??""} unique / ${e(z).length??""} total`)),u(Le,$e)};Y(ze,Le=>{e(T).length>0&&Le(Qe)})}var qe=E(ze,2);{var Je=Le=>{var $e=Bd(),Q=_($e,!0);v($e),ee(()=>ae(Q,e(k))),u(Le,$e)};Y(qe,Le=>{e(k)&&Le(Je)})}ee(()=>Se(he,1,`error-list ${e(x)?"opacity-50 pointer-events-none":""}`)),u(ne,de)},$$slots:{actions:!0,default:!0}}),Ae()}Ge(["input","change","click"]);var Gd=P('
        Developed by Simvia
        ',1);function rf(t,r){Me(r,!0);let a=D(ce({totalCases:0,shownCases:0,totalRunning:0,shownRunning:0,totalConverged:0,shownConverged:0})),n=D(ce([])),s=D(null);const i=M=>e(s)===null||e(s).includes(M);async function o(M){var te;const T=`/favicon-${M}.svg`;try{const ie=await fetch(T);if(!ie.ok||!((te=ie.headers.get("content-type"))!=null&&te.includes("svg")))return}catch{return}const B=document.querySelector('link[rel="icon"]');B&&(B.href=T)}async function l(){try{const M=await wo();jl(M.rows),Vl(M.doe_columns),f(n,M.rows.map(T=>T.case_id),!0),f(a,tc(Ra(),xn()),!0)}catch(M){console.error("Failed to load status:",M)}}Jn(()=>{l(),ko().then(M=>{_o(M),f(s,M.panels,!0),o(M.solver)}).catch(()=>{})});var d=Gd(),m=G(d);qi(m,{});var x=E(m,2);Yi(x,{});var k=E(x,2);xl(k,{get totalCases(){return e(a).totalCases},get shownCases(){return e(a).shownCases},get totalRunning(){return e(a).totalRunning},get shownRunning(){return e(a).shownRunning},get totalConverged(){return e(a).totalConverged},get shownConverged(){return e(a).shownConverged}});var b=E(k,2),z=_(b);{var A=M=>{Ac(M,{onRefresh:l})},h=V(()=>i("status"));Y(z,M=>{e(h)&&M(A)})}var c=E(z,2);{var C=M=>{eu(M,{get allCases(){return e(n)}})},R=V(()=>i("residuals"));Y(c,M=>{e(R)&&M(C)})}var p=E(c,2);{var y=M=>{Cu(M,{get allCases(){return e(n)}})},$=V(()=>i("probes"));Y(p,M=>{e($)&&M(y)})}var g=E(p,2);{var N=M=>{Fu(M,{get allCases(){return e(n)}})},I=V(()=>i("performance"));Y(g,M=>{e(I)&&M(N)})}var L=E(g,2);{var q=M=>{Pd(M,{get allCases(){return e(n)}})},H=V(()=>i("compare"));Y(L,M=>{e(H)&&M(q)})}var j=E(L,2);{var U=M=>{sd(M,{get allCases(){return e(n)}})},X=V(()=>i("tail"));Y(j,M=>{e(X)&&M(U)})}var K=E(j,2);{var re=M=>{Kd(M,{get allCases(){return e(n)}})},w=V(()=>i("errors"));Y(K,M=>{e(w)&&M(re)})}v(b);var S=E(b,2),O=E(_(S),2),F=_(O);v(O),v(S),ee(()=>ke(F,"src",yl)),u(t,d),Ae()}export{rf as component}; diff --git a/frontend/dist/_app/version.json b/frontend/dist/_app/version.json index 5535285..3e411a9 100644 --- a/frontend/dist/_app/version.json +++ b/frontend/dist/_app/version.json @@ -1 +1 @@ -{"version":"1789130391568"} \ No newline at end of file +{"version":"1789462962656"} \ No newline at end of file diff --git a/frontend/dist/index.html b/frontend/dist/index.html index c32cd83..30a7bdb 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -5,11 +5,11 @@ csauto by Simvia - - + + - + @@ -20,15 +20,15 @@ -{#if state.files.length > 0} + +{#if allCases.length > 0} files.filter((f) => { const stem = f.replace(/\.csv$/i, "").toLowerCase(); @@ -132,13 +127,18 @@ let emptyMessage = $derived( state.selectedCases.length === 0 ? "Please select at least one case." - : state.selectedColumns.length === 0 - ? "Please select at least one probe." - : "No data to display.", + : state.files.length === 0 + ? "No probe data for the selected cases yet." + : state.selectedColumns.length === 0 + ? "Please select at least one probe." + : "No data to display.", ); -{#if state.files.length > 0} + +{#if allCases.length > 0} {#if !autoRefresh} - {/if} diff --git a/frontend/src/lib/stores/appConfig.svelte.ts b/frontend/src/lib/stores/appConfig.svelte.ts index 2a6e123..342ebfc 100644 --- a/frontend/src/lib/stores/appConfig.svelte.ts +++ b/frontend/src/lib/stores/appConfig.svelte.ts @@ -33,3 +33,14 @@ export function hasCapability(name: string): boolean { export function hasControlAction(name: string): boolean { return config === null || config.control_actions.includes(name); } + +/** + * Execution backends the server offers, beside running on this machine. + * + * Empty while the config is null, unlike hasCapability which is permissive: + * offering a cloud run that the server may not support would let a user submit + * a campaign that fails, and a paid one at that. Local stays the safe default. + */ +export function getBackends(): string[] { + return config?.backends ?? []; +} diff --git a/frontend/src/lib/stores/settings.svelte.ts b/frontend/src/lib/stores/settings.svelte.ts index 91b70a0..5d3f93b 100644 --- a/frontend/src/lib/stores/settings.svelte.ts +++ b/frontend/src/lib/stores/settings.svelte.ts @@ -25,12 +25,21 @@ function readString(key: string, fallback: string): string { return localStorage.getItem(key) ?? fallback; } +function writeString(key: string, value: string): void { + if (value) { + localStorage.setItem(key, value); + } else { + localStorage.removeItem(key); + } +} + /* Run settings */ export interface RunSettings { n: number; nt: number; maxParallel: number | null; + backend?: string | null; } export function getRunSettings(): RunSettings { @@ -38,6 +47,7 @@ export function getRunSettings(): RunSettings { n: readNumber("csauto_run_n", 1), nt: readNumber("csauto_run_nt", 1), maxParallel: readNumber("csauto_run_max_parallel", 0) || null, + backend: readString("csauto_run_backend", "") || null, }; } @@ -45,6 +55,7 @@ export function setRunSettings(s: RunSettings): void { writeNumber("csauto_run_n", s.n); writeNumber("csauto_run_nt", s.nt); if (s.maxParallel) writeNumber("csauto_run_max_parallel", s.maxParallel); + writeString("csauto_run_backend", s.backend ?? ""); } /* Restart settings */ diff --git a/pyproject.toml b/pyproject.toml index 97b10fd..41c309d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ csauto = "csauto.cli:main" web = ["fastapi>=0.100", "uvicorn>=0.20", "pydantic>=2.0"] dev = ["pytest", "httpx", "ruff"] doe = ["scipy>=1.10"] +qarnot = ["qarnot>=2.23"] [tool.hatch.version] path = "VERSION" diff --git a/tests/integration/test_fake_backend.py b/tests/integration/test_fake_backend.py new file mode 100644 index 0000000..3ca239b --- /dev/null +++ b/tests/integration/test_fake_backend.py @@ -0,0 +1,60 @@ +"""The remote execution lifecycle, end to end, with no network. + +What tests/integration/test_stub_adapter.py is for solvers. +""" + +from __future__ import annotations + +from pathlib import Path + +from csauto.backend_sync import sync_backend_cases +from csauto.registry import STATUS_DONE, STATUS_RUNNING, load_registry +from csauto.runner import refresh_status, run_cases + + +def test_submit_observe_and_finish_on_the_fake_backend(monkeypatch, runs_dir: Path, case_factory) -> None: + from csauto.backends.fake import FakeBackend + + case_factory(runs_dir, "case0001") + backend = FakeBackend( + script=["RUNNING", "RUNNING", "DONE"], + stdout_lines=["iteration 1", "iteration 2"], + sync_files={"OUT/run_0001/progress.csv": "it,res\n1,1e-2\n"}, + final_files={"OUT/run_0001/result.dat": "done\n"}, + ) + monkeypatch.setattr("csauto.backends.get_backend", lambda _name: backend) + monkeypatch.setattr("shutil.which", lambda _name: "/bin/true") + + run_cases( + runs_dir, + nprocs=1, + nt=1, + max_parallel=1, + case_filter=["case0001"], + docker_image="image", + resume_only_failed=False, + source="test", + backend="fake", + ) + + case_dir = runs_dir / "case0001" + record = load_registry(runs_dir)["case0001"] + assert record["status"] == STATUS_RUNNING + assert record["task_id"].startswith("fake-") + + # refresh_status must not finalise a case the backend owns + assert refresh_status(runs_dir)[0]["status"] == STATUS_RUNNING + + sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) + assert (case_dir / "OUT" / "run_0001" / "progress.csv").is_file() + assert "iteration 1" in (case_dir / "csauto.stdout").read_text() + assert load_registry(runs_dir)["case0001"]["status"] == STATUS_RUNNING + + sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) + sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) + + record = load_registry(runs_dir)["case0001"] + assert record["status"] == STATUS_DONE + assert record["results_fetched"] is True + assert (case_dir / "OUT" / "run_0001" / "result.dat").read_text() == "done\n" + assert refresh_status(runs_dir)[0]["status"] == STATUS_DONE diff --git a/tests/integration/test_qarnot_live.py b/tests/integration/test_qarnot_live.py new file mode 100644 index 0000000..63f34ff --- /dev/null +++ b/tests/integration/test_qarnot_live.py @@ -0,0 +1,151 @@ +"""The real Qarnot round trip. Skipped unless explicitly enabled. + +Everything else about the Qarnot backend is tested against a fake connection, +which proves what csauto *asks* Qarnot to do. This proves what Qarnot actually +does with it, and it is the only test here that costs money. + +Run it with: + + CSAUTO_QARNOT_LIVE=1 QARNOT_TOKEN=... .venv/bin/python -m pytest \\ + tests/integration/test_qarnot_live.py -q -s + +It submits one tiny task, waits for it, and asserts the three things the design +rests on: that the case files arrive, that a partial result comes back while +the task is still running, and that the full results land on local disk. +""" + +from __future__ import annotations + +import contextlib +import os +import shutil +import tempfile +import time +from collections.abc import Iterator +from pathlib import Path + +import pytest + +LIVE = os.environ.get("CSAUTO_QARNOT_LIVE", "").strip() == "1" +TIMEOUT_S = 600 +POLL_S = 10 + +# A stable directory, deliberately not pytest's tmp_path. Bucket names derive +# from the campaign's absolute path, so a fresh tmp_path per run would create a +# new set of buckets every time and eat the account's bucket quota. +LIVE_ROOT = Path(tempfile.gettempdir()) / "csauto-qarnot-live" + +pytestmark = pytest.mark.skipif( + not LIVE, + reason="set CSAUTO_QARNOT_LIVE=1 and QARNOT_TOKEN to run against the real Qarnot API", +) + + +@pytest.fixture +def tiny_case() -> Iterator[Path]: + """A case that needs no solver: a shell loop writing into the results dir. + + Deliberately not code_saturne. This test answers questions about Qarnot, + not about the solver, and a real mesh would make it slow and expensive. + + The remote buckets are deleted afterwards. A Qarnot account has a finite + bucket count and a finite storage quota, and a test that leaks a bucket per + run exhausts both, which is exactly how this cleanup came to be written. + """ + runs_dir = LIVE_ROOT / "RUNS" + if runs_dir.exists(): + shutil.rmtree(runs_dir) + case_dir = runs_dir / "case0001" + (case_dir / "DATA").mkdir(parents=True) + (case_dir / "DATA" / "input.txt").write_text("hello from csauto\n", encoding="utf-8") + (runs_dir / "csauto.toml").write_text("solver = 'stub'\n", encoding="utf-8") + + yield case_dir + + _delete_remote_buckets(runs_dir, case_dir.name) + + +def _delete_remote_buckets(runs_dir: Path, case_id: str) -> None: + """Remove what this test created on the account. Never fails the test.""" + from csauto.backends.qarnot import QarnotBackend + from csauto.backends.qarnot_support import bucket_name + + campaign = bucket_name(runs_dir) + with contextlib.suppress(Exception): + connection = QarnotBackend()._connect() + for suffix in (case_id, f"{case_id}-out", "shared"): + with contextlib.suppress(Exception): + connection.retrieve_bucket(f"{campaign}-{suffix}").delete() + + +def test_a_real_task_round_trips(tiny_case: Path) -> None: + from csauto.backends.qarnot import QarnotBackend + + backend = QarnotBackend() + # cd into the case first: the working directory is the study, with the case + # in a directory of its own and the shared directories beside it, mirroring + # the local layout. That is what lets code_saturne find /MESH. + script = ( + "cd case0001 && " + "mkdir -p OUT/run_0001 && " + "cp DATA/input.txt OUT/run_0001/echoed.txt && " + "for i in 1 2 3 4 5 6; do " + "echo iteration $i >> OUT/run_0001/progress.log; echo iteration $i; sleep 10; " + "done && " + "echo finished > OUT/run_0001/final.txt" + ) + + task_id = backend.submit( + tiny_case, + ["sh", "-c", script], + "library/alpine:3.20", + nprocs=1, + nt=1, + observability_globs=("OUT/*/progress.log",), + ) + print(f"submitted task {task_id}") + assert task_id + + # Read the attribute, not str(): the SDK's __str__ returns + # "scheduling type Flex.", which would fail an equality check for the + # wrong reason. Costs nothing: the task was going to be submitted anyway. + submitted = backend._connect().retrieve_task(task_id) + assert submitted.scheduling_type.schedulingType == "Flex", "the scheduling class did not reach the task" + + saw_running = False + saw_partial = False + saw_stdout = False + deadline = time.monotonic() + TIMEOUT_S + state = None + + try: + while time.monotonic() < deadline: + state = backend.poll(task_id) + if state.stdout_delta: + saw_stdout = True + if state.status == "RUNNING": + saw_running = True + backend.sync(task_id, tiny_case) + if (tiny_case / "OUT" / "run_0001" / "progress.log").is_file(): + saw_partial = True + if state.status in ("DONE", "FAILED"): + break + time.sleep(POLL_S) + + assert state is not None, "the task never reported a state" + assert state.status == "DONE", f"the task ended {state.status}" + assert saw_running, "the task never went through RUNNING" + + backend.fetch_final(task_id, tiny_case) + finally: + backend.cancel(task_id) + + # The design's three load-bearing claims, in order. + assert saw_stdout, "fresh_stdout returned nothing: the Log Tail would stay empty during a cloud run" + assert saw_partial, ( + "no snapshot came back while the task ran: the whole targeted-observability design rests on this" + ) + assert (tiny_case / "OUT" / "run_0001" / "final.txt").read_text().strip() == "finished" + assert (tiny_case / "OUT" / "run_0001" / "echoed.txt").read_text() == "hello from csauto\n", ( + "the case input did not reach the task's working directory: the relative --case path is wrong" + ) diff --git a/tests/runner/test_runner.py b/tests/runner/test_runner.py index 686e0db..4383c94 100644 --- a/tests/runner/test_runner.py +++ b/tests/runner/test_runner.py @@ -1086,3 +1086,487 @@ def popen_stub(*_args, **_kwargs): ) assert load_registry(runs_dir)["case0001"]["status"] == STATUS_FAILED + + +def test_run_cases_with_a_backend_records_backend_and_task_id(monkeypatch, runs_dir: Path, case_factory) -> None: + case_factory(runs_dir, "case0001") + + def no_popen(*_args, **_kwargs): + raise AssertionError("Popen must not be used for a backend launch") + + monkeypatch.setattr("subprocess.Popen", no_popen) + monkeypatch.setattr("shutil.which", lambda _name: "/bin/true") + + run_cases( + runs_dir, + nprocs=2, + nt=3, + max_parallel=1, + case_filter=["case0001"], + docker_image="my_image", + resume_only_failed=False, + source="test", + backend="fake", + ) + + record = load_registry(runs_dir)["case0001"] + assert record["status"] == STATUS_RUNNING + assert record["backend"] == "fake" + assert record["task_id"].startswith("fake-") + assert record["pid"] is None + assert record["job_id"] is None + assert record["nprocs"] == 2 + assert record["nt"] == 3 + + +def test_local_launch_records_no_backend(monkeypatch, runs_dir: Path, case_factory) -> None: + """Non-regression: a local run must not gain a backend field.""" + case_factory(runs_dir, "case0001") + + class DummyProc: + pid = 4242 + + monkeypatch.setattr("subprocess.Popen", lambda *_a, **_k: DummyProc()) + monkeypatch.setattr("shutil.which", lambda _name: "/bin/true") + + run_cases( + runs_dir, + nprocs=1, + nt=1, + max_parallel=1, + case_filter=["case0001"], + docker_image="image", + resume_only_failed=False, + source="test", + ) + + record = load_registry(runs_dir)["case0001"] + assert record.get("backend") is None + assert record["pid"] + + +def test_refresh_status_does_not_finalize_a_backend_case(runs_dir: Path, case_factory) -> None: + """A backend case has no PID and no job id; the sync pass owns its status. + + Without the guard, should_finalize is True on the first refresh and the + STATUS_FAILED fallback marks the case failed before it has started. + """ + case_dir = case_factory(runs_dir, "case0001") + save_registry( + runs_dir, + { + "case0001": { + "case_id": "case0001", + "path": str(case_dir), + "status": STATUS_RUNNING, + "backend": "fake", + "task_id": "fake-0001", + "pid": None, + "job_id": None, + "start_time": datetime.now().isoformat(timespec="seconds"), + } + }, + ) + + rows = refresh_status(runs_dir) + + assert rows[0]["status"] == STATUS_RUNNING + assert load_registry(runs_dir)["case0001"]["status"] == STATUS_RUNNING + + +def test_refresh_status_still_finalizes_a_local_case_whose_process_is_gone(runs_dir: Path, case_factory) -> None: + """Non-regression: the guard must not disable finalisation for local cases.""" + case_dir = case_factory(runs_dir, "case0001") + save_registry( + runs_dir, + { + "case0001": { + "case_id": "case0001", + "path": str(case_dir), + "status": STATUS_RUNNING, + "pid": None, + "job_id": None, + "start_time": datetime.now().isoformat(timespec="seconds"), + } + }, + ) + + rows = refresh_status(runs_dir) + + assert rows[0]["status"] == STATUS_FAILED + + +def test_refresh_status_ignores_the_reserved_backend_key(runs_dir, case_factory) -> None: + """The _backend marker is campaign state, not a case; it must not be listed.""" + case_factory(runs_dir, "case0001") + save_registry( + runs_dir, + { + "_backend": {"last_sync": "2026-09-14T10:00:00"}, + "case0001": {"case_id": "case0001", "path": str(runs_dir / "case0001"), "status": "PREPARED"}, + }, + ) + + rows = refresh_status(runs_dir) + + assert [row["case_id"] for row in rows] == ["case0001"] + + +def test_backend_argv_uses_a_relative_case_path(monkeypatch, runs_dir, case_factory) -> None: + """The remote container has no idea where the case lives on this machine. + + Its working directory is the case, the only place it may write, so the + path is relative and the host path never travels. + """ + from csauto.backends.fake import FakeBackend + + case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["RUNNING"]) + submitted: dict[str, object] = {} + original_submit = backend.submit + + def record(case_dir, argv, image, nprocs, nt, observability_globs=(), options=None): + submitted["argv"] = list(argv) + submitted["globs"] = tuple(observability_globs) + return original_submit(case_dir, argv, image, nprocs, nt, observability_globs, options or {}) + + backend.submit = record # type: ignore[method-assign] + monkeypatch.setattr("csauto.backends.get_backend", lambda _name: backend) + monkeypatch.setattr("shutil.which", lambda _name: "/bin/true") + + run_cases( + runs_dir, + nprocs=1, + nt=1, + max_parallel=1, + case_filter=["case0001"], + docker_image="image", + resume_only_failed=False, + source="test", + backend="fake", + ) + + assert str(runs_dir) not in " ".join(str(part) for part in submitted["argv"]) + assert "." in submitted["argv"] + assert submitted["globs"], "the adapter's observability patterns must reach the backend" + + +def test_status_row_carries_the_backend_figures(runs_dir, case_factory) -> None: + """The dashboard shows what a cloud case costs; the figures live in the registry.""" + case_factory(runs_dir, "case0001") + save_registry( + runs_dir, + { + "case0001": { + "case_id": "case0001", + "path": str(runs_dir / "case0001"), + "status": STATUS_RUNNING, + "backend": "fake", + "task_id": "fake-0001", + "backend_progress": 0.25, + "backend_execution_time_s": 42.0, + "backend_core_count": 8, + } + }, + ) + + row = refresh_status(runs_dir)[0] + + assert row["backend"] == "fake" + assert row["backend_progress"] == 0.25 + assert row["backend_execution_time_s"] == 42.0 + assert row["backend_core_count"] == 8 + + +def test_status_row_of_a_local_case_has_no_backend_figures(runs_dir, case_factory) -> None: + case_factory(runs_dir, "case0001") + save_registry( + runs_dir, + {"case0001": {"case_id": "case0001", "path": str(runs_dir / "case0001"), "status": "PREPARED"}}, + ) + + row = refresh_status(runs_dir)[0] + + assert row["backend"] == "" + assert row["backend_execution_time_s"] is None + + +def test_submitting_to_a_backend_does_not_hold_the_registry_lock(monkeypatch, runs_dir, case_factory) -> None: + """A submit uploads the case and talks to a remote API. + + Holding the registry lock for that long blocks every reader, which freezes + the dashboard: /api/status calls load_registry, which takes the same lock. + """ + from csauto.backends.fake import FakeBackend + + case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["RUNNING"]) + original_submit = backend.submit + readable: list[bool] = [] + + def probing_submit(case_dir, argv, image, nprocs, nt, observability_globs=(), options=None): + done = threading.Event() + + def read_registry() -> None: + load_registry(runs_dir) + done.set() + + threading.Thread(target=read_registry, daemon=True).start() + readable.append(done.wait(timeout=5)) + return original_submit(case_dir, argv, image, nprocs, nt, observability_globs, options or {}) + + backend.submit = probing_submit # type: ignore[method-assign] + monkeypatch.setattr("csauto.backends.get_backend", lambda _name: backend) + monkeypatch.setattr("shutil.which", lambda _name: "/bin/true") + + run_cases( + runs_dir, + nprocs=1, + nt=1, + max_parallel=1, + case_filter=["case0001"], + docker_image="image", + resume_only_failed=False, + source="test", + backend="fake", + ) + + assert readable == [True], "the registry lock was held across the backend submit" + assert load_registry(runs_dir)["case0001"]["status"] == STATUS_RUNNING + + +def test_launch_options_reach_the_backend(monkeypatch, runs_dir, case_factory) -> None: + """The core carries the values without reading them.""" + from csauto.backends.fake import FakeBackend + + case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["RUNNING"]) + monkeypatch.setattr("csauto.backends.get_backend", lambda _name: backend) + monkeypatch.setattr("shutil.which", lambda _name: "/bin/true") + + run_cases( + runs_dir, + nprocs=1, + nt=1, + max_parallel=1, + case_filter=["case0001"], + docker_image="image", + resume_only_failed=False, + source="test", + backend="fake", + options={"speed": "fast"}, + ) + + assert backend.submitted_options == {"speed": "fast"} + + +def test_launch_options_are_recorded_in_the_case_history(monkeypatch, runs_dir, case_factory) -> None: + """An audit should show what a run asked for, and what it cost.""" + import json + + from csauto.backends.fake import FakeBackend + + case_dir = case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["RUNNING"]) + monkeypatch.setattr("csauto.backends.get_backend", lambda _name: backend) + monkeypatch.setattr("shutil.which", lambda _name: "/bin/true") + + run_cases( + runs_dir, + nprocs=1, + nt=1, + max_parallel=1, + case_filter=["case0001"], + docker_image="image", + resume_only_failed=False, + source="test", + backend="fake", + options={"speed": "fast"}, + ) + + entries = [json.loads(line) for line in (case_dir / ".csauto.history.jsonl").read_text().splitlines()] + runs = [entry for entry in entries if entry["action"] == "run"] + assert runs[-1]["details"]["options"] == {"speed": "fast"} + + +def test_a_local_run_records_no_options(monkeypatch, runs_dir, case_factory) -> None: + """Options belong to a backend launch; a local run must not grow a field.""" + import json + + case_dir = case_factory(runs_dir, "case0001") + monkeypatch.setattr("csauto.runner.read_container_id", lambda *_args, **_kwargs: None) + monkeypatch.setattr("shutil.which", lambda _name: "/bin/true") + + class DummyProc: + def __init__(self, pid: int = 12345) -> None: + self.pid = pid + + monkeypatch.setattr("subprocess.Popen", lambda *_args, **_kwargs: DummyProc()) + + run_cases( + runs_dir, + nprocs=1, + nt=1, + max_parallel=1, + case_filter=["case0001"], + docker_image="image", + resume_only_failed=False, + source="test", + ) + + entries = [json.loads(line) for line in (case_dir / ".csauto.history.jsonl").read_text().splitlines()] + runs = [entry for entry in entries if entry["action"] == "run"] + assert "options" not in runs[-1]["details"] + + +def test_a_backend_launch_prepares_the_case_first(monkeypatch, runs_dir, case_factory) -> None: + """The shared dirs land inside the case remotely; the adapter adjusts for it.""" + from csauto.backends.fake import FakeBackend + from csauto.solvers.code_saturne import CodeSaturneAdapter + + case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["RUNNING"]) + calls: list[str] = [] + + class Adapter(CodeSaturneAdapter): + def prepare_remote_case(self, case_dir) -> None: + calls.append(case_dir.name) + + monkeypatch.setattr("csauto.backends.get_backend", lambda _name: backend) + monkeypatch.setattr("shutil.which", lambda _name: "/bin/true") + + run_cases( + runs_dir, + nprocs=1, + nt=1, + max_parallel=1, + case_filter=["case0001"], + docker_image="image", + resume_only_failed=False, + source="test", + backend="fake", + adapter=Adapter(), + ) + + assert calls == ["case0001"] + + +def test_a_local_launch_does_not_prepare_a_remote_case(monkeypatch, runs_dir, case_factory) -> None: + from csauto.solvers.code_saturne import CodeSaturneAdapter + + case_factory(runs_dir, "case0001") + calls: list[str] = [] + + class Adapter(CodeSaturneAdapter): + def prepare_remote_case(self, case_dir) -> None: + calls.append(case_dir.name) + + monkeypatch.setattr("csauto.runner.read_container_id", lambda *_args, **_kwargs: None) + monkeypatch.setattr("shutil.which", lambda _name: "/bin/true") + + class DummyProc: + def __init__(self, pid: int = 12345) -> None: + self.pid = pid + + monkeypatch.setattr("subprocess.Popen", lambda *_args, **_kwargs: DummyProc()) + + run_cases( + runs_dir, + nprocs=1, + nt=1, + max_parallel=1, + case_filter=["case0001"], + docker_image="image", + resume_only_failed=False, + source="test", + adapter=Adapter(), + ) + + assert calls == [] + + +def test_resu_size_expires_even_when_directory_mtimes_do_not_move(monkeypatch, runs_dir, case_factory) -> None: + """Some filesystems never bump a directory's mtime when files change inside it. + + WSL2 is one, so a cache keyed on that mtime alone never invalidates and the + reported size stays frozen for the life of the process. A backend sync + writes into an existing run directory, which is exactly that case. + """ + from csauto import runner + from csauto.runner import _cached_resu_size_mb + from csauto.solvers.code_saturne import CodeSaturneAdapter + + adapter = CodeSaturneAdapter() + case_dir = case_factory(runs_dir, "case0001") + run_dir = case_dir / "RESU" / "20260101-0000" + run_dir.mkdir(parents=True) + (run_dir / "listing").write_text("x" * 1024, encoding="utf-8") + + now = 1000.0 + monkeypatch.setattr(runner.time, "monotonic", lambda: now) + _mtime, first = _cached_resu_size_mb(case_dir, adapter) + + (run_dir / "residuals.csv").write_text("y" * (512 * 1024), encoding="utf-8") + now += runner.RESU_SIZE_CACHE_TTL_S + 1 + _mtime, second = _cached_resu_size_mb(case_dir, adapter) + + assert first == 0.0 + assert second is not None and second > first + + +def test_resu_size_is_not_recomputed_within_its_cache_window(monkeypatch, runs_dir, case_factory) -> None: + """The status route is polled about once a second; walking RESU each time is not free.""" + from csauto import runner + from csauto.runner import _cached_resu_size_mb + from csauto.solvers.code_saturne import CodeSaturneAdapter + + adapter = CodeSaturneAdapter() + case_dir = case_factory(runs_dir, "case0001") + run_dir = case_dir / "RESU" / "20260101-0000" + run_dir.mkdir(parents=True) + (run_dir / "listing").write_text("x" * 1024, encoding="utf-8") + + walks: list[int] = [] + real_size = runner._resu_size_mb + + def counted(*args, **kwargs): + walks.append(1) + return real_size(*args, **kwargs) + + monkeypatch.setattr(runner, "_resu_size_mb", counted) + monkeypatch.setattr(runner.time, "monotonic", lambda: 1000.0) + + _cached_resu_size_mb(case_dir, adapter) + _cached_resu_size_mb(case_dir, adapter) + + assert len(walks) == 1 + + +def test_a_finished_case_reports_the_iteration_its_log_ends_on(runs_dir, case_factory) -> None: + """A stale run_status.running must not freeze LAST ITER at its capture point.""" + from csauto.registry import save_registry + + case_dir = case_factory(runs_dir, "case0001") + run_dir = case_dir / "RESU" / "20260101-0000" + run_dir.mkdir(parents=True) + (run_dir / "run_status.running").write_text("time step: 9479\n", encoding="utf-8") + (run_dir / "run_solver.log").write_text( + " INSTANT 100.000000000 TIME STEP NUMBER 10000\n END OF CALCULATION\n", + encoding="utf-8", + ) + save_registry( + runs_dir, + { + "case0001": { + "case_id": "case0001", + "path": str(case_dir), + "status": STATUS_DONE, + "start_time": "2026-01-01T00:00:00", + "end_time": "2026-01-01T01:00:00", + } + }, + ) + + row = refresh_status(runs_dir, adapter=CodeSaturneAdapter())[0] + + assert row["last_iter"] == 10000 diff --git a/tests/unit/test_backend_sync.py b/tests/unit/test_backend_sync.py new file mode 100644 index 0000000..76f8279 --- /dev/null +++ b/tests/unit/test_backend_sync.py @@ -0,0 +1,215 @@ +from __future__ import annotations + +from pathlib import Path + +from csauto.backend_sync import sync_backend_cases +from csauto.backends.fake import FakeBackend +from csauto.registry import STATUS_DONE, STATUS_FAILED, STATUS_RUNNING, load_registry, save_registry + + +def _register(runs_dir: Path, case_dir: Path, task_id: str = "fake-0001") -> None: + save_registry( + runs_dir, + { + "case0001": { + "case_id": "case0001", + "path": str(case_dir), + "status": STATUS_RUNNING, + "backend": "fake", + "task_id": task_id, + } + }, + ) + + +def test_sync_appends_stdout_to_the_launcher_log(runs_dir: Path, case_factory) -> None: + """csauto.stdout is what the Log Tail and Recent Errors already read.""" + case_dir = case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["RUNNING", "RUNNING"], stdout_lines=["step 1", "step 2"]) + task_id = backend.submit(case_dir, ["run"], "img", 1, 1) + _register(runs_dir, case_dir, task_id) + + sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) + sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) + + assert (case_dir / "csauto.stdout").read_text() == "step 1\nstep 2\n" + + +def test_sync_records_progress(runs_dir: Path, case_factory) -> None: + case_dir = case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["RUNNING", "RUNNING"]) + task_id = backend.submit(case_dir, ["run"], "img", 1, 1) + _register(runs_dir, case_dir, task_id) + + sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) + + assert load_registry(runs_dir)["case0001"]["backend_progress"] == 0.5 + + +def test_a_failed_poll_leaves_the_status_untouched(runs_dir: Path, case_factory) -> None: + """A flaky network must not mark cases as failed.""" + case_dir = case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["RUNNING"], fail_polls=1) + task_id = backend.submit(case_dir, ["run"], "img", 1, 1) + _register(runs_dir, case_dir, task_id) + + sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) + + record = load_registry(runs_dir)["case0001"] + assert record["status"] == STATUS_RUNNING + assert record["backend_poll_failures"] == 1 + + +def test_a_successful_poll_clears_the_failure_counter(runs_dir: Path, case_factory) -> None: + case_dir = case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["RUNNING"], fail_polls=1) + task_id = backend.submit(case_dir, ["run"], "img", 1, 1) + _register(runs_dir, case_dir, task_id) + + sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) + sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) + + assert load_registry(runs_dir)["case0001"]["backend_poll_failures"] == 0 + + +def test_done_only_after_the_results_are_local(runs_dir: Path, case_factory) -> None: + """DONE means the results are on local disk; Compare and Clean assume it.""" + case_dir = case_factory(runs_dir, "case0001") + backend = FakeBackend( + script=["DONE"], + final_files={"OUT/run_0001/result.dat": "done\n"}, + fail_fetch_final=1, + ) + task_id = backend.submit(case_dir, ["run"], "img", 1, 1) + _register(runs_dir, case_dir, task_id) + + sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) + assert load_registry(runs_dir)["case0001"]["status"] == STATUS_RUNNING + assert not (case_dir / "OUT" / "run_0001" / "result.dat").exists() + + sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) + record = load_registry(runs_dir)["case0001"] + assert record["status"] == STATUS_DONE + assert record["results_fetched"] is True + assert (case_dir / "OUT" / "run_0001" / "result.dat").read_text() == "done\n" + + +def test_a_failed_remote_task_is_reported_failed(runs_dir: Path, case_factory) -> None: + case_dir = case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["FAILED"]) + task_id = backend.submit(case_dir, ["run"], "img", 1, 1) + _register(runs_dir, case_dir, task_id) + + sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) + + assert load_registry(runs_dir)["case0001"]["status"] == STATUS_FAILED + + +def test_finished_cases_are_skipped(runs_dir: Path, case_factory) -> None: + case_dir = case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["DONE"]) + task_id = backend.submit(case_dir, ["run"], "img", 1, 1) + save_registry( + runs_dir, + { + "case0001": { + "case_id": "case0001", + "path": str(case_dir), + "status": STATUS_DONE, + "backend": "fake", + "task_id": task_id, + "results_fetched": True, + } + }, + ) + + assert sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) == 0 + + +def test_cases_without_a_backend_are_ignored(runs_dir: Path, case_factory) -> None: + case_dir = case_factory(runs_dir, "case0001") + save_registry( + runs_dir, + {"case0001": {"case_id": "case0001", "path": str(case_dir), "status": STATUS_RUNNING, "pid": 1}}, + ) + + assert sync_backend_cases(runs_dir) == 0 + + +def test_a_relaunched_case_is_not_overwritten(runs_dir: Path, case_factory) -> None: + """Phase 3 must skip a case whose task id changed while the network call ran.""" + case_dir = case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["DONE"], final_files={"OUT/x": "y\n"}) + task_id = backend.submit(case_dir, ["run"], "img", 1, 1) + _register(runs_dir, case_dir, task_id) + + def relaunch_then_poll(_name: str): + save_registry( + runs_dir, + { + "case0001": { + "case_id": "case0001", + "path": str(case_dir), + "status": STATUS_RUNNING, + "backend": "fake", + "task_id": "fake-9999", + } + }, + ) + return backend + + sync_backend_cases(runs_dir, backend_factory=relaunch_then_poll) + + record = load_registry(runs_dir)["case0001"] + assert record["task_id"] == "fake-9999" + assert record["status"] == STATUS_RUNNING + + +def test_sync_records_the_timing_figures(runs_dir: Path, case_factory) -> None: + """execution_time and running_core_count come free from the backend.""" + from csauto.backends.base import BackendState + + case_dir = case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["RUNNING"]) + task_id = backend.submit(case_dir, ["run"], "img", 1, 1) + _register(runs_dir, case_dir, task_id) + backend.poll = lambda _task_id: BackendState( # type: ignore[method-assign] + status=STATUS_RUNNING, execution_time_s=42.0, running_core_count=8 + ) + + sync_backend_cases(runs_dir, backend_factory=lambda _name: backend) + + record = load_registry(runs_dir)["case0001"] + assert record["backend_execution_time_s"] == 42.0 + assert record["backend_core_count"] == 8 + + +def test_a_throttled_sync_runs_once_then_holds(runs_dir: Path, case_factory) -> None: + """Every Refresh click must not become a provider round trip.""" + from csauto.backend_sync import sync_backend_cases_throttled + + case_dir = case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["RUNNING", "RUNNING"]) + task_id = backend.submit(case_dir, ["run"], "img", 1, 1) + _register(runs_dir, case_dir, task_id) + + first = sync_backend_cases_throttled(runs_dir, backend_factory=lambda _name: backend) + second = sync_backend_cases_throttled(runs_dir, backend_factory=lambda _name: backend) + + assert first is True + assert second is False + + +def test_a_throttled_sync_never_raises(runs_dir: Path, case_factory) -> None: + """A provider outage must not fail the click that asked for fresh data.""" + from csauto.backend_sync import sync_backend_cases_throttled + + case_dir = case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["RUNNING"]) + task_id = backend.submit(case_dir, ["run"], "img", 1, 1) + _register(runs_dir, case_dir, task_id) + + def explode(_name: str): + raise RuntimeError("provider is down") + + assert sync_backend_cases_throttled(runs_dir, backend_factory=explode) is True diff --git a/tests/unit/test_backends.py b/tests/unit/test_backends.py new file mode 100644 index 0000000..8940677 --- /dev/null +++ b/tests/unit/test_backends.py @@ -0,0 +1,161 @@ +from __future__ import annotations + +import dataclasses + +import pytest + +from csauto.backends import available_backends, get_backend +from csauto.backends.base import BackendState, ExecutionBackend + + +def test_backend_state_is_frozen_and_carries_csauto_vocabulary() -> None: + state = BackendState(status="RUNNING", progress=0.42, stdout_delta="x", stderr_delta="") + assert state.status == "RUNNING" + assert state.progress == 0.42 + with pytest.raises(dataclasses.FrozenInstanceError): + state.status = "DONE" # type: ignore[misc] + + +def test_get_backend_is_memoized_and_normalizes_the_name() -> None: + assert get_backend("fake") is get_backend(" Fake ") + + +def test_unknown_backend_raises_with_the_choices() -> None: + with pytest.raises(ValueError, match="Unknown backend"): + get_backend("nope") + + +def test_available_backends_lists_fake() -> None: + assert "fake" in available_backends() + + +def test_fake_backend_satisfies_the_protocol() -> None: + assert isinstance(get_backend("fake"), ExecutionBackend) + + +def test_fake_backend_walks_its_scripted_states(tmp_path) -> None: + from csauto.backends.fake import FakeBackend + + case_dir = tmp_path / "case0001" + case_dir.mkdir() + backend = FakeBackend(script=["PENDING", "RUNNING", "DONE"]) + + task_id = backend.submit(case_dir, ["run"], "image", 1, 1) + assert backend.poll(task_id).status == "PENDING" + assert backend.poll(task_id).status == "RUNNING" + assert backend.poll(task_id).status == "DONE" + assert backend.poll(task_id).status == "DONE" # stays on the last state + + +def test_fake_backend_emits_output_deltas_once(tmp_path) -> None: + from csauto.backends.fake import FakeBackend + + case_dir = tmp_path / "case0001" + case_dir.mkdir() + backend = FakeBackend(script=["RUNNING", "DONE"], stdout_lines=["step 1", "step 2"]) + + task_id = backend.submit(case_dir, ["run"], "image", 1, 1) + first = backend.poll(task_id) + second = backend.poll(task_id) + + assert first.stdout_delta == "step 1\n" + assert second.stdout_delta == "step 2\n" + assert backend.poll(task_id).stdout_delta == "" + + +def test_fake_backend_sync_writes_observability_files(tmp_path) -> None: + from csauto.backends.fake import FakeBackend + + case_dir = tmp_path / "case0001" + case_dir.mkdir() + backend = FakeBackend(script=["RUNNING"], sync_files={"OUT/run_0001/progress.csv": "it,res\n1,1e-2\n"}) + + task_id = backend.submit(case_dir, ["run"], "image", 1, 1) + backend.sync(task_id, case_dir) + + assert (case_dir / "OUT" / "run_0001" / "progress.csv").read_text() == "it,res\n1,1e-2\n" + + +def test_fake_backend_fetch_final_writes_the_result_marker(tmp_path) -> None: + from csauto.backends.fake import FakeBackend + + case_dir = tmp_path / "case0001" + case_dir.mkdir() + backend = FakeBackend(script=["DONE"], final_files={"OUT/run_0001/result.dat": "done\n"}) + + task_id = backend.submit(case_dir, ["run"], "image", 1, 1) + backend.fetch_final(task_id, case_dir) + + assert (case_dir / "OUT" / "run_0001" / "result.dat").read_text() == "done\n" + + +def test_fake_backend_cancel_forces_the_failed_state(tmp_path) -> None: + from csauto.backends.fake import FakeBackend + + case_dir = tmp_path / "case0001" + case_dir.mkdir() + backend = FakeBackend(script=["RUNNING", "RUNNING", "RUNNING"]) + + task_id = backend.submit(case_dir, ["run"], "image", 1, 1) + backend.cancel(task_id) + + assert backend.poll(task_id).status == "FAILED" + + +def test_fake_backend_can_be_told_to_fail_a_poll(tmp_path) -> None: + """The sync pass must survive a transient failure; the fake can produce one.""" + from csauto.backends.fake import FakeBackend + + case_dir = tmp_path / "case0001" + case_dir.mkdir() + backend = FakeBackend(script=["RUNNING"], fail_polls=1) + + task_id = backend.submit(case_dir, ["run"], "image", 1, 1) + with pytest.raises(RuntimeError, match="fake poll failure"): + backend.poll(task_id) + assert backend.poll(task_id).status == "RUNNING" + + +def test_a_backend_declares_what_a_user_may_choose() -> None: + from pathlib import Path # noqa: F401 - used by the sibling tests below + + from csauto.backends.base import LaunchOptions + from csauto.backends.fake import FakeBackend + + catalogue = FakeBackend().launch_options() + + assert isinstance(catalogue, LaunchOptions) + assert catalogue.degraded is False + assert [option.key for option in catalogue.options] == ["speed"] + + +def test_a_declared_option_carries_its_choices_and_default() -> None: + from csauto.backends.base import LaunchOption + from csauto.backends.fake import FakeBackend + + option = FakeBackend().launch_options().options[0] + + assert isinstance(option, LaunchOption) + assert option.default == "slow" + assert dict(option.choices) == {"slow": "Slow", "fast": "Fast"} + + +def test_submit_receives_the_chosen_options(tmp_path) -> None: + from csauto.backends.fake import FakeBackend + + backend = FakeBackend() + + backend.submit(tmp_path, ["run"], "img", 1, 1, (), {"speed": "fast"}) + + assert backend.submitted_options == {"speed": "fast"} + + +def test_submit_without_options_receives_an_empty_mapping(tmp_path) -> None: + """Every existing caller omits the argument; none of them may break.""" + from csauto.backends.fake import FakeBackend + + backend = FakeBackend() + + backend.submit(tmp_path, ["run"], "img", 1, 1) + + assert backend.submitted_options == {} diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 49508fd..d271143 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -321,3 +321,20 @@ def test_prepare_without_strict_only_warns_on_unused_column(tmp_path: Path, caps assert exit_code == 0 assert "extra_col" in capsys.readouterr().err assert (tmp_path / "RUNS" / "case0001").is_dir() + + +def test_status_runs_one_throttled_backend_sync(monkeypatch, runs_dir: Path, case_factory) -> None: + """The sync loop lives in the server; the CLI must not be useless without it.""" + calls = {"count": 0} + + def fake_sync(*_args, **_kwargs): + calls["count"] += 1 + return 0 + + monkeypatch.setattr("csauto.backend_sync.sync_backend_cases", fake_sync) + case_factory(runs_dir, "case0001") + + main(["status", str(runs_dir)]) + main(["status", str(runs_dir)]) + + assert calls["count"] == 1, "the second call within the throttle window must be skipped" diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 71d17dc..ee214a2 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -85,3 +85,63 @@ def test_load_config_invalid_mesh_mode_raises(tmp_path: Path) -> None: with pytest.raises(ValueError, match="mesh_mode"): load_config(cfg) + + +def test_backend_intervals_have_defaults(tmp_path) -> None: + from csauto.config import load_config + + config_path = tmp_path / "csauto.toml" + config_path.write_text("runtime = 'native'\n", encoding="utf-8") + config = load_config(config_path) + + assert config.backend_poll_interval_s == 15 + assert config.backend_sync_interval_s == 60 + + +def test_backend_intervals_are_read_and_validated(tmp_path) -> None: + from csauto.config import load_config + + config_path = tmp_path / "csauto.toml" + config_path.write_text("backend_poll_interval_s = 30\nbackend_sync_interval_s = 120\n", encoding="utf-8") + config = load_config(config_path) + assert config.backend_poll_interval_s == 30 + assert config.backend_sync_interval_s == 120 + + config_path.write_text("backend_poll_interval_s = 0\n", encoding="utf-8") + with pytest.raises(ValueError, match="backend_poll_interval_s"): + load_config(config_path) + + +def test_qarnot_settings_have_defaults(tmp_path: Path) -> None: + (tmp_path / "csauto.toml").write_text("solver = 'stub'\n", encoding="utf-8") + config = load_config(tmp_path / "csauto.toml") + + assert config.qarnot_profile == "docker-batch" + assert config.qarnot_snapshot_interval_s == 60 + assert config.qarnot_max_upload_mb == 512 + + +def test_qarnot_settings_are_read_from_their_table(tmp_path: Path) -> None: + (tmp_path / "csauto.toml").write_text( + "[qarnot]\nprofile = 'docker-batch'\nsnapshot_interval_s = 120\nmax_upload_mb = 64\n", + encoding="utf-8", + ) + config = load_config(tmp_path / "csauto.toml") + + assert config.qarnot_snapshot_interval_s == 120 + assert config.qarnot_max_upload_mb == 64 + + +def test_a_qarnot_token_in_the_config_file_is_rejected(tmp_path: Path) -> None: + """The campaign file is shared, committed and archived. The token is not.""" + (tmp_path / "csauto.toml").write_text("[qarnot]\ntoken = 'secret'\n", encoding="utf-8") + + with pytest.raises(ValueError, match="QARNOT_TOKEN"): + load_config(tmp_path / "csauto.toml") + + +def test_a_bad_qarnot_interval_is_rejected(tmp_path: Path) -> None: + (tmp_path / "csauto.toml").write_text("[qarnot]\nsnapshot_interval_s = 0\n", encoding="utf-8") + + with pytest.raises(ValueError, match="snapshot_interval_s"): + load_config(tmp_path / "csauto.toml") diff --git a/tests/unit/test_maintenance.py b/tests/unit/test_maintenance.py index 746937c..6317ab8 100644 --- a/tests/unit/test_maintenance.py +++ b/tests/unit/test_maintenance.py @@ -516,3 +516,114 @@ def find_setup_file(self, template_dir): items = run_doctor(runs_dir, check_setup=False, check_display=False, adapter=BareAdapter()) assert _has_item(items, "ok", "solver bare: capabilities none") + + +def test_doctor_says_nothing_about_qarnot_by_default(tmp_path: Path) -> None: + from csauto.maintenance import run_doctor + + (tmp_path / "case0001").mkdir() + items = run_doctor(tmp_path, check_display=False, check_setup=False) + + assert not [item for item in items if "qarnot" in item.message.lower()] + + +def test_doctor_reports_a_missing_token(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + from csauto.maintenance import run_doctor + + monkeypatch.delenv("QARNOT_TOKEN", raising=False) + (tmp_path / "case0001").mkdir() + + items = run_doctor(tmp_path, check_display=False, check_setup=False, backend="qarnot") + messages = [item.message for item in items] + + assert any("QARNOT_TOKEN" in message for message in messages) + assert any(item.level == "fail" for item in items if "QARNOT_TOKEN" in item.message) + + +def test_doctor_never_prints_the_token(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + from csauto.maintenance import run_doctor + + monkeypatch.setenv("QARNOT_TOKEN", "super-secret-value") + (tmp_path / "case0001").mkdir() + + items = run_doctor(tmp_path, check_display=False, check_setup=False, backend="qarnot") + + assert not [item for item in items if "super-secret-value" in item.message] + assert any("QARNOT_TOKEN is set" in item.message for item in items) + + +def test_doctor_reports_a_solver_that_cannot_run_remotely(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + from csauto.maintenance import run_doctor + from csauto.solvers.code_aster import CodeAsterAdapter + + monkeypatch.setenv("QARNOT_TOKEN", "x") + (tmp_path / "case0001").mkdir() + + items = run_doctor(tmp_path, check_display=False, check_setup=False, backend="qarnot", adapter=CodeAsterAdapter()) + + assert any(item.level == "fail" and "code_aster" in item.message for item in items) + + +def test_doctor_reports_the_qarnot_quotas(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + """A full account fails at upload time, which is too late to find out.""" + from csauto import maintenance + + class FakeUser: + email = "someone@example.com" + bucket_count = 8 + max_bucket = 10 + used_quota_bytes_bucket = 3 * 1024**3 + quota_bytes_bucket = 4 * 1024**3 + + class FakeConnection: + user_info = FakeUser() + + monkeypatch.setenv("QARNOT_TOKEN", "x") + monkeypatch.setattr(maintenance, "_qarnot_connection", lambda: FakeConnection()) + (tmp_path / "case0001").mkdir() + + items = run_doctor(tmp_path, check_display=False, check_setup=False, backend="qarnot") + messages = [item.message for item in items] + + assert any("buckets 8/10" in message for message in messages) + assert any("storage 3.0/4.0 GB" in message for message in messages) + + +def test_doctor_warns_when_the_qarnot_storage_is_nearly_full(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + from csauto import maintenance + + class FakeUser: + email = "someone@example.com" + bucket_count = 1 + max_bucket = 10 + used_quota_bytes_bucket = 39 * 1024**3 + quota_bytes_bucket = 40 * 1024**3 + + class FakeConnection: + user_info = FakeUser() + + monkeypatch.setenv("QARNOT_TOKEN", "x") + monkeypatch.setattr(maintenance, "_qarnot_connection", lambda: FakeConnection()) + (tmp_path / "case0001").mkdir() + + items = run_doctor(tmp_path, check_display=False, check_setup=False, backend="qarnot") + + assert any(item.level == "warn" and "storage" in item.message for item in items) + + +def test_doctor_reports_an_unreachable_qarnot_without_leaking_the_token( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + from csauto import maintenance + + def explode(): + raise RuntimeError("boom") + + monkeypatch.setenv("QARNOT_TOKEN", "super-secret-value") + monkeypatch.setattr(maintenance, "_qarnot_connection", explode) + (tmp_path / "case0001").mkdir() + + items = run_doctor(tmp_path, check_display=False, check_setup=False, backend="qarnot") + + assert any(item.level == "fail" and "not reachable" in item.message for item in items) + assert not [item for item in items if "super-secret-value" in item.message] diff --git a/tests/unit/test_qarnot_backend.py b/tests/unit/test_qarnot_backend.py new file mode 100644 index 0000000..4bb30be --- /dev/null +++ b/tests/unit/test_qarnot_backend.py @@ -0,0 +1,653 @@ +"""The Qarnot backend, driven by a fake connection. + +No SDK, no token, no network: the tests assert what csauto asks Qarnot to do, +which is the part csauto owns. +""" + +from __future__ import annotations + +import re +from pathlib import Path +from typing import Any + +import pytest + +from csauto.backends.qarnot import QarnotBackend + + +class FakeBucket: + def __init__(self, name: str) -> None: + self.uuid = name + self.files: dict[str, str] = {} + self.directories: list[tuple[str, str]] = [] + + def add_file(self, local: str, remote: str) -> None: + self.files[remote] = local + + def sync_directory(self, directory: str, verbose: bool = False, remote: str | None = None) -> None: + self.directories.append((directory, remote or "")) + + +class FakeTask: + def __init__(self, name: str, profile: str, instancecount: int) -> None: + self.name = name + self.profile = profile + self.instancecount = instancecount + self.constants: dict[str, str] = {} + self.resources: list[FakeBucket] = [] + self.results: FakeBucket | None = None + self.snapshot_whitelist: str | None = None + self.results_whitelist: str | None = None + self.snapshot_calls: list[tuple[int, str | None]] = [] + self.submitted = False + self.uuid = "task-0001" + self._hardware_constraints: list[Any] = [] + self.constraints_set_while_unsubmitted: bool | None = None + self._scheduling_type: Any = None + self.scheduling_set_while_unsubmitted: bool | None = None + + @property + def hardware_constraints(self) -> list[Any]: + return self._hardware_constraints + + @hardware_constraints.setter + def hardware_constraints(self, value: list[Any]) -> None: + # The real SDK raises once the task is launched; record the ordering + # instead so a test can assert it. + self.constraints_set_while_unsubmitted = not self.submitted + self._hardware_constraints = value + + @property + def scheduling_type(self) -> Any: + return self._scheduling_type + + @scheduling_type.setter + def scheduling_type(self, value: Any) -> None: + self.scheduling_set_while_unsubmitted = not self.submitted + self._scheduling_type = value + + def submit(self) -> None: + self.submitted = True + + def snapshot(self, interval: int, whitelist: str | None = None, **_: Any) -> None: + self.snapshot_calls.append((interval, whitelist)) + + +class FakeConnection: + def __init__(self) -> None: + self.buckets: dict[str, FakeBucket] = {} + self.tasks: list[FakeTask] = [] + + def retrieve_or_create_bucket(self, name: str) -> FakeBucket: + return self.buckets.setdefault(name, FakeBucket(name)) + + def create_task(self, name: str, profile: str, instancecount: int = 1) -> FakeTask: + task = FakeTask(name, profile, instancecount) + self.tasks.append(task) + return task + + def retrieve_task(self, uuid: str) -> FakeTask: + for task in self.tasks: + if task.uuid == uuid: + return task + raise KeyError(uuid) + + +class _MinCores: + """A minimum-core constraint as the account catalogue offers it.""" + + def __init__(self, count: int) -> None: + self._count = count + + def to_json(self) -> dict[str, object]: + return {"discriminator": "MinimumCoreHardwareConstraint", "coreCount": self._count} + + +@pytest.fixture +def campaign(tmp_path: Path) -> Path: + runs_dir = tmp_path / "RUNS" + (runs_dir / "MESH").mkdir(parents=True) + (runs_dir / "MESH" / "m.med").write_text("mesh", encoding="utf-8") + case_dir = runs_dir / "case0001" + (case_dir / "DATA").mkdir(parents=True) + (case_dir / "DATA" / "setup.xml").write_text("", encoding="utf-8") + (case_dir / "doe_row.csv").write_text("a\n1\n", encoding="utf-8") + (runs_dir / "csauto.toml").write_text("solver = 'code_saturne'\n", encoding="utf-8") + return case_dir + + +def _submit(backend: QarnotBackend, case_dir: Path, **kwargs: Any) -> str: + defaults: dict[str, Any] = { + "argv": ["run", "--case", ".", "-n", "4", "--nt", "1"], + "image": "simvia/code_saturne:9.0", + "nprocs": 4, + "nt": 1, + "observability_globs": ("RESU/*/listing",), + "options": {}, + } + defaults.update(kwargs) + return backend.submit(case_dir, **defaults) + + +def test_submit_returns_the_task_uuid(campaign: Path) -> None: + connection = FakeConnection() + + task_id = _submit(QarnotBackend(connection=connection), campaign) + + assert task_id == "task-0001" + assert connection.tasks[0].submitted is True + + +def test_submit_sets_the_docker_batch_constants(campaign: Path) -> None: + connection = FakeConnection() + + _submit(QarnotBackend(connection=connection), campaign) + + task = connection.tasks[0] + assert task.profile == "docker-batch" + assert task.constants["DOCKER_REPO"] == "simvia/code_saturne" + assert task.constants["DOCKER_TAG"] == "9.0" + assert task.constants["DOCKER_CMD"] == "run --case . -n 4 --nt 1" + + +def test_submit_asks_for_one_instance_not_one_per_process(campaign: Path) -> None: + """Qarnot instances are separate machines. nprocs would run four copies.""" + connection = FakeConnection() + + _submit(QarnotBackend(connection=connection), campaign, nprocs=4) + + assert connection.tasks[0].instancecount == 1 + + +def test_submit_uploads_the_case_inputs_but_not_the_mesh(campaign: Path) -> None: + connection = FakeConnection() + + _submit(QarnotBackend(connection=connection), campaign) + + case_bucket = next(b for b in connection.buckets.values() if b.uuid.endswith("-case0001")) + assert set(case_bucket.files) == {"DATA/setup.xml", "doe_row.csv"} + + +def test_submit_uploads_the_shared_directories_once_per_campaign(campaign: Path) -> None: + connection = FakeConnection() + backend = QarnotBackend(connection=connection) + + _submit(backend, campaign) + shared = next(b for b in connection.buckets.values() if b.uuid.endswith("-shared")) + assert shared.directories == [(str(campaign.parent / "MESH"), "MESH")] + + _submit(backend, campaign) + assert len(shared.directories) == 1, "unchanged shared dirs must not be re-uploaded" + + +def test_the_shared_bucket_is_re_uploaded_when_the_mesh_changes(campaign: Path) -> None: + connection = FakeConnection() + backend = QarnotBackend(connection=connection) + + _submit(backend, campaign) + (campaign.parent / "MESH" / "m.med").write_text("a different mesh", encoding="utf-8") + _submit(backend, campaign) + + shared = next(b for b in connection.buckets.values() if b.uuid.endswith("-shared")) + assert len(shared.directories) == 2 + + +def test_submit_whitelists_only_the_observability_files(campaign: Path) -> None: + connection = FakeConnection() + + _submit(QarnotBackend(connection=connection), campaign) + + task = connection.tasks[0] + assert re.match(task.snapshot_whitelist, "RESU/r1/listing") + assert task.snapshot_calls == [(60, task.snapshot_whitelist)] + + +def test_submit_does_not_snapshot_when_the_adapter_declares_nothing(campaign: Path) -> None: + connection = FakeConnection() + + _submit(QarnotBackend(connection=connection), campaign, observability_globs=()) + + assert connection.tasks[0].snapshot_calls == [] + + +def test_submit_refuses_a_solver_that_builds_no_remote_argv(campaign: Path) -> None: + connection = FakeConnection() + + with pytest.raises(ValueError, match="does not build a remote command"): + _submit(QarnotBackend(connection=connection), campaign, argv=[]) + + +def test_submit_refuses_an_oversized_case(campaign: Path) -> None: + (campaign.parent / "csauto.toml").write_text( + "solver = 'code_saturne'\n[qarnot]\nmax_upload_mb = 1\n", encoding="utf-8" + ) + (campaign / "DATA" / "big.dat").write_text("x" * (2 * 1024 * 1024), encoding="utf-8") + connection = FakeConnection() + + with pytest.raises(ValueError, match="over the 1 MB limit"): + _submit(QarnotBackend(connection=connection), campaign) + + +def test_a_missing_token_is_reported_without_leaking_anything(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("QARNOT_TOKEN", raising=False) + + with pytest.raises(RuntimeError, match="QARNOT_TOKEN"): + QarnotBackend()._connect() + + +class PolledTask(FakeTask): + def __init__(self, state: str, progress: float = 50.0) -> None: + super().__init__("t", "docker-batch", 1) + self.state = state + self.progress = progress + self.execution_time = 12.5 + self.running_core_count = 4 + self.aborted = False + self.downloaded: list[str] = [] + self.results = FakeBucket("out") + self._stdout = "iteration 1\n" + self._stderr = "" + + def fresh_stdout(self, instanceId: int | None = None) -> str: + out, self._stdout = self._stdout, "" + return out + + def fresh_stderr(self, instanceId: int | None = None) -> str: + out, self._stderr = self._stderr, "" + return out + + def download_results(self, output_dir: str, progress: Any = None) -> None: + self.downloaded.append(output_dir) + + def abort(self) -> None: + self.aborted = True + + +def _backend_with(task: PolledTask) -> QarnotBackend: + connection = FakeConnection() + task.uuid = "task-0001" + connection.tasks.append(task) + return QarnotBackend(connection=connection) + + +@pytest.mark.parametrize( + ("qarnot_state", "expected"), + [ + ("Submitted", "PENDING"), + ("PartiallyDispatched", "PENDING"), + ("FullyDispatched", "PENDING"), + ("PartiallyExecuting", "RUNNING"), + ("FullyExecuting", "RUNNING"), + ("UploadingResults", "RUNNING"), + ("DownloadingResults", "RUNNING"), + ("PendingCancel", "RUNNING"), + ("Success", "DONE"), + ("Failure", "FAILED"), + ("Cancelled", "FAILED"), + ("PendingDelete", "FAILED"), + ], +) +def test_poll_translates_every_qarnot_state(qarnot_state: str, expected: str) -> None: + backend = _backend_with(PolledTask(qarnot_state)) + + assert backend.poll("task-0001").status == expected + + +def test_an_unknown_state_is_reported_pending_not_failed() -> None: + """A state name csauto has never seen must not fail a running campaign.""" + backend = _backend_with(PolledTask("SomeNewState")) + + assert backend.poll("task-0001").status == "PENDING" + + +def test_poll_returns_progress_as_a_fraction() -> None: + """Qarnot reports a percentage; BackendState.progress is 0..1.""" + backend = _backend_with(PolledTask("FullyExecuting", progress=50.0)) + + assert backend.poll("task-0001").progress == 0.5 + + +def test_poll_returns_only_the_new_output() -> None: + task = PolledTask("FullyExecuting") + backend = _backend_with(task) + + assert backend.poll("task-0001").stdout_delta == "iteration 1\n" + assert backend.poll("task-0001").stdout_delta == "" + + +def test_poll_reports_the_timing_figures() -> None: + backend = _backend_with(PolledTask("FullyExecuting")) + + state = backend.poll("task-0001") + + assert state.execution_time_s == 12.5 + assert state.running_core_count == 4 + + +def test_cancel_aborts_the_task() -> None: + task = PolledTask("FullyExecuting") + backend = _backend_with(task) + + backend.cancel("task-0001") + + assert task.aborted is True + + +def test_qarnot_is_a_registered_backend() -> None: + from csauto.backends import available_backends + + assert "qarnot" in available_backends() + + +def test_cancel_leaves_a_finished_task_alone() -> None: + """Qarnot refuses to abort a finished task, and Stop must not blow up.""" + task = PolledTask("Success") + backend = _backend_with(task) + + backend.cancel("task-0001") + + assert task.aborted is False + + +def test_cancel_tolerates_a_task_that_finished_mid_call() -> None: + """The task can end between reading its state and aborting it.""" + task = PolledTask("FullyExecuting") + + def abort() -> None: + raise RuntimeError("Invalid operation on non-running task") + + task.abort = abort # type: ignore[method-assign] + backend = _backend_with(task) + + backend.cancel("task-0001") + + +def test_cancel_still_reports_a_real_failure() -> None: + task = PolledTask("FullyExecuting") + + def abort() -> None: + raise RuntimeError("invalid credentials") + + task.abort = abort # type: ignore[method-assign] + backend = _backend_with(task) + + with pytest.raises(RuntimeError, match="invalid credentials"): + backend.cancel("task-0001") + + +def test_submit_demands_enough_cores_for_the_requested_ranks(campaign: Path) -> None: + """Qarnot allocates any machine unless told otherwise. + + -n and --nt only reach code_saturne, inside DOCKER_CMD. Without a hardware + constraint the task can land on a node with fewer cores than the run asks + for, oversubscribing MPI on compute the user pays for. + + The constraint is kept only when the account offers it: Qarnot rejects the + whole submission for one it does not know. + """ + connection = FakeConnection() + connection.all_hardware_constraints = lambda: iter([_MinCores(8)]) + + _submit(QarnotBackend(connection=connection), campaign, nprocs=4, nt=2) + + constraints = [c.to_json() for c in connection.tasks[0].hardware_constraints] + assert constraints == [{"discriminator": "MinimumCoreHardwareConstraint", "coreCount": 8}] + + +def test_submit_never_demands_fewer_than_one_core(campaign: Path) -> None: + connection = FakeConnection() + connection.all_hardware_constraints = lambda: iter([_MinCores(1)]) + + _submit(QarnotBackend(connection=connection), campaign, nprocs=0, nt=0) + + assert connection.tasks[0].hardware_constraints[0].to_json()["coreCount"] == 1 + + +def test_the_core_constraint_is_set_before_submission(campaign: Path) -> None: + """The SDK refuses hardware_constraints once the task is launched.""" + connection = FakeConnection() + + _submit(QarnotBackend(connection=connection), campaign) + + task = connection.tasks[0] + assert task.constraints_set_while_unsubmitted is True + + +def test_submit_defaults_to_flex_scheduling(campaign: Path) -> None: + connection = FakeConnection() + + _submit(QarnotBackend(connection=connection), campaign) + + assert connection.tasks[0].scheduling_type.schedulingType == "Flex" + + +def test_submit_honours_the_chosen_scheduling(campaign: Path) -> None: + connection = FakeConnection() + + _submit(QarnotBackend(connection=connection), campaign, options={"scheduling": "OnDemand"}) + + assert connection.tasks[0].scheduling_type.schedulingType == "OnDemand" + + +def test_submit_rejects_an_unknown_scheduling(campaign: Path) -> None: + connection = FakeConnection() + + with pytest.raises(ValueError, match="Unknown scheduling"): + _submit(QarnotBackend(connection=connection), campaign, options={"scheduling": "Cheap"}) + + +def test_submit_pins_the_chosen_node(campaign: Path) -> None: + connection = FakeConnection() + connection.all_hardware_constraints = lambda: iter([_MinCores(2)]) + + _submit(QarnotBackend(connection=connection), campaign, nprocs=2, nt=1, options={"node": "r640-a"}) + + constraints = [c.to_json() for c in connection.tasks[0].hardware_constraints] + assert constraints == [ + {"discriminator": "MinimumCoreHardwareConstraint", "coreCount": 2}, + {"discriminator": "SpecificHardwareConstraint", "specificationKey": "r640-a"}, + ] + + +def test_submit_without_a_node_asks_only_for_cores(campaign: Path) -> None: + connection = FakeConnection() + connection.all_hardware_constraints = lambda: iter([_MinCores(2)]) + + _submit(QarnotBackend(connection=connection), campaign, nprocs=2, nt=1, options={"node": " "}) + + constraints = [c.to_json() for c in connection.tasks[0].hardware_constraints] + assert constraints == [{"discriminator": "MinimumCoreHardwareConstraint", "coreCount": 2}] + + +def test_the_scheduling_is_set_before_submission(campaign: Path) -> None: + """The SDK refuses scheduling_type once the task is launched.""" + connection = FakeConnection() + + _submit(QarnotBackend(connection=connection), campaign) + + assert connection.tasks[0].scheduling_set_while_unsubmitted is True + + +class _Spec: + """What all_hardware_constraints yields for a node specification.""" + + def __init__(self, key: str) -> None: + self._specification_key = key + + def to_json(self) -> dict[str, object]: + return {"discriminator": "SpecificHardwareConstraint", "specificationKey": self._specification_key} + + +class _Other: + def to_json(self) -> dict[str, object]: + return {"discriminator": "GpuHardwareConstraint"} + + +@pytest.fixture(autouse=True) +def _no_option_cache() -> None: + QarnotBackend.clear_options_cache() + + +def test_launch_options_always_offer_the_scheduling_choices() -> None: + connection = FakeConnection() + connection.all_hardware_constraints = lambda: iter(()) + + catalogue = QarnotBackend(connection=connection).launch_options() + + by_key = {option.key: option for option in catalogue.options} + assert [value for value, _label in by_key["scheduling"].choices] == ["Flex", "OnDemand", "Reserved"] + assert by_key["scheduling"].default == "Flex" + + +def test_launch_options_list_the_account_node_types() -> None: + connection = FakeConnection() + connection.all_hardware_constraints = lambda: iter([_Spec("r640-a"), _Other(), _Spec("r740-b")]) + + catalogue = QarnotBackend(connection=connection).launch_options() + + node = next(option for option in catalogue.options if option.key == "node") + assert [value for value, _label in node.choices] == ["", "r640-a", "r740-b"] + assert node.default == "" + assert catalogue.degraded is False + + +def test_launch_options_degrade_instead_of_raising() -> None: + """The dialog must still open, and the launch must still be possible.""" + + def explode(): + raise RuntimeError("qarnot is down") + + connection = FakeConnection() + connection.all_hardware_constraints = explode + + catalogue = QarnotBackend(connection=connection).launch_options() + + node = next(option for option in catalogue.options if option.key == "node") + assert catalogue.degraded is True + assert [value for value, _label in node.choices] == [""] + + +def test_launch_options_degrade_when_there_is_no_token(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("QARNOT_TOKEN", raising=False) + + catalogue = QarnotBackend().launch_options() + + assert catalogue.degraded is True + assert [option.key for option in catalogue.options] == ["scheduling", "node"] + + +def test_the_node_list_is_cached() -> None: + calls: list[int] = [] + + def counted(): + calls.append(1) + return iter([_Spec("r640-a")]) + + connection = FakeConnection() + connection.all_hardware_constraints = counted + backend = QarnotBackend(connection=connection) + + backend.launch_options() + backend.launch_options() + + assert len(calls) == 1 + + +def test_a_degraded_result_is_not_cached() -> None: + """A transient outage must not hide the node list for ten minutes.""" + states = iter([RuntimeError("down"), [_Spec("r640-a")]]) + + def flaky(): + value = next(states) + if isinstance(value, Exception): + raise value + return iter(value) + + connection = FakeConnection() + connection.all_hardware_constraints = flaky + backend = QarnotBackend(connection=connection) + + assert backend.launch_options().degraded is True + assert backend.launch_options().degraded is False + + +def test_submit_omits_a_core_constraint_the_account_does_not_offer(campaign: Path) -> None: + """Qarnot rejects an invented constraint: "Some constraints don't exist".""" + connection = FakeConnection() + connection.all_hardware_constraints = lambda: iter([_MinCores(4), _Spec("r640-a")]) + + _submit(QarnotBackend(connection=connection), campaign, nprocs=1, nt=1) + + assert connection.tasks[0].hardware_constraints == [] + + +def test_submit_keeps_a_core_constraint_the_account_offers(campaign: Path) -> None: + connection = FakeConnection() + connection.all_hardware_constraints = lambda: iter([_MinCores(4)]) + + _submit(QarnotBackend(connection=connection), campaign, nprocs=2, nt=2) + + constraints = [c.to_json() for c in connection.tasks[0].hardware_constraints] + assert constraints == [{"discriminator": "MinimumCoreHardwareConstraint", "coreCount": 4}] + + +def test_submit_omits_the_core_constraint_when_the_catalogue_is_unreachable(campaign: Path) -> None: + """Unverifiable is not the same as valid; sending it would fail the launch.""" + + def explode(): + raise RuntimeError("qarnot is down") + + connection = FakeConnection() + connection.all_hardware_constraints = explode + + _submit(QarnotBackend(connection=connection), campaign, nprocs=4, nt=1) + + assert connection.tasks[0].hardware_constraints == [] + + +def test_submit_always_keeps_the_chosen_node(campaign: Path) -> None: + """The node came from the catalogue, so it is valid by construction.""" + + def explode(): + raise RuntimeError("qarnot is down") + + connection = FakeConnection() + connection.all_hardware_constraints = explode + + _submit(QarnotBackend(connection=connection), campaign, nprocs=4, nt=1, options={"node": "r640-a"}) + + constraints = [c.to_json() for c in connection.tasks[0].hardware_constraints] + assert constraints == [{"discriminator": "SpecificHardwareConstraint", "specificationKey": "r640-a"}] + + +def test_only_the_results_directory_comes_back(campaign: Path) -> None: + """The task is handed the shared mesh; it must not hand it back every run.""" + import re + + connection = FakeConnection() + + _submit(QarnotBackend(connection=connection), campaign) + + pattern = connection.tasks[0].results_whitelist + assert re.match(pattern, "RESU/r1/listing") + assert not re.match(pattern, "MESH/mesh1.med") + + +def test_sync_downloads_into_the_case_directory(tmp_path: Path) -> None: + task = PolledTask("FullyExecuting") + backend = _backend_with(task) + + backend.sync("task-0001", tmp_path / "case0001") + + assert task.downloaded == [str(tmp_path / "case0001")] + + +def test_fetch_final_pulls_the_whole_results_bucket(tmp_path: Path) -> None: + task = PolledTask("Success") + pulled: list[str] = [] + task.results.get_all_files = lambda output_dir, progress=None: pulled.append(output_dir) # type: ignore[attr-defined] + backend = _backend_with(task) + + backend.fetch_final("task-0001", tmp_path / "case0001") + + assert pulled == [str(tmp_path / "case0001")] diff --git a/tests/unit/test_qarnot_support.py b/tests/unit/test_qarnot_support.py new file mode 100644 index 0000000..0d297f5 --- /dev/null +++ b/tests/unit/test_qarnot_support.py @@ -0,0 +1,208 @@ +"""Pure Qarnot logic: no SDK, no network, no account.""" + +from __future__ import annotations + +import re +from pathlib import Path + +import pytest + +from csauto.backends.qarnot_support import ( + bucket_name, + directory_signature, + ensure_upload_within, + snapshot_whitelist, + split_image, + upload_plan, +) + + +def test_bucket_name_is_s3_legal(tmp_path: Path) -> None: + campaign = tmp_path / "My_Study 2026" + campaign.mkdir() + + name = bucket_name(campaign) + + assert re.fullmatch(r"[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]", name), name + assert "_" not in name + assert 3 <= len(name) <= 63 + + +def test_bucket_name_is_stable_and_collision_free(tmp_path: Path) -> None: + """Two campaigns that normalise to the same slug must not share a bucket.""" + first = tmp_path / "a" / "My_Study" + second = tmp_path / "b" / "My-Study" + first.mkdir(parents=True) + second.mkdir(parents=True) + + assert bucket_name(first) == bucket_name(first) + assert bucket_name(first) != bucket_name(second) + + +def test_bucket_name_appends_a_suffix(tmp_path: Path) -> None: + assert bucket_name(tmp_path, "shared").endswith("-shared") + + +def test_snapshot_whitelist_is_a_regex_not_a_glob() -> None: + """Qarnot filters are regular expressions; the adapter declares globs.""" + pattern = snapshot_whitelist(("RESU/*/listing",)) + + assert re.match(pattern, "RESU/20260914-1200/listing") + assert not re.match(pattern, "RESU/a/b/listing"), "* must not cross a slash" + + +def test_snapshot_whitelist_escapes_regex_metacharacters() -> None: + pattern = snapshot_whitelist(("RESU/*/run_status.*",)) + + assert re.match(pattern, "RESU/r1/run_status.failed") + assert not re.match(pattern, "RESU/r1/run_statusXfailed") + + +def test_snapshot_whitelist_joins_several_patterns() -> None: + pattern = snapshot_whitelist(("a/*.csv", "b/*.log")) + + assert re.match(pattern, "a/x.csv") + assert re.match(pattern, "b/y.log") + assert not re.match(pattern, "c/z.txt") + + +def test_snapshot_whitelist_of_nothing_is_empty() -> None: + assert snapshot_whitelist(()) == "" + + +def test_upload_plan_skips_shared_and_results_directories(tmp_path: Path) -> None: + case_dir = tmp_path / "case0001" + (case_dir / "DATA").mkdir(parents=True) + (case_dir / "DATA" / "setup.xml").write_text("", encoding="utf-8") + (case_dir / "MESH").mkdir() + (case_dir / "MESH" / "huge.med").write_text("x" * 100, encoding="utf-8") + (case_dir / "RESU").mkdir() + (case_dir / "RESU" / "old.log").write_text("y", encoding="utf-8") + (case_dir / ".csauto.history.jsonl").write_text("{}", encoding="utf-8") + (case_dir / "doe_row.csv").write_text("a,b\n1,2\n", encoding="utf-8") + + plan = upload_plan(case_dir, shared_dir_names=("MESH", "POST"), results_dirname="RESU") + + assert set(plan.paths) == {"DATA/setup.xml", "doe_row.csv"} + assert plan.total_bytes == len("") + len("a,b\n1,2\n") + + +def test_ensure_upload_within_refuses_an_oversized_case() -> None: + from csauto.backends.qarnot_support import UploadPlan + + plan = UploadPlan(paths=("big.med",), total_bytes=3 * 1024 * 1024) + + with pytest.raises(ValueError, match=r"3\.0 MB"): + ensure_upload_within(plan, max_mb=2) + + +def test_ensure_upload_within_accepts_a_small_case() -> None: + from csauto.backends.qarnot_support import UploadPlan + + ensure_upload_within(UploadPlan(paths=("a",), total_bytes=10), max_mb=1) + + +def test_directory_signature_changes_when_a_file_changes(tmp_path: Path) -> None: + shared = tmp_path / "MESH" + shared.mkdir() + mesh = shared / "m.med" + mesh.write_text("one", encoding="utf-8") + + before = directory_signature([shared]) + mesh.write_text("one and more", encoding="utf-8") + + assert directory_signature([shared]) != before + + +def test_directory_signature_is_stable_when_nothing_changes(tmp_path: Path) -> None: + shared = tmp_path / "MESH" + shared.mkdir() + (shared / "m.med").write_text("one", encoding="utf-8") + + assert directory_signature([shared]) == directory_signature([shared]) + + +@pytest.mark.parametrize( + ("image", "expected"), + [ + ("simvia/code_saturne", ("simvia/code_saturne", "latest")), + ("simvia/code_saturne:9.0", ("simvia/code_saturne", "9.0")), + ("ubuntu", ("ubuntu", "latest")), + ], +) +def test_split_image(image: str, expected: tuple[str, str]) -> None: + assert split_image(image) == expected + + +def test_split_image_refuses_an_empty_image() -> None: + with pytest.raises(ValueError, match="no docker image"): + split_image("") + + +def test_minimum_core_constraint_shape() -> None: + from csauto.backends.qarnot_support import minimum_core_constraint + + assert minimum_core_constraint(4).to_json() == { + "discriminator": "MinimumCoreHardwareConstraint", + "coreCount": 4, + } + + +def test_minimum_core_constraint_matches_the_sdk() -> None: + """Guards the hand-written discriminator against an SDK rename. + + The constraint is built without importing the SDK, so the unit tests stay + runnable without the optional extra. This test is the price of that: when + the SDK is installed, the two must agree. + """ + sdk = pytest.importorskip("qarnot.hardware_constraint") + + from csauto.backends.qarnot_support import minimum_core_constraint + + assert minimum_core_constraint(7).to_json() == sdk.MinimumCoreHardware(7).to_json() + + +def test_scheduling_choices_are_the_three_qarnot_values() -> None: + from csauto.backends.qarnot_support import SCHEDULING_CHOICES + + assert [value for value, _label in SCHEDULING_CHOICES] == ["Flex", "OnDemand", "Reserved"] + + +def test_scheduling_choice_carries_the_attribute_the_sdk_reads() -> None: + """Task._to_json reads `.schedulingType` off whatever object is set.""" + from csauto.backends.qarnot_support import scheduling_choice + + assert scheduling_choice("OnDemand").schedulingType == "OnDemand" + + +def test_scheduling_choice_matches_the_sdk() -> None: + sdk = pytest.importorskip("qarnot.scheduling_type") + + from csauto.backends.qarnot_support import SCHEDULING_CHOICES, scheduling_choice + + for value, _label in SCHEDULING_CHOICES: + assert scheduling_choice(value).schedulingType == sdk.SchedulingType.from_string(value).schedulingType + + +def test_scheduling_choice_rejects_an_unknown_value() -> None: + from csauto.backends.qarnot_support import scheduling_choice + + with pytest.raises(ValueError, match="Nope"): + scheduling_choice("Nope") + + +def test_specific_hardware_constraint_shape() -> None: + from csauto.backends.qarnot_support import specific_hardware_constraint + + assert specific_hardware_constraint("r640-a").to_json() == { + "discriminator": "SpecificHardwareConstraint", + "specificationKey": "r640-a", + } + + +def test_specific_hardware_constraint_matches_the_sdk() -> None: + sdk = pytest.importorskip("qarnot.hardware_constraint") + + from csauto.backends.qarnot_support import specific_hardware_constraint + + assert specific_hardware_constraint("r640-a").to_json() == sdk.SpecificHardware("r640-a").to_json() diff --git a/tests/unit/test_registry.py b/tests/unit/test_registry.py index 42b12c0..0b1bc44 100644 --- a/tests/unit/test_registry.py +++ b/tests/unit/test_registry.py @@ -107,3 +107,39 @@ def test_registry_transaction_saves_normally_without_an_exception(runs_dir: Path update_case(registry, "case0001", status="DONE") assert load_registry(runs_dir)["case0001"]["status"] == "DONE" + + +def test_case_records_drops_reserved_keys() -> None: + from csauto.registry import case_records + + registry = { + "_backend": {"last_sync": "2026-09-14T10:00:00"}, + "case0001": {"case_id": "case0001", "status": "PREPARED"}, + } + + assert list(case_records(registry)) == ["case0001"] + + +def test_registry_lock_is_reentrant_within_a_thread(runs_dir: Path) -> None: + """flock is per descriptor: reopening the file deadlocks against ourselves. + + REGISTRY_THREAD_LOCK is an RLock, which advertises re-entrancy, so a nested + read looks safe and instead froze the whole process for ever. + """ + import threading + + from csauto.registry import registry_transaction, save_registry + + save_registry(runs_dir, {"case0001": {"case_id": "case0001", "status": "PREPARED"}}) + done = threading.Event() + seen: list[str] = [] + + def nested() -> None: + with registry_transaction(runs_dir): + seen.append(load_registry(runs_dir)["case0001"]["status"]) + done.set() + + threading.Thread(target=nested, daemon=True).start() + + assert done.wait(timeout=5), "a nested registry read deadlocked" + assert seen == ["PREPARED"] diff --git a/tests/unit/test_residuals.py b/tests/unit/test_residuals.py new file mode 100644 index 0000000..e758c75 --- /dev/null +++ b/tests/unit/test_residuals.py @@ -0,0 +1,64 @@ +"""Residual parsing, independent of where a run happened.""" + +from __future__ import annotations + +from pathlib import Path + + +def test_live_residual_rows_carry_the_real_time_step(tmp_path: Path) -> None: + """The abscissa is the solver's time step, not a count of printed blocks. + + code_saturne prints convergence at its listing frequency, so the Nth block + is almost never iteration N; plotting the block index squashes the curve + onto a wrong, far too short axis. + """ + from csauto.residuals import parse_residuals_from_log + + log = tmp_path / "run_solver.log" + log.write_text( + "\n".join( + [ + " INSTANT 10.000000000 TIME STEP NUMBER 1000", + " ** Information on convergence", + "------------------------------------------------------------", + " Variable Rhs norm N_iter Norm. residual Drift", + " Velocity 3.9e+02 5 1.0e-03 1.0", + "", + " INSTANT 15.000000000 TIME STEP NUMBER 1500", + " ** Information on convergence", + "------------------------------------------------------------", + " Variable Rhs norm N_iter Norm. residual Drift", + " Velocity 2.1e+02 4 5.0e-04 0.5", + "", + ] + ), + encoding="utf-8", + ) + + _fields, rows = parse_residuals_from_log(log) + + assert [row["iteration"] for row in rows] == ["1000", "1500"] + + +def test_live_residual_rows_fall_back_to_a_block_count(tmp_path: Path) -> None: + """A log without the time step markers still plots, on the old axis.""" + from csauto.residuals import parse_residuals_from_log + + log = tmp_path / "run_solver.log" + log.write_text( + "\n".join( + [ + " Variable Rhs norm N_iter Norm. residual Drift", + " Velocity 3.9e+02 5 1.0e-03 1.0", + "", + " Variable Rhs norm N_iter Norm. residual Drift", + " Velocity 2.1e+02 4 5.0e-04 0.5", + "", + ] + ), + encoding="utf-8", + ) + + _fields, rows = parse_residuals_from_log(log) + + assert [row["iteration"] for row in rows] == ["1", "2"] diff --git a/tests/unit/test_solvers.py b/tests/unit/test_solvers.py index 6dd4b36..36038a1 100644 --- a/tests/unit/test_solvers.py +++ b/tests/unit/test_solvers.py @@ -1,6 +1,7 @@ from __future__ import annotations from datetime import datetime +from pathlib import Path import pytest @@ -446,3 +447,181 @@ def test_code_saturne_detect_outcome_ignores_a_stale_failure_marker(tmp_path) -> start_time = datetime.fromtimestamp(time.time() - 60).isoformat(timespec="seconds") assert adapter.detect_outcome(case_dir, start_time) == STATUS_DONE + + +def test_code_saturne_declares_observability_globs() -> None: + from csauto.solvers.code_saturne import CodeSaturneAdapter + + globs = CodeSaturneAdapter().observability_globs + + assert "RESU/*/listing" in globs + assert all(not pattern.startswith("/") for pattern in globs), "patterns are relative to the case dir" + + +def test_an_adapter_that_declares_nothing_has_no_observability_globs() -> None: + from csauto.solvers.base import SolverAdapterBase + + assert SolverAdapterBase.observability_globs == () + + +def test_an_adapter_prepares_nothing_for_a_remote_case_by_default(tmp_path: Path) -> None: + from csauto.solvers.stub import StubAdapter + + before = sorted(p.name for p in tmp_path.iterdir()) + StubAdapter().prepare_remote_case(tmp_path) + + assert sorted(p.name for p in tmp_path.iterdir()) == before + + +def _setup_with_meshes(case_dir: Path) -> Path: + setup = case_dir / "DATA" / "setup.xml" + setup.parent.mkdir(parents=True, exist_ok=True) + setup.write_text( + '\n' + "" + '\n', + encoding="utf-8", + ) + return setup + + +def test_code_saturne_points_setup_at_the_mesh_dir_inside_the_case(tmp_path: Path) -> None: + """A backend puts the shared dirs inside the case; code_saturne looks beside it.""" + import xml.etree.ElementTree as ET + + from csauto.solvers.code_saturne import CodeSaturneAdapter + + setup = _setup_with_meshes(tmp_path) + + CodeSaturneAdapter().prepare_remote_case(tmp_path) + + node = ET.parse(setup).getroot().find(".//solution_domain/meshes_list/meshdir") + assert node is not None + assert node.get("name") == "MESH" + + +def test_code_saturne_leaves_an_existing_mesh_dir_alone(tmp_path: Path) -> None: + import xml.etree.ElementTree as ET + + from csauto.solvers.code_saturne import CodeSaturneAdapter + + setup = _setup_with_meshes(tmp_path) + setup.write_text( + setup.read_text(encoding="utf-8").replace("", ''), + encoding="utf-8", + ) + + CodeSaturneAdapter().prepare_remote_case(tmp_path) + + nodes = ET.parse(setup).getroot().findall(".//solution_domain/meshes_list/meshdir") + assert [n.get("name") for n in nodes] == ["ELSEWHERE"] + + +def test_code_saturne_prepare_is_idempotent(tmp_path: Path) -> None: + import xml.etree.ElementTree as ET + + from csauto.solvers.code_saturne import CodeSaturneAdapter + + setup = _setup_with_meshes(tmp_path) + adapter = CodeSaturneAdapter() + + adapter.prepare_remote_case(tmp_path) + adapter.prepare_remote_case(tmp_path) + + nodes = ET.parse(setup).getroot().findall(".//solution_domain/meshes_list/meshdir") + assert len(nodes) == 1 + + +def test_code_saturne_prepare_survives_a_case_without_a_setup(tmp_path: Path) -> None: + """Preparing must never be the thing that fails a launch.""" + from csauto.solvers.code_saturne import CodeSaturneAdapter + + CodeSaturneAdapter().prepare_remote_case(tmp_path) + + +def _case_with_stale_running_marker(tmp_path: Path) -> Path: + """A finished run whose progress marker was left behind mid-run. + + A backend sync downloads run_status.running during the run and only ever + adds files, so the marker survives the end of the calculation and keeps + reporting the iteration it was captured at. + """ + run_dir = tmp_path / "RESU" / "20260101-0000" + run_dir.mkdir(parents=True) + (run_dir / "run_status.running").write_text("time step: 9479\n", encoding="utf-8") + (run_dir / "run_solver.log").write_text( + " INSTANT 100.000000000 TIME STEP NUMBER 10000\n END OF CALCULATION\n", + encoding="utf-8", + ) + return tmp_path + + +def test_progress_of_a_finished_run_ignores_the_running_marker(tmp_path: Path) -> None: + from csauto.solvers.code_saturne import CodeSaturneAdapter + + case_dir = _case_with_stale_running_marker(tmp_path) + + assert CodeSaturneAdapter().read_progress(case_dir, running=False) == 10000 + + +def test_progress_of_a_live_run_still_prefers_the_running_marker(tmp_path: Path) -> None: + """While a run is in progress the marker is fresher than the log.""" + from csauto.solvers.code_saturne import CodeSaturneAdapter + + case_dir = _case_with_stale_running_marker(tmp_path) + + assert CodeSaturneAdapter().read_progress(case_dir) == 9479 + + +def _finished_looking_log() -> str: + return "===============================\n FINAL STAGE OF THE CALCULATION\n END OF CALCULATION\n" + + +def test_an_explicit_failure_marker_beats_a_log_that_looks_finished(tmp_path: Path) -> None: + """code_saturne prints its final stage even when it then aborts. + + The runaway-computation check kills the solver after that banner, and the + real message goes to `error`, not to the log, so the log heuristic alone + calls a diverged run a success. The marker the solver writes is explicit. + """ + from csauto.solvers.code_saturne import CodeSaturneAdapter + + run_dir = tmp_path / "RESU" / "20260101-0000" + run_dir.mkdir(parents=True) + (run_dir / "run_solver.log").write_text(_finished_looking_log(), encoding="utf-8") + (run_dir / "run_status.failed").write_text("", encoding="utf-8") + + assert CodeSaturneAdapter().detect_outcome(tmp_path) == "FAILED" + + +def test_a_finished_run_without_a_failure_marker_is_done(tmp_path: Path) -> None: + from csauto.solvers.code_saturne import CodeSaturneAdapter + + run_dir = tmp_path / "RESU" / "20260101-0000" + run_dir.mkdir(parents=True) + (run_dir / "run_solver.log").write_text(_finished_looking_log(), encoding="utf-8") + + assert CodeSaturneAdapter().detect_outcome(tmp_path) == "DONE" + + +def test_a_failure_marker_from_an_earlier_run_does_not_win(tmp_path: Path) -> None: + """Only this run's marker counts; an old one must not fail a good run.""" + import os + from datetime import datetime, timedelta + + from csauto.solvers.code_saturne import CodeSaturneAdapter + + now = datetime.now() + old_dir = tmp_path / "RESU" / "old-run" + old_dir.mkdir(parents=True) + stale = old_dir / "run_status.failed" + stale.write_text("", encoding="utf-8") + long_ago = (now - timedelta(days=1)).timestamp() + os.utime(stale, (long_ago, long_ago)) + + run_dir = tmp_path / "RESU" / "this-run" + run_dir.mkdir(parents=True) + (run_dir / "run_solver.log").write_text(_finished_looking_log(), encoding="utf-8") + + started = (now - timedelta(minutes=1)).isoformat(timespec="seconds") + assert CodeSaturneAdapter().detect_outcome(tmp_path, started) == "DONE" diff --git a/tests/web/test_api.py b/tests/web/test_api.py index 8cbc934..f1962b2 100644 --- a/tests/web/test_api.py +++ b/tests/web/test_api.py @@ -1322,3 +1322,228 @@ def test_run_without_restart_is_not_blocked_by_the_restart_guard(aster_env) -> N # A launch failure (no runtime available in CI) is acceptable here; # a capability rejection is not. assert "does not support restart" not in exc.read().decode("utf-8") + + +def test_the_server_runs_a_backend_sync_pass(runs_dir: Path, case_factory, registry_factory, monkeypatch) -> None: + """The loop lives outside refresh_status, which runs about twice a second. + + Uses TestClient as a context manager on purpose: the shared _start_server + helper does not, so the FastAPI lifespan (and therefore the loop) never + starts there. + """ + pytest.importorskip("fastapi") + from fastapi.testclient import TestClient + + calls = {"count": 0} + + def fake_sync(_runs_dir, **_kwargs): + calls["count"] += 1 + return 0 + + monkeypatch.setattr("csauto.backend_sync.sync_backend_cases", fake_sync) + case_dir = case_factory(runs_dir, "case0001") + registry_factory(runs_dir, "case0001", case_dir, status="PREPARED") + + app = create_fastapi_app(runs_dir, backend_poll_interval_s=1) + with TestClient(app) as client: + client.get("/api/status") + deadline = time.time() + 8.0 + while time.time() < deadline and calls["count"] == 0: + time.sleep(0.2) + + assert calls["count"] >= 1 + + +def test_kill_cancels_a_backend_task(runs_dir: Path, case_factory, monkeypatch) -> None: + from csauto.backends.fake import FakeBackend + from csauto.web_support import kill_case + + case_dir = case_factory(runs_dir, "case0001") + backend = FakeBackend(script=["RUNNING"]) + task_id = backend.submit(case_dir, ["run"], "img", 1, 1) + monkeypatch.setattr("csauto.backends.get_backend", lambda _name: backend) + save_registry( + runs_dir, + { + "case0001": { + "case_id": "case0001", + "path": str(case_dir), + "status": "RUNNING", + "backend": "fake", + "task_id": task_id, + "pid": None, + } + }, + ) + + kill_case(runs_dir, "case0001", actor=None, job_id_patterns=()) + + assert backend.poll(task_id).status == "FAILED" + assert load_registry(runs_dir)["case0001"]["status"] == "FAILED" + + +def test_app_config_lists_the_execution_backends(web_env) -> None: + """The dashboard asks what is on offer; it never hardcodes a provider name.""" + base_url, _case_dir = web_env + _status, body = _http_get(f"{base_url}/api/app_config") + data = json.loads(body) + + assert "qarnot" in data["backends"] + assert "fake" in data["backends"] + + +def test_app_config_never_returns_a_credential(web_env) -> None: + base_url, _case_dir = web_env + _status, body = _http_get(f"{base_url}/api/app_config") + + assert "token" not in body.lower() + assert "secret" not in body.lower() + + +def test_run_case_passes_the_chosen_backend_through(monkeypatch, web_env) -> None: + """The runtime is chosen per launch, so the choice must reach run_cases.""" + base_url, _case_dir = web_env + seen: dict[str, object] = {} + + def fake_run_cases(*args, **kwargs): + seen.update(kwargs) + + monkeypatch.setattr("csauto.fastapi_routes.actions.run_cases", fake_run_cases) + + status, _body = _http_post( + f"{base_url}/api/run_case", + {"cases": ["case0001"], "n": 1, "nt": 1, "backend": "fake"}, + ) + + assert status == 200 + assert seen["backend"] == "fake" + + +def test_run_case_defaults_to_no_backend(monkeypatch, web_env) -> None: + """Omitting the field must keep the local behaviour exactly as before.""" + base_url, _case_dir = web_env + seen: dict[str, object] = {} + + def fake_run_cases(*args, **kwargs): + seen.update(kwargs) + + monkeypatch.setattr("csauto.fastapi_routes.actions.run_cases", fake_run_cases) + + _http_post(f"{base_url}/api/run_case", {"cases": ["case0001"], "n": 1, "nt": 1}) + + assert seen.get("backend") is None + + +def test_run_case_rejects_an_unknown_backend(web_env) -> None: + base_url, _case_dir = web_env + + with pytest.raises(HTTPError) as excinfo: + _http_post( + f"{base_url}/api/run_case", + {"cases": ["case0001"], "n": 1, "nt": 1, "backend": "not-a-backend"}, + ) + + assert excinfo.value.code == 400 + assert "not-a-backend" in json.loads(excinfo.value.read())["detail"] + + +def test_run_case_forwards_the_launch_options(monkeypatch, web_env) -> None: + base_url, _case_dir = web_env + seen: dict[str, object] = {} + + def fake_run_cases(*args, **kwargs): + seen.update(kwargs) + + monkeypatch.setattr("csauto.fastapi_routes.actions.run_cases", fake_run_cases) + + status, _body = _http_post( + f"{base_url}/api/run_case", + {"cases": ["case0001"], "n": 1, "nt": 1, "backend": "fake", "options": {"speed": "fast"}}, + ) + + assert status == 200 + assert seen["options"] == {"speed": "fast"} + + +def test_run_case_without_options_forwards_none(monkeypatch, web_env) -> None: + base_url, _case_dir = web_env + seen: dict[str, object] = {} + + def fake_run_cases(*args, **kwargs): + seen.update(kwargs) + + monkeypatch.setattr("csauto.fastapi_routes.actions.run_cases", fake_run_cases) + + _http_post(f"{base_url}/api/run_case", {"cases": ["case0001"], "n": 1, "nt": 1}) + + assert seen.get("options") is None + + +def test_launch_options_returns_a_backend_catalogue(web_env) -> None: + base_url, _case_dir = web_env + _status, body = _http_get(f"{base_url}/api/launch_options?backend=fake") + data = json.loads(body) + + assert data["backend"] == "fake" + assert data["degraded"] is False + assert data["options"][0]["key"] == "speed" + assert data["options"][0]["default"] == "slow" + assert data["options"][0]["choices"] == [["slow", "Slow"], ["fast", "Fast"]] + + +def test_launch_options_rejects_an_unknown_backend(web_env) -> None: + base_url, _case_dir = web_env + + with pytest.raises(HTTPError) as excinfo: + _http_get(f"{base_url}/api/launch_options?backend=not-a-backend") + + assert excinfo.value.code == 400 + + +def test_launch_options_reports_a_backend_that_fails_as_degraded(monkeypatch, web_env) -> None: + """The dialog must open even when the provider is unreachable.""" + base_url, _case_dir = web_env + + class Broken: + name = "fake" + + def launch_options(self): + raise RuntimeError("provider is down") + + monkeypatch.setattr("csauto.backends.get_backend", lambda _name: Broken()) + + _status, body = _http_get(f"{base_url}/api/launch_options?backend=fake") + data = json.loads(body) + + assert data["degraded"] is True + assert data["options"] == [] + + +def test_launch_options_never_returns_a_credential(web_env) -> None: + base_url, _case_dir = web_env + _status, body = _http_get(f"{base_url}/api/launch_options?backend=fake") + + assert "token" not in body.lower() + assert "secret" not in body.lower() + + +def test_sync_backends_route_reports_whether_it_ran(web_env) -> None: + base_url, _case_dir = web_env + + status, body = _http_post(f"{base_url}/api/sync_backends", {}) + data = json.loads(body) + + assert status == 200 + assert data["status"] == "ok" + assert isinstance(data["synced"], bool) + + +def test_sync_backends_route_is_throttled(web_env) -> None: + """The Refresh button is clickable as fast as a user likes.""" + base_url, _case_dir = web_env + + _status, first = _http_post(f"{base_url}/api/sync_backends", {}) + _status, second = _http_post(f"{base_url}/api/sync_backends", {}) + + assert json.loads(first)["synced"] is True + assert json.loads(second)["synced"] is False diff --git a/tests/web/test_fastapi_server.py b/tests/web/test_fastapi_server.py index 63bafb8..baaffd9 100644 --- a/tests/web/test_fastapi_server.py +++ b/tests/web/test_fastapi_server.py @@ -302,7 +302,7 @@ def test_fastapi_server_cleanup_and_kill_error_paths(fastapi_server_env) -> None _http_post(f"{base_url}/api/kill_case", {"cases": ["case0001"]}) assert excinfo.value.code == 500 payload = json.loads(excinfo.value.read().decode("utf-8")) - assert payload["detail"] == "case0001: No PID, container_id or job_id for case0001" + assert payload["detail"] == "case0001: No PID, container_id, job_id or task_id for case0001" def test_fastapi_server_open_gui_without_display_returns_500(fastapi_server_env) -> None: