From fe82cb29a31bb0ebc4f6a2ee033bddf1abf68f98 Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Sat, 3 Oct 2026 11:11:25 +0100 Subject: [PATCH 1/4] Record browser receipts per commit so frontend PRs stop conflicting Every commit touching user-visible frontend source rewrote the single frontend-modern/browser-verification.json, so any two open frontend pull requests conflicted as soon as either merged. A merge could not repair the loser: concluding it staged main's frontend files as if the author had changed them, and the guard then demanded a receipt for files the author never ran. The only honest recovery was a rebuild on the new main and a fresh browser pass, as #2415 -> #2418 -> #2433 showed on 2-3 October. Each commit now adds its own receipt under frontend-modern/browser-verification/, named by the fingerprint of the content it verified. Receipts are only ever added, so git merges them unaided. The guard keeps every binding it had: the commit's own parent, the exact changed paths and their content digests, a desktop and a narrow viewport, non-empty routes, states and interactions, and the range check that each changed file's final content matches a receipt from a non-merge commit in the range. Concluding a merge no longer counts content a merged parent already carries as the commit's own change. Merged content that neither parent holds is flagged and still needs a follow-up receipt in range mode, and a merge commit may not record one. The shared file is retired: no commit may write it again. A commit that modified it while its own parent still carried it stays valid evidence in range mode, so a pull request that still rewrites it is repaired by merging main and taking main's copy rather than by a rebuild. The file is deleted in a change of its own, because deleting it here would conflict with every pull request that still rewrites it. --write-template writes the skeleton at the path the staged content requires. --prune deletes receipts already on main. A change that records a receipt may not delete one: git pairs the two as a rename, which conflicts with any other change deleting the same receipt. --- .gitleaks.toml | 4 +- .husky/pre-commit | 5 +- .../CANONICAL_DEVELOPMENT_PROTOCOL.md | 56 +- .../browser-verification/README.md | 25 + .../browser_verification_guard.py | 727 ++++++++++++++---- .../browser_verification_guard_test.py | 505 +++++++++++- 6 files changed, 1140 insertions(+), 182 deletions(-) create mode 100644 frontend-modern/browser-verification/README.md diff --git a/.gitleaks.toml b/.gitleaks.toml index 47fd4dce94..22ab7b190d 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -16,8 +16,10 @@ paths = [ '''_test\.go$''', '''_test\.ts$''', '''tests/integration/''', - # Generated receipt contains required SHA-256 content digests + # Generated receipts contain required SHA-256 content digests. The single + # shared receipt is retired but remains in history. '''^frontend-modern/browser-verification\.json$''', + '''^frontend-modern/browser-verification/[0-9a-f]{16}\.json$''', # tmp/ is gitignored but shows up in --no-git scans '''^tmp/''', ] diff --git a/.husky/pre-commit b/.husky/pre-commit index 3b10bd9594..dc6ea0c567 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -68,8 +68,9 @@ staged_files_match() { } # Normalize staged source before any guard hashes or audits it. The browser -# verification receipt binds to exact frontend bytes, so formatting after its -# guard can produce a commit that passed locally but correctly fails in CI. +# verification receipt binds to exact frontend bytes and is named by their +# fingerprint, so formatting after its guard can produce a commit that passed +# locally but correctly fails in CI. python3 scripts/release_control/format_staged_go.py python3 scripts/release_control/format_staged_frontend.py diff --git a/docs/release-control/v6/internal/CANONICAL_DEVELOPMENT_PROTOCOL.md b/docs/release-control/v6/internal/CANONICAL_DEVELOPMENT_PROTOCOL.md index 8b27e36192..d4b723cd58 100644 --- a/docs/release-control/v6/internal/CANONICAL_DEVELOPMENT_PROTOCOL.md +++ b/docs/release-control/v6/internal/CANONICAL_DEVELOPMENT_PROTOCOL.md @@ -231,10 +231,10 @@ misaligned, off-screen, or otherwise unusable fails this gate. Known browser defects in the changed journey remain part of the current task and block the commit unless the user explicitly narrows the requested scope. -The durable proof boundary is -`frontend-modern/browser-verification.json`. Every commit that changes +The durable proof boundary is one receipt file per commit under +`frontend-modern/browser-verification/`. Every commit that changes user-visible source under `frontend-modern/src/` or the frontend HTML entry -must update that receipt with: +must add its own receipt there with: 1. `base_sha`, equal to the parent revision whose current build was exercised 2. the exact changed user-visible frontend source paths and SHA-256 digests of @@ -245,13 +245,55 @@ must update that receipt with: 6. the interactions exercised 7. a UTC verification timestamp and `result: "passed"` +The receipt's file name is fixed by what it verified, not chosen: the first 16 +hexadecimal characters of the SHA-256 of its `content_sha256` map, serialized +as compact JSON with sorted keys, followed by `.json`. A commit records exactly +one receipt and never edits another commit's. + +Receipts are separate files because a shared one made every pair of open +frontend pull requests conflict. Until 2026-10-03 each frontend commit rewrote +`frontend-modern/browser-verification.json`, so the first pull request to merge +left every other one conflicting, and each conflict cost a rebuild on the new +`main` and a fresh browser pass. Files that are only ever added cannot collide. +The shared file is retired: the guard refuses any commit that writes it. Range +mode still accepts it as evidence from a commit that modified it, because that +commit was verified while its own parent still carried the file. A pull request +that still rewrites it is repaired by merging `main` and taking `main`'s copy of +the file, not by a rebuild. The file is deleted in a change of its own, because +deleting it conflicts with every pull request that still rewrites it. + `scripts/release_control/browser_verification_guard.py` enforces the receipt against the staged index locally and against the final integration range in canonical governance CI. Canonical contract completion remains checked per commit. A stale receipt, later source edit, incomplete path coverage, -missing responsive viewport, or omitted state/interaction evidence is a hard failure. Agents may -print a non-passing receipt skeleton with -`python3 scripts/release_control/browser_verification_guard.py --print-template` -after staging the intended frontend paths. +missing responsive viewport, or omitted state/interaction evidence is a hard failure. After +staging the intended frontend paths, agents may write a non-passing receipt +skeleton to the path the staged content requires with +`python3 scripts/release_control/browser_verification_guard.py --write-template`, +or print it with `--print-template`, which also names that path. Editing a +staged frontend file afterwards changes the fingerprint, so the receipt must be +re-verified and moved to the new name. + +Concluding a merge stages everything the other parent brought. The +staged-index guard does not count content a merged parent already carries as +the commit's own change, so merging current `main` into a branch needs no +receipt for the frontend work `main` brought and cannot be asked to vouch for +it. A merge commit may not record a receipt. Frontend content that differs from +every parent, a conflict resolution or two edits git combined, can be +committed, but it has had no browser pass: verify it and record its receipt in +a follow-up commit, which range mode requires. Because receipts no longer +conflict, two open pull requests that edit different parts of one frontend +file can both merge; range mode evaluates the merged result on the pull +request when its check runs and again on the push to `main`, where combined +content that no receipt verified fails. + +A receipt is read from the commit that recorded it, so once that commit is on +`main` its file is dead weight. +`python3 scripts/release_control/browser_verification_guard.py --prune` +deletes every receipt already on `origin/main`. A change that records a +receipt must not delete one: git reads a deleted receipt beside a newly added +one as a rename, and that rename conflicts with any other change that deletes +the same receipt. Pruning therefore lands only in changes that record no +receipt, and the guard enforces that per commit in both modes. An integration range that merges reviewed work onto a main that has moved is validated with `--base --commit `. The tip of such a diff --git a/frontend-modern/browser-verification/README.md b/frontend-modern/browser-verification/README.md new file mode 100644 index 0000000000..0bffc24e28 --- /dev/null +++ b/frontend-modern/browser-verification/README.md @@ -0,0 +1,25 @@ +# Browser verification receipts + +Each commit that changes user-visible frontend source adds one receipt here. +The file name is the fingerprint of the content the receipt verified, so no +two changes write the same file and open frontend pull requests cannot +conflict on their receipts. + +Create the skeleton for the staged change, then fill it in after the browser +pass: + +```bash +python3 scripts/release_control/browser_verification_guard.py --write-template +``` + +Do not edit, rename or delete another change's receipt in a commit that adds +one. A receipt is read from the commit that recorded it, so receipts whose +commits are already on `main` are dead weight; remove them in a change that +records none: + +```bash +python3 scripts/release_control/browser_verification_guard.py --prune +``` + +The rules live in the Frontend Browser Verification Gate section of +`docs/release-control/v6/internal/CANONICAL_DEVELOPMENT_PROTOCOL.md`. diff --git a/scripts/release_control/browser_verification_guard.py b/scripts/release_control/browser_verification_guard.py index 0c9fbe5fcd..1de3ed3f0a 100644 --- a/scripts/release_control/browser_verification_guard.py +++ b/scripts/release_control/browser_verification_guard.py @@ -1,5 +1,14 @@ #!/usr/bin/env python3 -"""Require fresh browser proof for staged user-visible frontend changes.""" +"""Require fresh browser proof for staged user-visible frontend changes. + +Every commit that changes user-visible frontend source records its browser +receipt in a file of its own under frontend-modern/browser-verification/, named +by the fingerprint of the content it verified. Until 2026-10-03 every such +commit rewrote one shared file, so any two open frontend pull requests +conflicted the moment either merged, and each conflict cost a rebuild and a +fresh browser pass. Receipts that are only ever added cannot collide, and git +merges them without help. +""" from __future__ import annotations @@ -8,6 +17,7 @@ from datetime import datetime import hashlib import json from pathlib import Path, PurePosixPath +import re import subprocess import sys from typing import Iterable, Sequence @@ -16,7 +26,17 @@ import format_staged_frontend REPO_ROOT = Path(__file__).resolve().parents[2] -RECEIPT_PATH = "frontend-modern/browser-verification.json" +RECEIPT_DIR = "frontend-modern/browser-verification" +# The single shared receipt every frontend commit rewrote before per-commit +# receipts. It is retired: nothing may write it again. Range mode still reads +# it from commits that modified it, because those commits were verified +# honestly against a tree that carried it and are still merging. The file +# itself is deleted in a change of its own, since deleting it conflicts with +# every open pull request that still rewrites it. +LEGACY_RECEIPT_PATH = "frontend-modern/browser-verification.json" +RECEIPT_FINGERPRINT_LENGTH = 16 +DEFAULT_LANDED_REF = "origin/main" +GUARD_COMMAND = "python3 scripts/release_control/browser_verification_guard.py" FRONTEND_SOURCE_PREFIX = "frontend-modern/src/" FRONTEND_ENTRY_FILES = {"frontend-modern/index.html"} FRONTEND_SOURCE_SUFFIXES = {".css", ".scss", ".ts", ".tsx"} @@ -33,6 +53,15 @@ def run_git(args: Sequence[str], *, repo_root: Path = REPO_ROOT) -> str: return result.stdout.strip() +def blocked(headline: str, details: Iterable[str] = (), advice: Iterable[str] = ()) -> int: + print(f"BLOCKED: {headline}", file=sys.stderr) + for detail in details: + print(f" - {detail}", file=sys.stderr) + for line in advice: + print(line, file=sys.stderr) + return 1 + + def git_blob_bytes(object_name: str, *, repo_root: Path = REPO_ROOT) -> bytes | None: result = subprocess.run( ["git", "show", object_name], @@ -139,12 +168,35 @@ def formatting_only_paths( return formatting_only +def is_receipt_path(path: str) -> bool: + pure_path = PurePosixPath(path) + return str(pure_path.parent) == RECEIPT_DIR and pure_path.suffix == ".json" + + +def receipt_fingerprint(digests: dict[str, str]) -> str: + canonical = json.dumps(digests, sort_keys=True, separators=(",", ":")) + return hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:RECEIPT_FINGERPRINT_LENGTH] + + +def receipt_path_for(digests: dict[str, str]) -> str: + """Where the receipt verifying exactly this content must be stored. + + The name is derived from the verified content rather than chosen, so two + changes can only share a receipt file when they verify identical bytes, + and the guard knows which file a commit owes without searching. It does + not include the parent, so rebuilding a commit on a new parent edits + base_sha in place instead of renaming the file. + """ + return f"{RECEIPT_DIR}/{receipt_fingerprint(digests)}.json" + + def load_receipt_text( + path: str, *, commit: str | None, repo_root: Path = REPO_ROOT, ) -> str: - object_name = f"{commit}:{RECEIPT_PATH}" if commit else f":{RECEIPT_PATH}" + object_name = f"{commit}:{path}" if commit else f":{path}" return run_git(["show", object_name], repo_root=repo_root) @@ -171,9 +223,17 @@ def validate_receipt( payload: object, *, changed_paths: Sequence[str], - expected_base: str, + expected_base: str | None, expected_content_sha256: dict[str, str], + receipt_path: str | None = None, ) -> list[str]: + """Reasons the receipt is not evidence for exactly this content. + + expected_base None validates a receipt staged without frontend source + changes, where the commit's parent is not knowable from the index alone + (a follow-up proof commit names HEAD, an amended one names HEAD's parent); + range mode binds those to their real parent. + """ errors: list[str] = [] if not isinstance(payload, dict): return ["receipt root must be a JSON object"] @@ -182,9 +242,26 @@ def validate_receipt( errors.append("version must be 1") if payload.get("result") != "passed": errors.append('result must be "passed"') - if payload.get("base_sha") != expected_base: + if expected_base is None: + base_sha = payload.get("base_sha") + if not isinstance(base_sha, str) or not re.fullmatch(r"[0-9a-f]{40}", base_sha): + errors.append("base_sha must be the full commit id of the verified parent") + elif payload.get("base_sha") != expected_base: errors.append(f"base_sha must match the verified parent {expected_base}") + digests = payload.get("content_sha256") + if ( + receipt_path is not None + and is_receipt_path(receipt_path) + and isinstance(digests, dict) + and all(isinstance(key, str) and isinstance(value, str) for key, value in digests.items()) + and receipt_path != receipt_path_for(digests) + ): + errors.append( + f"receipt must be stored at {receipt_path_for(digests)}, " + "the fingerprint of its content_sha256" + ) + receipt_paths = payload.get("changed_paths") if not isinstance(receipt_paths, list) or not all( isinstance(path, str) and path for path in receipt_paths @@ -246,18 +323,110 @@ def validate_receipt( return errors +def blob_ids( + revision: str | None, + paths: Sequence[str], + *, + repo_root: Path = REPO_ROOT, +) -> dict[str, str]: + """Object ids under the given paths in a commit, or in the index when revision is None. + + A path that does not exist there is simply absent from the result. + """ + ids: dict[str, str] = {} + for start in range(0, len(paths), 200): + chunk = list(paths[start : start + 200]) + if revision is None: + command = ["ls-files", "--stage", "-z", "--", *chunk] + else: + command = ["ls-tree", "-r", "-z", revision, "--", *chunk] + output = run_git(["--literal-pathspecs", *command], repo_root=repo_root) + for entry in output.split("\0"): + meta, separator, path = entry.partition("\t") + if not separator: + continue + fields = meta.split() + ids[path] = fields[1] if revision is None else fields[2] + return ids + + +def merge_parents(*, commit: str | None, repo_root: Path = REPO_ROOT) -> list[str]: + """Parents beyond the first: the merge being concluded, or a merge commit's own.""" + if commit: + return run_git( + ["rev-list", "--parents", "-n", "1", commit], repo_root=repo_root + ).split()[2:] + merge_head = Path(run_git(["rev-parse", "--git-path", "MERGE_HEAD"], repo_root=repo_root)) + if not merge_head.is_absolute(): + merge_head = repo_root / merge_head + if not merge_head.exists(): + return [] + return merge_head.read_text(encoding="utf-8").split() + + +def merged_in_paths( + paths: Sequence[str], + *, + commit: str | None, + repo_root: Path = REPO_ROOT, +) -> set[str]: + """Paths whose new state is exactly what a merged parent already carries. + + Concluding a merge stages everything the other parent brought, so a plain + staged-file reading mistakes the other side's frontend work and receipts + for this commit's own. Demanding a receipt for them asked the author to + vouch for files they never ran, which made a merge unable to repair a + branch that fell behind main. Content a parent already holds was verified + in that parent's history and is not this commit's change. + """ + parents = merge_parents(commit=commit, repo_root=repo_root) + if not parents or not paths: + return set() + new = blob_ids(commit, paths, repo_root=repo_root) + inherited: set[str] = set() + for parent in parents: + theirs = blob_ids(parent, paths, repo_root=repo_root) + inherited.update(path for path in paths if new.get(path) == theirs.get(path)) + return inherited + + +def parse_receipt_changes(name_status: str) -> dict[str, str]: + changes: dict[str, str] = {} + for line in name_status.splitlines(): + status, _, path = line.partition("\t") + if path == LEGACY_RECEIPT_PATH or is_receipt_path(path): + changes[path] = status[:1] + return changes + + +def receipt_changes(*, commit: str | None, repo_root: Path = REPO_ROOT) -> dict[str, str]: + """Receipt files a commit, or the staged index, adds (A), modifies (M) or deletes (D). + + Read from git rather than from the caller's path list, and with rename + detection off: git reports a deleted receipt and a similar new one as a + single rename, which would hide the deletion. + """ + if commit: + command = ["diff", "--no-renames", "--name-status", f"{commit}^", commit] + else: + command = ["diff", "--cached", "--no-renames", "--name-status"] + return parse_receipt_changes( + run_git([*command, "--", RECEIPT_DIR, LEGACY_RECEIPT_PATH], repo_root=repo_root) + ) + + def receipt_commits_in_range( base: str, head: str, *, repo_root: Path = REPO_ROOT, ) -> list[str]: - """Non-merge commits in base..head that author a receipt version. + """Non-merge commits in base..head that touch a receipt. - Merge commits are excluded: a merge resolution of the shared receipt keeps - or combines other commits' records without a browser run of its own, so it - can never be evidence. --full-history keeps receipt commits on merged side - branches that default history simplification would prune. + Merge commits are excluded: a merge resolution keeps or combines other + commits' records without a browser run of its own, so it can never be + evidence. --full-history keeps receipt commits on merged side branches + that default history simplification would prune. """ output = run_git( [ @@ -267,55 +436,104 @@ def receipt_commits_in_range( "--full-history", f"{base}..{head}", "--", - RECEIPT_PATH, + RECEIPT_DIR, + LEGACY_RECEIPT_PATH, ], repo_root=repo_root, ) return [line.strip() for line in output.splitlines() if line.strip()] +PRUNE_SEPARATELY = ( + "Git reads a deleted receipt beside a newly added one as a rename, and that " + "rename conflicts with any other change that deletes the same receipt. Prune " + f"landed receipts in a change that records none: {GUARD_COMMAND} --prune" +) + + +RETIRED_RECEIPT = ( + f"{LEGACY_RECEIPT_PATH} is retired and must not be written. Each commit now " + f"records its receipt in its own file under {RECEIPT_DIR}/, so concurrent " + "frontend changes no longer conflict." +) + + def range_receipt_coverage( base: str, head: str, *, repo_root: Path = REPO_ROOT, -) -> tuple[dict[str, set[str]], list[str]]: +) -> tuple[dict[str, set[str]], list[str], list[str]]: """Content digests verified by valid receipts authored inside base..head. Each receipt is validated exactly as the per-commit guard validates it at the commit that recorded it: bound to that commit's own parent, and its changed_paths and content_sha256 matching that commit's tree. A receipt - that fails contributes no coverage and is reported. + that fails contributes no coverage and is reported as a diagnostic. + + Violations are returned separately because they block the range even when + coverage is complete: a commit that both records and deletes receipts. """ covered: dict[str, set[str]] = {} diagnostics: list[str] = [] + violations: list[str] = [] for commit in receipt_commits_in_range(base, head, repo_root=repo_root): try: - payload = json.loads(load_receipt_text(commit=commit, repo_root=repo_root)) + changes = receipt_changes(commit=commit, repo_root=repo_root) commit_base = expected_base_sha(commit=commit, repo_root=repo_root) - except (json.JSONDecodeError, subprocess.CalledProcessError) as exc: - diagnostics.append(f"{commit[:12]}: unable to load receipt: {exc}") + except subprocess.CalledProcessError as exc: + diagnostics.append(f"{commit[:12]}: unable to read receipt changes: {exc}") continue - listed = payload.get("changed_paths") if isinstance(payload, dict) else None - listed_paths = ( - [path for path in listed if isinstance(path, str) and path] - if isinstance(listed, list) - else [] + recorded = sorted( + path for path, status in changes.items() + if status != "D" and path != LEGACY_RECEIPT_PATH ) - errors = validate_receipt( - payload, - changed_paths=listed_paths, - expected_base=commit_base, - expected_content_sha256=content_sha256( - listed_paths, commit=commit, repo_root=repo_root - ), - ) - if errors: - diagnostics.append(f"{commit[:12]}: receipt is invalid: " + "; ".join(errors)) - continue - for path, digest in payload["content_sha256"].items(): - covered.setdefault(path, set()).add(digest) - return covered, diagnostics + legacy_status = changes.get(LEGACY_RECEIPT_PATH) + if legacy_status == "M": + # The commit's own parent still carried the shared receipt, so it + # was verified before per-commit receipts and is honest evidence. + recorded.append(LEGACY_RECEIPT_PATH) + elif legacy_status == "A": + diagnostics.append( + f"{commit[:12]}: recreates the retired {LEGACY_RECEIPT_PATH}, which is not " + f"evidence; record the receipt under {RECEIPT_DIR}/ instead" + ) + if recorded and any( + status == "D" and path != LEGACY_RECEIPT_PATH for path, status in changes.items() + ): + violations.append( + f"{commit[:12]}: records a receipt and deletes another. " + PRUNE_SEPARATELY + ) + for receipt_path in recorded: + label = f"{commit[:12]} {PurePosixPath(receipt_path).name}" + try: + payload = json.loads( + load_receipt_text(receipt_path, commit=commit, repo_root=repo_root) + ) + except (json.JSONDecodeError, subprocess.CalledProcessError) as exc: + diagnostics.append(f"{label}: unable to load receipt: {exc}") + continue + listed = payload.get("changed_paths") if isinstance(payload, dict) else None + listed_paths = ( + [path for path in listed if isinstance(path, str) and path] + if isinstance(listed, list) + else [] + ) + errors = validate_receipt( + payload, + changed_paths=listed_paths, + expected_base=commit_base, + expected_content_sha256=content_sha256( + listed_paths, commit=commit, repo_root=repo_root + ), + receipt_path=receipt_path, + ) + if errors: + diagnostics.append(f"{label}: receipt is invalid: " + "; ".join(errors)) + continue + for path, digest in payload["content_sha256"].items(): + covered.setdefault(path, set()).add(digest) + return covered, diagnostics, violations def range_coverage_errors( @@ -324,9 +542,12 @@ def range_coverage_errors( base: str, head: str, repo_root: Path = REPO_ROOT, -) -> list[str]: +) -> tuple[list[str], list[str]]: """Changed frontend paths whose final content no receipt in range verified. + Returned beside the range's receipt-rule violations, which block on their + own. + Integration merges reviewed commits onto a main that keeps moving, so the range tip is usually a merge and the range carries several independently verified changes. Binding one tip receipt to the tip's parent and to the @@ -335,17 +556,17 @@ def range_coverage_errors( pass verified: its digest at head must equal the digest recorded by a valid receipt from a non-merge commit in the range. """ - covered, diagnostics = range_receipt_coverage(base, head, repo_root=repo_root) + covered, diagnostics, violations = range_receipt_coverage(base, head, repo_root=repo_root) final = content_sha256(changed_paths, commit=head, repo_root=repo_root) uncovered = [path for path in changed_paths if final[path] not in covered.get(path, set())] if not uncovered: - return [] + return [], violations errors = [ f"{path} final content {final[path][:12]} is not verified by any valid receipt in the range" for path in uncovered ] errors.extend(diagnostics) - return errors + return errors, violations def build_template(paths: Sequence[str], *, repo_root: Path = REPO_ROOT) -> dict: @@ -367,6 +588,310 @@ def build_template(paths: Sequence[str], *, repo_root: Path = REPO_ROOT) -> dict } +def emit_template(paths: Sequence[str], *, write: bool, repo_root: Path = REPO_ROOT) -> int: + template = build_template(paths, repo_root=repo_root) + if not template["changed_paths"]: + if write: + return blocked( + "no user-visible frontend source is staged, so there is no receipt to write." + ) + print(json.dumps(template, indent=2)) + return 0 + receipt_path = receipt_path_for(template["content_sha256"]) + text = json.dumps(template, indent=2) + "\n" + if not write: + print(text, end="") + print(f"Save the completed receipt as {receipt_path} and stage it.", file=sys.stderr) + return 0 + target = repo_root / receipt_path + if target.exists(): + return blocked(f"{receipt_path} already exists; edit it rather than overwriting a receipt.") + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(text, encoding="utf-8") + print(receipt_path) + return 0 + + +def prune_landed_receipts(landed_ref: str, *, repo_root: Path = REPO_ROOT) -> int: + """Delete receipts whose commits are already on landed_ref. + + A receipt is read from the commit that recorded it, so once that commit is + on main the file in the tree is dead weight. Only a branch that records no + receipt of its own may prune (see PRUNE_SEPARATELY). + """ + try: + merge_base = run_git(["merge-base", landed_ref, "HEAD"], repo_root=repo_root) + branch_changes = parse_receipt_changes( + run_git( + ["diff", "--no-renames", "--name-status", merge_base, "HEAD", "--", RECEIPT_DIR], + repo_root=repo_root, + ) + ) + staged_changes = receipt_changes(commit=None, repo_root=repo_root) + recorded = sorted( + path + for changes in (branch_changes, staged_changes) + for path, status in changes.items() + if status != "D" and path != LEGACY_RECEIPT_PATH + ) + if recorded: + return blocked( + f"this branch records {len(recorded)} receipt(s) of its own " + f"({', '.join(PurePosixPath(path).name for path in recorded)}).", + advice=[PRUNE_SEPARATELY], + ) + landed = blob_ids(landed_ref, [RECEIPT_DIR], repo_root=repo_root) + prunable = sorted( + path + for path, blob in blob_ids(None, [RECEIPT_DIR], repo_root=repo_root).items() + if is_receipt_path(path) and landed.get(path) == blob + ) + for start in range(0, len(prunable), 200): + run_git( + ["--literal-pathspecs", "rm", "--quiet", "--", *prunable[start : start + 200]], + repo_root=repo_root, + ) + except subprocess.CalledProcessError as exc: + return blocked(f"unable to prune browser verification receipts: {exc}") + if not prunable: + print(f"No receipts on {landed_ref} to prune.") + return 0 + print( + f"Pruned {len(prunable)} receipt(s) already on {landed_ref}. " + "Commit the staged deletions; they need no receipt." + ) + return 0 + + +def check_commit( + paths: Sequence[str], + *, + commit: str | None, + repo_root: Path = REPO_ROOT, +) -> int: + """Validate one commit, or the staged index, against its own receipt.""" + try: + merging = bool(merge_parents(commit=commit, repo_root=repo_root)) + changes = receipt_changes(commit=commit, repo_root=repo_root) + frontend_paths = frontend_runtime_paths(paths) + inherited = merged_in_paths( + sorted({*changes, *frontend_paths}), commit=commit, repo_root=repo_root + ) + except subprocess.CalledProcessError as exc: + return blocked(f"unable to inspect browser verification state: {exc}") + + changes = {path: status for path, status in changes.items() if path not in inherited} + if changes.get(LEGACY_RECEIPT_PATH, "D") != "D": + return blocked( + RETIRED_RECEIPT, + advice=[ + "While concluding a merge, take the merged parent's version: " + f"git checkout MERGE_HEAD -- {LEGACY_RECEIPT_PATH}" + if merging + else f"Unstage it and run: {GUARD_COMMAND} --write-template" + ], + ) + frontend_paths = [path for path in frontend_paths if path not in inherited] + recorded = sorted( + path for path, status in changes.items() if status != "D" and path != LEGACY_RECEIPT_PATH + ) + pruned = sorted( + path for path, status in changes.items() if status == "D" and path != LEGACY_RECEIPT_PATH + ) + + if frontend_paths: + reformatted = formatting_only_paths(frontend_paths, commit=commit, repo_root=repo_root) + if reformatted: + print( + f"Browser verification guard: {len(reformatted)} path(s) are prettier-only " + "reformats of their committed content, with no visual delta to verify." + ) + frontend_paths = [path for path in frontend_paths if path not in reformatted] + + if merging: + if recorded: + return blocked( + "a merge commit cannot record browser evidence.", + details=recorded, + advice=[ + "A merge resolution has no browser run behind it, so range validation " + "ignores receipts a merge commit writes.", + "Take the merged parent's version of each file (git checkout MERGE_HEAD -- " + ") or unstage it, conclude the merge, then record fresh evidence in a " + "follow-up commit.", + ], + ) + if frontend_paths: + print( + f"Browser verification guard: {len(frontend_paths)} frontend source file(s) " + "carry merged content that neither parent holds:", + file=sys.stderr, + ) + for path in frontend_paths: + print(f" - {path}", file=sys.stderr) + print( + "The merge may be concluded, but that content has had no browser pass. Verify " + "it, then record a receipt in a follow-up commit, or range validation will " + "block the change:\n" + f" printf '%s\\n' {' '.join(frontend_paths)} | {GUARD_COMMAND} " + "--files-from-stdin --write-template", + file=sys.stderr, + ) + return 0 + print( + "Browser verification guard skipped (the merge changes no user-visible frontend " + "source beyond what its parents already carry)." + ) + return 0 + + if recorded and pruned: + return blocked( + "a commit that records a receipt must not delete another.", + details=pruned, + advice=[PRUNE_SEPARATELY], + ) + + if not frontend_paths: + if not recorded: + print("Browser verification guard skipped (no user-visible frontend source changes).") + return 0 + # A proof commit that follows its source change. Everything except the + # parent binding is checkable here; range mode binds the parent. + failures: list[str] = [] + for receipt_path in recorded: + try: + payload = json.loads( + load_receipt_text(receipt_path, commit=commit, repo_root=repo_root) + ) + except (json.JSONDecodeError, subprocess.CalledProcessError) as exc: + failures.append(f"{receipt_path}: unable to load receipt: {exc}") + continue + listed = payload.get("changed_paths") if isinstance(payload, dict) else None + listed_paths = ( + [path for path in listed if isinstance(path, str) and path] + if isinstance(listed, list) + else [] + ) + failures.extend( + f"{receipt_path}: {error}" + for error in validate_receipt( + payload, + changed_paths=listed_paths, + expected_base=None, + expected_content_sha256=content_sha256( + listed_paths, commit=commit, repo_root=repo_root + ), + receipt_path=receipt_path, + ) + ) + if failures: + return blocked("browser verification receipt is invalid:", details=failures) + print( + f"Browser verification guard passed ({len(recorded)} receipt(s) recorded without " + "frontend source changes; range validation binds each to its parent)." + ) + return 0 + + try: + digests = content_sha256(frontend_paths, commit=commit, repo_root=repo_root) + expected_base = expected_base_sha(commit=commit, repo_root=repo_root) + except subprocess.CalledProcessError as exc: + return blocked(f"unable to load browser verification receipt: {exc}") + receipt_path = receipt_path_for(digests) + strays = [path for path in recorded if path != receipt_path] + if receipt_path not in recorded: + advice = [ + "Run the current build in a browser, exercise the complete interaction matrix, " + "then record routes, desktop and narrow viewports, states, and interactions.", + f"Start from a skeleton at that path with: {GUARD_COMMAND} --write-template", + ] + if strays: + advice.append( + "A receipt's file name is the fingerprint of the content it verified. " + f"{', '.join(strays)} verifies different content: the staged source changed " + "after it was written, or it was named by hand." + ) + return blocked( + f"{receipt_path} must be added and staged for user-visible frontend changes.", + advice=advice, + ) + if strays: + return blocked( + f"a commit records exactly one receipt, {receipt_path}.", + details=strays, + advice=["Unstage the others; one receipt covers every frontend file the commit changes."], + ) + + try: + payload = json.loads(load_receipt_text(receipt_path, commit=commit, repo_root=repo_root)) + except (json.JSONDecodeError, subprocess.CalledProcessError) as exc: + return blocked(f"unable to load browser verification receipt: {exc}") + + errors = validate_receipt( + payload, + changed_paths=frontend_paths, + expected_base=expected_base, + expected_content_sha256=digests, + receipt_path=receipt_path, + ) + if errors: + return blocked("browser verification receipt is invalid:", details=errors) + + print( + "Browser verification guard passed " + f"({len(frontend_paths)} frontend source file(s), " + f"{len(payload['routes'])} route(s), {len(payload['viewports'])} viewport(s))." + ) + return 0 + + +def check_range(paths: Sequence[str], *, base: str, head: str, repo_root: Path = REPO_ROOT) -> int: + """Validate base..head: violations block always, coverage when frontend source changed.""" + changed_frontend_paths = frontend_runtime_paths(paths) + if changed_frontend_paths: + reformatted = formatting_only_paths( + changed_frontend_paths, commit=head, base=base, repo_root=repo_root + ) + if reformatted: + print( + f"Browser verification guard: {len(reformatted)} path(s) are prettier-only " + "reformats of their committed content, with no visual delta to verify." + ) + changed_frontend_paths = [ + path for path in changed_frontend_paths if path not in reformatted + ] + try: + errors, violations = range_coverage_errors( + changed_frontend_paths, base=base, head=head, repo_root=repo_root + ) + except subprocess.CalledProcessError as exc: + return blocked(f"unable to evaluate browser verification range: {exc}") + if errors: + return blocked( + f"browser verification does not cover {base[:12]}..{head[:12]}:", + details=[*errors, *violations], + advice=[ + "Content changed after its browser pass (a correction, a conflict resolution, " + "or two changes to one file) needs a fresh receipt for the final content, " + "committed in its own non-merge commit and bound to that commit's parent." + ], + ) + if violations: + return blocked( + f"browser verification receipts in {base[:12]}..{head[:12]} break the receipt rules:", + details=violations, + ) + if not changed_frontend_paths: + print("Browser verification guard skipped (no user-visible frontend source changes).") + return 0 + print( + "Browser verification guard passed " + f"({len(changed_frontend_paths)} frontend source file(s) across " + f"{base[:12]}..{head[:12]} covered by in-range receipts)." + ) + return 0 + + def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( @@ -376,7 +901,7 @@ def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: ) parser.add_argument( "--commit", - help="Validate the receipt stored in this commit against that commit's parent.", + help="Validate the receipt recorded by this commit against that commit's parent.", ) parser.add_argument( "--base", @@ -391,6 +916,27 @@ def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: action="store_true", help="Print a non-passing receipt template for the current staged frontend paths.", ) + parser.add_argument( + "--write-template", + action="store_true", + help=( + "Write that template to the receipt file the staged frontend content " + "requires, and print its path." + ), + ) + parser.add_argument( + "--prune", + action="store_true", + help=( + "Delete and stage the removal of receipts whose commits are already on " + "--landed-on. Run it on a branch that records no receipt of its own." + ), + ) + parser.add_argument( + "--landed-on", + default=DEFAULT_LANDED_REF, + help=f"Ref that --prune treats as landed (default: {DEFAULT_LANDED_REF}).", + ) args = parser.parse_args(argv) if args.base and not args.commit: parser.error("--base requires --commit") @@ -399,6 +945,9 @@ def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: def main(argv: Sequence[str] | None = None) -> int: args = parse_args(argv) + if args.prune: + return prune_landed_receipts(args.landed_on, repo_root=REPO_ROOT) + if args.files_from_stdin: paths = stdin_files(sys.stdin) elif args.base: @@ -409,102 +958,14 @@ def main(argv: Sequence[str] | None = None) -> int: ).splitlines() ) else: - paths = staged_files() + paths = staged_files(repo_root=REPO_ROOT) - if args.print_template: - print(json.dumps(build_template(paths), indent=2)) - return 0 - - changed_frontend_paths = frontend_runtime_paths(paths) - if changed_frontend_paths: - reformatted = formatting_only_paths( - changed_frontend_paths, commit=args.commit, base=args.base, repo_root=REPO_ROOT - ) - if reformatted: - print( - f"Browser verification guard: {len(reformatted)} path(s) are prettier-only " - "reformats of their committed content, with no visual delta to verify." - ) - changed_frontend_paths = [ - path for path in changed_frontend_paths if path not in reformatted - ] - - if not changed_frontend_paths: - print("Browser verification guard skipped (no user-visible frontend source changes).") - return 0 + if args.print_template or args.write_template: + return emit_template(paths, write=args.write_template, repo_root=REPO_ROOT) if args.base: - try: - errors = range_coverage_errors( - changed_frontend_paths, - base=args.base, - head=args.commit, - repo_root=REPO_ROOT, - ) - except subprocess.CalledProcessError as exc: - print(f"BLOCKED: unable to evaluate browser verification range: {exc}", file=sys.stderr) - return 1 - if errors: - print( - f"BLOCKED: browser verification does not cover {args.base[:12]}..{args.commit[:12]}:", - file=sys.stderr, - ) - for error in errors: - print(f" - {error}", file=sys.stderr) - print( - "Content changed after its browser pass (a correction, a conflict resolution, " - "or two changes to one file) needs a fresh receipt for the final content, " - "committed in its own non-merge commit and bound to that commit's parent.", - file=sys.stderr, - ) - return 1 - print( - "Browser verification guard passed " - f"({len(changed_frontend_paths)} frontend source file(s) across " - f"{args.base[:12]}..{args.commit[:12]} covered by in-range receipts)." - ) - return 0 - - if RECEIPT_PATH not in paths: - print( - f"BLOCKED: {RECEIPT_PATH} must be updated and staged for user-visible frontend changes.", - file=sys.stderr, - ) - print( - "Run the current build in a browser, exercise the complete interaction matrix, " - "then record routes, desktop and narrow viewports, states, and interactions.", - file=sys.stderr, - ) - return 1 - - try: - payload = json.loads(load_receipt_text(commit=args.commit)) - expected_base = expected_base_sha(commit=args.commit) - except (json.JSONDecodeError, subprocess.CalledProcessError) as exc: - print(f"BLOCKED: unable to load browser verification receipt: {exc}", file=sys.stderr) - return 1 - - errors = validate_receipt( - payload, - changed_paths=changed_frontend_paths, - expected_base=expected_base, - expected_content_sha256=content_sha256( - changed_frontend_paths, - commit=args.commit, - ), - ) - if errors: - print("BLOCKED: browser verification receipt is invalid:", file=sys.stderr) - for error in errors: - print(f" - {error}", file=sys.stderr) - return 1 - - print( - "Browser verification guard passed " - f"({len(changed_frontend_paths)} frontend source file(s), " - f"{len(payload['routes'])} route(s), {len(payload['viewports'])} viewport(s))." - ) - return 0 + return check_range(paths, base=args.base, head=args.commit, repo_root=REPO_ROOT) + return check_commit(paths, commit=args.commit, repo_root=REPO_ROOT) if __name__ == "__main__": diff --git a/scripts/release_control/browser_verification_guard_test.py b/scripts/release_control/browser_verification_guard_test.py index b0076a6728..7244377fef 100644 --- a/scripts/release_control/browser_verification_guard_test.py +++ b/scripts/release_control/browser_verification_guard_test.py @@ -14,10 +14,13 @@ import unittest from unittest.mock import patch from browser_verification_guard import ( - RECEIPT_PATH, + LEGACY_RECEIPT_PATH, + RECEIPT_DIR, formatting_only_paths, frontend_runtime_paths, + is_receipt_path, main, + receipt_path_for, validate_receipt, ) from format_staged_frontend_test import REAL_PRETTIER @@ -26,6 +29,7 @@ from repo_file_io import strip_local_git_env BASE_SHA = "a" * 40 CHANGED_PATH = "frontend-modern/src/components/Example.tsx" +OTHER_PATH = "frontend-modern/src/components/Other.tsx" CONTENT_SHA = "c" * 64 REPO_ROOT = Path(__file__).resolve().parents[2] @@ -57,13 +61,6 @@ class BrowserVerificationGuardTest(unittest.TestCase): self.assertLess(formatter, guard) - def test_blocks_frontend_change_when_receipt_is_not_in_commit(self) -> None: - with ( - patch("sys.stdin", StringIO(CHANGED_PATH + "\n")), - patch("sys.stderr", new=StringIO()), - ): - self.assertEqual(main(["--files-from-stdin"]), 1) - def test_frontend_runtime_paths_exclude_tests_and_receipt(self) -> None: self.assertEqual( frontend_runtime_paths( @@ -71,7 +68,8 @@ class BrowserVerificationGuardTest(unittest.TestCase): CHANGED_PATH, "frontend-modern/src/components/Example.test.tsx", "frontend-modern/src/components/__tests__/Example.tsx", - RECEIPT_PATH, + LEGACY_RECEIPT_PATH, + receipt_path_for({CHANGED_PATH: CONTENT_SHA}), "frontend-modern/index.html", "internal/api/server.go", ] @@ -134,6 +132,52 @@ class BrowserVerificationGuardTest(unittest.TestCase): self.assertTrue(any("content_sha256" in error for error in errors)) + def test_receipt_name_is_the_fingerprint_of_the_content_it_verified(self) -> None: + digests = {CHANGED_PATH: CONTENT_SHA, OTHER_PATH: "d" * 64} + path = receipt_path_for(digests) + + self.assertTrue(is_receipt_path(path)) + self.assertEqual(path, receipt_path_for(dict(reversed(digests.items())))) + self.assertNotEqual(path, receipt_path_for({**digests, OTHER_PATH: "e" * 64})) + self.assertFalse(is_receipt_path(LEGACY_RECEIPT_PATH)) + self.assertFalse(is_receipt_path(f"{RECEIPT_DIR}/README.md")) + self.assertFalse(is_receipt_path(f"{RECEIPT_DIR}/nested/receipt.json")) + + def test_rejects_receipt_stored_under_a_hand_picked_name(self) -> None: + errors = validate_receipt( + valid_receipt(), + changed_paths=[CHANGED_PATH], + expected_base=BASE_SHA, + expected_content_sha256={CHANGED_PATH: CONTENT_SHA}, + receipt_path=f"{RECEIPT_DIR}/storage-fix.json", + ) + + self.assertEqual(len(errors), 1) + self.assertIn(receipt_path_for({CHANGED_PATH: CONTENT_SHA}), errors[0]) + + def test_proof_only_receipt_still_needs_a_real_parent_id(self) -> None: + receipt = valid_receipt() + receipt["base_sha"] = "HEAD" + + errors = validate_receipt( + receipt, + changed_paths=[CHANGED_PATH], + expected_base=None, + expected_content_sha256={CHANGED_PATH: CONTENT_SHA}, + ) + + self.assertTrue(any("base_sha" in error for error in errors)) + receipt["base_sha"] = BASE_SHA + self.assertEqual( + validate_receipt( + receipt, + changed_paths=[CHANGED_PATH], + expected_base=None, + expected_content_sha256={CHANGED_PATH: CONTENT_SHA}, + ), + [], + ) + class FormattingOnlyExemptionTest(unittest.TestCase): """A prettier sweep has no visual delta, but a real edit must still block.""" @@ -195,22 +239,15 @@ class FormattingOnlyExemptionTest(unittest.TestCase): self.assertEqual(self.resolve(repo_root), set()) -OTHER_PATH = "frontend-modern/src/components/Other.tsx" - - -class IntegrationRangeTest(unittest.TestCase): - """--base validates a merged integration range, not one tip receipt. - - The maintainer coordinator merges reviewed candidates onto a main that - keeps moving, so the range tip is a merge whose parent no lane receipt can - name. Each frontend file must still ship with content a browser pass - verified, recorded by a non-merge commit bound to its own parent. - """ +class GuardRepoTestCase(unittest.TestCase): + """A throwaway repository the guard runs against instead of this one.""" def setUp(self) -> None: self.tmpdir = tempfile.TemporaryDirectory() self.addCleanup(self.tmpdir.cleanup) self.repo_root = Path(self.tmpdir.name) + # Under the pre-commit hook, GIT_DIR and GIT_INDEX_FILE are exported + # and would point this temp repo's plumbing at the real repository. self.env = strip_local_git_env(os.environ.copy()) self.env["PULSE_PRETTIER_BIN"] = str(self.repo_root / "missing-prettier") self.git("init", "--quiet", "--initial-branch=main") @@ -238,7 +275,7 @@ class IntegrationRangeTest(unittest.TestCase): self.git("commit", "--quiet", "--allow-empty", "-m", message) return self.git("rev-parse", "HEAD") - def write_receipt(self, paths: list[str], *, base: str | None = None) -> None: + def build_receipt(self, paths: list[str], *, base: str | None = None) -> dict: receipt = valid_receipt() receipt["base_sha"] = base or self.git("rev-parse", "HEAD") receipt["changed_paths"] = paths @@ -246,23 +283,259 @@ class IntegrationRangeTest(unittest.TestCase): path: hashlib.sha256((self.repo_root / path).read_bytes()).hexdigest() for path in paths } - self.write(RECEIPT_PATH, json.dumps(receipt, indent=2) + "\n") + return receipt + + def write_receipt( + self, paths: list[str], *, base: str | None = None, at: str | None = None + ) -> str: + receipt = self.build_receipt(paths, base=base) + receipt_path = at or receipt_path_for(receipt["content_sha256"]) + self.write(receipt_path, json.dumps(receipt, indent=2) + "\n") + return receipt_path def verified_change(self, path: str, text: str, message: str) -> str: self.write(path, text) self.write_receipt([path]) return self.commit(message) - def run_range(self, *extra: str) -> tuple[int, str]: - stderr = StringIO() + def run_guard(self, *argv: str, stdin: str = "") -> tuple[int, str, str]: + stdout, stderr = StringIO(), StringIO() with ( patch.dict("os.environ", self.env, clear=True), patch("browser_verification_guard.REPO_ROOT", self.repo_root), - patch("sys.stdout", new=StringIO()), + patch("sys.stdin", StringIO(stdin)), + patch("sys.stdout", new=stdout), patch("sys.stderr", new=stderr), ): - status = main(["--base", self.base, "--commit", self.git("rev-parse", "HEAD"), *extra]) - return status, stderr.getvalue() + status = main(list(argv)) + return status, stdout.getvalue(), stderr.getvalue() + + def run_range(self, *extra: str, base: str | None = None) -> tuple[int, str]: + status, _, stderr = self.run_guard( + "--base", base or self.base, "--commit", self.git("rev-parse", "HEAD"), *extra + ) + return status, stderr + + def receipts_in_tree(self, revision: str = "HEAD") -> list[str]: + listing = self.git("ls-tree", "-r", "--name-only", revision, "--", RECEIPT_DIR) + return [line for line in listing.splitlines() if line] + + +class StagedIndexTest(GuardRepoTestCase): + """The pre-commit form: one commit owes exactly one receipt of its own.""" + + def stage_change(self) -> str: + self.write(CHANGED_PATH, "export const a = 1;\n") + self.git("add", CHANGED_PATH) + return receipt_path_for(self.build_receipt([CHANGED_PATH])["content_sha256"]) + + def test_blocks_frontend_change_until_its_receipt_is_staged(self) -> None: + expected = self.stage_change() + + status, _, stderr = self.run_guard() + + self.assertEqual(status, 1) + self.assertIn(f"{expected} must be added and staged", stderr) + self.assertIn("--write-template", stderr) + + def test_stdin_path_list_is_held_to_the_same_receipt(self) -> None: + self.assertEqual(self.run_guard("--files-from-stdin", stdin=CHANGED_PATH + "\n")[0], 1) + + def test_accepts_receipt_staged_at_its_fingerprint_path(self) -> None: + self.stage_change() + self.git("add", self.write_receipt([CHANGED_PATH])) + + status, stdout, stderr = self.run_guard() + + self.assertEqual((status, stderr), (0, "")) + self.assertIn("1 frontend source file(s)", stdout) + + def test_write_template_creates_the_owed_receipt_but_never_a_passing_one(self) -> None: + expected = self.stage_change() + + status, stdout, _ = self.run_guard("--write-template") + self.assertEqual((status, stdout.strip()), (0, expected)) + self.git("add", expected) + status, _, stderr = self.run_guard() + self.assertEqual(status, 1) + self.assertIn('result must be "passed"', stderr) + + # A completed receipt is never overwritten by a second template. + self.assertEqual(self.run_guard("--write-template")[0], 1) + self.git("add", self.write_receipt([CHANGED_PATH])) + self.assertEqual(self.run_guard()[0], 0) + + def test_print_template_names_the_file_to_save(self) -> None: + expected = self.stage_change() + + status, stdout, stderr = self.run_guard("--print-template") + + self.assertEqual(status, 0) + self.assertEqual(json.loads(stdout)["changed_paths"], [CHANGED_PATH]) + self.assertIn(expected, stderr) + self.assertFalse((self.repo_root / expected).exists()) + + def test_write_template_refuses_when_nothing_needs_verifying(self) -> None: + self.assertEqual(self.run_guard("--write-template")[0], 1) + self.assertEqual(self.receipts_in_tree(), []) + + def test_blocks_receipt_named_by_hand(self) -> None: + expected = self.stage_change() + self.git("add", self.write_receipt([CHANGED_PATH], at=f"{RECEIPT_DIR}/storage-fix.json")) + + status, _, stderr = self.run_guard() + + self.assertEqual(status, 1) + self.assertIn(f"{expected} must be added and staged", stderr) + self.assertIn("storage-fix.json verifies different content", stderr) + + def test_blocks_source_edited_after_its_receipt_was_written(self) -> None: + self.stage_change() + stale = self.write_receipt([CHANGED_PATH]) + self.write(CHANGED_PATH, "export const a = 2;\n") + self.git("add", CHANGED_PATH, stale) + + status, _, stderr = self.run_guard() + + self.assertEqual(status, 1) + self.assertIn("the staged source changed after it was written", stderr) + + def test_blocks_a_second_receipt_in_one_commit(self) -> None: + self.stage_change() + self.git("add", self.write_receipt([CHANGED_PATH])) + self.git("add", self.write_receipt([OTHER_PATH])) + + status, _, stderr = self.run_guard() + + self.assertEqual(status, 1) + self.assertIn("exactly one receipt", stderr) + + def test_blocks_writing_the_retired_shared_receipt(self) -> None: + self.stage_change() + self.git("add", self.write_receipt([CHANGED_PATH], at=LEGACY_RECEIPT_PATH)) + + status, _, stderr = self.run_guard() + + self.assertEqual(status, 1) + self.assertIn(f"{LEGACY_RECEIPT_PATH} is retired", stderr) + + def test_blocks_recording_and_pruning_in_one_commit(self) -> None: + landed = self.verified_change(OTHER_PATH, "export const b = 1;\n", "landed") + self.assertTrue(landed) + (old,) = self.receipts_in_tree() + self.stage_change() + self.git("add", self.write_receipt([CHANGED_PATH])) + self.git("rm", "--quiet", old) + + status, _, stderr = self.run_guard() + + self.assertEqual(status, 1) + self.assertIn("must not delete another", stderr) + self.assertIn("--prune", stderr) + + def test_proof_only_commit_is_checked_against_the_content_it_names(self) -> None: + self.write(CHANGED_PATH, "export const a = 1;\n") + self.commit("frontend change without proof") + receipt_path = self.write_receipt([CHANGED_PATH]) + self.git("add", receipt_path) + self.assertEqual(self.run_guard()[0], 0) + + payload = json.loads((self.repo_root / receipt_path).read_text()) + payload["viewports"] = [{"width": 1280, "height": 800}] + self.write(receipt_path, json.dumps(payload)) + self.git("add", receipt_path) + status, _, stderr = self.run_guard() + self.assertEqual(status, 1) + self.assertIn("narrow width", stderr) + + def test_pruning_alone_needs_no_receipt(self) -> None: + self.verified_change(OTHER_PATH, "export const b = 1;\n", "landed") + self.git("rm", "--quiet", *self.receipts_in_tree()) + + self.assertEqual(self.run_guard()[0], 0) + + +class ConcludingMergeTest(GuardRepoTestCase): + """A merge is not asked to vouch for what its other parent already verified. + + With one shared receipt, concluding `git merge main` staged main's + frontend files as if the author had changed them, so the guard demanded a + receipt for files the author never ran and a branch behind main could only + be rebuilt and re-verified. + """ + + def start_merge(self, *, ours: tuple[str, str], theirs: tuple[str, str]) -> None: + self.git("checkout", "--quiet", "-b", "topic", self.base) + self.verified_change(*ours, "topic change") + self.git("checkout", "--quiet", "main") + self.verified_change(*theirs, "main moves") + self.git("checkout", "--quiet", "topic") + subprocess.run( + ["git", "-c", "user.email=t@example.com", "-c", "user.name=t", + "merge", "--quiet", "--no-ff", "--no-commit", "main"], + cwd=self.repo_root, capture_output=True, env=self.env, + ) + + def test_passes_frontend_work_the_merged_parent_already_carries(self) -> None: + self.start_merge( + ours=(CHANGED_PATH, "export const a = 1;\n"), + theirs=(OTHER_PATH, "export const b = 1;\n"), + ) + self.assertIn(OTHER_PATH, self.git("diff", "--cached", "--name-only")) + + status, stdout, stderr = self.run_guard() + + self.assertEqual((status, stderr), (0, "")) + self.assertIn("beyond what its parents already carry", stdout) + self.commit("merge main") + self.assertEqual(self.run_range(base=self.git("rev-parse", "main"))[0], 0) + + def test_flags_merged_content_and_range_holds_it_to_a_follow_up_receipt(self) -> None: + self.start_merge( + ours=(CHANGED_PATH, "export const a = 1;\n"), + theirs=(CHANGED_PATH, "export const a = 2;\n"), + ) + self.write(CHANGED_PATH, "export const a = 3;\n") + self.git("add", CHANGED_PATH) + + status, _, stderr = self.run_guard() + self.assertEqual(status, 0) + self.assertIn(CHANGED_PATH, stderr) + self.assertIn("no browser pass", stderr) + + self.commit("merge main") + main_tip = self.git("rev-parse", "main") + status, stderr = self.run_range(base=main_tip) + self.assertEqual(status, 1) + self.assertIn(CHANGED_PATH, stderr) + + self.git("add", self.write_receipt([CHANGED_PATH])) + self.assertEqual(self.run_guard()[0], 0) + self.commit("record proof for the merged content") + self.assertEqual(self.run_range(base=main_tip)[0], 0) + + def test_blocks_a_receipt_written_inside_the_merge(self) -> None: + self.start_merge( + ours=(CHANGED_PATH, "export const a = 1;\n"), + theirs=(CHANGED_PATH, "export const a = 2;\n"), + ) + self.write(CHANGED_PATH, "export const a = 3;\n") + self.git("add", CHANGED_PATH, self.write_receipt([CHANGED_PATH])) + + status, _, stderr = self.run_guard() + + self.assertEqual(status, 1) + self.assertIn("a merge commit cannot record browser evidence", stderr) + + +class IntegrationRangeTest(GuardRepoTestCase): + """--base validates a merged integration range, not one tip receipt. + + The maintainer coordinator merges reviewed candidates onto a main that + keeps moving, so the range tip is a merge whose parent no lane receipt can + name. Each frontend file must still ship with content a browser pass + verified, recorded by a non-merge commit bound to its own parent. + """ def merge_advanced_main_into_candidate(self) -> None: # main advances with its own verified frontend change while the @@ -272,15 +545,11 @@ class IntegrationRangeTest(unittest.TestCase): self.verified_change(CHANGED_PATH, "export const a = 1;\n", "candidate") self.git("checkout", "--quiet", "main") self.verified_change(OTHER_PATH, "export const b = 1;\n", "upstream") - # Both sides edited the shared receipt; the resolution keeps one side. - subprocess.run( - ["git", "-c", "user.email=t@example.com", "-c", "user.name=t", - "merge", "--quiet", "--no-ff", "candidate", "-m", "integrate"], - cwd=self.repo_root, capture_output=True, env=self.env, - ) - self.git("checkout", "--quiet", "--theirs", RECEIPT_PATH) - self.commit("integrate candidate") + # Each side added a receipt file of its own, so git merges them + # unaided; self.git raises if the merge stops on a conflict. + self.git("merge", "--quiet", "--no-ff", "candidate", "-m", "integrate candidate") self.assertEqual(len(self.git("log", "-1", "--format=%P").split()), 2) + self.assertEqual(len(self.receipts_in_tree()), 2) def test_accepts_merge_of_verified_candidate_onto_advanced_main(self) -> None: self.merge_advanced_main_into_candidate() @@ -309,14 +578,15 @@ class IntegrationRangeTest(unittest.TestCase): def test_workflow_accepts_additive_receipt_correction_but_not_unverified_edit(self) -> None: self.write(CHANGED_PATH, "export const a = 1;\n") - self.write_receipt([CHANGED_PATH]) - payload = json.loads((self.repo_root / RECEIPT_PATH).read_text()) + receipt_path = self.write_receipt([CHANGED_PATH]) + payload = json.loads((self.repo_root / receipt_path).read_text()) payload["verified_at"] = "2026-10-02T13:00:00+00:00" - self.write(RECEIPT_PATH, json.dumps(payload)) + self.write(receipt_path, json.dumps(payload)) original = self.commit("frontend with malformed timestamp") self.assertNotEqual(self.run_workflow_browser_step().returncode, 0) - self.write_receipt([CHANGED_PATH]) + # Same verified content, so the correction edits the same file. + self.assertEqual(self.write_receipt([CHANGED_PATH]), receipt_path) self.commit("correct receipt without rewriting source history") corrected = self.run_workflow_browser_step() self.assertEqual(corrected.returncode, 0, corrected.stdout + corrected.stderr) @@ -382,6 +652,163 @@ class IntegrationRangeTest(unittest.TestCase): with patch("sys.stderr", new=StringIO()), self.assertRaises(SystemExit): main(["--base", self.base]) + def test_second_branch_merges_cleanly_after_the_first_and_its_range_passes(self) -> None: + # Two pull requests cut from the same main, touching different + # frontend files. With one shared receipt the second always conflicted + # once the first merged. + for branch, path in (("first", CHANGED_PATH), ("second", OTHER_PATH)): + self.git("checkout", "--quiet", "-b", branch, self.base) + self.verified_change(path, "export const changed = 1;\n", branch) + self.git("checkout", "--quiet", "main") + self.git("merge", "--quiet", "--no-ff", "first", "-m", "merge first") + main_after_first = self.git("rev-parse", "HEAD") + self.assertEqual(self.run_range()[0], 0) + + self.git("merge", "--quiet", "--no-ff", "second", "-m", "merge second") + + self.assertEqual(self.run_range(base=main_after_first), (0, "")) + self.assertEqual(len(self.receipts_in_tree()), 2) + + def test_edits_to_one_file_that_git_combines_still_need_their_own_proof(self) -> None: + # Receipts no longer conflict, so two pull requests editing different + # parts of one file both merge. Neither browser pass saw the combined + # content, and range validation of the landed merge must say so. + lines = ["export const a = 0;", "", "", "", "export const z = 0;", ""] + self.write(CHANGED_PATH, "\n".join(lines)) + seeded = self.commit("multi-line source") + for branch, index in (("first", 0), ("second", 4)): + self.git("checkout", "--quiet", "-b", branch, seeded) + edited = list(lines) + edited[index] = edited[index].replace("0", "1") + self.verified_change(CHANGED_PATH, "\n".join(edited), branch) + self.git("checkout", "--quiet", "main") + self.git("merge", "--quiet", "--no-ff", "first", "-m", "merge first") + main_after_first = self.git("rev-parse", "HEAD") + self.git("merge", "--quiet", "--no-ff", "second", "-m", "merge second") + + status, stderr = self.run_range(base=main_after_first) + + self.assertEqual(status, 1) + self.assertIn(f"{CHANGED_PATH} final content", stderr) + + self.write_receipt([CHANGED_PATH]) + self.commit("record proof for the combined content") + self.assertEqual(self.run_range(base=main_after_first)[0], 0) + + def test_receipt_named_by_hand_is_not_evidence(self) -> None: + self.write(CHANGED_PATH, "export const a = 1;\n") + self.write_receipt([CHANGED_PATH], at=f"{RECEIPT_DIR}/storage-fix.json") + self.commit("hand-named proof") + + status, stderr = self.run_range() + + self.assertEqual(status, 1) + self.assertIn("receipt must be stored at", stderr) + + def test_commit_that_records_and_prunes_blocks_a_fully_covered_range(self) -> None: + self.verified_change(OTHER_PATH, "export const b = 1;\n", "landed") + landed = self.git("rev-parse", "HEAD") + (old,) = self.receipts_in_tree() + self.write(CHANGED_PATH, "export const a = 1;\n") + self.write_receipt([CHANGED_PATH]) + self.git("rm", "--quiet", old) + self.commit("verified change that also prunes") + + status, stderr = self.run_range(base=landed) + + self.assertEqual(status, 1) + self.assertIn("records a receipt and deletes another", stderr) + self.assertNotIn("is not verified", stderr) + + +class RetiredSharedReceiptTest(GuardRepoTestCase): + """Work verified against the shared receipt keeps its evidence; nothing new may write it.""" + + def setUp(self) -> None: + super().setUp() + self.write_receipt([CHANGED_PATH], at=LEGACY_RECEIPT_PATH) + self.base = self.commit("main while the shared receipt was current") + + def shared_receipt_change(self, path: str, text: str, message: str) -> str: + self.write(path, text) + self.write_receipt([path], at=LEGACY_RECEIPT_PATH) + return self.commit(message) + + def test_in_flight_commit_is_repaired_by_merging_main(self) -> None: + self.git("checkout", "--quiet", "-b", "in-flight", self.base) + self.shared_receipt_change(CHANGED_PATH, "export const a = 1;\n", "in flight") + self.git("checkout", "--quiet", "main") + landed = self.shared_receipt_change(OTHER_PATH, "export const b = 1;\n", "landed first") + + self.git("checkout", "--quiet", "in-flight") + merge = subprocess.run( + ["git", "-c", "user.email=t@example.com", "-c", "user.name=t", + "merge", "--quiet", "--no-ff", "main"], + cwd=self.repo_root, capture_output=True, text=True, env=self.env, + ) + self.assertNotEqual(merge.returncode, 0, "both sides rewrote the shared receipt") + + # A hand-combined resolution is a new write of the retired file. + self.write(LEGACY_RECEIPT_PATH, "{}\n") + self.git("add", LEGACY_RECEIPT_PATH) + status, _, stderr = self.run_guard() + self.assertEqual(status, 1) + self.assertIn(f"{LEGACY_RECEIPT_PATH} is retired", stderr) + self.assertIn("git checkout MERGE_HEAD", stderr) + + self.git("checkout", "MERGE_HEAD", "--", LEGACY_RECEIPT_PATH) + self.assertEqual(self.run_guard()[0], 0) + self.commit("merge main") + self.assertEqual(self.run_range(base=landed), (0, "")) + + +class PruneTest(GuardRepoTestCase): + """Landed receipts are deleted only by changes that record none.""" + + def setUp(self) -> None: + super().setUp() + self.verified_change(CHANGED_PATH, "export const a = 1;\n", "landed change") + self.landed_receipts = self.receipts_in_tree() + + def test_prunes_receipts_already_on_the_landed_ref(self) -> None: + self.git("checkout", "--quiet", "-b", "chore/prune") + + status, stdout, _ = self.run_guard("--prune", "--landed-on", "main") + + self.assertEqual(status, 0) + self.assertIn("Pruned 1 receipt(s)", stdout) + self.assertEqual( + self.git("diff", "--cached", "--name-only", "--diff-filter=D").splitlines(), + self.landed_receipts, + ) + self.assertEqual(self.run_guard()[0], 0) + self.assertEqual(self.run_guard("--prune", "--landed-on", "main")[0], 0) + + def test_refuses_on_a_branch_that_records_a_receipt(self) -> None: + self.git("checkout", "--quiet", "-b", "fix/topic") + self.verified_change(OTHER_PATH, "export const b = 1;\n", "topic change") + + status, _, stderr = self.run_guard("--prune", "--landed-on", "main") + + self.assertEqual(status, 1) + self.assertIn("records 1 receipt(s) of its own", stderr) + self.assertEqual(self.git("diff", "--cached", "--name-only"), "") + + def test_pruning_never_conflicts_with_an_open_frontend_change(self) -> None: + self.git("checkout", "--quiet", "-b", "fix/topic") + self.verified_change(OTHER_PATH, "export const b = 1;\n", "topic change") + self.git("checkout", "--quiet", "-b", "chore/prune", "main") + self.assertEqual(self.run_guard("--prune", "--landed-on", "main")[0], 0) + self.commit("prune landed receipts") + + for first, second in (("chore/prune", "fix/topic"), ("fix/topic", "chore/prune")): + self.git("checkout", "--quiet", "-B", "trial", "main") + self.git("merge", "--quiet", "--no-ff", first, "-m", f"merge {first}") + before_second = self.git("rev-parse", "HEAD") + self.git("merge", "--quiet", "--no-ff", second, "-m", f"merge {second}") + self.assertEqual(self.run_range(base=before_second)[0], 0, (first, second)) + self.assertEqual(len(self.receipts_in_tree()), 1) + if __name__ == "__main__": unittest.main() From 2a247e244e6dbb87ee663f46819b216aa4eaaf9f Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Sat, 3 Oct 2026 11:15:28 +0100 Subject: [PATCH 2/4] Delete the retired shared browser receipt The per-commit receipt change left frontend-modern/browser-verification.json in the tree, frozen, so that it would not conflict with the pull requests still rewriting it. Nothing reads the file at the tip: range mode reads a receipt from the commit that recorded it. Left in place it invites the old habit and keeps those pull requests conflicting with each other on it. The guard now refuses a staged tree or a range tip that carries the file, rather than only a commit that writes it. Without that, resolving the modify/delete conflict by keeping the file changes nothing against HEAD, passes the hook, and restores the file on main when the branch merges. A pull request that still rewrites the file is repaired by merging main and removing the file with git rm. Its commits remain valid evidence in range mode. --- .../CANONICAL_DEVELOPMENT_PROTOCOL.md | 12 +-- frontend-modern/browser-verification.json | 100 ------------------ .../browser_verification_guard.py | 34 +++--- .../browser_verification_guard_test.py | 44 +++++--- 4 files changed, 56 insertions(+), 134 deletions(-) delete mode 100644 frontend-modern/browser-verification.json diff --git a/docs/release-control/v6/internal/CANONICAL_DEVELOPMENT_PROTOCOL.md b/docs/release-control/v6/internal/CANONICAL_DEVELOPMENT_PROTOCOL.md index d4b723cd58..97a1e6dc3a 100644 --- a/docs/release-control/v6/internal/CANONICAL_DEVELOPMENT_PROTOCOL.md +++ b/docs/release-control/v6/internal/CANONICAL_DEVELOPMENT_PROTOCOL.md @@ -255,12 +255,12 @@ frontend pull requests conflict. Until 2026-10-03 each frontend commit rewrote `frontend-modern/browser-verification.json`, so the first pull request to merge left every other one conflicting, and each conflict cost a rebuild on the new `main` and a fresh browser pass. Files that are only ever added cannot collide. -The shared file is retired: the guard refuses any commit that writes it. Range -mode still accepts it as evidence from a commit that modified it, because that -commit was verified while its own parent still carried the file. A pull request -that still rewrites it is repaired by merging `main` and taking `main`'s copy of -the file, not by a rebuild. The file is deleted in a change of its own, because -deleting it conflicts with every pull request that still rewrites it. +The shared file is retired and deleted. The guard refuses a staged tree or a +range tip that carries it, so resolving a merge by keeping the file cannot +bring it back. Range mode still accepts it as evidence from a commit that +modified it, because that commit was verified while its own parent still +carried the file. A pull request that still rewrites it is repaired by merging +`main` and removing the file with `git rm`, not by a rebuild. `scripts/release_control/browser_verification_guard.py` enforces the receipt against the staged index locally and against the final integration range in diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json deleted file mode 100644 index db181fda93..0000000000 --- a/frontend-modern/browser-verification.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "version": 1, - "base_sha": "0e90236d3bb901dc84d4d07e70d4d2a09d5e6705", - "verified_at": "2026-10-02T23:55:48Z", - "result": "passed", - "changed_paths": [ - "frontend-modern/src/components/shared/Table.tsx" - ], - "content_sha256": { - "frontend-modern/src/components/shared/Table.tsx": "8e5029d5ee9dddc86a4733fa1e7249793cdbedb135f3634e75112f8fb3b327c9" - }, - "routes": [ - "/proxmox/overview", - "/proxmox/storage", - "/proxmox/replication", - "/proxmox/ceph", - "/proxmox/mail", - "/docker/overview", - "/docker/images", - "/docker/storage", - "/docker/networks", - "/docker/swarm", - "/kubernetes/overview", - "/kubernetes/nodes", - "/kubernetes/workloads", - "/kubernetes/services", - "/kubernetes/storage", - "/kubernetes/configuration", - "/kubernetes/events", - "/truenas/overview", - "/truenas/storage", - "/truenas/services", - "/truenas/apps", - "/truenas/vms", - "/truenas/shares", - "/truenas/protection", - "/vmware/overview", - "/vmware/storage", - "/vmware/networks", - "/vmware/health", - "/vmware/activity", - "/standalone/machines", - "/standalone/availability", - "/alerts/overview", - "/alerts/history", - "/alerts/thresholds", - "/actions", - "/settings/infrastructure", - "/settings/monitoring", - "/settings/pulse-intelligence", - "/settings/organization", - "/settings/security-users", - "/settings/security-webhooks" - ], - "viewports": [ - { - "width": 1440, - "height": 900 - }, - { - "width": 1280, - "height": 900 - }, - { - "width": 900, - "height": 900 - }, - { - "width": 700, - "height": 900 - }, - { - "width": 560, - "height": 900 - }, - { - "width": 390, - "height": 900 - } - ], - "states": [ - "Mock stack on origin/main plus this change; Playwright sweep of every table on the routes above at all six viewports, before and after, recording computed th/td padding per cell and leaf elements with scrollWidth > clientWidth under hidden overflow (455 table states compared)", - "Default TableCell/TableHead with no caller padding keep 8px/12px horizontal and 2px/6px vertical padding (Settings infrastructure source and agent tables unchanged at 12px/12px/6px/6px)", - "Platform tables (Proxmox nodes, Docker hosts and containers, Kubernetes clusters/nodes/deployments/pods, TrueNAS systems, vSphere, Machines): cells 12px -> 8px horizontal at 700px and up and 8px -> 6px at 560/390px; header cells keep the stylesheet 6px horizontal and drop to 2px vertical, header rows 29px -> 21px; row height 32px unchanged; clipped-cell count improved in 89 table states, unchanged in 362; the 4 states counted worse are live mock-value drift (relative-time and usage labels) with identical or wider content boxes", - "Physical Disks table (Proxmox > Storage > Physical Disks): cells 8px -> 4px horizontal at 390px and 12px -> 6px at 700px, matching the composed px-1 sm:px-1.5 lg:px-2; clipped cells at 390px 93 -> 18; detail row 12px -> 16px horizontal at 700px and 1440px", - "Storage pools table unchanged at 6px/8px cell padding across 390, 560 and 700px", - "Inline detail rows (Proxmox node, Kubernetes controller, TrueNAS system expansions): detail cell padding 12px/12px/2px/2px -> 0, detail panel inset now only its own px-2 sm:px-4; incident timeline panel in Alert History gets its p-3", - "Alerts overview stats table: dot column right padding 12px -> 0 and value column 12px -> 6px at 700px and up as its classes name; Alert History header cells 12px -> 8px horizontal; AI cost table (Settings > Pulse Intelligence) Est. USD column 12px -> 4px as its px-1 names", - "Workloads table header cells now 6px/8px to match their raw td data cells; Proxmox replication compact-mode !px-1 override still applies at 390px", - "Reactive case: switching a cell class between px-1 and a non-padding class re-resolves the base padding (unit test)" - ], - "interactions": [ - "Load each route at 1440, 1280, 900, 700, 560 and 390px and run the padding and clip sweep over all tables", - "Click Physical Disks on Proxmox > Storage at 390, 560, 700 and 1440px and expand the first disk row", - "Expand the first node row on Proxmox > Overview, the first deployment row on Kubernetes > Workloads, the first host row on Docker > Overview and the first system row on TrueNAS > Overview at 390, 560, 700 and 1440px", - "Expand the first alert row incident timeline on Alerts > History at 1440px", - "Compare before and after JSON sweeps per route, tab, viewport and table; re-measure the four states counted worse three times each", - "Revert and restore the change on the running dev server to A/B the Kubernetes clusters memory bar label at 560px" - ] -} diff --git a/scripts/release_control/browser_verification_guard.py b/scripts/release_control/browser_verification_guard.py index 1de3ed3f0a..71dd877d3c 100644 --- a/scripts/release_control/browser_verification_guard.py +++ b/scripts/release_control/browser_verification_guard.py @@ -30,9 +30,7 @@ RECEIPT_DIR = "frontend-modern/browser-verification" # The single shared receipt every frontend commit rewrote before per-commit # receipts. It is retired: nothing may write it again. Range mode still reads # it from commits that modified it, because those commits were verified -# honestly against a tree that carried it and are still merging. The file -# itself is deleted in a change of its own, since deleting it conflicts with -# every open pull request that still rewrites it. +# honestly against a tree that carried it and are still merging. LEGACY_RECEIPT_PATH = "frontend-modern/browser-verification.json" RECEIPT_FINGERPRINT_LENGTH = 16 DEFAULT_LANDED_REF = "origin/main" @@ -452,7 +450,8 @@ PRUNE_SEPARATELY = ( RETIRED_RECEIPT = ( - f"{LEGACY_RECEIPT_PATH} is retired and must not be written. Each commit now " + f"{LEGACY_RECEIPT_PATH} is retired and must not exist. Remove it with git rm; a " + "receipt it held stays valid in the commit that recorded it. Each commit now " f"records its receipt in its own file under {RECEIPT_DIR}/, so concurrent " "frontend changes no longer conflict." ) @@ -472,11 +471,15 @@ def range_receipt_coverage( that fails contributes no coverage and is reported as a diagnostic. Violations are returned separately because they block the range even when - coverage is complete: a commit that both records and deletes receipts. + coverage is complete: a tip that still carries the retired shared receipt + (recreated, or kept while resolving a merge with the main that deleted + it), or a commit that both records and deletes receipts. """ covered: dict[str, set[str]] = {} diagnostics: list[str] = [] violations: list[str] = [] + if LEGACY_RECEIPT_PATH in blob_ids(head, [LEGACY_RECEIPT_PATH], repo_root=repo_root): + violations.append(RETIRED_RECEIPT) for commit in receipt_commits_in_range(base, head, repo_root=repo_root): try: changes = receipt_changes(commit=commit, repo_root=repo_root) @@ -677,20 +680,25 @@ def check_commit( inherited = merged_in_paths( sorted({*changes, *frontend_paths}), commit=commit, repo_root=repo_root ) + # The index is held to the tree it will become: a merge that keeps the + # shared receipt changes nothing against HEAD yet would restore it on + # main. A named commit is held only to what it writes itself. + retired_receipt_written = ( + changes.get(LEGACY_RECEIPT_PATH, "D") != "D" + if commit + else LEGACY_RECEIPT_PATH + in blob_ids(None, [LEGACY_RECEIPT_PATH], repo_root=repo_root) + ) except subprocess.CalledProcessError as exc: return blocked(f"unable to inspect browser verification state: {exc}") - changes = {path: status for path, status in changes.items() if path not in inherited} - if changes.get(LEGACY_RECEIPT_PATH, "D") != "D": + if retired_receipt_written: return blocked( RETIRED_RECEIPT, - advice=[ - "While concluding a merge, take the merged parent's version: " - f"git checkout MERGE_HEAD -- {LEGACY_RECEIPT_PATH}" - if merging - else f"Unstage it and run: {GUARD_COMMAND} --write-template" - ], + advice=[f"For a new receipt, run: {GUARD_COMMAND} --write-template"], ) + + changes = {path: status for path, status in changes.items() if path not in inherited} frontend_paths = [path for path in frontend_paths if path not in inherited] recorded = sorted( path for path, status in changes.items() if status != "D" and path != LEGACY_RECEIPT_PATH diff --git a/scripts/release_control/browser_verification_guard_test.py b/scripts/release_control/browser_verification_guard_test.py index 7244377fef..1ea81ef175 100644 --- a/scripts/release_control/browser_verification_guard_test.py +++ b/scripts/release_control/browser_verification_guard_test.py @@ -61,6 +61,9 @@ class BrowserVerificationGuardTest(unittest.TestCase): self.assertLess(formatter, guard) + def test_shared_receipt_is_retired_from_the_tree(self) -> None: + self.assertFalse((REPO_ROOT / LEGACY_RECEIPT_PATH).exists()) + def test_frontend_runtime_paths_exclude_tests_and_receipt(self) -> None: self.assertEqual( frontend_runtime_paths( @@ -722,23 +725,21 @@ class IntegrationRangeTest(GuardRepoTestCase): class RetiredSharedReceiptTest(GuardRepoTestCase): - """Work verified against the shared receipt keeps its evidence; nothing new may write it.""" + """Work verified against the shared receipt keeps its evidence; nothing new may use it.""" def setUp(self) -> None: super().setUp() self.write_receipt([CHANGED_PATH], at=LEGACY_RECEIPT_PATH) self.base = self.commit("main while the shared receipt was current") - def shared_receipt_change(self, path: str, text: str, message: str) -> str: - self.write(path, text) - self.write_receipt([path], at=LEGACY_RECEIPT_PATH) - return self.commit(message) - - def test_in_flight_commit_is_repaired_by_merging_main(self) -> None: + def test_in_flight_commit_survives_a_merge_of_the_retirement(self) -> None: self.git("checkout", "--quiet", "-b", "in-flight", self.base) - self.shared_receipt_change(CHANGED_PATH, "export const a = 1;\n", "in flight") + self.write(CHANGED_PATH, "export const a = 1;\n") + self.write_receipt([CHANGED_PATH], at=LEGACY_RECEIPT_PATH) + self.commit("verified before the retirement") self.git("checkout", "--quiet", "main") - landed = self.shared_receipt_change(OTHER_PATH, "export const b = 1;\n", "landed first") + self.git("rm", "--quiet", LEGACY_RECEIPT_PATH) + retired = self.commit("retire the shared receipt") self.git("checkout", "--quiet", "in-flight") merge = subprocess.run( @@ -746,20 +747,33 @@ class RetiredSharedReceiptTest(GuardRepoTestCase): "merge", "--quiet", "--no-ff", "main"], cwd=self.repo_root, capture_output=True, text=True, env=self.env, ) - self.assertNotEqual(merge.returncode, 0, "both sides rewrote the shared receipt") + self.assertNotEqual(merge.returncode, 0, "modify/delete on the shared receipt") - # A hand-combined resolution is a new write of the retired file. - self.write(LEGACY_RECEIPT_PATH, "{}\n") + # Keeping the file changes nothing against HEAD, but would restore it + # on main once this branch merges. self.git("add", LEGACY_RECEIPT_PATH) status, _, stderr = self.run_guard() self.assertEqual(status, 1) self.assertIn(f"{LEGACY_RECEIPT_PATH} is retired", stderr) - self.assertIn("git checkout MERGE_HEAD", stderr) - self.git("checkout", "MERGE_HEAD", "--", LEGACY_RECEIPT_PATH) + self.git("rm", "--quiet", "--force", LEGACY_RECEIPT_PATH) self.assertEqual(self.run_guard()[0], 0) self.commit("merge main") - self.assertEqual(self.run_range(base=landed), (0, "")) + self.assertEqual(self.run_range(base=retired), (0, "")) + + def test_recreating_the_shared_receipt_is_rejected_even_with_a_valid_one(self) -> None: + self.git("rm", "--quiet", LEGACY_RECEIPT_PATH) + retired = self.commit("retire the shared receipt") + self.write(CHANGED_PATH, "export const a = 1;\n") + self.write_receipt([CHANGED_PATH]) + self.write_receipt([CHANGED_PATH], at=LEGACY_RECEIPT_PATH) + self.commit("verified, but also recreates the shared receipt") + + status, stderr = self.run_range(base=retired) + + self.assertEqual(status, 1) + self.assertIn(f"{LEGACY_RECEIPT_PATH} is retired", stderr) + self.assertNotIn("is not verified", stderr) class PruneTest(GuardRepoTestCase): From 9e7a5ffdebcc045f710f58695ad7a1d856035ca0 Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Sat, 3 Oct 2026 15:19:35 +0100 Subject: [PATCH 3/4] Move frontend to Tailwind CSS 4 and jscpd 5 to drop braces GHSA-vfj7-8cjw-p6xm affects every braces release and has no fix, so the required frontend audit failed with seven high findings on any pull request that changes the dependency graph. Tailwind CSS 3 (chokidar, fast-glob, micromatch) and jscpd 4 (@jscpd/finder, fast-glob) were the only paths in, both dev-only, and no override can remove braces from fast-glob or micromatch. Tailwind CSS 4.3.3 through @tailwindcss/vite and jscpd 5.4.0 leave none; npm audit over the full graph reports zero. The stylesheet keeps the v3 rendering. It pins the v3 palette (inlined, so opacity modifiers compile to static colors), font stacks and line heights, keeps v3's cascade order by placing former component and unlayered rules around the generated utilities, and restores v3 hover on touch devices, gray-200 default borders, the 3px blue ring, placeholder color, button cursor, table-cell padding and the space-* and divide-* child selectors. The 900/950 shades Pulse overrode with 25% alpha are opaque now and their callers say /25, because v4 multiplies an opacity modifier into a translucent color instead of replacing it. Opacity modifiers on var() colors that v3 never generated are removed. Utility order changed from lexical to numeric, so a few caller overrides that silently lost in v3 would start to win. Touch targets keep their authored 44px phone size (the Machines row stays 32px); cosmetic overrides v3 never applied are removed, and overrides it did apply use the important suffix. Contract-Neutral: Tailwind CSS 4 migration for GHSA-vfj7-8cjw-p6xm: utility class renames and stylesheet compatibility, no subsystem contract change --- .../subsystems/deployment-installability.md | 27 + frontend-modern/DESIGN_SYSTEM.md | 2 +- frontend-modern/browser-verification.json | 834 +++++- frontend-modern/package-lock.json | 2333 +++++------------ frontend-modern/package.json | 7 +- frontend-modern/postcss.config.js | 6 - .../scripts/shared-template-registry.json | 182 +- frontend-modern/src/AppLayout.tsx | 44 +- .../src/__tests__/App.architecture.test.ts | 2 +- frontend-modern/src/api/patrol.ts | 20 +- .../src/components/AI/AICostDashboard.tsx | 18 +- .../src/components/AI/Chat/ApprovalCard.tsx | 48 +- .../AI/Chat/AssistantCommandHelpDialog.tsx | 6 +- .../src/components/AI/Chat/ChatMessages.tsx | 4 +- .../AI/Chat/MentionAutocomplete.tsx | 8 +- .../src/components/AI/Chat/MessageItem.tsx | 24 +- .../src/components/AI/Chat/ModelSelector.tsx | 2 +- .../src/components/AI/Chat/QuestionCard.tsx | 14 +- .../AI/Chat/SlashCommandAutocomplete.tsx | 2 +- .../src/components/AI/Chat/ThinkingBlock.tsx | 2 +- .../components/AI/Chat/ToolExecutionBlock.tsx | 34 +- .../AI/Chat/__tests__/AIChat.test.tsx | 24 +- .../AI/Chat/__tests__/MessageItem.test.tsx | 4 +- .../__tests__/ToolExecutionBlock.test.tsx | 10 +- .../src/components/AI/Chat/index.tsx | 92 +- .../src/components/AI/FindingsPanel.tsx | 106 +- .../AI/__tests__/FindingsPanel.test.ts | 14 +- .../src/components/AI/aiChatUtils.ts | 2 +- .../Alerts/AlertResourceTableDesktop.tsx | 14 +- .../Alerts/AlertResourceTableMobile.tsx | 24 +- .../Alerts/AlertResourceTableRow.tsx | 20 +- .../src/components/Alerts/BulkEditDialog.tsx | 8 +- .../components/Alerts/EmailProviderSelect.tsx | 4 +- .../Alerts/IncidentAssistantHandoffButton.tsx | 2 +- .../Alerts/IncidentTimelineEventCard.tsx | 4 +- .../Alerts/InvestigateAlertButton.tsx | 10 +- .../components/Alerts/ResourceTable.test.tsx | 2 +- .../src/components/Alerts/ResourceTable.tsx | 4 +- .../sections/CollapsibleSection.tsx | 22 +- .../src/components/Alerts/ThresholdsTable.tsx | 16 +- .../ThresholdsTableAgentsResourcesSection.tsx | 2 +- ...holdsTableDockerIgnoredPrefixesSection.tsx | 2 +- ...ThresholdsTableDockerServiceGapSection.tsx | 4 +- ...resholdsTableDockerUpdateAlertsSection.tsx | 2 +- .../components/Alerts/WebhookConfigForm.tsx | 10 +- .../components/Alerts/WebhookConfigList.tsx | 24 +- .../src/components/AppBootstrapStatus.tsx | 2 +- .../src/components/Brand/PulseBrandMark.tsx | 2 +- .../Discovery/AvailabilityProposalCard.tsx | 20 +- .../src/components/Discovery/DiscoveryTab.tsx | 78 +- .../components/Docker/SwarmServicesDrawer.tsx | 12 +- .../src/components/ErrorBoundary.tsx | 4 +- .../src/components/GitHubStarBanner.tsx | 2 +- .../AvailabilityProbeStatusCard.tsx | 12 +- .../Infrastructure/ClusterDeployBanner.tsx | 2 +- .../MaintenanceVerificationSection.tsx | 18 +- .../Infrastructure/ReportMergeModal.tsx | 6 +- .../Infrastructure/ResourceActionHistory.tsx | 26 +- .../ResourceCorrelationSummary.tsx | 32 +- .../Infrastructure/ResourceDetailDrawer.tsx | 6 +- .../ResourceDetailDrawerOverviewTab.tsx | 68 +- .../ResourceDetailDrawerSupportDisclosure.tsx | 2 +- .../Infrastructure/ResourceDetailSummary.tsx | 8 +- .../Infrastructure/ResourceFacetSummary.tsx | 4 +- .../ResourceInvestigationContextTables.tsx | 2 +- .../ResourceOperatorStateSection.tsx | 62 +- .../Infrastructure/ResourcePolicySummary.tsx | 2 +- .../UnifiedResourceHostTableCard.tsx | 8 +- .../UnifiedResourcePBSTableSection.tsx | 8 +- .../UnifiedResourcePMGTableSection.tsx | 8 +- .../UnifiedResourceSourceBadgeCell.tsx | 2 +- .../ResourceDetailDrawer.history.test.tsx | 2 +- ...urceDetailDrawer.identity-runtime.test.tsx | 4 +- ...ourceDetailDrawer.truenas-details.test.tsx | 2 +- .../ResourceOperatorStateSection.test.ts | 2 +- ...esourceTable.performance.contract.test.tsx | 2 +- ...lDrawerOperationalModel.branchcov2.test.ts | 4 +- ...sourceDetailDrawerOperationalModel.test.ts | 10 +- .../unifiedResourceTableStateModel.test.ts | 2 +- .../resourceDetailDrawerOperationalModel.ts | 4 +- .../Kubernetes/K8sDeploymentsDrawer.tsx | 10 +- .../Kubernetes/K8sNamespacesDrawer.tsx | 10 +- frontend-modern/src/components/Login.tsx | 30 +- .../src/components/OrgSwitcher.tsx | 2 +- .../src/components/SecurityWarning.tsx | 6 +- .../Settings/AIChatMaintenanceSection.tsx | 4 +- .../Settings/AIModelSelectionSection.tsx | 45 +- .../AIProviderConfigurationSection.tsx | 12 +- .../Settings/AIRuntimeControlsSection.tsx | 38 +- .../src/components/Settings/AISettings.tsx | 8 +- .../Settings/AISettingsStatusAndActions.tsx | 2 +- .../components/Settings/APITokenManager.tsx | 42 +- .../Settings/APITokenManagerDialogs.tsx | 10 +- .../Settings/AgentIntegrationsPanel.tsx | 20 +- .../Settings/AgentProfilesPanel.tsx | 24 +- .../src/components/Settings/AuditLogPanel.tsx | 6 +- .../components/Settings/AuditWebhookPanel.tsx | 6 +- .../Settings/AvailabilitySettingsPanel.tsx | 4 +- .../Settings/BackupTransferDialogs.tsx | 8 +- .../BillingAdminOrganizationsTable.tsx | 2 +- .../components/Settings/BillingAdminPanel.tsx | 2 +- .../Settings/BrandingSettingsCard.tsx | 4 +- .../Settings/ChangePasswordModal.tsx | 8 +- .../Settings/CommercialBillingSections.tsx | 4 +- .../AvailabilityTargetSlot.tsx | 2 +- .../CredentialSlots/NodeCredentialSlot.tsx | 6 +- .../components/Settings/DataHandlingPanel.tsx | 24 +- .../components/Settings/DiagnosticsPanel.tsx | 2 +- .../Settings/DiagnosticsResultsPanel.tsx | 25 +- .../Settings/DiscoverySettingsForm.tsx | 10 +- .../Settings/DockerRuntimeSettingsCard.tsx | 2 +- .../Settings/GeneralSettingsPanel.tsx | 4 +- .../GuestDockerDiscoverySettingsCard.tsx | 2 +- .../InfrastructureAgentDoctorPage.tsx | 18 +- .../InfrastructureInstallerSection.tsx | 70 +- .../Settings/InfrastructureSourceManager.tsx | 86 +- .../Settings/InfrastructureSourcePicker.tsx | 10 +- .../Settings/InfrastructureWorkspace.tsx | 20 +- .../NetworkBoundarySettingsSection.tsx | 10 +- .../NodeModalClusterMembersSection.tsx | 2 +- .../Settings/NodeModalMonitoringSection.tsx | 10 +- .../Settings/NodeModalSetupGuideSection.tsx | 62 +- .../Settings/NodeModalStatusFooter.tsx | 10 +- .../OrganizationAccessManagementSection.tsx | 6 +- .../OrganizationAccessMembersSection.tsx | 2 +- .../OrganizationOverviewDetailsSection.tsx | 2 +- .../OrganizationSharingCreateSection.tsx | 16 +- .../Settings/ProLicensePlanSection.tsx | 8 +- .../Settings/QuickSecuritySetup.tsx | 26 +- .../Settings/RecoverySettingsPanel.tsx | 26 +- .../Settings/RelaySettingsPanel.tsx | 2 +- .../components/Settings/ReportingPanel.tsx | 8 +- .../components/Settings/ResourcePicker.tsx | 12 +- .../components/Settings/RolesEditorDialog.tsx | 6 +- .../components/Settings/SSOProvidersPanel.tsx | 14 +- .../components/Settings/SecurityAuthPanel.tsx | 12 +- .../Settings/SecurityOverviewPanel.tsx | 12 +- .../SelfHostedCommercialRecoverySection.tsx | 4 +- .../components/Settings/SettingsPageShell.tsx | 24 +- .../Settings/SuggestProfileModal.tsx | 20 +- .../components/Settings/SystemLogsPanel.tsx | 4 +- .../Settings/UpdateInstallGuide.tsx | 26 +- .../Settings/UpdatesSettingsPanel.tsx | 14 +- .../Settings/UserAssignmentsDialog.tsx | 8 +- .../Settings/UserAssignmentsPanel.tsx | 4 +- .../Settings/__tests__/AISettings.test.tsx | 2 +- .../__tests__/APITokenManager.test.tsx | 2 +- .../InfrastructureSourceManager.test.tsx | 2 +- .../__tests__/ProLicensePanel.test.tsx | 2 +- ...ilitySettingsModel.branchcov0724pm.test.ts | 2 +- .../availabilitySettingsModel.test.ts | 8 +- .../connectionsTableModel.coverage.test.ts | 8 +- .../__tests__/settingsArchitecture.test.ts | 6 +- .../Settings/availabilitySettingsModel.ts | 12 +- .../Settings/connectionsTableModel.ts | 10 +- .../Settings/useConnectionsLedger.ts | 10 +- .../Settings/useProLicensePanelState.ts | 2 +- .../SetupWizard/SetupCompletionPanel.tsx | 14 +- .../SetupWizard/steps/SecurityStep.tsx | 6 +- .../SetupWizard/steps/WelcomeStep.tsx | 8 +- .../components/Storage/EnhancedStorageBar.tsx | 2 +- .../Storage/StorageDetailKeyValueRow.tsx | 2 +- .../components/Storage/StoragePoolDetail.tsx | 14 +- .../components/Storage/StoragePoolsTable.tsx | 8 +- .../__tests__/StoragePoolDetail.test.tsx | 2 +- .../src/components/Toast/Toast.tsx | 10 +- .../components/Toast/__tests__/Toast.test.tsx | 2 +- .../src/components/TokenRevealDialog.tsx | 10 +- .../src/components/UpdateBanner.tsx | 34 +- .../components/UpdateConfirmationModal.tsx | 4 +- .../src/components/WhatsNewCard.tsx | 4 +- .../AvailabilityProbeSuggestionCard.tsx | 4 +- .../src/components/Workloads/DiskList.tsx | 4 +- .../Workloads/DrawerDiskListCard.tsx | 6 +- .../components/Workloads/EnhancedCPUBar.tsx | 2 +- .../Workloads/GuestDrawerHistory.tsx | 12 +- .../Workloads/GuestPhysicalDisks.tsx | 2 +- .../src/components/Workloads/GuestRow.tsx | 2 +- .../components/Workloads/GuestRowCells.tsx | 12 +- .../Workloads/MetricMiniSparkline.tsx | 2 +- .../components/Workloads/StackedDiskBar.tsx | 10 +- .../Workloads/StackedMemoryBar.test.tsx | 3 +- .../components/Workloads/StackedMemoryBar.tsx | 9 +- .../components/Workloads/ThresholdSlider.tsx | 6 +- .../components/Workloads/WorkloadPanel.tsx | 12 +- .../components/Workloads/WorkloadsFilter.tsx | 4 +- .../components/Workloads/WorkloadsSurface.tsx | 4 +- .../components/Workloads/WorkloadsTable.tsx | 2 +- .../GuestDrawerOverview.filesystems.test.tsx | 2 +- ...loadsSurface.performance.contract.test.tsx | 8 +- .../components/Workloads/useGuestRowState.ts | 10 +- .../CommercialMigrationBanner.test.tsx | 6 +- .../components/__tests__/DemoBanner.test.tsx | 4 +- .../__tests__/ErrorBoundary.test.tsx | 4 +- .../__tests__/GitHubStarBanner.test.tsx | 2 +- .../UpdateConfirmationModal.test.tsx | 4 +- .../src/components/patrol/ApprovalBanner.tsx | 8 +- .../src/components/patrol/ApprovalSection.tsx | 4 +- .../patrol/InvestigationMessages.tsx | 22 +- .../patrol/InvestigationSection.tsx | 18 +- .../components/patrol/RemediationStatus.tsx | 8 +- .../src/components/patrol/RunHistoryEntry.tsx | 18 +- .../src/components/patrol/RunHistoryPanel.tsx | 2 +- .../components/patrol/RunToolCallTrace.tsx | 10 +- .../src/components/shared/AIModelPicker.tsx | 12 +- .../src/components/shared/Button.test.tsx | 8 +- .../src/components/shared/CalloutCard.tsx | 32 +- .../src/components/shared/Card.tsx | 8 +- .../src/components/shared/ColumnPicker.tsx | 2 +- .../components/shared/CommandPaletteModal.tsx | 4 +- .../shared/ContainerUpdateBadge.tsx | 4 +- .../src/components/shared/CopyableCodeRow.tsx | 2 +- .../components/shared/DetailSectionTable.tsx | 12 +- .../src/components/shared/Dialog.tsx | 4 +- .../shared/DiscoveryReadinessBadge.tsx | 10 +- .../shared/DrawerAttentionSection.tsx | 9 +- .../components/shared/ExternalTextLink.tsx | 8 +- .../components/shared/FeatureGateSection.tsx | 2 +- .../shared/FilterBar/AddFilterMenu.tsx | 6 +- .../shared/FilterBar/FilterBar.test.tsx | 4 +- .../components/shared/FilterBar/FilterBar.tsx | 4 +- .../shared/FilterBar/FilterChip.tsx | 4 +- .../FilterBar/ViewOptionsDisclosure.test.tsx | 2 +- .../FilterBar/ViewOptionsDisclosure.tsx | 2 +- .../shared/FilterButtonGroup.test.tsx | 4 +- .../components/shared/FilterButtonGroup.tsx | 2 +- .../components/shared/FilterToolbar.test.tsx | 2 +- .../src/components/shared/FilterToolbar.tsx | 6 +- frontend-modern/src/components/shared/Form.ts | 8 +- .../src/components/shared/HelpIcon.tsx | 6 +- .../src/components/shared/HistoryChart.tsx | 4 +- .../components/shared/HistoryChartHeader.tsx | 6 +- .../components/shared/HistoryChartOverlay.tsx | 2 +- .../components/shared/HistoryChartTooltip.tsx | 2 +- .../components/shared/InfoCardFrame.test.tsx | 4 +- .../src/components/shared/InfoCardFrame.tsx | 2 +- .../shared/KeyboardShortcutsModal.tsx | 2 +- .../components/shared/MetadataBadge.test.tsx | 2 +- .../src/components/shared/MetadataBadge.tsx | 30 +- .../src/components/shared/MobileNavBar.tsx | 6 +- .../src/components/shared/NodeGroupHeader.tsx | 4 +- .../components/shared/ObjectDrawerHeader.tsx | 4 +- .../src/components/shared/ProgressBar.tsx | 2 +- .../src/components/shared/PulseDataGrid.tsx | 4 +- .../src/components/shared/SearchField.tsx | 6 +- .../components/shared/SearchTipsPopover.tsx | 10 +- .../shared/SettingsLoadingSkeleton.tsx | 2 +- .../SharedPrimitives.guardrails.test.ts | 272 +- .../components/shared/SkipToContentLink.tsx | 2 +- .../src/components/shared/StatusDot.tsx | 2 +- .../shared/StatusIndicatorBadge.test.tsx | 4 +- .../shared/StatusIndicatorBadge.tsx | 2 +- .../src/components/shared/Subtabs.tsx | 10 +- .../src/components/shared/TableCardHeader.tsx | 4 +- .../src/components/shared/TagBadges.tsx | 4 +- .../shared/TechnicalDetailsDisclosure.tsx | 4 +- .../shared/TlsVerificationWarningBanner.tsx | 2 +- .../src/components/shared/Tooltip.tsx | 4 +- .../src/components/shared/TooltipPortal.tsx | 2 +- .../components/shared/WebInterfaceLink.tsx | 4 +- .../shared/WebInterfaceUrlField.tsx | 12 +- .../components/shared/WorkloadTypeBadge.tsx | 2 +- .../__tests__/DetailSectionTable.test.tsx | 4 +- .../__tests__/SearchTipsPopover.test.tsx | 4 +- .../shared/__tests__/StatusDot.test.tsx | 2 +- .../shared/__tests__/Table.test.tsx | 2 +- .../__tests__/WebInterfaceUrlField.test.tsx | 2 +- ...inerUpdateBadgeModel.branchcov0717.test.ts | 17 +- ...ntainerUpdateBadgeModel.branchcov2.test.ts | 8 +- .../dialogModel.branchcov0712.test.ts | 6 +- .../mobileNavBarModel.branchcov0712.test.ts | 2 +- ...rchTipsPopoverModel.branchcov0712c.test.ts | 4 +- ...tionCardGroupModel.branchcov0720pm.test.ts | 12 +- .../statusBadgeModel.branchcov0713.test.ts | 2 +- .../src/components/shared/buttonModel.ts | 52 +- .../shared/cards/NetworkInterfacesCard.tsx | 4 +- .../src/components/shared/cards/RaidCard.tsx | 4 +- .../shared/containerUpdateBadgeModel.ts | 10 +- .../src/components/shared/dialogModel.ts | 2 +- .../shared/filterButtonGroupModel.ts | 20 +- .../shared/groupedTableRowPresentation.ts | 4 +- .../components/shared/mobileNavBarModel.ts | 4 +- .../shared/searchInputEnhancementsModel.ts | 12 +- .../shared/searchTipsPopoverModel.ts | 4 +- .../components/shared/selectablePillModel.ts | 4 +- .../shared/selectionCardGroupModel.ts | 8 +- .../components/shared/sourcePlatformBadges.ts | 2 +- .../src/components/shared/statusBadgeModel.ts | 2 +- .../shared/summaryInteractionA11y.ts | 2 +- .../src/components/shared/tagInputModel.ts | 6 +- .../src/components/shared/toggleModel.ts | 2 +- .../features/actions/ActionDecisionPacket.tsx | 10 +- .../features/actions/ActionReviewDialog.tsx | 4 +- .../AlertAppriseDestinationsSection.tsx | 4 +- .../alerts/AlertDeadManDestinationSection.tsx | 18 +- .../alerts/AlertDeliveryHealthCard.tsx | 2 +- .../features/alerts/AlertDeliveryLogCard.tsx | 10 +- .../alerts/AlertDeliveryPausedCard.tsx | 6 +- .../alerts/AlertDestinationsLoadErrorCard.tsx | 4 +- .../alerts/AlertDestinationsLoadingState.tsx | 28 +- .../alerts/AlertEscalationSection.tsx | 4 +- .../alerts/AlertHistoryAdministrationCard.tsx | 2 +- .../alerts/AlertHistoryFrequencyCard.tsx | 6 +- .../alerts/AlertIncidentSynthesisSummary.tsx | 4 +- .../AlertOverviewActiveAlertsSection.tsx | 2 +- .../alerts/AlertOverviewAlertCard.tsx | 4 +- .../alerts/AlertQueueActionFeedback.tsx | 2 +- .../src/features/alerts/AlertSnoozeAction.tsx | 2 +- .../alerts/ResourceMonitoringPolicyAction.tsx | 4 +- .../src/features/alerts/tabs/ScheduleTab.tsx | 2 +- .../features/docker/DockerConfigsTable.tsx | 4 +- .../DockerContainerLifecycleControls.tsx | 8 +- .../docker/DockerHostDrawerOverview.tsx | 4 +- .../src/features/docker/DockerHostsTable.tsx | 4 +- .../docker/DockerNativeTableShared.tsx | 2 +- .../features/docker/DockerNetworksTable.tsx | 14 +- .../features/docker/DockerSecretsTable.tsx | 2 +- .../features/docker/DockerServicesTable.tsx | 7 +- .../docker/DockerStorageUsageTable.tsx | 8 +- .../src/features/docker/DockerTasksTable.tsx | 4 +- .../features/docker/DockerVolumesTable.tsx | 4 +- .../kubernetes/KubernetesAutoscalingTable.tsx | 11 +- .../kubernetes/KubernetesClustersTable.tsx | 4 +- .../kubernetes/KubernetesConfigTable.tsx | 8 +- .../kubernetes/KubernetesControllersTable.tsx | 6 +- .../kubernetes/KubernetesEventsTable.tsx | 11 +- .../kubernetes/KubernetesNetworkingTable.tsx | 9 +- .../kubernetes/KubernetesNodesTable.tsx | 6 +- .../kubernetes/KubernetesPolicyTable.tsx | 8 +- .../kubernetes/KubernetesServicesTable.tsx | 11 +- .../kubernetes/KubernetesStorageTable.tsx | 7 +- .../patrol/PatrolAttentionWorkbench.tsx | 48 +- .../PatrolAutopilotAcknowledgementDialog.tsx | 2 +- .../patrol/PatrolIntelligenceBanners.tsx | 40 +- .../patrol/PatrolIntelligenceHeader.tsx | 12 +- .../patrol/PatrolIntelligenceSurface.tsx | 8 +- .../patrol/PatrolIntelligenceWorkspace.tsx | 9 +- .../features/patrol/PatrolObjectivesPanel.tsx | 10 +- .../features/patrol/PatrolRecentWorkPanel.tsx | 2 +- .../patrol/PatrolWeeklyDigestCard.tsx | 2 +- .../patrolCommercialBoundary.test.ts | 4 +- .../PlatformOutdatedAgentNotice.tsx | 4 +- .../platformPage/PlatformWindowedRows.tsx | 4 +- .../__tests__/PlatformDetailTable.test.tsx | 2 +- .../platformOverviewLayout.guardrails.test.ts | 8 +- .../__tests__/sharedPlatformPage.test.ts | 8 +- .../platformPage/sharedPlatformPage.tsx | 16 +- .../features/proxmox/BackupActivityChart.tsx | 16 +- .../proxmox/ProxmoxBackupServersTable.tsx | 2 +- .../proxmox/ProxmoxBackupsCoverageStrip.tsx | 6 +- .../features/proxmox/ProxmoxBackupsTable.tsx | 6 +- .../proxmox/ProxmoxCephClusterDrawer.tsx | 4 +- .../src/features/proxmox/ProxmoxCephTable.tsx | 2 +- .../features/proxmox/ProxmoxCoverageTable.tsx | 4 +- .../proxmox/ProxmoxMailGatewayDrawer.tsx | 10 +- .../proxmox/ProxmoxMailGatewayTable.tsx | 2 +- .../features/proxmox/ProxmoxNodesTable.tsx | 18 +- .../features/proxmox/ProxmoxPageSurface.tsx | 2 +- .../proxmox/ProxmoxRecoverableTable.tsx | 2 +- .../proxmox/ProxmoxReplicationTable.tsx | 16 +- .../ProxmoxReplicationTable.test.tsx | 4 +- .../proxmoxBackupsTableShared.test.tsx | 2 +- .../__tests__/proxmoxHostTableModel.test.ts | 10 +- .../proxmox/proxmoxBackupsTableShared.tsx | 6 +- .../features/proxmox/proxmoxHostTableModel.ts | 8 +- .../standalone/AgentsMachinesTable.tsx | 24 +- .../standalone/AvailabilityFleetView.tsx | 10 +- .../standalone/StandalonePageSurface.tsx | 2 +- .../__tests__/storageDomain.test.ts | 8 +- .../storageFilterPresentation.test.ts | 2 +- ...RowAlertPresentation.branchcov0713.test.ts | 4 +- .../storageBackups/detailPresentation.ts | 15 +- .../storageBackups/diskPresentation.ts | 8 +- .../storageBackups/groupPresentation.ts | 6 +- .../features/storageBackups/storageDomain.ts | 8 +- .../storageFilterPresentation.ts | 2 +- .../storageBackups/storagePagePresentation.ts | 2 +- .../storageRowAlertPresentation.ts | 6 +- .../storageBackups/zfsPresentation.ts | 2 +- .../src/features/truenas/TrueNASAppsTable.tsx | 6 +- .../features/truenas/TrueNASServicesTable.tsx | 2 +- .../features/truenas/TrueNASSystemsTable.tsx | 4 +- .../src/features/vmware/VmwarePageSurface.tsx | 2 +- .../src/features/vmware/VsphereHostsTable.tsx | 6 +- frontend-modern/src/index.css | 1539 ++++++----- frontend-modern/src/pages/Actions.tsx | 8 +- frontend-modern/src/pages/Alerts.tsx | 2 +- frontend-modern/src/pages/Docs.tsx | 2 +- frontend-modern/src/pages/PricingHandoff.tsx | 6 +- .../pages/__tests__/Alerts.helpers.test.ts | 20 +- .../__tests__/dependencySecurity.test.ts | 9 +- frontend-modern/src/tailwind-palette.css | 291 ++ ...ionAuditPresentation.branchcov0712.test.ts | 10 +- ...actionAuditPresentation.branchcov2.test.ts | 10 +- ...pabilityPresentation.branchcov0713.test.ts | 12 +- ...iFindingPresentation.branchcov0717.test.ts | 14 +- .../aiFindingPresentation.coverage2.test.ts | 6 +- .../aiProviderHealthPresentation.test.ts | 6 +- ...SettingsPresentation.branchcov0718.test.ts | 10 +- .../__tests__/aiSettingsPresentation.test.ts | 4 +- .../alertActivationPresentation.test.ts | 4 +- .../alertFrequencyPresentation.test.ts | 4 +- ...alertGroupingPresentation.coverage.test.ts | 6 +- .../alertGroupingPresentation.test.ts | 4 +- ...HistoryPresentation.branchcov0712c.test.ts | 8 +- ...ncidentPresentation.branchcov0712b.test.ts | 18 +- ...IncidentPresentation.branchcov0717.test.ts | 61 +- .../alertIncidentPresentation.test.ts | 39 +- ...alertOverviewPresentation.coverage.test.ts | 2 +- .../alertOverviewPresentation.test.ts | 10 +- ...rceTablePresentation.branchcov0718.test.ts | 2 +- .../alertResourceTablePresentation.test.ts | 4 +- .../alertSchedulePresentation.test.ts | 6 +- .../__tests__/alertTabsPresentation.test.ts | 4 +- ...auditLogPresentation.branchcov0713.test.ts | 6 +- .../__tests__/auditLogPresentation.test.ts | 4 +- ...coveryPresentation.branchcov0723pm.test.ts | 2 +- .../__tests__/emptyStatePresentation.test.ts | 2 +- .../frontendResourceTypeBoundaries.test.ts | 44 +- .../licensePresentation.coverage.test.ts | 8 +- .../__tests__/licensePresentation.test.ts | 4 +- ...odeModalPresentation.branchcov0718.test.ts | 14 +- .../__tests__/patrolRunPresentation.test.ts | 2 +- .../patrolSummaryPresentation.test.ts | 2 +- .../recoveryArtifactModePresentation.test.ts | 4 +- ...veryItemTypePresentation.branchcov.test.ts | 8 +- ...temTypePresentation.branchcov0712b.test.ts | 11 +- ...veryOutcomePresentation.branchcov2.test.ts | 8 +- .../recoveryTimelinePresentation.test.ts | 4 +- .../__tests__/remediationPresentation.test.ts | 4 +- ...rceBadgePresentation.branchcov0717.test.ts | 2 +- ...eBadgePresentation.branchcov0725am.test.ts | 2 +- ...sourceBadgePresentation.branchcov2.test.ts | 2 +- ...ourceChangePresentation.branchcov2.test.ts | 20 +- ...ityScorePresentation.branchcov0718.test.ts | 20 +- .../securityScorePresentation.test.ts | 11 +- ...ProviderPresentation.branchcov0717.test.ts | 14 +- .../utils/__tests__/status.branchcov2.test.ts | 8 +- .../__tests__/systemLogsPresentation.test.ts | 2 +- ...tStatusPresentation.branchcov0712c.test.ts | 4 +- ...ntStatusPresentation.branchcov0713.test.ts | 9 +- .../unifiedAgentStatusPresentation.test.ts | 8 +- .../workloadGuestPresentation.test.ts | 8 +- ...oadTypePresentation.branchcov0712c.test.ts | 16 +- .../src/utils/actionAuditPresentation.ts | 16 +- .../src/utils/agentCapabilityPresentation.ts | 12 +- .../agentProfileSuggestionPresentation.ts | 4 +- .../src/utils/aiControlLevelPresentation.ts | 18 +- .../src/utils/aiFindingPresentation.ts | 74 +- .../src/utils/aiProviderHealthPresentation.ts | 6 +- .../src/utils/aiSettingsPresentation.ts | 4 +- .../src/utils/alertActivationPresentation.ts | 2 +- .../src/utils/alertFrequencyPresentation.ts | 4 +- .../src/utils/alertGroupingPresentation.ts | 4 +- .../src/utils/alertHistoryPresentation.ts | 8 +- .../src/utils/alertIncidentPresentation.ts | 32 +- .../src/utils/alertOverviewPresentation.ts | 10 +- .../utils/alertResourceTablePresentation.ts | 4 +- .../src/utils/alertSchedulePresentation.ts | 4 +- .../src/utils/alertSeverityPresentation.ts | 8 +- .../src/utils/alertTabsPresentation.ts | 6 +- frontend-modern/src/utils/alerts.ts | 12 +- .../src/utils/auditLogPresentation.ts | 14 +- .../src/utils/auditWebhookPresentation.ts | 4 +- .../src/utils/discoveryPresentation.ts | 16 +- .../src/utils/emptyStatePresentation.ts | 8 +- .../src/utils/environmentLockPresentation.ts | 2 +- .../src/utils/licensePresentation.ts | 34 +- .../src/utils/nodeModalPresentation.ts | 6 +- frontend-modern/src/utils/orgUtils.ts | 6 +- .../src/utils/patrolRunPresentation.ts | 6 +- .../src/utils/patrolSummaryPresentation.ts | 6 +- .../platformSupportManifest.generated.ts | 52 +- frontend-modern/src/utils/raidPresentation.ts | 6 +- .../utils/recoveryArtifactModePresentation.ts | 4 +- .../src/utils/recoveryItemTypePresentation.ts | 4 +- .../src/utils/recoveryOutcomePresentation.ts | 8 +- .../recoveryTimelineChartPresentation.ts | 2 +- .../src/utils/recoveryTimelinePresentation.ts | 6 +- .../src/utils/relayPresentation.ts | 8 +- .../src/utils/remediationPresentation.ts | 4 +- .../src/utils/resourceBadgePresentation.ts | 6 +- .../src/utils/resourceChangePresentation.ts | 40 +- .../src/utils/resourcePolicyPresentation.ts | 14 +- .../src/utils/securityScorePresentation.ts | 29 +- .../src/utils/semanticTonePresentation.ts | 8 +- .../src/utils/serviceHealthPresentation.ts | 6 +- frontend-modern/src/utils/sourcePlatforms.ts | 2 +- .../src/utils/sourceTypePresentation.ts | 6 +- .../src/utils/ssoProviderPresentation.ts | 14 +- frontend-modern/src/utils/status.ts | 8 +- .../src/utils/systemLogsPresentation.ts | 2 +- .../utils/unifiedAgentStatusPresentation.ts | 8 +- .../src/utils/updatesPresentation.ts | 4 +- .../src/utils/workloadGuestPresentation.ts | 10 +- .../src/utils/workloadTypePresentation.ts | 8 +- frontend-modern/tailwind.config.js | 77 - frontend-modern/vite.config.ts | 7 +- scripts/check-assistant-identity.mjs | 6 +- .../scripts/managed-local-backend.mjs | 2 - 500 files changed, 5301 insertions(+), 5051 deletions(-) delete mode 100644 frontend-modern/postcss.config.js create mode 100644 frontend-modern/src/tailwind-palette.css delete mode 100644 frontend-modern/tailwind.config.js diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index efa5d4e8c8..606c88a4ab 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -2814,6 +2814,33 @@ promotion and rollback boundary unchanged. `brace-expansion` floors to 1.1.21 and 5.0.12 and the reviewed `dompurify` floor to 3.4.16, so a later downgrade is rejected. +### braces removed from the frontend dependency graph (3 Oct 2026) + +`GHSA-vfj7-8cjw-p6xm` affects every `braces` release (up to 3.0.3) and has no +patched version, so the required frontend dependency audit failed with seven +high findings on every pull request that changes the dependency graph. All of +them were dev-only: Tailwind CSS 3 reached `braces` through `chokidar`, +`fast-glob` and `micromatch`, and `jscpd` 4 through `@jscpd/finder` and +`fast-glob`. Every `fast-glob` and `micromatch` release depends on `braces`, so +an override cannot close it. `frontend-modern` therefore moves to Tailwind CSS +4.3.3 through `@tailwindcss/vite` (dropping `postcss.config.js`, +`tailwind.config.js`, `autoprefixer` and the direct `postcss` dependency) and to +`jscpd` 5.4.0, which has no runtime dependencies; `npm audit` over the full graph +then reports no findings. `npm audit --omit=dev` was already clean, so no shipped +bundle code changes for security reasons, and the installer, artifact, signing, +promotion and rollback boundary is unchanged. The stylesheet keeps the v3 +rendering: it pins the v3 palette, font stacks and line heights, keeps the v3 +cascade order, and restores the v3 `space-*`, `divide-*`, hover, border, ring, +placeholder and button-cursor behaviour. Tailwind CSS 4 is tested on Safari +16.4, Chrome 111 and Firefox 128 and later. The production stylesheet itself +needs cascade layers (Safari 15.4, Chrome 99, Firefox 97), because the build +lowers media range syntax and nesting and folds palette opacity modifiers to +static colors; that is below the container-query floor (Safari 16, Chrome 105, +Firefox 110) the platform tables already required. +`frontend-modern/src/security/__tests__/dependencySecurity.test.ts` now rejects +any locked `braces` copy, so the advisory cannot return through a later +dependency, and drops the `autoprefixer` floor because the package is gone. + ### Reviewed @types/node 26.6.2 refresh The 2026-09-23 `npm-minor-patch` group (Dependabot #2189) advances the diff --git a/frontend-modern/DESIGN_SYSTEM.md b/frontend-modern/DESIGN_SYSTEM.md index 194f098d15..a15ecd9d7c 100644 --- a/frontend-modern/DESIGN_SYSTEM.md +++ b/frontend-modern/DESIGN_SYSTEM.md @@ -6,7 +6,7 @@ This document outlines the standard UI primitives, tokens, and components that c ## Core Principles 1. **Never hardcode hex values** or use static `gray` / `white` / `slate` labels for structural layout colors. -2. Use the **Semantic Tokens**. These resolve dynamically inside `tailwind.config.js` via `index.css` CSS-variables to flawlessly support light/dark transitions without needing a literal `dark:` prefix in the layout classes. +2. Use the **Semantic Tokens**. These are declared as Tailwind `@theme` colors in `index.css` and resolve through its CSS variables to flawlessly support light/dark transitions without needing a literal `dark:` prefix in the layout classes. ## Enforcement - `npm run lint` is a hard gate for design-system regressions in `src/components` and `src/pages`. diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index db181fda93..c1728456c2 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,100 +1,802 @@ { "version": 1, - "base_sha": "0e90236d3bb901dc84d4d07e70d4d2a09d5e6705", - "verified_at": "2026-10-02T23:55:48Z", + "base_sha": "74fd189fd03098b7ed94d806b15f570722048670", + "verified_at": "2026-10-03T14:24:00Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/components/shared/Table.tsx" + "frontend-modern/src/AppLayout.tsx", + "frontend-modern/src/api/patrol.ts", + "frontend-modern/src/components/AI/AICostDashboard.tsx", + "frontend-modern/src/components/AI/Chat/ApprovalCard.tsx", + "frontend-modern/src/components/AI/Chat/AssistantCommandHelpDialog.tsx", + "frontend-modern/src/components/AI/Chat/ChatMessages.tsx", + "frontend-modern/src/components/AI/Chat/MentionAutocomplete.tsx", + "frontend-modern/src/components/AI/Chat/MessageItem.tsx", + "frontend-modern/src/components/AI/Chat/ModelSelector.tsx", + "frontend-modern/src/components/AI/Chat/QuestionCard.tsx", + "frontend-modern/src/components/AI/Chat/SlashCommandAutocomplete.tsx", + "frontend-modern/src/components/AI/Chat/ThinkingBlock.tsx", + "frontend-modern/src/components/AI/Chat/ToolExecutionBlock.tsx", + "frontend-modern/src/components/AI/Chat/index.tsx", + "frontend-modern/src/components/AI/FindingsPanel.tsx", + "frontend-modern/src/components/AI/aiChatUtils.ts", + "frontend-modern/src/components/Alerts/AlertResourceTableDesktop.tsx", + "frontend-modern/src/components/Alerts/AlertResourceTableMobile.tsx", + "frontend-modern/src/components/Alerts/AlertResourceTableRow.tsx", + "frontend-modern/src/components/Alerts/BulkEditDialog.tsx", + "frontend-modern/src/components/Alerts/EmailProviderSelect.tsx", + "frontend-modern/src/components/Alerts/IncidentAssistantHandoffButton.tsx", + "frontend-modern/src/components/Alerts/IncidentTimelineEventCard.tsx", + "frontend-modern/src/components/Alerts/InvestigateAlertButton.tsx", + "frontend-modern/src/components/Alerts/ResourceTable.tsx", + "frontend-modern/src/components/Alerts/Thresholds/sections/CollapsibleSection.tsx", + "frontend-modern/src/components/Alerts/ThresholdsTable.tsx", + "frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx", + "frontend-modern/src/components/Alerts/ThresholdsTableDockerIgnoredPrefixesSection.tsx", + "frontend-modern/src/components/Alerts/ThresholdsTableDockerServiceGapSection.tsx", + "frontend-modern/src/components/Alerts/ThresholdsTableDockerUpdateAlertsSection.tsx", + "frontend-modern/src/components/Alerts/WebhookConfigForm.tsx", + "frontend-modern/src/components/Alerts/WebhookConfigList.tsx", + "frontend-modern/src/components/AppBootstrapStatus.tsx", + "frontend-modern/src/components/Brand/PulseBrandMark.tsx", + "frontend-modern/src/components/Discovery/AvailabilityProposalCard.tsx", + "frontend-modern/src/components/Discovery/DiscoveryTab.tsx", + "frontend-modern/src/components/Docker/SwarmServicesDrawer.tsx", + "frontend-modern/src/components/ErrorBoundary.tsx", + "frontend-modern/src/components/GitHubStarBanner.tsx", + "frontend-modern/src/components/Infrastructure/AvailabilityProbeStatusCard.tsx", + "frontend-modern/src/components/Infrastructure/ClusterDeployBanner.tsx", + "frontend-modern/src/components/Infrastructure/MaintenanceVerificationSection.tsx", + "frontend-modern/src/components/Infrastructure/ReportMergeModal.tsx", + "frontend-modern/src/components/Infrastructure/ResourceActionHistory.tsx", + "frontend-modern/src/components/Infrastructure/ResourceCorrelationSummary.tsx", + "frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx", + "frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx", + "frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx", + "frontend-modern/src/components/Infrastructure/ResourceDetailSummary.tsx", + "frontend-modern/src/components/Infrastructure/ResourceFacetSummary.tsx", + "frontend-modern/src/components/Infrastructure/ResourceInvestigationContextTables.tsx", + "frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx", + "frontend-modern/src/components/Infrastructure/ResourcePolicySummary.tsx", + "frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx", + "frontend-modern/src/components/Infrastructure/UnifiedResourcePBSTableSection.tsx", + "frontend-modern/src/components/Infrastructure/UnifiedResourcePMGTableSection.tsx", + "frontend-modern/src/components/Infrastructure/UnifiedResourceSourceBadgeCell.tsx", + "frontend-modern/src/components/Infrastructure/resourceDetailDrawerOperationalModel.ts", + "frontend-modern/src/components/Kubernetes/K8sDeploymentsDrawer.tsx", + "frontend-modern/src/components/Kubernetes/K8sNamespacesDrawer.tsx", + "frontend-modern/src/components/Login.tsx", + "frontend-modern/src/components/OrgSwitcher.tsx", + "frontend-modern/src/components/SecurityWarning.tsx", + "frontend-modern/src/components/Settings/AIChatMaintenanceSection.tsx", + "frontend-modern/src/components/Settings/AIModelSelectionSection.tsx", + "frontend-modern/src/components/Settings/AIProviderConfigurationSection.tsx", + "frontend-modern/src/components/Settings/AIRuntimeControlsSection.tsx", + "frontend-modern/src/components/Settings/AISettings.tsx", + "frontend-modern/src/components/Settings/AISettingsStatusAndActions.tsx", + "frontend-modern/src/components/Settings/APITokenManager.tsx", + "frontend-modern/src/components/Settings/APITokenManagerDialogs.tsx", + "frontend-modern/src/components/Settings/AgentIntegrationsPanel.tsx", + "frontend-modern/src/components/Settings/AgentProfilesPanel.tsx", + "frontend-modern/src/components/Settings/AuditLogPanel.tsx", + "frontend-modern/src/components/Settings/AuditWebhookPanel.tsx", + "frontend-modern/src/components/Settings/AvailabilitySettingsPanel.tsx", + "frontend-modern/src/components/Settings/BackupTransferDialogs.tsx", + "frontend-modern/src/components/Settings/BillingAdminOrganizationsTable.tsx", + "frontend-modern/src/components/Settings/BillingAdminPanel.tsx", + "frontend-modern/src/components/Settings/BrandingSettingsCard.tsx", + "frontend-modern/src/components/Settings/ChangePasswordModal.tsx", + "frontend-modern/src/components/Settings/CommercialBillingSections.tsx", + "frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx", + "frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/NodeCredentialSlot.tsx", + "frontend-modern/src/components/Settings/DataHandlingPanel.tsx", + "frontend-modern/src/components/Settings/DiagnosticsPanel.tsx", + "frontend-modern/src/components/Settings/DiagnosticsResultsPanel.tsx", + "frontend-modern/src/components/Settings/DiscoverySettingsForm.tsx", + "frontend-modern/src/components/Settings/DockerRuntimeSettingsCard.tsx", + "frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx", + "frontend-modern/src/components/Settings/GuestDockerDiscoverySettingsCard.tsx", + "frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx", + "frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx", + "frontend-modern/src/components/Settings/InfrastructureSourceManager.tsx", + "frontend-modern/src/components/Settings/InfrastructureSourcePicker.tsx", + "frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx", + "frontend-modern/src/components/Settings/NetworkBoundarySettingsSection.tsx", + "frontend-modern/src/components/Settings/NodeModalClusterMembersSection.tsx", + "frontend-modern/src/components/Settings/NodeModalMonitoringSection.tsx", + "frontend-modern/src/components/Settings/NodeModalSetupGuideSection.tsx", + "frontend-modern/src/components/Settings/NodeModalStatusFooter.tsx", + "frontend-modern/src/components/Settings/OrganizationAccessManagementSection.tsx", + "frontend-modern/src/components/Settings/OrganizationAccessMembersSection.tsx", + "frontend-modern/src/components/Settings/OrganizationOverviewDetailsSection.tsx", + "frontend-modern/src/components/Settings/OrganizationSharingCreateSection.tsx", + "frontend-modern/src/components/Settings/ProLicensePlanSection.tsx", + "frontend-modern/src/components/Settings/QuickSecuritySetup.tsx", + "frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx", + "frontend-modern/src/components/Settings/RelaySettingsPanel.tsx", + "frontend-modern/src/components/Settings/ReportingPanel.tsx", + "frontend-modern/src/components/Settings/ResourcePicker.tsx", + "frontend-modern/src/components/Settings/RolesEditorDialog.tsx", + "frontend-modern/src/components/Settings/SSOProvidersPanel.tsx", + "frontend-modern/src/components/Settings/SecurityAuthPanel.tsx", + "frontend-modern/src/components/Settings/SecurityOverviewPanel.tsx", + "frontend-modern/src/components/Settings/SelfHostedCommercialRecoverySection.tsx", + "frontend-modern/src/components/Settings/SettingsPageShell.tsx", + "frontend-modern/src/components/Settings/SuggestProfileModal.tsx", + "frontend-modern/src/components/Settings/SystemLogsPanel.tsx", + "frontend-modern/src/components/Settings/UpdateInstallGuide.tsx", + "frontend-modern/src/components/Settings/UpdatesSettingsPanel.tsx", + "frontend-modern/src/components/Settings/UserAssignmentsDialog.tsx", + "frontend-modern/src/components/Settings/UserAssignmentsPanel.tsx", + "frontend-modern/src/components/Settings/availabilitySettingsModel.ts", + "frontend-modern/src/components/Settings/connectionsTableModel.ts", + "frontend-modern/src/components/Settings/useConnectionsLedger.ts", + "frontend-modern/src/components/Settings/useProLicensePanelState.ts", + "frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx", + "frontend-modern/src/components/SetupWizard/steps/SecurityStep.tsx", + "frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx", + "frontend-modern/src/components/Storage/EnhancedStorageBar.tsx", + "frontend-modern/src/components/Storage/StorageDetailKeyValueRow.tsx", + "frontend-modern/src/components/Storage/StoragePoolDetail.tsx", + "frontend-modern/src/components/Storage/StoragePoolsTable.tsx", + "frontend-modern/src/components/Toast/Toast.tsx", + "frontend-modern/src/components/TokenRevealDialog.tsx", + "frontend-modern/src/components/UpdateBanner.tsx", + "frontend-modern/src/components/UpdateConfirmationModal.tsx", + "frontend-modern/src/components/WhatsNewCard.tsx", + "frontend-modern/src/components/Workloads/AvailabilityProbeSuggestionCard.tsx", + "frontend-modern/src/components/Workloads/DiskList.tsx", + "frontend-modern/src/components/Workloads/DrawerDiskListCard.tsx", + "frontend-modern/src/components/Workloads/EnhancedCPUBar.tsx", + "frontend-modern/src/components/Workloads/GuestDrawerHistory.tsx", + "frontend-modern/src/components/Workloads/GuestPhysicalDisks.tsx", + "frontend-modern/src/components/Workloads/GuestRow.tsx", + "frontend-modern/src/components/Workloads/GuestRowCells.tsx", + "frontend-modern/src/components/Workloads/MetricMiniSparkline.tsx", + "frontend-modern/src/components/Workloads/StackedDiskBar.tsx", + "frontend-modern/src/components/Workloads/StackedMemoryBar.tsx", + "frontend-modern/src/components/Workloads/ThresholdSlider.tsx", + "frontend-modern/src/components/Workloads/WorkloadPanel.tsx", + "frontend-modern/src/components/Workloads/WorkloadsFilter.tsx", + "frontend-modern/src/components/Workloads/WorkloadsSurface.tsx", + "frontend-modern/src/components/Workloads/WorkloadsTable.tsx", + "frontend-modern/src/components/Workloads/useGuestRowState.ts", + "frontend-modern/src/components/patrol/ApprovalBanner.tsx", + "frontend-modern/src/components/patrol/ApprovalSection.tsx", + "frontend-modern/src/components/patrol/InvestigationMessages.tsx", + "frontend-modern/src/components/patrol/InvestigationSection.tsx", + "frontend-modern/src/components/patrol/RemediationStatus.tsx", + "frontend-modern/src/components/patrol/RunHistoryEntry.tsx", + "frontend-modern/src/components/patrol/RunHistoryPanel.tsx", + "frontend-modern/src/components/patrol/RunToolCallTrace.tsx", + "frontend-modern/src/components/shared/AIModelPicker.tsx", + "frontend-modern/src/components/shared/CalloutCard.tsx", + "frontend-modern/src/components/shared/Card.tsx", + "frontend-modern/src/components/shared/ColumnPicker.tsx", + "frontend-modern/src/components/shared/CommandPaletteModal.tsx", + "frontend-modern/src/components/shared/ContainerUpdateBadge.tsx", + "frontend-modern/src/components/shared/CopyableCodeRow.tsx", + "frontend-modern/src/components/shared/DetailSectionTable.tsx", + "frontend-modern/src/components/shared/Dialog.tsx", + "frontend-modern/src/components/shared/DiscoveryReadinessBadge.tsx", + "frontend-modern/src/components/shared/DrawerAttentionSection.tsx", + "frontend-modern/src/components/shared/ExternalTextLink.tsx", + "frontend-modern/src/components/shared/FeatureGateSection.tsx", + "frontend-modern/src/components/shared/FilterBar/AddFilterMenu.tsx", + "frontend-modern/src/components/shared/FilterBar/FilterBar.tsx", + "frontend-modern/src/components/shared/FilterBar/FilterChip.tsx", + "frontend-modern/src/components/shared/FilterBar/ViewOptionsDisclosure.tsx", + "frontend-modern/src/components/shared/FilterButtonGroup.tsx", + "frontend-modern/src/components/shared/FilterToolbar.tsx", + "frontend-modern/src/components/shared/Form.ts", + "frontend-modern/src/components/shared/HelpIcon.tsx", + "frontend-modern/src/components/shared/HistoryChart.tsx", + "frontend-modern/src/components/shared/HistoryChartHeader.tsx", + "frontend-modern/src/components/shared/HistoryChartOverlay.tsx", + "frontend-modern/src/components/shared/HistoryChartTooltip.tsx", + "frontend-modern/src/components/shared/InfoCardFrame.tsx", + "frontend-modern/src/components/shared/KeyboardShortcutsModal.tsx", + "frontend-modern/src/components/shared/MetadataBadge.tsx", + "frontend-modern/src/components/shared/MobileNavBar.tsx", + "frontend-modern/src/components/shared/NodeGroupHeader.tsx", + "frontend-modern/src/components/shared/ObjectDrawerHeader.tsx", + "frontend-modern/src/components/shared/ProgressBar.tsx", + "frontend-modern/src/components/shared/PulseDataGrid.tsx", + "frontend-modern/src/components/shared/SearchField.tsx", + "frontend-modern/src/components/shared/SearchTipsPopover.tsx", + "frontend-modern/src/components/shared/SettingsLoadingSkeleton.tsx", + "frontend-modern/src/components/shared/SkipToContentLink.tsx", + "frontend-modern/src/components/shared/StatusDot.tsx", + "frontend-modern/src/components/shared/StatusIndicatorBadge.tsx", + "frontend-modern/src/components/shared/Subtabs.tsx", + "frontend-modern/src/components/shared/TableCardHeader.tsx", + "frontend-modern/src/components/shared/TagBadges.tsx", + "frontend-modern/src/components/shared/TechnicalDetailsDisclosure.tsx", + "frontend-modern/src/components/shared/TlsVerificationWarningBanner.tsx", + "frontend-modern/src/components/shared/Tooltip.tsx", + "frontend-modern/src/components/shared/TooltipPortal.tsx", + "frontend-modern/src/components/shared/WebInterfaceLink.tsx", + "frontend-modern/src/components/shared/WebInterfaceUrlField.tsx", + "frontend-modern/src/components/shared/WorkloadTypeBadge.tsx", + "frontend-modern/src/components/shared/buttonModel.ts", + "frontend-modern/src/components/shared/cards/NetworkInterfacesCard.tsx", + "frontend-modern/src/components/shared/cards/RaidCard.tsx", + "frontend-modern/src/components/shared/containerUpdateBadgeModel.ts", + "frontend-modern/src/components/shared/dialogModel.ts", + "frontend-modern/src/components/shared/filterButtonGroupModel.ts", + "frontend-modern/src/components/shared/groupedTableRowPresentation.ts", + "frontend-modern/src/components/shared/mobileNavBarModel.ts", + "frontend-modern/src/components/shared/searchInputEnhancementsModel.ts", + "frontend-modern/src/components/shared/searchTipsPopoverModel.ts", + "frontend-modern/src/components/shared/selectablePillModel.ts", + "frontend-modern/src/components/shared/selectionCardGroupModel.ts", + "frontend-modern/src/components/shared/sourcePlatformBadges.ts", + "frontend-modern/src/components/shared/statusBadgeModel.ts", + "frontend-modern/src/components/shared/summaryInteractionA11y.ts", + "frontend-modern/src/components/shared/tagInputModel.ts", + "frontend-modern/src/components/shared/toggleModel.ts", + "frontend-modern/src/features/actions/ActionDecisionPacket.tsx", + "frontend-modern/src/features/actions/ActionReviewDialog.tsx", + "frontend-modern/src/features/alerts/AlertAppriseDestinationsSection.tsx", + "frontend-modern/src/features/alerts/AlertDeadManDestinationSection.tsx", + "frontend-modern/src/features/alerts/AlertDeliveryHealthCard.tsx", + "frontend-modern/src/features/alerts/AlertDeliveryLogCard.tsx", + "frontend-modern/src/features/alerts/AlertDeliveryPausedCard.tsx", + "frontend-modern/src/features/alerts/AlertDestinationsLoadErrorCard.tsx", + "frontend-modern/src/features/alerts/AlertDestinationsLoadingState.tsx", + "frontend-modern/src/features/alerts/AlertEscalationSection.tsx", + "frontend-modern/src/features/alerts/AlertHistoryAdministrationCard.tsx", + "frontend-modern/src/features/alerts/AlertHistoryFrequencyCard.tsx", + "frontend-modern/src/features/alerts/AlertIncidentSynthesisSummary.tsx", + "frontend-modern/src/features/alerts/AlertOverviewActiveAlertsSection.tsx", + "frontend-modern/src/features/alerts/AlertOverviewAlertCard.tsx", + "frontend-modern/src/features/alerts/AlertQueueActionFeedback.tsx", + "frontend-modern/src/features/alerts/AlertSnoozeAction.tsx", + "frontend-modern/src/features/alerts/ResourceMonitoringPolicyAction.tsx", + "frontend-modern/src/features/alerts/tabs/ScheduleTab.tsx", + "frontend-modern/src/features/docker/DockerConfigsTable.tsx", + "frontend-modern/src/features/docker/DockerContainerLifecycleControls.tsx", + "frontend-modern/src/features/docker/DockerHostDrawerOverview.tsx", + "frontend-modern/src/features/docker/DockerHostsTable.tsx", + "frontend-modern/src/features/docker/DockerNativeTableShared.tsx", + "frontend-modern/src/features/docker/DockerNetworksTable.tsx", + "frontend-modern/src/features/docker/DockerSecretsTable.tsx", + "frontend-modern/src/features/docker/DockerServicesTable.tsx", + "frontend-modern/src/features/docker/DockerStorageUsageTable.tsx", + "frontend-modern/src/features/docker/DockerTasksTable.tsx", + "frontend-modern/src/features/docker/DockerVolumesTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesAutoscalingTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesClustersTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesConfigTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesControllersTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesEventsTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesNetworkingTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesPolicyTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesServicesTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesStorageTable.tsx", + "frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx", + "frontend-modern/src/features/patrol/PatrolAutopilotAcknowledgementDialog.tsx", + "frontend-modern/src/features/patrol/PatrolIntelligenceBanners.tsx", + "frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx", + "frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx", + "frontend-modern/src/features/patrol/PatrolIntelligenceWorkspace.tsx", + "frontend-modern/src/features/patrol/PatrolObjectivesPanel.tsx", + "frontend-modern/src/features/patrol/PatrolRecentWorkPanel.tsx", + "frontend-modern/src/features/patrol/PatrolWeeklyDigestCard.tsx", + "frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.tsx", + "frontend-modern/src/features/platformPage/PlatformWindowedRows.tsx", + "frontend-modern/src/features/platformPage/sharedPlatformPage.tsx", + "frontend-modern/src/features/proxmox/BackupActivityChart.tsx", + "frontend-modern/src/features/proxmox/ProxmoxBackupServersTable.tsx", + "frontend-modern/src/features/proxmox/ProxmoxBackupsCoverageStrip.tsx", + "frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx", + "frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx", + "frontend-modern/src/features/proxmox/ProxmoxCephTable.tsx", + "frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx", + "frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx", + "frontend-modern/src/features/proxmox/ProxmoxMailGatewayTable.tsx", + "frontend-modern/src/features/proxmox/ProxmoxNodesTable.tsx", + "frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx", + "frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx", + "frontend-modern/src/features/proxmox/ProxmoxReplicationTable.tsx", + "frontend-modern/src/features/proxmox/proxmoxBackupsTableShared.tsx", + "frontend-modern/src/features/proxmox/proxmoxHostTableModel.ts", + "frontend-modern/src/features/standalone/AgentsMachinesTable.tsx", + "frontend-modern/src/features/standalone/AvailabilityFleetView.tsx", + "frontend-modern/src/features/standalone/StandalonePageSurface.tsx", + "frontend-modern/src/features/storageBackups/detailPresentation.ts", + "frontend-modern/src/features/storageBackups/diskPresentation.ts", + "frontend-modern/src/features/storageBackups/groupPresentation.ts", + "frontend-modern/src/features/storageBackups/storageDomain.ts", + "frontend-modern/src/features/storageBackups/storageFilterPresentation.ts", + "frontend-modern/src/features/storageBackups/storagePagePresentation.ts", + "frontend-modern/src/features/storageBackups/storageRowAlertPresentation.ts", + "frontend-modern/src/features/storageBackups/zfsPresentation.ts", + "frontend-modern/src/features/truenas/TrueNASAppsTable.tsx", + "frontend-modern/src/features/truenas/TrueNASServicesTable.tsx", + "frontend-modern/src/features/truenas/TrueNASSystemsTable.tsx", + "frontend-modern/src/features/vmware/VmwarePageSurface.tsx", + "frontend-modern/src/features/vmware/VsphereHostsTable.tsx", + "frontend-modern/src/index.css", + "frontend-modern/src/pages/Actions.tsx", + "frontend-modern/src/pages/Alerts.tsx", + "frontend-modern/src/pages/Docs.tsx", + "frontend-modern/src/pages/PricingHandoff.tsx", + "frontend-modern/src/tailwind-palette.css", + "frontend-modern/src/utils/actionAuditPresentation.ts", + "frontend-modern/src/utils/agentCapabilityPresentation.ts", + "frontend-modern/src/utils/agentProfileSuggestionPresentation.ts", + "frontend-modern/src/utils/aiControlLevelPresentation.ts", + "frontend-modern/src/utils/aiFindingPresentation.ts", + "frontend-modern/src/utils/aiProviderHealthPresentation.ts", + "frontend-modern/src/utils/aiSettingsPresentation.ts", + "frontend-modern/src/utils/alertActivationPresentation.ts", + "frontend-modern/src/utils/alertFrequencyPresentation.ts", + "frontend-modern/src/utils/alertGroupingPresentation.ts", + "frontend-modern/src/utils/alertHistoryPresentation.ts", + "frontend-modern/src/utils/alertIncidentPresentation.ts", + "frontend-modern/src/utils/alertOverviewPresentation.ts", + "frontend-modern/src/utils/alertResourceTablePresentation.ts", + "frontend-modern/src/utils/alertSchedulePresentation.ts", + "frontend-modern/src/utils/alertSeverityPresentation.ts", + "frontend-modern/src/utils/alertTabsPresentation.ts", + "frontend-modern/src/utils/alerts.ts", + "frontend-modern/src/utils/auditLogPresentation.ts", + "frontend-modern/src/utils/auditWebhookPresentation.ts", + "frontend-modern/src/utils/discoveryPresentation.ts", + "frontend-modern/src/utils/emptyStatePresentation.ts", + "frontend-modern/src/utils/environmentLockPresentation.ts", + "frontend-modern/src/utils/licensePresentation.ts", + "frontend-modern/src/utils/nodeModalPresentation.ts", + "frontend-modern/src/utils/orgUtils.ts", + "frontend-modern/src/utils/patrolRunPresentation.ts", + "frontend-modern/src/utils/patrolSummaryPresentation.ts", + "frontend-modern/src/utils/platformSupportManifest.generated.ts", + "frontend-modern/src/utils/raidPresentation.ts", + "frontend-modern/src/utils/recoveryArtifactModePresentation.ts", + "frontend-modern/src/utils/recoveryItemTypePresentation.ts", + "frontend-modern/src/utils/recoveryOutcomePresentation.ts", + "frontend-modern/src/utils/recoveryTimelineChartPresentation.ts", + "frontend-modern/src/utils/recoveryTimelinePresentation.ts", + "frontend-modern/src/utils/relayPresentation.ts", + "frontend-modern/src/utils/remediationPresentation.ts", + "frontend-modern/src/utils/resourceBadgePresentation.ts", + "frontend-modern/src/utils/resourceChangePresentation.ts", + "frontend-modern/src/utils/resourcePolicyPresentation.ts", + "frontend-modern/src/utils/securityScorePresentation.ts", + "frontend-modern/src/utils/semanticTonePresentation.ts", + "frontend-modern/src/utils/serviceHealthPresentation.ts", + "frontend-modern/src/utils/sourcePlatforms.ts", + "frontend-modern/src/utils/sourceTypePresentation.ts", + "frontend-modern/src/utils/ssoProviderPresentation.ts", + "frontend-modern/src/utils/status.ts", + "frontend-modern/src/utils/systemLogsPresentation.ts", + "frontend-modern/src/utils/unifiedAgentStatusPresentation.ts", + "frontend-modern/src/utils/updatesPresentation.ts", + "frontend-modern/src/utils/workloadGuestPresentation.ts", + "frontend-modern/src/utils/workloadTypePresentation.ts" ], "content_sha256": { - "frontend-modern/src/components/shared/Table.tsx": "8e5029d5ee9dddc86a4733fa1e7249793cdbedb135f3634e75112f8fb3b327c9" + "frontend-modern/src/AppLayout.tsx": "4c421ddf417442345029db9769f7a02742c35853193fcd55d0f80605c735b0ee", + "frontend-modern/src/api/patrol.ts": "c43045b383a0c7c7b08f6d1c11f163f827b1540988f996ea9819eb8ef9747d61", + "frontend-modern/src/components/AI/AICostDashboard.tsx": "5fb4edb294b1fa3fd767385aedec94da5531bb4d2a83b4f8b520cb610df1fc84", + "frontend-modern/src/components/AI/Chat/ApprovalCard.tsx": "92713fb6fa6ef2badb1d1c44fac3aa2f30da460a7da81f8c6eed3e7866349df2", + "frontend-modern/src/components/AI/Chat/AssistantCommandHelpDialog.tsx": "7d6ed3466c0ff32796da341912eeaed4d15c95f0e357a171afe72baead89b49e", + "frontend-modern/src/components/AI/Chat/ChatMessages.tsx": "93ce947185e031470b6d208a4736f9f5fc9effc2b8a427ea20306f2da392d3ce", + "frontend-modern/src/components/AI/Chat/MentionAutocomplete.tsx": "830745d44c72469e93dad88dc1f0ca8a49b6b7a4c6f176c4292f1dea8b7d3e8f", + "frontend-modern/src/components/AI/Chat/MessageItem.tsx": "0978b6d97f99cdc6cbba1076a5c105757e3a1f690d44e216e81d127c7af8d608", + "frontend-modern/src/components/AI/Chat/ModelSelector.tsx": "c3ee6a034d0d486f0779844e006c28822d14cd022b4ff81341ba00c4f89acc79", + "frontend-modern/src/components/AI/Chat/QuestionCard.tsx": "1cf3907918fbd674541e1a7daa369179df85cd6cab9df16e3b79255133da0028", + "frontend-modern/src/components/AI/Chat/SlashCommandAutocomplete.tsx": "d48683ba239d1734642a6ff2a985e50aa67ece0f03a1de4e6c452be7cb97433f", + "frontend-modern/src/components/AI/Chat/ThinkingBlock.tsx": "dc44bf234ae14b6afdb8c8a9031051ea1eec04af1bc3a82afe174257278aa119", + "frontend-modern/src/components/AI/Chat/ToolExecutionBlock.tsx": "920e6211c322aa35893fa46ba11b0325e5d2978366a6634b8374bc0065ba27c0", + "frontend-modern/src/components/AI/Chat/index.tsx": "a81e210aa68284c357a5d11d1a043273bee27ce37b199cae5e79c9494c82391e", + "frontend-modern/src/components/AI/FindingsPanel.tsx": "9e6dc1d1abb0ede2b8c0db178ca03b46e1277c877dbbd06de3268d95c9052882", + "frontend-modern/src/components/AI/aiChatUtils.ts": "3eaa28e9054fa81de1d9ef1b86076de47105e3cf17ebea7ca50f55b97664a6b1", + "frontend-modern/src/components/Alerts/AlertResourceTableDesktop.tsx": "d3b66d77a6c3d673f6f74a8e551f50e6bcc3859d8e7d7e2b2f23f9b45a7441a1", + "frontend-modern/src/components/Alerts/AlertResourceTableMobile.tsx": "a3a2f273e3527fa792a018dd67578ea365f5b24d4fc3ec6c9a4f740e57503a07", + "frontend-modern/src/components/Alerts/AlertResourceTableRow.tsx": "cc30f0421157dd9810bffe3fff0cc51bf5a7beb971a01016662912245e8805cf", + "frontend-modern/src/components/Alerts/BulkEditDialog.tsx": "0f12377f7ad59dcf5a1a91957a39bfbd9a024672096703772fa6d2ca94b57255", + "frontend-modern/src/components/Alerts/EmailProviderSelect.tsx": "db5c019341a95fd9126503ebb1c8f30c12f72411c662c653cdf5060cefb1f50e", + "frontend-modern/src/components/Alerts/IncidentAssistantHandoffButton.tsx": "80d67f1c1b02584fd2b73535a210e201bd040672cd1297f71abc0d7a23f568a1", + "frontend-modern/src/components/Alerts/IncidentTimelineEventCard.tsx": "0627220909c0ac2bf77e639452e4723481998231354c615729e64df8b6e6d1b1", + "frontend-modern/src/components/Alerts/InvestigateAlertButton.tsx": "07e2cd935d6e53bb4caf7af0ad1c9b5c1a963e4d74e456cfaa0056528f8760b4", + "frontend-modern/src/components/Alerts/ResourceTable.tsx": "0e1968d5de14f91f8d4ad7922b0d69c74e258e906d319be5d743b67bda401146", + "frontend-modern/src/components/Alerts/Thresholds/sections/CollapsibleSection.tsx": "5910c6b18a0ff28d4c0a58fbdc8bf4068dc209a6a046eddd60371fc8e7268ee3", + "frontend-modern/src/components/Alerts/ThresholdsTable.tsx": "c74b6523f09dfb5fba80e2e7e4ac3fe351d34a6c8e4db3257d40c282c89e6549", + "frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx": "4363e45ec7dad02f89b755a6439f354a8596d4e763a5061cf2f1d4967d252fe0", + "frontend-modern/src/components/Alerts/ThresholdsTableDockerIgnoredPrefixesSection.tsx": "190def2e0d3334ee1bb11fa668a1b6d0e4d267593e44e52db3028d9f148a2e1c", + "frontend-modern/src/components/Alerts/ThresholdsTableDockerServiceGapSection.tsx": "249e29abf060f9bc6858c75aa9ddff5ad784ab3d9d54b2845041b294075e0307", + "frontend-modern/src/components/Alerts/ThresholdsTableDockerUpdateAlertsSection.tsx": "807195beab3c810e7d7a15a6a9bcaece2d6f2338fafe9aebb1d376584688c563", + "frontend-modern/src/components/Alerts/WebhookConfigForm.tsx": "c00b86d5b8467ad55b2ac52cd7b9ccf71882ef9dd7f447e92399dbe642e9cb38", + "frontend-modern/src/components/Alerts/WebhookConfigList.tsx": "2941d65e826edc72ec8f3cedfd74b5f7eb55ea23bf5f655aae43b741735ec822", + "frontend-modern/src/components/AppBootstrapStatus.tsx": "2c68db90032bb93434357a99c46172d9ce668c5158db72a5ca1d3eb0302c743e", + "frontend-modern/src/components/Brand/PulseBrandMark.tsx": "e3c6d88d815b6596bc378f3d6644e440b1981ff8ca2a61e285f8aad307ef5424", + "frontend-modern/src/components/Discovery/AvailabilityProposalCard.tsx": "e3f911e0e5c6ab96de1e7e147fe6b1a741c6ba2961f09c35fcb9be0988120d6b", + "frontend-modern/src/components/Discovery/DiscoveryTab.tsx": "7247ec7b450b4d65e82ea734a907a3a3b0529edde6ab73b6a28fef36b23c03f4", + "frontend-modern/src/components/Docker/SwarmServicesDrawer.tsx": "b06c9b8c444afac1e1e3fe8c685ef13c9da3338a27168b148cd7095f6aad23e9", + "frontend-modern/src/components/ErrorBoundary.tsx": "114f1fc07f4da41dd0f70549e6edae04def1d13bcf64369a24deb1052b4256d9", + "frontend-modern/src/components/GitHubStarBanner.tsx": "28c34c47bf97c7390f8d618c08f7f3c254a369a1b3323989b5ce5f5fff19333b", + "frontend-modern/src/components/Infrastructure/AvailabilityProbeStatusCard.tsx": "6898faeff884ef39ec7ab8c99b6da96bf4137bffc4556c5c5be584e51bd23ad3", + "frontend-modern/src/components/Infrastructure/ClusterDeployBanner.tsx": "a5fddf162e9beb4f1d3efe2ccde35ed41daffb0063a05da7a299bd810b4e305b", + "frontend-modern/src/components/Infrastructure/MaintenanceVerificationSection.tsx": "ca116f9c678ebfa3cb8b2c700684d51cc7e70cb0ac0bddf4564ee4a7694e2b49", + "frontend-modern/src/components/Infrastructure/ReportMergeModal.tsx": "f6492d36a72766b7874b34cf6cfbb53a836573104fe292644d4e3dc3c90f64ce", + "frontend-modern/src/components/Infrastructure/ResourceActionHistory.tsx": "4ef18bf414cc70fef647b30db62a56000e4fcf6a4ef784b0a2eaa914816f2462", + "frontend-modern/src/components/Infrastructure/ResourceCorrelationSummary.tsx": "2f8df87b78c619a176a98b1587e692c5a50a46d2b36f310ec6ab75c7ad68ee5d", + "frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx": "c9b38b6dbbf1a4da8b822a43010785aeb9b7d80b4e48565917a04021919ccb9d", + "frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx": "75f0c3c835438e4d04a8883f60614c979daba07fcfbbb9b2495274cec19b6218", + "frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx": "5968fed2253b4c8791e69514329c9953375cce2a1767347b4f880cb5431698e0", + "frontend-modern/src/components/Infrastructure/ResourceDetailSummary.tsx": "ec9d97ed7bc72f4422b396c943fef546585664a0cc2fdbc06eca23826bccc1ac", + "frontend-modern/src/components/Infrastructure/ResourceFacetSummary.tsx": "cff28a266149b1d44c369b5837ec0dd9078e5f7c7829edf1417c44e7f9e8b6d8", + "frontend-modern/src/components/Infrastructure/ResourceInvestigationContextTables.tsx": "7e43e31ea77c9adb5b228751fd3e14b792f0735f2a8a48fdbdf4fa3e15b34559", + "frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx": "f01cf995556c06ce66277932ff1b92971428d6cdfe0eef5494750c4de3572c1d", + "frontend-modern/src/components/Infrastructure/ResourcePolicySummary.tsx": "8a7b51c655eb1a47152c56206c53321dc07e08324f2d605c9bb35fab8c5609f1", + "frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx": "3446e206482f10df5ce79e35ee6067019113e0e6abd940356de55019c50d5722", + "frontend-modern/src/components/Infrastructure/UnifiedResourcePBSTableSection.tsx": "57d0023ba93d6720a57b5a28f29e9b33c170f98cfc00a9d980b24192b414bd08", + "frontend-modern/src/components/Infrastructure/UnifiedResourcePMGTableSection.tsx": "5f4cb02af71ade8ec6c5f72bc35282ce636bc35691ff2991bced3e7f72068f41", + "frontend-modern/src/components/Infrastructure/UnifiedResourceSourceBadgeCell.tsx": "057ace33675536df16da966cd5e64c7136a18dd52152670404bb959fe65af711", + "frontend-modern/src/components/Infrastructure/resourceDetailDrawerOperationalModel.ts": "37215bc8215ef8819f30fc5e6a82de7ed2872217f7c8a08e07df25cc9c90119b", + "frontend-modern/src/components/Kubernetes/K8sDeploymentsDrawer.tsx": "400e0d7756cc2c30adf651ccf6d960d1db6f40c458b428d3129ff36cc249530e", + "frontend-modern/src/components/Kubernetes/K8sNamespacesDrawer.tsx": "4569d7a5ba7d0ea55d680440ce1b75d0555a7213054608792414342eaa2397b7", + "frontend-modern/src/components/Login.tsx": "2060e6846a0d1cfe069da826a0685a62375cef69b3643b72f6ee82ad162713c6", + "frontend-modern/src/components/OrgSwitcher.tsx": "b65743de66f6a2fb4bdee0ffb27f0d754e7625209d659aed9a7be4a6890835c3", + "frontend-modern/src/components/SecurityWarning.tsx": "1b4e66e1356c22e51649721c8314211c3bce6ce7cce7790224a50a3739157a5e", + "frontend-modern/src/components/Settings/AIChatMaintenanceSection.tsx": "6f9b6466622cab2a847f788a8e7c7d8af6a5f8d34ac1c40197301d0b9eb6a5ef", + "frontend-modern/src/components/Settings/AIModelSelectionSection.tsx": "ffaa33f26882e1af856c96a59c8ca7bd5471058a413b81010a0e4fad768c1aa9", + "frontend-modern/src/components/Settings/AIProviderConfigurationSection.tsx": "35d8a2a138532e7b8d463239d007eaaa3b7665b57550e782662b9906be0e0127", + "frontend-modern/src/components/Settings/AIRuntimeControlsSection.tsx": "0c42d95d8a4e6de980f5d2b6dbdc32085b29157157e646e0a7aa67612ef5fa86", + "frontend-modern/src/components/Settings/AISettings.tsx": "57ca2b7a503151beb0119f884c23f7e447343e669658f4a1f9c241f7957cac45", + "frontend-modern/src/components/Settings/AISettingsStatusAndActions.tsx": "e9c0242db3ab629cc1d29ecf1a24857dae94a56407177404b5fa7f1ea22b4e40", + "frontend-modern/src/components/Settings/APITokenManager.tsx": "cdadce997848fd84857b51149f79592a60846012b74de51122d98e69b4f26ca2", + "frontend-modern/src/components/Settings/APITokenManagerDialogs.tsx": "a6556fa5719503d0f05fda8a97e18f205a55bb557b8e401a99c5905c3a641606", + "frontend-modern/src/components/Settings/AgentIntegrationsPanel.tsx": "16ec3219b16accedbd3531f75db3ff6ceaa49a9fe7fa917960e94e6b683c1f84", + "frontend-modern/src/components/Settings/AgentProfilesPanel.tsx": "c52ee2cdbc6ca6cceaa6bf3b43ef42d55a3b8c1333125d1d488993f00650c2a9", + "frontend-modern/src/components/Settings/AuditLogPanel.tsx": "cd8c91c0f24f7eff85c97406dc511cd8249c4a1c0efc3bdbc73d69c6e343a821", + "frontend-modern/src/components/Settings/AuditWebhookPanel.tsx": "3851d132f455015467f13a05125ce8bdaeea744d86f09ed3068e0b852db0dbd7", + "frontend-modern/src/components/Settings/AvailabilitySettingsPanel.tsx": "7fd0f5a6f35a7a093851b989bb63079ab03cdf451b1347367e5c02f44cad0a2e", + "frontend-modern/src/components/Settings/BackupTransferDialogs.tsx": "f593c4a5dbbecee8f449d8004d29efcac295d594d9e84b0a44b9bd6dfa2ccf13", + "frontend-modern/src/components/Settings/BillingAdminOrganizationsTable.tsx": "19f53dd58d2d9c7ee37667e4eb84102de688302aec099e69eb329fffc871ed43", + "frontend-modern/src/components/Settings/BillingAdminPanel.tsx": "60f1f2a520b6e2f85916d2bf174b46c9dfae5d2e491aa34a8c3e1af927296975", + "frontend-modern/src/components/Settings/BrandingSettingsCard.tsx": "e382ff7070fc4c01ba62ab3c366a284bfef28b1e49b5e0b9a9716401a774823c", + "frontend-modern/src/components/Settings/ChangePasswordModal.tsx": "a28dba9ae0992ff42226fec24e77399d9eea32a3551b07d704fcdcdd6ce8ce04", + "frontend-modern/src/components/Settings/CommercialBillingSections.tsx": "8a028ccdd9783dc421b4dd0b646806b41122e0165e7398198fcc3f2da0716abc", + "frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx": "2a38ac312dfb4b2890012c402827dfe2b00efcb047589812f8d077fc885bb8fe", + "frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/NodeCredentialSlot.tsx": "a3de7bfa65511aba00a16ef40424be988f8f72931568031882c93455b7945c03", + "frontend-modern/src/components/Settings/DataHandlingPanel.tsx": "d99c9d682209b218f902c19c01411aa8d633ac5c4cef0fe44412fae616534daa", + "frontend-modern/src/components/Settings/DiagnosticsPanel.tsx": "08d8b88c7a53847011c0cf70be26a70087221a3349a78dcee171f00a28abcd0a", + "frontend-modern/src/components/Settings/DiagnosticsResultsPanel.tsx": "e841af8288e956f5a9f16e7a3573082cee3fe51eb849f5667268c745bbd423ad", + "frontend-modern/src/components/Settings/DiscoverySettingsForm.tsx": "d4477e531b2ee23fc3cfd02cf616dd3325589c9e3121cf7001039a25418655d5", + "frontend-modern/src/components/Settings/DockerRuntimeSettingsCard.tsx": "1c9355c33675faf4f92bd7b32ae1cec4b5713f0e7bd0726fbee3d9aa4e648ae9", + "frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx": "58cbabb0d157332cf2ad72cf7f53bbf58ccde39f4e995a1bec93ce825ed9a804", + "frontend-modern/src/components/Settings/GuestDockerDiscoverySettingsCard.tsx": "6ef7feef441adedb76c5659a960cfd5ee58624c4be8a7c474578a93a9f93f0fc", + "frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx": "7a711d0626bdd171c69a738da247ef6aec8c98346057de072f84dcecea0addfe", + "frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx": "5166d68f983bdca9f168437250f5b4f739f431204656fa901d3459b6f17439e2", + "frontend-modern/src/components/Settings/InfrastructureSourceManager.tsx": "6c77691095b642aee2a9d964fdb2863426cbf75b2787bc96c4807d43a3aa8580", + "frontend-modern/src/components/Settings/InfrastructureSourcePicker.tsx": "955de90f3a7b7eae79c4822e103ed22a982403b1c2c574a922a5c662e129f160", + "frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx": "dc287f2392c61d1099c06546d9284f3809ef7b1a382679730ba08cd6b72f88ff", + "frontend-modern/src/components/Settings/NetworkBoundarySettingsSection.tsx": "d5cdc61740cef29f122438e84f01ead56766d5d7e937c448c9af3435b41f8ea1", + "frontend-modern/src/components/Settings/NodeModalClusterMembersSection.tsx": "ed036203cef01e3a3a150da268f8e9da8ccda3ffa3490fcb1d590a7043c92be4", + "frontend-modern/src/components/Settings/NodeModalMonitoringSection.tsx": "458f5b833cc2139b3adde4f8c65c7bcf42a80380f7e20af0d473649caf8e82be", + "frontend-modern/src/components/Settings/NodeModalSetupGuideSection.tsx": "0118b486d7bcc7bc38afc69eb6a345f904aae901183e8b188ef0ec9b747019fa", + "frontend-modern/src/components/Settings/NodeModalStatusFooter.tsx": "ca0e8b456ee10b0f6b406eb7ca4cda7c75d7832e52280884f9cc9d4811c85692", + "frontend-modern/src/components/Settings/OrganizationAccessManagementSection.tsx": "382e9b5d55fbebb721e2d6de6286f4920d87733d0259965f865de0ffaf0c23b0", + "frontend-modern/src/components/Settings/OrganizationAccessMembersSection.tsx": "1d74c699d9304ceec29df9b19c238d9f119ba453f77707fdfd95483032dc2553", + "frontend-modern/src/components/Settings/OrganizationOverviewDetailsSection.tsx": "9bdfa79a89c0063e8f1309f4f974ec7e1872f5ca13bac5fd06ac5cfb5f18ef52", + "frontend-modern/src/components/Settings/OrganizationSharingCreateSection.tsx": "46625c86a8368013c971771d5e6e2354ad3a465788bab16c99fa9419721297ea", + "frontend-modern/src/components/Settings/ProLicensePlanSection.tsx": "4e321e4e4ee2d3890651502e9ae105188d68676fe4f3857a01a84ae7bbc9ca6f", + "frontend-modern/src/components/Settings/QuickSecuritySetup.tsx": "e98360c43b71b5ad5049e861ce65ec954a81a6985bc8ef495156c4d3ac9f7177", + "frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx": "e0880610229f305f20a737aadb1c48b50f3c3c0f48887b7d02bd678d8ed161ec", + "frontend-modern/src/components/Settings/RelaySettingsPanel.tsx": "dc93986289cc5d1cbb66adae4f30da4be0d6eda996b47690961597c30f3a1297", + "frontend-modern/src/components/Settings/ReportingPanel.tsx": "a4809aaed9f5b226949029f43fb2597255633ff9d45ac74a450d723f027d9d48", + "frontend-modern/src/components/Settings/ResourcePicker.tsx": "13d23bfb96921a5d789f6171483822ce1cdde018f5d366ce762184b15daad51d", + "frontend-modern/src/components/Settings/RolesEditorDialog.tsx": "c3324244fcb9175d63ac7ae9e8075419157ce51f7049aa223dc8e8215738da58", + "frontend-modern/src/components/Settings/SSOProvidersPanel.tsx": "d2f3e94a40ea352dde15954a1ed4476d2776ac97638f0199d7a8f1f39e05e781", + "frontend-modern/src/components/Settings/SecurityAuthPanel.tsx": "3f6e389fd6a004ce63f3dd177d17c379c9cdaf466c8aed88c59e25b99fd54ba0", + "frontend-modern/src/components/Settings/SecurityOverviewPanel.tsx": "6ed24129944782c7a2154941460faf29ac650adcd41a01e32e937112acb6d73e", + "frontend-modern/src/components/Settings/SelfHostedCommercialRecoverySection.tsx": "238745b0d56c32048009129e6ea2ee231950196d53e7eb6f68447d9d7b0847d7", + "frontend-modern/src/components/Settings/SettingsPageShell.tsx": "51156ab7af99310d8d5967414d869c275d345db6c21a9c7d98855495c3c19496", + "frontend-modern/src/components/Settings/SuggestProfileModal.tsx": "434a29fb1049a1a6c585e151af21fe936a6b4ef671cab01c68865dfbedab8e8f", + "frontend-modern/src/components/Settings/SystemLogsPanel.tsx": "f42d2e559fa65c03b0846a28726098a2082da6206a18e2efcf33a2dd8df11ddb", + "frontend-modern/src/components/Settings/UpdateInstallGuide.tsx": "01ab141607a534c5163a051f227f5c9e9434f845f67e3bc915b46d175b864b6e", + "frontend-modern/src/components/Settings/UpdatesSettingsPanel.tsx": "66f4e07ea0e6198d4580e0b2079ea6358d8e750f8b6c03841dde89d2c47e1f1a", + "frontend-modern/src/components/Settings/UserAssignmentsDialog.tsx": "cb59691911062a15913644d2de9f02c27cc86932fcf20024224aaa058171437a", + "frontend-modern/src/components/Settings/UserAssignmentsPanel.tsx": "bf05b6645e76955054f805c7699f244e98975b0fd79a958c074f6bf118f5d567", + "frontend-modern/src/components/Settings/availabilitySettingsModel.ts": "ca34b63a15e0b8d9ce6331439e09fd8fe76c2d44465547dc478e0d088d96c3a7", + "frontend-modern/src/components/Settings/connectionsTableModel.ts": "5ed828ac5652c0c813cd5179b8d8dc13c9b76d59380e2f54a691ef2c4af9c601", + "frontend-modern/src/components/Settings/useConnectionsLedger.ts": "39e5c3cfeef25c3b6198f954fe774f4ec57ac85aaf444bf787bc392c22853109", + "frontend-modern/src/components/Settings/useProLicensePanelState.ts": "0b71e9fb64fbe946aa56058f588caaf64ed5f4d9c25ad15277f66cb4eca520a1", + "frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx": "0dea55e92f9dd8f15c4faaaf5377b596ee9b291d2071cefaf48f31b430aa8a73", + "frontend-modern/src/components/SetupWizard/steps/SecurityStep.tsx": "68cb0ad01917bc4096f318d3ed3129d807f297c903a6afaaa6a93f05188cfffd", + "frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx": "cd309f7512602e4e661a74dd0da0cb10a756901e403d26fe64451fcfe1cf9781", + "frontend-modern/src/components/Storage/EnhancedStorageBar.tsx": "053be0f27fc62284583a698b398f2cc966a66ceb75dbadeeda79d53311885194", + "frontend-modern/src/components/Storage/StorageDetailKeyValueRow.tsx": "5fc49dda638a5dbd5abae3c6d5ef8b8328da70d24bd6a79b7a7bbf0cdfb235ce", + "frontend-modern/src/components/Storage/StoragePoolDetail.tsx": "40b13df152ebdf819af836700d86f9c451dfd973fd54ddb1fd6e501bdebe4fec", + "frontend-modern/src/components/Storage/StoragePoolsTable.tsx": "916448b8d29943b86d7a1904ffcd24c092872322aedcc90822a4fe369673c9d5", + "frontend-modern/src/components/Toast/Toast.tsx": "797187aab16e3340496a1c1738bc6513f2f8c48977c123618ee3687d4a050ba6", + "frontend-modern/src/components/TokenRevealDialog.tsx": "95cdde5f92eaa0376a7578d1e4c65e36d92fbc6e2fdad83f8aabb99b94a20171", + "frontend-modern/src/components/UpdateBanner.tsx": "a8fdc28100f5b72718ad12dacf1047f4d15a18abdf85d534bb471a48c99f4b95", + "frontend-modern/src/components/UpdateConfirmationModal.tsx": "f09606339879c51a1c4caf51d9fb26a8c154839cbcf01ba53a9290021df324a2", + "frontend-modern/src/components/WhatsNewCard.tsx": "2081d440a67c1584257a3d90b6a5746ca3278e38e94082d89a65813bc6223d9d", + "frontend-modern/src/components/Workloads/AvailabilityProbeSuggestionCard.tsx": "ae7875a04af91f5fed299553b342cd09c409f8991c3d7afc8e93164d269d2965", + "frontend-modern/src/components/Workloads/DiskList.tsx": "fe94782c65955eea183413d90c34bf32abfdce9ff3a4642eab4904cf7117ed0c", + "frontend-modern/src/components/Workloads/DrawerDiskListCard.tsx": "4c9af9c377f2ee5b026ff985eb754894e23dfb88525b2d327dd4afad0f00370f", + "frontend-modern/src/components/Workloads/EnhancedCPUBar.tsx": "f6de759fd694f1a3ebabac3067ae10604919feaa9b24bc02368a071fa7e2c438", + "frontend-modern/src/components/Workloads/GuestDrawerHistory.tsx": "f9f2e76ef0fad04251d0cb5873ce7df4d14d3a1ee5586daca731a2aa71addb3c", + "frontend-modern/src/components/Workloads/GuestPhysicalDisks.tsx": "ed9f8a69c82f922dee6e1392627771578bef32d3b1f38da632b3163d7d6063c4", + "frontend-modern/src/components/Workloads/GuestRow.tsx": "feacf9cf9d2507501d7ab83ecffdf417889e8df21664d6ced0b4cc997ab0b10e", + "frontend-modern/src/components/Workloads/GuestRowCells.tsx": "db6dec5c11fdbf49f68d751763352ea3c30d0be51e5c0e52cdf15926b380ac25", + "frontend-modern/src/components/Workloads/MetricMiniSparkline.tsx": "8b0eda5aa1abf8699f5222cc0463c7f7483d3500be9fd6c25b44a224d13d3d86", + "frontend-modern/src/components/Workloads/StackedDiskBar.tsx": "d99a2dd365b6d83739612a5411b3c459c137104ebd1efb95924966ec4ae92593", + "frontend-modern/src/components/Workloads/StackedMemoryBar.tsx": "abb1539a3c5828025fdd63667d936f726ea8e468d9c00e3d5da4664e35a90447", + "frontend-modern/src/components/Workloads/ThresholdSlider.tsx": "21fb9b1a967008fde466770c9a863d7e700b75f3be1c50ac334ec082c165e0ac", + "frontend-modern/src/components/Workloads/WorkloadPanel.tsx": "d0a4752f5ca91e97108275ee635641f02e91f6e8d82e3618a2f0d39fc35cc8fe", + "frontend-modern/src/components/Workloads/WorkloadsFilter.tsx": "a257ef62377a8475307ba6932c5b7d90d1ff2a37b82a28292a1d8c38a412df52", + "frontend-modern/src/components/Workloads/WorkloadsSurface.tsx": "539a443d5c139fceff17abc4d1ea3d2951714eeea8a2fbc92d87692e8d50f151", + "frontend-modern/src/components/Workloads/WorkloadsTable.tsx": "78d9dfe454579677ca4d5e3c2d18be69b6ef0937e357764cf0fbe681c7b29e4d", + "frontend-modern/src/components/Workloads/useGuestRowState.ts": "457c0d5d2612baef3a2356fd7042ab999f426b8fe27cd633d607fcff607e90ba", + "frontend-modern/src/components/patrol/ApprovalBanner.tsx": "35ef6025c20a2a8da7970cbe78fec9d7886e874ed0b2801b19da5db6d597c235", + "frontend-modern/src/components/patrol/ApprovalSection.tsx": "16c21c9e7210593006ca7d0b7857d23f899e2b875f2ab96c2dd514f975da446a", + "frontend-modern/src/components/patrol/InvestigationMessages.tsx": "7fdc4ff3ab278dba469e46e755756dbd15b1ef160808bfc23bd8eeb376d3e53c", + "frontend-modern/src/components/patrol/InvestigationSection.tsx": "c9cd5b390f9fbdb805aac55c556ab6746fb310c06ef4a7841ad46a9a70a4a1ad", + "frontend-modern/src/components/patrol/RemediationStatus.tsx": "9f18d9eae10d32063069721db0ce28ab121b9a7903d0ea1c175efe16b72c79d8", + "frontend-modern/src/components/patrol/RunHistoryEntry.tsx": "1f7bbd1aa49afd92198d0e2a90d324cb19a8b23a8af937419feceba3e3b22218", + "frontend-modern/src/components/patrol/RunHistoryPanel.tsx": "dc944c200a4a205a1ae953baad1afafb326abe3535e4e40f2a949c9b53ff843f", + "frontend-modern/src/components/patrol/RunToolCallTrace.tsx": "3043cc9e08bbe096d8231e88138ab744ec744a57379c6ec49d92592c82fba039", + "frontend-modern/src/components/shared/AIModelPicker.tsx": "2ed54c040320ec56fab38e58686f465137659aa78be019642c39607054e99a3a", + "frontend-modern/src/components/shared/CalloutCard.tsx": "49c421188e0c955a7154b86ad81c06519d071dbf583f6f452f86bad3b2141f50", + "frontend-modern/src/components/shared/Card.tsx": "20bd378ec801f47e5c3c48e186b7f4237280ba45dca55f7d5b8a14b03677ea8d", + "frontend-modern/src/components/shared/ColumnPicker.tsx": "b17016f890dba4ab31f5bb0144da55e310cbeeb168342156821747d1d09f06bd", + "frontend-modern/src/components/shared/CommandPaletteModal.tsx": "44c9b5d82a221936efddb23319e6dbd04e0b940c3aa093e6edcfc606f6a79c20", + "frontend-modern/src/components/shared/ContainerUpdateBadge.tsx": "9f4044015315a82be336bd9125c00afcfc83fec7650e59a23e659ef9a70ce5e2", + "frontend-modern/src/components/shared/CopyableCodeRow.tsx": "1a61d384b317bb6b1ddd56904fd6455afb54f824be8920e1cc6ac9de493eebbe", + "frontend-modern/src/components/shared/DetailSectionTable.tsx": "df4b83fbadb66e80b84abe31a0c5e57856ba203195578b174f2915372e1a0dc9", + "frontend-modern/src/components/shared/Dialog.tsx": "518fc81186801a45dd865e756fb952bb366fccbfed9a29c66a18cca4a0d499c4", + "frontend-modern/src/components/shared/DiscoveryReadinessBadge.tsx": "7a68483e221612e0f79722884f4aa1c7c528efa6ad38c8c4f7f583de0d656687", + "frontend-modern/src/components/shared/DrawerAttentionSection.tsx": "71f31900505e3356b3843200d7b0229a722ffc3ba68a43946b4bb3792ef96ff2", + "frontend-modern/src/components/shared/ExternalTextLink.tsx": "cbc009d9a5633b345904a64bb11df4ebd2d3b96c7c00d76d5d7e8caa7f607bcf", + "frontend-modern/src/components/shared/FeatureGateSection.tsx": "c9f3e13bc0a04b7f7190d70ae5b96b3026b59fa62555951850e3c65b15bd9261", + "frontend-modern/src/components/shared/FilterBar/AddFilterMenu.tsx": "9e7f05884c72cce34ff69bd2c419fc2e11b520eda976ec67d62c012ef62c7ad6", + "frontend-modern/src/components/shared/FilterBar/FilterBar.tsx": "386e265eeb8c55854d4a370c578e8fb83e1aad4c9f1dac60213b202814e12978", + "frontend-modern/src/components/shared/FilterBar/FilterChip.tsx": "c7eb7d168b2751889fac9415662c3c7ce5dbe01e6b56482abdc8bdcd25be3546", + "frontend-modern/src/components/shared/FilterBar/ViewOptionsDisclosure.tsx": "e2a81b990f56dc97663b1f319bafc3380f9319c2c9beea47f1594e8c2bd1081c", + "frontend-modern/src/components/shared/FilterButtonGroup.tsx": "a6b8f14452d7272c6ec87548ed8b89f0515193b9e54dcd31903c01c4828b0265", + "frontend-modern/src/components/shared/FilterToolbar.tsx": "864fdf324d954de6b91a0b79e2093de77eb26a6c68651f7963185673632e70c9", + "frontend-modern/src/components/shared/Form.ts": "ba01c891aaee0ea0fb0c443382536293dd95e2fdf3dda1542eeda5359f637825", + "frontend-modern/src/components/shared/HelpIcon.tsx": "bdf2b510632b7fb66771e00d5c5557623587feb9ee8ecc07a493b25d136c1abb", + "frontend-modern/src/components/shared/HistoryChart.tsx": "1c19a3dcedbc257bfd8e06973c16fa89c2828c9b7e45371975f7ab6386cce58d", + "frontend-modern/src/components/shared/HistoryChartHeader.tsx": "2b795422ca54cde763edd9b4a504493155dc8fa3935c51ebe0a63e51132adb3f", + "frontend-modern/src/components/shared/HistoryChartOverlay.tsx": "8533e11d960193220af84b8737501a8cd5b035ac6af83b5677c9d0eb1fe38f13", + "frontend-modern/src/components/shared/HistoryChartTooltip.tsx": "4ba8f9cb79bad7eeeb3af507375a6d246c866a31c8496901c9c6c3b5288b000d", + "frontend-modern/src/components/shared/InfoCardFrame.tsx": "f55622fed896138ee70670c134124479416cb95bab551b04f3570724130914a8", + "frontend-modern/src/components/shared/KeyboardShortcutsModal.tsx": "e5e6cb7bcfcd2e273b360abacfd766719351e14d756dcce02566dc1c297844ab", + "frontend-modern/src/components/shared/MetadataBadge.tsx": "1a7c7dcd20671a2f88fa09b1c1866ed568e2d06646d602e099861bc767df720d", + "frontend-modern/src/components/shared/MobileNavBar.tsx": "928ac70ceb8d81b87afc973b63f95ca7b1046a6e1228d59c9d2054a0e79ede64", + "frontend-modern/src/components/shared/NodeGroupHeader.tsx": "062c7f53ceaca78588bf0d2c0bf440581ec2b572bfcabbed1663481a20a9867a", + "frontend-modern/src/components/shared/ObjectDrawerHeader.tsx": "e3ec94868ef6f35d8c0cf6ac8c0fddde6006d539992aaf924633ebb8a7960348", + "frontend-modern/src/components/shared/ProgressBar.tsx": "42aedbe9ace44990bd864314d66ca92ef30613ceff6b52fbb62741efd86d8b60", + "frontend-modern/src/components/shared/PulseDataGrid.tsx": "db3696df71e4399aea7368b33a9b38488506735b05cd45741816667a6306aa90", + "frontend-modern/src/components/shared/SearchField.tsx": "c35df7aa1a6ccc941b7a9115f3788731889d6506b7db4d8eb3d513e6f2b46bb8", + "frontend-modern/src/components/shared/SearchTipsPopover.tsx": "77f8b9680dc122556c8e6393247e2ca5d508b0fd41fb202dd8fd0145ea12a030", + "frontend-modern/src/components/shared/SettingsLoadingSkeleton.tsx": "7df88ef95b04cfe91cb8ccdd6fabef036c67b423aafa691b6233887164a94d81", + "frontend-modern/src/components/shared/SkipToContentLink.tsx": "f61a08799e1df1ef022bda7c533631a0d6280b4fd72a49824bafb91fbcee5c12", + "frontend-modern/src/components/shared/StatusDot.tsx": "874b9bd3e8b18bbd8197ed5894ca7d9c9e3ad23c5cf233f25c24797df7dbbf77", + "frontend-modern/src/components/shared/StatusIndicatorBadge.tsx": "168cc2b605840c64cfd205c657d3acea4803f3bb065d82ca0e10fef5c61e506f", + "frontend-modern/src/components/shared/Subtabs.tsx": "d137c44dc3f8e4f0413c77a6725bb58d8280a1362b4f32571404b97225d7e80b", + "frontend-modern/src/components/shared/TableCardHeader.tsx": "ca640817f23a3ebed5200bc22925e46e56512ca50b242ca0b0d19e1280208af7", + "frontend-modern/src/components/shared/TagBadges.tsx": "375a6393d6de0a259908cd99022c0a815cb4e8df2ac1767edfdbb6642df02f11", + "frontend-modern/src/components/shared/TechnicalDetailsDisclosure.tsx": "ef8e248841efb9a392af0d5c6c0fc37102724eccea7d47a1cdb226e14d81a7ef", + "frontend-modern/src/components/shared/TlsVerificationWarningBanner.tsx": "aedb8d55f926700d226467e148beb77dc817f82d45b9711893c1642847e016a2", + "frontend-modern/src/components/shared/Tooltip.tsx": "a815fc91654d55d3261b6a935daab0b23426c8b803e54444d72e50e5ef3cf2b3", + "frontend-modern/src/components/shared/TooltipPortal.tsx": "664f04c7778ae138dba8ad1047058a6963060f186031f639350eeb73a8e4d590", + "frontend-modern/src/components/shared/WebInterfaceLink.tsx": "db9e1994f42c4ab6e8863ebee77ea1d8ab1552cb31be6b23cc80f9164b11cfea", + "frontend-modern/src/components/shared/WebInterfaceUrlField.tsx": "b91977883999991fa8246d0e8628d43c5d425264b18fbd466d688e27a7cac4a3", + "frontend-modern/src/components/shared/WorkloadTypeBadge.tsx": "fb8e8f2c5c44c47b3e8381fa1054e0a3aa6a4db575aa5d477fa4309e094406cf", + "frontend-modern/src/components/shared/buttonModel.ts": "868349753949769f2485cb0470ac2812ba4eaf404e3a50a81af60678c5892bdb", + "frontend-modern/src/components/shared/cards/NetworkInterfacesCard.tsx": "a628498b35841e0ef73b87c2aaceda458d99f5069351d1cf7a16db70286cf680", + "frontend-modern/src/components/shared/cards/RaidCard.tsx": "ab63e4afce31a26da06825b526c1c6e40c8193f03d00c9af315950e2f9acce73", + "frontend-modern/src/components/shared/containerUpdateBadgeModel.ts": "4a24e7b857a172892c1f4abc293de6d33b6be407476e48cacccf0947eeaa7c6e", + "frontend-modern/src/components/shared/dialogModel.ts": "55987d316c38be24b771a8d31ae74136fd0aa13d28dd88239cfea216d391b3d9", + "frontend-modern/src/components/shared/filterButtonGroupModel.ts": "01dd282f323a8d0ee1f85561c77d83d3c073e6372a2258964e60de019ee7f836", + "frontend-modern/src/components/shared/groupedTableRowPresentation.ts": "3665ff989387f4261275476014ac5e8d2ee5feb652159f53087a062c30f7c495", + "frontend-modern/src/components/shared/mobileNavBarModel.ts": "7f98d2a58c1beb9eb1e5e84dbea5717fbcf9ca68a59f06269ea423e5c6973d5b", + "frontend-modern/src/components/shared/searchInputEnhancementsModel.ts": "4bfba0b400184d3a9eb85b3c0f04ced02eed4f171510fd246dbb8d7d6d5acf17", + "frontend-modern/src/components/shared/searchTipsPopoverModel.ts": "7e6e85e55f2df2fa9dc5ee45d49bfa815fcf4317e47217e2ead83523fc800948", + "frontend-modern/src/components/shared/selectablePillModel.ts": "aaf34aaf2f33d7dccd553bb69bc7dfab0cfd2e2818068caab139d2ae53846ec9", + "frontend-modern/src/components/shared/selectionCardGroupModel.ts": "ef2c99a471eaf64553c526fbd540d84954df06de6663cd2d4a32ae632a8a7b8e", + "frontend-modern/src/components/shared/sourcePlatformBadges.ts": "dbe107f52c57222b8e1fff249a6ccd5fe8ac0face80fb224a9c440a2c9e070bd", + "frontend-modern/src/components/shared/statusBadgeModel.ts": "701b513c734b54066ed2db20cfd99821383e7e2b8a03bcbe7a08ca72fce650c5", + "frontend-modern/src/components/shared/summaryInteractionA11y.ts": "f861aa2cc7f1089f44de381c15942120f5bb623f60c03ec1f342eaa76a7dfdd4", + "frontend-modern/src/components/shared/tagInputModel.ts": "7c9e12a0b3625e901f2672c1efdf732d802d0f1d00310da4e0c46d01af106d56", + "frontend-modern/src/components/shared/toggleModel.ts": "1a88ace8749a5958058a07f2a33b4e103166ad2b7a0d09ef08ea28af43d6fbec", + "frontend-modern/src/features/actions/ActionDecisionPacket.tsx": "46dabe5cb9cc26d3b1614ef74d29d09d3553dc54f907626b98c9a5e7e7eadd5e", + "frontend-modern/src/features/actions/ActionReviewDialog.tsx": "d215aea99db933f3689ae36c6a268111c10054337994461355031f068bec3834", + "frontend-modern/src/features/alerts/AlertAppriseDestinationsSection.tsx": "39aa637b157c3e3ccf9a2c32c79e1a66488d7e5069593cd53edcd6c6464d42b3", + "frontend-modern/src/features/alerts/AlertDeadManDestinationSection.tsx": "69dcd143f337ffd0f3d52ce97e96bf09f507bef4ae3d7923507497b088ee1760", + "frontend-modern/src/features/alerts/AlertDeliveryHealthCard.tsx": "6a0e1f34e7cb495663e26495f83cb0c5f14c8eccfe42be077fe1daa5430fb314", + "frontend-modern/src/features/alerts/AlertDeliveryLogCard.tsx": "22833823ac50577682312e38aba5710bc145e15e7f4b00e6c00a955c8b017495", + "frontend-modern/src/features/alerts/AlertDeliveryPausedCard.tsx": "b0c07063973fa178cfdd6451a8aa16164f346ae59b4ba5bae350895c3ce9ae58", + "frontend-modern/src/features/alerts/AlertDestinationsLoadErrorCard.tsx": "ccbf801dfc2122da4c854462e479a3ebfa4fe24c4ddd665e30400f8d710d530d", + "frontend-modern/src/features/alerts/AlertDestinationsLoadingState.tsx": "6da60c07d55c19d03a1aa7142060bd543841ddf1090dd252e637530da0ee9c94", + "frontend-modern/src/features/alerts/AlertEscalationSection.tsx": "b9cc1f3b5403c26c64c492e1b5cf6c7ae309881ea55f14823d8ec54d1667547f", + "frontend-modern/src/features/alerts/AlertHistoryAdministrationCard.tsx": "988adf5e0bc384e33f12702d1ea75ceec53a80653c47a232a14b3b93d157ecd8", + "frontend-modern/src/features/alerts/AlertHistoryFrequencyCard.tsx": "e2431446c099ece3116445c9fdb51259eae0b535c4c8863650beb32d63977dfa", + "frontend-modern/src/features/alerts/AlertIncidentSynthesisSummary.tsx": "9926aae0eb5157a6acde84e7be7b50b582a7de719ad74af1bb2de38d89fa14f8", + "frontend-modern/src/features/alerts/AlertOverviewActiveAlertsSection.tsx": "ea741de043a6489541973b08a3d8efe598724bc76e37e00f5f0fadc04c01a5cc", + "frontend-modern/src/features/alerts/AlertOverviewAlertCard.tsx": "2f6e854f3bd1b1c4766528ab356fce96a05035fc67d17924d854bd6286f35d87", + "frontend-modern/src/features/alerts/AlertQueueActionFeedback.tsx": "d487a98988d2aa4baa7e96a34854070b788b09fd4df06859309f2d108fdbdd9f", + "frontend-modern/src/features/alerts/AlertSnoozeAction.tsx": "75f7d128e70b3e4665c9497e6137a37872a0cb3df34cc661b483c7dc76dc2697", + "frontend-modern/src/features/alerts/ResourceMonitoringPolicyAction.tsx": "5dc3207d9748bcb1e532ae1207907b4fd6332b282985bff5ac832e3faceb83d1", + "frontend-modern/src/features/alerts/tabs/ScheduleTab.tsx": "474f2fbf49a8a46c96a46edd0549b3cf47f0c8ab9ec5596a650c4d15e4e80295", + "frontend-modern/src/features/docker/DockerConfigsTable.tsx": "16fc39a1f246b1f664ab837a297d3934666039f4541f2918457dc7e7840993e8", + "frontend-modern/src/features/docker/DockerContainerLifecycleControls.tsx": "dc748c7629b3db2c5b8e87f8aad124f18f64c10b5d357dd1bf3a3fdc6eda1046", + "frontend-modern/src/features/docker/DockerHostDrawerOverview.tsx": "9f724383802f23193b008b5f821427433832cd27fd03a394481d8d4dd57491b2", + "frontend-modern/src/features/docker/DockerHostsTable.tsx": "5e38e9e2ae6ba49a37154b23cf24a5b7ae1236c384163736d3dc473ae3c711f7", + "frontend-modern/src/features/docker/DockerNativeTableShared.tsx": "a817cdf9268b4b6d10399dec6caf7aed4d3f175c5583a76253d9e3a8e77323b7", + "frontend-modern/src/features/docker/DockerNetworksTable.tsx": "78dde7baff1ed642cf659754aeb4e3ca0895550a77a43463e7227cc1ee568c68", + "frontend-modern/src/features/docker/DockerSecretsTable.tsx": "1cc37083cc10f537d2c212d83ef613409669d650d6a87fe98d559f892105badf", + "frontend-modern/src/features/docker/DockerServicesTable.tsx": "73f8a294caed802a7da95f89962d1b610fe60bbe641ea39fbd233a09e3661181", + "frontend-modern/src/features/docker/DockerStorageUsageTable.tsx": "8b8fb2e76e4dfee8c4a26d813068c494837c4d328deeed2b828ffeca51b732fe", + "frontend-modern/src/features/docker/DockerTasksTable.tsx": "e74ce53b4d8e9bc7bd22632607c583b1e984f39ca2bed65d1d030a9e94c6a8c5", + "frontend-modern/src/features/docker/DockerVolumesTable.tsx": "67461b8cd980c007b7648c2ed3b61e7a5e557f212cd138ec80426faef56f9730", + "frontend-modern/src/features/kubernetes/KubernetesAutoscalingTable.tsx": "8114113f67b4d6e11b087c7e6767d2f9c5944c751359cd3eff06454fb5b489c6", + "frontend-modern/src/features/kubernetes/KubernetesClustersTable.tsx": "654f8e21a1afa4ab4e3102164e6c52c68063777d5330bbdebf72dfbbc69a89b0", + "frontend-modern/src/features/kubernetes/KubernetesConfigTable.tsx": "db2594730d99ae22f65bf42116321d23c4fa66648f44d2dc667bf26401a77a07", + "frontend-modern/src/features/kubernetes/KubernetesControllersTable.tsx": "d3803fa93798f509f44e00fb515a4415e966d8820c1149107064891344fa3f3b", + "frontend-modern/src/features/kubernetes/KubernetesEventsTable.tsx": "c615b68c4b40af4a1688086fae164a8aad7312c21f8d2de8a8936c66aa7c164d", + "frontend-modern/src/features/kubernetes/KubernetesNetworkingTable.tsx": "d750ad9fe003e3edad34b135435bf09b355485f1401db82c1924dadddb57108c", + "frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx": "ab53b41c52a2f9bb40f5b0cc90f799ba4ef70a1463e014fd3742504f243767e9", + "frontend-modern/src/features/kubernetes/KubernetesPolicyTable.tsx": "b29d65cb342ceada86265434a438031f6adb65d27cb51351f2d11ed7c566084e", + "frontend-modern/src/features/kubernetes/KubernetesServicesTable.tsx": "dbcd7a43d41cc1fde20e4d05081fcb343578daeaebf8edd41ba501d669f0c496", + "frontend-modern/src/features/kubernetes/KubernetesStorageTable.tsx": "14d614e5aa421a991c02ef4342bf888d044c98d043d5caa888dee757ed212339", + "frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx": "17664b98217a353adc5af13964ec74f53d310abdf1962cc1435f8a189b4490b9", + "frontend-modern/src/features/patrol/PatrolAutopilotAcknowledgementDialog.tsx": "a3673f76715848ee6f0c34292e09fdaaf7bd6d3fa45348e934a899a4e462431f", + "frontend-modern/src/features/patrol/PatrolIntelligenceBanners.tsx": "a27c9973ac925d925c9511f85da877fbbfd9aefd870e4509d699d3553be86a13", + "frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx": "62706787851fbea76c835a8e2bb34f6f4967de33ee326501de44f9f58435985f", + "frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx": "dcdd0e0c58df7887c341f7d5c6e900158e612c04c7beb1197168c98cda4e6085", + "frontend-modern/src/features/patrol/PatrolIntelligenceWorkspace.tsx": "1770ce0c181a64e1de73ab17291216ec67d61b0e1993b2ecd8f1c33eb74099cc", + "frontend-modern/src/features/patrol/PatrolObjectivesPanel.tsx": "407aaad855fdaee779698a2b101281c9021d079d81917cee658b13a34d6b2fd1", + "frontend-modern/src/features/patrol/PatrolRecentWorkPanel.tsx": "22c9e85c144a50ab3055e843f10a2f83a3c35eec8af0d94bef44aebc1d33995d", + "frontend-modern/src/features/patrol/PatrolWeeklyDigestCard.tsx": "663575526acb6b2ea2b56def0122f459b253888f3e78cfc29c9859a96406f7b5", + "frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.tsx": "9dfe82bde38c39e88069d026800efce66c70cc8e42afc11340dd4204a33e3cda", + "frontend-modern/src/features/platformPage/PlatformWindowedRows.tsx": "0ee276286634abf14800d6f0f0021935ffb296307dbc6f3acd7764fa5b8ac587", + "frontend-modern/src/features/platformPage/sharedPlatformPage.tsx": "2266e87a50c0c2fd74b644cf32ba315f379a09f96fac2f82ef4a10749a5627fd", + "frontend-modern/src/features/proxmox/BackupActivityChart.tsx": "a2264f88229dbfd362648c201b20ccf674795f6daf4e510688bf6ec9fca9cdf2", + "frontend-modern/src/features/proxmox/ProxmoxBackupServersTable.tsx": "8b2d2ec1f0a2a3fe780ddc82c502880c2485c3389e2c462e0126f158745104d3", + "frontend-modern/src/features/proxmox/ProxmoxBackupsCoverageStrip.tsx": "a6cf34887798459643ba37cb01325b13709ebc33abc21ef66a688ac3239ab8fc", + "frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx": "2ae25d7a93043d37baccfd345bc0fa02a3dac706474279f3030a95d4a947cf5f", + "frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx": "b5333f20fe331a0ed094bd6543644c05533c1b36a0e5737f3ce4415e0072f260", + "frontend-modern/src/features/proxmox/ProxmoxCephTable.tsx": "4733b992aea7e20f5e3e9abbe597fd22aa44953e9ee44529ea4bd2a63fa0dfd8", + "frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx": "4f242d18904ce1205fa5e2643d35d4671c3464e374d3b26d8b921ca20df82ff4", + "frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx": "5e297168468cb2a0ae4f9c54926dade6481e5ca137bd08ae6d84b68477bde601", + "frontend-modern/src/features/proxmox/ProxmoxMailGatewayTable.tsx": "073545b11f2617928563aba81b6e272f4363cb86f79dd41b1a04117888e30de8", + "frontend-modern/src/features/proxmox/ProxmoxNodesTable.tsx": "25e2f2f5231d22920d2d2c1c4285e35dec4932ad73b6dc89857055884d2403d5", + "frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx": "80b4104e5dea31a356a6eb55c3de1fb24cec3b808d480aea2c7ed49ae429b542", + "frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx": "f4ac21e5a0397911c986c0fc154223b724645cbc01631a3941de43b566260c74", + "frontend-modern/src/features/proxmox/ProxmoxReplicationTable.tsx": "27a23f0c17288ad4f02338dcd7e4f771e932ab06d2a9ee09190d69aba9d0222a", + "frontend-modern/src/features/proxmox/proxmoxBackupsTableShared.tsx": "cd3fb5015f6a58f58a6be9dd568916a465dc9c55970cf297c8f75efc4fb8434f", + "frontend-modern/src/features/proxmox/proxmoxHostTableModel.ts": "7c77a2bd1c71753a83c55b2c9efb2d0b526f94f192439d474864723f02d57889", + "frontend-modern/src/features/standalone/AgentsMachinesTable.tsx": "6a26fd37020b1f81304685ab2fd9d8a3f9ed1c4a9a88a0287739b661fbb87e88", + "frontend-modern/src/features/standalone/AvailabilityFleetView.tsx": "a15aff11954b837fe6bbe7f2e5cc5701a5c42707dc70e0725d47d2cc3aed4d4a", + "frontend-modern/src/features/standalone/StandalonePageSurface.tsx": "8a5a5bc27c8fcc2a42b75a7f775775fb32f2fc9ecd025018b810e9b80108f1dc", + "frontend-modern/src/features/storageBackups/detailPresentation.ts": "90cb44fe5d276ea01ff5d1e9c37511ffd9ed1833e142ca3a4cc9336006d79eb4", + "frontend-modern/src/features/storageBackups/diskPresentation.ts": "c89274cfb53e86aa9306ac1c1a1e4febf480defc7aef4317c96da2158953de34", + "frontend-modern/src/features/storageBackups/groupPresentation.ts": "1366244532e7ddd289323af2065c2e32197e472c0572a07809557145e08585aa", + "frontend-modern/src/features/storageBackups/storageDomain.ts": "278ae487e439509d801958e801c781e4ae6a1713a271e910a5acc5cfad9aa47c", + "frontend-modern/src/features/storageBackups/storageFilterPresentation.ts": "5b606861c23d9f2b92b335807756510b233f9905bb15187bd3a259ddae0c2ac7", + "frontend-modern/src/features/storageBackups/storagePagePresentation.ts": "345942741c42e036736711b7613cde53448119adb94485ab81182625dca0bc50", + "frontend-modern/src/features/storageBackups/storageRowAlertPresentation.ts": "2f424e2349067441950b5f1da8d5b37ef42227dcc43b9dab7bf74a706f39a26c", + "frontend-modern/src/features/storageBackups/zfsPresentation.ts": "8accab0db26582d95aa40f98497e98b41a327b514197aea86af5cb13d08cc2bb", + "frontend-modern/src/features/truenas/TrueNASAppsTable.tsx": "04762448e46b3c1badd41fe2b9ecacef82f0282c3ea2490c4448a129bb473586", + "frontend-modern/src/features/truenas/TrueNASServicesTable.tsx": "9c2f4ae52b2c7be725e8f64cc5f59c9058d2c248bebbb95d864fa2a998a86e14", + "frontend-modern/src/features/truenas/TrueNASSystemsTable.tsx": "1a3c1b776852b5d718230a78589021ddfce86c61dc98c6e006f82cb9c1423231", + "frontend-modern/src/features/vmware/VmwarePageSurface.tsx": "7fc101afebe9a55d9e60efcf5a3bca630563dc398d0f65053e5c649c862a59df", + "frontend-modern/src/features/vmware/VsphereHostsTable.tsx": "99206612b0b2244fb0ecdbcf728395338e140eaf498daf44d7f96c08679b9e66", + "frontend-modern/src/index.css": "58b26f9589954ca33974f308bdba1a1f70306a90894d29c7ae2a57a6140bc35e", + "frontend-modern/src/pages/Actions.tsx": "f21b460b7346582e2af403ece22b1222b0512912cfe3ab072be2eca5a3d3a369", + "frontend-modern/src/pages/Alerts.tsx": "815928e2758dae874ffced5968128524f2bcb358764b682f05cee68fa3a7ef23", + "frontend-modern/src/pages/Docs.tsx": "2ddb5a1daef50d23a99a7d1cb510c73cd7a85600f07ea9f69ad72f741f3b5229", + "frontend-modern/src/pages/PricingHandoff.tsx": "361397533fe4c7a6feb9e767a3f9004e509b74fb142438f0344614cc0b73e174", + "frontend-modern/src/tailwind-palette.css": "70d33f3d7f50d397c9d14f07e96afaf2d76ca0d068cb11c3c3662f8f888fe8c7", + "frontend-modern/src/utils/actionAuditPresentation.ts": "7ededfa7ee1d990c2b6fe7efa8d8c4b4d9747687182e67804b321403e31662a8", + "frontend-modern/src/utils/agentCapabilityPresentation.ts": "f548264970ec5277e3e4677ae8879ed218dbec4cdff9edfbaceeca2020ea03e9", + "frontend-modern/src/utils/agentProfileSuggestionPresentation.ts": "0a62c988644a571d177f854488bf14a348844db977e4c11a1bd0ac1735b47c59", + "frontend-modern/src/utils/aiControlLevelPresentation.ts": "a1c669accdf689a1696179ca3145e06207c9f93f97f4481493aa73c5670db0cf", + "frontend-modern/src/utils/aiFindingPresentation.ts": "1ce5cb2a13cbe8c627d22afde1923c82c39e048faddaf9d46062aa91336100e3", + "frontend-modern/src/utils/aiProviderHealthPresentation.ts": "04875a58c288fde08e17e58528a0239fd419c0706b13ad4761b77e6563b86e9b", + "frontend-modern/src/utils/aiSettingsPresentation.ts": "33b4970d041bbd9cbba88eba5d62bbdd3855ac388f1ca25fb7deba2aee6e9e94", + "frontend-modern/src/utils/alertActivationPresentation.ts": "22780429fc4c944d965aee1a6241f6793c58f6b0f76397ea772896de20929ba0", + "frontend-modern/src/utils/alertFrequencyPresentation.ts": "03139ee758052b838d0fc7c86a41b7b4c21030e06516584adedfa75825fa8755", + "frontend-modern/src/utils/alertGroupingPresentation.ts": "ee0e2b7c7f357c817d1ae8bc5c962b3c3a0955d43d62948bdd44b1035fc45698", + "frontend-modern/src/utils/alertHistoryPresentation.ts": "560b4571b97dd5b63c3e1d444da1b93dd3bc494bf8ba9c022b0f9d5a6fe18abe", + "frontend-modern/src/utils/alertIncidentPresentation.ts": "9ab65844b92b1a765a10f33100058a0268e4681cb61b4aec70f9db35d471910e", + "frontend-modern/src/utils/alertOverviewPresentation.ts": "e17c4a7030b6b2b50c59624e563e5c413ab981bb39f066f0f7cad482d4373798", + "frontend-modern/src/utils/alertResourceTablePresentation.ts": "337fad4877039214a332ddfc6c2c9ab1cc02d329251c8a9885a5266da4ef933b", + "frontend-modern/src/utils/alertSchedulePresentation.ts": "d0921d895d003084b1c15dcff172a56ba889451c4f7ddce787abb064318e8c74", + "frontend-modern/src/utils/alertSeverityPresentation.ts": "23362496c93c96371f055f1a216fca2a0d465485ee2448203ac1d3f83b35cd9a", + "frontend-modern/src/utils/alertTabsPresentation.ts": "7617902999621e3af01f27f68b74384467e285f7007ecc5ca0150fd6142e48b3", + "frontend-modern/src/utils/alerts.ts": "628a7a41b5546bc3c05ce19c254cd3bc1c2aa683b71716133f9c2ad281f339c2", + "frontend-modern/src/utils/auditLogPresentation.ts": "fd7204b3b8dc1932f35b444433f7007b8a25e641d296b2108668ab7d8b0779ad", + "frontend-modern/src/utils/auditWebhookPresentation.ts": "dce90b54d19282bd3001b228da5d47a664ecdcbfb990d630808adffe772d1710", + "frontend-modern/src/utils/discoveryPresentation.ts": "15c2b0acbfff32d0d16db4aed473087afbdd5b2e46cc47d614c1863051d57c97", + "frontend-modern/src/utils/emptyStatePresentation.ts": "244354879e9f476e5245f982b607375765b74b54473111e43b4185d7396f255e", + "frontend-modern/src/utils/environmentLockPresentation.ts": "5044e5d00f5b3775b229c6c45b37b48b2ae421d1e6d5b6d34c9dc11d827f7625", + "frontend-modern/src/utils/licensePresentation.ts": "b6765aaf7c535f9bafa5668d8b5d4f78ff8295ea184c389c28095e63dceaf425", + "frontend-modern/src/utils/nodeModalPresentation.ts": "c2dbc6ab6b7588c6d01a102ffec18105d1cd589e908c6e4f70465fd6f1751d1e", + "frontend-modern/src/utils/orgUtils.ts": "920a53fef0f4980c923437f11d6e34418666835f2697450fefa1fa9f586169ba", + "frontend-modern/src/utils/patrolRunPresentation.ts": "931ddf0759bf999deb288a4234a5c6c3cc1254217b39f0d2c992b4cca866644d", + "frontend-modern/src/utils/patrolSummaryPresentation.ts": "338fc8fd68f0a412f88ea138ca1d2ef9679c5b2b37483cad8a51a888c5ee8c6e", + "frontend-modern/src/utils/platformSupportManifest.generated.ts": "68b2c2e0c3f0f66ac9d0cb81d922066b18da99824f7799997f1efea84ca444cf", + "frontend-modern/src/utils/raidPresentation.ts": "affdc07e317093024688c32757ec2ae36ae822a52460030ce203b966a576e403", + "frontend-modern/src/utils/recoveryArtifactModePresentation.ts": "6d171805355a668d4c0a99889ded4f587f90fc1ceb1b4d7d7e424b46627615c5", + "frontend-modern/src/utils/recoveryItemTypePresentation.ts": "f897246ebce4cb5d2a2201f02787b24bc999098fe8024b98bc87025b509922b0", + "frontend-modern/src/utils/recoveryOutcomePresentation.ts": "7b96feadef679031c7bd64c6034f2d1eb7a8fed892cb9705babd8a9546e9ec36", + "frontend-modern/src/utils/recoveryTimelineChartPresentation.ts": "36980c4ffb2292beeeaa3e45aa075fb135e2e8be75755bacbac59c9b19ddc141", + "frontend-modern/src/utils/recoveryTimelinePresentation.ts": "a63caa8fdf67e4e15f82f8818314663081f16433f01950754802d17b4ac3322e", + "frontend-modern/src/utils/relayPresentation.ts": "cf668ef232330d9de915129baff5be7f939d7cf8f4d1eaed96f24b375230f1a7", + "frontend-modern/src/utils/remediationPresentation.ts": "2bfa6550311500f7f42c69f57f48ce9e43263b1f514f1d85067e9ec3e2291f2b", + "frontend-modern/src/utils/resourceBadgePresentation.ts": "2cd546618528d3421490291a0dfb63ca2d6fd6a2735d48872b4fe4f60accf582", + "frontend-modern/src/utils/resourceChangePresentation.ts": "9dec581a704f25c4891efd9a4bd1be7814536d79df2b59daef84f84c5aad9086", + "frontend-modern/src/utils/resourcePolicyPresentation.ts": "08300bb46f436cac1b2e2c9841e34f0e54a0a23b34027463d6d85b289885b918", + "frontend-modern/src/utils/securityScorePresentation.ts": "ff4a97c9d884197072fdf4bcaef0ab9b3a8eeabd323ce9e29515a2c3112eadab", + "frontend-modern/src/utils/semanticTonePresentation.ts": "6e8e58f7184d887529e6873679b45b569662ba5a993a592262947d03a3603fd2", + "frontend-modern/src/utils/serviceHealthPresentation.ts": "8d4acb9257fb04bf22fb1ecf8baa76be8bd73d4f8ff7a30613d523f6fd9112d2", + "frontend-modern/src/utils/sourcePlatforms.ts": "d2e23d3e6f0bd0b85f5c5cf44363c89a62d92ab9d87cdbfe09aea32608874e77", + "frontend-modern/src/utils/sourceTypePresentation.ts": "ea6b7e2ab9d28f64096761828abffe2d1d28621f29a23eb16ef3cd6877d33149", + "frontend-modern/src/utils/ssoProviderPresentation.ts": "bebd1e6a80e91ba2f382f51fc92ee21219a2d8ddbfc25cca835687b2273bc97f", + "frontend-modern/src/utils/status.ts": "47aa5db56107290cd573732cc2d080a6cbb518e3c50a8e733a06809516c701fe", + "frontend-modern/src/utils/systemLogsPresentation.ts": "3e4e34d9642eb8a9b30499ff5dde2a98c7ca92020ee02ff53b59fb6c121797a1", + "frontend-modern/src/utils/unifiedAgentStatusPresentation.ts": "2040add4e038d43b1576d08ec8e3a5c701e11b55f4edd73df035ce033a3468e0", + "frontend-modern/src/utils/updatesPresentation.ts": "64d91c2e62f812ec20f7e28eae6a0189b2bcc09dc1b56b2df719c1a443def63c", + "frontend-modern/src/utils/workloadGuestPresentation.ts": "7ac58b7802065bb87ee07c64c609714b219e243d981be174c17dfe179e34a69c", + "frontend-modern/src/utils/workloadTypePresentation.ts": "18289c2ee223c8ae28bdacc099993aa45b00642ea6b7b10ae0128582c9886e03" }, "routes": [ "/proxmox/overview", "/proxmox/storage", "/proxmox/replication", + "/proxmox/backups/date", "/proxmox/ceph", "/proxmox/mail", "/docker/overview", - "/docker/images", - "/docker/storage", - "/docker/networks", - "/docker/swarm", "/kubernetes/overview", - "/kubernetes/nodes", - "/kubernetes/workloads", - "/kubernetes/services", - "/kubernetes/storage", - "/kubernetes/configuration", - "/kubernetes/events", "/truenas/overview", - "/truenas/storage", - "/truenas/services", - "/truenas/apps", - "/truenas/vms", - "/truenas/shares", - "/truenas/protection", "/vmware/overview", - "/vmware/storage", - "/vmware/networks", - "/vmware/health", - "/vmware/activity", "/standalone/machines", - "/standalone/availability", - "/alerts/overview", - "/alerts/history", - "/alerts/thresholds", + "/alerts", + "/patrol", "/actions", "/settings/infrastructure", - "/settings/monitoring", - "/settings/pulse-intelligence", - "/settings/organization", - "/settings/security-users", - "/settings/security-webhooks" + "/docs/TROUBLESHOOTING" ], "viewports": [ { "width": 1440, - "height": 900 - }, - { - "width": 1280, - "height": 900 - }, - { - "width": 900, - "height": 900 - }, - { - "width": 700, - "height": 900 - }, - { - "width": 560, - "height": 900 + "height": 1600 }, { "width": 390, - "height": 900 + "height": 1400 + }, + { + "width": 800, + "height": 1400 } ], "states": [ - "Mock stack on origin/main plus this change; Playwright sweep of every table on the routes above at all six viewports, before and after, recording computed th/td padding per cell and leaf elements with scrollWidth > clientWidth under hidden overflow (455 table states compared)", - "Default TableCell/TableHead with no caller padding keep 8px/12px horizontal and 2px/6px vertical padding (Settings infrastructure source and agent tables unchanged at 12px/12px/6px/6px)", - "Platform tables (Proxmox nodes, Docker hosts and containers, Kubernetes clusters/nodes/deployments/pods, TrueNAS systems, vSphere, Machines): cells 12px -> 8px horizontal at 700px and up and 8px -> 6px at 560/390px; header cells keep the stylesheet 6px horizontal and drop to 2px vertical, header rows 29px -> 21px; row height 32px unchanged; clipped-cell count improved in 89 table states, unchanged in 362; the 4 states counted worse are live mock-value drift (relative-time and usage labels) with identical or wider content boxes", - "Physical Disks table (Proxmox > Storage > Physical Disks): cells 8px -> 4px horizontal at 390px and 12px -> 6px at 700px, matching the composed px-1 sm:px-1.5 lg:px-2; clipped cells at 390px 93 -> 18; detail row 12px -> 16px horizontal at 700px and 1440px", - "Storage pools table unchanged at 6px/8px cell padding across 390, 560 and 700px", - "Inline detail rows (Proxmox node, Kubernetes controller, TrueNAS system expansions): detail cell padding 12px/12px/2px/2px -> 0, detail panel inset now only its own px-2 sm:px-4; incident timeline panel in Alert History gets its p-3", - "Alerts overview stats table: dot column right padding 12px -> 0 and value column 12px -> 6px at 700px and up as its classes name; Alert History header cells 12px -> 8px horizontal; AI cost table (Settings > Pulse Intelligence) Est. USD column 12px -> 4px as its px-1 names", - "Workloads table header cells now 6px/8px to match their raw td data cells; Proxmox replication compact-mode !px-1 override still applies at 390px", - "Reactive case: switching a cell class between px-1 and a non-padding class re-resolves the base padding (unit test)" + "Private mock stack: main (Tailwind v3) and this change (Tailwind v4) served side by side from one backend; Playwright loaded each route on both and compared every element's computed style plus a full-page screenshot pixel diff", + "64 route/theme/viewport states (16 routes x light and dark x 1440 and 390 with touch emulation at 390): 59 pixel-identical; the rest are mock drift (alert counts, last-refresh and last-scanned timestamps, tab-strip scroll timing) verified identical on rerun, plus 12 antialiased edge pixels on vSphere at 390", + "Docs page: identical pixels; prose text colors differ by at most 2/255 per channel because @tailwindcss/typography reads Tailwind 4's own palette", + "Machines at 390: row action button now renders its declared 44x44 touch target (28x28 under v3, whose lexical sort let the base min-h-6 win) while rows stay 32px; pixel-identical to main", + "Runtime scan of 82 states (every tab one level under each platform, Alerts, Patrol, Actions and Settings, with the first row expanded, at 1440 and 390) plus a static scan of shared-primitive call sites for class pairs whose cascade winner differs between v3 and v4: touch-target overrides keep their authored 44px phone size, cosmetic overrides that v3 never applied were removed so they keep rendering as today, and overrides v3 did apply use the v4 important suffix", + "Proxmox guest drawer and Docker host drawer at 1440 and the Proxmox storage table header at 800: pixel-identical", + "npm audit --json over the full graph: 0 findings; braces, micromatch, fast-glob and chokidar absent from the lockfile" ], "interactions": [ - "Load each route at 1440, 1280, 900, 700, 560 and 390px and run the padding and clip sweep over all tables", - "Click Physical Disks on Proxmox > Storage at 390, 560, 700 and 1440px and expand the first disk row", - "Expand the first node row on Proxmox > Overview, the first deployment row on Kubernetes > Workloads, the first host row on Docker > Overview and the first system row on TrueNAS > Overview at 390, 560, 700 and 1440px", - "Expand the first alert row incident timeline on Alerts > History at 1440px", - "Compare before and after JSON sweeps per route, tab, viewport and table; re-measure the four states counted worse three times each", - "Revert and restore the change on the running dev server to A/B the Kubernetes clusters memory bar label at 560px" + "Hovered the second table row on Proxmox, Docker and Alerts at 1440 and at 390 with touch emulation in light and dark: identical row background and pixels (hover is not limited to hover-capable pointers)", + "Keyboard-focused the search field with Shift+Tab then Tab: same 2px blue focus ring; the transparent 2px outline becomes outline-style none (outline-hidden keeps it in forced-colors mode)", + "Expanded the first row on Proxmox and Docker at 1440 and 390: identical pixels apart from timestamp drift", + "Measured the Machines row action button and row height at 390 and 1440 on both builds", + "Opened the Snooze menu on an Alerts card at 1440 on both builds: identical pixels" ] } diff --git a/frontend-modern/package-lock.json b/frontend-modern/package-lock.json index b7fa03015b..6810a21570 100644 --- a/frontend-modern/package-lock.json +++ b/frontend-modern/package-lock.json @@ -21,22 +21,21 @@ "@eslint/js": "^9.39.2", "@solidjs/testing-library": "^0.8.5", "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.3.3", "@testing-library/jest-dom": "^6.5.0", "@types/node": "^26.6.2", "@types/qrcode": "^1.5.6", "@typescript-eslint/eslint-plugin": "^8.70.0", "@typescript-eslint/parser": "^8.70.0", "@vitest/coverage-v8": "^4.1.11", - "autoprefixer": "^10.6.1", "eslint": "^9.20.0", "eslint-config-prettier": "^10.0.0", "eslint-plugin-solid": "^0.18.0", "globals": "^17.12.0", - "jscpd": "^4.0.8", + "jscpd": "^5.4.0", "jsdom": "^24.1.0", - "postcss": "^8.5.28", "prettier": "3.9.8", - "tailwindcss": "^3.4.18", + "tailwindcss": "^4.3.3", "typescript": "^5.3.0", "typescript-eslint": "^8.70.0", "vite": "^6.4.3", @@ -52,19 +51,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@asamuzakjp/css-color": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", @@ -395,17 +381,6 @@ "node": ">=18" } }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -1295,108 +1270,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jscpd/badge-reporter": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@jscpd/badge-reporter/-/badge-reporter-4.2.5.tgz", - "integrity": "sha512-ktXrjPeRaRyUDktxTroSA2/w5sshXpQplWkUuq/e6XqEpKBSbGEnwZLIaegSijOrMwIcCXPQ9k4feXIz5eVJNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "badgen": "^3.2.3", - "colors": "^1.4.0", - "fs-extra": "^11.2.0" - } - }, - "node_modules/@jscpd/core": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@jscpd/core/-/core-4.2.5.tgz", - "integrity": "sha512-Esf2deHxaoNEjePwf2jqP6Urzj+BAOsJVPFLbnnSsV+q7rLNMcn0UEEoKBXIOOt4qMkrkhl9DfwpMyPPOr6GkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventemitter3": "^5.0.1" - } - }, - "node_modules/@jscpd/finder": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@jscpd/finder/-/finder-4.2.5.tgz", - "integrity": "sha512-Rw0dtwp/EeLANbujOubuQeJIuXXXkAlT+f5geZhwkB9TxEYP0hqNrdOJUK/TDBKQjRGrOizEtdNy+S4UlbdzOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jscpd/core": "4.2.5", - "@jscpd/tokenizer": "4.2.5", - "blamer": "^1.0.6", - "bytes": "^3.1.2", - "cli-table3": "^0.6.5", - "colors": "^1.4.0", - "fast-glob": "^3.3.2", - "fs-extra": "^11.2.0", - "markdown-table": "^2.0.0", - "pug": "^3.0.4" - } - }, - "node_modules/@jscpd/html-reporter": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@jscpd/html-reporter/-/html-reporter-4.2.5.tgz", - "integrity": "sha512-zMMIKbvi43dMgeNeHXlHQy1ovf+KJrzNlUubaBvCAVatqP23ksW8d3fmsevIQG9mMMTH0D1xOz+SxUn1FREOPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "colors": "1.4.0", - "fs-extra": "^11.2.0", - "pug": "^3.0.4" - } - }, - "node_modules/@jscpd/tokenizer": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@jscpd/tokenizer/-/tokenizer-4.2.5.tgz", - "integrity": "sha512-UM8Wx/jwahmflqQExlcKMQTYOAy58N/fn7Pv6NYrkD3EZm/FTk7gW97wkXy5aDE1Ts9oBUpT9tLY2rz7ogCHAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jscpd/core": "4.2.5", - "spark-md5": "^3.0.2" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.62.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", @@ -1824,6 +1697,275 @@ "dev": true, "license": "MIT" }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, "node_modules/@tailwindcss/typography": { "version": "0.5.20", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.20.tgz", @@ -1837,6 +1979,21 @@ "tailwindcss": ">=3.0.0 || >=4.0.0 || insiders" } }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -1988,13 +2145,6 @@ "@types/node": "*" } }, - "node_modules/@types/sarif": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", - "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -2453,34 +2603,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, - "license": "MIT" - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2498,20 +2620,6 @@ "dequal": "^2.0.3" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "license": "MIT" - }, - "node_modules/assert-never": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz", - "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==", - "dev": true, - "license": "MIT" - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -2548,108 +2656,6 @@ "dev": true, "license": "MIT" }, - "node_modules/autoprefixer": { - "version": "10.6.1", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.6.1.tgz", - "integrity": "sha512-cL1Qz6ADZhcEbny/8HPfe99J6HhNoYtpX2LFLIbhgGE7Q1hlQVkYFdetDN7Id3KiQxhDrHwzlHr/YQCnZ8+xSA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.9", - "caniuse-lite": "^1.0.30001810", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/autoprefixer/node_modules/browserslist": { - "version": "4.29.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz", - "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.11.23", - "caniuse-lite": "^1.0.30001810", - "electron-to-chromium": "^1.5.427", - "node-releases": "^2.0.55", - "update-browserslist-db": "^1.3.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/autoprefixer/node_modules/update-browserslist-db": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", - "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/babel-plugin-jsx-dom-expressions": { "version": "0.40.7", "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.7.tgz", @@ -2699,26 +2705,6 @@ } } }, - "node_modules/babel-walk": { - "version": "3.0.0-canary-5", - "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", - "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.9.6" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/badgen": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/badgen/-/badgen-3.3.2.tgz", - "integrity": "sha512-fbQwK9norfdzbdsoPwbLIAmgBXDGEme3jeIyqPAH7o6vp9lmuLHS7uXULvOiQ6XnMLkYNG4gDjILf74hgtTAug==", - "dev": true, - "license": "MIT" - }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -2742,33 +2728,6 @@ "node": ">=6.0.0" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/blamer": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/blamer/-/blamer-1.0.7.tgz", - "integrity": "sha512-GbBStl/EVlSWkiJQBZps3H1iARBrC7vt++Jb/TTmCNu/jZ04VW7tSN1nScbFXBUy1AN+jzeL7Zep9sbQxLhXKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^4.0.0", - "which": "^2.0.2" - }, - "engines": { - "node": ">=8.9" - } - }, "node_modules/brace-expansion": { "version": "5.0.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", @@ -2782,19 +2741,6 @@ "node": "20 || >=22" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browserslist": { "version": "4.28.8", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", @@ -2829,16 +2775,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2853,23 +2789,6 @@ "node": ">= 0.4" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2889,16 +2808,6 @@ "node": ">=6" } }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001810", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", @@ -2947,70 +2856,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/character-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", - "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-regex": "^1.0.3" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, "node_modules/cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -3054,16 +2899,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3077,16 +2912,6 @@ "node": ">= 0.8" } }, - "node_modules/commander": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", - "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=22.12.0" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3094,17 +2919,6 @@ "dev": true, "license": "MIT" }, - "node_modules/constantinople": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", - "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.6.0", - "@babel/types": "^7.6.1" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3249,12 +3063,15 @@ "node": ">=6" } }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } }, "node_modules/dijkstrajs": { "version": "1.0.3", @@ -3262,20 +3079,6 @@ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", "license": "MIT" }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, - "license": "MIT" - }, - "node_modules/doctypes": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", - "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", - "dev": true, - "license": "MIT" - }, "node_modules/dom-accessibility-api": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", @@ -3320,14 +3123,18 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "node_modules/enhanced-resolve": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz", + "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==", "dev": true, "license": "MIT", "dependencies": { - "once": "^1.4.0" + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" } }, "node_modules/entities": { @@ -3733,37 +3540,6 @@ "node": ">=0.10.0" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, "node_modules/expect-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", @@ -3781,36 +3557,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3825,16 +3571,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3848,19 +3584,6 @@ "node": ">=16.0.0" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3916,35 +3639,6 @@ "node": ">= 6" } }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fs-extra": { - "version": "11.3.6", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", - "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4028,22 +3722,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4219,16 +3897,6 @@ "node": ">= 14" } }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8.12.0" - } - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -4296,59 +3964,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-expression": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", - "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^7.1.1", - "object-assign": "^4.1.1" - } - }, - "node_modules/is-expression/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4394,16 +4009,6 @@ "node": ">=8" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -4411,45 +4016,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-what": { "version": "4.1.16", "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", @@ -4510,22 +4076,15 @@ } }, "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "dev": true, "license": "MIT", "bin": { - "jiti": "bin/jiti.js" + "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/js-stringify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", - "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", - "dev": true, - "license": "MIT" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4557,37 +4116,154 @@ } }, "node_modules/jscpd": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/jscpd/-/jscpd-4.2.5.tgz", - "integrity": "sha512-KDpApYw1ChGelfHb7MwYTEx694OnW52pv3McAasidUV4ILcGDQMiVJzB+vI8ox+ZPVfOSvdXQCk8uRa9B0LXnw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jscpd/-/jscpd-5.4.0.tgz", + "integrity": "sha512-6mU8OHff4rPJ3D5lIsQns82/aQhDas22gMsGurHx1wcrfXWOZDNaobi6rKhLevo+77ntWKUXOwtM6lcrkJeL7w==", "dev": true, "license": "MIT", - "dependencies": { - "@jscpd/badge-reporter": "4.2.5", - "@jscpd/core": "4.2.5", - "@jscpd/finder": "4.2.5", - "@jscpd/html-reporter": "4.2.5", - "@jscpd/tokenizer": "4.2.5", - "colors": "^1.4.0", - "commander": "^15.0.0", - "fs-extra": "^11.2.0", - "jscpd-sarif-reporter": "4.2.5" - }, "bin": { - "jscpd": "bin/jscpd" + "jscpd": "run-jscpd.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://opencollective.com/jscpd" + }, + "optionalDependencies": { + "jscpd-darwin-arm64": "5.4.0", + "jscpd-darwin-x64": "5.4.0", + "jscpd-linux-arm64-gnu": "5.4.0", + "jscpd-linux-arm64-musl": "5.4.0", + "jscpd-linux-x64-gnu": "5.4.0", + "jscpd-linux-x64-musl": "5.4.0", + "jscpd-windows-arm64-msvc": "5.4.0", + "jscpd-windows-x64-msvc": "5.4.0" } }, - "node_modules/jscpd-sarif-reporter": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/jscpd-sarif-reporter/-/jscpd-sarif-reporter-4.2.5.tgz", - "integrity": "sha512-O8LcM9grAS5yO5x1Q0yegYaYcUX//IEBEyvzGFSYCeo1YzHbMnAI6EK7oTrwD+7Csjvfg9m8B8G7OOxzcSlr9w==", + "node_modules/jscpd-darwin-arm64": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jscpd-darwin-arm64/-/jscpd-darwin-arm64-5.4.0.tgz", + "integrity": "sha512-fNIDtNn9WE4YXoFoqf+3tkHb1+GaKKOeKzh77Wx61Gfa9iDxlCv5rO0Fjy7oWmGehUUKqslDaeXXtn/RoDprCg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "colors": "^1.4.0", - "fs-extra": "^11.2.0", - "node-sarif-builder": "^4.1.0" - } + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/jscpd-darwin-x64": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jscpd-darwin-x64/-/jscpd-darwin-x64-5.4.0.tgz", + "integrity": "sha512-bxlnCVEb69SGIsas1+PaqZyFY8iYVTVjCBxfjVD74UafX115Ysw+oO9LAlsJaGsKUnK8Zsq5bhM33kBHux6bTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/jscpd-linux-arm64-gnu": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jscpd-linux-arm64-gnu/-/jscpd-linux-arm64-gnu-5.4.0.tgz", + "integrity": "sha512-c4hX5QjuRyzLFJ+A+jNGHRpGlDvxcx+zzs4RJmZHpl23istuTMIkelwTOWliF4gXOm0RWXDlX/ywWKr9+Lre7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/jscpd-linux-arm64-musl": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jscpd-linux-arm64-musl/-/jscpd-linux-arm64-musl-5.4.0.tgz", + "integrity": "sha512-p4FUCNyL8WPTlizdZqfjontFwJ/+jnERYM2+YX2miYvm0NxzsIi23MhfzPl/eUHxkiT5wethV0NRYEAkJr999Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/jscpd-linux-x64-gnu": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jscpd-linux-x64-gnu/-/jscpd-linux-x64-gnu-5.4.0.tgz", + "integrity": "sha512-44NXS0gtYbjBCc53Gwilgj7PPwnWv9YTF/rGc0ZfnFcpzSoOKKU11yH+qHfCVie1hHte1wHyZ1f9Kjc8nGe2/g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/jscpd-linux-x64-musl": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jscpd-linux-x64-musl/-/jscpd-linux-x64-musl-5.4.0.tgz", + "integrity": "sha512-m9XHj14Zwia9RjWDkHDrl6VJ3W/b7m8qjFqjhcJ668Vkxoj4Ri/qeo0bo6is+fSw+bYxYexV6xxqIKRDolc8Lg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/jscpd-windows-arm64-msvc": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jscpd-windows-arm64-msvc/-/jscpd-windows-arm64-msvc-5.4.0.tgz", + "integrity": "sha512-AoP5RXyb9gIf8xmFwAc+Rgh4XJNyX1upcD+R1A6u4XjsDo2/oF+KKB4dtKnG2DbFQ+0fyFg/ClOcw+asQZJfPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/jscpd-windows-x64-msvc": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jscpd-windows-x64-msvc/-/jscpd-windows-x64-msvc-5.4.0.tgz", + "integrity": "sha512-dYkb5mAo5i7fjrtf2Ft6z9a/I38RJMd4JirEgJiW2BIv5pAGmfMVPDy1xEXL3hJqraXzeMIfnzqJFanefCyC6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/jsdom": { "version": "24.1.3", @@ -4677,30 +4353,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jstransformer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", - "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-promise": "^2.0.0", - "promise": "^7.0.1" - } - }, "node_modules/kebab-case": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.2.tgz", @@ -4739,25 +4391,278 @@ "node": ">= 0.8.0" } }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, "engines": { - "node": ">=14" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/antonk52" + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, "node_modules/locate-path": { "version": "6.0.0", @@ -4846,20 +4751,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/markdown-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "repeat-string": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/marked": { "version": "17.0.6", "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.6.tgz", @@ -4898,37 +4789,6 @@ "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -4952,16 +4812,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -4995,18 +4845,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "node_modules/nanoid": { "version": "3.3.18", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", @@ -5043,43 +4881,6 @@ "node": ">=18" } }, - "node_modules/node-sarif-builder": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-4.1.0.tgz", - "integrity": "sha512-IWqZF6u0EI/07HTBm+zZ+MgXgWl09dnSJRGaDCPBSlOqilDcx6pj3Mpb3HvPN8V2Gr+ISw7ZrMsL7STWs1F++w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/sarif": "^2.1.7", - "fs-extra": "^11.1.1" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/nwsapi": { "version": "2.2.24", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", @@ -5087,26 +4888,6 @@ "dev": true, "license": "MIT" }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/obug": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", @@ -5121,32 +4902,6 @@ "node": ">=12.20.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5251,13 +5006,6 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -5272,39 +5020,6 @@ "dev": true, "license": "ISC" }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/pngjs": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", @@ -5343,133 +5058,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", - "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.1.1" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-nested/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-selector-parser": { "version": "6.0.10", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", @@ -5484,13 +5072,6 @@ "node": ">=4" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5545,16 +5126,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "asap": "~2.0.3" - } - }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -5568,153 +5139,6 @@ "url": "https://github.com/sponsors/lupomontero" } }, - "node_modules/pug": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.4.tgz", - "integrity": "sha512-kFfq5mMzrS7+wrl5pLJzZEzemx34OQ0w4SARfhy/3yxTlhbstsudDwJzhf1hP02yHzbjoVMSXUj/Sz6RNfMyXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "pug-code-gen": "^3.0.4", - "pug-filters": "^4.0.0", - "pug-lexer": "^5.0.1", - "pug-linker": "^4.0.0", - "pug-load": "^3.0.0", - "pug-parser": "^6.0.0", - "pug-runtime": "^3.0.1", - "pug-strip-comments": "^2.0.0" - } - }, - "node_modules/pug-attrs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", - "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", - "dev": true, - "license": "MIT", - "dependencies": { - "constantinople": "^4.0.1", - "js-stringify": "^1.0.2", - "pug-runtime": "^3.0.0" - } - }, - "node_modules/pug-code-gen": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.4.tgz", - "integrity": "sha512-6okWYIKdasTyXICyEtvobmTZAVX57JkzgzIi4iRJlin8kmhG+Xry2dsus+Mun/nGCn6F2U49haHI5mkELXB14g==", - "dev": true, - "license": "MIT", - "dependencies": { - "constantinople": "^4.0.1", - "doctypes": "^1.1.0", - "js-stringify": "^1.0.2", - "pug-attrs": "^3.0.0", - "pug-error": "^2.1.0", - "pug-runtime": "^3.0.1", - "void-elements": "^3.1.0", - "with": "^7.0.0" - } - }, - "node_modules/pug-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", - "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", - "dev": true, - "license": "MIT" - }, - "node_modules/pug-filters": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", - "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "constantinople": "^4.0.1", - "jstransformer": "1.0.0", - "pug-error": "^2.0.0", - "pug-walk": "^2.0.0", - "resolve": "^1.15.1" - } - }, - "node_modules/pug-lexer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", - "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", - "dev": true, - "license": "MIT", - "dependencies": { - "character-parser": "^2.2.0", - "is-expression": "^4.0.0", - "pug-error": "^2.0.0" - } - }, - "node_modules/pug-linker": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", - "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pug-error": "^2.0.0", - "pug-walk": "^2.0.0" - } - }, - "node_modules/pug-load": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", - "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-assign": "^4.1.1", - "pug-walk": "^2.0.0" - } - }, - "node_modules/pug-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", - "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pug-error": "^2.0.0", - "token-stream": "1.0.0" - } - }, - "node_modules/pug-runtime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", - "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", - "dev": true, - "license": "MIT" - }, - "node_modules/pug-strip-comments": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", - "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "pug-error": "^2.0.0" - } - }, - "node_modules/pug-walk": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", - "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5749,27 +5173,6 @@ "dev": true, "license": "MIT" }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -5777,29 +5180,6 @@ "dev": true, "license": "MIT" }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -5814,16 +5194,6 @@ "node": ">=8" } }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5846,28 +5216,6 @@ "dev": true, "license": "MIT" }, - "node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -5878,17 +5226,6 @@ "node": ">=4" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rollup": { "version": "4.62.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", @@ -5941,30 +5278,6 @@ "dev": true, "license": "MIT" }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -6055,13 +5368,6 @@ "dev": true, "license": "ISC" }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/solid-js": { "version": "1.9.15", "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.15.tgz", @@ -6098,13 +5404,6 @@ "node": ">=0.10.0" } }, - "node_modules/spark-md5": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", - "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==", - "dev": true, - "license": "(WTFPL OR MIT)" - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -6145,16 +5444,6 @@ "node": ">=8" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -6191,39 +5480,6 @@ "inline-style-parser": "0.2.7" } }, - "node_modules/sucrase": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6237,19 +5493,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -6258,78 +5501,24 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", - "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.7", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } + "license": "MIT" }, - "node_modules/tailwindcss/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "dev": true, "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, "engines": { - "node": ">=4" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" + "node": ">=6" }, - "engines": { - "node": ">=0.8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/tinybench": { @@ -6407,26 +5596,6 @@ "node": ">=14.0.0" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/token-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", - "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", - "dev": true, - "license": "MIT" - }, "node_modules/tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -6479,13 +5648,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6544,16 +5706,6 @@ "dev": true, "license": "MIT" }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/update-browserslist-db": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", @@ -6909,16 +6061,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/void-elements": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", - "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -7019,22 +6161,6 @@ "node": ">=8" } }, - "node_modules/with": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", - "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.9.6", - "@babel/types": "^7.9.6", - "assert-never": "^1.2.1", - "babel-walk": "3.0.0-canary-5" - }, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -7045,13 +6171,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, "node_modules/ws": { "version": "8.21.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", diff --git a/frontend-modern/package.json b/frontend-modern/package.json index 2c1df0c446..f88f48c0d9 100644 --- a/frontend-modern/package.json +++ b/frontend-modern/package.json @@ -72,22 +72,21 @@ "@eslint/js": "^9.39.2", "@solidjs/testing-library": "^0.8.5", "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.3.3", "@testing-library/jest-dom": "^6.5.0", "@types/node": "^26.6.2", "@types/qrcode": "^1.5.6", "@typescript-eslint/eslint-plugin": "^8.70.0", "@typescript-eslint/parser": "^8.70.0", "@vitest/coverage-v8": "^4.1.11", - "autoprefixer": "^10.6.1", "eslint": "^9.20.0", "eslint-config-prettier": "^10.0.0", "eslint-plugin-solid": "^0.18.0", "globals": "^17.12.0", - "jscpd": "^4.0.8", + "jscpd": "^5.4.0", "jsdom": "^24.1.0", - "postcss": "^8.5.28", "prettier": "3.9.8", - "tailwindcss": "^3.4.18", + "tailwindcss": "^4.3.3", "typescript": "^5.3.0", "typescript-eslint": "^8.70.0", "vite": "^6.4.3", diff --git a/frontend-modern/postcss.config.js b/frontend-modern/postcss.config.js deleted file mode 100644 index e99ebc2c0e..0000000000 --- a/frontend-modern/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} \ No newline at end of file diff --git a/frontend-modern/scripts/shared-template-registry.json b/frontend-modern/scripts/shared-template-registry.json index 13a0f34266..75e83008da 100644 --- a/frontend-modern/scripts/shared-template-registry.json +++ b/frontend-modern/scripts/shared-template-registry.json @@ -265,7 +265,7 @@ "patterns": [ "}" ], "legacyReason": "Retired migration debt. Neutral copy-value icon buttons belong to CopyValueButton.", @@ -5686,7 +5686,7 @@ "scopes": ["src/components", "src/features", "src/pages"], "extensions": [".tsx"], "allPatterns": [ - "inline-flex min-h-7 min-w-7 shrink-0 items-center justify-center rounded text-blue-700 transition-colors hover:bg-blue-100 dark:text-blue-200 dark:hover:bg-blue-950", + "inline-flex min-h-7 min-w-7 shrink-0 items-center justify-center rounded-sm text-blue-700 transition-colors hover:bg-blue-100 dark:text-blue-200 dark:hover:bg-blue-950/25", "fallback={}" ], "legacyReason": "Retired migration debt. Accent copy-value icon buttons belong to CopyValueButton.", @@ -5708,7 +5708,7 @@ "scopes": ["src/components", "src/features", "src/pages"], "extensions": [".tsx"], "allPatterns": [ - "flex items-start gap-2 rounded bg-surface-alt px-2 py-1.5", + "flex items-start gap-2 rounded-sm bg-surface-alt px-2 py-1.5", "break-all font-mono text-xs text-base-content" ], "legacyReason": "Retired migration debt. Copyable code rows belong to CopyableCodeRow so code styling and copy behavior stay canonical.", @@ -6012,7 +6012,7 @@ "extensions": [".tsx"], "allPatterns": [ "px-3 py-1.5 bg-green-600 hover:bg-green-700", - "text-white text-xs font-medium rounded" + "text-white text-xs font-medium rounded-sm" ], "legacyReason": "Retired migration debt. Patrol approval execute actions belong to the shared Button primitive family.", "allowedPaths": [], @@ -6035,7 +6035,7 @@ "extensions": [".tsx"], "allPatterns": [ "px-3 py-1.5 bg-amber-600 hover:bg-amber-700", - "text-white text-xs font-medium rounded" + "text-white text-xs font-medium rounded-sm" ], "legacyReason": "Retired migration debt. Patrol approval warning actions belong to the shared Button primitive family.", "allowedPaths": [], @@ -6058,7 +6058,7 @@ "extensions": [".tsx"], "allPatterns": [ "px-3 py-1.5 bg-blue-600 hover:bg-blue-700", - "text-white text-xs font-medium rounded" + "text-white text-xs font-medium rounded-sm" ], "legacyReason": "Retired migration debt. Patrol Assistant handoff actions belong to the shared Button primitive family.", "allowedPaths": [], @@ -6103,7 +6103,7 @@ "scopes": ["src/components/Settings", "src/features", "src/pages"], "extensions": [".tsx"], "allPatterns": [ - "rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900" + "rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900/25" ], "legacyReason": "Retired migration debt. Positive outlined settings actions belong to the shared Button primitive family.", "allowedPaths": [], @@ -6124,7 +6124,7 @@ }, "scopes": ["src/components/Settings", "src/features", "src/pages"], "extensions": [".tsx"], - "allPatterns": ["rounded-md px-3 py-2 text-sm font-medium text-emerald-900"], + "allPatterns": ["rounded-md px-3 py-2 text-sm font-medium text-emerald-900/25"], "legacyReason": "Retired migration debt. Positive ghost settings actions belong to the shared Button primitive family.", "allowedPaths": [], "ignoredPaths": ["src/components/shared/Button.test.tsx"], @@ -6146,7 +6146,7 @@ "extensions": [".tsx"], "allPatterns": [ "rounded-md px-2 py-1 text-xs font-medium text-emerald-700", - "hover:bg-emerald-50 dark:text-emerald-300 dark:hover:bg-emerald-900" + "hover:bg-emerald-50 dark:text-emerald-300 dark:hover:bg-emerald-900/25" ], "legacyReason": "Retired migration debt. Positive row actions belong to the shared Button primitive family.", "allowedPaths": [], @@ -6255,7 +6255,7 @@ "extensions": [".tsx"], "allPatterns": [ "rounded-md px-2 py-1 text-xs font-medium text-red-600", - "hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900" + "hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900/25" ], "legacyReason": "Retired migration debt. Destructive row actions belong to the shared Button primitive family.", "allowedPaths": [], diff --git a/frontend-modern/src/AppLayout.tsx b/frontend-modern/src/AppLayout.tsx index 19d5c9a972..330044bf00 100644 --- a/frontend-modern/src/AppLayout.tsx +++ b/frontend-modern/src/AppLayout.tsx @@ -136,9 +136,9 @@ export function sessionHasSettingsAccess(scopes: string[] | undefined): boolean } const NAV_TAB_ICON_CLASS = 'w-4 h-4 shrink-0'; const AI_CHAT_MOBILE_LAUNCHER_BUTTON_CLASS = - 'group relative flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-full bg-surface-hover text-blue-600 transition-colors hover:bg-border hover:text-blue-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:text-blue-400 dark:hover:text-blue-300'; + 'group relative flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-surface-hover text-blue-600 transition-colors hover:bg-border hover:text-blue-700 focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:text-blue-400 dark:hover:text-blue-300'; const AI_CHAT_DESKTOP_LAUNCHER_BUTTON_CLASS = - 'fixed right-0 top-1/2 z-40 flex min-h-9 min-w-10 -translate-y-1/2 items-center justify-center rounded-l-lg border border-r-0 border-border bg-surface px-2.5 py-2.5 text-blue-600 transition-colors duration-200 hover:bg-surface-hover hover:text-blue-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:text-blue-400 dark:hover:text-blue-300'; + 'fixed right-0 top-1/2 z-40 flex min-h-9 min-w-10 -translate-y-1/2 items-center justify-center rounded-l-lg border border-r-0 border-border bg-surface px-2.5 py-2.5 text-blue-600 transition-colors duration-200 hover:bg-surface-hover hover:text-blue-700 focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:text-blue-400 dark:hover:text-blue-300'; function getDesktopUtilityTabAriaLabel(tab: UtilityTab): string { const count = tab.count ?? 0; @@ -208,7 +208,7 @@ export function ConnectionStatusBadge(props: { aria-label={status().detail} > - + - + - + ); createEffect(() => { @@ -691,7 +691,7 @@ export function AppLayout(props: AppLayoutProps) { const className = () => { if (isActive()) { - return `${baseClasses} bg-surface text-blue-600 dark:text-blue-400 border-border border-b border-b-surface shadow-sm font-semibold`; + return `${baseClasses} bg-surface text-blue-600 dark:text-blue-400 border-border border-b border-b-surface shadow-xs font-semibold`; } if (needsSetup()) { return `${baseClasses} text-muted opacity-70 bg-base hover:bg-surface-hover`; @@ -718,7 +718,7 @@ export function AppLayout(props: AppLayoutProps) { - + Pro @@ -976,7 +976,7 @@ export function AppLayout(props: AppLayoutProps) {
Loading view...
}> @@ -1004,7 +1004,7 @@ export function AppLayout(props: AppLayoutProps) { href="https://github.com/rcourtman/Pulse/releases" target="_blank" rel="noopener noreferrer" - class="inline-flex min-h-8 items-center break-all rounded px-1 py-1 text-blue-600 hover:underline dark:text-blue-400 sm:min-h-9" + class="inline-flex min-h-8 items-center break-all rounded-sm px-1 py-1 text-blue-600 hover:underline dark:text-blue-400 sm:min-h-9" > {props.versionInfo()?.version || 'loading...'} @@ -1023,7 +1023,7 @@ export function AppLayout(props: AppLayoutProps) { Get Support diff --git a/frontend-modern/src/__tests__/App.architecture.test.ts b/frontend-modern/src/__tests__/App.architecture.test.ts index dca733a7ee..e26e2ddef5 100644 --- a/frontend-modern/src/__tests__/App.architecture.test.ts +++ b/frontend-modern/src/__tests__/App.architecture.test.ts @@ -639,7 +639,7 @@ describe('App architecture', () => { expect(appLayoutSource).toContain('animate-pulse-brand'); expect(appLayoutSource).toContain('pulse-brand-wordmark'); expect(appLayoutSource).toContain("'pb-safe-or-14 xl:pb-0'"); - expect(appStylesSource).toContain('.pb-safe-or-14'); + expect(appStylesSource).toContain('@utility pb-safe-or-14'); expect(appStylesSource).toContain('.pulse-shell--full-width'); expect(appStylesSource).toContain('.pulse-wide-data-surface.space-y-3'); expect(appStylesSource).toContain('.filter-bar > div > div:first-child button'); diff --git a/frontend-modern/src/api/patrol.ts b/frontend-modern/src/api/patrol.ts index 5354edf4d0..1703d8eab5 100644 --- a/frontend-modern/src/api/patrol.ts +++ b/frontend-modern/src/api/patrol.ts @@ -721,26 +721,26 @@ export const investigationOutcomeLabels: Record = */ export const investigationOutcomeColors: Record = { resolved: - 'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900 dark:text-green-300', + 'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900/25 dark:text-green-300', fix_queued: - 'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-900 dark:text-blue-300', + 'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-900/25 dark:text-blue-300', fix_executed: - 'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900 dark:text-green-300', + 'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900/25 dark:text-green-300', fix_failed: - 'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-900 dark:text-red-300', + 'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-900/25 dark:text-red-300', fix_rejected: - 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-300', + 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-300', needs_attention: - 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-300', + 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-300', cannot_fix: 'border-slate-200 bg-slate-50 text-slate-600', timed_out: - 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-300', + 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-300', fix_verified: - 'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900 dark:text-green-300', + 'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900/25 dark:text-green-300', fix_verification_failed: - 'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-900 dark:text-red-300', + 'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-900/25 dark:text-red-300', fix_verification_unknown: - 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-300', + 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-300', }; // ============================================================================= diff --git a/frontend-modern/src/components/AI/AICostDashboard.tsx b/frontend-modern/src/components/AI/AICostDashboard.tsx index 6a5ac8c1d4..27babcf133 100644 --- a/frontend-modern/src/components/AI/AICostDashboard.tsx +++ b/frontend-modern/src/components/AI/AICostDashboard.tsx @@ -287,10 +287,10 @@ export const AICostDashboard: Component = () => { return ( -
+
-
+
{ -
+
Showing the last {summary()?.effective_days} days due to a {summary()?.retention_days} -day retention window.
-
+
Estimated spend ({formatUSD(estimatedTotalUSD() ?? 0)}) is above your budget ( {formatUSD(budgetForRange() ?? 0)}).
@@ -357,14 +357,14 @@ export const AICostDashboard: Component = () => {