From f2c98056d1a0c257f7f50993eed27d5f498deaa9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 00:17:24 +0000 Subject: [PATCH] chore: publish from main --- .codespellrc | 2 +- .github/plugin/marketplace.json | 6 + .../.github/plugin/plugin.json | 20 + .../pr-artifact-explorer/assets/preview.png | Bin 0 -> 83436 bytes .../extensions/pr-artifact-explorer/README.md | 66 + .../pr-artifact-explorer/accounts.mjs | 329 + .../pr-artifact-explorer/assets/app.css | 3814 ++++++++++++ .../pr-artifact-explorer/assets/app.js | 5362 +++++++++++++++++ .../assets/asciinema-player-worker.min.js | 1 + .../assets/asciinema-player.css | 762 +++ .../assets/asciinema-player.min.js | 3 + .../assets/octicons/archive.svg | 1 + .../assets/octicons/browser.svg | 1 + .../assets/octicons/check-circle.svg | 1 + .../assets/octicons/check.svg | 1 + .../assets/octicons/chevron-down.svg | 1 + .../assets/octicons/chevron-left.svg | 1 + .../assets/octicons/chevron-right.svg | 1 + .../assets/octicons/clock.svg | 1 + .../assets/octicons/code.svg | 1 + .../assets/octicons/comment.svg | 1 + .../assets/octicons/download.svg | 1 + .../assets/octicons/eye.svg | 1 + .../assets/octicons/file-binary.svg | 1 + .../assets/octicons/file-code.svg | 1 + .../assets/octicons/file-directory.svg | 1 + .../assets/octicons/file-media.svg | 1 + .../assets/octicons/file-zip.svg | 1 + .../assets/octicons/file.svg | 1 + .../assets/octicons/filter.svg | 1 + .../assets/octicons/gear.svg | 1 + .../assets/octicons/git-pull-request.svg | 1 + .../assets/octicons/kebab-horizontal.svg | 1 + .../assets/octicons/link-external.svg | 1 + .../assets/octicons/mark-github.svg | 1 + .../assets/octicons/markdown.svg | 1 + .../assets/octicons/package.svg | 1 + .../assets/octicons/person.svg | 1 + .../assets/octicons/pin-slash.svg | 1 + .../assets/octicons/pin.svg | 1 + .../assets/octicons/play.svg | 1 + .../assets/octicons/repo.svg | 1 + .../assets/octicons/search.svg | 1 + .../assets/octicons/sidebar-collapse.svg | 1 + .../assets/octicons/sidebar-expand.svg | 1 + .../assets/octicons/star-fill.svg | 1 + .../assets/octicons/star.svg | 1 + .../assets/octicons/sync.svg | 1 + .../assets/octicons/terminal.svg | 1 + .../assets/octicons/trash.svg | 1 + .../assets/octicons/workflow.svg | 1 + .../assets/octicons/x-circle.svg | 1 + .../assets/octicons/x.svg | 1 + .../assets/preview-dark.png | Bin 0 -> 83024 bytes .../pr-artifact-explorer/assets/preview.png | Bin 0 -> 83436 bytes .../assets/primer-color-modes.css | 2 + .../assets/primer-core.css | 7 + .../assets/primer-product.css | 7 + .../assets/trx-preview.js | 768 +++ .../cache-coordinator.mjs | 63 + .../extensions/pr-artifact-explorer/cache.mjs | 531 ++ .../pr-artifact-explorer/constants.mjs | 24 + .../copilot-extension.json | 4 + .../pr-artifact-explorer/detector.mjs | 218 + .../pr-artifact-explorer/extension.mjs | 215 + .../pr-artifact-explorer/github.mjs | 712 +++ .../pr-artifact-explorer/memory-cache.mjs | 130 + .../pr-artifact-explorer/preview.mjs | 298 + .../pr-artifact-explorer/render.mjs | 99 + .../review-fixes.test.mjs | 566 ++ .../pr-artifact-explorer/security.mjs | 38 + .../pr-artifact-explorer/server.mjs | 962 +++ .../extensions/pr-artifact-explorer/state.mjs | 290 + .../third-party-licenses/asciinema-player.txt | 201 + .../third-party-licenses/octicons.txt | 21 + .../third-party-licenses/primer-css.txt | 21 + .../extensions/pr-artifact-explorer/zip.mjs | 401 ++ 77 files changed, 15984 insertions(+), 1 deletion(-) create mode 100644 extensions/pr-artifact-explorer/.github/plugin/plugin.json create mode 100644 extensions/pr-artifact-explorer/assets/preview.png create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/README.md create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/accounts.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/app.css create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/app.js create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/asciinema-player-worker.min.js create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/asciinema-player.css create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/asciinema-player.min.js create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/archive.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/browser.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/check-circle.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/check.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/chevron-down.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/chevron-left.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/chevron-right.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/clock.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/code.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/comment.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/download.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/eye.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/file-binary.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/file-code.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/file-directory.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/file-media.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/file-zip.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/file.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/filter.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/gear.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/git-pull-request.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/kebab-horizontal.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/link-external.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/mark-github.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/markdown.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/package.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/person.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/pin-slash.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/pin.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/play.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/repo.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/search.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/sidebar-collapse.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/sidebar-expand.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/star-fill.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/star.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/sync.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/terminal.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/trash.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/workflow.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/x-circle.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/octicons/x.svg create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/preview-dark.png create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/preview.png create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/primer-color-modes.css create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/primer-core.css create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/primer-product.css create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/trx-preview.js create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/cache-coordinator.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/cache.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/constants.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/copilot-extension.json create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/detector.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/extension.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/github.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/memory-cache.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/preview.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/render.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/review-fixes.test.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/security.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/server.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/state.mjs create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/third-party-licenses/asciinema-player.txt create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/third-party-licenses/octicons.txt create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/third-party-licenses/primer-css.txt create mode 100644 extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/zip.mjs diff --git a/.codespellrc b/.codespellrc index d38ea35d..91f8be36 100644 --- a/.codespellrc +++ b/.codespellrc @@ -67,4 +67,4 @@ ignore-words-list = numer,wit,aks,edn,ser,ois,gir,rouge,categor,aline,ative,afte # Skip certain files and directories # *.tm7 - MTM DataContract exports; embedded base64 icon blobs trigger false positives -skip = .git,node_modules,package-lock.json,*.lock,website/build,website/.docusaurus,.all-contributorrc,./skills/geofeed-tuner/assets/*.json,./skills/geofeed-tuner/references/*.txt,./plugins/fastah-ip-geo-tools/skills/geofeed-tuner/assets/*.json,./plugins/fastah-ip-geo-tools/skills/geofeed-tuner/references/*.txt,./extensions/arcade-canvas/game/phaser.min.js,*.tm7 +skip = .git,node_modules,package-lock.json,*.lock,website/build,website/.docusaurus,.all-contributorrc,./skills/geofeed-tuner/assets/*.json,./skills/geofeed-tuner/references/*.txt,./plugins/fastah-ip-geo-tools/skills/geofeed-tuner/assets/*.json,./plugins/fastah-ip-geo-tools/skills/geofeed-tuner/references/*.txt,./extensions/arcade-canvas/game/phaser.min.js,./extensions/pr-artifact-explorer/assets/asciinema-player.min.js,./extensions/pr-artifact-explorer/assets/asciinema-player-worker.min.js,./extensions/pr-artifact-explorer/assets/primer-*.css,*.tm7 diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index 536f0f85..5f2d71d4 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -1031,6 +1031,12 @@ "description": "Complete toolkit for developing Power Platform custom connectors with Model Context Protocol integration for Microsoft Copilot Studio", "version": "1.0.0" }, + { + "name": "pr-artifact-explorer", + "source": "extensions/pr-artifact-explorer", + "description": "Navigate pull requests and securely explore GitHub Actions artifacts, including test results, static sites, terminal recordings, and source files.", + "version": "1.0.0" + }, { "name": "project-documenter", "source": "plugins/project-documenter", diff --git a/extensions/pr-artifact-explorer/.github/plugin/plugin.json b/extensions/pr-artifact-explorer/.github/plugin/plugin.json new file mode 100644 index 00000000..971b8706 --- /dev/null +++ b/extensions/pr-artifact-explorer/.github/plugin/plugin.json @@ -0,0 +1,20 @@ +{ + "name": "pr-artifact-explorer", + "description": "Navigate pull requests and securely explore GitHub Actions artifacts, including test results, static sites, terminal recordings, and source files.", + "version": "1.0.0", + "author": { + "name": "David Pine", + "url": "https://github.com/IEvangelist" + }, + "keywords": [ + "actions-artifacts", + "artifact-browser", + "canvas", + "copilot-extension", + "github-actions", + "pull-requests", + "test-results" + ], + "logo": "assets/preview.png", + "extensions": "extensions" +} diff --git a/extensions/pr-artifact-explorer/assets/preview.png b/extensions/pr-artifact-explorer/assets/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..fb201000342dada9e88b1b344bb4e4b837c0c659 GIT binary patch literal 83436 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 dpXs4wq+OD|IoLeDH^P$0K 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 **GitHub, the missing parts:** GitHub Actions makes artifacts easy to produce, but pull-request reviewers still have to download opaque ZIP files and inspect them out of context. Artifact Explorer turns those artifacts into a fast, GitHub-native, navigable experience inside Copilot. + +Artifact Explorer is a community-built Copilot canvas extension with the internal extension ID `pr-artifact-explorer`. It is not an official GitHub product or endorsed feature. + + + + + Artifact Explorer showing pull request workflow artifacts in a responsive GitHub-style interface + + +## What it does + +- Navigates repositories, pull requests, artifacts, and files with persistent breadcrumbs. +- Loads pull requests progressively, caches result pages, and supports query filters for fast review. +- Bounds artifact discovery to the newest 30 workflow runs per head commit and reports when older runs are omitted. +- Downloads an artifact once, indexes its ZIP, and streams entries directly from the archive without extracting them to disk. +- Previews root-level static sites in a sandboxed loopback frame when the artifact contains `index.html`. +- Plays asciinema terminal recordings and renders structured TRX test reports. +- Syntax-highlights JSON and XML, previews images and PDFs, renders text with safe escaping, and identifies binary content. +- Keeps large files available for direct download and falls back to the original artifact archive when a ZIP entry uses an unsupported encoding. +- Provides per-artifact cleanup, clear-all cleanup, and a cache usage view. +- Uses a responsive Primer interface that follows Copilot's light and dark themes. + +## Security and local data + +Artifact Explorer inherits eligible GitHub accounts from the Copilot app, GitHub CLI, or process environment. Tokens remain inside the extension process: the canvas receives only sanitized account metadata, while authenticated GitHub API requests are made by the local extension server. + +Artifact ZIPs and preferences are stored under: + +```text +$COPILOT_HOME/extensions/pr-artifact-explorer/artifacts/ +``` + +When `COPILOT_HOME` is unset, it defaults to `~/.copilot`. Cached archives remain local until removed through the per-artifact or clear-all controls. + +The canvas server validates its canonical loopback host and requires a per-instance capability token for API, artifact-content, and event requests. Static-site previews bind to a separate ephemeral `127.0.0.1` port, validate their loopback host, apply a restrictive content security policy, prevent path traversal, and stream files from the ZIP. A preview requires `index.html` at the artifact root. Static content is untrusted, remains sandboxed by the canvas, and has no access to GitHub tokens. + +## Install + +Ask Copilot to install the committed extension: + +```text +Install this extension: https://github.com/github/awesome-copilot/tree/main/extensions/pr-artifact-explorer +``` + +You can also copy the folder to one of the supported extension locations: + +- `~/.copilot/extensions/pr-artifact-explorer/` for user scope +- `.github/extensions/pr-artifact-explorer/` for project scope + +Reload extensions, then ask Copilot to open the `pr-artifact-explorer` canvas. You can optionally provide a repository (`owner/name`), pull request number, or GitHub pull request URL when opening it. + +## Agent actions + +- `open_pull_request { repository, pullNumber }` - navigate an open canvas to a pull request. +- `inspect_artifact { repository, artifactId }` - download, index, detect, and open an Actions artifact. +- `cache_status` - report local artifact cache usage. +- `clear_cache { artifactId? }` - remove one cached artifact or clear the entire cache. +- `accounts` - list sanitized GitHub account metadata available to the extension. +- `set_account { id }` - select the GitHub account used for repository and artifact requests. + +## Included third-party components + +The extension bundles Primer CSS, Octicons, and the asciinema player for offline rendering. Their notices are preserved in `third-party-licenses/`. diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/accounts.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/accounts.mjs new file mode 100644 index 00000000..8542f520 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/accounts.mjs @@ -0,0 +1,329 @@ +import { createHash } from "node:crypto"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { GITHUB_API, USER_AGENT } from "./constants.mjs"; + +const execFileAsync = promisify(execFile); +const CACHE_TTL_MS = 5 * 60 * 1000; +const RESOLUTION_CACHE_LIMIT = 20; +const SOURCE_PRIORITY = Object.freeze({ copilot: 30, gh: 20, env: 10 }); +const SOURCE_LABEL = Object.freeze({ + copilot: "Copilot app", + gh: "GitHub CLI", + env: "Environment", +}); + +const cachedResolutions = new Map(); + +function cleanGhEnvironment() { + const env = { ...process.env }; + delete env.GH_TOKEN; + delete env.GITHUB_TOKEN; + return env; +} + +function tokenHash(token) { + return createHash("sha256").update(token).digest("hex").slice(0, 12); +} + +function decodeCopilotLabel(variableName) { + let encoded = variableName.replace(/^COPILOT_GH_ACCOUNT_/, ""); + encoded = encoded.replace(/_([0-9A-Fa-f]{2})_/g, (_, hex) => + String.fromCharCode(Number.parseInt(hex, 16))); + return encoded.replace(/^github\.com_/, "") || null; +} + +async function listGhAccounts() { + let output = ""; + try { + const result = await execFileAsync("gh", ["auth", "status"], { + timeout: 8_000, + env: cleanGhEnvironment(), + windowsHide: true, + }); + output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`; + } catch (error) { + if (error?.code === "ENOENT") return []; + output = `${error?.stdout ?? ""}\n${error?.stderr ?? ""}`; + if (!output.trim()) return []; + } + + const accounts = []; + const seen = new Set(); + const pattern = /Logged in to (\S+) account (\S+)/g; + let match; + while ((match = pattern.exec(output)) !== null) { + const key = `${match[1].toLowerCase()}/${match[2].toLowerCase()}`; + if (!seen.has(key)) { + seen.add(key); + accounts.push({ host: match[1], login: match[2] }); + } + } + return accounts; +} + +async function readGhToken(host, login) { + try { + const { stdout } = await execFileAsync( + "gh", + ["auth", "token", "--hostname", host, "--user", login], + { + timeout: 8_000, + env: cleanGhEnvironment(), + windowsHide: true, + }, + ); + return stdout.trim() || null; + } catch (error) { + if (error?.code === "ENOENT") return null; + return null; + } +} + +async function detectCandidates() { + const candidates = []; + const seenTokens = new Set(); + const add = (source, token, hintedLogin) => { + if (!token) return; + const hash = tokenHash(token); + if (seenTokens.has(hash)) return; + seenTokens.add(hash); + candidates.push({ source, token, hash, hintedLogin: hintedLogin ?? null }); + }; + + for (const [name, value] of Object.entries(process.env)) { + if (name.startsWith("COPILOT_GH_ACCOUNT_") && value) { + add("copilot", value, decodeCopilotLabel(name)); + } + } + add("env", process.env.GH_TOKEN, null); + add("env", process.env.GITHUB_TOKEN, null); + + for (const account of await listGhAccounts()) { + if (account.host.toLowerCase() !== "github.com") continue; + add("gh", await readGhToken(account.host, account.login), account.login); + } + return candidates; +} + +async function apiProbe(token, path) { + const response = await fetch(`${GITHUB_API}${path}`, { + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${token}`, + "User-Agent": USER_AGENT, + "X-GitHub-Api-Version": "2022-11-28", + }, + signal: AbortSignal.timeout(12_000), + }); + const payload = await response.json().catch(() => null); + return { response, payload }; +} + +async function probeCandidate(candidate, repository) { + const safe = { + source: candidate.source, + hash: candidate.hash, + hintedLogin: candidate.hintedLogin, + }; + + try { + const viewer = await apiProbe(candidate.token, "/user"); + if (!viewer.response.ok || !viewer.payload?.login) { + return { + ...safe, + token: candidate.token, + login: candidate.hintedLogin ?? "unknown", + avatarUrl: null, + status: "failed", + repositoryAccess: false, + reason: viewer.payload?.message ?? `Authentication failed (${viewer.response.status}).`, + scopes: [], + }; + } + + let repositoryAccess = null; + let repositoryReason = null; + if (repository) { + const access = await apiProbe( + candidate.token, + `/repos/${repository.split("/").map(encodeURIComponent).join("/")}`, + ); + repositoryAccess = access.response.ok; + if (!repositoryAccess) { + repositoryReason = access.payload?.message ?? `Repository check failed (${access.response.status}).`; + } + } + + const scopesHeader = viewer.response.headers.get("x-oauth-scopes") ?? ""; + return { + ...safe, + token: candidate.token, + login: viewer.payload.login, + avatarUrl: viewer.payload.avatar_url ?? null, + name: viewer.payload.name ?? null, + profileUrl: viewer.payload.html_url ?? null, + type: viewer.payload.type ?? null, + company: viewer.payload.company ?? null, + status: repositoryAccess === false ? "limited" : "ok", + repositoryAccess, + reason: repositoryReason, + scopes: scopesHeader.split(",").map((scope) => scope.trim()).filter(Boolean), + }; + } catch (error) { + return { + ...safe, + token: candidate.token, + login: candidate.hintedLogin ?? "unknown", + avatarUrl: null, + name: null, + profileUrl: null, + type: null, + company: null, + status: "failed", + repositoryAccess: false, + reason: error instanceof Error ? error.message : String(error), + scopes: [], + }; + } +} + +function accountId(login) { + return `acct:${String(login).toLowerCase()}`; +} + +function candidateScore(candidate) { + let score = SOURCE_PRIORITY[candidate.source] ?? 0; + if (candidate.status === "ok") score += 1_000; + if (candidate.repositoryAccess === true) score += 10_000; + if (candidate.status === "failed") score -= 100_000; + return score; +} + +function publicAccount(account) { + const { token, ...safe } = account; + return safe; +} + +export function invalidateAccounts() { + cachedResolutions.clear(); +} + +async function resolveAccountsUncached(preferredId, repository) { + const probes = await Promise.all( + (await detectCandidates()).map((candidate) => probeCandidate(candidate, repository)), + ); + const groups = new Map(); + for (const probe of probes) { + const keyForLogin = String(probe.login).toLowerCase(); + if (!groups.has(keyForLogin)) groups.set(keyForLogin, []); + groups.get(keyForLogin).push(probe); + } + + const accounts = []; + for (const [loginKey, candidates] of groups) { + candidates.sort((left, right) => candidateScore(right) - candidateScore(left)); + const best = candidates[0]; + accounts.push({ + id: accountId(loginKey), + login: best.login, + avatarUrl: best.avatarUrl ?? candidates.find((item) => item.avatarUrl)?.avatarUrl ?? null, + name: best.name ?? candidates.find((item) => item.name)?.name ?? null, + profileUrl: + best.profileUrl ?? candidates.find((item) => item.profileUrl)?.profileUrl ?? null, + type: best.type ?? candidates.find((item) => item.type)?.type ?? null, + company: best.company ?? candidates.find((item) => item.company)?.company ?? null, + status: best.status, + repositoryAccess: best.repositoryAccess, + reason: best.reason ?? null, + scopes: best.scopes, + sourceKinds: [...new Set(candidates.map((item) => item.source))], + sources: candidates.map((item, index) => ({ + label: SOURCE_LABEL[item.source] ?? item.source, + source: item.source, + hash: item.hash, + status: item.status, + repositoryAccess: item.repositoryAccess, + reason: item.reason ?? null, + scopes: item.scopes, + chosen: index === 0, + })), + token: best.token, + }); + } + accounts.sort((left, right) => { + const leftAccess = left.repositoryAccess === true ? 1 : 0; + const rightAccess = right.repositoryAccess === true ? 1 : 0; + if (leftAccess !== rightAccess) return rightAccess - leftAccess; + if (left.status !== right.status) return left.status === "ok" ? -1 : 1; + return left.login.localeCompare(right.login); + }); + + let active = preferredId + ? accounts.find((account) => account.id === preferredId && account.status !== "failed") + : null; + active ??= accounts.find((account) => account.repositoryAccess === true); + active ??= accounts.find((account) => account.status === "ok"); + active ??= null; + + const value = { + active: active ? publicAccount(active) : null, + activeToken: active?.token ?? null, + accounts: accounts.map(publicAccount), + }; + return value; +} + +export async function resolveAccounts({ + preferredId = null, + repository = null, + force = false, +} = {}) { + const key = `${preferredId ?? ""}|${repository ?? ""}`; + let entry = cachedResolutions.get(key); + if (!force && entry?.value && Date.now() - entry.at < CACHE_TTL_MS) { + cachedResolutions.delete(key); + cachedResolutions.set(key, entry); + return entry.value; + } + if (entry?.promise && (!force || entry.forceRefresh)) return entry.promise; + + entry ??= { + at: 0, + forceRefresh: false, + promise: null, + value: null, + }; + const promise = resolveAccountsUncached(preferredId, repository).then( + (value) => { + if (cachedResolutions.get(key) === entry && entry.promise === promise) { + entry.at = Date.now(); + entry.forceRefresh = false; + entry.promise = null; + entry.value = value; + cachedResolutions.delete(key); + cachedResolutions.set(key, entry); + } + return value; + }, + (error) => { + if (cachedResolutions.get(key) === entry && entry.promise === promise) { + entry.forceRefresh = false; + entry.promise = null; + if (!entry.value || Date.now() - entry.at >= CACHE_TTL_MS) { + cachedResolutions.delete(key); + } + } + throw error; + }, + ); + entry.forceRefresh = force; + entry.promise = promise; + cachedResolutions.set(key, entry); + while (cachedResolutions.size > RESOLUTION_CACHE_LIMIT) { + const candidate = [...cachedResolutions].find(([, value]) => !value.promise); + if (!candidate) break; + cachedResolutions.delete(candidate[0]); + } + return promise; +} diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/app.css b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/app.css new file mode 100644 index 00000000..d25e82b4 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/app.css @@ -0,0 +1,3814 @@ +:root { + --app-bg: var(--bgColor-default, var(--background-color-default, #ffffff)); + --app-bg-muted: var(--bgColor-muted, var(--background-color-muted, #f6f8fa)); + --app-bg-inset: var(--bgColor-inset, #f6f8fa); + --app-fg: var(--fgColor-default, var(--text-color-default, #1f2328)); + --app-fg-muted: var(--fgColor-muted, var(--text-color-muted, #59636e)); + --app-border: var(--borderColor-default, var(--border-color-default, #d1d9e0)); + --app-border-muted: var(--borderColor-muted, #d8dee4); + --app-accent: var(--fgColor-accent, var(--true-color-blue, #0969da)); + --app-success: var(--fgColor-success, var(--true-color-green, #1a7f37)); + --app-danger: var(--fgColor-danger, var(--true-color-red, #cf222e)); + --app-attention: var(--fgColor-attention, var(--true-color-yellow, #9a6700)); + --app-done: var(--fgColor-done, #8250df); + --app-header-bg: rgb(from var(--header-bgColor, #25292e) r g b / 1); + --app-radius: 6px; + --app-font: var( + --fontStack-system, + var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif) + ); + --app-mono: var( + --fontStack-monospace, + var(--font-mono, "SFMono-Regular", Consolas, "Liberation Mono", monospace) + ); +} + +[data-color-mode="dark"] { + --app-header-bg: rgb(21, 27, 35); +} + +@media (prefers-color-scheme: dark) { + [data-color-mode="auto"] { + --app-header-bg: rgb(21, 27, 35); + } +} + +* { + box-sizing: border-box; +} + +html, +body { + min-width: 320px; + min-height: 100%; +} + +body { + margin: 0; + color: var(--app-fg); + background: var(--app-bg); + font-family: var(--app-font); +} + +button, +input, +select { + font-family: inherit; +} + +.app-button { + display: inline-flex; + height: 32px; + padding: 0 var(--base-size-12, 12px); + color: var(--button-default-fgColor-rest, var(--app-fg)); + font-size: var(--text-body-size-small, 12px); + font-weight: var(--base-text-weight-semibold, 600); + line-height: 30px; + text-decoration: none; + white-space: nowrap; + background: var(--button-default-bgColor-rest, var(--app-bg-muted)); + border: 1px solid var(--button-default-borderColor-rest, var(--app-border)); + border-radius: var(--app-radius); + box-shadow: var(--button-default-shadow-resting, 0 1px 0 rgb(31 35 40 / 4%)); + align-items: center; + justify-content: center; + gap: var(--base-size-6, 6px); + cursor: pointer; +} + +.app-button:hover:not(:disabled) { + color: var(--button-default-fgColor-rest, var(--app-fg)); + text-decoration: none; + background: var(--button-default-bgColor-hover, var(--app-bg-inset)); + border-color: var(--button-default-borderColor-hover, var(--app-border)); +} + +.app-button:active:not(:disabled) { + background: var(--button-default-bgColor-active, var(--app-bg-inset)); + border-color: var(--button-default-borderColor-active, var(--app-border)); +} + +.app-button:focus-visible { + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: 2px; +} + +.app-button:disabled, +.app-button[aria-disabled="true"] { + color: var(--button-default-fgColor-disabled, var(--app-fg-muted)); + background: var(--button-default-bgColor-disabled, var(--app-bg-muted)); + border-color: var(--button-default-borderColor-disabled, var(--app-border-muted)); + box-shadow: none; + cursor: not-allowed; + opacity: 0.65; +} + +.app-button-primary { + color: var(--button-primary-fgColor-rest, #ffffff); + background: var(--button-primary-bgColor-rest, #1f883d); + border-color: var(--button-primary-borderColor-rest, rgb(31 35 40 / 15%)); +} + +.app-button-primary:hover:not(:disabled) { + color: var(--button-primary-fgColor-rest, #ffffff); + background: var(--button-primary-bgColor-hover, #1a7f37); + border-color: var(--button-primary-borderColor-hover, rgb(31 35 40 / 15%)); +} + +.app-button-danger { + color: var(--button-danger-fgColor-rest, var(--app-danger)); + background: var(--button-danger-bgColor-rest, var(--button-default-bgColor-rest, var(--app-bg-muted))); + border-color: var(--button-danger-borderColor-rest, var(--app-border)); +} + +.app-button-danger:hover:not(:disabled) { + color: var(--button-danger-fgColor-hover, #ffffff); + background: var(--button-danger-bgColor-hover, var(--app-danger)); + border-color: var(--button-danger-borderColor-hover, var(--app-danger)); +} + +.app-button-invisible { + color: var(--button-invisible-fgColor-rest, var(--app-fg)); + background: var(--button-invisible-bgColor-rest, transparent); + border-color: var(--button-invisible-borderColor-rest, transparent); + box-shadow: none; +} + +.app-button-invisible:hover:not(:disabled) { + color: var(--button-invisible-fgColor-hover, var(--app-fg)); + background: var(--button-invisible-bgColor-hover, var(--app-bg-muted)); + border-color: var(--button-invisible-borderColor-hover, transparent); +} + +.app-button-small { + height: 28px; + padding: 0 var(--base-size-8, 8px); + line-height: 26px; +} + +.app-button-icon-only { + width: 28px; + padding: 0; +} + +.app-button > .octicon { + width: 16px; + height: 16px; + flex: none; +} + +.app-shell { + min-height: 100dvh; +} + +.app-header { + position: sticky; + top: 0; + z-index: 30; + min-height: 60px; + gap: var(--base-size-12, 12px); + padding: var(--base-size-12, 12px) var(--base-size-16, 16px); + background: var(--app-header-bg); +} + +.app-brand { + gap: var(--base-size-8, 8px); + margin-right: 0; + font-weight: 400; +} + +.app-brand-mark { + width: 28px; + height: 28px; +} + +.repository-form { + display: flex; + max-width: 620px; + margin-right: auto; + gap: var(--base-size-8, 8px); + align-items: center; +} + +.repository-combobox { + position: relative; + width: 100%; + min-width: 220px; +} + +.repository-input-shell { + position: relative; + display: flex; + height: 34px; + overflow: hidden; + color: var(--header-fgColor-default, #ffffff); + background: transparent; + border: 1px solid var(--headerSearch-borderColor, rgb(255 255 255 / 24%)); + border-radius: var(--app-radius); + align-items: center; +} + +.repository-combobox.is-open .repository-input-shell, +.repository-input-shell:focus-within { + background: transparent; + border-color: var(--focus-outlineColor, var(--color-accent-fg, #58a6ff)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--focus-outlineColor, #58a6ff) 35%, transparent); +} + +.repository-input { + flex: 1; + width: 100%; + min-width: 0; + height: 32px; + padding: 0 34px; + color: inherit; + background: transparent; + border: 0; + outline: 0; +} + +.repository-input::placeholder { + color: var(--header-fgColor-logo, rgb(255 255 255 / 72%)); +} + +.repository-input[aria-invalid="true"] { + color: var(--header-fgColor-default, #ffffff); +} + +.repository-search-icon { + position: absolute; + top: 50%; + left: 10px; + z-index: 1; + color: rgb(255 255 255 / 75%); + transform: translateY(-50%); + pointer-events: none; +} + +.repository-search-icon.spin { + animation-name: repository-search-spin; + transform-origin: center; +} + +.repository-menu-button { + position: absolute; + right: 0; + display: grid; + width: 32px; + height: 32px; + padding: 0; + color: rgb(255 255 255 / 78%); + background: transparent; + border: 0; + border-left: 1px solid rgb(255 255 255 / 14%); + place-items: center; +} + +.repository-menu-button:hover { + color: #ffffff; + background: rgb(255 255 255 / 10%); +} + +.repository-menu-button:focus-visible, +.account-chip:focus-visible { + outline: 2px solid var(--focus-outlineColor, #58a6ff); + outline-offset: 2px; +} + +.repository-panel { + position: absolute; + top: calc(100% + 8px); + left: 0; + z-index: 60; + width: min(520px, calc(100vw - 32px)); + max-height: min(520px, calc(100vh - 100px)); + overflow: hidden auto; + color: var(--app-fg); + background: var(--overlay-bgColor, var(--app-bg)); + border: 1px solid var(--app-border); + border-radius: var(--borderRadius-large, 12px); + box-shadow: var(--shadow-floating-large, 0 8px 24px rgb(140 149 159 / 20%)); +} + +.repository-panel[hidden] { + display: none; +} + +.repository-panel-header, +.repository-panel-footer { + display: flex; + padding: var(--base-size-8, 8px) var(--base-size-12, 12px); + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + background: var(--app-bg-muted); + align-items: center; + justify-content: space-between; + gap: var(--base-size-12, 12px); +} + +.repository-panel-header { + position: sticky; + top: 0; + z-index: 1; + border-bottom: 1px solid var(--app-border); +} + +.repository-panel-header strong { + color: var(--app-fg); + font-size: var(--text-body-size-medium, 14px); +} + +.repository-panel-footer { + position: sticky; + bottom: 0; + border-top: 1px solid var(--app-border); + display: block; + line-height: var(--text-body-lineHeight-medium, 20px); + font-size: var(--text-body-size-medium, 14px); +} + +.repository-panel-footer code { + margin: 0 2px; + padding: 2px 5px; + color: var(--app-fg); + font-family: var(--app-mono); + font-size: inherit; + background: var(--app-bg); + border-radius: var(--borderRadius-medium, 6px); +} + +.repository-results { + padding: var(--base-size-8, 8px); +} + +.repository-result-group + .repository-result-group { + padding-top: var(--base-size-8, 8px); + margin-top: var(--base-size-8, 8px); + border-top: 1px solid var(--app-border-muted); +} + +.repository-result-heading { + padding: 2px var(--base-size-8, 8px) var(--base-size-4, 4px); + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + font-weight: var(--base-text-weight-semibold, 600); +} + +.repository-result { + display: flex; + min-width: 0; + border-radius: var(--app-radius); + align-items: stretch; +} + +.repository-result:has(.repository-result-main.is-active), +.repository-result:hover { + background: var(--control-transparent-bgColor-hover, var(--app-bg-muted)); +} + +.repository-result-main { + display: flex; + min-width: 0; + flex: 1; + padding: var(--base-size-8, 8px); + color: var(--app-fg); + text-align: left; + background: transparent; + border: 0; + border-radius: var(--app-radius); + align-items: flex-start; + gap: var(--base-size-8, 8px); +} + +.repository-result-main:focus-visible { + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.repository-result-main > .octicon { + flex: none; + margin-top: 2px; + color: var(--app-fg-muted); +} + +.repository-result-copy { + display: flex; + min-width: 0; + flex: 1; + flex-direction: column; +} + +.repository-result-name { + display: flex; + min-width: 0; + font-weight: var(--base-text-weight-semibold, 600); + align-items: center; + gap: var(--base-size-6, 6px); +} + +.repository-result-description { + overflow: hidden; + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + text-overflow: ellipsis; + white-space: nowrap; +} + +.repository-result-actions { + display: inline-flex; + flex: none; + padding-right: var(--base-size-4, 4px); + align-items: center; + gap: 2px; +} + +.repository-result-action { + display: grid; + width: 28px; + height: 28px; + padding: 0; + color: var(--app-fg-muted); + background: transparent; + border: 1px solid transparent; + border-radius: var(--app-radius); + place-items: center; +} + +.repository-result-action:hover:not(:disabled), +.repository-result-action.is-selected { + color: var(--app-accent); + background: var(--control-transparent-bgColor-hover, var(--app-bg-muted)); + border-color: var(--app-border); +} + +.repository-result-action:disabled { + opacity: 0.35; +} + +.repository-result-status { + display: flex; + min-height: 48px; + padding: var(--base-size-12, 12px); + color: var(--app-fg-muted); + align-items: center; + justify-content: center; + gap: var(--base-size-8, 8px); +} + +.repository-result-status.error { + color: var(--app-danger); +} + +.account-control, +.header-cache-item { + margin-right: 0; +} + +.account-chip { + display: grid; + width: 36px; + height: 36px; + padding: 0; + color: var(--header-fgColor-default, #ffffff); + text-decoration: none; + background: transparent; + border: 0; + border-radius: 50%; + place-items: center; +} + +.account-chip:hover, +.account-chip.is-active { + color: #ffffff; + text-decoration: none; + background: rgb(255 255 255 / 10%); +} + +.account-avatar, +.account-avatar-fallback { + display: grid; + width: 32px; + height: 32px; + overflow: hidden; + flex: none; + background: rgb(255 255 255 / 10%); + border: 1px solid rgb(255 255 255 / 28%); + border-radius: 50%; + place-items: center; +} + +.account-avatar-fallback .octicon { + width: 13px; + height: 13px; +} + +.artifact-filter { + min-width: 180px; + width: min(300px, 32vw); + flex: 1 1 240px; +} + +.cache-link { + display: inline-flex; + gap: var(--base-size-6, 6px); + align-items: center; + font-weight: 400; +} + +.breadcrumb-bar { + position: sticky; + top: 60px; + z-index: 20; + min-height: 42px; + padding: 0 var(--base-size-16, 16px); + overflow: visible; + background: var(--app-bg); + border-bottom: 1px solid var(--app-border); +} + +.breadcrumbs { + position: relative; + display: flex; + width: 100%; + max-width: 1280px; + min-height: 41px; + padding: 0; + margin: 0 auto; + overflow: visible; + list-style: none; + align-items: center; +} + +.breadcrumb-item { + display: inline-flex; + min-width: 0; + max-width: min(24vw, 320px); + flex: none; + align-items: center; + white-space: nowrap; +} + +.breadcrumb-item + .breadcrumb-item::before { + display: block; + width: 16px; + height: 16px; + margin: 0 var(--base-size-4, 4px); + flex: none; + content: ""; + background: var(--app-fg-muted); + mask: url("/assets/octicons/chevron-right.svg") center / 16px 16px no-repeat; + opacity: 0.55; +} + +.breadcrumb-item a { + display: block; + min-width: 0; + overflow: hidden; + color: var(--app-fg-muted); + text-decoration: none; +} + +.breadcrumb-item a:hover { + color: var(--app-accent); + text-decoration: underline; +} + +.breadcrumb-item-static { + color: var(--app-fg-muted); + cursor: default; +} + +.breadcrumb-item-label, +.breadcrumb-label-full, +.breadcrumb-label-compact { + display: block; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.breadcrumb-label-compact { + display: none; +} + +.breadcrumb-item-root { + max-width: min(28vw, 240px); +} + +.breadcrumb-item[aria-current="page"] { + min-width: 0; + max-width: none; + overflow: hidden; + flex: 1; + color: var(--app-fg); + font-weight: 600; +} + +.breadcrumb-item[aria-current="page"] .breadcrumb-item-label { + width: 100%; +} + +.breadcrumb-overflow { + position: static; + display: none; + max-width: none; +} + +.breadcrumb-overflow-trigger { + display: grid; + width: 28px; + height: 28px; + padding: 0; + color: var(--app-fg-muted); + background: transparent; + border: 1px solid transparent; + border-radius: var(--app-radius); + place-items: center; + cursor: pointer; +} + +.breadcrumb-overflow-trigger:hover, +.breadcrumb-overflow.is-open .breadcrumb-overflow-trigger { + color: var(--app-fg); + background: var(--app-bg-muted); + border-color: var(--app-border); +} + +.breadcrumb-overflow-trigger:focus-visible, +.breadcrumb-overflow-option:focus-visible { + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.breadcrumb-overflow-menu { + position: absolute; + top: calc(100% + var(--base-size-8, 8px)); + left: 0; + z-index: 30; + width: min(320px, calc(100vw - 32px)); + overflow: hidden; + color: var(--app-fg); + background: var(--overlay-bgColor, var(--app-bg)); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + box-shadow: var(--shadow-floating-large, 0 8px 24px rgb(140 149 159 / 20%)); +} + +.breadcrumb-overflow-menu[hidden] { + display: none; +} + +.breadcrumb-overflow-header { + min-height: 36px; + padding: var(--base-size-8, 8px) var(--base-size-12, 12px); + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + font-weight: var(--base-text-weight-semibold, 600); + line-height: 20px; + border-bottom: 1px solid var(--app-border); +} + +.breadcrumb-overflow-option { + display: block; + min-height: 36px; + padding: var(--base-size-8, 8px) var(--base-size-12, 12px); + overflow: hidden; + color: var(--app-fg); + font-size: var(--text-body-size-small, 12px); + line-height: 20px; + text-decoration: none; + text-overflow: ellipsis; + white-space: nowrap; + border-top: 1px solid var(--app-border-muted); +} + +.breadcrumb-overflow-header + .breadcrumb-overflow-option { + border-top: 0; +} + +.breadcrumb-overflow-option:hover { + color: var(--app-fg); + text-decoration: none; + background: var(--app-bg-muted); +} + +.breadcrumb-overflow-option-static, +.breadcrumb-overflow-option-static:hover { + color: var(--app-fg-muted); + background: transparent; + cursor: default; +} + +.app-main { + width: 100%; + padding: var(--base-size-24, 24px) var(--base-size-16, 16px) + var(--base-size-40, 40px); +} + +.page-header { + display: flex; + gap: var(--base-size-16, 16px); + margin-bottom: var(--base-size-16, 16px); + align-items: flex-start; + justify-content: space-between; +} + +.page-title { + margin: 0; + font-size: 24px; + font-weight: 400; + line-height: 1.25; + overflow-wrap: anywhere; +} + +.page-title strong { + font-weight: 600; +} + +.page-title-number { + color: var(--app-fg-muted); + font-weight: 300; +} + +.page-subtitle { + margin: var(--base-size-4, 4px) 0 0; + color: var(--app-fg-muted); +} + +.page-actions { + display: flex; + gap: var(--base-size-8, 8px); + flex: none; + align-items: center; +} + +.repo-heading { + display: inline-flex; + gap: var(--base-size-8, 8px); + align-items: center; +} + +.pulls-repo-heading { + font-size: var(--text-title-size-medium, 20px); +} + +.Box { + overflow: hidden; + background: var(--app-bg); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); +} + +.Box-header { + display: flex; + min-height: 46px; + padding: var(--base-size-8, 8px) var(--base-size-16, 16px); + background: var(--app-bg-muted); + border-bottom: 1px solid var(--app-border); + align-items: center; +} + +.Box-title { + margin: 0; + font-size: 14px; + font-weight: 600; +} + +.Box-footer { + display: flex; + min-height: 40px; + padding: var(--base-size-8, 8px) var(--base-size-16, 16px); + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + line-height: 20px; + background: var(--app-bg-muted); + border-top: 1px solid var(--app-border); + align-items: center; + justify-content: space-between; + gap: var(--base-size-16, 16px); +} + +.Box-footer-count { + margin-left: auto; + font-variant-numeric: tabular-nums; + text-align: right; + white-space: nowrap; +} + +.Box-row { + position: relative; + display: flex; + min-width: 0; + gap: var(--base-size-12, 12px); + padding: var(--base-size-12, 12px) var(--base-size-16, 16px); + border-top: 1px solid var(--app-border-muted); +} + +.Box-header + .Box-row { + border-top: 0; +} + +.Box-row:hover { + background: var(--app-bg-muted); +} + +.Box-row-main { + min-width: 0; + flex: 1; +} + +.Box-row-title { + color: var(--app-fg); + font-weight: 600; + overflow-wrap: anywhere; +} + +.Box-row-title:hover { + color: var(--app-accent); +} + +.title-inline-code { + padding: 2px 4px; + margin: 0; + color: inherit; + white-space: normal; + background: var(--bgColor-neutral-muted, rgb(110 118 129 / 40%)); + border-radius: var(--app-radius); + font-family: + ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", + monospace; + font-size: 90%; + font-weight: inherit; + line-height: 1; +} + +.Box-row-meta { + margin-top: var(--base-size-4, 4px); + color: var(--app-fg-muted); + font-size: 12px; +} + +.Box-row-actions { + display: flex; + gap: var(--base-size-8, 8px); + flex: none; + align-items: center; +} + +.pull-icon { + margin-top: 2px; + color: var(--app-success); + flex: none; +} + +.pull-icon.draft { + color: var(--app-fg-muted); +} + +.pull-icon.closed { + color: var(--app-danger); +} + +.pull-icon.merged { + color: var(--app-done); +} + +.labels { + display: inline-flex; + gap: var(--base-size-4, 4px); + margin-left: var(--base-size-6, 6px); + flex-wrap: wrap; + vertical-align: middle; +} + +.issue-label { + display: inline-flex; + min-height: 18px; + padding: 0 6px; + border: 1px solid color-mix(in srgb, var(--label-color, var(--app-border)) 65%, transparent); + border-radius: 2em; + color: var(--label-text-light, var(--app-fg)); + background: color-mix(in srgb, var(--label-color, var(--app-border)) 18%, transparent); + font-size: 11px; + font-weight: 500; + line-height: 16px; + align-items: center; +} + +[data-color-mode="dark"] .issue-label { + color: var(--label-text-dark, var(--app-fg)); +} + +@media (prefers-color-scheme: dark) { + [data-color-mode="auto"] .issue-label { + color: var(--label-text-dark, var(--app-fg)); + } +} + +.toolbar { + display: flex; + gap: var(--base-size-8, 8px); + align-items: center; +} + +.toolbar-spacer { + flex: 1; +} + +.filter-select { + width: auto; + min-width: 112px; +} + +.artifact-list-box { + overflow: visible; +} + +.artifact-toolbar { + position: relative; + gap: var(--base-size-8, 8px); + flex-wrap: wrap; +} + +.artifact-toolbar .Box-title { + display: inline-flex; + gap: var(--base-size-8, 8px); + align-items: center; +} + +.artifact-refresh { + margin-left: auto; + flex: none; +} + +.artifact-filter-picker { + position: relative; + flex: none; +} + +.artifact-filter-controls { + display: flex; + align-items: center; + gap: var(--base-size-8, 8px); +} + +.artifact-filter-trigger { + max-width: 220px; + font-weight: 400; +} + +.artifact-filter-trigger > span { + overflow: hidden; + text-overflow: ellipsis; +} + +.artifact-filter-trigger.is-filtered { + color: var(--app-accent); + background: var( + --button-default-bgColor-rest, + var(--app-bg-muted) + ); + border-color: var(--borderColor-accent-emphasis, var(--app-accent)); +} + +.artifact-filter-picker.is-open .artifact-filter-trigger { + background: var(--button-default-bgColor-active, var(--app-bg-inset)); + border-color: var(--button-default-borderColor-active, var(--app-border)); +} + +.artifact-filter-panel { + position: absolute; + top: calc(100% + 8px); + right: 0; + z-index: 25; + width: min(300px, calc(100vw - 32px)); + overflow: hidden; + color: var(--app-fg); + background: var(--overlay-bgColor, var(--app-bg)); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + box-shadow: var(--shadow-floating-large, 0 8px 24px rgb(140 149 159 / 20%)); +} + +.artifact-filter-panel[hidden] { + display: none; +} + +.artifact-filter-options { + max-height: min(320px, 60dvh); + overflow-y: auto; +} + +.artifact-filter-panel-header { + display: flex; + min-height: 40px; + padding: var(--base-size-4, 4px) var(--base-size-8, 8px) + var(--base-size-4, 4px) var(--base-size-12, 12px); + border-bottom: 1px solid var(--app-border); + align-items: center; + justify-content: space-between; + gap: var(--base-size-8, 8px); +} + +.artifact-filter-panel-header strong { + font-size: var(--text-body-size-small, 12px); +} + +.artifact-filter-close { + display: grid; + width: 28px; + height: 28px; + padding: 0; + color: var(--app-fg-muted); + background: transparent; + border: 0; + border-radius: var(--app-radius); + place-items: center; + cursor: pointer; +} + +.artifact-filter-close:hover { + color: var(--app-fg); + background: var(--app-bg-muted); +} + +.artifact-filter-option { + display: flex; + width: 100%; + min-height: 36px; + padding: var(--base-size-6, 6px) var(--base-size-12, 12px); + color: var(--app-fg); + text-align: left; + background: transparent; + border: 0; + border-top: 1px solid var(--app-border-muted); + align-items: center; + gap: var(--base-size-8, 8px); + cursor: pointer; +} + +.artifact-filter-option:first-child { + border-top: 0; +} + +.artifact-filter-option:hover { + background: var(--app-bg-muted); +} + +.artifact-filter-option[aria-checked="true"] { + background: var( + --bgColor-accent-muted, + color-mix(in srgb, var(--app-accent) 12%, transparent) + ); +} + +.artifact-filter-check { + display: grid; + width: 16px; + height: 16px; + flex: none; + color: var(--app-accent); + place-items: center; +} + +.artifact-filter-option-copy { + min-width: 0; + overflow: hidden; + flex: 1; + font-size: var(--text-body-size-small, 12px); + text-overflow: ellipsis; + white-space: nowrap; +} + +.artifact-filter-option-count { + min-width: 2ch; + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + font-variant-numeric: tabular-nums; + text-align: right; +} + +.artifact-filter-trigger:focus-visible, +.artifact-filter-close:focus-visible, +.artifact-filter-option:focus-visible { + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.pull-query-form { + display: block; + margin-bottom: var(--base-size-16, 16px); +} + +.pull-query-input-wrap { + position: relative; + min-width: 0; + background: var(--control-bgColor-rest, var(--app-bg)); + border-radius: var(--app-radius); +} + +.pull-query-input-wrap > .octicon { + position: absolute; + top: 50%; + left: var(--base-size-12, 12px); + z-index: 3; + color: var(--app-fg-muted); + transform: translateY(-50%); + pointer-events: none; +} + +.pull-query-highlight { + position: absolute; + inset: 0; + z-index: 1; + overflow: hidden; + padding: 6px var(--base-size-12, 12px) 6px 36px; + color: var(--app-fg); + font-family: var(--app-mono); + font-size: var(--text-body-size-small, 12px); + line-height: 20px; + white-space: pre; + pointer-events: none; +} + +.pull-query-highlight > span { + display: inline-block; + transform: translateX(0); +} + +.pull-query-syntax-negation { + color: var(--fgColor-danger, var(--app-danger)); + font-weight: var(--base-text-weight-semibold, 600); +} + +.pull-query-syntax-qualifier { + color: var(--fgColor-accent, var(--app-accent)); + font-weight: var(--base-text-weight-semibold, 600); +} + +.pull-query-syntax-punctuation { + color: var(--app-fg-muted); +} + +.pull-query-syntax-value { + color: var(--fgColor-done, var(--app-fg)); +} + +.pull-query-input { + position: relative; + z-index: 2; + width: 100%; + height: 34px; + padding: 5px var(--base-size-12, 12px) 5px 36px; + color: transparent; + caret-color: var(--app-fg); + font-family: var(--app-mono); + font-size: var(--text-body-size-small, 12px); + line-height: 20px; + background: transparent; + -webkit-text-fill-color: transparent; +} + +.pull-query-input::placeholder { + color: var(--app-fg-muted); + opacity: 1; + -webkit-text-fill-color: var(--app-fg-muted); +} + +.pull-query-suggestions { + position: absolute; + top: calc(100% + var(--base-size-4, 4px)); + right: 0; + left: 0; + z-index: 25; + overflow: hidden; + color: var(--app-fg); + background: var(--app-bg); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + box-shadow: var(--shadow-floating-small, 0 8px 24px rgb(140 149 159 / 20%)); +} + +.pull-query-suggestion-heading { + padding: var(--base-size-8, 8px) var(--base-size-12, 12px); + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + font-weight: var(--base-text-weight-semibold, 600); + background: var(--app-bg-muted); + border-bottom: 1px solid var(--app-border-muted); +} + +.pull-query-suggestion-options { + overflow-y: auto; + max-height: 280px; + padding: var(--base-size-4, 4px); +} + +.pull-query-suggestion { + display: grid; + width: 100%; + padding: var(--base-size-8, 8px); + color: var(--app-fg); + text-align: left; + background: transparent; + border: 0; + border-radius: var(--app-radius); + grid-template-columns: minmax(130px, 0.38fr) minmax(0, 1fr); + align-items: baseline; + gap: var(--base-size-12, 12px); +} + +.pull-query-suggestion:hover, +.pull-query-suggestion.is-active { + background: var(--control-transparent-bgColor-hover, var(--app-bg-muted)); +} + +.pull-query-suggestion:focus-visible { + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.pull-query-suggestion code { + overflow: hidden; + color: var(--app-fg); + font-family: var(--app-mono); + font-size: var(--text-body-size-small, 12px); + font-weight: var(--base-text-weight-semibold, 600); + text-overflow: ellipsis; + white-space: nowrap; +} + +.pull-query-suggestion > span { + overflow: hidden; + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + text-overflow: ellipsis; + white-space: nowrap; +} + +.pull-query-suggestion-footer { + display: flex; + padding: var(--base-size-8, 8px) var(--base-size-12, 12px); + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + background: var(--app-bg-muted); + border-top: 1px solid var(--app-border-muted); + gap: var(--base-size-16, 16px); +} + +.pull-query-suggestion-footer kbd { + padding: 1px 5px; + color: var(--app-fg); + font-family: var(--app-mono); + font-size: 11px; + background: var(--app-bg); + border: 1px solid var(--app-border); + border-bottom-width: 2px; + border-radius: var(--borderRadius-small, 4px); + box-shadow: none; +} + +@media (forced-colors: active) { + .pull-query-highlight { + display: none; + } + + .pull-query-input { + color: CanvasText; + -webkit-text-fill-color: CanvasText; + } +} + +.pull-list-box { + overflow: visible; +} + +.pull-list-progress { + display: flex; + min-height: 36px; + padding: var(--base-size-8, 8px) var(--base-size-16, 16px); + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + background: var(--app-bg-muted); + border-bottom: 1px solid var(--app-border); + align-items: center; + gap: var(--base-size-8, 8px); +} + +.pull-list-refresh-status { + display: inline-flex; + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + align-items: center; + gap: var(--base-size-4, 4px); +} + +.pull-list-box.is-loading > .pull-list-rows, +.pull-list-box.is-loading > #pull-list-footer { + opacity: 0.55; + pointer-events: none; +} + +.pull-list-toolbar { + position: relative; + border-radius: var(--app-radius) var(--app-radius) 0 0; +} + +.pull-state-picker, +.pull-author-picker, +.pull-signal-picker { + position: relative; +} + +.pull-state-trigger { + display: inline-flex; + height: 32px; + padding: 0 var(--base-size-8, 8px); + color: var(--app-fg-muted); + font-size: var(--text-body-size-medium, 14px); + font-weight: 400; + background: transparent; + border: 1px solid transparent; + border-radius: var(--app-radius); + align-items: center; + gap: var(--base-size-4, 4px); +} + +.pull-state-trigger:hover, +.pull-state-picker.is-open .pull-state-trigger, +.pull-author-picker.is-open .pull-state-trigger, +.pull-signal-picker.is-open .pull-state-trigger { + color: var(--app-fg); + background: var(--control-transparent-bgColor-hover, var(--app-bg-inset)); + border-color: var(--app-border); +} + +.pull-state-trigger.is-filtered { + color: var(--app-accent); + font-weight: var(--base-text-weight-semibold, 600); +} + +.pull-filter-value { + max-width: 150px; + overflow: hidden; + color: var(--app-fg-muted); + font-weight: 400; + text-overflow: ellipsis; + white-space: nowrap; +} + +.pull-state-trigger:focus-visible, +.pull-state-close:focus-visible, +.pull-state-option:focus-visible { + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.pull-state-panel { + position: absolute; + top: calc(100% + 8px); + right: 0; + z-index: 25; + width: min(340px, calc(100vw - 32px)); + overflow: hidden; + color: var(--app-fg); + background: var(--overlay-bgColor, var(--app-bg)); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + box-shadow: var(--shadow-floating-large, 0 8px 24px rgb(140 149 159 / 20%)); +} + +.pull-state-panel[hidden], +.pull-author-panel[hidden] { + display: none; +} + +.pull-author-panel { + position: absolute; + top: calc(100% + 8px); + right: 0; + z-index: 25; + width: min(340px, calc(100vw - 32px)); + overflow: hidden; + color: var(--app-fg); + background: var(--overlay-bgColor, var(--app-bg)); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + box-shadow: var(--shadow-floating-large, 0 8px 24px rgb(140 149 159 / 20%)); +} + +.pull-state-panel-header { + display: flex; + min-height: 40px; + padding: var(--base-size-4, 4px) var(--base-size-8, 8px) + var(--base-size-4, 4px) var(--base-size-12, 12px); + border-bottom: 1px solid var(--app-border); + align-items: center; + justify-content: space-between; + gap: var(--base-size-8, 8px); +} + +.pull-state-panel-header strong { + font-size: var(--text-body-size-small, 12px); +} + +.pull-state-close { + display: grid; + width: 28px; + height: 28px; + padding: 0; + color: var(--app-fg-muted); + background: transparent; + border: 0; + border-radius: var(--app-radius); + place-items: center; +} + +.pull-state-close:hover { + color: var(--app-fg); + background: var(--app-bg-muted); +} + +.pull-state-option { + display: flex; + width: 100%; + min-height: 48px; + padding: var(--base-size-6, 6px) var(--base-size-12, 12px); + color: var(--app-fg); + text-align: left; + background: transparent; + border: 0; + border-top: 1px solid var(--app-border-muted); + align-items: center; + gap: var(--base-size-8, 8px); +} + +.pull-state-option:first-child { + border-top: 0; +} + +.pull-state-option:hover { + background: var(--app-bg-muted); +} + +.pull-state-option[aria-checked="true"] { + background: var(--bgColor-accent-muted, color-mix(in srgb, var(--app-accent) 12%, transparent)); +} + +.pull-signal-option { + min-height: 40px; +} + +.pull-state-check { + display: grid; + width: 16px; + height: 16px; + flex: none; + color: var(--app-accent); + place-items: center; +} + +.pull-state-option strong, +.pull-state-option small { + display: block; +} + +.pull-author-filter-wrap { + position: relative; + padding: var(--base-size-8, 8px); + border-bottom: 1px solid var(--app-border); +} + +.pull-author-filter-wrap > .octicon { + position: absolute; + top: 50%; + left: 18px; + color: var(--app-fg-muted); + transform: translateY(-50%); + pointer-events: none; +} + +.pull-author-filter-wrap .form-control { + width: 100%; + padding-left: 32px; +} + +.pull-author-options { + max-height: 288px; + overflow: auto; +} + +.pull-author-option { + display: grid; + width: 100%; + min-height: 40px; + padding: var(--base-size-6, 6px) var(--base-size-12, 12px); + color: var(--app-fg); + text-align: left; + background: transparent; + border: 0; + border-top: 1px solid var(--app-border-muted); + grid-template-columns: 16px 20px minmax(0, 1fr); + align-items: center; + gap: var(--base-size-8, 8px); +} + +.pull-author-option:first-child { + border-top: 0; +} + +.pull-author-option:hover, +.pull-author-option[aria-checked="true"] { + background: var(--app-bg-muted); +} + +.pull-author-option:focus-visible { + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.pull-author-check { + display: grid; + width: 16px; + color: var(--app-accent); + place-items: center; +} + +.pull-author-avatar { + display: grid; + width: 20px; + height: 20px; + overflow: hidden; + background: var(--app-bg-muted); + border: 1px solid var(--app-border); + border-radius: 50%; + object-fit: cover; + place-items: center; +} + +.pull-author-avatar-fallback { + color: var(--app-fg-muted); +} + +.pull-author-avatar-fallback .octicon { + width: 12px; + height: 12px; +} + +.pull-author-identity { + display: flex; + min-width: 0; + align-items: baseline; + gap: var(--base-size-6, 6px); +} + +.pull-author-identity strong { + flex: none; +} + +.pull-author-name { + min-width: 0; + color: var(--app-fg-muted); + font-weight: 400; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.pull-author-status { + display: flex; + min-height: 44px; + padding: var(--base-size-8, 8px); + color: var(--app-fg-muted); + align-items: center; + justify-content: center; + gap: var(--base-size-8, 8px); +} + +.pull-author-status.error { + color: var(--app-danger); +} + +.pull-state-option small { + margin-top: 0; + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + line-height: 16px; +} + +.pull-row { + min-height: 70px; +} + +.pull-row .Box-row-title { + font-size: 16px; + line-height: 24px; +} + +.pull-list-toolbar .Box-title { + display: inline-flex; + align-items: center; + gap: var(--base-size-8, 8px); +} + +.pull-list-heading-icon { + color: var(--app-fg-muted); +} + +.pull-row-title-line, +.pull-row-meta { + display: flex; + min-width: 0; + align-items: center; + flex-wrap: wrap; +} + +.pull-row-title-line { + gap: var(--base-size-4, 4px); +} + +.pull-row-title-line .labels { + margin-left: var(--base-size-2, 2px); +} + +.pull-check { + display: inline-grid; + width: 16px; + height: 16px; + flex: none; + place-items: center; +} + +.pull-check .octicon { + width: 16px; + height: 16px; +} + +.pull-check.passing { + color: var(--app-success); +} + +.pull-check.failing { + color: var(--app-danger); +} + +.pull-check.pending { + color: var(--app-attention); +} + +.pull-row-meta { + gap: var(--base-size-6, 6px); +} + +.author-association { + display: inline-flex; + min-height: 20px; + padding: 0 7px; + border: 1px solid var(--app-border); + border-radius: 2em; + align-items: center; +} + +.pull-review-status { + display: inline-flex; + align-items: center; +} + +.pull-review-status::before { + margin-right: var(--base-size-6, 6px); + color: var(--app-fg-muted); + content: "•"; +} + +.pull-comments { + display: inline-flex; + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + align-items: center; + gap: var(--base-size-4, 4px); +} + +.state-badge { + display: inline-flex; + gap: var(--base-size-6, 6px); + min-height: 32px; + padding: 5px 12px; + border-radius: 2em; + color: #ffffff; + background: var(--app-success); + font-size: 14px; + font-weight: 600; + align-items: center; +} + +.state-badge.closed { + background: var(--app-danger); +} + +.state-badge.merged { + background: var(--app-done); +} + +.state-badge.draft { + background: var(--app-fg-muted); +} + +.pr-summary { + display: flex; + gap: var(--base-size-8, 8px); + margin-bottom: var(--base-size-24, 24px); + color: var(--app-fg-muted); + align-items: center; + flex-wrap: wrap; +} + +.avatar { + width: 20px; + height: 20px; + border-radius: 50%; +} + +.artifact-icon { + margin-top: 2px; + color: var(--app-fg-muted); + flex: none; + width: 14px; + height: 14px; +} + +.artifact-row.is-clickable, +.cached-artifact-row.is-clickable { + cursor: pointer; +} + +.artifact-row.is-downloading, +.artifact-row.is-downloading .artifact-row-overlay, +.artifact-row.is-downloading .Box-row-actions .app-button:disabled { + cursor: progress; +} + +.artifact-row.is-downloading .Box-row-actions .app-button:disabled { + opacity: 1; +} + +.artifact-row.has-transfer-row, +.artifact-row.has-transfer-row:hover, +.artifact-transfer-row { + background: color-mix(in srgb, var(--app-accent) 4%, var(--app-bg)); + box-shadow: inset 3px 0 0 var(--app-accent); +} + +.artifact-transfer-row { + display: grid; + min-width: 0; + padding: var(--base-size-10, 10px) var(--base-size-16, 16px) + var(--base-size-12, 12px); + border-top: 1px solid color-mix( + in srgb, + var(--app-accent) 18%, + var(--app-border-muted) + ); + grid-template-columns: 14px minmax(0, 1fr); + gap: var(--base-size-12, 12px); +} + +.artifact-transfer-icon { + width: 14px; + height: 14px; + margin-top: 2px; + color: var(--app-accent); +} + +.artifact-transfer-row .artifact-download-progress { + max-width: none; + padding: 0; + margin: 0; + background: transparent; + border: 0; + border-radius: 0; +} + +.artifact-row-overlay { + position: absolute; + inset: 0; + z-index: 1; + padding: 0; + background: transparent; + border: 0; + border-radius: inherit; + cursor: pointer; +} + +.artifact-row-overlay:focus-visible { + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.artifact-row.is-expired { + color: var(--app-fg-muted); +} + +.artifact-row .Box-row-actions, +.cached-artifact-row .Box-row-actions { + position: relative; + z-index: 2; +} + +.artifact-summary { + display: flex; + gap: var(--base-size-8, 8px); + color: var(--app-fg-muted); + font-size: 12px; + align-items: center; + flex-wrap: wrap; +} + +.artifact-download-progress { + max-width: 680px; + margin-top: var(--base-size-8, 8px); + padding: var(--base-size-8, 8px) var(--base-size-10, 10px); + background: var(--app-bg-muted); + border: 1px solid var(--app-border-muted); + border-radius: var(--app-radius); +} + +.artifact-download-copy { + display: flex; + min-width: 0; + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + justify-content: space-between; + gap: var(--base-size-12, 12px); +} + +.artifact-download-phrase { + min-width: 0; + color: var(--app-fg); + font-weight: 600; +} + +.artifact-download-stats { + flex: none; + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.artifact-download-stats:empty { + display: none; +} + +.artifact-download-track { + height: 6px; + margin-top: var(--base-size-6, 6px); + overflow: hidden; + background: var(--app-bg-inset); + border-radius: 2em; +} + +.artifact-download-bar { + display: block; + width: var(--artifact-download-percent, 0%); + height: 100%; + background: var(--app-accent); + border-radius: inherit; + transition: width 200ms linear; +} + +.artifact-download-progress.is-indeterminate .artifact-download-bar { + width: 35%; + animation: artifact-download-indeterminate 1.2s ease-in-out infinite; +} + +.artifact-download-progress.is-error { + border-color: var(--app-danger); +} + +.artifact-download-progress.is-error .artifact-download-bar { + background: var(--app-danger); +} + +@keyframes artifact-download-indeterminate { + from { + transform: translateX(-120%); + } + + to { + transform: translateX(360%); + } +} + +.artifact-summary-item { + display: inline-flex; + gap: var(--base-size-4, 4px); + align-items: center; +} + +.artifact-summary-item .octicon { + width: 14px; + height: 14px; +} + +.status-success { + color: var(--app-success); +} + +.status-danger { + color: var(--app-danger); +} + +.status-attention { + color: var(--app-attention); +} + +.status-muted { + color: var(--app-fg-muted); +} + +.account-repository-context { + margin-bottom: var(--base-size-16, 16px); +} + +.account-list { + display: grid; + gap: var(--base-size-12, 12px); +} + +.account-card { + overflow: hidden; + background: var(--app-bg); + border: 1px solid var(--app-border); + border-radius: var(--borderRadius-large, 12px); +} + +.account-card.is-active { + border-color: var(--borderColor-accent-emphasis, var(--app-accent)); + box-shadow: inset 0 0 0 1px + color-mix(in srgb, var(--borderColor-accent-emphasis, var(--app-accent)) 24%, transparent); +} + +.account-card-main { + display: flex; + width: 100%; + min-width: 0; + padding: var(--base-size-16, 16px); + color: var(--app-fg); + text-align: left; + background: transparent; + border: 0; + align-items: center; + gap: var(--base-size-12, 12px); +} + +.account-card-main:not(:disabled):hover { + background: var(--app-bg-muted); +} + +.account-card-main:disabled { + color: var(--app-fg); + cursor: default; + opacity: 1; +} + +.account-card-main:focus-visible { + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.account-card-avatar { + display: grid; + width: 44px; + height: 44px; + overflow: hidden; + flex: none; + background: var(--app-bg-muted); + border: 1px solid var(--app-border); + border-radius: 50%; + object-fit: cover; + place-items: center; +} + +.account-card-avatar-fallback .octicon { + width: 22px; + height: 22px; + color: var(--app-fg-muted); +} + +.account-card-identity { + display: flex; + min-width: 0; + flex: 1; + flex-direction: column; +} + +.account-card-title { + display: flex; + min-width: 0; + font-size: var(--text-title-size-small, 16px); + align-items: center; + gap: var(--base-size-8, 8px); + flex-wrap: wrap; +} + +.account-card-login, +.account-card-company { + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); +} + +.account-access { + display: inline-flex; + flex: none; + padding: 4px 8px; + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + font-weight: var(--base-text-weight-semibold, 600); + background: var(--app-bg-muted); + border: 1px solid var(--app-border); + border-radius: 2em; + align-items: center; + gap: var(--base-size-4, 4px); +} + +.account-access.available { + color: var(--app-success); +} + +.account-access.limited { + color: var(--app-attention); +} + +.account-access.failed { + color: var(--app-danger); +} + +.account-card-footer { + display: flex; + min-height: 45px; + padding: var(--base-size-8, 8px) var(--base-size-16, 16px); + background: var(--app-bg-muted); + border-top: 1px solid var(--app-border-muted); + align-items: center; + justify-content: space-between; + gap: var(--base-size-12, 12px); +} + +.account-scope-list { + display: flex; + min-width: 0; + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + align-items: center; + gap: var(--base-size-4, 4px); + flex-wrap: wrap; +} + +.account-scope-list code { + padding: 2px 7px; + color: var(--app-fg-muted); + background: var(--app-bg); + border: 1px solid var(--app-border-muted); + border-radius: 2em; + font-family: var(--app-mono); + font-size: 11px; + line-height: 16px; +} + +.account-sources { + border-top: 1px solid var(--app-border-muted); +} + +.account-sources summary { + display: flex; + min-height: 40px; + padding: var(--base-size-8, 8px) var(--base-size-16, 16px); + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + list-style: none; + align-items: center; + justify-content: space-between; + gap: var(--base-size-12, 12px); + cursor: pointer; +} + +.account-sources summary::-webkit-details-marker { + display: none; +} + +.account-sources summary:hover { + color: var(--app-fg); + background: var(--app-bg-muted); +} + +.account-sources summary:focus-visible { + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.account-sources-chevron { + flex: none; + transition: transform 120ms ease-out; +} + +.account-sources[open] .account-sources-chevron { + transform: rotate(180deg); +} + +.credential-source-list { + border-top: 1px solid var(--app-border-muted); +} + +.credential-source { + display: flex; + padding: var(--base-size-12, 12px) var(--base-size-16, 16px); + border-top: 1px solid var(--app-border-muted); + align-items: flex-start; + gap: var(--base-size-8, 8px); +} + +.credential-source:first-child { + border-top: 0; +} + +.credential-source-icon { + color: var(--app-success); +} + +.credential-source-copy { + display: flex; + min-width: 0; + flex: 1; + flex-direction: column; +} + +.credential-source-name { + display: flex; + font-weight: var(--base-text-weight-semibold, 600); + align-items: center; + gap: var(--base-size-8, 8px); +} + +.credential-source-meta, +.credential-source-error { + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); +} + +.credential-source-error { + color: var(--app-danger); +} + +.explorer { + display: grid; + min-height: min(680px, calc(100dvh - 220px)); + overflow: hidden; + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + grid-template-columns: minmax(230px, 310px) minmax(0, 1fr); +} + +.explorer.is-sidebar-collapsed { + grid-template-columns: minmax(0, 1fr); +} + +.explorer.is-sidebar-collapsed .file-panel { + display: none; +} + +.file-panel { + position: relative; + z-index: 2; + min-width: 0; + background: var(--app-bg); + border-right: 1px solid var(--app-border); +} + +.file-panel-header { + position: relative; + padding: var(--base-size-12, 12px); + background: var(--app-bg-muted); + border-bottom: 1px solid var(--app-border); +} + +.file-filter-toolbar { + display: grid; + grid-template-columns: minmax(0, 1fr) 32px; + gap: var(--base-size-8, 8px); +} + +.file-filter-wrap { + position: relative; +} + +.file-filter { + width: 100%; + padding-left: 32px; +} + +.file-filter-icon { + position: absolute; + top: 50%; + left: 10px; + color: var(--app-fg-muted); + transform: translateY(-50%); + pointer-events: none; +} + +.file-tree-filter-root { + position: relative; +} + +.file-tree-filter-trigger { + width: 32px; + height: 32px; +} + +.file-tree-filter-trigger.is-filtered { + color: var(--app-accent); + background: var(--bgColor-accent-muted, var(--app-bg)); + border-color: var(--borderColor-accent-emphasis, var(--app-accent)); +} + +.file-tree-filter-root.is-open .file-tree-filter-trigger { + background: var(--button-default-bgColor-active, var(--app-bg-inset)); +} + +.file-tree-filter-menu { + position: absolute; + top: calc(100% + var(--base-size-8, 8px)); + left: 0; + z-index: 35; + width: min(260px, calc(100vw - 32px)); + overflow: hidden; + color: var(--app-fg); + background: var(--overlay-bgColor, var(--app-bg)); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + box-shadow: var(--shadow-floating-large, 0 8px 24px rgb(140 149 159 / 20%)); +} + +.file-tree-filter-menu[hidden] { + display: none; +} + +.file-tree-filter-heading { + padding: var(--base-size-12, 12px) var(--base-size-12, 12px) + var(--base-size-8, 8px); + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + font-weight: var(--base-text-weight-semibold, 600); +} + +.file-tree-filter-options { + max-height: min(240px, 45dvh); + padding-bottom: var(--base-size-4, 4px); + overflow-y: auto; +} + +.file-tree-filter-categories { + padding: var(--base-size-4, 4px) 0; + border-top: 1px solid var(--app-border-muted); +} + +.file-tree-filter-option { + display: grid; + width: 100%; + min-height: 36px; + padding: var(--base-size-6, 6px) var(--base-size-12, 12px); + color: var(--app-fg); + text-align: left; + background: transparent; + border: 0; + grid-template-columns: 16px minmax(0, 1fr) auto; + align-items: center; + gap: var(--base-size-8, 8px); + cursor: pointer; +} + +.file-tree-filter-option:hover { + background: var(--app-bg-muted); +} + +.file-tree-filter-check { + display: grid; + width: 16px; + height: 16px; + color: var(--app-fg-muted); + place-items: center; +} + +.file-tree-filter-option[aria-checked="true"] .file-tree-filter-check { + color: var(--app-fg); +} + +.file-tree-filter-copy { + min-width: 0; + overflow: hidden; + font-size: var(--text-body-size-small, 12px); + text-overflow: ellipsis; + white-space: nowrap; +} + +.file-tree-filter-option .Counter { + min-width: 24px; + font-variant-numeric: tabular-nums; + text-align: center; +} + +.file-tree-filter-trigger:focus-visible, +.file-tree-filter-option:focus-visible { + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.file-list { + max-height: min(620px, calc(100dvh - 278px)); + padding: var(--base-size-4, 4px) 0; + margin: 0; + overflow: auto; + list-style: none; +} + +.file-tree-group { + padding: 0; + margin: 0; + list-style: none; +} + +.file-tree-item { + margin: 0; +} + +.file-tree-row { + display: grid; + width: 100%; + min-height: 30px; + padding: var(--base-size-4, 4px) var(--base-size-8, 8px) + var(--base-size-4, 4px) + calc(var(--base-size-8, 8px) + var(--file-tree-depth, 0) * 16px); + color: var(--app-fg); + font-size: var(--text-body-size-small, 12px); + text-align: left; + background: transparent; + border: 0; + align-items: center; + gap: var(--base-size-6, 6px); + cursor: pointer; +} + +.file-tree-directory { + grid-template-columns: 12px 16px minmax(0, 1fr); + font-weight: var(--base-text-weight-semibold, 600); +} + +.file-tree-file { + grid-template-columns: 12px 16px minmax(0, 1fr) auto; +} + +.file-tree-row:hover { + background: var(--app-bg-muted); +} + +.file-tree-row:focus-visible { + position: relative; + z-index: 1; + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.file-tree-file[aria-current="true"] { + background: var(--bgColor-accent-muted, #ddf4ff); +} + +.file-tree-chevron { + width: 12px; + height: 12px; + color: var(--app-fg-muted); +} + +.file-tree-folder, +.file-tree-file-icon { + color: var(--app-fg-muted); +} + +.file-tree-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.file-tree-size { + color: var(--app-fg-muted); + font-size: 11px; + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.file-tree-spacer { + width: 12px; + height: 1px; +} + +.preview-panel { + position: relative; + z-index: 1; + display: flex; + min-width: 0; + min-height: 0; + background: var(--app-bg-inset); + flex-direction: column; +} + +.preview-toolbar { + display: flex; + min-height: 48px; + gap: var(--base-size-8, 8px); + padding: var(--base-size-8, 8px) var(--base-size-12, 12px); + background: var(--app-bg); + border-bottom: 1px solid var(--app-border); + align-items: center; +} + +.preview-path { + min-width: 0; + overflow: hidden; + font-family: var(--app-mono); + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; +} + +.preview-content { + position: relative; + display: grid; + min-height: 0; + padding: var(--base-size-16, 16px); + overflow: auto; + place-items: start stretch; + flex: 1; +} + +.preview-frame { + width: 100%; + min-height: min(600px, calc(100dvh - 310px)); + background: var(--app-bg); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); +} + +.preview-image { + max-width: 100%; + max-height: min(600px, calc(100dvh - 310px)); + margin: auto; + object-fit: contain; +} + +.text-preview { + width: 100%; + min-height: 100%; + padding: var(--base-size-16, 16px); + margin: 0; + overflow: auto; + color: var(--app-fg); + background: var(--app-bg); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + font-family: var(--app-mono); + font-size: 12px; + line-height: 1.5; + tab-size: 2; + white-space: pre; +} + +.syntax-code code { + color: inherit; + font: inherit; +} + +.syntax-xml .syntax-punctuation { + color: var(--app-fg-muted); +} + +.syntax-xml .syntax-tag { + color: var(--app-done); +} + +.syntax-xml .syntax-attribute { + color: var(--app-accent); +} + +.syntax-xml .syntax-string, +.syntax-xml .syntax-cdata { + color: var(--app-success); +} + +.syntax-xml .syntax-comment { + color: var(--app-fg-muted); + font-style: italic; +} + +.syntax-xml .syntax-meta { + color: var(--app-attention); +} + +.syntax-json .syntax-key { + color: var(--app-accent); +} + +.syntax-json .syntax-string { + color: var(--app-success); +} + +.syntax-json .syntax-number { + color: var(--app-attention); +} + +.syntax-json .syntax-boolean { + color: var(--app-done); + font-weight: var(--base-text-weight-semibold, 600); +} + +.syntax-json .syntax-null { + color: var(--app-fg-muted); + font-style: italic; +} + +.trx-preview { + display: block; + width: 100%; + min-width: 0; +} + +.trx-overview, +.trx-results-panel, +.trx-raw-panel { + overflow: hidden; + background: var(--app-bg); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); +} + +.trx-results-panel, +.trx-raw-panel { + margin-top: var(--base-size-16, 16px); +} + +.trx-run-heading, +.trx-section-heading { + display: flex; + min-width: 0; + padding: var(--base-size-16, 16px); + align-items: flex-start; + justify-content: space-between; + gap: var(--base-size-16, 16px); +} + +.trx-run-heading h2, +.trx-section-heading h3, +.trx-error h2, +.trx-no-results h4 { + margin: 0; + color: var(--app-fg); +} + +.trx-run-heading h2 { + font-size: 20px; + line-height: 28px; + overflow-wrap: anywhere; +} + +.trx-section-heading { + border-bottom: 1px solid var(--app-border-muted); +} + +.trx-section-heading h3 { + font-size: 16px; + line-height: 24px; +} + +.trx-section-heading p { + margin: var(--base-size-2, 2px) 0 0; + color: var(--app-fg-muted); + font-size: 12px; +} + +.trx-eyebrow { + display: block; + margin-bottom: var(--base-size-2, 2px); + color: var(--app-fg-muted); + font-size: 11px; + font-weight: var(--base-text-weight-semibold, 600); + letter-spacing: 0.04em; + line-height: 16px; + text-transform: uppercase; +} + +.trx-run-outcome, +.trx-outcome-badge { + display: inline-flex; + white-space: nowrap; + border: 1px solid currentColor; + border-radius: 999px; + align-items: center; + gap: var(--base-size-4, 4px); +} + +.trx-run-outcome { + min-height: 28px; + padding: 2px var(--base-size-8, 8px); + font-size: 12px; + font-weight: var(--base-text-weight-semibold, 600); +} + +.trx-outcome-passed { + color: var(--app-success); +} + +.trx-outcome-failed { + color: var(--app-danger); +} + +.trx-outcome-skipped { + color: var(--app-attention); +} + +.trx-outcome-other { + color: var(--app-done); +} + +.trx-metrics { + display: grid; + padding: 0 var(--base-size-16, 16px) var(--base-size-16, 16px); + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: var(--base-size-8, 8px); +} + +.trx-metric { + display: grid; + min-width: 0; + padding: var(--base-size-12, 12px); + color: var(--app-fg); + background: var(--app-bg-muted); + border: 1px solid var(--app-border-muted); + border-radius: var(--app-radius); + grid-template-columns: auto minmax(0, 1fr); + grid-template-rows: auto auto; + column-gap: var(--base-size-8, 8px); +} + +.trx-metric-icon { + grid-row: 1 / span 2; + align-self: center; +} + +.trx-metric-passed .trx-metric-icon { + color: var(--app-success); +} + +.trx-metric-failed .trx-metric-icon { + color: var(--app-danger); +} + +.trx-metric-skipped .trx-metric-icon { + color: var(--app-attention); +} + +.trx-metric-value { + min-width: 0; + font-size: 20px; + font-weight: var(--base-text-weight-semibold, 600); + font-variant-numeric: tabular-nums; + line-height: 24px; +} + +.trx-metric-label { + color: var(--app-fg-muted); + font-size: 12px; + line-height: 16px; +} + +.trx-run-metadata, +.trx-result-metadata { + display: grid; + margin: 0; + gap: 0; +} + +.trx-run-metadata { + padding: var(--base-size-12, 12px) var(--base-size-16, 16px); + background: var(--app-bg-muted); + border-top: 1px solid var(--app-border-muted); + grid-template-columns: repeat(3, minmax(0, 1fr)); + column-gap: var(--base-size-24, 24px); + row-gap: var(--base-size-12, 12px); +} + +.trx-meta-item { + min-width: 0; +} + +.trx-meta-item dt { + color: var(--app-fg-muted); + font-size: 11px; + font-weight: var(--base-text-weight-semibold, 600); + line-height: 16px; +} + +.trx-meta-item dd { + margin: 0; + overflow: hidden; + color: var(--app-fg); + font-size: 12px; + line-height: 18px; + overflow-wrap: anywhere; + text-overflow: ellipsis; +} + +.trx-controls { + display: flex; + padding: var(--base-size-12, 12px) var(--base-size-16, 16px); + align-items: center; + gap: var(--base-size-12, 12px); + flex-wrap: wrap; +} + +.trx-search { + position: relative; + display: block; + width: min(320px, 100%); +} + +.trx-search > .octicon { + position: absolute; + top: 8px; + left: 10px; + z-index: 1; + width: 14px; + height: 14px; + color: var(--app-fg-muted); + pointer-events: none; +} + +.trx-search .form-control { + width: 100%; + padding-left: 32px; +} + +.trx-filters { + display: flex; + min-width: 0; + overflow-x: auto; + border: 1px solid var(--app-border); + border-radius: var(--app-radius); +} + +.trx-filter { + display: inline-flex; + min-height: 32px; + padding: 0 var(--base-size-8, 8px); + color: var(--app-fg-muted); + font-size: 12px; + white-space: nowrap; + background: var(--app-bg); + border: 0; + border-left: 1px solid var(--app-border-muted); + align-items: center; + gap: var(--base-size-4, 4px); + cursor: pointer; +} + +.trx-filter:first-child { + border-left: 0; +} + +.trx-filter:hover { + color: var(--app-fg); + background: var(--app-bg-muted); +} + +.trx-filter[aria-pressed="true"] { + color: var(--app-fg); + font-weight: var(--base-text-weight-semibold, 600); + background: var( + --bgColor-accent-muted, + color-mix(in srgb, var(--app-accent) 12%, transparent) + ); +} + +.trx-filter:focus-visible { + position: relative; + z-index: 1; + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.trx-limit-note { + display: flex; + padding: var(--base-size-8, 8px) var(--base-size-16, 16px); + color: var(--app-fg-muted); + font-size: 12px; + line-height: 18px; + background: var(--app-bg-muted); + border-top: 1px solid var(--app-border-muted); + gap: var(--base-size-8, 8px); +} + +.trx-limit-note .octicon { + margin-top: 1px; +} + +.trx-result-list { + border-top: 1px solid var(--app-border-muted); +} + +.trx-result { + color: var(--app-fg); + border-top: 1px solid var(--app-border-muted); +} + +.trx-result:first-child { + border-top: 0; +} + +.trx-result[hidden] { + display: none; +} + +.trx-result > summary { + display: grid; + min-width: 0; + min-height: 56px; + padding: var(--base-size-8, 8px) var(--base-size-16, 16px); + padding-left: calc( + var(--base-size-16, 16px) + var(--trx-depth, 0) * var(--base-size-12, 12px) + ); + list-style: none; + cursor: pointer; + align-items: center; + grid-template-columns: 16px 16px minmax(0, 1fr) auto auto; + gap: var(--base-size-8, 8px); +} + +.trx-result > summary::-webkit-details-marker { + display: none; +} + +.trx-result > summary:hover { + background: var(--app-bg-muted); +} + +.trx-result > summary:focus-visible { + position: relative; + z-index: 1; + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.trx-result-chevron { + color: var(--app-fg-muted); + transition: transform 80ms ease; +} + +.trx-result[open] .trx-result-chevron { + transform: rotate(90deg); +} + +.trx-result-status { + color: currentColor; +} + +.trx-result-copy { + display: flex; + min-width: 0; + flex-direction: column; +} + +.trx-result-name { + overflow: hidden; + color: var(--app-fg); + font-size: 13px; + font-weight: var(--base-text-weight-semibold, 600); + line-height: 20px; + overflow-wrap: anywhere; + text-overflow: ellipsis; +} + +.trx-result-secondary { + overflow: hidden; + color: var(--app-fg-muted); + font-size: 11px; + line-height: 16px; + overflow-wrap: anywhere; + text-overflow: ellipsis; +} + +.trx-result-duration { + color: var(--app-fg-muted); + font-size: 11px; + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.trx-outcome-badge { + min-height: 22px; + padding: 0 var(--base-size-6, 6px); + font-size: 11px; + font-weight: var(--base-text-weight-semibold, 600); +} + +.trx-result-details { + padding: var(--base-size-16, 16px); + padding-left: calc( + var(--base-size-56, 56px) + var(--trx-depth, 0) * var(--base-size-12, 12px) + ); + background: var(--app-bg-muted); + border-top: 1px solid var(--app-border-muted); +} + +.trx-result-metadata { + margin-bottom: var(--base-size-16, 16px); + grid-template-columns: repeat(3, minmax(0, 1fr)); + column-gap: var(--base-size-24, 24px); + row-gap: var(--base-size-12, 12px); +} + +.trx-output-block, +.trx-result-files, +.trx-properties { + margin-top: var(--base-size-16, 16px); +} + +.trx-output-block:first-child, +.trx-result-files:first-child, +.trx-properties:first-child { + margin-top: 0; +} + +.trx-output-block h4, +.trx-result-files h4, +.trx-properties h4 { + margin: 0 0 var(--base-size-6, 6px); + color: var(--app-fg); + font-size: 12px; + line-height: 18px; +} + +.trx-output-block pre { + max-height: 360px; + padding: var(--base-size-12, 12px); + margin: 0; + overflow: auto; + color: var(--app-fg); + background: var(--app-bg); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + font-family: var(--app-mono); + font-size: 11px; + line-height: 17px; + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.trx-truncation-note, +.trx-detail-empty { + margin: var(--base-size-6, 6px) 0 0; + color: var(--app-fg-muted); + font-size: 11px; +} + +.trx-result-files ul { + padding-left: var(--base-size-24, 24px); + margin: 0; + color: var(--app-fg-muted); + font-size: 11px; +} + +.trx-properties dl { + display: grid; + margin: 0; + gap: var(--base-size-4, 4px); +} + +.trx-properties dl > div { + display: grid; + grid-template-columns: minmax(120px, 0.3fr) minmax(0, 1fr); + gap: var(--base-size-12, 12px); +} + +.trx-properties dt { + color: var(--app-fg-muted); + font-size: 11px; + font-weight: var(--base-text-weight-semibold, 600); +} + +.trx-properties dd { + margin: 0; + font-size: 11px; + overflow-wrap: anywhere; +} + +.trx-no-results { + padding: var(--base-size-48, 48px) var(--base-size-16, 16px); + color: var(--app-fg-muted); + text-align: center; +} + +.trx-no-results > .octicon { + width: 24px; + height: 24px; + margin-bottom: var(--base-size-8, 8px); +} + +.trx-no-results h4 { + font-size: 14px; +} + +.trx-no-results p { + margin: var(--base-size-4, 4px) 0 0; + font-size: 12px; +} + +.trx-raw-source { + min-height: 420px; + border: 0; + border-radius: 0; +} + +.trx-error { + display: flex; + padding: var(--base-size-16, 16px); + color: var(--app-danger); + background: var( + --bgColor-danger-muted, + color-mix(in srgb, var(--app-danger) 10%, var(--app-bg)) + ); + border: 1px solid var(--app-danger); + border-radius: var(--app-radius); + gap: var(--base-size-12, 12px); +} + +.trx-error-icon .octicon { + width: 20px; + height: 20px; +} + +.trx-error h2 { + font-size: 16px; + line-height: 20px; +} + +.trx-error p { + margin: var(--base-size-4, 4px) 0 0; + color: var(--app-fg); + font-size: 12px; + line-height: 18px; +} + +.asciinema-preview { + display: flex; + width: 100%; + min-width: 0; + flex-direction: column; + gap: 0; +} + +.player-control-bar { + display: flex; + padding: var(--base-size-12, 12px); + background: var(--app-bg-muted); + border: 1px solid var(--app-border); + border-top: 0; + border-radius: var(--app-radius); + border-top-left-radius: 0; + border-top-right-radius: 0; + align-items: center; + gap: var(--base-size-12, 12px); + flex-wrap: wrap; +} + +.player-control-group { + display: flex; + min-width: 0; + flex-direction: column; + gap: var(--base-size-4, 4px); +} + +.player-control-label { + color: var(--app-fg-muted); + font-size: 11px; + font-weight: var(--base-text-weight-semibold, 600); + line-height: 16px; +} + +.player-segmented, +.player-stepper { + display: inline-flex; + overflow: hidden; + background: var(--app-bg); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + align-items: center; +} + +.player-segment, +.player-step { + height: 28px; + padding: 0 var(--base-size-8, 8px); + color: var(--app-fg-muted); + font-size: var(--text-body-size-small, 12px); + font-weight: 400; + line-height: 26px; + white-space: nowrap; + background: transparent; + border: 0; + border-left: 1px solid var(--app-border-muted); + cursor: pointer; +} + +.player-segment:first-child, +.player-step:first-child { + border-left: 0; +} + +.player-segment:hover, +.player-step:hover:not(:disabled) { + color: var(--app-fg); + background: var(--app-bg-inset); +} + +.player-segment[aria-pressed="true"] { + color: var(--app-fg); + font-weight: var(--base-text-weight-semibold, 600); + background: var( + --bgColor-accent-muted, + color-mix(in srgb, var(--app-accent) 12%, transparent) + ); +} + +.player-segment:focus-visible, +.player-step:focus-visible { + position: relative; + z-index: 1; + outline: 2px solid var(--focus-outlineColor, var(--app-accent)); + outline-offset: -2px; +} + +.player-step:disabled { + color: var(--app-fg-muted); + cursor: not-allowed; + opacity: 0.5; +} + +.player-step-value { + min-width: 48px; + height: 28px; + padding: 0 var(--base-size-6, 6px); + color: var(--app-fg); + font-size: var(--text-body-size-small, 12px); + font-variant-numeric: tabular-nums; + line-height: 28px; + text-align: center; + border-right: 1px solid var(--app-border-muted); + border-left: 1px solid var(--app-border-muted); +} + +.asciinema-host { + width: 100%; + min-width: 0; + overflow: auto; + background: #121314; + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.asciinema-host .ap-player { + max-width: 100%; + margin: 0 auto; +} + +.preview-empty { + display: grid; + width: 100%; + min-height: 360px; + padding: var(--base-size-32, 32px); + color: var(--app-fg-muted); + text-align: center; + place-items: center; +} + +.preview-empty-inner { + max-width: 480px; +} + +.preview-empty-inner > .octicon { + width: 32px; + height: 32px; + margin-bottom: var(--base-size-12, 12px); +} + +.cache-stats { + display: flex; + gap: var(--base-size-8, 8px); + color: var(--app-fg-muted); + align-items: center; +} + +.blankslate { + padding: var(--base-size-64, 64px) var(--base-size-24, 24px); + color: var(--app-fg-muted); + text-align: center; +} + +.blankslate .octicon { + width: 32px; + height: 32px; + margin-bottom: var(--base-size-12, 12px); +} + +.blankslate-actions { + display: flex; + margin-top: var(--base-size-16, 16px); + justify-content: center; +} + +.blankslate-actions .octicon { + width: 16px; + height: 16px; + margin-bottom: 0; +} + +.blankslate h2 { + margin: 0 0 var(--base-size-8, 8px); + color: var(--app-fg); + font-size: 18px; +} + +.blankslate p { + max-width: 560px; + margin: 0 auto; +} + +.flash { + border-radius: var(--app-radius); +} + +.loading-list { + overflow: hidden; + border: 1px solid var(--app-border); + border-radius: var(--app-radius); +} + +.skeleton-row { + display: grid; + gap: var(--base-size-12, 12px); + padding: var(--base-size-16, 16px); + border-top: 1px solid var(--app-border); + grid-template-columns: 18px minmax(0, 1fr) 90px; +} + +.skeleton-row:first-child { + border-top: 0; +} + +.skeleton { + min-height: 14px; + background: var(--app-bg-muted); + border-radius: var(--app-radius); +} + +.skeleton.short { + width: 55%; + margin-top: var(--base-size-8, 8px); +} + +@media (prefers-reduced-motion: no-preference) { + .skeleton { + animation: skeleton-pulse 1.6s ease-in-out infinite; + } +} + +@keyframes skeleton-pulse { + 50% { + opacity: 0.45; + } +} + +.toast-region { + position: fixed; + right: var(--base-size-16, 16px); + bottom: var(--base-size-16, 16px); + z-index: 60; + display: grid; + max-width: min(420px, calc(100vw - 32px)); + gap: var(--base-size-8, 8px); +} + +.toast-message { + padding: var(--base-size-12, 12px) var(--base-size-16, 16px); + color: var(--app-fg); + background: var(--app-bg); + border: 1px solid var(--app-border); + border-radius: var(--app-radius); + box-shadow: var(--shadow-resting-medium, 0 8px 24px rgb(140 149 159 / 20%)); +} + +.toast-message.error { + border-color: var(--app-danger); +} + +.mono { + font-family: var(--app-mono); +} + +.nowrap { + white-space: nowrap; +} + +.octicon { + display: inline-block; + width: 16px; + height: 16px; + background: currentColor; + mask: var(--octicon-url) center / contain no-repeat; + vertical-align: text-bottom; + flex: none; +} + +.icon-archive { --octicon-url: url("/assets/octicons/archive.svg"); } +.icon-browser { --octicon-url: url("/assets/octicons/browser.svg"); } +.icon-check { --octicon-url: url("/assets/octicons/check.svg"); } +.icon-check-circle { --octicon-url: url("/assets/octicons/check-circle.svg"); } +.icon-chevron-down { --octicon-url: url("/assets/octicons/chevron-down.svg"); } +.icon-chevron-left { --octicon-url: url("/assets/octicons/chevron-left.svg"); } +.icon-chevron-right { --octicon-url: url("/assets/octicons/chevron-right.svg"); } +.icon-clock { --octicon-url: url("/assets/octicons/clock.svg"); } +.icon-code { --octicon-url: url("/assets/octicons/code.svg"); } +.icon-comment { --octicon-url: url("/assets/octicons/comment.svg"); } +.icon-download { --octicon-url: url("/assets/octicons/download.svg"); } +.icon-eye { --octicon-url: url("/assets/octicons/eye.svg"); } +.icon-file { --octicon-url: url("/assets/octicons/file.svg"); } +.icon-file-binary { --octicon-url: url("/assets/octicons/file-binary.svg"); } +.icon-file-code { --octicon-url: url("/assets/octicons/file-code.svg"); } +.icon-file-directory { --octicon-url: url("/assets/octicons/file-directory.svg"); } +.icon-file-media { --octicon-url: url("/assets/octicons/file-media.svg"); } +.icon-file-zip { --octicon-url: url("/assets/octicons/file-zip.svg"); } +.icon-filter { --octicon-url: url("/assets/octicons/filter.svg"); } +.icon-gear { --octicon-url: url("/assets/octicons/gear.svg"); } +.icon-git-pull-request { --octicon-url: url("/assets/octicons/git-pull-request.svg"); } +.icon-kebab-horizontal { --octicon-url: url("/assets/octicons/kebab-horizontal.svg"); } +.icon-link-external { --octicon-url: url("/assets/octicons/link-external.svg"); } +.icon-mark-github { --octicon-url: url("/assets/octicons/mark-github.svg"); } +.icon-markdown { --octicon-url: url("/assets/octicons/markdown.svg"); } +.icon-package { --octicon-url: url("/assets/octicons/package.svg"); } +.icon-person { --octicon-url: url("/assets/octicons/person.svg"); } +.icon-pin { --octicon-url: url("/assets/octicons/pin.svg"); } +.icon-pin-slash { --octicon-url: url("/assets/octicons/pin-slash.svg"); } +.icon-play { --octicon-url: url("/assets/octicons/play.svg"); } +.icon-repo { --octicon-url: url("/assets/octicons/repo.svg"); } +.icon-search { --octicon-url: url("/assets/octicons/search.svg"); } +.icon-sidebar-collapse { --octicon-url: url("/assets/octicons/sidebar-collapse.svg"); } +.icon-sidebar-expand { --octicon-url: url("/assets/octicons/sidebar-expand.svg"); } +.icon-star { --octicon-url: url("/assets/octicons/star.svg"); } +.icon-star-fill { --octicon-url: url("/assets/octicons/star-fill.svg"); } +.icon-sync { --octicon-url: url("/assets/octicons/sync.svg"); } +.icon-terminal { --octicon-url: url("/assets/octicons/terminal.svg"); } +.icon-trash { --octicon-url: url("/assets/octicons/trash.svg"); } +.icon-workflow { --octicon-url: url("/assets/octicons/workflow.svg"); } +.icon-x { --octicon-url: url("/assets/octicons/x.svg"); } +.icon-x-circle { --octicon-url: url("/assets/octicons/x-circle.svg"); } + +.spin { + animation: app-spin 1s linear infinite; +} + +@keyframes app-spin { + to { + transform: rotate(360deg); + } +} + +@keyframes repository-search-spin { + from { + transform: translateY(-50%) rotate(0deg); + } + + to { + transform: translateY(-50%) rotate(360deg); + } +} + +@media (min-width: 768px) { + .breadcrumb-bar { + padding-right: 0; + padding-left: 0; + } + + .breadcrumbs { + box-sizing: border-box; + padding-right: var(--base-size-16, 16px); + padding-left: var(--base-size-16, 16px); + } + + .breadcrumb-overflow-menu { + left: var(--base-size-16, 16px); + } +} + +@media (min-width: 1600px) { + .app-header { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1280px) minmax(0, 1fr); + column-gap: 0; + } + + .app-brand { + grid-column: 1; + grid-row: 1; + justify-self: start; + } + + .repository-form { + width: min(620px, calc(100% - var(--base-size-16, 16px))); + margin-right: 0; + margin-left: var(--base-size-16, 16px); + grid-column: 2; + grid-row: 1; + justify-self: start; + } + + .header-cache-item, + .account-control { + grid-column: 3; + grid-row: 1; + justify-self: end; + } + + .header-cache-item { + margin-right: 48px; + } +} + +@media (prefers-reduced-motion: reduce) { + .spin, + .account-sources-chevron, + .artifact-download-bar { + animation: none; + transition: none; + } +} + +@media (max-width: 900px) { + .breadcrumbs.has-overflow .breadcrumb-item-collapsible { + display: none; + } + + .breadcrumbs.has-overflow .breadcrumb-overflow { + display: inline-flex; + } + + .breadcrumbs.has-overflow .breadcrumb-item-root { + max-width: min(34vw, 200px); + } + + .breadcrumbs.has-overflow + .breadcrumb-item-current + .breadcrumb-item-label.has-compact-label + .breadcrumb-label-full { + display: none; + } + + .breadcrumbs.has-overflow + .breadcrumb-item-current + .breadcrumb-item-label.has-compact-label + .breadcrumb-label-compact { + display: block; + } +} + +@media (max-width: 767px) { + .app-header { + min-height: auto; + flex-wrap: wrap; + } + + .app-brand span:last-child, + .header-cache-item #cache-label { + display: none; + } + + .repository-form { + max-width: none; + order: 3; + width: 100%; + } + + .repository-input { + min-width: 0; + } + + .account-control { + margin-left: auto; + } + + .toolbar { + flex-wrap: wrap; + } + + .pull-state-picker, + .pull-author-picker, + .pull-signal-picker { + position: static; + } + + .pull-state-panel, + .pull-author-panel { + top: calc(100% + 1px); + right: 0; + width: min(340px, 100%); + } + + .artifact-filter { + width: 100%; + max-width: none; + flex-basis: 100%; + } + + .artifact-filter-picker { + position: static; + } + + .artifact-filter-controls { + min-width: 0; + flex: 1 1 180px; + } + + .artifact-filter-picker:last-child { + min-width: 0; + flex: 1; + } + + .artifact-filter-picker:last-child .artifact-filter-trigger { + width: 100%; + max-width: none; + } + + .artifact-filter-panel { + top: calc(100% + 8px); + right: 0; + left: 0; + width: 100%; + } + + .artifact-filter-trigger { + max-width: min(220px, calc(100vw - 96px)); + } + + .breadcrumb-bar { + position: relative; + top: auto; + padding-right: var(--base-size-12, 12px); + padding-left: var(--base-size-12, 12px); + } + + .app-main { + padding: var(--base-size-16, 16px) var(--base-size-12, 12px) + var(--base-size-32, 32px); + } + + .page-header { + flex-direction: column; + } + + .page-actions { + width: 100%; + flex-wrap: wrap; + } + + .account-card-main { + align-items: flex-start; + flex-wrap: wrap; + } + + .account-access { + margin-left: 56px; + } + + .account-card-footer { + align-items: flex-start; + flex-direction: column; + } + + .player-control-bar { + align-items: stretch; + } + + .player-control-group { + flex: 1 1 100%; + } + + .player-segmented { + display: grid; + width: 100%; + grid-auto-columns: minmax(0, 1fr); + grid-auto-flow: column; + } + + .player-segment { + min-width: 0; + padding-right: var(--base-size-6, 6px); + padding-left: var(--base-size-6, 6px); + overflow: hidden; + text-overflow: ellipsis; + } + + .player-stepper { + align-self: flex-start; + } + + .Box-row { + align-items: flex-start; + } + + .Box-row-actions { + align-self: center; + } + + .explorer { + min-height: auto; + grid-template-columns: 1fr; + } + + .file-panel { + border-right: 0; + border-bottom: 1px solid var(--app-border); + } + + .file-tree-filter-menu { + right: 0; + left: auto; + } + + .file-list { + max-height: 240px; + } + + .preview-content { + min-height: 420px; + } + + .preview-frame { + min-height: 420px; + } + + .trx-run-metadata, + .trx-result-metadata { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .skeleton-row { + grid-template-columns: 18px minmax(0, 1fr); + } + + .skeleton-row > :last-child { + display: none; + } +} + +@media (max-width: 480px) { + .trx-run-heading, + .trx-section-heading { + align-items: stretch; + flex-direction: column; + gap: var(--base-size-8, 8px); + } + + .trx-run-outcome { + align-self: flex-start; + } + + .trx-metrics { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .trx-run-metadata, + .trx-result-metadata { + grid-template-columns: 1fr; + } + + .trx-controls { + align-items: stretch; + flex-direction: column; + } + + .trx-search, + .trx-filters { + width: 100%; + } + + .trx-filter { + flex: 1 0 auto; + } + + .trx-result > summary { + padding-left: var(--base-size-12, 12px); + grid-template-columns: 16px 16px minmax(0, 1fr) auto; + } + + .trx-result-duration { + display: none; + } + + .trx-outcome-badge { + grid-column: 3 / -1; + justify-self: start; + } + + .trx-result-details { + padding-left: var(--base-size-16, 16px); + } + + .trx-properties dl > div { + grid-template-columns: 1fr; + gap: 0; + } + + .pull-query-suggestion { + grid-template-columns: minmax(0, 1fr); + gap: var(--base-size-2, 2px); + } + + .pull-query-suggestion-footer { + justify-content: space-between; + } + + .artifact-transfer-row { + padding-right: var(--base-size-12, 12px); + padding-left: var(--base-size-12, 12px); + } + + .artifact-download-copy { + flex-direction: column; + gap: var(--base-size-2, 2px); + } + + .artifact-download-stats { + white-space: normal; + } + + .account-chip { + width: 36px; + padding: 0; + } + + .labels { + display: flex; + margin: var(--base-size-4, 4px) 0 0; + } + + .Box-row { + padding-right: var(--base-size-12, 12px); + padding-left: var(--base-size-12, 12px); + } + + .artifact-summary { + display: grid; + } +} + +@media (max-width: 479px) { + .breadcrumbs:not(.has-overflow) .breadcrumb-item-root { + max-width: min(30vw, 160px); + } + + .breadcrumbs:not(.has-overflow) .breadcrumb-item-collapsible { + max-width: min(32vw, 140px); + } + + .breadcrumbs.has-overflow .breadcrumb-item-root { + display: none; + } + + .breadcrumbs.has-overflow .breadcrumb-overflow::before { + display: none; + } + + .breadcrumb-overflow-menu { + width: calc(100vw - 24px); + } +} + +@media (min-width: 480px) and (max-width: 900px) { + .breadcrumb-overflow-menu .breadcrumb-overflow-option-root { + display: none; + } + + .breadcrumb-overflow-menu + .breadcrumb-overflow-option-root + + .breadcrumb-overflow-option { + border-top: 0; + } +} diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/app.js b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/app.js new file mode 100644 index 00000000..bbc2969c --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/app.js @@ -0,0 +1,5362 @@ +const view = document.getElementById("view"); +const breadcrumbs = document.getElementById("breadcrumbs"); +const repositoryForm = document.getElementById("repository-form"); +const repositoryInput = document.getElementById("repository-input"); +const repositoryCombobox = document.getElementById("repository-combobox"); +const repositoryMenuButton = document.getElementById("repository-menu-button"); +const repositoryPanel = document.getElementById("repository-panel"); +const repositoryResults = document.getElementById("repository-results"); +const accountButton = document.getElementById("account-button"); +const accountAvatar = document.getElementById("account-avatar"); +const accountFallback = document.getElementById("account-fallback"); +const cacheLabel = document.getElementById("cache-label"); +const toastRegion = document.getElementById("toast-region"); +const capabilityToken = + document.querySelector('meta[name="pr-artifact-explorer-token"]')?.content ?? ""; + +let bootstrapState = null; +let currentArtifact = null; +let currentPullContext = null; +let currentPlayer = null; +let currentPlayerIsPlaying = false; +let artifactFilters = { + context: null, + query: "", + status: "all", + run: "latest", +}; +const artifactDownloadProgress = new Map(); +const LARGE_ARTIFACT_PROGRESS_BYTES = 2 * 1024 * 1024; +const MAX_VISIBLE_ARTIFACT_FILES = 1_000; +const EXPAND_ALL_FILE_TREE_LIMIT = 200; +const PULL_PAYLOAD_CACHE_FRESH_MS = 30 * 1_000; +const PULL_PAYLOAD_CACHE_STALE_MS = 10 * 60 * 1_000; +const PULL_PAYLOAD_CACHE_LIMIT = 30; +const PULL_QUERY_QUALIFIERS = [ + { + name: "in", + description: "Search within a field", + values: [ + ["title", "Pull request titles"], + ["body", "Pull request descriptions"], + ["comments", "Pull request comments"], + ], + }, + { + name: "is", + description: "Filter by state or type", + values: [ + ["open", "Open pull requests"], + ["closed", "Closed pull requests"], + ["merged", "Merged pull requests"], + ["unmerged", "Pull requests that are not merged"], + ["draft", "Draft pull requests"], + ["pr", "Pull requests only"], + ], + }, + { + name: "author", + description: "Opened by a user", + dynamicValues: "authors", + }, + { + name: "assignee", + description: "Assigned to a user", + dynamicValues: "authors", + }, + { + name: "label", + description: "Has a label", + dynamicValues: "labels", + }, + { + name: "review", + description: "Filter by review state", + values: [ + ["none", "No reviews"], + ["required", "Review required"], + ["approved", "Approved"], + ["changes_requested", "Changes requested"], + ], + }, + { + name: "reviewed-by", + description: "Reviewed by a user", + dynamicValues: "authors", + }, + { + name: "review-requested", + description: "Review requested from a user", + dynamicValues: "authors", + }, + { + name: "draft", + description: "Filter by draft state", + values: [ + ["true", "Draft pull requests"], + ["false", "Ready for review"], + ], + }, + { + name: "status", + description: "Filter by commit status", + values: [ + ["success", "Checks succeeded"], + ["failure", "Checks failed"], + ["pending", "Checks are pending"], + ], + }, + { + name: "no", + description: "Exclude missing metadata", + values: [ + ["label", "No labels"], + ["assignee", "No assignee"], + ["milestone", "No milestone"], + ["project", "No project"], + ], + }, + { + name: "sort", + description: "Choose result order", + values: [ + ["updated-desc", "Recently updated first"], + ["updated-asc", "Least recently updated first"], + ["created-desc", "Newest first"], + ["created-asc", "Oldest first"], + ["comments-desc", "Most commented first"], + ["comments-asc", "Least commented first"], + ], + }, + { name: "base", description: "Targets a base branch" }, + { name: "head", description: "Comes from a head branch" }, + { name: "milestone", description: "Belongs to a milestone" }, + { name: "mentions", description: "Mentions a user" }, + { name: "commenter", description: "Commented on by a user" }, + { name: "involves", description: "Involves a user" }, + { name: "created", description: "Created on a date or range" }, + { name: "updated", description: "Updated on a date or range" }, + { name: "closed", description: "Closed on a date or range" }, + { name: "merged", description: "Merged on a date or range" }, +]; +const MANIFEST_FILE_NAMES = new Set([ + "bun.lock", + "bun.lockb", + "cargo.lock", + "cargo.toml", + "composer.json", + "composer.lock", + "compose.yaml", + "compose.yml", + "deno.json", + "deno.jsonc", + "directory.build.props", + "directory.build.targets", + "dockerfile", + "gemfile", + "gemfile.lock", + "global.json", + "go.mod", + "go.sum", + "mix.exs", + "mix.lock", + "npm-shrinkwrap.json", + "nuget.config", + "package-lock.json", + "package.json", + "pipfile", + "pipfile.lock", + "pnpm-lock.yaml", + "poetry.lock", + "pubspec.yaml", + "pyproject.toml", + "yarn.lock", +]); +const VENDORED_PATH_SEGMENTS = new Set([ + ".pnpm", + ".venv", + "__pycache__", + "bower_components", + "node_modules", + "site-packages", + "third-party", + "third_party", + "vendor", + "vendors", +]); +const FILE_PATH_COLLATOR = new Intl.Collator("en", { + numeric: true, + sensitivity: "base", +}); +const pullPayloadCache = new Map(); +let renderSequence = 0; +let repositorySearchTimer = null; +let repositorySearchController = null; +let repositorySearchSequence = 0; +let repositoryRemoteResults = []; +let repositorySearchState = "idle"; +let repositorySearchError = null; +let repositoryActiveIndex = -1; +let repositoryPickerQuery = ""; +let pullAuthors = []; +let pullAuthorsRepository = null; +let pullAuthorsState = "idle"; +let pullAuthorsError = null; +let pullAuthorsLoadSequence = 0; +let pullQueryLabels = []; +let pullQuerySuggestionIndex = -1; +let pullQueryPendingCommit = null; +let pullTableRefreshSequence = 0; +let pullFilterStorageWarningShown = false; +const PULL_FILTER_STORAGE_KEY = "pr-artifact-explorer:pull-filters:v1"; +const MAX_LOCAL_PULL_FILTER_REPOSITORIES = 50; +const previewThemeMedia = window.matchMedia("(prefers-color-scheme: dark)"); + +function escapeHtml(value) { + return String(value ?? "") + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +} + +function titleHtml(value) { + const source = String(value ?? ""); + let result = ""; + let cursor = 0; + + while (cursor < source.length) { + const opening = source.indexOf("`", cursor); + if (opening === -1) { + result += escapeHtml(source.slice(cursor)); + break; + } + + result += escapeHtml(source.slice(cursor, opening)); + let runLength = 1; + while (source[opening + runLength] === "`") runLength++; + const delimiter = "`".repeat(runLength); + let closing = source.indexOf(delimiter, opening + runLength); + while ( + closing !== -1 && + (source[closing - 1] === "`" || source[closing + runLength] === "`") + ) { + closing = source.indexOf(delimiter, closing + runLength); + } + + if (closing === -1) { + result += escapeHtml(source.slice(opening)); + break; + } + + let code = source.slice(opening + runLength, closing).replace(/\s+/g, " "); + if (code.startsWith(" ") && code.endsWith(" ") && code.trim()) { + code = code.slice(1, -1); + } + result += `${escapeHtml(code)}`; + cursor = closing + runLength; + } + + return result; +} + +function highlightXmlTag(token) { + const opening = token.startsWith("") ? "/>" : ">"; + const nameStart = opening.length; + const name = token.slice(nameStart).match(/^[^\s/>]+/)?.[0]; + if (!name) { + return `${escapeHtml(token)}`; + } + + const attributesStart = nameStart + name.length; + const attributesEnd = token.length - ending.length; + const attributes = token.slice(attributesStart, attributesEnd); + const matcher = /([^\s=/>]+)(\s*=\s*)("(?:[^"]*)"|'(?:[^']*)'|[^\s>]+)/g; + let highlightedAttributes = ""; + let cursor = 0; + for (const match of attributes.matchAll(matcher)) { + highlightedAttributes += escapeHtml(attributes.slice(cursor, match.index)); + highlightedAttributes += `${escapeHtml(match[1])}`; + highlightedAttributes += `${escapeHtml(match[2])}`; + highlightedAttributes += `${escapeHtml(match[3])}`; + cursor = match.index + match[0].length; + } + highlightedAttributes += escapeHtml(attributes.slice(cursor)); + + return [ + `${escapeHtml(opening)}`, + `${escapeHtml(name)}`, + highlightedAttributes, + `${escapeHtml(ending)}`, + ].join(""); +} + +function highlightXml(value) { + const source = String(value ?? ""); + let highlighted = ""; + let cursor = 0; + while (cursor < source.length) { + const opening = source.indexOf("<", cursor); + if (opening === -1) { + highlighted += escapeHtml(source.slice(cursor)); + break; + } + highlighted += escapeHtml(source.slice(cursor, opening)); + + let closing; + if (source.startsWith("", opening + 4); + closing = index === -1 ? source.length : index + 3; + } else if (source.startsWith("", opening + 9); + closing = index === -1 ? source.length : index + 3; + } else { + let quote = null; + let subsetDepth = 0; + let foundClosing = false; + closing = source.length; + for (let index = opening + 1; index < source.length; index++) { + const character = source[index]; + if (quote) { + if (character === quote) quote = null; + continue; + } + if (character === '"' || character === "'") { + quote = character; + } else if (character === "[") { + subsetDepth++; + } else if (character === "]" && subsetDepth > 0) { + subsetDepth--; + } else if (character === ">" && subsetDepth === 0) { + closing = index + 1; + foundClosing = true; + break; + } + } + if (!foundClosing) { + highlighted += escapeHtml(source.slice(opening)); + break; + } + } + + const token = source.slice(opening, closing); + if (token.startsWith("2+#d(MYk=4@OdIXW|}rMO-)F3@R659gs>{d+lIx@7ud<)3iYPNjkKU=&Nh0%EYphK6|C&i~X=Sef zCk=>CFma!osl6!yg5+3Lb^AfX`i&`x%+!0jPQ0J|x)Qp9wiA8&g}qLI?EG~Zf;fG% z*0Pd!yw92gDemHKRcZ>2NMElflH-y)7A(w!0P_^{vAJ=DB80$IyTdoduUtj=Z(9VnpCyl@lbjE3dAaie!DG0~xRqmG zZq&?%$*+bSwZkf0-W?sW^cZsyROgkyDwJ*bs@LrU1P?B-1~(^pi0!E@Z79_D;IQ;L zZS1^_wCns#@nbBeN~_WO$~)mwZVS>s`hHO*+n*>*O)aVR#cQD9kUNY7nWXK5>P*Ca zqh?dvb5`^&cL<@l18h~%{;>*N+g*ju71eI&*fP=Bd%{0sKLHVH(+#Br@vcaY4D;q# zaA$Xd=_JvO4fgR@b^D-QzrrCEz;>u8R^uKpAk)aAJaj4QwxkDEHEUn+X&()^p6_^8!a<9`{oMGq|;!zsdsK7D$%QZQL$PN`E8x`u0*6Y^*}Di8G(TP^lrVgih8jCpaF85pGTWE0znaqd9kn$BvU=ILap1 z?R86d87Z@KzySR|L5b8+3{VFF*BF_bao)Mg!SevEQ+K=SnP#HaV@#B81?7E4da@th z1a_~mUc^xHtb+hK#3bG#qENgW<;tLo!w(B8VM z&APRpNhVL9a@W@7_(Uih;jd~O_x{dDV?TxA=b*9BOnQh+d)sqxwSW^j>~pO3X-mA( znBItgP0|dxTvSo~v}0^&Xvz1Rm!>T=;qQ5FEHHq*kL@7Qb(JF)zftaVatOSue+q|@ zDVUTftY{+6g;5F^a5CyC?%uQwWZcrtf7a1}BTQOd1&jU7zoHURLT<+rB^(X_C(1A} zBm6*=rYMM_SJ!i=a#1_91deLaQ7@kDv%hR&;cpBR dpXs4wq+OD|IoLeDH^P$0K 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.btn{background-color:var(--button-default-bgColor-hover, var(--color-btn-hover-bg));border-color:var(--button-default-borderColor-hover, var(--color-btn-hover-border));transition-duration:.1s}.btn:active{background-color:var(--button-default-bgColor-active, var(--color-btn-active-bg));border-color:var(--button-default-borderColor-active, var(--color-btn-active-border));transition:none}.btn.selected,.btn[aria-selected=true]{background-color:var(--button-default-bgColor-selected, var(--color-btn-selected-bg));box-shadow:var(--shadow-inset, var(--color-primer-shadow-inset))}.btn:disabled,.btn.disabled,.btn[aria-disabled=true]{color:var(--fgColor-disabled, var(--color-primer-fg-disabled));background-color:var(--button-default-bgColor-disabled, var(--color-btn-bg));border-color:var(--button-default-borderColor-disabled, var(--color-btn-border))}.btn:disabled .octicon,.btn.disabled .octicon,.btn[aria-disabled=true] .octicon{color:var(--fgColor-disabled, var(--color-primer-fg-disabled))}.btn-primary{color:var(--button-primary-fgColor-rest, var(--color-btn-primary-text));background-color:var(--button-primary-bgColor-rest, var(--color-btn-primary-bg));border-color:var(--button-primary-borderColor-rest, var(--color-btn-primary-border));box-shadow:var(--shadow-resting-small, var(--color-btn-primary-shadow)),var(--shadow-highlight, var(--color-btn-primary-inset-shadow))}.btn-primary:hover,.btn-primary.hover,[open]>.btn-primary{background-color:var(--button-primary-bgColor-hover, var(--color-btn-primary-hover-bg));border-color:var(--button-primary-borderColor-hover, var(--color-btn-primary-hover-border))}.btn-primary:focus{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.btn-primary:focus:not(:focus-visible){outline:solid 1px rgba(0,0,0,0);box-shadow:none}.btn-primary:focus-visible{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.btn-primary:active,.btn-primary.selected,.btn-primary[aria-selected=true]{background-color:var(--button-primary-bgColor-active, var(--color-btn-primary-selected-bg));box-shadow:var(--button-primary-shadow-selected, var(--color-btn-primary-selected-shadow))}.btn-primary:disabled,.btn-primary.disabled,.btn-primary[aria-disabled=true]{color:var(--button-primary-fgColor-disabled, var(--color-btn-primary-disabled-text));background-color:var(--button-primary-bgColor-disabled, var(--color-btn-primary-disabled-bg));border-color:var(--button-primary-borderColor-disabled, var(--color-btn-primary-disabled-border))}.btn-primary:disabled .octicon,.btn-primary.disabled .octicon,.btn-primary[aria-disabled=true] .octicon{color:var(--button-primary-fgColor-disabled, var(--color-btn-primary-disabled-text))}.btn-primary .Counter{color:inherit;background-color:var(--buttonCounter-primary-bgColor-rest, var(--color-btn-primary-counter-bg))}.btn-primary .octicon{color:var(--button-primary-iconColor-rest, var(--color-btn-primary-icon))}a.btn-primary:focus{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}a.btn-primary:focus:not(:focus-visible){outline:solid 1px rgba(0,0,0,0);box-shadow:none}a.btn-primary:focus-visible{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.btn-outline{color:var(--button-outline-fgColor-rest, var(--color-btn-outline-text))}.btn-outline:hover,[open]>.btn-outline{color:var(--button-outline-fgColor-hover, var(--color-btn-outline-hover-text));background-color:var(--button-outline-bgColor-hover, var(--color-btn-outline-hover-bg));border-color:var(--button-outline-borderColor-hover, var(--color-btn-outline-hover-border));box-shadow:var(--shadow-resting-small, var(--color-btn-outline-hover-shadow)),var(--shadow-highlight, var(--color-btn-outline-hover-inset-shadow))}.btn-outline:hover .Counter,[open]>.btn-outline .Counter{background-color:var(--buttonCounter-outline-bgColor-hover, var(--color-btn-outline-hover-counter-bg))}.btn-outline:hover .octicon,[open]>.btn-outline .octicon{color:inherit}.btn-outline:active,.btn-outline.selected,.btn-outline[aria-selected=true]{color:var(--button-outline-fgColor-active, var(--color-btn-outline-selected-text));background-color:var(--button-outline-bgColor-active, var(--color-btn-outline-selected-bg));border-color:var(--button-outline-borderColor-active, var(--color-btn-outline-selected-border));box-shadow:var(--button-outline-shadow-selected, var(--color-btn-outline-selected-shadow))}.btn-outline:active:focus,.btn-outline.selected:focus,.btn-outline[aria-selected=true]:focus{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.btn-outline:active:focus:not(:focus-visible),.btn-outline.selected:focus:not(:focus-visible),.btn-outline[aria-selected=true]:focus:not(:focus-visible){outline:solid 1px rgba(0,0,0,0);box-shadow:none}.btn-outline:active:focus-visible,.btn-outline.selected:focus-visible,.btn-outline[aria-selected=true]:focus-visible{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.btn-outline:disabled,.btn-outline.disabled,.btn-outline[aria-disabled=true]{color:var(--button-outline-fgColor-disabled, var(--color-btn-outline-disabled-text));background-color:var(--button-outline-bgColor-disabled, var(--color-btn-outline-disabled-bg));border-color:var(--button-default-borderColor-rest, var(--color-btn-border));box-shadow:none}.btn-outline:disabled .Counter,.btn-outline.disabled .Counter,.btn-outline[aria-disabled=true] .Counter{background-color:var(--buttonCounter-outline-bgColor-disabled, var(--color-btn-outline-disabled-counter-bg))}.btn-outline .Counter{color:inherit;background-color:var(--buttonCounter-outline-bgColor-rest, var(--color-btn-outline-counter-bg))}.btn-danger{color:var(--button-danger-fgColor-rest, var(--color-btn-danger-text))}.btn-danger .octicon{color:var(--button-danger-iconColor-rest, var(--color-btn-danger-icon))}.btn-danger:hover,[open]>.btn-danger{color:var(--button-danger-fgColor-hover, var(--color-btn-danger-hover-text));background-color:var(--button-danger-bgColor-hover, var(--color-btn-danger-hover-bg));border-color:var(--button-danger-borderColor-hover, var(--color-btn-danger-hover-border));box-shadow:var(--shadow-resting-small, var(--color-btn-danger-hover-shadow)),var(--shadow-highlight, var(--color-btn-danger-hover-inset-shadow))}.btn-danger:hover .Counter,[open]>.btn-danger .Counter{background-color:var(--buttonCounter-danger-bgColor-hover, var(--color-btn-danger-hover-counter-bg))}.btn-danger:hover .octicon,[open]>.btn-danger .octicon{color:var(--button-danger-iconColor-hover, var(--color-btn-danger-hover-icon))}.btn-danger:active,.btn-danger.selected,.btn-danger[aria-selected=true]{color:var(--button-danger-fgColor-active, var(--color-btn-danger-selected-text));background-color:var(--button-danger-bgColor-active, var(--color-btn-danger-selected-bg));border-color:var(--button-danger-borderColor-active, var(--color-btn-danger-selected-border));box-shadow:var(--button-danger-shadow-selected, var(--color-btn-danger-selected-shadow))}.btn-danger:disabled,.btn-danger.disabled,.btn-danger[aria-disabled=true]{color:var(--button-danger-fgColor-disabled, var(--color-btn-danger-disabled-text));background-color:var(--button-danger-bgColor-disabled, var(--color-btn-danger-disabled-bg));border-color:var(--button-default-borderColor-rest, var(--color-btn-border));box-shadow:none}.btn-danger:disabled .Counter,.btn-danger.disabled .Counter,.btn-danger[aria-disabled=true] .Counter{background-color:var(--buttonCounter-danger-bgColor-disabled, var(--color-btn-danger-disabled-counter-bg))}.btn-danger:disabled .octicon,.btn-danger.disabled .octicon,.btn-danger[aria-disabled=true] .octicon{color:var(--button-danger-fgColor-disabled, var(--color-btn-danger-disabled-text))}.btn-danger .Counter{color:inherit;background-color:var(--buttonCounter-danger-bgColor-rest, var(--color-btn-danger-counter-bg))}.btn-sm{padding:3px 12px;font-size:12px;line-height:20px}.btn-sm .octicon{vertical-align:text-top}.btn-large{padding:.75em 1.5em;font-size:inherit;line-height:1.5;border-radius:.5em}.btn-block{display:block;width:100%;text-align:center}.BtnGroup{display:inline-block;vertical-align:middle}.BtnGroup::before{display:table;content:""}.BtnGroup::after{display:table;clear:both;content:""}.BtnGroup+.BtnGroup,.BtnGroup+.btn{margin-left:var(--base-size-4)}.BtnGroup-item{position:relative;float:left;border-right-width:0;border-radius:0}.BtnGroup-item:first-child{border-top-left-radius:6px;border-bottom-left-radius:6px}.BtnGroup-item:last-child{border-right-width:1px;border-top-right-radius:6px;border-bottom-right-radius:6px}.BtnGroup-item.selected,.BtnGroup-item[aria-selected=true],.BtnGroup-item:focus,.BtnGroup-item:active,.BtnGroup-item:hover{border-right-width:1px}.BtnGroup-item.selected+.BtnGroup-item,.BtnGroup-item.selected+.BtnGroup-parent .BtnGroup-item,.BtnGroup-item[aria-selected=true]+.BtnGroup-item,.BtnGroup-item[aria-selected=true]+.BtnGroup-parent .BtnGroup-item,.BtnGroup-item:focus+.BtnGroup-item,.BtnGroup-item:focus+.BtnGroup-parent .BtnGroup-item,.BtnGroup-item:active+.BtnGroup-item,.BtnGroup-item:active+.BtnGroup-parent .BtnGroup-item,.BtnGroup-item:hover+.BtnGroup-item,.BtnGroup-item:hover+.BtnGroup-parent .BtnGroup-item{border-left-width:0}.BtnGroup-parent{float:left}.BtnGroup-parent:first-child .BtnGroup-item{border-top-left-radius:6px;border-bottom-left-radius:6px}.BtnGroup-parent:last-child .BtnGroup-item{border-right-width:1px;border-top-right-radius:6px;border-bottom-right-radius:6px}.BtnGroup-parent .BtnGroup-item{border-right-width:0;border-radius:0}.BtnGroup-parent.selected .BtnGroup-item,.BtnGroup-parent[aria-selected=true] .BtnGroup-item,.BtnGroup-parent:focus .BtnGroup-item,.BtnGroup-parent:active .BtnGroup-item,.BtnGroup-parent:hover .BtnGroup-item{border-right-width:1px}.BtnGroup-parent.selected+.BtnGroup-item,.BtnGroup-parent.selected+.BtnGroup-parent .BtnGroup-item,.BtnGroup-parent[aria-selected=true]+.BtnGroup-item,.BtnGroup-parent[aria-selected=true]+.BtnGroup-parent .BtnGroup-item,.BtnGroup-parent:focus+.BtnGroup-item,.BtnGroup-parent:focus+.BtnGroup-parent .BtnGroup-item,.BtnGroup-parent:active+.BtnGroup-item,.BtnGroup-parent:active+.BtnGroup-parent .BtnGroup-item,.BtnGroup-parent:hover+.BtnGroup-item,.BtnGroup-parent:hover+.BtnGroup-parent .BtnGroup-item{border-left-width:0}.BtnGroup-item:focus,.BtnGroup-item:active,.BtnGroup-parent:focus,.BtnGroup-parent:active{z-index:1}.btn-link{display:inline-block;padding:0;font-size:inherit;color:var(--fgColor-accent, var(--color-accent-fg));text-decoration:none;white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none;background-color:rgba(0,0,0,0);border:0;appearance:none}.btn-link:hover{text-decoration:underline}.btn-link:disabled,.btn-link:disabled:hover,.btn-link[aria-disabled=true],.btn-link[aria-disabled=true]:hover{color:var(--fgColor-disabled, var(--color-primer-fg-disabled));cursor:default}.btn-link:not(.dropdown-item):focus,.btn-link:not(.dropdown-item):focus-visible{border-radius:6px;outline-offset:0}.btn-invisible{color:var(--fgColor-accent, var(--color-accent-fg));background-color:rgba(0,0,0,0);border:0;border-radius:6px;box-shadow:none}.btn-invisible:hover,.btn-invisible.zeroclipboard-is-hover{color:var(--fgColor-accent, var(--color-accent-fg));background-color:var(--button-default-bgColor-hover, var(--color-btn-hover-bg));outline:none;box-shadow:none}.btn-invisible:active,.btn-invisible.selected,.btn-invisible[aria-selected=true],.btn-invisible.zeroclipboard-is-active{color:var(--fgColor-accent, var(--color-accent-fg));background:none;border-color:var(--button-default-borderColor-active, var(--color-btn-active-border));outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:none}.btn-invisible:active .btn-invisible.zeroclipboard-is-active{background-color:var(--button-default-bgColor-selected, var(--color-btn-selected-bg))}.btn-invisible:disabled,.btn-invisible.disabled,.btn-invisible[aria-disabled=true]{color:var(--fgColor-disabled, var(--color-primer-fg-disabled));background-color:rgba(0,0,0,0)}.btn-octicon{display:inline-block;padding:5px;margin-left:5px;line-height:1;color:var(--fgColor-muted, var(--color-fg-muted));vertical-align:middle;background:rgba(0,0,0,0);border:0;box-shadow:none}.btn-octicon:hover{color:var(--fgColor-accent, var(--color-accent-fg))}.btn-octicon:focus,.btn-octicon:focus-visible{border-radius:6px}.btn-octicon.disabled,.btn-octicon[aria-disabled=true]{color:var(--fgColor-disabled, var(--color-primer-fg-disabled));cursor:default}.btn-octicon.disabled:hover,.btn-octicon[aria-disabled=true]:hover{color:var(--fgColor-disabled, var(--color-primer-fg-disabled))}.btn-octicon-danger:hover{color:var(--fgColor-danger, var(--color-danger-fg))}.close-button{padding:0;color:var(--fgColor-muted, var(--color-fg-muted));background:rgba(0,0,0,0);border:0}.close-button:hover{color:var(--fgColor-default, var(--color-fg-default))}.close-button:active{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:none}.hidden-text-expander{display:block}.hidden-text-expander.inline{position:relative;top:-1px;display:inline-block;margin-left:5px;line-height:0}.hidden-text-expander a,.ellipsis-expander{display:inline-block;height:12px;padding:0 5px 5px;font-size:12px;font-weight:var(--base-text-weight-semibold, 600);line-height:6px;color:var(--fgColor-default, var(--color-fg-default));text-decoration:none;vertical-align:middle;background:var(--bgColor-neutral-muted, var(--color-neutral-muted));border:0;border-radius:1px}.hidden-text-expander a:hover,.ellipsis-expander:hover{text-decoration:none;background-color:var(--bgColor-accent-muted, var(--color-accent-muted))}.hidden-text-expander a:active,.ellipsis-expander:active{color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis));background-color:var(--bgColor-accent-emphasis, var(--color-accent-emphasis))}.btn-with-count{float:left;border-top-right-radius:0;border-bottom-right-radius:0}.btn-with-count:focus{z-index:1}.social-count{position:relative;float:left;padding:3px 12px;font-size:12px;font-weight:var(--base-text-weight-semibold, 600);line-height:20px;color:var(--fgColor-default, var(--color-fg-default));vertical-align:middle;background-color:var(--bgColor-default, var(--color-canvas-default));border:1px solid var(--button-default-borderColor-rest, var(--color-btn-border));border-left:0;border-top-right-radius:6px;border-bottom-right-radius:6px;box-shadow:var(--shadow-resting-small, var(--color-shadow-small)),var(--shadow-highlight, var(--color-primer-shadow-highlight))}.social-count:hover,.social-count:active{text-decoration:none}.social-count:hover{color:var(--fgColor-accent, var(--color-accent-fg));cursor:pointer}.TableObject{display:table}.TableObject-item{display:table-cell;width:1%;white-space:nowrap;vertical-align:middle}.TableObject-item--primary{width:99%}.form-control,.form-select{padding:5px 12px;font-size:14px;line-height:20px;color:var(--fgColor-default, var(--color-fg-default));vertical-align:middle;background-color:var(--bgColor-default, var(--color-canvas-default));background-repeat:no-repeat;background-position:right 8px center;border:1px solid var(--control-borderColor-rest, var(--color-border-default));border-radius:6px;box-shadow:var(--shadow-inset, var(--color-primer-shadow-inset));transition:80ms cubic-bezier(0.33, 1, 0.68, 1);transition-property:color,background-color,box-shadow,border-color}.form-control:focus,.form-select:focus{border-color:var(--focus-outlineColor, var(--color-accent-fg));outline:none;box-shadow:inset 0 0 0 1px var(--focus-outlineColor, var(--color-accent-fg))}.form-control:focus:not(:focus-visible),.form-select:focus:not(:focus-visible){border-color:rgba(0,0,0,0);border-color:var(--focus-outlineColor, var(--color-accent-fg));outline:none;box-shadow:inset 0 0 0 1px rgba(0,0,0,0)}.form-control:focus-visible,.form-select:focus-visible{border-color:var(--focus-outlineColor, var(--color-accent-fg));outline:none;box-shadow:inset 0 0 0 1px var(--focus-outlineColor, var(--color-accent-fg))}.form-control.border-0:focus,.form-control.border-0:focus-visible,.form-select.border-0:focus,.form-select.border-0:focus-visible{border:1px solid var(--borderColor-accent-emphasis, var(--color-accent-fg)) !important}.form-control[disabled],fieldset[disabled] .form-control,.form-select[disabled],fieldset[disabled] .form-select{color:var(--control-fgColor-disabled, var(--color-primer-fg-disabled));background-color:var(--control-bgColor-disabled, var(--color-input-disabled-bg));border-color:var(--control-borderColor-disabled, var(--color-border-default));-webkit-text-fill-color:var(--control-fgColor-disabled, var(--color-primer-fg-disabled));opacity:1}.form-control[disabled]::placeholder,fieldset[disabled] .form-control::placeholder,.form-select[disabled]::placeholder,fieldset[disabled] .form-select::placeholder{color:var(--control-fgColor-disabled, var(--color-primer-fg-disabled))}@supports(-webkit-touch-callout: none){.form-control,.form-select{font-size:16px}@media(min-width: 768px){.form-control,.form-select{font-size:14px}}}textarea.form-control{padding-top:var(--base-size-8);padding-bottom:var(--base-size-8);line-height:1.5}.input-contrast{background-color:var(--bgColor-default)}.input-contrast:focus{background-color:var(--bgColor-default, var(--color-canvas-default))}.input-sm{min-height:28px;padding-top:3px;padding-bottom:3px;font-size:12px;line-height:20px}.input-lg{font-size:16px}.input-block{display:block;width:100%}.input-monospace{font-family:var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace)}.input-hide-webkit-autofill::-webkit-contacts-auto-fill-button{position:absolute;right:0;display:none !important;pointer-events:none;visibility:hidden}.form-checkbox{padding-left:20px;margin:15px 0;vertical-align:middle}.form-checkbox label em.highlight{position:relative;left:calc(var(--base-size-4)*-1);padding:2px var(--base-size-4);font-style:normal;background:var(--bgColor-attention-muted, var(--color-attention-subtle));border-radius:6px}.form-checkbox input[type=checkbox],.form-checkbox input[type=radio]{float:left;margin:5px 0 0 -20px;vertical-align:middle}.form-checkbox .note{display:block;margin:0;font-size:12px;font-weight:var(--base-text-weight-normal, 400);color:var(--fgColor-muted, var(--color-fg-muted))}.form-checkbox-details{display:none}.form-checkbox-details-trigger:checked~* .form-checkbox-details,.form-checkbox-details-trigger:checked~.form-checkbox-details{display:block}.hfields{margin:15px 0}.hfields::before{display:table;content:""}.hfields::after{display:table;clear:both;content:""}.hfields .form-group{float:left;margin:0 30px 0 0}.hfields .form-group dt label,.hfields .form-group .form-group-header label{display:inline-block;margin:5px 0 0;color:var(--fgColor-muted, var(--color-fg-muted))}.hfields .form-group dt img,.hfields .form-group .form-group-header img{position:relative;top:-2px}.hfields .btn{float:left;margin:28px 25px 0 -20px}.hfields .form-select{margin-top:5px}input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{margin:0;appearance:none}.form-actions::before{display:table;content:""}.form-actions::after{display:table;clear:both;content:""}.form-actions .btn{float:right}.form-actions .btn+.btn{margin-right:5px}.form-warning{padding:var(--base-size-8) 10px;margin:10px 0;font-size:14px;color:var(--fgColor-attention, var(--color-attention-fg));background:var(--bgColor-attention-muted, var(--color-attention-subtle));border:1px solid var(--borderColor-attention-emphasis, var(--color-attention-emphasis));border-radius:6px}.form-warning p{margin:0;line-height:1.5}.form-warning a{font-weight:var(--base-text-weight-semibold, 600)}.form-select{display:inline-block;max-width:100%;height:32px;padding-right:var(--base-size-24);background-color:var(--bgColor-default, var(--color-canvas-default));background-image:url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzU4NjA2OSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNC40MjcgOS40MjdsMy4zOTYgMy4zOTZhLjI1MS4yNTEgMCAwMC4zNTQgMGwzLjM5Ni0zLjM5NkEuMjUuMjUgMCAwMDExLjM5NiA5SDQuNjA0YS4yNS4yNSAwIDAwLS4xNzcuNDI3ek00LjQyMyA2LjQ3TDcuODIgMy4wNzJhLjI1LjI1IDAgMDEuMzU0IDBMMTEuNTcgNi40N2EuMjUuMjUgMCAwMS0uMTc3LjQyN0g0LjZhLjI1LjI1IDAgMDEtLjE3Ny0uNDI3eiIgLz48L3N2Zz4=");background-repeat:no-repeat;background-position:right 4px center;background-size:16px;appearance:none}.form-select::-ms-expand{opacity:0}.form-select[multiple]{height:auto}[data-color-mode=light][data-light-theme*=dark] .form-select,[data-color-mode=dark][data-dark-theme*=dark] .form-select{background-image:url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzZlNzY4MSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNC40MjcgOS40MjdsMy4zOTYgMy4zOTZhLjI1MS4yNTEgMCAwMC4zNTQgMGwzLjM5Ni0zLjM5NkEuMjUuMjUgMCAwMDExLjM5NiA5SDQuNjA0YS4yNS4yNSAwIDAwLS4xNzcuNDI3ek00LjQyMyA2LjQ3TDcuODIgMy4wNzJhLjI1LjI1IDAgMDEuMzU0IDBMMTEuNTcgNi40N2EuMjUuMjUgMCAwMS0uMTc3LjQyN0g0LjZhLjI1LjI1IDAgMDEtLjE3Ny0uNDI3eiIgLz48L3N2Zz4=")}@media(prefers-color-scheme: light){[data-color-mode=auto][data-light-theme*=dark] .form-select{background-image:url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzZlNzY4MSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNC40MjcgOS40MjdsMy4zOTYgMy4zOTZhLjI1MS4yNTEgMCAwMC4zNTQgMGwzLjM5Ni0zLjM5NkEuMjUuMjUgMCAwMDExLjM5NiA5SDQuNjA0YS4yNS4yNSAwIDAwLS4xNzcuNDI3ek00LjQyMyA2LjQ3TDcuODIgMy4wNzJhLjI1LjI1IDAgMDEuMzU0IDBMMTEuNTcgNi40N2EuMjUuMjUgMCAwMS0uMTc3LjQyN0g0LjZhLjI1LjI1IDAgMDEtLjE3Ny0uNDI3eiIgLz48L3N2Zz4=")}}@media(prefers-color-scheme: dark){[data-color-mode=auto][data-dark-theme*=dark] .form-select{background-image:url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzZlNzY4MSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNC40MjcgOS40MjdsMy4zOTYgMy4zOTZhLjI1MS4yNTEgMCAwMC4zNTQgMGwzLjM5Ni0zLjM5NkEuMjUuMjUgMCAwMDExLjM5NiA5SDQuNjA0YS4yNS4yNSAwIDAwLS4xNzcuNDI3ek00LjQyMyA2LjQ3TDcuODIgMy4wNzJhLjI1LjI1IDAgMDEuMzU0IDBMMTEuNTcgNi40N2EuMjUuMjUgMCAwMS0uMTc3LjQyN0g0LjZhLjI1LjI1IDAgMDEtLjE3Ny0uNDI3eiIgLz48L3N2Zz4=")}}.select-sm{height:28px;padding-top:3px;padding-bottom:3px;font-size:12px}.select-sm[multiple]{height:auto;min-height:0}.form-group{margin:15px 0}.form-group .form-control.autocomplete-embedded-icon-wrap:focus-within{background-color:var(--bgColor-default, var(--color-canvas-default))}.form-group .form-control{width:440px;max-width:100%;margin-right:5px;background-color:var(--bgColor-muted, var(--color-canvas-inset))}.form-group .form-control:focus{background-color:var(--bgColor-default, var(--color-canvas-default))}.form-group .form-control.shorter{width:130px}.form-group .form-control.short{width:250px}.form-group .form-control.input-block,.form-group .form-control.long{width:100%}.form-group textarea.form-control{width:100%;height:200px;min-height:200px}.form-group textarea.form-control.short{height:50px;min-height:50px}.form-group dt,.form-group .form-group-header{margin:0 0 6px}.form-group label{position:static}.form-group.flattened dt,.form-group.flattened .form-group-header{float:left;margin:0;line-height:32px}.form-group.flattened dd,.form-group.flattened .form-group-body{line-height:32px}.form-group dd h4,.form-group .form-group-body h4{margin:var(--base-size-4) 0 0}.form-group dd h4.is-error,.form-group .form-group-body h4.is-error{color:var(--fgColor-danger, var(--color-danger-fg))}.form-group dd h4.is-success,.form-group .form-group-body h4.is-success{color:var(--fgColor-success, var(--color-success-fg))}.form-group dd h4+.note,.form-group .form-group-body h4+.note{margin-top:0}.form-group.required dt label::after,.form-group.required .form-group-header label::after{padding-left:5px;color:var(--fgColor-danger, var(--color-danger-fg));content:"*"}.form-group .success,.form-group .error,.form-group .indicator{display:none;font-size:12px;font-weight:var(--base-text-weight-semibold, 600)}.form-group.loading{opacity:.5}.form-group.loading .indicator{display:inline}.form-group.loading .spinner{display:inline-block;vertical-align:middle}.form-group.successful .success{display:inline;color:var(--fgColor-success, var(--color-success-fg))}.form-group.successed .success,.form-group.successed .warning,.form-group.successed .error,.form-group.warn .success,.form-group.warn .warning,.form-group.warn .error,.form-group.errored .success,.form-group.errored .warning,.form-group.errored .error{position:absolute;z-index:10;display:block;max-width:450px;padding:var(--base-size-4) var(--base-size-8);margin:var(--base-size-8) 0 0;font-size:12px;font-weight:var(--base-text-weight-normal, 400);border-style:solid;border-width:1px;border-radius:6px}.form-group.successed .success::after,.form-group.successed .success::before,.form-group.successed .warning::after,.form-group.successed .warning::before,.form-group.successed .error::after,.form-group.successed .error::before,.form-group.warn .success::after,.form-group.warn .success::before,.form-group.warn .warning::after,.form-group.warn .warning::before,.form-group.warn .error::after,.form-group.warn .error::before,.form-group.errored .success::after,.form-group.errored .success::before,.form-group.errored .warning::after,.form-group.errored .warning::before,.form-group.errored .error::after,.form-group.errored .error::before{position:absolute;bottom:100%;left:10px;z-index:15;width:0;height:0;pointer-events:none;content:" ";border:solid rgba(0,0,0,0)}.form-group.successed .success::after,.form-group.successed .warning::after,.form-group.successed .error::after,.form-group.warn .success::after,.form-group.warn .warning::after,.form-group.warn .error::after,.form-group.errored .success::after,.form-group.errored .warning::after,.form-group.errored .error::after{border-width:5px}.form-group.successed .success::before,.form-group.successed .warning::before,.form-group.successed .error::before,.form-group.warn .success::before,.form-group.warn .warning::before,.form-group.warn .error::before,.form-group.errored .success::before,.form-group.errored .warning::before,.form-group.errored .error::before{margin-left:-1px;border-width:6px}.form-group.successed .success{color:var(--fgColor-default, var(--color-fg-default));background-color:var(--bgColor-default, var(--color-canvas-default));background-image:linear-gradient(var(--bgColor-success-muted, var(--bgColor-success-muted, var(--color-success-subtle))), var(--color-success-subtle));border-color:var(--borderColor-success-muted, var(--color-success-muted))}.form-group.successed .success::after{border-bottom-color:var(--borderColor-success-muted, var(--color-success-muted))}.form-group.successed .success::before{border-bottom-color:var(--borderColor-success-muted, var(--color-success-muted))}.form-group.warn .form-control:not(:focus,:focus-visible){border-color:var(--borderColor-attention-emphasis, var(--color-attention-emphasis))}.form-group.warn .warning{color:var(--fgColor-default, var(--color-fg-default));background-color:var(--bgColor-default, var(--color-canvas-default));background-image:linear-gradient(var(--bgColor-attention-muted, var(--bgColor-attention-muted, var(--color-attention-subtle))), var(--color-attention-subtle));border-color:var(--borderColor-attention-muted, var(--color-attention-muted))}.form-group.warn .warning::after{border-bottom-color:var(--borderColor-attention-muted, var(--color-attention-muted))}.form-group.warn .warning::before{border-bottom-color:var(--borderColor-attention-muted, var(--color-attention-muted))}.form-group.errored .form-control:not(:focus,:focus-visible){border-color:var(--borderColor-danger-emphasis, var(--color-danger-emphasis))}.form-group.errored label{color:var(--fgColor-danger, var(--color-danger-fg))}.form-group.errored .error{color:var(--fgColor-default, var(--color-fg-default));background-color:var(--bgColor-default, var(--color-canvas-default));background-image:linear-gradient(var(--bgColor-danger-muted, var(--bgColor-danger-muted, var(--color-danger-subtle))), var(--color-danger-subtle));border-color:var(--borderColor-danger-muted, var(--color-danger-muted))}.form-group.errored .error::after{border-bottom-color:var(--borderColor-danger-muted, var(--color-danger-muted))}.form-group.errored .error::before{border-bottom-color:var(--borderColor-danger-muted, var(--color-danger-muted))}.note{min-height:17px;margin:var(--base-size-4) 0 2px;font-size:12px;color:var(--fgColor-muted, var(--color-fg-muted))}.note .spinner{margin-right:3px;vertical-align:middle}.input-group{display:table}.input-group .form-control{position:relative;width:100%}.input-group .form-control:focus{z-index:2}.input-group .form-control+.btn{margin-left:0}.input-group.inline{display:inline-table}.input-group:focus-within button{outline-offset:0}.input-group .form-control.autocomplete-embedded-icon-wrap{display:inline-flex;padding:calc(var(--base-size-4)*1.25) var(--base-size-8)}.input-group .form-control,.input-group-button{display:table-cell}.input-group-button{width:1%;vertical-align:middle}.input-group-button--autocomplete-embedded-icon{vertical-align:bottom}.input-group .form-control:first-child,.input-group-button:first-child .btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group .form-control:first-child,.input-group-button:first-child .btn:not(.btn-primary){border-color:var(--control-borderColor-rest, var(--color-border-default))}.input-group-button:first-child .btn{margin-right:-1px}.input-group .form-control:last-child,.input-group-button:last-child .btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group .form-control:last-child,.input-group-button:last-child .btn:not(.btn-primary){border-color:var(--control-borderColor-rest, var(--color-border-default))}.input-group-button:last-child .btn{margin-left:-1px}.radio-group::before{display:table;content:""}.radio-group::after{display:table;clear:both;content:""}.radio-label{float:left;padding:6px var(--base-size-16) 6px calc(var(--base-size-16) + 12px + var(--base-size-8));margin-left:-1px;font-size:14px;line-height:20px;color:var(--fgColor-default, var(--color-fg-default));cursor:pointer;border:1px solid var(--borderColor-default, var(--color-border-default))}:checked+.radio-label{position:relative;z-index:1;border-color:var(--borderColor-accent-emphasis, var(--color-accent-emphasis))}.radio-label:first-of-type{margin-left:0;border-top-left-radius:6px;border-bottom-left-radius:6px}.radio-label:last-of-type{border-top-right-radius:6px;border-bottom-right-radius:6px}.radio-label .octicon{margin-left:var(--base-size-4);color:var(--fgColor-muted, var(--color-fg-subtle))}.radio-input{z-index:3;float:left;margin:10px calc(var(--base-size-32)*-1) 0 var(--base-size-16)}.radio-input:disabled{position:relative}.radio-input:disabled+.radio-label{color:var(--fgColor-disabled, var(--color-primer-fg-disabled));cursor:default;background-color:var(--bgColor-neutral-muted, var(--color-neutral-subtle))}.radio-input:disabled+.radio-label .octicon{color:inherit}.AppFrame .AppFrame-a11yNav{position:absolute;z-index:1000;display:flex;width:100%;padding:var(--base-size-16, 16px);background:var(--bgColor-inset, var(--color-canvas-inset));padding-block-end:calc(var(--base-size-16, 16px) - var(--borderWidth-thin, 1px));isolation:isolate;align-items:center;gap:var(--base-size-8, 8px)}.AppFrame .AppFrame-a11yNav:not(:focus-within){width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(1px, 1px, 1px, 1px);border:0}.AppFrame .AppFrame-a11yNav:focus-within{top:0;left:0}@media(max-width: 767.98px){.AppFrame .AppFrame-a11yNav:focus-within{justify-content:center}}.AppFrame .AppFrame-a11yLink{transition:none}.AppFrame .AppFrame-a11yLink:not(:focus){display:block;width:var(--base-size-8, 8px);height:var(--base-size-8, 8px);overflow:hidden;text-indent:var(--base-size-128, 128px);pointer-events:none;background:var(--borderColor-default, var(--color-border-default));border-radius:var(--borderRadius-full, 100vh)}.AppFrame .AppFrame-a11yLink:focus{z-index:20;display:grid;width:auto;height:auto;min-height:var(--control-medium-size, 32px);padding:0 var(--control-medium-paddingInline-spacious, 16px);overflow:auto;color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis));background:var(--bgColor-accent-emphasis, var(--color-accent-emphasis));border-radius:var(--borderRadius-full, 100vh);align-items:center}@media(pointer: coarse){.AppFrame .AppFrame-a11yLink:focus::after{position:absolute;top:50%;left:50%;width:100%;height:100%;min-height:var(--control-minTarget-coarse, 44px);content:"";transform:translateX(-50%) translateY(-50%)}}@media(prefers-reduced-motion: no-preference){.AppFrame .AppFrame-a11yLink:focus{animation:AppFrame-a11yLink-focus 200ms ease-out}}@keyframes AppFrame-a11yLink-focus{0%{color:var(--fgColor-accent, var(--color-accent-emphasis));transform:scale(0.3, 0.25)}50%{color:var(--fgColor-accent, var(--color-accent-emphasis));transform:scale(1, 1)}55%{color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}100%{transform:scaleX(1)}}.AppFrame .AppFrame-main{display:flex;min-height:100vh;flex-direction:column}@supports(height: 100dvh){.AppFrame .AppFrame-main{min-height:100dvh}}.AppFrame .AppFrame-header-wrapper{position:relative;height:min-content;overflow:visible}.AppFrame .AppFrame-header-wrapper .AppFrame-header{position:sticky;top:0;z-index:1}.AppFrame .AppFrame-header{flex:0 0 auto}.AppFrame .AppFrame-subheader{flex:0 0 auto}.AppFrame .AppFrame-body{flex:1 0;height:100%}.AppFrame .AppFrame-footer{flex:0 0 auto}.container-sm{max-width:544px;margin-right:auto;margin-left:auto}.container-md{max-width:768px;margin-right:auto;margin-left:auto}.container-lg{max-width:1012px;margin-right:auto;margin-left:auto}.container-xl{max-width:1280px;margin-right:auto;margin-left:auto}.col-1{width:8.33333333%}.col-2{width:16.66666666%}.col-3{width:24.99999999%}.col-4{width:33.33333332%}.col-5{width:41.66666665%}.col-6{width:49.99999998%}.col-7{width:58.33333331%}.col-8{width:66.66666664%}.col-9{width:74.99999997%}.col-10{width:83.3333333%}.col-11{width:91.66666663%}.col-12{width:100%}@media(min-width: 544px){.col-sm-1{width:8.33333333%}.col-sm-2{width:16.66666666%}.col-sm-3{width:24.99999999%}.col-sm-4{width:33.33333332%}.col-sm-5{width:41.66666665%}.col-sm-6{width:49.99999998%}.col-sm-7{width:58.33333331%}.col-sm-8{width:66.66666664%}.col-sm-9{width:74.99999997%}.col-sm-10{width:83.3333333%}.col-sm-11{width:91.66666663%}.col-sm-12{width:100%}}@media(min-width: 768px){.col-md-1{width:8.33333333%}.col-md-2{width:16.66666666%}.col-md-3{width:24.99999999%}.col-md-4{width:33.33333332%}.col-md-5{width:41.66666665%}.col-md-6{width:49.99999998%}.col-md-7{width:58.33333331%}.col-md-8{width:66.66666664%}.col-md-9{width:74.99999997%}.col-md-10{width:83.3333333%}.col-md-11{width:91.66666663%}.col-md-12{width:100%}}@media(min-width: 1012px){.col-lg-1{width:8.33333333%}.col-lg-2{width:16.66666666%}.col-lg-3{width:24.99999999%}.col-lg-4{width:33.33333332%}.col-lg-5{width:41.66666665%}.col-lg-6{width:49.99999998%}.col-lg-7{width:58.33333331%}.col-lg-8{width:66.66666664%}.col-lg-9{width:74.99999997%}.col-lg-10{width:83.3333333%}.col-lg-11{width:91.66666663%}.col-lg-12{width:100%}}@media(min-width: 1280px){.col-xl-1{width:8.33333333%}.col-xl-2{width:16.66666666%}.col-xl-3{width:24.99999999%}.col-xl-4{width:33.33333332%}.col-xl-5{width:41.66666665%}.col-xl-6{width:49.99999998%}.col-xl-7{width:58.33333331%}.col-xl-8{width:66.66666664%}.col-xl-9{width:74.99999997%}.col-xl-10{width:83.3333333%}.col-xl-11{width:91.66666663%}.col-xl-12{width:100%}}.gutter{margin-right:calc(var(--base-size-16)*-1);margin-left:calc(var(--base-size-16)*-1)}.gutter>[class*=col-]{padding-right:var(--base-size-16) !important;padding-left:var(--base-size-16) !important}.gutter-condensed{margin-right:calc(var(--base-size-8)*-1);margin-left:calc(var(--base-size-8)*-1)}.gutter-condensed>[class*=col-]{padding-right:var(--base-size-8) !important;padding-left:var(--base-size-8) !important}.gutter-spacious{margin-right:calc(var(--base-size-24)*-1);margin-left:calc(var(--base-size-24)*-1)}.gutter-spacious>[class*=col-]{padding-right:var(--base-size-24) !important;padding-left:var(--base-size-24) !important}@media(min-width: 544px){.gutter-sm{margin-right:calc(var(--base-size-16)*-1);margin-left:calc(var(--base-size-16)*-1)}.gutter-sm>[class*=col-]{padding-right:var(--base-size-16) !important;padding-left:var(--base-size-16) !important}.gutter-sm-condensed{margin-right:calc(var(--base-size-8)*-1);margin-left:calc(var(--base-size-8)*-1)}.gutter-sm-condensed>[class*=col-]{padding-right:var(--base-size-8) !important;padding-left:var(--base-size-8) !important}.gutter-sm-spacious{margin-right:calc(var(--base-size-24)*-1);margin-left:calc(var(--base-size-24)*-1)}.gutter-sm-spacious>[class*=col-]{padding-right:var(--base-size-24) !important;padding-left:var(--base-size-24) !important}}@media(min-width: 768px){.gutter-md{margin-right:calc(var(--base-size-16)*-1);margin-left:calc(var(--base-size-16)*-1)}.gutter-md>[class*=col-]{padding-right:var(--base-size-16) !important;padding-left:var(--base-size-16) !important}.gutter-md-condensed{margin-right:calc(var(--base-size-8)*-1);margin-left:calc(var(--base-size-8)*-1)}.gutter-md-condensed>[class*=col-]{padding-right:var(--base-size-8) !important;padding-left:var(--base-size-8) !important}.gutter-md-spacious{margin-right:calc(var(--base-size-24)*-1);margin-left:calc(var(--base-size-24)*-1)}.gutter-md-spacious>[class*=col-]{padding-right:var(--base-size-24) !important;padding-left:var(--base-size-24) !important}}@media(min-width: 1012px){.gutter-lg{margin-right:calc(var(--base-size-16)*-1);margin-left:calc(var(--base-size-16)*-1)}.gutter-lg>[class*=col-]{padding-right:var(--base-size-16) !important;padding-left:var(--base-size-16) !important}.gutter-lg-condensed{margin-right:calc(var(--base-size-8)*-1);margin-left:calc(var(--base-size-8)*-1)}.gutter-lg-condensed>[class*=col-]{padding-right:var(--base-size-8) !important;padding-left:var(--base-size-8) !important}.gutter-lg-spacious{margin-right:calc(var(--base-size-24)*-1);margin-left:calc(var(--base-size-24)*-1)}.gutter-lg-spacious>[class*=col-]{padding-right:var(--base-size-24) !important;padding-left:var(--base-size-24) !important}}@media(min-width: 1280px){.gutter-xl{margin-right:calc(var(--base-size-16)*-1);margin-left:calc(var(--base-size-16)*-1)}.gutter-xl>[class*=col-]{padding-right:var(--base-size-16) !important;padding-left:var(--base-size-16) !important}.gutter-xl-condensed{margin-right:calc(var(--base-size-8)*-1);margin-left:calc(var(--base-size-8)*-1)}.gutter-xl-condensed>[class*=col-]{padding-right:var(--base-size-8) !important;padding-left:var(--base-size-8) !important}.gutter-xl-spacious{margin-right:calc(var(--base-size-24)*-1);margin-left:calc(var(--base-size-24)*-1)}.gutter-xl-spacious>[class*=col-]{padding-right:var(--base-size-24) !important;padding-left:var(--base-size-24) !important}}.offset-1{margin-left:8.33333333% !important}.offset-2{margin-left:16.66666666% !important}.offset-3{margin-left:24.99999999% !important}.offset-4{margin-left:33.33333332% !important}.offset-5{margin-left:41.66666665% !important}.offset-6{margin-left:49.99999998% !important}.offset-7{margin-left:58.33333331% !important}.offset-8{margin-left:66.66666664% !important}.offset-9{margin-left:74.99999997% !important}.offset-10{margin-left:83.3333333% !important}.offset-11{margin-left:91.66666663% !important}@media(min-width: 544px){.offset-sm-1{margin-left:8.33333333% !important}.offset-sm-2{margin-left:16.66666666% !important}.offset-sm-3{margin-left:24.99999999% !important}.offset-sm-4{margin-left:33.33333332% !important}.offset-sm-5{margin-left:41.66666665% !important}.offset-sm-6{margin-left:49.99999998% !important}.offset-sm-7{margin-left:58.33333331% !important}.offset-sm-8{margin-left:66.66666664% !important}.offset-sm-9{margin-left:74.99999997% !important}.offset-sm-10{margin-left:83.3333333% !important}.offset-sm-11{margin-left:91.66666663% !important}}@media(min-width: 768px){.offset-md-1{margin-left:8.33333333% !important}.offset-md-2{margin-left:16.66666666% !important}.offset-md-3{margin-left:24.99999999% !important}.offset-md-4{margin-left:33.33333332% !important}.offset-md-5{margin-left:41.66666665% !important}.offset-md-6{margin-left:49.99999998% !important}.offset-md-7{margin-left:58.33333331% !important}.offset-md-8{margin-left:66.66666664% !important}.offset-md-9{margin-left:74.99999997% !important}.offset-md-10{margin-left:83.3333333% !important}.offset-md-11{margin-left:91.66666663% !important}}@media(min-width: 1012px){.offset-lg-1{margin-left:8.33333333% !important}.offset-lg-2{margin-left:16.66666666% !important}.offset-lg-3{margin-left:24.99999999% !important}.offset-lg-4{margin-left:33.33333332% !important}.offset-lg-5{margin-left:41.66666665% !important}.offset-lg-6{margin-left:49.99999998% !important}.offset-lg-7{margin-left:58.33333331% !important}.offset-lg-8{margin-left:66.66666664% !important}.offset-lg-9{margin-left:74.99999997% !important}.offset-lg-10{margin-left:83.3333333% !important}.offset-lg-11{margin-left:91.66666663% !important}}@media(min-width: 1280px){.offset-xl-1{margin-left:8.33333333% !important}.offset-xl-2{margin-left:16.66666666% !important}.offset-xl-3{margin-left:24.99999999% !important}.offset-xl-4{margin-left:33.33333332% !important}.offset-xl-5{margin-left:41.66666665% !important}.offset-xl-6{margin-left:49.99999998% !important}.offset-xl-7{margin-left:58.33333331% !important}.offset-xl-8{margin-left:66.66666664% !important}.offset-xl-9{margin-left:74.99999997% !important}.offset-xl-10{margin-left:83.3333333% !important}.offset-xl-11{margin-left:91.66666663% !important}}:root{--Layout-pane-width: 220px;--Layout-content-width: 100%;--Layout-template-columns: 1fr var(--Layout-pane-width);--Layout-template-areas: "content pane";--Layout-column-gap: var(--base-size-16);--Layout-row-gap: var(--base-size-16);--Layout-outer-spacing-x: 0px;--Layout-outer-spacing-y: 0px;--Layout-inner-spacing-min: 0px;--Layout-inner-spacing-max: 0px}.PageLayout{display:block;margin:var(--Layout-outer-spacing-y) var(--Layout-outer-spacing-x)}@media(min-width: 768px){.PageLayout.PageLayout--panePos-start{--Layout-template-columns: var(--Layout-pane-width) minmax(0, calc(100% - var(--Layout-pane-width) - var(--Layout-column-gap)));--Layout-template-areas: "pane content"}.PageLayout.PageLayout--panePos-end{--Layout-template-columns: minmax(0, calc(100% - var(--Layout-pane-width) - var(--Layout-column-gap))) var(--Layout-pane-width);--Layout-template-areas: "content pane"}.PageLayout .PageLayout-header--hasDivider{padding-bottom:max(var(--Layout-row-gap),var(--Layout-inner-spacing-min));border-bottom:1px solid var(--borderColor-default, var(--color-border-default))}.PageLayout .PageLayout-footer--hasDivider{padding-top:max(var(--Layout-row-gap),var(--Layout-inner-spacing-min));border-top:1px solid var(--borderColor-default, var(--color-border-default))}.PageLayout.PageLayout--hasPaneDivider.PageLayout--panePos-start .PageLayout-pane{border-right:1px solid var(--borderColor-default, var(--color-border-default))}.PageLayout.PageLayout--hasPaneDivider.PageLayout--panePos-start:not(.PageLayout--columnGap-none) .PageLayout-pane{padding-right:calc(var(--Layout-column-gap) - 1px);margin-right:calc(var(--Layout-column-gap)*-1)}.PageLayout.PageLayout--hasPaneDivider.PageLayout--panePos-start:not(.PageLayout--columnGap-none) .PageLayout-content{margin-left:var(--Layout-column-gap)}.PageLayout.PageLayout--hasPaneDivider.PageLayout--panePos-end .PageLayout-pane{border-left:1px solid var(--borderColor-default, var(--color-border-default))}.PageLayout.PageLayout--hasPaneDivider.PageLayout--panePos-end:not(.PageLayout--columnGap-none) .PageLayout-pane{padding-left:calc(var(--Layout-column-gap) - 1px);margin-left:calc(var(--Layout-column-gap)*-1)}.PageLayout.PageLayout--hasPaneDivider.PageLayout--panePos-end:not(.PageLayout--columnGap-none) .PageLayout-content{margin-right:var(--Layout-column-gap)}.PageLayout .PageLayout-pane--sticky{position:sticky;top:0;max-height:100vh;overflow:auto;scrollbar-width:thin}@supports(max-height: 100dvh){.PageLayout .PageLayout-pane--sticky{max-height:100dvh}}.PageLayout [class^=PageLayout-content-centered-]{max-width:calc(var(--Layout-content-width) + var(--Layout-pane-width) + var(--Layout-column-gap));margin-right:auto;margin-left:auto}.PageLayout.PageLayout--hasPaneDivider [class^=PageLayout-content-centered-]{max-width:calc(var(--Layout-content-width) + var(--Layout-pane-width) + var(--Layout-column-gap)*2)}.PageLayout.PageLayout--panePos-start [class^=PageLayout-content-centered-]>[class^=container-]{margin-left:0}.PageLayout.PageLayout--panePos-end [class^=PageLayout-content-centered-]>[class^=container-]{margin-right:0}.PageLayout .PageLayout-content-centered-sm{--Layout-content-width: 544px}.PageLayout .PageLayout-content-centered-md{--Layout-content-width: 768px}.PageLayout .PageLayout-content-centered-lg{--Layout-content-width: 1012px}.PageLayout .PageLayout-content-centered-xl{--Layout-content-width: 1280px}}@media(min-width: 768px)and (min-width: 544px){.PageLayout{--Layout-pane-width: 220px}}@media(min-width: 768px)and (min-width: 768px){.PageLayout{--Layout-pane-width: 256px}}@media(min-width: 768px)and (min-width: 1012px){.PageLayout{--Layout-pane-width: 296px}}@media(min-width: 768px)and (min-width: 768px){.PageLayout.PageLayout--paneWidth-narrow{--Layout-pane-width: 240px}}@media(min-width: 768px)and (min-width: 1012px){.PageLayout.PageLayout--paneWidth-narrow{--Layout-pane-width: 256px}}@media(min-width: 768px)and (min-width: 1012px){.PageLayout.PageLayout--paneWidth-wide{--Layout-pane-width: 320px}}@media(min-width: 768px)and (min-width: 1280px){.PageLayout.PageLayout--paneWidth-wide{--Layout-pane-width: 336px}}@media(max-width: 767.98px){.PageLayout.PageLayout--responsive-stackRegions{--Layout-template-columns: 1fr;--Layout-template-areas: "content" "pane"}.PageLayout.PageLayout--responsive-stackRegions.PageLayout--responsive-panePos-start{--Layout-template-areas: "pane" "content"}.PageLayout.PageLayout--responsive-separateRegions{--Layout-template-columns: 1fr;--Layout-template-areas: "content"}.PageLayout.PageLayout--responsive-separateRegions.PageLayout--responsive-primary-content{--Layout-template-areas: "content"}.PageLayout.PageLayout--responsive-separateRegions.PageLayout--responsive-primary-content .PageLayout-pane{display:none}.PageLayout.PageLayout--responsive-separateRegions.PageLayout--responsive-primary-pane{--Layout-template-areas: "pane"}.PageLayout.PageLayout--responsive-separateRegions.PageLayout--responsive-primary-pane .PageLayout-content{display:none}.PageLayout .PageLayout-region--dividerNarrow-line-before{position:relative;margin-top:var(--Layout-row-gap)}.PageLayout .PageLayout-region--dividerNarrow-line-before::before{position:absolute;left:calc(var(--Layout-outer-spacing-x)*-1);display:block;width:calc(100% + var(--Layout-outer-spacing-x)*2);height:1px;content:"";background-color:var(--borderColor-default, var(--color-border-default));top:calc(-1px - var(--Layout-row-gap))}.PageLayout .PageLayout-region--dividerNarrow-line-after{position:relative;margin-bottom:var(--Layout-row-gap)}.PageLayout .PageLayout-region--dividerNarrow-line-after::after{position:absolute;left:calc(var(--Layout-outer-spacing-x)*-1);display:block;width:calc(100% + var(--Layout-outer-spacing-x)*2);height:1px;content:"";background-color:var(--borderColor-default, var(--color-border-default));bottom:calc(-1px - var(--Layout-row-gap))}.PageLayout .PageLayout-region--dividerNarrow-filled-before{position:relative;margin-top:calc(var(--base-size-8) + var(--Layout-row-gap))}.PageLayout .PageLayout-region--dividerNarrow-filled-before::after{position:absolute;bottom:calc(var(--base-size-8)*-1);left:calc(var(--Layout-outer-spacing-x)*-1);display:block;width:calc(100% + var(--Layout-outer-spacing-x)*2);height:var(--base-size-8);content:"";background-color:var(--bgColor-inset, var(--color-canvas-inset));box-shadow:inset 0 1px var(--borderColor-default, var(--color-border-default)),inset 0 -1px var(--borderColor-default, var(--color-border-default));top:calc(var(--base-size-8)*-1 - var(--Layout-row-gap))}.PageLayout .PageLayout-region--dividerNarrow-filled-after{position:relative;margin-bottom:calc(var(--base-size-8) + var(--Layout-row-gap))}.PageLayout .PageLayout-region--dividerNarrow-filled-after::before{position:absolute;bottom:calc(var(--base-size-8)*-1);left:calc(var(--Layout-outer-spacing-x)*-1);display:block;width:calc(100% + var(--Layout-outer-spacing-x)*2);height:var(--base-size-8);content:"";background-color:var(--bgColor-inset, var(--color-canvas-inset));box-shadow:inset 0 1px var(--borderColor-default, var(--color-border-default)),inset 0 -1px var(--borderColor-default, var(--color-border-default));bottom:calc(var(--base-size-8)*-1 - var(--Layout-row-gap))}}.PageLayout-wrapper{display:grid;grid:auto-flow/1fr;row-gap:var(--Layout-row-gap)}.PageLayout-columns{display:grid;column-gap:var(--Layout-column-gap);row-gap:var(--Layout-row-gap);grid-template-columns:var(--Layout-template-columns);grid-template-rows:1fr;grid-template-areas:var(--Layout-template-areas)}.PageLayout-columns .PageLayout-content{padding-right:var(--Layout-inner-spacing-max);padding-left:var(--Layout-inner-spacing-max);grid-area:content}.PageLayout-columns .PageLayout-pane{grid-area:pane}.PageLayout--outerSpacing-normal{--Layout-outer-spacing-x: var(--base-size-16);--Layout-outer-spacing-y: var(--base-size-16)}@media(min-width: 1012px){.PageLayout--outerSpacing-normal{--Layout-outer-spacing-x: var(--base-size-24);--Layout-outer-spacing-y: var(--base-size-24)}}.PageLayout--outerSpacing-condensed{--Layout-outer-spacing-x: var(--base-size-16);--Layout-outer-spacing-y: var(--base-size-16)}.PageLayout--innerSpacing-normal{--Layout-inner-spacing-min: var(--base-size-16);--Layout-inner-spacing-max: var(--base-size-16)}@media(min-width: 1012px){.PageLayout--innerSpacing-normal{--Layout-inner-spacing-max: var(--base-size-24)}}.PageLayout--innerSpacing-condensed{--Layout-inner-spacing-min: var(--base-size-16);--Layout-inner-spacing-max: var(--base-size-16)}.PageLayout--columnGap-normal{--Layout-column-gap: var(--base-size-16)}@media(min-width: 1012px){.PageLayout--columnGap-normal{--Layout-column-gap: var(--base-size-24)}}.PageLayout--columnGap-condensed{--Layout-column-gap: var(--base-size-16)}.PageLayout--columnGap-none{--Layout-column-gap: 0px}.PageLayout--rowGap-normal{--Layout-row-gap: var(--base-size-16)}@media(min-width: 1012px){.PageLayout--rowGap-normal{--Layout-row-gap: var(--base-size-24)}}.PageLayout--rowGap-none{--Layout-row-gap: 0px}.PageLayout--rowGap-condensed{--Layout-row-gap: var(--base-size-16)}.PageLayout-header,.PageLayout-content,.PageLayout-pane,.PageLayout-footer{padding:var(--Layout-inner-spacing-min)}.Stack{--Stack-gap-whenRegular: var(--stack-gap-normal, 16px);--Stack-gap-whenNarrow: var(--stack-gap-normal, 16px);--Stack-gap-whenWide: var(--Stack-gap-whenRegular);--Stack-divider-color: var(--borderColor-default, var(--color-border-default));display:flex;flex-flow:column;align-items:stretch;align-content:flex-start;gap:var(--Stack-gap-whenRegular)}@media(max-width: 767.98px){.Stack{gap:var(--Stack-gap-whenNarrow)}}@media(min-width: 1400px){.Stack{gap:var(--Stack-gap-whenWide)}}.Stack-divider{display:none;padding:0;margin:0;border:0;align-self:stretch}.Stack-item{flex:0 1 auto;min-inline-size:0}@media(max-width: 767.98px){.Stack--dir-inline-whenNarrow{flex-flow:row}.Stack--dir-block-whenNarrow{flex-flow:column}.Stack--gap-none-whenNarrow{--Stack-gap-whenNarrow: 0}.Stack--gap-condensed-whenNarrow{--Stack-gap-whenNarrow: var(--stack-gap-condensed, 8px)}.Stack--gap-normal-whenNarrow{--Stack-gap-whenNarrow: var(--stack-gap-normal, 16px)}.Stack--align-start-whenNarrow{align-items:flex-start}.Stack--align-center-whenNarrow{align-items:center}.Stack--align-end-whenNarrow{align-items:flex-end}.Stack--align-baseline-whenNarrow{align-items:baseline}.Stack--alignWrap-start-whenNarrow{align-content:flex-start}.Stack--alignWrap-center-whenNarrow{align-content:center}.Stack--alignWrap-end-whenNarrow{align-content:flex-end}.Stack--alignWrap-distribute-whenNarrow{align-content:space-between}.Stack--alignWrap-distributeEvenly-whenNarrow{align-content:space-evenly}.Stack--spread-start-whenNarrow{justify-content:flex-start}.Stack--spread-center-whenNarrow{justify-content:center}.Stack--spread-end-whenNarrow{justify-content:flex-end}.Stack--spread-distribute-whenNarrow{justify-content:space-between}.Stack--spread-distributeEvenly-whenNarrow{justify-content:space-evenly}.Stack--wrap-whenNarrow{flex-wrap:wrap}.Stack--nowrap-whenNarrow{flex-wrap:nowrap}.Stack--showDividers-whenNarrow>.Stack-divider,.Stack--showDividers-whenNarrow>.Stack-item>.Stack-divider{display:block}:not(.Stack--dir-inline-whenNarrow)>.Stack-divider,:not(.Stack--dir-inline-whenNarrow)>.Stack-item>.Stack-divider{border-block-end:var(--borderWidth-thin, var(--borderWidth-thin)) solid var(--Stack-divider-color);inline-size:auto;block-size:0}.Stack--dir-inline-whenNarrow>.Stack-divider,.Stack--dir-inline-whenNarrow>.Stack-item>.Stack-divider{border-inline-end:var(--borderWidth-thin, var(--borderWidth-thin)) solid var(--Stack-divider-color);inline-size:0;block-size:auto}.Stack-item--expand-whenNarrow{flex-grow:1}.Stack-item--keepSize-whenNarrow{flex-shrink:0}}@media(min-width: 768px){.Stack--dir-inline-whenRegular{flex-flow:row}.Stack--dir-block-whenRegular{flex-flow:column}.Stack--gap-none-whenRegular{--Stack-gap-whenRegular: 0}.Stack--gap-condensed-whenRegular{--Stack-gap-whenRegular: var(--stack-gap-condensed, 8px)}.Stack--gap-normal-whenRegular{--Stack-gap-whenRegular: var(--stack-gap-normal, 16px)}.Stack--gap-spacious-whenRegular{--Stack-gap-whenRegular: var(--stack-gap-spacious, 24px)}.Stack--align-start-whenRegular{align-items:flex-start}.Stack--align-center-whenRegular{align-items:center}.Stack--align-end-whenRegular{align-items:flex-end}.Stack--align-baseline-whenRegular{align-items:baseline}.Stack--alignWrap-start-whenRegular{align-content:flex-start}.Stack--alignWrap-center-whenRegular{align-content:center}.Stack--alignWrap-end-whenRegular{align-content:flex-end}.Stack--alignWrap-distribute-whenRegular{align-content:space-between}.Stack--alignWrap-distributeEvenly-whenRegular{align-content:space-evenly}.Stack--spread-start-whenRegular{justify-content:flex-start}.Stack--spread-center-whenRegular{justify-content:center}.Stack--spread-end-whenRegular{justify-content:flex-end}.Stack--spread-distribute-whenRegular{justify-content:space-between}.Stack--spread-distributeEvenly-whenRegular{justify-content:space-evenly}.Stack--wrap-whenRegular{flex-wrap:wrap}.Stack--nowrap-whenRegular{flex-wrap:nowrap}.Stack--showDividers-whenRegular>.Stack-divider,.Stack--showDividers-whenRegular>.Stack-item>.Stack-divider{display:block}:not(.Stack--dir-inline-whenRegular)>.Stack-divider,:not(.Stack--dir-inline-whenRegular)>.Stack-item>.Stack-divider{border-block-end:var(--borderWidth-thin, var(--borderWidth-thin)) solid var(--Stack-divider-color);inline-size:auto;block-size:0}.Stack--dir-inline-whenRegular>.Stack-divider,.Stack--dir-inline-whenRegular>.Stack-item>.Stack-divider{border-inline-end:var(--borderWidth-thin, var(--borderWidth-thin)) solid var(--Stack-divider-color);inline-size:0;block-size:auto}.Stack-item--expand-whenRegular{flex-grow:1}.Stack-item--keepSize-whenRegular{flex-shrink:0}}@media(min-width: 1400px){.Stack--dir-inline-whenWide{flex-flow:row}.Stack--dir-block-whenWide{flex-flow:column}.Stack--gap-none-whenWide{--Stack-gap-whenWide: 0}.Stack--gap-condensed-whenWide{--Stack-gap-whenWide: var(--stack-gap-condensed, 8px)}.Stack--gap-normal-whenWide{--Stack-gap-whenWide: var(--stack-gap-normal, 16px)}.Stack--gap-spacious-whenWide{--Stack-gap-whenWide: var(--stack-gap-spacious, 24px)}.Stack--align-start-whenWide{align-items:flex-start}.Stack--align-center-whenWide{align-items:center}.Stack--align-end-whenWide{align-items:flex-end}.Stack--align-baseline-whenWide{align-items:baseline}.Stack--alignWrap-start-whenWide{align-content:flex-start}.Stack--alignWrap-center-whenWide{align-content:center}.Stack--alignWrap-end-whenWide{align-content:flex-end}.Stack--alignWrap-distribute-whenWide{align-content:space-between}.Stack--alignWrap-distributeEvenly-whenWide{align-content:space-evenly}.Stack--spread-start-whenWide{justify-content:flex-start}.Stack--spread-center-whenWide{justify-content:center}.Stack--spread-end-whenWide{justify-content:flex-end}.Stack--spread-distribute-whenWide{justify-content:space-between}.Stack--spread-distributeEvenly-whenWide{justify-content:space-evenly}.Stack--wrap-whenWide{flex-wrap:wrap}.Stack--nowrap-whenWide{flex-wrap:nowrap}.Stack--showDividers-whenWide>.Stack-divider,.Stack--showDividers-whenWide>.Stack-item>.Stack-divider{display:block}:not(.Stack--dir-inline-whenWide)>.Stack-divider,:not(.Stack--dir-inline-whenWide)>.Stack-item>.Stack-divider{border-block-end:var(--borderWidth-thin, var(--borderWidth-thin)) solid var(--Stack-divider-color);inline-size:auto;block-size:0}.Stack--dir-inline-whenWide>.Stack-divider,.Stack--dir-inline-whenWide>.Stack-item>.Stack-divider{border-inline-end:var(--borderWidth-thin, var(--borderWidth-thin)) solid var(--Stack-divider-color);inline-size:0;block-size:auto}.Stack-item--expand-whenWide{flex-grow:1}.Stack-item--keepSize-whenWide{flex-shrink:0}}.filter-list{list-style-type:none}.filter-list.small .filter-item{padding:6px 12px;font-size:12px}.filter-list.pjax-active .filter-item{color:var(--fgColor-muted, var(--color-fg-muted));background-color:rgba(0,0,0,0)}.filter-list.pjax-active .filter-item.pjax-active{color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis));background-color:var(--bgColor-accent-emphasis, var(--color-accent-emphasis))}.filter-item{position:relative;display:block;padding:var(--base-size-8) var(--base-size-16);margin-bottom:var(--base-size-4);overflow:hidden;font-size:14px;color:var(--fgColor-muted, var(--color-fg-muted));text-decoration:none;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;border-radius:6px}.filter-item:hover{text-decoration:none;background-color:var(--bgColor-muted, var(--color-canvas-subtle))}.filter-item.selected,.filter-item[aria-selected=true],.filter-item[aria-current]:not([aria-current=false]){color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis));background-color:var(--bgColor-accent-emphasis, var(--color-accent-emphasis))}.filter-item.selected:focus,.filter-item[aria-selected=true]:focus,.filter-item[aria-current]:not([aria-current=false]):focus{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.filter-item.selected:focus:not(:focus-visible),.filter-item[aria-selected=true]:focus:not(:focus-visible),.filter-item[aria-current]:not([aria-current=false]):focus:not(:focus-visible){outline:solid 1px rgba(0,0,0,0);box-shadow:none}.filter-item.selected:focus-visible,.filter-item[aria-selected=true]:focus-visible,.filter-item[aria-current]:not([aria-current=false]):focus-visible{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.filter-item .count{float:right;font-weight:var(--base-text-weight-semibold, 600)}.filter-item .bar{position:absolute;top:var(--base-size-2);right:0;bottom:var(--base-size-2);z-index:-1;display:inline-block;background-color:var(--bgColor-neutral-muted, var(--color-neutral-subtle))}.SideNav{background-color:var(--bgColor-muted, var(--color-canvas-subtle))}.SideNav-item{position:relative;display:block;width:100%;padding:12px var(--base-size-16);color:var(--fgColor-default, var(--color-fg-default));text-align:left;background-color:rgba(0,0,0,0);border:0;border-top:1px solid var(--borderColor-muted, var(--color-border-muted))}.SideNav-item:first-child{border-top:0}.SideNav-item:last-child{box-shadow:0 1px 0 var(--borderColor-default, var(--color-border-default))}.SideNav-item::before{position:absolute;top:0;bottom:0;left:0;z-index:1;width:2px;pointer-events:none;content:""}.SideNav-item:hover{text-decoration:none;background-color:var(--bgColor-neutral-muted, var(--color-neutral-subtle))}.SideNav-item:active{background-color:var(--bgColor-muted, var(--color-canvas-subtle))}.SideNav-item[aria-current]:not([aria-current=false]),.SideNav-item[aria-selected=true]{background-color:var(--sideNav-bgColor-selected, var(--color-sidenav-selected-bg))}.SideNav-item[aria-current]:not([aria-current=false])::before,.SideNav-item[aria-selected=true]::before{background-color:var(--underlineNav-borderColor-active, var(--color-primer-border-active))}.SideNav-icon{width:16px;margin-right:var(--base-size-8);color:var(--fgColor-muted, var(--color-fg-muted))}.SideNav-subItem{position:relative;display:block;width:100%;padding:var(--base-size-4) 0;color:var(--fgColor-accent, var(--color-accent-fg));text-align:left;background-color:rgba(0,0,0,0);border:0}.SideNav-subItem:hover{color:var(--fgColor-default, var(--color-fg-default));text-decoration:none}.SideNav-subItem[aria-current]:not([aria-current=false]),.SideNav-subItem[aria-selected=true]{font-weight:var(--base-text-weight-medium, 500);color:var(--fgColor-default, var(--color-fg-default))}.subnav{margin-bottom:20px}.subnav::before{display:table;content:""}.subnav::after{display:table;clear:both;content:""}.subnav-bordered{padding-bottom:20px;border-bottom:1px solid var(--borderColor-muted, var(--color-border-muted))}.subnav-flush{margin-bottom:0}.subnav-item{position:relative;float:left;padding:5px var(--base-size-16);font-weight:var(--base-text-weight-medium, 500);line-height:20px;color:var(--fgColor-default, var(--color-fg-default));border:1px solid var(--control-borderColor-rest, var(--color-border-default))}.subnav-item+.subnav-item{margin-left:-1px}.subnav-item:hover,.subnav-item:focus{text-decoration:none;background-color:var(--bgColor-muted, var(--color-canvas-subtle))}.subnav-item.selected,.subnav-item[aria-selected=true],.subnav-item[aria-current]:not([aria-current=false]){z-index:2;color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis));background-color:var(--bgColor-accent-emphasis, var(--color-accent-emphasis));border-color:var(--borderColor-accent-emphasis, var(--color-accent-emphasis))}.subnav-item.selected:focus,.subnav-item[aria-selected=true]:focus,.subnav-item[aria-current]:not([aria-current=false]):focus{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.subnav-item.selected:focus:not(:focus-visible),.subnav-item[aria-selected=true]:focus:not(:focus-visible),.subnav-item[aria-current]:not([aria-current=false]):focus:not(:focus-visible){outline:solid 1px rgba(0,0,0,0);box-shadow:none}.subnav-item.selected:focus-visible,.subnav-item[aria-selected=true]:focus-visible,.subnav-item[aria-current]:not([aria-current=false]):focus-visible{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.subnav-item:first-child{border-top-left-radius:6px;border-bottom-left-radius:6px}.subnav-item:last-child{border-top-right-radius:6px;border-bottom-right-radius:6px}.subnav-search{position:relative;margin-left:12px}.subnav-search-input{width:320px;padding-left:var(--base-size-32);color:var(--fgColor-muted, var(--color-fg-muted))}.subnav-search-input-wide{width:500px}.subnav-search-icon{position:absolute;top:9px;left:var(--base-size-8);display:block;color:var(--fgColor-muted, var(--color-fg-muted));text-align:center;pointer-events:none}.subnav-search-context .btn{border-top-right-radius:0;border-bottom-right-radius:0}.subnav-search-context .btn:hover,.subnav-search-context .btn:focus,.subnav-search-context .btn:active,.subnav-search-context .btn.selected{z-index:2}.subnav-search-context+.subnav-search{margin-left:-1px}.subnav-search-context+.subnav-search .subnav-search-input{border-top-left-radius:0;border-bottom-left-radius:0}.subnav-search-context .select-menu-modal-holder{z-index:30}.subnav-search-context .select-menu-modal{width:220px}.subnav-search-context .select-menu-item-icon{color:inherit}.subnav-spacer-right{padding-right:12px}.pagination a,.pagination span,.pagination em{min-width:32px;padding:5px 10px;font-style:normal;line-height:20px;color:var(--fgColor-default, var(--color-fg-default));text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;user-select:none;border:1px solid rgba(0,0,0,0);border-radius:6px;transition:border-color .2s cubic-bezier(0.3, 0, 0.5, 1)}.pagination a:hover,.pagination a:focus,.pagination span:hover,.pagination span:focus,.pagination em:hover,.pagination em:focus{text-decoration:none;border-color:var(--borderColor-default, var(--color-border-default));transition-duration:.1s}.pagination a:active,.pagination span:active,.pagination em:active{border-color:var(--borderColor-muted, var(--color-border-muted));transition:none}.pagination .previous_page,.pagination .next_page{color:var(--fgColor-accent, var(--color-accent-fg))}.pagination .current,.pagination .current:hover,.pagination [aria-current]:not([aria-current=false]){color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis));background-color:var(--bgColor-accent-emphasis, var(--color-accent-emphasis));border-color:rgba(0,0,0,0)}.pagination .gap,.pagination .disabled,.pagination [aria-disabled=true],.pagination .gap:hover,.pagination .disabled:hover,.pagination [aria-disabled=true]:hover{color:var(--fgColor-disabled, var(--color-primer-fg-disabled));cursor:default;border-color:rgba(0,0,0,0)}@supports(clip-path: polygon(50% 0, 100% 50%, 50% 100%)){.pagination .previous_page::before,.pagination .next_page::after{display:inline-block;width:16px;height:16px;vertical-align:text-bottom;content:"";background-color:currentColor}.pagination .previous_page::before{margin-right:var(--base-size-4);clip-path:polygon(9.8px 12.8px, 8.7px 12.8px, 4.5px 8.5px, 4.5px 7.5px, 8.7px 3.2px, 9.8px 4.3px, 6.1px 8px, 9.8px 11.7px, 9.8px 12.8px)}.pagination .next_page::after{margin-left:var(--base-size-4);clip-path:polygon(6.2px 3.2px, 7.3px 3.2px, 11.5px 7.5px, 11.5px 8.5px, 7.3px 12.8px, 6.2px 11.7px, 9.9px 8px, 6.2px 4.3px, 6.2px 3.2px)}}.pagination>a,.pagination>span,.pagination>em{display:none}.pagination>:first-child,.pagination>:last-child,.pagination>.previous_page,.pagination>.next_page{display:inline-block}@media(min-width: 544px){.pagination>:nth-child(2),.pagination>:nth-last-child(2),.pagination>.current,.pagination>.gap{display:inline-block}}@media(min-width: 768px){.pagination>a,.pagination>span,.pagination>em{display:inline-block}}.paginate-container{margin-top:var(--base-size-16);margin-bottom:var(--base-size-16);text-align:center}.paginate-container .pagination{display:inline-block}.tooltipped{position:relative}.tooltipped::after{position:absolute;z-index:1000000;display:none;padding:var(--overlay-paddingBlock-condensed, 0.25rem) var(--overlay-padding-condensed, 0.5rem);font:var(--text-body-shorthand-small, normal normal 11px/1.5 var(--fontStack-sansSerif, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"));-webkit-font-smoothing:subpixel-antialiased;color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis));text-align:center;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;overflow-wrap:break-word;white-space:pre;pointer-events:none;content:attr(aria-label);background:var(--bgColor-emphasis, var(--color-neutral-emphasis-plus));border-radius:var(--borderRadius-medium);opacity:0}@keyframes tooltip-appear{from{opacity:0}to{opacity:1}}.tooltipped:hover::before,.tooltipped:hover::after,.tooltipped:active::before,.tooltipped:active::after,.tooltipped:focus::before,.tooltipped:focus::after{display:inline-block;text-decoration:none;animation-name:tooltip-appear;animation-duration:.1s;animation-fill-mode:forwards;animation-timing-function:ease-in}.tooltipped-no-delay:hover::before,.tooltipped-no-delay:hover::after,.tooltipped-no-delay:active::before,.tooltipped-no-delay:active::after,.tooltipped-no-delay:focus::before,.tooltipped-no-delay:focus::after{animation-delay:0s}.tooltipped-multiline:hover::after,.tooltipped-multiline:active::after,.tooltipped-multiline:focus::after{display:table-cell}.tooltipped-s::after,.tooltipped-se::after,.tooltipped-sw::after{top:100%;right:50%;margin-top:6px}.tooltipped-se::after{right:auto;left:50%;margin-left:calc(var(--base-size-16)*-1)}.tooltipped-sw::after{margin-right:calc(var(--base-size-16)*-1)}.tooltipped-n::after,.tooltipped-ne::after,.tooltipped-nw::after{right:50%;bottom:100%;margin-bottom:6px}.tooltipped-ne::after{right:auto;left:50%;margin-left:calc(var(--base-size-16)*-1)}.tooltipped-nw::after{margin-right:calc(var(--base-size-16)*-1)}.tooltipped-s::after,.tooltipped-n::after{transform:translateX(50%)}.tooltipped-w::after{right:100%;bottom:50%;margin-right:6px;transform:translateY(50%)}.tooltipped-e::after{bottom:50%;left:100%;margin-left:6px;transform:translateY(50%)}.tooltipped-align-right-1::after,.tooltipped-align-right-2::after{right:0;margin-right:0}.tooltipped-align-right-1::before{right:10px}.tooltipped-align-right-2::before{right:15px}.tooltipped-align-left-1::after,.tooltipped-align-left-2::after{left:0;margin-left:0}.tooltipped-align-left-1::before{left:5px}.tooltipped-align-left-2::before{left:10px}.tooltipped-multiline::after{width:max-content;max-width:var(--overlay-width-small, 20rem);overflow-wrap:break-word;white-space:pre-line;border-collapse:separate}.tooltipped-multiline.tooltipped-s::after,.tooltipped-multiline.tooltipped-n::after{right:auto;left:50%;transform:translateX(-50%)}.tooltipped-multiline.tooltipped-w::after,.tooltipped-multiline.tooltipped-e::after{right:100%}.tooltipped-sticky::before,.tooltipped-sticky::after{display:inline-block}.tooltipped-sticky.tooltipped-multiline::after{display:table-cell}.anim-fade-in{animation-name:fade-in;animation-duration:1s;animation-timing-function:ease-in-out}.anim-fade-in.fast{animation-duration:300ms}@keyframes fade-in{0%{opacity:0}100%{opacity:1}}.anim-fade-out{animation-name:fade-out;animation-duration:1s;animation-fill-mode:forwards;animation-timing-function:ease-out}.anim-fade-out.fast{animation-duration:.3s}@keyframes fade-out{0%{opacity:1}100%{opacity:0}}.anim-fade-up{opacity:0;animation-name:fade-up;animation-duration:.3s;animation-fill-mode:forwards;animation-timing-function:ease-out;animation-delay:1s}@keyframes fade-up{0%{opacity:.8;transform:translateY(100%)}100%{opacity:1;transform:translateY(0)}}.anim-fade-down{animation-name:fade-down;animation-duration:.3s;animation-fill-mode:forwards;animation-timing-function:ease-in}@keyframes fade-down{0%{opacity:1;transform:translateY(0)}100%{opacity:.5;transform:translateY(100%)}}.anim-grow-x{width:0%;animation-name:grow-x;animation-duration:.3s;animation-fill-mode:forwards;animation-timing-function:ease;animation-delay:.5s}@keyframes grow-x{to{width:100%}}.anim-shrink-x{animation-name:shrink-x;animation-duration:.3s;animation-fill-mode:forwards;animation-timing-function:ease-in-out;animation-delay:.5s}@keyframes shrink-x{to{width:0%}}.anim-scale-in{animation-name:scale-in;animation-duration:.15s;animation-timing-function:cubic-bezier(0.2, 0, 0.13, 1.5)}@keyframes scale-in{0%{opacity:0;transform:scale(0.5)}100%{opacity:1;transform:scale(1)}}.anim-pulse{animation-name:pulse;animation-duration:2s;animation-timing-function:linear;animation-iteration-count:infinite}@keyframes pulse{0%{opacity:.3}10%{opacity:1}100%{opacity:.3}}.anim-pulse-in{animation-name:pulse-in;animation-duration:.5s}@keyframes pulse-in{0%{transform:scale3d(1, 1, 1)}50%{transform:scale3d(1.1, 1.1, 1.1)}100%{transform:scale3d(1, 1, 1)}}.hover-grow,.anim-hover-grow{transition:transform .3s;backface-visibility:hidden}.hover-grow:hover,.anim-hover-grow:hover{transform:scale(1.025)}.anim-rotate{animation:rotate-keyframes 1s linear infinite}@keyframes rotate-keyframes{100%{transform:rotate(360deg)}}.border-x{border-right:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important;border-left:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-y{border-top:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important;border-bottom:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border{border:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-0{border:0 !important}.border-top{border-top:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-right{border-right:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-bottom{border-bottom:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-left{border-left:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-top-0{border-top:0 !important}.border-right-0{border-right:0 !important}.border-bottom-0{border-bottom:0 !important}.border-left-0{border-left:0 !important}.rounded{border-radius:var(--borderRadius-medium, 6px) !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:var(--borderRadius-small, 4px) !important}.rounded-2{border-radius:var(--borderRadius-medium, 6px) !important}.rounded-3{border-radius:var(--borderRadius-large, 8px) !important}.rounded-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-top-1{border-top-left-radius:var(--borderRadius-small, 4px) !important;border-top-right-radius:var(--borderRadius-small, 4px) !important}.rounded-top-2{border-top-left-radius:var(--borderRadius-medium, 6px) !important;border-top-right-radius:var(--borderRadius-medium, 6px) !important}.rounded-top-3{border-top-left-radius:var(--borderRadius-medium, 8px) !important;border-top-right-radius:var(--borderRadius-medium, 8px) !important}.rounded-right-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-right-1{border-top-right-radius:var(--borderRadius-small, 4px) !important;border-bottom-right-radius:var(--borderRadius-small, 4px) !important}.rounded-right-2{border-top-right-radius:var(--borderRadius-medium, 6px) !important;border-bottom-right-radius:var(--borderRadius-medium, 6px) !important}.rounded-right-3{border-top-right-radius:var(--borderRadius-medium, 8px) !important;border-bottom-right-radius:var(--borderRadius-medium, 8px) !important}.rounded-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-bottom-1{border-bottom-right-radius:var(--borderRadius-small, 4px) !important;border-bottom-left-radius:var(--borderRadius-small, 4px) !important}.rounded-bottom-2{border-bottom-right-radius:var(--borderRadius-medium, 6px) !important;border-bottom-left-radius:var(--borderRadius-medium, 6px) !important}.rounded-bottom-3{border-bottom-right-radius:var(--borderRadius-medium, 8px) !important;border-bottom-left-radius:var(--borderRadius-medium, 8px) !important}.rounded-left-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-left-1{border-bottom-left-radius:var(--borderRadius-small, 4px) !important;border-top-left-radius:var(--borderRadius-small, 4px) !important}.rounded-left-2{border-bottom-left-radius:var(--borderRadius-medium, 6px) !important;border-top-left-radius:var(--borderRadius-medium, 6px) !important}.rounded-left-3{border-bottom-left-radius:var(--borderRadius-medium, 8px) !important;border-top-left-radius:var(--borderRadius-medium, 8px) !important}@media(min-width: 544px){.border-sm{border:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-sm-0{border:0 !important}.border-sm-top{border-top:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-sm-right{border-right:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-sm-bottom{border-bottom:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-sm-left{border-left:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-sm-top-0{border-top:0 !important}.border-sm-right-0{border-right:0 !important}.border-sm-bottom-0{border-bottom:0 !important}.border-sm-left-0{border-left:0 !important}.rounded-sm{border-radius:var(--borderRadius-medium, 6px) !important}.rounded-sm-0{border-radius:0 !important}.rounded-sm-1{border-radius:var(--borderRadius-small, 4px) !important}.rounded-sm-2{border-radius:var(--borderRadius-medium, 6px) !important}.rounded-sm-3{border-radius:var(--borderRadius-large, 8px) !important}.rounded-sm-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-sm-top-1{border-top-left-radius:var(--borderRadius-small, 4px) !important;border-top-right-radius:var(--borderRadius-small, 4px) !important}.rounded-sm-top-2{border-top-left-radius:var(--borderRadius-medium, 6px) !important;border-top-right-radius:var(--borderRadius-medium, 6px) !important}.rounded-sm-top-3{border-top-left-radius:var(--borderRadius-medium, 8px) !important;border-top-right-radius:var(--borderRadius-medium, 8px) !important}.rounded-sm-right-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-sm-right-1{border-top-right-radius:var(--borderRadius-small, 4px) !important;border-bottom-right-radius:var(--borderRadius-small, 4px) !important}.rounded-sm-right-2{border-top-right-radius:var(--borderRadius-medium, 6px) !important;border-bottom-right-radius:var(--borderRadius-medium, 6px) !important}.rounded-sm-right-3{border-top-right-radius:var(--borderRadius-medium, 8px) !important;border-bottom-right-radius:var(--borderRadius-medium, 8px) !important}.rounded-sm-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-sm-bottom-1{border-bottom-right-radius:var(--borderRadius-small, 4px) !important;border-bottom-left-radius:var(--borderRadius-small, 4px) !important}.rounded-sm-bottom-2{border-bottom-right-radius:var(--borderRadius-medium, 6px) !important;border-bottom-left-radius:var(--borderRadius-medium, 6px) !important}.rounded-sm-bottom-3{border-bottom-right-radius:var(--borderRadius-medium, 8px) !important;border-bottom-left-radius:var(--borderRadius-medium, 8px) !important}.rounded-sm-left-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-sm-left-1{border-bottom-left-radius:var(--borderRadius-small, 4px) !important;border-top-left-radius:var(--borderRadius-small, 4px) !important}.rounded-sm-left-2{border-bottom-left-radius:var(--borderRadius-medium, 6px) !important;border-top-left-radius:var(--borderRadius-medium, 6px) !important}.rounded-sm-left-3{border-bottom-left-radius:var(--borderRadius-medium, 8px) !important;border-top-left-radius:var(--borderRadius-medium, 8px) !important}}@media(min-width: 768px){.border-md{border:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-md-0{border:0 !important}.border-md-top{border-top:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-md-right{border-right:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-md-bottom{border-bottom:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-md-left{border-left:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-md-top-0{border-top:0 !important}.border-md-right-0{border-right:0 !important}.border-md-bottom-0{border-bottom:0 !important}.border-md-left-0{border-left:0 !important}.rounded-md{border-radius:var(--borderRadius-medium, 6px) !important}.rounded-md-0{border-radius:0 !important}.rounded-md-1{border-radius:var(--borderRadius-small, 4px) !important}.rounded-md-2{border-radius:var(--borderRadius-medium, 6px) !important}.rounded-md-3{border-radius:var(--borderRadius-large, 8px) !important}.rounded-md-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-md-top-1{border-top-left-radius:var(--borderRadius-small, 4px) !important;border-top-right-radius:var(--borderRadius-small, 4px) !important}.rounded-md-top-2{border-top-left-radius:var(--borderRadius-medium, 6px) !important;border-top-right-radius:var(--borderRadius-medium, 6px) !important}.rounded-md-top-3{border-top-left-radius:var(--borderRadius-medium, 8px) !important;border-top-right-radius:var(--borderRadius-medium, 8px) !important}.rounded-md-right-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-md-right-1{border-top-right-radius:var(--borderRadius-small, 4px) !important;border-bottom-right-radius:var(--borderRadius-small, 4px) !important}.rounded-md-right-2{border-top-right-radius:var(--borderRadius-medium, 6px) !important;border-bottom-right-radius:var(--borderRadius-medium, 6px) !important}.rounded-md-right-3{border-top-right-radius:var(--borderRadius-medium, 8px) !important;border-bottom-right-radius:var(--borderRadius-medium, 8px) !important}.rounded-md-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-md-bottom-1{border-bottom-right-radius:var(--borderRadius-small, 4px) !important;border-bottom-left-radius:var(--borderRadius-small, 4px) !important}.rounded-md-bottom-2{border-bottom-right-radius:var(--borderRadius-medium, 6px) !important;border-bottom-left-radius:var(--borderRadius-medium, 6px) !important}.rounded-md-bottom-3{border-bottom-right-radius:var(--borderRadius-medium, 8px) !important;border-bottom-left-radius:var(--borderRadius-medium, 8px) !important}.rounded-md-left-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-md-left-1{border-bottom-left-radius:var(--borderRadius-small, 4px) !important;border-top-left-radius:var(--borderRadius-small, 4px) !important}.rounded-md-left-2{border-bottom-left-radius:var(--borderRadius-medium, 6px) !important;border-top-left-radius:var(--borderRadius-medium, 6px) !important}.rounded-md-left-3{border-bottom-left-radius:var(--borderRadius-medium, 8px) !important;border-top-left-radius:var(--borderRadius-medium, 8px) !important}}@media(min-width: 1012px){.border-lg{border:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-lg-0{border:0 !important}.border-lg-top{border-top:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-lg-right{border-right:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-lg-bottom{border-bottom:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-lg-left{border-left:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-lg-top-0{border-top:0 !important}.border-lg-right-0{border-right:0 !important}.border-lg-bottom-0{border-bottom:0 !important}.border-lg-left-0{border-left:0 !important}.rounded-lg{border-radius:var(--borderRadius-medium, 6px) !important}.rounded-lg-0{border-radius:0 !important}.rounded-lg-1{border-radius:var(--borderRadius-small, 4px) !important}.rounded-lg-2{border-radius:var(--borderRadius-medium, 6px) !important}.rounded-lg-3{border-radius:var(--borderRadius-large, 8px) !important}.rounded-lg-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-lg-top-1{border-top-left-radius:var(--borderRadius-small, 4px) !important;border-top-right-radius:var(--borderRadius-small, 4px) !important}.rounded-lg-top-2{border-top-left-radius:var(--borderRadius-medium, 6px) !important;border-top-right-radius:var(--borderRadius-medium, 6px) !important}.rounded-lg-top-3{border-top-left-radius:var(--borderRadius-medium, 8px) !important;border-top-right-radius:var(--borderRadius-medium, 8px) !important}.rounded-lg-right-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-lg-right-1{border-top-right-radius:var(--borderRadius-small, 4px) !important;border-bottom-right-radius:var(--borderRadius-small, 4px) !important}.rounded-lg-right-2{border-top-right-radius:var(--borderRadius-medium, 6px) !important;border-bottom-right-radius:var(--borderRadius-medium, 6px) !important}.rounded-lg-right-3{border-top-right-radius:var(--borderRadius-medium, 8px) !important;border-bottom-right-radius:var(--borderRadius-medium, 8px) !important}.rounded-lg-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-lg-bottom-1{border-bottom-right-radius:var(--borderRadius-small, 4px) !important;border-bottom-left-radius:var(--borderRadius-small, 4px) !important}.rounded-lg-bottom-2{border-bottom-right-radius:var(--borderRadius-medium, 6px) !important;border-bottom-left-radius:var(--borderRadius-medium, 6px) !important}.rounded-lg-bottom-3{border-bottom-right-radius:var(--borderRadius-medium, 8px) !important;border-bottom-left-radius:var(--borderRadius-medium, 8px) !important}.rounded-lg-left-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-lg-left-1{border-bottom-left-radius:var(--borderRadius-small, 4px) !important;border-top-left-radius:var(--borderRadius-small, 4px) !important}.rounded-lg-left-2{border-bottom-left-radius:var(--borderRadius-medium, 6px) !important;border-top-left-radius:var(--borderRadius-medium, 6px) !important}.rounded-lg-left-3{border-bottom-left-radius:var(--borderRadius-medium, 8px) !important;border-top-left-radius:var(--borderRadius-medium, 8px) !important}}@media(min-width: 1280px){.border-xl{border:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-xl-0{border:0 !important}.border-xl-top{border-top:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-xl-right{border-right:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-xl-bottom{border-bottom:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-xl-left{border-left:var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !important}.border-xl-top-0{border-top:0 !important}.border-xl-right-0{border-right:0 !important}.border-xl-bottom-0{border-bottom:0 !important}.border-xl-left-0{border-left:0 !important}.rounded-xl{border-radius:var(--borderRadius-medium, 6px) !important}.rounded-xl-0{border-radius:0 !important}.rounded-xl-1{border-radius:var(--borderRadius-small, 4px) !important}.rounded-xl-2{border-radius:var(--borderRadius-medium, 6px) !important}.rounded-xl-3{border-radius:var(--borderRadius-large, 8px) !important}.rounded-xl-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-xl-top-1{border-top-left-radius:var(--borderRadius-small, 4px) !important;border-top-right-radius:var(--borderRadius-small, 4px) !important}.rounded-xl-top-2{border-top-left-radius:var(--borderRadius-medium, 6px) !important;border-top-right-radius:var(--borderRadius-medium, 6px) !important}.rounded-xl-top-3{border-top-left-radius:var(--borderRadius-medium, 8px) !important;border-top-right-radius:var(--borderRadius-medium, 8px) !important}.rounded-xl-right-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-xl-right-1{border-top-right-radius:var(--borderRadius-small, 4px) !important;border-bottom-right-radius:var(--borderRadius-small, 4px) !important}.rounded-xl-right-2{border-top-right-radius:var(--borderRadius-medium, 6px) !important;border-bottom-right-radius:var(--borderRadius-medium, 6px) !important}.rounded-xl-right-3{border-top-right-radius:var(--borderRadius-medium, 8px) !important;border-bottom-right-radius:var(--borderRadius-medium, 8px) !important}.rounded-xl-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-xl-bottom-1{border-bottom-right-radius:var(--borderRadius-small, 4px) !important;border-bottom-left-radius:var(--borderRadius-small, 4px) !important}.rounded-xl-bottom-2{border-bottom-right-radius:var(--borderRadius-medium, 6px) !important;border-bottom-left-radius:var(--borderRadius-medium, 6px) !important}.rounded-xl-bottom-3{border-bottom-right-radius:var(--borderRadius-medium, 8px) !important;border-bottom-left-radius:var(--borderRadius-medium, 8px) !important}.rounded-xl-left-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-xl-left-1{border-bottom-left-radius:var(--borderRadius-small, 4px) !important;border-top-left-radius:var(--borderRadius-small, 4px) !important}.rounded-xl-left-2{border-bottom-left-radius:var(--borderRadius-medium, 6px) !important;border-top-left-radius:var(--borderRadius-medium, 6px) !important}.rounded-xl-left-3{border-bottom-left-radius:var(--borderRadius-medium, 8px) !important;border-top-left-radius:var(--borderRadius-medium, 8px) !important}}.circle{border-radius:var(--borderRadius-full, 50%) !important}.border-dashed{border-style:dashed !important}.color-shadow-small{box-shadow:var(--shadow-resting-small, var(--color-shadow-small)) !important}.color-shadow-medium{box-shadow:var(--shadow-resting-medium, var(--color-shadow-medium)) !important}.color-shadow-large{box-shadow:var(--shadow-floating-large, var(--color-shadow-large)) !important}.color-shadow-extra-large{box-shadow:var(--shadow-floating-xlarge, var(--color-shadow-extra-large)) !important}.shadow-floating-small{box-shadow:var(--shadow-floating-small, var(--color-overlay-shadow)) !important}.box-shadow-none{box-shadow:none !important}.color-fg-default,.fgColor-default{color:var(--fgColor-default, var(--color-fg-default)) !important}.color-fg-muted,.fgColor-muted{color:var(--fgColor-muted, var(--color-fg-muted)) !important}.color-fg-subtle{color:var(--fgColor-muted, var(--color-fg-subtle)) !important}.color-fg-accent,.fgColor-accent{color:var(--fgColor-accent, var(--color-accent-fg)) !important}.color-fg-success,.fgColor-success{color:var(--fgColor-success, var(--color-success-fg)) !important}.color-fg-attention,.fgColor-attention{color:var(--fgColor-attention, var(--color-attention-fg)) !important}.color-fg-severe,.fgColor-severe{color:var(--fgColor-severe, var(--color-severe-fg)) !important}.color-fg-danger,.fgColor-danger{color:var(--fgColor-danger, var(--color-danger-fg)) !important}.color-fg-open,.fgColor-open{color:var(--fgColor-open, var(--color-open-fg)) !important}.color-fg-closed,.fgColor-closed{color:var(--fgColor-closed, var(--color-closed-fg)) !important}.color-fg-done,.fgColor-done{color:var(--fgColor-done, var(--color-done-fg)) !important}.color-fg-sponsors,.fgColor-sponsors{color:var(--fgColor-sponsors, var(--color-sponsors-fg)) !important}.color-fg-on-emphasis,.fgColor-onEmphasis{color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)) !important}.color-bg-default,.bgColor-default{background-color:var(--bgColor-default, var(--color-canvas-default)) !important}.color-bg-overlay{background-color:var(--overlay-bgColor, var(--color-canvas-overlay)) !important}.color-bg-inset,.bgColor-inset{background-color:var(--bgColor-inset, var(--color-canvas-inset)) !important}.color-bg-subtle,.bgColor-muted{background-color:var(--bgColor-muted, var(--color-canvas-subtle)) !important}.color-bg-emphasis,.bgColor-emphasis{background-color:var(--bgColor-emphasis, var(--color-neutral-emphasis-plus)) !important}.color-bg-accent,.bgColor-accent-muted{background-color:var(--bgColor-accent-muted, var(--color-accent-subtle)) !important}.color-bg-accent-emphasis,.bgColor-accent-emphasis{background-color:var(--bgColor-accent-emphasis, var(--color-accent-emphasis)) !important}.color-bg-success,.bgColor-success-muted{background-color:var(--bgColor-success-muted, var(--color-success-subtle)) !important}.color-bg-success-emphasis,.bgColor-success-emphasis{background-color:var(--bgColor-success-emphasis, var(--color-success-emphasis)) !important}.color-bg-attention,.bgColor-attention-muted{background-color:var(--bgColor-attention-muted, var(--color-attention-subtle)) !important}.color-bg-attention-emphasis,.bgColor-attention-emphasis{background-color:var(--bgColor-attention-emphasis, var(--color-attention-emphasis)) !important}.color-bg-severe,.bgColor-severe-muted{background-color:var(--bgColor-severe-muted, var(--color-severe-subtle)) !important}.color-bg-severe-emphasis,.bgColor-severe-emphasis{background-color:var(--bgColor-severe-emphasis, var(--color-severe-emphasis)) !important}.color-bg-danger,.bgColor-danger-muted{background-color:var(--bgColor-danger-muted, var(--color-danger-subtle)) !important}.color-bg-danger-emphasis,.bgColor-danger-emphasis{background-color:var(--bgColor-danger-emphasis, var(--color-danger-emphasis)) !important}.color-bg-open,.bgColor-open-muted{background-color:var(--bgColor-open-muted, var(--color-open-subtle)) !important}.color-bg-open-emphasis,.bgColor-open-emphasis{background-color:var(--bgColor-open-emphasis, var(--color-open-emphasis)) !important}.color-bg-closed,.bgColor-closed-muted{background-color:var(--bgColor-closed-muted, var(--color-closed-subtle)) !important}.color-bg-closed-emphasis,.bgColor-closed-emphasis{background-color:var(--bgColor-closed-emphasis, var(--color-closed-emphasis)) !important}.color-bg-done,.bgColor-done-muted{background-color:var(--bgColor-done-muted, var(--color-done-subtle)) !important}.color-bg-done-emphasis,.bgColor-done-emphasis{background-color:var(--bgColor-done-emphasis, var(--color-done-emphasis)) !important}.color-bg-sponsors,.bgColor-sponsors-muted{background-color:var(--bgColor-sponsors-muted, var(--color-sponsors-subtle)) !important}.color-bg-sponsors-emphasis,.bgColor-sponsors-emphasis{background-color:var(--bgColor-sponsors-emphasis, var(--color-sponsors-emphasis)) !important}.color-bg-transparent,.bgColor-transparent{background-color:rgba(0,0,0,0) !important}.color-border-default,.borderColor-default{border-color:var(--borderColor-default, var(--color-border-default)) !important}.color-border-muted,.borderColor-muted{border-color:var(--borderColor-muted, var(--color-border-muted)) !important}.color-border-subtle{border-color:var(--borderColor-muted, var(--color-border-subtle)) !important}.color-border-accent,.borderColor-accent-muted{border-color:var(--borderColor-accent-muted, var(--color-accent-muted)) !important}.color-border-accent-emphasis,.borderColor-accent-emphasis{border-color:var(--borderColor-accent-emphasis, var(--color-accent-emphasis)) !important}.color-border-success,.borderColor-success-muted{border-color:var(--borderColor-success-muted, var(--color-success-muted)) !important}.color-border-success-emphasis,.borderColor-success-emphasis{border-color:var(--borderColor-success-emphasis, var(--color-success-emphasis)) !important}.color-border-attention,.borderColor-attention-muted{border-color:var(--borderColor-attention-muted, var(--color-attention-muted)) !important}.color-border-attention-emphasis,.borderColor-attention-emphasis{border-color:var(--borderColor-attention-emphasis, var(--color-attention-emphasis)) !important}.color-border-severe,.borderColor-severe-muted{border-color:var(--borderColor-severe-muted, var(--color-severe-muted)) !important}.color-border-severe-emphasis,.borderColor-severe-emphasis{border-color:var(--borderColor-severe-emphasis, var(--color-severe-emphasis)) !important}.color-border-danger,.borderColor-danger-muted{border-color:var(--borderColor-danger-muted, var(--color-danger-muted)) !important}.color-border-danger-emphasis,.borderColor-danger-emphasis{border-color:var(--borderColor-danger-emphasis, var(--color-danger-emphasis)) !important}.color-border-open,.borderColor-open-muted{border-color:var(--borderColor-open-muted, var(--color-open-muted)) !important}.color-border-open-emphasis,.borderColor-open-emphasis{border-color:var(--borderColor-open-emphasis, var(--color-open-emphasis)) !important}.color-border-closed,.borderColor-closed-muted{border-color:var(--borderColor-closed-muted, var(--color-closed-muted)) !important}.color-border-closed-emphasis,.borderColor-closed-emphasis{border-color:var(--borderColor-closed-emphasis, var(--color-closed-emphasis)) !important}.color-border-done,.borderColor-done-muted{border-color:var(--borderColor-done-muted, var(--color-done-muted)) !important}.color-border-done-emphasis,.borderColor-done-emphasis{border-color:var(--borderColor-done-emphasis, var(--color-done-emphasis)) !important}.color-border-sponsors,.borderColor-sponsors-muted{border-color:var(--borderColor-sponsors-muted, var(--color-sponsors-muted)) !important}.color-border-sponsors-emphasis,.borderColor-sponsors-emphasis{border-color:var(--borderColor-sponsors-emphasis, var(--color-sponsors-emphasis)) !important}.color-fg-inherit,.fgColor-inherit{color:inherit !important}.details-overlay[open]>summary::before{position:fixed;top:0;right:0;bottom:0;left:0;z-index:80;display:block;cursor:default;content:" ";background:rgba(0,0,0,0)}.details-overlay-dark[open]>summary::before{z-index:111;background:var(--overlay-backdrop-bgColor, var(--color-primer-canvas-backdrop))}.details-reset>summary{list-style:none;transition:80ms cubic-bezier(0.33, 1, 0.68, 1);transition-property:color,background-color,box-shadow,border-color}.details-reset>summary:focus{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:none}.details-reset>summary:focus:not(:focus-visible){outline:solid 1px rgba(0,0,0,0)}.details-reset>summary:focus-visible{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:none}.details-reset>summary.btn-primary:focus{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.details-reset>summary.btn-primary:focus:not(:focus-visible){outline:solid 1px rgba(0,0,0,0);box-shadow:none}.details-reset>summary.btn-primary:focus-visible{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.details-reset>summary::before{display:none}.details-reset>summary::-webkit-details-marker{display:none}.details-overlay>summary{transition:80ms cubic-bezier(0.33, 1, 0.68, 1);transition-property:color,background-color,box-shadow,border-color}.details-overlay>summary:focus{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:none}.details-overlay>summary:focus:not(:focus-visible){outline:solid 1px rgba(0,0,0,0)}.details-overlay>summary:focus-visible{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:none}.details-overlay>summary.btn-primary:focus{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.details-overlay>summary.btn-primary:focus:not(:focus-visible){outline:solid 1px rgba(0,0,0,0);box-shadow:none}.details-overlay>summary.btn-primary:focus-visible{outline:2px solid var(--focus-outlineColor, var(--color-accent-fg));outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.flex-row{flex-direction:row !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column{flex-direction:column !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.flex-justify-start{justify-content:flex-start !important}.flex-justify-end{justify-content:flex-end !important}.flex-justify-center{justify-content:center !important}.flex-justify-between{justify-content:space-between !important}.flex-justify-around{justify-content:space-around !important}.flex-items-start{align-items:flex-start !important}.flex-items-end{align-items:flex-end !important}.flex-items-center{align-items:center !important}.flex-items-baseline{align-items:baseline !important}.flex-items-stretch{align-items:stretch !important}.flex-content-start{align-content:flex-start !important}.flex-content-end{align-content:flex-end !important}.flex-content-center{align-content:center !important}.flex-content-between{align-content:space-between !important}.flex-content-around{align-content:space-around !important}.flex-content-stretch{align-content:stretch !important}.flex-1{flex:1 !important}.flex-auto{flex:auto !important}.flex-grow-0{flex-grow:0 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-self-auto{align-self:auto !important}.flex-self-start{align-self:flex-start !important}.flex-self-end{align-self:flex-end !important}.flex-self-center{align-self:center !important}.flex-self-baseline{align-self:baseline !important}.flex-self-stretch{align-self:stretch !important}.flex-order-1{order:1 !important}.flex-order-2{order:2 !important}.flex-order-none{order:inherit !important}@media(min-width: 544px){.flex-sm-row{flex-direction:row !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column{flex-direction:column !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.flex-sm-justify-start{justify-content:flex-start !important}.flex-sm-justify-end{justify-content:flex-end !important}.flex-sm-justify-center{justify-content:center !important}.flex-sm-justify-between{justify-content:space-between !important}.flex-sm-justify-around{justify-content:space-around !important}.flex-sm-items-start{align-items:flex-start !important}.flex-sm-items-end{align-items:flex-end !important}.flex-sm-items-center{align-items:center !important}.flex-sm-items-baseline{align-items:baseline !important}.flex-sm-items-stretch{align-items:stretch !important}.flex-sm-content-start{align-content:flex-start !important}.flex-sm-content-end{align-content:flex-end !important}.flex-sm-content-center{align-content:center !important}.flex-sm-content-between{align-content:space-between !important}.flex-sm-content-around{align-content:space-around !important}.flex-sm-content-stretch{align-content:stretch !important}.flex-sm-1{flex:1 !important}.flex-sm-auto{flex:auto !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-self-auto{align-self:auto !important}.flex-sm-self-start{align-self:flex-start !important}.flex-sm-self-end{align-self:flex-end !important}.flex-sm-self-center{align-self:center !important}.flex-sm-self-baseline{align-self:baseline !important}.flex-sm-self-stretch{align-self:stretch !important}.flex-sm-order-1{order:1 !important}.flex-sm-order-2{order:2 !important}.flex-sm-order-none{order:inherit !important}}@media(min-width: 768px){.flex-md-row{flex-direction:row !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column{flex-direction:column !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.flex-md-justify-start{justify-content:flex-start !important}.flex-md-justify-end{justify-content:flex-end !important}.flex-md-justify-center{justify-content:center !important}.flex-md-justify-between{justify-content:space-between !important}.flex-md-justify-around{justify-content:space-around !important}.flex-md-items-start{align-items:flex-start !important}.flex-md-items-end{align-items:flex-end !important}.flex-md-items-center{align-items:center !important}.flex-md-items-baseline{align-items:baseline !important}.flex-md-items-stretch{align-items:stretch !important}.flex-md-content-start{align-content:flex-start !important}.flex-md-content-end{align-content:flex-end !important}.flex-md-content-center{align-content:center !important}.flex-md-content-between{align-content:space-between !important}.flex-md-content-around{align-content:space-around !important}.flex-md-content-stretch{align-content:stretch !important}.flex-md-1{flex:1 !important}.flex-md-auto{flex:auto !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-self-auto{align-self:auto !important}.flex-md-self-start{align-self:flex-start !important}.flex-md-self-end{align-self:flex-end !important}.flex-md-self-center{align-self:center !important}.flex-md-self-baseline{align-self:baseline !important}.flex-md-self-stretch{align-self:stretch !important}.flex-md-order-1{order:1 !important}.flex-md-order-2{order:2 !important}.flex-md-order-none{order:inherit !important}}@media(min-width: 1012px){.flex-lg-row{flex-direction:row !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column{flex-direction:column !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.flex-lg-justify-start{justify-content:flex-start !important}.flex-lg-justify-end{justify-content:flex-end !important}.flex-lg-justify-center{justify-content:center !important}.flex-lg-justify-between{justify-content:space-between !important}.flex-lg-justify-around{justify-content:space-around !important}.flex-lg-items-start{align-items:flex-start !important}.flex-lg-items-end{align-items:flex-end !important}.flex-lg-items-center{align-items:center !important}.flex-lg-items-baseline{align-items:baseline !important}.flex-lg-items-stretch{align-items:stretch !important}.flex-lg-content-start{align-content:flex-start !important}.flex-lg-content-end{align-content:flex-end !important}.flex-lg-content-center{align-content:center !important}.flex-lg-content-between{align-content:space-between !important}.flex-lg-content-around{align-content:space-around !important}.flex-lg-content-stretch{align-content:stretch !important}.flex-lg-1{flex:1 !important}.flex-lg-auto{flex:auto !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-self-auto{align-self:auto !important}.flex-lg-self-start{align-self:flex-start !important}.flex-lg-self-end{align-self:flex-end !important}.flex-lg-self-center{align-self:center !important}.flex-lg-self-baseline{align-self:baseline !important}.flex-lg-self-stretch{align-self:stretch !important}.flex-lg-order-1{order:1 !important}.flex-lg-order-2{order:2 !important}.flex-lg-order-none{order:inherit !important}}@media(min-width: 1280px){.flex-xl-row{flex-direction:row !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column{flex-direction:column !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.flex-xl-justify-start{justify-content:flex-start !important}.flex-xl-justify-end{justify-content:flex-end !important}.flex-xl-justify-center{justify-content:center !important}.flex-xl-justify-between{justify-content:space-between !important}.flex-xl-justify-around{justify-content:space-around !important}.flex-xl-items-start{align-items:flex-start !important}.flex-xl-items-end{align-items:flex-end !important}.flex-xl-items-center{align-items:center !important}.flex-xl-items-baseline{align-items:baseline !important}.flex-xl-items-stretch{align-items:stretch !important}.flex-xl-content-start{align-content:flex-start !important}.flex-xl-content-end{align-content:flex-end !important}.flex-xl-content-center{align-content:center !important}.flex-xl-content-between{align-content:space-between !important}.flex-xl-content-around{align-content:space-around !important}.flex-xl-content-stretch{align-content:stretch !important}.flex-xl-1{flex:1 !important}.flex-xl-auto{flex:auto !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-self-auto{align-self:auto !important}.flex-xl-self-start{align-self:flex-start !important}.flex-xl-self-end{align-self:flex-end !important}.flex-xl-self-center{align-self:center !important}.flex-xl-self-baseline{align-self:baseline !important}.flex-xl-self-stretch{align-self:stretch !important}.flex-xl-order-1{order:1 !important}.flex-xl-order-2{order:2 !important}.flex-xl-order-none{order:inherit !important}}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}@media(min-width: 544px){.position-sm-static{position:static !important}.position-sm-relative{position:relative !important}.position-sm-absolute{position:absolute !important}.position-sm-fixed{position:fixed !important}.position-sm-sticky{position:sticky !important}}@media(min-width: 768px){.position-md-static{position:static !important}.position-md-relative{position:relative !important}.position-md-absolute{position:absolute !important}.position-md-fixed{position:fixed !important}.position-md-sticky{position:sticky !important}}@media(min-width: 1012px){.position-lg-static{position:static !important}.position-lg-relative{position:relative !important}.position-lg-absolute{position:absolute !important}.position-lg-fixed{position:fixed !important}.position-lg-sticky{position:sticky !important}}@media(min-width: 1280px){.position-xl-static{position:static !important}.position-xl-relative{position:relative !important}.position-xl-absolute{position:absolute !important}.position-xl-fixed{position:fixed !important}.position-xl-sticky{position:sticky !important}}.top-0{top:0 !important}.right-0{right:0 !important}.bottom-0{bottom:0 !important}.left-0{left:0 !important}.top-auto{top:auto !important}.right-auto{right:auto !important}.bottom-auto{bottom:auto !important}.left-auto{left:auto !important}@media(min-width: 544px){.top-sm-0{top:0 !important}.right-sm-0{right:0 !important}.bottom-sm-0{bottom:0 !important}.left-sm-0{left:0 !important}.top-sm-auto{top:auto !important}.right-sm-auto{right:auto !important}.bottom-sm-auto{bottom:auto !important}.left-sm-auto{left:auto !important}}@media(min-width: 768px){.top-md-0{top:0 !important}.right-md-0{right:0 !important}.bottom-md-0{bottom:0 !important}.left-md-0{left:0 !important}.top-md-auto{top:auto !important}.right-md-auto{right:auto !important}.bottom-md-auto{bottom:auto !important}.left-md-auto{left:auto !important}}@media(min-width: 1012px){.top-lg-0{top:0 !important}.right-lg-0{right:0 !important}.bottom-lg-0{bottom:0 !important}.left-lg-0{left:0 !important}.top-lg-auto{top:auto !important}.right-lg-auto{right:auto !important}.bottom-lg-auto{bottom:auto !important}.left-lg-auto{left:auto !important}}@media(min-width: 1280px){.top-xl-0{top:0 !important}.right-xl-0{right:0 !important}.bottom-xl-0{bottom:0 !important}.left-xl-0{left:0 !important}.top-xl-auto{top:auto !important}.right-xl-auto{right:auto !important}.bottom-xl-auto{bottom:auto !important}.left-xl-auto{left:auto !important}}.v-align-middle{vertical-align:middle !important}.v-align-top{vertical-align:top !important}.v-align-bottom{vertical-align:bottom !important}.v-align-text-top{vertical-align:text-top !important}.v-align-text-bottom{vertical-align:text-bottom !important}.v-align-baseline{vertical-align:baseline !important}.overflow-visible{overflow:visible !important}.overflow-x-visible{overflow-x:visible !important}.overflow-y-visible{overflow-y:visible !important}.overflow-hidden{overflow:hidden !important}.overflow-x-hidden{overflow-x:hidden !important}.overflow-y-hidden{overflow-y:hidden !important}.overflow-auto{overflow:auto !important}.overflow-x-auto{overflow-x:auto !important}.overflow-y-auto{overflow-y:auto !important}.overflow-scroll{overflow:scroll !important}.overflow-x-scroll{overflow-x:scroll !important}.overflow-y-scroll{overflow-y:scroll !important}@media(min-width: 544px){.overflow-sm-visible{overflow:visible !important}.overflow-sm-x-visible{overflow-x:visible !important}.overflow-sm-y-visible{overflow-y:visible !important}.overflow-sm-hidden{overflow:hidden !important}.overflow-sm-x-hidden{overflow-x:hidden !important}.overflow-sm-y-hidden{overflow-y:hidden !important}.overflow-sm-auto{overflow:auto !important}.overflow-sm-x-auto{overflow-x:auto !important}.overflow-sm-y-auto{overflow-y:auto !important}.overflow-sm-scroll{overflow:scroll !important}.overflow-sm-x-scroll{overflow-x:scroll !important}.overflow-sm-y-scroll{overflow-y:scroll !important}}@media(min-width: 768px){.overflow-md-visible{overflow:visible !important}.overflow-md-x-visible{overflow-x:visible !important}.overflow-md-y-visible{overflow-y:visible !important}.overflow-md-hidden{overflow:hidden !important}.overflow-md-x-hidden{overflow-x:hidden !important}.overflow-md-y-hidden{overflow-y:hidden !important}.overflow-md-auto{overflow:auto !important}.overflow-md-x-auto{overflow-x:auto !important}.overflow-md-y-auto{overflow-y:auto !important}.overflow-md-scroll{overflow:scroll !important}.overflow-md-x-scroll{overflow-x:scroll !important}.overflow-md-y-scroll{overflow-y:scroll !important}}@media(min-width: 1012px){.overflow-lg-visible{overflow:visible !important}.overflow-lg-x-visible{overflow-x:visible !important}.overflow-lg-y-visible{overflow-y:visible !important}.overflow-lg-hidden{overflow:hidden !important}.overflow-lg-x-hidden{overflow-x:hidden !important}.overflow-lg-y-hidden{overflow-y:hidden !important}.overflow-lg-auto{overflow:auto !important}.overflow-lg-x-auto{overflow-x:auto !important}.overflow-lg-y-auto{overflow-y:auto !important}.overflow-lg-scroll{overflow:scroll !important}.overflow-lg-x-scroll{overflow-x:scroll !important}.overflow-lg-y-scroll{overflow-y:scroll !important}}@media(min-width: 1280px){.overflow-xl-visible{overflow:visible !important}.overflow-xl-x-visible{overflow-x:visible !important}.overflow-xl-y-visible{overflow-y:visible !important}.overflow-xl-hidden{overflow:hidden !important}.overflow-xl-x-hidden{overflow-x:hidden !important}.overflow-xl-y-hidden{overflow-y:hidden !important}.overflow-xl-auto{overflow:auto !important}.overflow-xl-x-auto{overflow-x:auto !important}.overflow-xl-y-auto{overflow-y:auto !important}.overflow-xl-scroll{overflow:scroll !important}.overflow-xl-x-scroll{overflow-x:scroll !important}.overflow-xl-y-scroll{overflow-y:scroll !important}}.clearfix::before{display:table;content:""}.clearfix::after{display:table;clear:both;content:""}.float-left{float:left !important}.float-right{float:right !important}.float-none{float:none !important}@media(min-width: 544px){.float-sm-left{float:left !important}.float-sm-right{float:right !important}.float-sm-none{float:none !important}}@media(min-width: 768px){.float-md-left{float:left !important}.float-md-right{float:right !important}.float-md-none{float:none !important}}@media(min-width: 1012px){.float-lg-left{float:left !important}.float-lg-right{float:right !important}.float-lg-none{float:none !important}}@media(min-width: 1280px){.float-xl-left{float:left !important}.float-xl-right{float:right !important}.float-xl-none{float:none !important}}.width-fit{max-width:100% !important}.width-full{width:100% !important}.width-comfortable{max-width:65ch !important}.height-fit{max-height:100% !important}.height-full{height:100% !important}.min-width-0{min-width:0 !important}.width-auto{width:auto !important}.direction-rtl{direction:rtl !important}.direction-ltr{direction:ltr !important}@media(min-width: 544px){.width-sm-auto{width:auto !important}.direction-sm-rtl{direction:rtl !important}.direction-sm-ltr{direction:ltr !important}}@media(min-width: 768px){.width-md-auto{width:auto !important}.direction-md-rtl{direction:rtl !important}.direction-md-ltr{direction:ltr !important}}@media(min-width: 1012px){.width-lg-auto{width:auto !important}.direction-lg-rtl{direction:rtl !important}.direction-lg-ltr{direction:ltr !important}}@media(min-width: 1280px){.width-xl-auto{width:auto !important}.direction-xl-rtl{direction:rtl !important}.direction-xl-ltr{direction:ltr !important}}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mb-0{margin-bottom:0 !important}.mr-0{margin-right:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:var(--base-size-4, 4px) !important}.mt-1{margin-top:var(--base-size-4, 4px) !important}.mb-1{margin-bottom:var(--base-size-4, 4px) !important}.mr-1{margin-right:var(--base-size-4, 4px) !important}.ml-1{margin-left:var(--base-size-4, 4px) !important}.mt-n1{margin-top:calc(-1*var(--base-size-4, 4px)) !important}.mb-n1{margin-bottom:calc(-1*var(--base-size-4, 4px)) !important}.mr-n1{margin-right:calc(-1*var(--base-size-4, 4px)) !important}.ml-n1{margin-left:calc(-1*var(--base-size-4, 4px)) !important}.mx-1{margin-right:var(--base-size-4, 4px) !important;margin-left:var(--base-size-4, 4px) !important}.my-1{margin-top:var(--base-size-4, 4px) !important;margin-bottom:var(--base-size-4, 4px) !important}.m-2{margin:var(--base-size-8, 8px) !important}.mt-2{margin-top:var(--base-size-8, 8px) !important}.mb-2{margin-bottom:var(--base-size-8, 8px) !important}.mr-2{margin-right:var(--base-size-8, 8px) !important}.ml-2{margin-left:var(--base-size-8, 8px) !important}.mt-n2{margin-top:calc(-1*var(--base-size-8, 8px)) !important}.mb-n2{margin-bottom:calc(-1*var(--base-size-8, 8px)) !important}.mr-n2{margin-right:calc(-1*var(--base-size-8, 8px)) !important}.ml-n2{margin-left:calc(-1*var(--base-size-8, 8px)) !important}.mx-2{margin-right:var(--base-size-8, 8px) !important;margin-left:var(--base-size-8, 8px) !important}.my-2{margin-top:var(--base-size-8, 8px) !important;margin-bottom:var(--base-size-8, 8px) !important}.m-3{margin:var(--base-size-16, 16px) !important}.mt-3{margin-top:var(--base-size-16, 16px) !important}.mb-3{margin-bottom:var(--base-size-16, 16px) !important}.mr-3{margin-right:var(--base-size-16, 16px) !important}.ml-3{margin-left:var(--base-size-16, 16px) !important}.mt-n3{margin-top:calc(-1*var(--base-size-16, 16px)) !important}.mb-n3{margin-bottom:calc(-1*var(--base-size-16, 16px)) !important}.mr-n3{margin-right:calc(-1*var(--base-size-16, 16px)) !important}.ml-n3{margin-left:calc(-1*var(--base-size-16, 16px)) !important}.mx-3{margin-right:var(--base-size-16, 16px) !important;margin-left:var(--base-size-16, 16px) !important}.my-3{margin-top:var(--base-size-16, 16px) !important;margin-bottom:var(--base-size-16, 16px) !important}.m-4{margin:var(--base-size-24, 24px) !important}.mt-4{margin-top:var(--base-size-24, 24px) !important}.mb-4{margin-bottom:var(--base-size-24, 24px) !important}.mr-4{margin-right:var(--base-size-24, 24px) !important}.ml-4{margin-left:var(--base-size-24, 24px) !important}.mt-n4{margin-top:calc(-1*var(--base-size-24, 24px)) !important}.mb-n4{margin-bottom:calc(-1*var(--base-size-24, 24px)) !important}.mr-n4{margin-right:calc(-1*var(--base-size-24, 24px)) !important}.ml-n4{margin-left:calc(-1*var(--base-size-24, 24px)) !important}.mx-4{margin-right:var(--base-size-24, 24px) !important;margin-left:var(--base-size-24, 24px) !important}.my-4{margin-top:var(--base-size-24, 24px) !important;margin-bottom:var(--base-size-24, 24px) !important}.m-5{margin:var(--base-size-32, 32px) !important}.mt-5{margin-top:var(--base-size-32, 32px) !important}.mb-5{margin-bottom:var(--base-size-32, 32px) !important}.mr-5{margin-right:var(--base-size-32, 32px) !important}.ml-5{margin-left:var(--base-size-32, 32px) !important}.mt-n5{margin-top:calc(-1*var(--base-size-32, 32px)) !important}.mb-n5{margin-bottom:calc(-1*var(--base-size-32, 32px)) !important}.mr-n5{margin-right:calc(-1*var(--base-size-32, 32px)) !important}.ml-n5{margin-left:calc(-1*var(--base-size-32, 32px)) !important}.mx-5{margin-right:var(--base-size-32, 32px) !important;margin-left:var(--base-size-32, 32px) !important}.my-5{margin-top:var(--base-size-32, 32px) !important;margin-bottom:var(--base-size-32, 32px) !important}.m-6{margin:var(--base-size-40, 40px) !important}.mt-6{margin-top:var(--base-size-40, 40px) !important}.mb-6{margin-bottom:var(--base-size-40, 40px) !important}.mr-6{margin-right:var(--base-size-40, 40px) !important}.ml-6{margin-left:var(--base-size-40, 40px) !important}.mt-n6{margin-top:calc(-1*var(--base-size-40, 40px)) !important}.mb-n6{margin-bottom:calc(-1*var(--base-size-40, 40px)) !important}.mr-n6{margin-right:calc(-1*var(--base-size-40, 40px)) !important}.ml-n6{margin-left:calc(-1*var(--base-size-40, 40px)) !important}.mx-6{margin-right:var(--base-size-40, 40px) !important;margin-left:var(--base-size-40, 40px) !important}.my-6{margin-top:var(--base-size-40, 40px) !important;margin-bottom:var(--base-size-40, 40px) !important}.mt-7{margin-top:var(--base-size-48, 48px) !important}.mb-7{margin-bottom:var(--base-size-48, 48px) !important}.mt-n7{margin-top:calc(-1*var(--base-size-48, 48px)) !important}.mb-n7{margin-bottom:calc(-1*var(--base-size-48, 48px)) !important}.my-7{margin-top:var(--base-size-48, 48px) !important;margin-bottom:var(--base-size-48, 48px) !important}.mt-8{margin-top:var(--base-size-64, 64px) !important}.mb-8{margin-bottom:var(--base-size-64, 64px) !important}.mt-n8{margin-top:calc(-1*var(--base-size-64, 64px)) !important}.mb-n8{margin-bottom:calc(-1*var(--base-size-64, 64px)) !important}.my-8{margin-top:var(--base-size-64, 64px) !important;margin-bottom:var(--base-size-64, 64px) !important}.mt-9{margin-top:var(--base-size-80, 80px) !important}.mb-9{margin-bottom:var(--base-size-80, 80px) !important}.mt-n9{margin-top:calc(-1*var(--base-size-80, 80px)) !important}.mb-n9{margin-bottom:calc(-1*var(--base-size-80, 80px)) !important}.my-9{margin-top:var(--base-size-80, 80px) !important;margin-bottom:var(--base-size-80, 80px) !important}.mt-10{margin-top:var(--base-size-96, 96px) !important}.mb-10{margin-bottom:var(--base-size-96, 96px) !important}.mt-n10{margin-top:calc(-1*var(--base-size-96, 96px)) !important}.mb-n10{margin-bottom:calc(-1*var(--base-size-96, 96px)) !important}.my-10{margin-top:var(--base-size-96, 96px) !important;margin-bottom:var(--base-size-96, 96px) !important}.mt-11{margin-top:var(--base-size-112, 112px) !important}.mb-11{margin-bottom:var(--base-size-112, 112px) !important}.mt-n11{margin-top:calc(-1*var(--base-size-112, 112px)) !important}.mb-n11{margin-bottom:calc(-1*var(--base-size-112, 112px)) !important}.my-11{margin-top:var(--base-size-112, 112px) !important;margin-bottom:var(--base-size-112, 112px) !important}.mt-12{margin-top:var(--base-size-128, 128px) !important}.mb-12{margin-bottom:var(--base-size-128, 128px) !important}.mt-n12{margin-top:calc(-1*var(--base-size-128, 128px)) !important}.mb-n12{margin-bottom:calc(-1*var(--base-size-128, 128px)) !important}.my-12{margin-top:var(--base-size-128, 128px) !important;margin-bottom:var(--base-size-128, 128px) !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}@media(min-width: 544px){.m-sm-0{margin:0 !important}.mt-sm-0{margin-top:0 !important}.mb-sm-0{margin-bottom:0 !important}.mr-sm-0{margin-right:0 !important}.ml-sm-0{margin-left:0 !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.m-sm-1{margin:var(--base-size-4, 4px) !important}.mt-sm-1{margin-top:var(--base-size-4, 4px) !important}.mb-sm-1{margin-bottom:var(--base-size-4, 4px) !important}.mr-sm-1{margin-right:var(--base-size-4, 4px) !important}.ml-sm-1{margin-left:var(--base-size-4, 4px) !important}.mt-sm-n1{margin-top:calc(-1*var(--base-size-4, 4px)) !important}.mb-sm-n1{margin-bottom:calc(-1*var(--base-size-4, 4px)) !important}.mr-sm-n1{margin-right:calc(-1*var(--base-size-4, 4px)) !important}.ml-sm-n1{margin-left:calc(-1*var(--base-size-4, 4px)) !important}.mx-sm-1{margin-right:var(--base-size-4, 4px) !important;margin-left:var(--base-size-4, 4px) !important}.my-sm-1{margin-top:var(--base-size-4, 4px) !important;margin-bottom:var(--base-size-4, 4px) !important}.m-sm-2{margin:var(--base-size-8, 8px) !important}.mt-sm-2{margin-top:var(--base-size-8, 8px) !important}.mb-sm-2{margin-bottom:var(--base-size-8, 8px) !important}.mr-sm-2{margin-right:var(--base-size-8, 8px) !important}.ml-sm-2{margin-left:var(--base-size-8, 8px) !important}.mt-sm-n2{margin-top:calc(-1*var(--base-size-8, 8px)) !important}.mb-sm-n2{margin-bottom:calc(-1*var(--base-size-8, 8px)) !important}.mr-sm-n2{margin-right:calc(-1*var(--base-size-8, 8px)) !important}.ml-sm-n2{margin-left:calc(-1*var(--base-size-8, 8px)) !important}.mx-sm-2{margin-right:var(--base-size-8, 8px) !important;margin-left:var(--base-size-8, 8px) !important}.my-sm-2{margin-top:var(--base-size-8, 8px) !important;margin-bottom:var(--base-size-8, 8px) !important}.m-sm-3{margin:var(--base-size-16, 16px) !important}.mt-sm-3{margin-top:var(--base-size-16, 16px) !important}.mb-sm-3{margin-bottom:var(--base-size-16, 16px) !important}.mr-sm-3{margin-right:var(--base-size-16, 16px) !important}.ml-sm-3{margin-left:var(--base-size-16, 16px) !important}.mt-sm-n3{margin-top:calc(-1*var(--base-size-16, 16px)) !important}.mb-sm-n3{margin-bottom:calc(-1*var(--base-size-16, 16px)) !important}.mr-sm-n3{margin-right:calc(-1*var(--base-size-16, 16px)) !important}.ml-sm-n3{margin-left:calc(-1*var(--base-size-16, 16px)) !important}.mx-sm-3{margin-right:var(--base-size-16, 16px) !important;margin-left:var(--base-size-16, 16px) !important}.my-sm-3{margin-top:var(--base-size-16, 16px) !important;margin-bottom:var(--base-size-16, 16px) !important}.m-sm-4{margin:var(--base-size-24, 24px) !important}.mt-sm-4{margin-top:var(--base-size-24, 24px) !important}.mb-sm-4{margin-bottom:var(--base-size-24, 24px) !important}.mr-sm-4{margin-right:var(--base-size-24, 24px) !important}.ml-sm-4{margin-left:var(--base-size-24, 24px) !important}.mt-sm-n4{margin-top:calc(-1*var(--base-size-24, 24px)) !important}.mb-sm-n4{margin-bottom:calc(-1*var(--base-size-24, 24px)) !important}.mr-sm-n4{margin-right:calc(-1*var(--base-size-24, 24px)) !important}.ml-sm-n4{margin-left:calc(-1*var(--base-size-24, 24px)) !important}.mx-sm-4{margin-right:var(--base-size-24, 24px) !important;margin-left:var(--base-size-24, 24px) !important}.my-sm-4{margin-top:var(--base-size-24, 24px) !important;margin-bottom:var(--base-size-24, 24px) !important}.m-sm-5{margin:var(--base-size-32, 32px) !important}.mt-sm-5{margin-top:var(--base-size-32, 32px) !important}.mb-sm-5{margin-bottom:var(--base-size-32, 32px) !important}.mr-sm-5{margin-right:var(--base-size-32, 32px) !important}.ml-sm-5{margin-left:var(--base-size-32, 32px) !important}.mt-sm-n5{margin-top:calc(-1*var(--base-size-32, 32px)) !important}.mb-sm-n5{margin-bottom:calc(-1*var(--base-size-32, 32px)) !important}.mr-sm-n5{margin-right:calc(-1*var(--base-size-32, 32px)) !important}.ml-sm-n5{margin-left:calc(-1*var(--base-size-32, 32px)) !important}.mx-sm-5{margin-right:var(--base-size-32, 32px) !important;margin-left:var(--base-size-32, 32px) !important}.my-sm-5{margin-top:var(--base-size-32, 32px) !important;margin-bottom:var(--base-size-32, 32px) !important}.m-sm-6{margin:var(--base-size-40, 40px) !important}.mt-sm-6{margin-top:var(--base-size-40, 40px) !important}.mb-sm-6{margin-bottom:var(--base-size-40, 40px) !important}.mr-sm-6{margin-right:var(--base-size-40, 40px) !important}.ml-sm-6{margin-left:var(--base-size-40, 40px) !important}.mt-sm-n6{margin-top:calc(-1*var(--base-size-40, 40px)) !important}.mb-sm-n6{margin-bottom:calc(-1*var(--base-size-40, 40px)) !important}.mr-sm-n6{margin-right:calc(-1*var(--base-size-40, 40px)) !important}.ml-sm-n6{margin-left:calc(-1*var(--base-size-40, 40px)) !important}.mx-sm-6{margin-right:var(--base-size-40, 40px) !important;margin-left:var(--base-size-40, 40px) !important}.my-sm-6{margin-top:var(--base-size-40, 40px) !important;margin-bottom:var(--base-size-40, 40px) !important}.mt-sm-7{margin-top:var(--base-size-48, 48px) !important}.mb-sm-7{margin-bottom:var(--base-size-48, 48px) !important}.mt-sm-n7{margin-top:calc(-1*var(--base-size-48, 48px)) !important}.mb-sm-n7{margin-bottom:calc(-1*var(--base-size-48, 48px)) !important}.my-sm-7{margin-top:var(--base-size-48, 48px) !important;margin-bottom:var(--base-size-48, 48px) !important}.mt-sm-8{margin-top:var(--base-size-64, 64px) !important}.mb-sm-8{margin-bottom:var(--base-size-64, 64px) !important}.mt-sm-n8{margin-top:calc(-1*var(--base-size-64, 64px)) !important}.mb-sm-n8{margin-bottom:calc(-1*var(--base-size-64, 64px)) !important}.my-sm-8{margin-top:var(--base-size-64, 64px) !important;margin-bottom:var(--base-size-64, 64px) !important}.mt-sm-9{margin-top:var(--base-size-80, 80px) !important}.mb-sm-9{margin-bottom:var(--base-size-80, 80px) !important}.mt-sm-n9{margin-top:calc(-1*var(--base-size-80, 80px)) !important}.mb-sm-n9{margin-bottom:calc(-1*var(--base-size-80, 80px)) !important}.my-sm-9{margin-top:var(--base-size-80, 80px) !important;margin-bottom:var(--base-size-80, 80px) !important}.mt-sm-10{margin-top:var(--base-size-96, 96px) !important}.mb-sm-10{margin-bottom:var(--base-size-96, 96px) !important}.mt-sm-n10{margin-top:calc(-1*var(--base-size-96, 96px)) !important}.mb-sm-n10{margin-bottom:calc(-1*var(--base-size-96, 96px)) !important}.my-sm-10{margin-top:var(--base-size-96, 96px) !important;margin-bottom:var(--base-size-96, 96px) !important}.mt-sm-11{margin-top:var(--base-size-112, 112px) !important}.mb-sm-11{margin-bottom:var(--base-size-112, 112px) !important}.mt-sm-n11{margin-top:calc(-1*var(--base-size-112, 112px)) !important}.mb-sm-n11{margin-bottom:calc(-1*var(--base-size-112, 112px)) !important}.my-sm-11{margin-top:var(--base-size-112, 112px) !important;margin-bottom:var(--base-size-112, 112px) !important}.mt-sm-12{margin-top:var(--base-size-128, 128px) !important}.mb-sm-12{margin-bottom:var(--base-size-128, 128px) !important}.mt-sm-n12{margin-top:calc(-1*var(--base-size-128, 128px)) !important}.mb-sm-n12{margin-bottom:calc(-1*var(--base-size-128, 128px)) !important}.my-sm-12{margin-top:var(--base-size-128, 128px) !important;margin-bottom:var(--base-size-128, 128px) !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}}@media(min-width: 768px){.m-md-0{margin:0 !important}.mt-md-0{margin-top:0 !important}.mb-md-0{margin-bottom:0 !important}.mr-md-0{margin-right:0 !important}.ml-md-0{margin-left:0 !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.m-md-1{margin:var(--base-size-4, 4px) !important}.mt-md-1{margin-top:var(--base-size-4, 4px) !important}.mb-md-1{margin-bottom:var(--base-size-4, 4px) !important}.mr-md-1{margin-right:var(--base-size-4, 4px) !important}.ml-md-1{margin-left:var(--base-size-4, 4px) !important}.mt-md-n1{margin-top:calc(-1*var(--base-size-4, 4px)) !important}.mb-md-n1{margin-bottom:calc(-1*var(--base-size-4, 4px)) !important}.mr-md-n1{margin-right:calc(-1*var(--base-size-4, 4px)) !important}.ml-md-n1{margin-left:calc(-1*var(--base-size-4, 4px)) !important}.mx-md-1{margin-right:var(--base-size-4, 4px) !important;margin-left:var(--base-size-4, 4px) !important}.my-md-1{margin-top:var(--base-size-4, 4px) !important;margin-bottom:var(--base-size-4, 4px) !important}.m-md-2{margin:var(--base-size-8, 8px) !important}.mt-md-2{margin-top:var(--base-size-8, 8px) !important}.mb-md-2{margin-bottom:var(--base-size-8, 8px) !important}.mr-md-2{margin-right:var(--base-size-8, 8px) !important}.ml-md-2{margin-left:var(--base-size-8, 8px) !important}.mt-md-n2{margin-top:calc(-1*var(--base-size-8, 8px)) !important}.mb-md-n2{margin-bottom:calc(-1*var(--base-size-8, 8px)) !important}.mr-md-n2{margin-right:calc(-1*var(--base-size-8, 8px)) !important}.ml-md-n2{margin-left:calc(-1*var(--base-size-8, 8px)) !important}.mx-md-2{margin-right:var(--base-size-8, 8px) !important;margin-left:var(--base-size-8, 8px) !important}.my-md-2{margin-top:var(--base-size-8, 8px) !important;margin-bottom:var(--base-size-8, 8px) !important}.m-md-3{margin:var(--base-size-16, 16px) !important}.mt-md-3{margin-top:var(--base-size-16, 16px) !important}.mb-md-3{margin-bottom:var(--base-size-16, 16px) !important}.mr-md-3{margin-right:var(--base-size-16, 16px) !important}.ml-md-3{margin-left:var(--base-size-16, 16px) !important}.mt-md-n3{margin-top:calc(-1*var(--base-size-16, 16px)) !important}.mb-md-n3{margin-bottom:calc(-1*var(--base-size-16, 16px)) !important}.mr-md-n3{margin-right:calc(-1*var(--base-size-16, 16px)) !important}.ml-md-n3{margin-left:calc(-1*var(--base-size-16, 16px)) !important}.mx-md-3{margin-right:var(--base-size-16, 16px) !important;margin-left:var(--base-size-16, 16px) !important}.my-md-3{margin-top:var(--base-size-16, 16px) !important;margin-bottom:var(--base-size-16, 16px) !important}.m-md-4{margin:var(--base-size-24, 24px) !important}.mt-md-4{margin-top:var(--base-size-24, 24px) !important}.mb-md-4{margin-bottom:var(--base-size-24, 24px) !important}.mr-md-4{margin-right:var(--base-size-24, 24px) !important}.ml-md-4{margin-left:var(--base-size-24, 24px) !important}.mt-md-n4{margin-top:calc(-1*var(--base-size-24, 24px)) !important}.mb-md-n4{margin-bottom:calc(-1*var(--base-size-24, 24px)) !important}.mr-md-n4{margin-right:calc(-1*var(--base-size-24, 24px)) !important}.ml-md-n4{margin-left:calc(-1*var(--base-size-24, 24px)) !important}.mx-md-4{margin-right:var(--base-size-24, 24px) !important;margin-left:var(--base-size-24, 24px) !important}.my-md-4{margin-top:var(--base-size-24, 24px) !important;margin-bottom:var(--base-size-24, 24px) !important}.m-md-5{margin:var(--base-size-32, 32px) !important}.mt-md-5{margin-top:var(--base-size-32, 32px) !important}.mb-md-5{margin-bottom:var(--base-size-32, 32px) !important}.mr-md-5{margin-right:var(--base-size-32, 32px) !important}.ml-md-5{margin-left:var(--base-size-32, 32px) !important}.mt-md-n5{margin-top:calc(-1*var(--base-size-32, 32px)) !important}.mb-md-n5{margin-bottom:calc(-1*var(--base-size-32, 32px)) !important}.mr-md-n5{margin-right:calc(-1*var(--base-size-32, 32px)) !important}.ml-md-n5{margin-left:calc(-1*var(--base-size-32, 32px)) !important}.mx-md-5{margin-right:var(--base-size-32, 32px) !important;margin-left:var(--base-size-32, 32px) !important}.my-md-5{margin-top:var(--base-size-32, 32px) !important;margin-bottom:var(--base-size-32, 32px) !important}.m-md-6{margin:var(--base-size-40, 40px) !important}.mt-md-6{margin-top:var(--base-size-40, 40px) !important}.mb-md-6{margin-bottom:var(--base-size-40, 40px) !important}.mr-md-6{margin-right:var(--base-size-40, 40px) !important}.ml-md-6{margin-left:var(--base-size-40, 40px) !important}.mt-md-n6{margin-top:calc(-1*var(--base-size-40, 40px)) !important}.mb-md-n6{margin-bottom:calc(-1*var(--base-size-40, 40px)) !important}.mr-md-n6{margin-right:calc(-1*var(--base-size-40, 40px)) !important}.ml-md-n6{margin-left:calc(-1*var(--base-size-40, 40px)) !important}.mx-md-6{margin-right:var(--base-size-40, 40px) !important;margin-left:var(--base-size-40, 40px) !important}.my-md-6{margin-top:var(--base-size-40, 40px) !important;margin-bottom:var(--base-size-40, 40px) !important}.mt-md-7{margin-top:var(--base-size-48, 48px) !important}.mb-md-7{margin-bottom:var(--base-size-48, 48px) !important}.mt-md-n7{margin-top:calc(-1*var(--base-size-48, 48px)) !important}.mb-md-n7{margin-bottom:calc(-1*var(--base-size-48, 48px)) !important}.my-md-7{margin-top:var(--base-size-48, 48px) !important;margin-bottom:var(--base-size-48, 48px) !important}.mt-md-8{margin-top:var(--base-size-64, 64px) !important}.mb-md-8{margin-bottom:var(--base-size-64, 64px) !important}.mt-md-n8{margin-top:calc(-1*var(--base-size-64, 64px)) !important}.mb-md-n8{margin-bottom:calc(-1*var(--base-size-64, 64px)) !important}.my-md-8{margin-top:var(--base-size-64, 64px) !important;margin-bottom:var(--base-size-64, 64px) !important}.mt-md-9{margin-top:var(--base-size-80, 80px) !important}.mb-md-9{margin-bottom:var(--base-size-80, 80px) !important}.mt-md-n9{margin-top:calc(-1*var(--base-size-80, 80px)) !important}.mb-md-n9{margin-bottom:calc(-1*var(--base-size-80, 80px)) !important}.my-md-9{margin-top:var(--base-size-80, 80px) !important;margin-bottom:var(--base-size-80, 80px) !important}.mt-md-10{margin-top:var(--base-size-96, 96px) !important}.mb-md-10{margin-bottom:var(--base-size-96, 96px) !important}.mt-md-n10{margin-top:calc(-1*var(--base-size-96, 96px)) !important}.mb-md-n10{margin-bottom:calc(-1*var(--base-size-96, 96px)) !important}.my-md-10{margin-top:var(--base-size-96, 96px) !important;margin-bottom:var(--base-size-96, 96px) !important}.mt-md-11{margin-top:var(--base-size-112, 112px) !important}.mb-md-11{margin-bottom:var(--base-size-112, 112px) !important}.mt-md-n11{margin-top:calc(-1*var(--base-size-112, 112px)) !important}.mb-md-n11{margin-bottom:calc(-1*var(--base-size-112, 112px)) !important}.my-md-11{margin-top:var(--base-size-112, 112px) !important;margin-bottom:var(--base-size-112, 112px) !important}.mt-md-12{margin-top:var(--base-size-128, 128px) !important}.mb-md-12{margin-bottom:var(--base-size-128, 128px) !important}.mt-md-n12{margin-top:calc(-1*var(--base-size-128, 128px)) !important}.mb-md-n12{margin-bottom:calc(-1*var(--base-size-128, 128px)) !important}.my-md-12{margin-top:var(--base-size-128, 128px) !important;margin-bottom:var(--base-size-128, 128px) !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}}@media(min-width: 1012px){.m-lg-0{margin:0 !important}.mt-lg-0{margin-top:0 !important}.mb-lg-0{margin-bottom:0 !important}.mr-lg-0{margin-right:0 !important}.ml-lg-0{margin-left:0 !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.m-lg-1{margin:var(--base-size-4, 4px) !important}.mt-lg-1{margin-top:var(--base-size-4, 4px) !important}.mb-lg-1{margin-bottom:var(--base-size-4, 4px) !important}.mr-lg-1{margin-right:var(--base-size-4, 4px) !important}.ml-lg-1{margin-left:var(--base-size-4, 4px) !important}.mt-lg-n1{margin-top:calc(-1*var(--base-size-4, 4px)) !important}.mb-lg-n1{margin-bottom:calc(-1*var(--base-size-4, 4px)) !important}.mr-lg-n1{margin-right:calc(-1*var(--base-size-4, 4px)) !important}.ml-lg-n1{margin-left:calc(-1*var(--base-size-4, 4px)) !important}.mx-lg-1{margin-right:var(--base-size-4, 4px) !important;margin-left:var(--base-size-4, 4px) !important}.my-lg-1{margin-top:var(--base-size-4, 4px) !important;margin-bottom:var(--base-size-4, 4px) !important}.m-lg-2{margin:var(--base-size-8, 8px) !important}.mt-lg-2{margin-top:var(--base-size-8, 8px) !important}.mb-lg-2{margin-bottom:var(--base-size-8, 8px) !important}.mr-lg-2{margin-right:var(--base-size-8, 8px) !important}.ml-lg-2{margin-left:var(--base-size-8, 8px) !important}.mt-lg-n2{margin-top:calc(-1*var(--base-size-8, 8px)) !important}.mb-lg-n2{margin-bottom:calc(-1*var(--base-size-8, 8px)) !important}.mr-lg-n2{margin-right:calc(-1*var(--base-size-8, 8px)) !important}.ml-lg-n2{margin-left:calc(-1*var(--base-size-8, 8px)) !important}.mx-lg-2{margin-right:var(--base-size-8, 8px) !important;margin-left:var(--base-size-8, 8px) !important}.my-lg-2{margin-top:var(--base-size-8, 8px) !important;margin-bottom:var(--base-size-8, 8px) !important}.m-lg-3{margin:var(--base-size-16, 16px) !important}.mt-lg-3{margin-top:var(--base-size-16, 16px) !important}.mb-lg-3{margin-bottom:var(--base-size-16, 16px) !important}.mr-lg-3{margin-right:var(--base-size-16, 16px) !important}.ml-lg-3{margin-left:var(--base-size-16, 16px) !important}.mt-lg-n3{margin-top:calc(-1*var(--base-size-16, 16px)) !important}.mb-lg-n3{margin-bottom:calc(-1*var(--base-size-16, 16px)) !important}.mr-lg-n3{margin-right:calc(-1*var(--base-size-16, 16px)) !important}.ml-lg-n3{margin-left:calc(-1*var(--base-size-16, 16px)) !important}.mx-lg-3{margin-right:var(--base-size-16, 16px) !important;margin-left:var(--base-size-16, 16px) !important}.my-lg-3{margin-top:var(--base-size-16, 16px) !important;margin-bottom:var(--base-size-16, 16px) !important}.m-lg-4{margin:var(--base-size-24, 24px) !important}.mt-lg-4{margin-top:var(--base-size-24, 24px) !important}.mb-lg-4{margin-bottom:var(--base-size-24, 24px) !important}.mr-lg-4{margin-right:var(--base-size-24, 24px) !important}.ml-lg-4{margin-left:var(--base-size-24, 24px) !important}.mt-lg-n4{margin-top:calc(-1*var(--base-size-24, 24px)) !important}.mb-lg-n4{margin-bottom:calc(-1*var(--base-size-24, 24px)) !important}.mr-lg-n4{margin-right:calc(-1*var(--base-size-24, 24px)) !important}.ml-lg-n4{margin-left:calc(-1*var(--base-size-24, 24px)) !important}.mx-lg-4{margin-right:var(--base-size-24, 24px) !important;margin-left:var(--base-size-24, 24px) !important}.my-lg-4{margin-top:var(--base-size-24, 24px) !important;margin-bottom:var(--base-size-24, 24px) !important}.m-lg-5{margin:var(--base-size-32, 32px) !important}.mt-lg-5{margin-top:var(--base-size-32, 32px) !important}.mb-lg-5{margin-bottom:var(--base-size-32, 32px) !important}.mr-lg-5{margin-right:var(--base-size-32, 32px) !important}.ml-lg-5{margin-left:var(--base-size-32, 32px) !important}.mt-lg-n5{margin-top:calc(-1*var(--base-size-32, 32px)) !important}.mb-lg-n5{margin-bottom:calc(-1*var(--base-size-32, 32px)) !important}.mr-lg-n5{margin-right:calc(-1*var(--base-size-32, 32px)) !important}.ml-lg-n5{margin-left:calc(-1*var(--base-size-32, 32px)) !important}.mx-lg-5{margin-right:var(--base-size-32, 32px) !important;margin-left:var(--base-size-32, 32px) !important}.my-lg-5{margin-top:var(--base-size-32, 32px) !important;margin-bottom:var(--base-size-32, 32px) !important}.m-lg-6{margin:var(--base-size-40, 40px) !important}.mt-lg-6{margin-top:var(--base-size-40, 40px) !important}.mb-lg-6{margin-bottom:var(--base-size-40, 40px) !important}.mr-lg-6{margin-right:var(--base-size-40, 40px) !important}.ml-lg-6{margin-left:var(--base-size-40, 40px) !important}.mt-lg-n6{margin-top:calc(-1*var(--base-size-40, 40px)) !important}.mb-lg-n6{margin-bottom:calc(-1*var(--base-size-40, 40px)) !important}.mr-lg-n6{margin-right:calc(-1*var(--base-size-40, 40px)) !important}.ml-lg-n6{margin-left:calc(-1*var(--base-size-40, 40px)) !important}.mx-lg-6{margin-right:var(--base-size-40, 40px) !important;margin-left:var(--base-size-40, 40px) !important}.my-lg-6{margin-top:var(--base-size-40, 40px) !important;margin-bottom:var(--base-size-40, 40px) !important}.mt-lg-7{margin-top:var(--base-size-48, 48px) !important}.mb-lg-7{margin-bottom:var(--base-size-48, 48px) !important}.mt-lg-n7{margin-top:calc(-1*var(--base-size-48, 48px)) !important}.mb-lg-n7{margin-bottom:calc(-1*var(--base-size-48, 48px)) !important}.my-lg-7{margin-top:var(--base-size-48, 48px) !important;margin-bottom:var(--base-size-48, 48px) !important}.mt-lg-8{margin-top:var(--base-size-64, 64px) !important}.mb-lg-8{margin-bottom:var(--base-size-64, 64px) !important}.mt-lg-n8{margin-top:calc(-1*var(--base-size-64, 64px)) !important}.mb-lg-n8{margin-bottom:calc(-1*var(--base-size-64, 64px)) !important}.my-lg-8{margin-top:var(--base-size-64, 64px) !important;margin-bottom:var(--base-size-64, 64px) !important}.mt-lg-9{margin-top:var(--base-size-80, 80px) !important}.mb-lg-9{margin-bottom:var(--base-size-80, 80px) !important}.mt-lg-n9{margin-top:calc(-1*var(--base-size-80, 80px)) !important}.mb-lg-n9{margin-bottom:calc(-1*var(--base-size-80, 80px)) !important}.my-lg-9{margin-top:var(--base-size-80, 80px) !important;margin-bottom:var(--base-size-80, 80px) !important}.mt-lg-10{margin-top:var(--base-size-96, 96px) !important}.mb-lg-10{margin-bottom:var(--base-size-96, 96px) !important}.mt-lg-n10{margin-top:calc(-1*var(--base-size-96, 96px)) !important}.mb-lg-n10{margin-bottom:calc(-1*var(--base-size-96, 96px)) !important}.my-lg-10{margin-top:var(--base-size-96, 96px) !important;margin-bottom:var(--base-size-96, 96px) !important}.mt-lg-11{margin-top:var(--base-size-112, 112px) !important}.mb-lg-11{margin-bottom:var(--base-size-112, 112px) !important}.mt-lg-n11{margin-top:calc(-1*var(--base-size-112, 112px)) !important}.mb-lg-n11{margin-bottom:calc(-1*var(--base-size-112, 112px)) !important}.my-lg-11{margin-top:var(--base-size-112, 112px) !important;margin-bottom:var(--base-size-112, 112px) !important}.mt-lg-12{margin-top:var(--base-size-128, 128px) !important}.mb-lg-12{margin-bottom:var(--base-size-128, 128px) !important}.mt-lg-n12{margin-top:calc(-1*var(--base-size-128, 128px)) !important}.mb-lg-n12{margin-bottom:calc(-1*var(--base-size-128, 128px)) !important}.my-lg-12{margin-top:var(--base-size-128, 128px) !important;margin-bottom:var(--base-size-128, 128px) !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}}@media(min-width: 1280px){.m-xl-0{margin:0 !important}.mt-xl-0{margin-top:0 !important}.mb-xl-0{margin-bottom:0 !important}.mr-xl-0{margin-right:0 !important}.ml-xl-0{margin-left:0 !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.m-xl-1{margin:var(--base-size-4, 4px) !important}.mt-xl-1{margin-top:var(--base-size-4, 4px) !important}.mb-xl-1{margin-bottom:var(--base-size-4, 4px) !important}.mr-xl-1{margin-right:var(--base-size-4, 4px) !important}.ml-xl-1{margin-left:var(--base-size-4, 4px) !important}.mt-xl-n1{margin-top:calc(-1*var(--base-size-4, 4px)) !important}.mb-xl-n1{margin-bottom:calc(-1*var(--base-size-4, 4px)) !important}.mr-xl-n1{margin-right:calc(-1*var(--base-size-4, 4px)) !important}.ml-xl-n1{margin-left:calc(-1*var(--base-size-4, 4px)) !important}.mx-xl-1{margin-right:var(--base-size-4, 4px) !important;margin-left:var(--base-size-4, 4px) !important}.my-xl-1{margin-top:var(--base-size-4, 4px) !important;margin-bottom:var(--base-size-4, 4px) !important}.m-xl-2{margin:var(--base-size-8, 8px) !important}.mt-xl-2{margin-top:var(--base-size-8, 8px) !important}.mb-xl-2{margin-bottom:var(--base-size-8, 8px) !important}.mr-xl-2{margin-right:var(--base-size-8, 8px) !important}.ml-xl-2{margin-left:var(--base-size-8, 8px) !important}.mt-xl-n2{margin-top:calc(-1*var(--base-size-8, 8px)) !important}.mb-xl-n2{margin-bottom:calc(-1*var(--base-size-8, 8px)) !important}.mr-xl-n2{margin-right:calc(-1*var(--base-size-8, 8px)) !important}.ml-xl-n2{margin-left:calc(-1*var(--base-size-8, 8px)) !important}.mx-xl-2{margin-right:var(--base-size-8, 8px) !important;margin-left:var(--base-size-8, 8px) !important}.my-xl-2{margin-top:var(--base-size-8, 8px) !important;margin-bottom:var(--base-size-8, 8px) !important}.m-xl-3{margin:var(--base-size-16, 16px) !important}.mt-xl-3{margin-top:var(--base-size-16, 16px) !important}.mb-xl-3{margin-bottom:var(--base-size-16, 16px) !important}.mr-xl-3{margin-right:var(--base-size-16, 16px) !important}.ml-xl-3{margin-left:var(--base-size-16, 16px) !important}.mt-xl-n3{margin-top:calc(-1*var(--base-size-16, 16px)) !important}.mb-xl-n3{margin-bottom:calc(-1*var(--base-size-16, 16px)) !important}.mr-xl-n3{margin-right:calc(-1*var(--base-size-16, 16px)) !important}.ml-xl-n3{margin-left:calc(-1*var(--base-size-16, 16px)) !important}.mx-xl-3{margin-right:var(--base-size-16, 16px) !important;margin-left:var(--base-size-16, 16px) !important}.my-xl-3{margin-top:var(--base-size-16, 16px) !important;margin-bottom:var(--base-size-16, 16px) !important}.m-xl-4{margin:var(--base-size-24, 24px) !important}.mt-xl-4{margin-top:var(--base-size-24, 24px) !important}.mb-xl-4{margin-bottom:var(--base-size-24, 24px) !important}.mr-xl-4{margin-right:var(--base-size-24, 24px) !important}.ml-xl-4{margin-left:var(--base-size-24, 24px) !important}.mt-xl-n4{margin-top:calc(-1*var(--base-size-24, 24px)) !important}.mb-xl-n4{margin-bottom:calc(-1*var(--base-size-24, 24px)) !important}.mr-xl-n4{margin-right:calc(-1*var(--base-size-24, 24px)) !important}.ml-xl-n4{margin-left:calc(-1*var(--base-size-24, 24px)) !important}.mx-xl-4{margin-right:var(--base-size-24, 24px) !important;margin-left:var(--base-size-24, 24px) !important}.my-xl-4{margin-top:var(--base-size-24, 24px) !important;margin-bottom:var(--base-size-24, 24px) !important}.m-xl-5{margin:var(--base-size-32, 32px) !important}.mt-xl-5{margin-top:var(--base-size-32, 32px) !important}.mb-xl-5{margin-bottom:var(--base-size-32, 32px) !important}.mr-xl-5{margin-right:var(--base-size-32, 32px) !important}.ml-xl-5{margin-left:var(--base-size-32, 32px) !important}.mt-xl-n5{margin-top:calc(-1*var(--base-size-32, 32px)) !important}.mb-xl-n5{margin-bottom:calc(-1*var(--base-size-32, 32px)) !important}.mr-xl-n5{margin-right:calc(-1*var(--base-size-32, 32px)) !important}.ml-xl-n5{margin-left:calc(-1*var(--base-size-32, 32px)) !important}.mx-xl-5{margin-right:var(--base-size-32, 32px) !important;margin-left:var(--base-size-32, 32px) !important}.my-xl-5{margin-top:var(--base-size-32, 32px) !important;margin-bottom:var(--base-size-32, 32px) !important}.m-xl-6{margin:var(--base-size-40, 40px) !important}.mt-xl-6{margin-top:var(--base-size-40, 40px) !important}.mb-xl-6{margin-bottom:var(--base-size-40, 40px) !important}.mr-xl-6{margin-right:var(--base-size-40, 40px) !important}.ml-xl-6{margin-left:var(--base-size-40, 40px) !important}.mt-xl-n6{margin-top:calc(-1*var(--base-size-40, 40px)) !important}.mb-xl-n6{margin-bottom:calc(-1*var(--base-size-40, 40px)) !important}.mr-xl-n6{margin-right:calc(-1*var(--base-size-40, 40px)) !important}.ml-xl-n6{margin-left:calc(-1*var(--base-size-40, 40px)) !important}.mx-xl-6{margin-right:var(--base-size-40, 40px) !important;margin-left:var(--base-size-40, 40px) !important}.my-xl-6{margin-top:var(--base-size-40, 40px) !important;margin-bottom:var(--base-size-40, 40px) !important}.mt-xl-7{margin-top:var(--base-size-48, 48px) !important}.mb-xl-7{margin-bottom:var(--base-size-48, 48px) !important}.mt-xl-n7{margin-top:calc(-1*var(--base-size-48, 48px)) !important}.mb-xl-n7{margin-bottom:calc(-1*var(--base-size-48, 48px)) !important}.my-xl-7{margin-top:var(--base-size-48, 48px) !important;margin-bottom:var(--base-size-48, 48px) !important}.mt-xl-8{margin-top:var(--base-size-64, 64px) !important}.mb-xl-8{margin-bottom:var(--base-size-64, 64px) !important}.mt-xl-n8{margin-top:calc(-1*var(--base-size-64, 64px)) !important}.mb-xl-n8{margin-bottom:calc(-1*var(--base-size-64, 64px)) !important}.my-xl-8{margin-top:var(--base-size-64, 64px) !important;margin-bottom:var(--base-size-64, 64px) !important}.mt-xl-9{margin-top:var(--base-size-80, 80px) !important}.mb-xl-9{margin-bottom:var(--base-size-80, 80px) !important}.mt-xl-n9{margin-top:calc(-1*var(--base-size-80, 80px)) !important}.mb-xl-n9{margin-bottom:calc(-1*var(--base-size-80, 80px)) !important}.my-xl-9{margin-top:var(--base-size-80, 80px) !important;margin-bottom:var(--base-size-80, 80px) !important}.mt-xl-10{margin-top:var(--base-size-96, 96px) !important}.mb-xl-10{margin-bottom:var(--base-size-96, 96px) !important}.mt-xl-n10{margin-top:calc(-1*var(--base-size-96, 96px)) !important}.mb-xl-n10{margin-bottom:calc(-1*var(--base-size-96, 96px)) !important}.my-xl-10{margin-top:var(--base-size-96, 96px) !important;margin-bottom:var(--base-size-96, 96px) !important}.mt-xl-11{margin-top:var(--base-size-112, 112px) !important}.mb-xl-11{margin-bottom:var(--base-size-112, 112px) !important}.mt-xl-n11{margin-top:calc(-1*var(--base-size-112, 112px)) !important}.mb-xl-n11{margin-bottom:calc(-1*var(--base-size-112, 112px)) !important}.my-xl-11{margin-top:var(--base-size-112, 112px) !important;margin-bottom:var(--base-size-112, 112px) !important}.mt-xl-12{margin-top:var(--base-size-128, 128px) !important}.mb-xl-12{margin-bottom:var(--base-size-128, 128px) !important}.mt-xl-n12{margin-top:calc(-1*var(--base-size-128, 128px)) !important}.mb-xl-n12{margin-bottom:calc(-1*var(--base-size-128, 128px)) !important}.my-xl-12{margin-top:var(--base-size-128, 128px) !important;margin-bottom:var(--base-size-128, 128px) !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-right:0 !important;padding-left:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:var(--base-size-4, 4px) !important}.pt-1{padding-top:var(--base-size-4, 4px) !important}.pr-1{padding-right:var(--base-size-4, 4px) !important}.pb-1{padding-bottom:var(--base-size-4, 4px) !important}.pl-1{padding-left:var(--base-size-4, 4px) !important}.px-1{padding-right:var(--base-size-4, 4px) !important;padding-left:var(--base-size-4, 4px) !important}.py-1{padding-top:var(--base-size-4, 4px) !important;padding-bottom:var(--base-size-4, 4px) !important}.p-2{padding:var(--base-size-8, 8px) !important}.pt-2{padding-top:var(--base-size-8, 8px) !important}.pr-2{padding-right:var(--base-size-8, 8px) !important}.pb-2{padding-bottom:var(--base-size-8, 8px) !important}.pl-2{padding-left:var(--base-size-8, 8px) !important}.px-2{padding-right:var(--base-size-8, 8px) !important;padding-left:var(--base-size-8, 8px) !important}.py-2{padding-top:var(--base-size-8, 8px) !important;padding-bottom:var(--base-size-8, 8px) !important}.p-3{padding:var(--base-size-16, 16px) !important}.pt-3{padding-top:var(--base-size-16, 16px) !important}.pr-3{padding-right:var(--base-size-16, 16px) !important}.pb-3{padding-bottom:var(--base-size-16, 16px) !important}.pl-3{padding-left:var(--base-size-16, 16px) !important}.px-3{padding-right:var(--base-size-16, 16px) !important;padding-left:var(--base-size-16, 16px) !important}.py-3{padding-top:var(--base-size-16, 16px) !important;padding-bottom:var(--base-size-16, 16px) !important}.p-4{padding:var(--base-size-24, 24px) !important}.pt-4{padding-top:var(--base-size-24, 24px) !important}.pr-4{padding-right:var(--base-size-24, 24px) !important}.pb-4{padding-bottom:var(--base-size-24, 24px) !important}.pl-4{padding-left:var(--base-size-24, 24px) !important}.px-4{padding-right:var(--base-size-24, 24px) !important;padding-left:var(--base-size-24, 24px) !important}.py-4{padding-top:var(--base-size-24, 24px) !important;padding-bottom:var(--base-size-24, 24px) !important}.p-5{padding:var(--base-size-32, 32px) !important}.pt-5{padding-top:var(--base-size-32, 32px) !important}.pr-5{padding-right:var(--base-size-32, 32px) !important}.pb-5{padding-bottom:var(--base-size-32, 32px) !important}.pl-5{padding-left:var(--base-size-32, 32px) !important}.px-5{padding-right:var(--base-size-32, 32px) !important;padding-left:var(--base-size-32, 32px) !important}.py-5{padding-top:var(--base-size-32, 32px) !important;padding-bottom:var(--base-size-32, 32px) !important}.p-6{padding:var(--base-size-40, 40px) !important}.pt-6{padding-top:var(--base-size-40, 40px) !important}.pr-6{padding-right:var(--base-size-40, 40px) !important}.pb-6{padding-bottom:var(--base-size-40, 40px) !important}.pl-6{padding-left:var(--base-size-40, 40px) !important}.px-6{padding-right:var(--base-size-40, 40px) !important;padding-left:var(--base-size-40, 40px) !important}.py-6{padding-top:var(--base-size-40, 40px) !important;padding-bottom:var(--base-size-40, 40px) !important}.pt-7{padding-top:var(--base-size-48, 48px) !important}.pr-7{padding-right:var(--base-size-48, 48px) !important}.pb-7{padding-bottom:var(--base-size-48, 48px) !important}.pl-7{padding-left:var(--base-size-48, 48px) !important}.py-7{padding-top:var(--base-size-48, 48px) !important;padding-bottom:var(--base-size-48, 48px) !important}.pt-8{padding-top:var(--base-size-64, 64px) !important}.pr-8{padding-right:var(--base-size-64, 64px) !important}.pb-8{padding-bottom:var(--base-size-64, 64px) !important}.pl-8{padding-left:var(--base-size-64, 64px) !important}.py-8{padding-top:var(--base-size-64, 64px) !important;padding-bottom:var(--base-size-64, 64px) !important}.pt-9{padding-top:var(--base-size-80, 80px) !important}.pr-9{padding-right:var(--base-size-80, 80px) !important}.pb-9{padding-bottom:var(--base-size-80, 80px) !important}.pl-9{padding-left:var(--base-size-80, 80px) !important}.py-9{padding-top:var(--base-size-80, 80px) !important;padding-bottom:var(--base-size-80, 80px) !important}.pt-10{padding-top:var(--base-size-96, 96px) !important}.pr-10{padding-right:var(--base-size-96, 96px) !important}.pb-10{padding-bottom:var(--base-size-96, 96px) !important}.pl-10{padding-left:var(--base-size-96, 96px) !important}.py-10{padding-top:var(--base-size-96, 96px) !important;padding-bottom:var(--base-size-96, 96px) !important}.pt-11{padding-top:var(--base-size-112, 112px) !important}.pr-11{padding-right:var(--base-size-112, 112px) !important}.pb-11{padding-bottom:var(--base-size-112, 112px) !important}.pl-11{padding-left:var(--base-size-112, 112px) !important}.py-11{padding-top:var(--base-size-112, 112px) !important;padding-bottom:var(--base-size-112, 112px) !important}.pt-12{padding-top:var(--base-size-128, 128px) !important}.pr-12{padding-right:var(--base-size-128, 128px) !important}.pb-12{padding-bottom:var(--base-size-128, 128px) !important}.pl-12{padding-left:var(--base-size-128, 128px) !important}.py-12{padding-top:var(--base-size-128, 128px) !important;padding-bottom:var(--base-size-128, 128px) !important}@media(min-width: 544px){.p-sm-0{padding:0 !important}.pt-sm-0{padding-top:0 !important}.pr-sm-0{padding-right:0 !important}.pb-sm-0{padding-bottom:0 !important}.pl-sm-0{padding-left:0 !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.p-sm-1{padding:var(--base-size-4, 4px) !important}.pt-sm-1{padding-top:var(--base-size-4, 4px) !important}.pr-sm-1{padding-right:var(--base-size-4, 4px) !important}.pb-sm-1{padding-bottom:var(--base-size-4, 4px) !important}.pl-sm-1{padding-left:var(--base-size-4, 4px) !important}.px-sm-1{padding-right:var(--base-size-4, 4px) !important;padding-left:var(--base-size-4, 4px) !important}.py-sm-1{padding-top:var(--base-size-4, 4px) !important;padding-bottom:var(--base-size-4, 4px) !important}.p-sm-2{padding:var(--base-size-8, 8px) !important}.pt-sm-2{padding-top:var(--base-size-8, 8px) !important}.pr-sm-2{padding-right:var(--base-size-8, 8px) !important}.pb-sm-2{padding-bottom:var(--base-size-8, 8px) !important}.pl-sm-2{padding-left:var(--base-size-8, 8px) !important}.px-sm-2{padding-right:var(--base-size-8, 8px) !important;padding-left:var(--base-size-8, 8px) !important}.py-sm-2{padding-top:var(--base-size-8, 8px) !important;padding-bottom:var(--base-size-8, 8px) !important}.p-sm-3{padding:var(--base-size-16, 16px) !important}.pt-sm-3{padding-top:var(--base-size-16, 16px) !important}.pr-sm-3{padding-right:var(--base-size-16, 16px) !important}.pb-sm-3{padding-bottom:var(--base-size-16, 16px) !important}.pl-sm-3{padding-left:var(--base-size-16, 16px) !important}.px-sm-3{padding-right:var(--base-size-16, 16px) !important;padding-left:var(--base-size-16, 16px) !important}.py-sm-3{padding-top:var(--base-size-16, 16px) !important;padding-bottom:var(--base-size-16, 16px) !important}.p-sm-4{padding:var(--base-size-24, 24px) !important}.pt-sm-4{padding-top:var(--base-size-24, 24px) !important}.pr-sm-4{padding-right:var(--base-size-24, 24px) !important}.pb-sm-4{padding-bottom:var(--base-size-24, 24px) !important}.pl-sm-4{padding-left:var(--base-size-24, 24px) !important}.px-sm-4{padding-right:var(--base-size-24, 24px) !important;padding-left:var(--base-size-24, 24px) !important}.py-sm-4{padding-top:var(--base-size-24, 24px) !important;padding-bottom:var(--base-size-24, 24px) !important}.p-sm-5{padding:var(--base-size-32, 32px) !important}.pt-sm-5{padding-top:var(--base-size-32, 32px) !important}.pr-sm-5{padding-right:var(--base-size-32, 32px) !important}.pb-sm-5{padding-bottom:var(--base-size-32, 32px) !important}.pl-sm-5{padding-left:var(--base-size-32, 32px) !important}.px-sm-5{padding-right:var(--base-size-32, 32px) !important;padding-left:var(--base-size-32, 32px) !important}.py-sm-5{padding-top:var(--base-size-32, 32px) !important;padding-bottom:var(--base-size-32, 32px) !important}.p-sm-6{padding:var(--base-size-40, 40px) !important}.pt-sm-6{padding-top:var(--base-size-40, 40px) !important}.pr-sm-6{padding-right:var(--base-size-40, 40px) !important}.pb-sm-6{padding-bottom:var(--base-size-40, 40px) !important}.pl-sm-6{padding-left:var(--base-size-40, 40px) !important}.px-sm-6{padding-right:var(--base-size-40, 40px) !important;padding-left:var(--base-size-40, 40px) !important}.py-sm-6{padding-top:var(--base-size-40, 40px) !important;padding-bottom:var(--base-size-40, 40px) !important}.pt-sm-7{padding-top:var(--base-size-48, 48px) !important}.pr-sm-7{padding-right:var(--base-size-48, 48px) !important}.pb-sm-7{padding-bottom:var(--base-size-48, 48px) !important}.pl-sm-7{padding-left:var(--base-size-48, 48px) !important}.py-sm-7{padding-top:var(--base-size-48, 48px) !important;padding-bottom:var(--base-size-48, 48px) !important}.pt-sm-8{padding-top:var(--base-size-64, 64px) !important}.pr-sm-8{padding-right:var(--base-size-64, 64px) !important}.pb-sm-8{padding-bottom:var(--base-size-64, 64px) !important}.pl-sm-8{padding-left:var(--base-size-64, 64px) !important}.py-sm-8{padding-top:var(--base-size-64, 64px) !important;padding-bottom:var(--base-size-64, 64px) !important}.pt-sm-9{padding-top:var(--base-size-80, 80px) !important}.pr-sm-9{padding-right:var(--base-size-80, 80px) !important}.pb-sm-9{padding-bottom:var(--base-size-80, 80px) !important}.pl-sm-9{padding-left:var(--base-size-80, 80px) !important}.py-sm-9{padding-top:var(--base-size-80, 80px) !important;padding-bottom:var(--base-size-80, 80px) !important}.pt-sm-10{padding-top:var(--base-size-96, 96px) !important}.pr-sm-10{padding-right:var(--base-size-96, 96px) !important}.pb-sm-10{padding-bottom:var(--base-size-96, 96px) !important}.pl-sm-10{padding-left:var(--base-size-96, 96px) !important}.py-sm-10{padding-top:var(--base-size-96, 96px) !important;padding-bottom:var(--base-size-96, 96px) !important}.pt-sm-11{padding-top:var(--base-size-112, 112px) !important}.pr-sm-11{padding-right:var(--base-size-112, 112px) !important}.pb-sm-11{padding-bottom:var(--base-size-112, 112px) !important}.pl-sm-11{padding-left:var(--base-size-112, 112px) !important}.py-sm-11{padding-top:var(--base-size-112, 112px) !important;padding-bottom:var(--base-size-112, 112px) !important}.pt-sm-12{padding-top:var(--base-size-128, 128px) !important}.pr-sm-12{padding-right:var(--base-size-128, 128px) !important}.pb-sm-12{padding-bottom:var(--base-size-128, 128px) !important}.pl-sm-12{padding-left:var(--base-size-128, 128px) !important}.py-sm-12{padding-top:var(--base-size-128, 128px) !important;padding-bottom:var(--base-size-128, 128px) !important}}@media(min-width: 768px){.p-md-0{padding:0 !important}.pt-md-0{padding-top:0 !important}.pr-md-0{padding-right:0 !important}.pb-md-0{padding-bottom:0 !important}.pl-md-0{padding-left:0 !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.p-md-1{padding:var(--base-size-4, 4px) !important}.pt-md-1{padding-top:var(--base-size-4, 4px) !important}.pr-md-1{padding-right:var(--base-size-4, 4px) !important}.pb-md-1{padding-bottom:var(--base-size-4, 4px) !important}.pl-md-1{padding-left:var(--base-size-4, 4px) !important}.px-md-1{padding-right:var(--base-size-4, 4px) !important;padding-left:var(--base-size-4, 4px) !important}.py-md-1{padding-top:var(--base-size-4, 4px) !important;padding-bottom:var(--base-size-4, 4px) !important}.p-md-2{padding:var(--base-size-8, 8px) !important}.pt-md-2{padding-top:var(--base-size-8, 8px) !important}.pr-md-2{padding-right:var(--base-size-8, 8px) !important}.pb-md-2{padding-bottom:var(--base-size-8, 8px) !important}.pl-md-2{padding-left:var(--base-size-8, 8px) !important}.px-md-2{padding-right:var(--base-size-8, 8px) !important;padding-left:var(--base-size-8, 8px) !important}.py-md-2{padding-top:var(--base-size-8, 8px) !important;padding-bottom:var(--base-size-8, 8px) !important}.p-md-3{padding:var(--base-size-16, 16px) !important}.pt-md-3{padding-top:var(--base-size-16, 16px) !important}.pr-md-3{padding-right:var(--base-size-16, 16px) !important}.pb-md-3{padding-bottom:var(--base-size-16, 16px) !important}.pl-md-3{padding-left:var(--base-size-16, 16px) !important}.px-md-3{padding-right:var(--base-size-16, 16px) !important;padding-left:var(--base-size-16, 16px) !important}.py-md-3{padding-top:var(--base-size-16, 16px) !important;padding-bottom:var(--base-size-16, 16px) !important}.p-md-4{padding:var(--base-size-24, 24px) !important}.pt-md-4{padding-top:var(--base-size-24, 24px) !important}.pr-md-4{padding-right:var(--base-size-24, 24px) !important}.pb-md-4{padding-bottom:var(--base-size-24, 24px) !important}.pl-md-4{padding-left:var(--base-size-24, 24px) !important}.px-md-4{padding-right:var(--base-size-24, 24px) !important;padding-left:var(--base-size-24, 24px) !important}.py-md-4{padding-top:var(--base-size-24, 24px) !important;padding-bottom:var(--base-size-24, 24px) !important}.p-md-5{padding:var(--base-size-32, 32px) !important}.pt-md-5{padding-top:var(--base-size-32, 32px) !important}.pr-md-5{padding-right:var(--base-size-32, 32px) !important}.pb-md-5{padding-bottom:var(--base-size-32, 32px) !important}.pl-md-5{padding-left:var(--base-size-32, 32px) !important}.px-md-5{padding-right:var(--base-size-32, 32px) !important;padding-left:var(--base-size-32, 32px) !important}.py-md-5{padding-top:var(--base-size-32, 32px) !important;padding-bottom:var(--base-size-32, 32px) !important}.p-md-6{padding:var(--base-size-40, 40px) !important}.pt-md-6{padding-top:var(--base-size-40, 40px) !important}.pr-md-6{padding-right:var(--base-size-40, 40px) !important}.pb-md-6{padding-bottom:var(--base-size-40, 40px) !important}.pl-md-6{padding-left:var(--base-size-40, 40px) !important}.px-md-6{padding-right:var(--base-size-40, 40px) !important;padding-left:var(--base-size-40, 40px) !important}.py-md-6{padding-top:var(--base-size-40, 40px) !important;padding-bottom:var(--base-size-40, 40px) !important}.pt-md-7{padding-top:var(--base-size-48, 48px) !important}.pr-md-7{padding-right:var(--base-size-48, 48px) !important}.pb-md-7{padding-bottom:var(--base-size-48, 48px) !important}.pl-md-7{padding-left:var(--base-size-48, 48px) !important}.py-md-7{padding-top:var(--base-size-48, 48px) !important;padding-bottom:var(--base-size-48, 48px) !important}.pt-md-8{padding-top:var(--base-size-64, 64px) !important}.pr-md-8{padding-right:var(--base-size-64, 64px) !important}.pb-md-8{padding-bottom:var(--base-size-64, 64px) !important}.pl-md-8{padding-left:var(--base-size-64, 64px) !important}.py-md-8{padding-top:var(--base-size-64, 64px) !important;padding-bottom:var(--base-size-64, 64px) !important}.pt-md-9{padding-top:var(--base-size-80, 80px) !important}.pr-md-9{padding-right:var(--base-size-80, 80px) !important}.pb-md-9{padding-bottom:var(--base-size-80, 80px) !important}.pl-md-9{padding-left:var(--base-size-80, 80px) !important}.py-md-9{padding-top:var(--base-size-80, 80px) !important;padding-bottom:var(--base-size-80, 80px) !important}.pt-md-10{padding-top:var(--base-size-96, 96px) !important}.pr-md-10{padding-right:var(--base-size-96, 96px) !important}.pb-md-10{padding-bottom:var(--base-size-96, 96px) !important}.pl-md-10{padding-left:var(--base-size-96, 96px) !important}.py-md-10{padding-top:var(--base-size-96, 96px) !important;padding-bottom:var(--base-size-96, 96px) !important}.pt-md-11{padding-top:var(--base-size-112, 112px) !important}.pr-md-11{padding-right:var(--base-size-112, 112px) !important}.pb-md-11{padding-bottom:var(--base-size-112, 112px) !important}.pl-md-11{padding-left:var(--base-size-112, 112px) !important}.py-md-11{padding-top:var(--base-size-112, 112px) !important;padding-bottom:var(--base-size-112, 112px) !important}.pt-md-12{padding-top:var(--base-size-128, 128px) !important}.pr-md-12{padding-right:var(--base-size-128, 128px) !important}.pb-md-12{padding-bottom:var(--base-size-128, 128px) !important}.pl-md-12{padding-left:var(--base-size-128, 128px) !important}.py-md-12{padding-top:var(--base-size-128, 128px) !important;padding-bottom:var(--base-size-128, 128px) !important}}@media(min-width: 1012px){.p-lg-0{padding:0 !important}.pt-lg-0{padding-top:0 !important}.pr-lg-0{padding-right:0 !important}.pb-lg-0{padding-bottom:0 !important}.pl-lg-0{padding-left:0 !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.p-lg-1{padding:var(--base-size-4, 4px) !important}.pt-lg-1{padding-top:var(--base-size-4, 4px) !important}.pr-lg-1{padding-right:var(--base-size-4, 4px) !important}.pb-lg-1{padding-bottom:var(--base-size-4, 4px) !important}.pl-lg-1{padding-left:var(--base-size-4, 4px) !important}.px-lg-1{padding-right:var(--base-size-4, 4px) !important;padding-left:var(--base-size-4, 4px) !important}.py-lg-1{padding-top:var(--base-size-4, 4px) !important;padding-bottom:var(--base-size-4, 4px) !important}.p-lg-2{padding:var(--base-size-8, 8px) !important}.pt-lg-2{padding-top:var(--base-size-8, 8px) !important}.pr-lg-2{padding-right:var(--base-size-8, 8px) !important}.pb-lg-2{padding-bottom:var(--base-size-8, 8px) !important}.pl-lg-2{padding-left:var(--base-size-8, 8px) !important}.px-lg-2{padding-right:var(--base-size-8, 8px) !important;padding-left:var(--base-size-8, 8px) !important}.py-lg-2{padding-top:var(--base-size-8, 8px) !important;padding-bottom:var(--base-size-8, 8px) !important}.p-lg-3{padding:var(--base-size-16, 16px) !important}.pt-lg-3{padding-top:var(--base-size-16, 16px) !important}.pr-lg-3{padding-right:var(--base-size-16, 16px) !important}.pb-lg-3{padding-bottom:var(--base-size-16, 16px) !important}.pl-lg-3{padding-left:var(--base-size-16, 16px) !important}.px-lg-3{padding-right:var(--base-size-16, 16px) !important;padding-left:var(--base-size-16, 16px) !important}.py-lg-3{padding-top:var(--base-size-16, 16px) !important;padding-bottom:var(--base-size-16, 16px) !important}.p-lg-4{padding:var(--base-size-24, 24px) !important}.pt-lg-4{padding-top:var(--base-size-24, 24px) !important}.pr-lg-4{padding-right:var(--base-size-24, 24px) !important}.pb-lg-4{padding-bottom:var(--base-size-24, 24px) !important}.pl-lg-4{padding-left:var(--base-size-24, 24px) !important}.px-lg-4{padding-right:var(--base-size-24, 24px) !important;padding-left:var(--base-size-24, 24px) !important}.py-lg-4{padding-top:var(--base-size-24, 24px) !important;padding-bottom:var(--base-size-24, 24px) !important}.p-lg-5{padding:var(--base-size-32, 32px) !important}.pt-lg-5{padding-top:var(--base-size-32, 32px) !important}.pr-lg-5{padding-right:var(--base-size-32, 32px) !important}.pb-lg-5{padding-bottom:var(--base-size-32, 32px) !important}.pl-lg-5{padding-left:var(--base-size-32, 32px) !important}.px-lg-5{padding-right:var(--base-size-32, 32px) !important;padding-left:var(--base-size-32, 32px) !important}.py-lg-5{padding-top:var(--base-size-32, 32px) !important;padding-bottom:var(--base-size-32, 32px) !important}.p-lg-6{padding:var(--base-size-40, 40px) !important}.pt-lg-6{padding-top:var(--base-size-40, 40px) !important}.pr-lg-6{padding-right:var(--base-size-40, 40px) !important}.pb-lg-6{padding-bottom:var(--base-size-40, 40px) !important}.pl-lg-6{padding-left:var(--base-size-40, 40px) !important}.px-lg-6{padding-right:var(--base-size-40, 40px) !important;padding-left:var(--base-size-40, 40px) !important}.py-lg-6{padding-top:var(--base-size-40, 40px) !important;padding-bottom:var(--base-size-40, 40px) !important}.pt-lg-7{padding-top:var(--base-size-48, 48px) !important}.pr-lg-7{padding-right:var(--base-size-48, 48px) !important}.pb-lg-7{padding-bottom:var(--base-size-48, 48px) !important}.pl-lg-7{padding-left:var(--base-size-48, 48px) !important}.py-lg-7{padding-top:var(--base-size-48, 48px) !important;padding-bottom:var(--base-size-48, 48px) !important}.pt-lg-8{padding-top:var(--base-size-64, 64px) !important}.pr-lg-8{padding-right:var(--base-size-64, 64px) !important}.pb-lg-8{padding-bottom:var(--base-size-64, 64px) !important}.pl-lg-8{padding-left:var(--base-size-64, 64px) !important}.py-lg-8{padding-top:var(--base-size-64, 64px) !important;padding-bottom:var(--base-size-64, 64px) !important}.pt-lg-9{padding-top:var(--base-size-80, 80px) !important}.pr-lg-9{padding-right:var(--base-size-80, 80px) !important}.pb-lg-9{padding-bottom:var(--base-size-80, 80px) !important}.pl-lg-9{padding-left:var(--base-size-80, 80px) !important}.py-lg-9{padding-top:var(--base-size-80, 80px) !important;padding-bottom:var(--base-size-80, 80px) !important}.pt-lg-10{padding-top:var(--base-size-96, 96px) !important}.pr-lg-10{padding-right:var(--base-size-96, 96px) !important}.pb-lg-10{padding-bottom:var(--base-size-96, 96px) !important}.pl-lg-10{padding-left:var(--base-size-96, 96px) !important}.py-lg-10{padding-top:var(--base-size-96, 96px) !important;padding-bottom:var(--base-size-96, 96px) !important}.pt-lg-11{padding-top:var(--base-size-112, 112px) !important}.pr-lg-11{padding-right:var(--base-size-112, 112px) !important}.pb-lg-11{padding-bottom:var(--base-size-112, 112px) !important}.pl-lg-11{padding-left:var(--base-size-112, 112px) !important}.py-lg-11{padding-top:var(--base-size-112, 112px) !important;padding-bottom:var(--base-size-112, 112px) !important}.pt-lg-12{padding-top:var(--base-size-128, 128px) !important}.pr-lg-12{padding-right:var(--base-size-128, 128px) !important}.pb-lg-12{padding-bottom:var(--base-size-128, 128px) !important}.pl-lg-12{padding-left:var(--base-size-128, 128px) !important}.py-lg-12{padding-top:var(--base-size-128, 128px) !important;padding-bottom:var(--base-size-128, 128px) !important}}@media(min-width: 1280px){.p-xl-0{padding:0 !important}.pt-xl-0{padding-top:0 !important}.pr-xl-0{padding-right:0 !important}.pb-xl-0{padding-bottom:0 !important}.pl-xl-0{padding-left:0 !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.p-xl-1{padding:var(--base-size-4, 4px) !important}.pt-xl-1{padding-top:var(--base-size-4, 4px) !important}.pr-xl-1{padding-right:var(--base-size-4, 4px) !important}.pb-xl-1{padding-bottom:var(--base-size-4, 4px) !important}.pl-xl-1{padding-left:var(--base-size-4, 4px) !important}.px-xl-1{padding-right:var(--base-size-4, 4px) !important;padding-left:var(--base-size-4, 4px) !important}.py-xl-1{padding-top:var(--base-size-4, 4px) !important;padding-bottom:var(--base-size-4, 4px) !important}.p-xl-2{padding:var(--base-size-8, 8px) !important}.pt-xl-2{padding-top:var(--base-size-8, 8px) !important}.pr-xl-2{padding-right:var(--base-size-8, 8px) !important}.pb-xl-2{padding-bottom:var(--base-size-8, 8px) !important}.pl-xl-2{padding-left:var(--base-size-8, 8px) !important}.px-xl-2{padding-right:var(--base-size-8, 8px) !important;padding-left:var(--base-size-8, 8px) !important}.py-xl-2{padding-top:var(--base-size-8, 8px) !important;padding-bottom:var(--base-size-8, 8px) !important}.p-xl-3{padding:var(--base-size-16, 16px) !important}.pt-xl-3{padding-top:var(--base-size-16, 16px) !important}.pr-xl-3{padding-right:var(--base-size-16, 16px) !important}.pb-xl-3{padding-bottom:var(--base-size-16, 16px) !important}.pl-xl-3{padding-left:var(--base-size-16, 16px) !important}.px-xl-3{padding-right:var(--base-size-16, 16px) !important;padding-left:var(--base-size-16, 16px) !important}.py-xl-3{padding-top:var(--base-size-16, 16px) !important;padding-bottom:var(--base-size-16, 16px) !important}.p-xl-4{padding:var(--base-size-24, 24px) !important}.pt-xl-4{padding-top:var(--base-size-24, 24px) !important}.pr-xl-4{padding-right:var(--base-size-24, 24px) !important}.pb-xl-4{padding-bottom:var(--base-size-24, 24px) !important}.pl-xl-4{padding-left:var(--base-size-24, 24px) !important}.px-xl-4{padding-right:var(--base-size-24, 24px) !important;padding-left:var(--base-size-24, 24px) !important}.py-xl-4{padding-top:var(--base-size-24, 24px) !important;padding-bottom:var(--base-size-24, 24px) !important}.p-xl-5{padding:var(--base-size-32, 32px) !important}.pt-xl-5{padding-top:var(--base-size-32, 32px) !important}.pr-xl-5{padding-right:var(--base-size-32, 32px) !important}.pb-xl-5{padding-bottom:var(--base-size-32, 32px) !important}.pl-xl-5{padding-left:var(--base-size-32, 32px) !important}.px-xl-5{padding-right:var(--base-size-32, 32px) !important;padding-left:var(--base-size-32, 32px) !important}.py-xl-5{padding-top:var(--base-size-32, 32px) !important;padding-bottom:var(--base-size-32, 32px) !important}.p-xl-6{padding:var(--base-size-40, 40px) !important}.pt-xl-6{padding-top:var(--base-size-40, 40px) !important}.pr-xl-6{padding-right:var(--base-size-40, 40px) !important}.pb-xl-6{padding-bottom:var(--base-size-40, 40px) !important}.pl-xl-6{padding-left:var(--base-size-40, 40px) !important}.px-xl-6{padding-right:var(--base-size-40, 40px) !important;padding-left:var(--base-size-40, 40px) !important}.py-xl-6{padding-top:var(--base-size-40, 40px) !important;padding-bottom:var(--base-size-40, 40px) !important}.pt-xl-7{padding-top:var(--base-size-48, 48px) !important}.pr-xl-7{padding-right:var(--base-size-48, 48px) !important}.pb-xl-7{padding-bottom:var(--base-size-48, 48px) !important}.pl-xl-7{padding-left:var(--base-size-48, 48px) !important}.py-xl-7{padding-top:var(--base-size-48, 48px) !important;padding-bottom:var(--base-size-48, 48px) !important}.pt-xl-8{padding-top:var(--base-size-64, 64px) !important}.pr-xl-8{padding-right:var(--base-size-64, 64px) !important}.pb-xl-8{padding-bottom:var(--base-size-64, 64px) !important}.pl-xl-8{padding-left:var(--base-size-64, 64px) !important}.py-xl-8{padding-top:var(--base-size-64, 64px) !important;padding-bottom:var(--base-size-64, 64px) !important}.pt-xl-9{padding-top:var(--base-size-80, 80px) !important}.pr-xl-9{padding-right:var(--base-size-80, 80px) !important}.pb-xl-9{padding-bottom:var(--base-size-80, 80px) !important}.pl-xl-9{padding-left:var(--base-size-80, 80px) !important}.py-xl-9{padding-top:var(--base-size-80, 80px) !important;padding-bottom:var(--base-size-80, 80px) !important}.pt-xl-10{padding-top:var(--base-size-96, 96px) !important}.pr-xl-10{padding-right:var(--base-size-96, 96px) !important}.pb-xl-10{padding-bottom:var(--base-size-96, 96px) !important}.pl-xl-10{padding-left:var(--base-size-96, 96px) !important}.py-xl-10{padding-top:var(--base-size-96, 96px) !important;padding-bottom:var(--base-size-96, 96px) !important}.pt-xl-11{padding-top:var(--base-size-112, 112px) !important}.pr-xl-11{padding-right:var(--base-size-112, 112px) !important}.pb-xl-11{padding-bottom:var(--base-size-112, 112px) !important}.pl-xl-11{padding-left:var(--base-size-112, 112px) !important}.py-xl-11{padding-top:var(--base-size-112, 112px) !important;padding-bottom:var(--base-size-112, 112px) !important}.pt-xl-12{padding-top:var(--base-size-128, 128px) !important}.pr-xl-12{padding-right:var(--base-size-128, 128px) !important}.pb-xl-12{padding-bottom:var(--base-size-128, 128px) !important}.pl-xl-12{padding-left:var(--base-size-128, 128px) !important}.py-xl-12{padding-top:var(--base-size-128, 128px) !important;padding-bottom:var(--base-size-128, 128px) !important}}.p-responsive{padding-right:var(--base-size-16) !important;padding-left:var(--base-size-16) !important}@media(min-width: 544px){.p-responsive{padding-right:var(--base-size-40) !important;padding-left:var(--base-size-40) !important}}@media(min-width: 1012px){.p-responsive{padding-right:var(--base-size-16) !important;padding-left:var(--base-size-16) !important}}.h1{font-size:var(--h1-size-mobile, 26px) !important}@media(min-width: 768px){.h1{font-size:var(--h1-size, 32px) !important}}.h2{font-size:var(--h2-size-mobile, 22px) !important}@media(min-width: 768px){.h2{font-size:var(--h2-size, 24px) !important}}.h3{font-size:var(--h3-size-mobile, 18px) !important}@media(min-width: 768px){.h3{font-size:var(--h3-size, 20px) !important}}.h4{font-size:var(--h4-size, 16px) !important}.h5{font-size:var(--h5-size, 14px) !important}.h6{font-size:var(--h6-size, 12px) !important}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:var(--base-text-weight-semibold, 600) !important}.f1{font-size:var(--h1-size-mobile, 26px) !important}@media(min-width: 768px){.f1{font-size:var(--h1-size, 32px) !important}}.f2{font-size:var(--h2-size-mobile, 22px) !important}@media(min-width: 768px){.f2{font-size:var(--h2-size, 24px) !important}}.f3{font-size:var(--h3-size-mobile, 18px) !important}@media(min-width: 768px){.f3{font-size:var(--h3-size, 20px) !important}}.f4{font-size:var(--h4-size, 16px) !important}@media(min-width: 768px){.f4{font-size:var(--h4-size, 16px) !important}}.f5{font-size:var(--h5-size, 14px) !important}.f6{font-size:var(--h6-size, 12px) !important}.f00-light{font-size:var(--h00-size-mobile, 40px) !important;font-weight:var(--base-text-weight-light, 300) !important}@media(min-width: 768px){.f00-light{font-size:var(--h00-size, 48px) !important}}.f0-light{font-size:var(--h0-size-mobile, 32px) !important;font-weight:var(--base-text-weight-light, 300) !important}@media(min-width: 768px){.f0-light{font-size:var(--h0-size, 40px) !important}}.f1-light{font-size:var(--h1-size-mobile, 26px) !important;font-weight:var(--base-text-weight-light, 300) !important}@media(min-width: 768px){.f1-light{font-size:var(--h1-size, 32px) !important}}.f2-light{font-size:var(--h2-size-mobile, 22px) !important;font-weight:var(--base-text-weight-light, 300) !important}@media(min-width: 768px){.f2-light{font-size:var(--h2-size, 24px) !important}}.f3-light{font-size:var(--h3-size-mobile, 18px) !important;font-weight:var(--base-text-weight-light, 300) !important}@media(min-width: 768px){.f3-light{font-size:var(--h3-size, 20px) !important}}.text-small{font-size:var(--h6-size, 12px) !important}.lead{margin-bottom:30px;font-size:var(--h3-size, 20px);font-weight:var(--base-text-weight-light, 300)}.lh-condensed-ultra{line-height:1 !important}.lh-condensed{line-height:1.25 !important}.lh-default{line-height:1.5 !important}.lh-0{line-height:0 !important}@media(min-width: 544px){.lh-sm-condensed-ultra{line-height:1 !important}.lh-sm-condensed{line-height:1.25 !important}.lh-sm-default{line-height:1.5 !important}.lh-sm-0{line-height:0 !important}}@media(min-width: 768px){.lh-md-condensed-ultra{line-height:1 !important}.lh-md-condensed{line-height:1.25 !important}.lh-md-default{line-height:1.5 !important}.lh-md-0{line-height:0 !important}}@media(min-width: 1012px){.lh-lg-condensed-ultra{line-height:1 !important}.lh-lg-condensed{line-height:1.25 !important}.lh-lg-default{line-height:1.5 !important}.lh-lg-0{line-height:0 !important}}@media(min-width: 1280px){.lh-xl-condensed-ultra{line-height:1 !important}.lh-xl-condensed{line-height:1.25 !important}.lh-xl-default{line-height:1.5 !important}.lh-xl-0{line-height:0 !important}}.text-right{text-align:right !important}.text-left{text-align:left !important}.text-center{text-align:center !important}@media(min-width: 544px){.text-sm-right{text-align:right !important}.text-sm-left{text-align:left !important}.text-sm-center{text-align:center !important}}@media(min-width: 768px){.text-md-right{text-align:right !important}.text-md-left{text-align:left !important}.text-md-center{text-align:center !important}}@media(min-width: 1012px){.text-lg-right{text-align:right !important}.text-lg-left{text-align:left !important}.text-lg-center{text-align:center !important}}@media(min-width: 1280px){.text-xl-right{text-align:right !important}.text-xl-left{text-align:left !important}.text-xl-center{text-align:center !important}}.text-normal{font-weight:var(--base-text-weight-normal, 400) !important}.text-bold{font-weight:var(--base-text-weight-semibold, 600) !important}.text-semibold{font-weight:var(--base-text-weight-medium, 500) !important}.text-light{font-weight:var(--base-text-weight-light, 300) !important}.text-italic{font-style:italic !important}.text-uppercase{text-transform:uppercase !important}.text-underline{text-decoration:underline !important}.no-underline{text-decoration:none !important}.no-wrap{white-space:nowrap !important}.ws-normal{white-space:normal !important}.wb-break-word{word-break:break-word !important;overflow-wrap:break-word !important}.wb-break-all{word-break:break-all !important}.text-emphasized{font-weight:var(--base-text-weight-semibold, 600)}.list-style-none{list-style:none !important}.text-mono{font-family:var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace) !important}.user-select-none{-webkit-user-select:none !important;user-select:none !important}.text-capitalize{text-transform:capitalize !important}.d-block{display:block !important}.d-flex{display:flex !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.d-table{display:table !important}.d-table-cell{display:table-cell !important}@media(min-width: 544px){.d-sm-block{display:block !important}.d-sm-flex{display:flex !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.d-sm-table{display:table !important}.d-sm-table-cell{display:table-cell !important}}@media(min-width: 768px){.d-md-block{display:block !important}.d-md-flex{display:flex !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.d-md-table{display:table !important}.d-md-table-cell{display:table-cell !important}}@media(min-width: 1012px){.d-lg-block{display:block !important}.d-lg-flex{display:flex !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.d-lg-table{display:table !important}.d-lg-table-cell{display:table-cell !important}}@media(min-width: 1280px){.d-xl-block{display:block !important}.d-xl-flex{display:flex !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.d-xl-table{display:table !important}.d-xl-table-cell{display:table-cell !important}}.v-hidden{visibility:hidden !important}.v-visible{visibility:visible !important}@media(max-width: 543.98px){.hide-sm{display:none !important}}@media(min-width: 544px)and (max-width: 767.98px){.hide-md{display:none !important}}@media(min-width: 768px)and (max-width: 1011.98px){.hide-lg{display:none !important}}@media(min-width: 1012px){.hide-xl{display:none !important}}.show-whenNarrow,.show-whenRegular,.show-whenWide,.show-whenRegular.hide-whenWide{display:none !important}.hide-whenNarrow,.hide-whenRegular,.hide-whenWide{display:block !important}@media(max-width: 767.98px){.show-whenNarrow{display:block !important}.hide-whenNarrow{display:none !important}}@media(min-width: 768px){.show-whenRegular,.show-whenRegular.hide-whenWide{display:block !important}.hide-whenRegular{display:none !important}}@media(min-width: 1280px){.show-whenWide{display:block !important}.hide-whenWide,.show-whenRegular.hide-whenWide{display:none !important}}.table-fixed{table-layout:fixed !important}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip-path:rect(0 0 0 0);overflow-wrap:normal;border:0}.show-on-focus{position:absolute !important}.show-on-focus:not(:focus){width:1px !important;height:1px !important;padding:0 !important;overflow:hidden !important;clip:rect(1px, 1px, 1px, 1px) !important;border:0 !important}.show-on-focus:focus{z-index:999} +/*# sourceMappingURL=core.css.map */ \ No newline at end of file diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/primer-product.css b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/primer-product.css new file mode 100644 index 00000000..c39d3a97 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/primer-product.css @@ -0,0 +1,7 @@ +/*! + * @primer/css/product + * http://primer.style/css + * + * Released under MIT license. Copyright (c) 2019 GitHub Inc. + */.suggester{position:relative;top:0;left:0;min-width:180px;padding:0;margin:0;margin-top:var(--base-size-24);list-style:none;cursor:pointer;background:var(--overlay-bgColor, var(--color-canvas-overlay));border:1px solid var(--borderColor-default, var(--color-border-default));border-radius:6px;box-shadow:var(--shadow-resting-medium, var(--color-shadow-medium))}.suggester li{display:block;padding:var(--base-size-4) var(--base-size-8);font-weight:var(--base-text-weight-medium, 500);border-bottom:1px solid var(--borderColor-muted, var(--color-border-muted))}.suggester li small{font-weight:var(--base-text-weight-normal, 400);color:var(--fgColor-muted, var(--color-fg-muted))}.suggester li:last-child{border-bottom:0;border-bottom-right-radius:6px;border-bottom-left-radius:6px}.suggester li:first-child{border-top-left-radius:6px;border-top-right-radius:6px}.suggester li:hover{color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis));text-decoration:none;background:var(--bgColor-accent-emphasis, var(--color-accent-emphasis))}.suggester li:hover small{color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.suggester li:hover .octicon{color:inherit !important}.suggester li[aria-selected=true],.suggester li.navigation-focus{color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis));text-decoration:none;background:var(--bgColor-accent-emphasis, var(--color-accent-emphasis))}.suggester li[aria-selected=true] small,.suggester li.navigation-focus small{color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))}.suggester li[aria-selected=true] .octicon,.suggester li.navigation-focus .octicon{color:inherit !important}.suggester-container{position:absolute;top:0;left:0;z-index:30}@media(max-width: 544px){.page-responsive .suggester-container{right:var(--base-size-8) !important;left:var(--base-size-8) !important}.page-responsive .suggester li{padding:var(--base-size-8) var(--base-size-16)}}.avatar-parent-child{position:relative}.avatar-child{position:absolute;right:-15%;bottom:-9%;background-color:var(--bgColor-default, var(--color-canvas-default));border-radius:4px;box-shadow:var(--avatar-shadow, var(--color-avatar-child-shadow))}.CircleBadge{display:flex;align-items:center;justify-content:center;background-color:var(--bgColor-default, var(--color-canvas-default));border-radius:50%;box-shadow:var(--shadow-resting-medium, var(--color-shadow-medium))}.CircleBadge-icon{max-width:60% !important;height:auto !important;max-height:55% !important}.CircleBadge--small{width:56px;height:56px}.CircleBadge--medium{width:96px;height:96px}.CircleBadge--large{width:128px;height:128px}.DashedConnection{position:relative}.DashedConnection::before{position:absolute;top:50%;left:0;width:100%;content:"";border-bottom:2px dashed var(--borderColor-default, var(--color-border-default))}.DashedConnection .CircleBadge{position:relative}.branch-name{display:inline-block;padding:2px 6px;font:12px var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace);color:var(--fgColor-muted, var(--color-fg-muted));word-break:break-all;background-color:var(--bgColor-accent-muted, var(--color-accent-subtle));border-radius:6px}.branch-name .octicon{margin:1px -2px 0 0;color:var(--fgColor-muted, var(--color-fg-muted))}a.branch-name{color:var(--fgColor-accent, var(--color-accent-fg));background-color:var(--bgColor-accent-muted, var(--color-accent-subtle))}a.branch-name .octicon{color:var(--fgColor-accent, var(--color-accent-fg))}.Header{z-index:32;display:flex;padding:var(--base-size-16);font-size:14px;line-height:1.5;color:var(--header-fgColor-default, var(--color-header-text));background-color:var(--header-bgColor, var(--color-header-bg));align-items:center;flex-wrap:nowrap}.Header-item{display:flex;margin-right:var(--base-size-16);align-self:stretch;align-items:center;flex-wrap:nowrap}.Header-item--full{flex:auto}.Header-link{font-weight:var(--base-text-weight-semibold, 600);color:var(--header-fgColor-logo, var(--color-header-logo));white-space:nowrap}.Header-link:hover,.Header-link:focus{color:var(--header-fgColor-default, var(--color-header-text));text-decoration:none}.Header-input{color:var(--header-fgColor-default, var(--color-header-text));background-color:var(--headerSearch-bgColor, var(--color-header-search-bg));border:1px solid var(--headerSearch-borderColor, var(--color-header-search-border));box-shadow:none}.Header-input::placeholder{color:rgba(255,255,255,.75)}.IssueLabel{display:inline-block;padding:0 7px;font-size:12px;font-weight:var(--base-text-weight-medium, 500);line-height:18px;white-space:nowrap;border:1px solid rgba(0,0,0,0);border-radius:2em}.IssueLabel .g-emoji{position:relative;top:-0.05em;display:inline-block;font-size:1em;line-height:1}.IssueLabel:hover{text-decoration:none}.IssueLabel--big{padding-right:10px;padding-left:10px;line-height:22px}.AnimatedEllipsis{display:inline-block;overflow:hidden;vertical-align:bottom}.AnimatedEllipsis::after{display:inline-block;content:"...";animation:AnimatedEllipsis-keyframes 1.2s steps(4, jump-none) infinite}@keyframes AnimatedEllipsis-keyframes{0%{transform:translateX(-100%)}}.markdown-body{font-family:var(--fontStack-sansSerif, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji");font-size:16px;line-height:1.5;overflow-wrap:break-word}.markdown-body::before{display:table;content:""}.markdown-body::after{display:table;clear:both;content:""}.markdown-body>*:first-child{margin-top:0 !important}.markdown-body>*:last-child{margin-bottom:0 !important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:var(--fgColor-danger, var(--color-danger-fg))}.markdown-body .anchor{float:left;padding-right:var(--base-size-4);margin-left:-20px;line-height:1}.markdown-body .anchor:focus{outline:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre,.markdown-body details{margin-top:0;margin-bottom:var(--base-size-16)}.markdown-body hr{height:.25em;padding:0;margin:var(--base-size-24) 0;background-color:var(--borderColor-default, var(--color-border-default));border:0}.markdown-body blockquote{padding:0 1em;color:var(--fgColor-muted, var(--color-fg-muted));border-left:.25em solid var(--borderColor-default, var(--color-border-default))}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:var(--base-size-24);margin-bottom:var(--base-size-16);font-weight:var(--base-text-weight-semibold, 600);line-height:1.25}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:var(--fgColor-default, var(--color-fg-default));vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 tt,.markdown-body h1 code,.markdown-body h2 tt,.markdown-body h2 code,.markdown-body h3 tt,.markdown-body h3 code,.markdown-body h4 tt,.markdown-body h4 code,.markdown-body h5 tt,.markdown-body h5 code,.markdown-body h6 tt,.markdown-body h6 code{padding:0 .2em;font-size:inherit}.markdown-body h1{padding-bottom:.3em;font-size:2em;border-bottom:1px solid var(--borderColor-muted, var(--color-border-muted))}.markdown-body h2{padding-bottom:.3em;font-size:1.5em;border-bottom:1px solid var(--borderColor-muted, var(--color-border-muted))}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:.875em}.markdown-body h6{font-size:.85em;color:var(--fgColor-muted, var(--color-fg-muted))}.markdown-body summary h1,.markdown-body summary h2,.markdown-body summary h3,.markdown-body summary h4,.markdown-body summary h5,.markdown-body summary h6{display:inline-block}.markdown-body summary h1 .anchor,.markdown-body summary h2 .anchor,.markdown-body summary h3 .anchor,.markdown-body summary h4 .anchor,.markdown-body summary h5 .anchor,.markdown-body summary h6 .anchor{margin-left:-40px}.markdown-body summary h1,.markdown-body summary h2{padding-bottom:0;border-bottom:0}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul.no-list,.markdown-body ol.no-list{padding:0;list-style-type:none}.markdown-body ol[type="a s"]{list-style-type:lower-alpha}.markdown-body ol[type="A s"]{list-style-type:upper-alpha}.markdown-body ol[type="i s"]{list-style-type:lower-roman}.markdown-body ol[type="I s"]{list-style-type:upper-roman}.markdown-body ol[type="1"]{list-style-type:decimal}.markdown-body div>ol:not([type]){list-style-type:decimal}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:var(--base-size-16)}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{padding:0;margin-top:var(--base-size-16);font-size:1em;font-style:italic;font-weight:var(--base-text-weight-semibold, 600)}.markdown-body dl dt+dt{margin-top:0}.markdown-body dl dd{padding:0 var(--base-size-16);margin-bottom:var(--base-size-16)}.markdown-body table{display:block;width:100%;width:max-content;max-width:100%;overflow:auto;font-variant:tabular-nums}.markdown-body table th{font-weight:var(--base-text-weight-semibold, 600)}.markdown-body table th,.markdown-body table td{padding:6px 13px;border:1px solid var(--borderColor-default, var(--color-border-default))}.markdown-body table td>:last-child{margin-bottom:0}.markdown-body table tr{background-color:var(--bgColor-default, var(--color-canvas-default));border-top:1px solid var(--borderColor-muted, var(--color-border-muted))}.markdown-body table tr:nth-child(2n){background-color:var(--bgColor-muted, var(--color-canvas-subtle))}.markdown-body table img{background-color:rgba(0,0,0,0)}.markdown-body img{max-width:100%;box-sizing:content-box}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:rgba(0,0,0,0)}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{display:block;float:left;width:auto;padding:7px;margin:13px 0 0;overflow:hidden;border:1px solid var(--borderColor-default, var(--color-border-default))}.markdown-body span.frame span img{display:block;float:left}.markdown-body span.frame span span{display:block;padding:5px 0 0;clear:both;color:var(--fgColor-default, var(--color-fg-default))}.markdown-body span.align-center{display:block;overflow:hidden;clear:both}.markdown-body span.align-center>span{display:block;margin:13px auto 0;overflow:hidden;text-align:center}.markdown-body span.align-center span img{margin:0 auto;text-align:center}.markdown-body span.align-right{display:block;overflow:hidden;clear:both}.markdown-body span.align-right>span{display:block;margin:13px 0 0;overflow:hidden;text-align:right}.markdown-body span.align-right span img{margin:0;text-align:right}.markdown-body span.float-left{display:block;float:left;margin-right:13px;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{display:block;float:right;margin-left:13px;overflow:hidden}.markdown-body span.float-right>span{display:block;margin:13px auto 0;overflow:hidden;text-align:right}.markdown-body code,.markdown-body tt{padding:.2em .4em;margin:0;font-size:85%;white-space:break-spaces;background-color:var(--bgColor-neutral-muted, var(--color-neutral-muted));border-radius:6px}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{text-decoration:inherit}.markdown-body samp{font-size:85%}.markdown-body pre{overflow-wrap:normal}.markdown-body pre code{font-size:100%}.markdown-body pre>code{padding:0;margin:0;word-break:normal;white-space:pre;background:rgba(0,0,0,0);border:0}.markdown-body .highlight{margin-bottom:var(--base-size-16)}.markdown-body .highlight pre{margin-bottom:0;word-break:normal}.markdown-body .highlight pre,.markdown-body pre{padding:var(--base-size-16);overflow:auto;font-size:85%;line-height:1.45;color:var(--fgColor-default, var(--color-fg-default));background-color:var(--bgColor-muted, var(--color-canvas-subtle));border-radius:6px}.markdown-body pre code,.markdown-body pre tt{display:inline;padding:0;margin:0;overflow:visible;line-height:inherit;overflow-wrap:normal;background-color:rgba(0,0,0,0);border:0}.markdown-body .csv-data td,.markdown-body .csv-data th{padding:5px;overflow:hidden;font-size:12px;line-height:1;text-align:left;white-space:nowrap}.markdown-body .csv-data .blob-num{padding:10px var(--base-size-8) 9px;text-align:right;background:var(--bgColor-default, var(--color-canvas-default));border:0}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{font-weight:var(--base-text-weight-semibold, 600);background:var(--bgColor-muted, var(--color-canvas-subtle));border-top:0}.markdown-body [data-footnote-ref]::before{content:"["}.markdown-body [data-footnote-ref]::after{content:"]"}.markdown-body .footnotes{font-size:12px;color:var(--fgColor-muted, var(--color-fg-muted));border-top:1px solid var(--borderColor-default, var(--color-border-default))}.markdown-body .footnotes ol{padding-left:var(--base-size-16)}.markdown-body .footnotes ol ul{display:inline-block;padding-left:var(--base-size-16);margin-top:var(--base-size-16)}.markdown-body .footnotes li{position:relative}.markdown-body .footnotes li:target::before{position:absolute;top:calc(var(--base-size-8)*-1);right:calc(var(--base-size-8)*-1);bottom:calc(var(--base-size-8)*-1);left:calc(var(--base-size-24)*-1);pointer-events:none;content:"";border:2px solid var(--borderColor-accent-emphasis, var(--color-accent-emphasis));border-radius:6px}.markdown-body .footnotes li:target{color:var(--fgColor-default, var(--color-fg-default))}.markdown-body .footnotes .data-footnote-backref g-emoji{font-family:monospace}.SelectMenu{position:fixed;top:0;right:0;bottom:0;left:0;z-index:99;display:flex;padding:var(--base-size-16);pointer-events:none;flex-direction:column}@media(min-width: 544px){.SelectMenu{position:absolute;top:auto;right:auto;bottom:auto;left:auto;padding:0}}.SelectMenu::before{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;content:"";background-color:var(--overlay-backdrop-bgColor, var(--color-primer-canvas-backdrop))}@media(min-width: 544px){.SelectMenu::before{display:none}}.SelectMenu-modal{position:relative;z-index:99;display:flex;max-height:66%;margin:auto 0;overflow:hidden;pointer-events:auto;flex-direction:column;background-color:var(--overlay-bgColor, var(--color-canvas-overlay));border:1px solid var(--selectMenu-borderColor, var(--color-select-menu-backdrop-border));border-radius:12px;box-shadow:var(--shadow-floating-legacy, var(--color-overlay-shadow));animation:SelectMenu-modal-animation .12s cubic-bezier(0, 0.1, 0.1, 1) backwards}@keyframes SelectMenu-modal-animation{0%{opacity:0;transform:scale(0.9)}}@keyframes SelectMenu-modal-animation--sm{0%{opacity:0;transform:translateY(calc(var(--base-size-16) * -1))}}@media(min-width: 544px){.SelectMenu-modal{width:300px;height:auto;max-height:480px;margin:var(--base-size-8) 0 var(--base-size-16) 0;font-size:12px;border-color:var(--borderColor-default, var(--color-border-default));border-radius:6px;box-shadow:var(--shadow-floating-legacy, var(--color-overlay-shadow));animation-name:SelectMenu-modal-animation--sm}}.SelectMenu-header{display:flex;padding:var(--base-size-16);flex:none;align-items:center;border-bottom:1px solid var(--borderColor-muted, var(--color-border-muted))}@media(min-width: 544px){.SelectMenu-header{padding:7px 7px 7px var(--base-size-16)}}.SelectMenu-title{flex:1;font-size:14px;font-weight:var(--base-text-weight-semibold, 600)}@media(min-width: 544px){.SelectMenu-title{font-size:inherit}}.SelectMenu-closeButton{padding:var(--base-size-16);margin:calc(var(--base-size-16)*-1);line-height:1;color:var(--fgColor-muted, var(--color-fg-muted));background-color:rgba(0,0,0,0);border:0}@media(min-width: 544px){.SelectMenu-closeButton{padding:var(--base-size-8);margin:calc(var(--base-size-8)*-1) -7px}}.SelectMenu-filter{padding:var(--base-size-16);margin:0;border-bottom:1px solid var(--borderColor-muted, var(--color-border-muted))}@media(min-width: 544px){.SelectMenu-filter{padding:var(--base-size-8)}}.SelectMenu-input{display:block;width:100%}@media(min-width: 544px){.SelectMenu-input{font-size:14px}}.SelectMenu-list{position:relative;padding:0;margin:0;margin-bottom:-1px;flex:auto;overflow-x:hidden;overflow-y:auto;background-color:var(--overlay-bgColor, var(--color-canvas-overlay));-webkit-overflow-scrolling:touch}.SelectMenu-item{display:flex;align-items:center;width:100%;padding:var(--base-size-16);overflow:hidden;color:var(--fgColor-default, var(--color-fg-default));text-align:left;cursor:pointer;background-color:var(--overlay-bgColor, var(--color-canvas-overlay));border:0;border-bottom:1px solid var(--borderColor-muted, var(--color-border-muted))}@media(min-width: 544px){.SelectMenu-item{padding-top:7px;padding-bottom:7px}}.SelectMenu-list--borderless .SelectMenu-item{border-bottom:0}.SelectMenu-icon{width:var(--base-size-16);margin-right:var(--base-size-8);flex-shrink:0}.SelectMenu-icon--check{visibility:hidden;transition:transform .12s cubic-bezier(0.5, 0.1, 1, 0.5),visibility 0s .12s linear;transform:scale(0)}.SelectMenu-tabs{display:flex;flex-shrink:0;overflow-x:auto;overflow-y:hidden;box-shadow:inset 0 -1px 0 var(--borderColor-muted, var(--color-border-muted));-webkit-overflow-scrolling:touch}.SelectMenu-tabs::-webkit-scrollbar{display:none}@media(min-width: 544px){.SelectMenu-tabs{padding:var(--base-size-8) var(--base-size-8) 0 var(--base-size-8)}}.SelectMenu-tab{flex:1;padding:var(--base-size-8) var(--base-size-16);font-size:12px;font-weight:var(--base-text-weight-medium, 500);color:var(--fgColor-muted, var(--color-fg-muted));text-align:center;background-color:rgba(0,0,0,0);border:0;box-shadow:inset 0 -1px 0 var(--borderColor-muted, var(--color-border-muted))}@media(min-width: 544px){.SelectMenu-tab{flex:none;padding:var(--base-size-4) var(--base-size-16);border:1px solid rgba(0,0,0,0);border-bottom-width:0;border-top-left-radius:6px;border-top-right-radius:6px}}.SelectMenu-tab[aria-selected=true]{z-index:1;color:var(--fgColor-default, var(--color-fg-default));cursor:default;background-color:var(--overlay-bgColor, var(--color-canvas-overlay));box-shadow:0 0 0 1px var(--borderColor-muted, var(--color-border-muted))}@media(min-width: 544px){.SelectMenu-tab[aria-selected=true]{border-color:var(--borderColor-muted, var(--color-border-muted));box-shadow:none}}.SelectMenu-message{padding:7px var(--base-size-16);text-align:center;background-color:var(--overlay-bgColor, var(--color-canvas-overlay));border-bottom:1px solid var(--borderColor-muted, var(--color-border-muted))}.SelectMenu-blankslate,.SelectMenu-loading{padding:var(--base-size-24) var(--base-size-16);text-align:center;background-color:var(--overlay-bgColor, var(--color-canvas-overlay))}.SelectMenu-divider{padding:var(--base-size-4) var(--base-size-16);margin:0;font-size:12px;font-weight:var(--base-text-weight-medium, 500);color:var(--fgColor-muted, var(--color-fg-muted));background-color:var(--bgColor-muted, var(--color-canvas-subtle));border-bottom:1px solid var(--borderColor-muted, var(--color-border-muted))}.SelectMenu-list--borderless .SelectMenu-divider{border-top:1px solid var(--borderColor-muted, var(--color-border-muted))}.SelectMenu-list--borderless .SelectMenu-divider:empty{padding:0;border-top:0}.SelectMenu-footer{z-index:0;padding:var(--base-size-8) var(--base-size-16);font-size:12px;color:var(--fgColor-muted, var(--color-fg-muted));text-align:center;border-top:1px solid var(--borderColor-muted, var(--color-border-muted))}@media(min-width: 544px){.SelectMenu-footer{padding:7px var(--base-size-16)}}.SelectMenu--hasFilter .SelectMenu-modal{height:80%;max-height:none;margin-top:0}@media(min-width: 544px){.SelectMenu--hasFilter .SelectMenu-modal{height:auto;max-height:480px;margin-top:var(--base-size-8)}}.SelectMenu-tab:focus,.SelectMenu-item:focus{outline:0}.SelectMenu-item:hover{text-decoration:none}.SelectMenu-item[aria-checked=true]{font-weight:var(--base-text-weight-medium, 500);color:var(--fgColor-default, var(--color-fg-default))}.SelectMenu-item[aria-checked=true] .SelectMenu-icon--check{visibility:visible;transition:transform .12s cubic-bezier(0, 0, 0.2, 1),visibility 0s linear;transform:scale(1)}.SelectMenu-item:disabled,.SelectMenu-item[aria-disabled=true]{color:var(--fgColor-disabled, var(--color-primer-fg-disabled));pointer-events:none}@media(hover: hover){body:not(.intent-mouse) .SelectMenu-closeButton:focus,.SelectMenu-closeButton:hover{color:var(--fgColor-default, var(--color-fg-default))}.SelectMenu-closeButton:active{color:var(--fgColor-muted, var(--color-fg-muted))}body:not(.intent-mouse) .SelectMenu-item:focus,.SelectMenu-item:hover{background-color:var(--bgColor-neutral-muted, var(--color-neutral-subtle))}.SelectMenu-item:active{background-color:var(--bgColor-muted, var(--color-canvas-subtle))}body:not(.intent-mouse) .SelectMenu-tab:focus{background-color:var(--selectMenu-bgColor-active, var(--color-select-menu-tap-focus-bg))}.SelectMenu-tab:hover{color:var(--fgColor-default, var(--color-fg-default))}.SelectMenu-tab:not([aria-selected=true]):active{color:var(--fgColor-default, var(--color-fg-default));background-color:var(--bgColor-muted, var(--color-canvas-subtle))}}@media(hover: none){.SelectMenu-item:focus,.SelectMenu-item:active{background-color:var(--bgColor-muted, var(--color-canvas-subtle))}.SelectMenu-item{-webkit-tap-highlight-color:var(--control-bgColor-active, var(--color-select-menu-tap-highlight))}}.Toast{display:flex;margin:var(--base-size-8);color:var(--fgColor-default, var(--color-fg-default));background-color:var(--bgColor-default, var(--color-canvas-default));border-radius:6px;box-shadow:inset 0 0 0 1px var(--borderColor-default, var(--color-border-default)),var(--shadow-floating-legacy, var(--color-overlay-shadow))}@media(min-width: 544px){.Toast{width:max-content;max-width:450px;margin:var(--base-size-16)}}.Toast-icon{display:flex;align-items:center;justify-content:center;width:calc(var(--base-size-16)*3);flex-shrink:0;color:var(--fgColor-onEmphasis, var(--color-fg-on-emphasis));background-color:var(--bgColor-accent-emphasis, var(--color-accent-emphasis));border:1px solid rgba(0,0,0,0);border-right:0;border-top-left-radius:inherit;border-bottom-left-radius:inherit}.Toast-content{padding:var(--base-size-16)}.Toast-dismissButton{max-height:54px;padding:var(--base-size-16);color:inherit;background-color:rgba(0,0,0,0);border:0}.Toast-dismissButton:hover{opacity:.7}.Toast-dismissButton:active{opacity:.5}.Toast--loading{color:var(--fgColor-default, var(--color-fg-default));box-shadow:inset 0 0 0 1px var(--borderColor-default, var(--color-border-default)),var(--shadow-floating-legacy, var(--color-overlay-shadow))}.Toast--loading .Toast-icon{background-color:var(--bgColor-neutral-emphasis, var(--color-neutral-emphasis))}.Toast--error{color:var(--fgColor-default, var(--color-fg-default));box-shadow:inset 0 0 0 1px var(--borderColor-default, var(--color-border-default)),var(--shadow-floating-legacy, var(--color-overlay-shadow))}.Toast--error .Toast-icon{background-color:var(--bgColor-danger-emphasis, var(--color-danger-emphasis))}.Toast--warning{color:var(--fgColor-default, var(--color-fg-default));box-shadow:inset 0 0 0 1px var(--borderColor-default, var(--color-border-default)),var(--shadow-floating-legacy, var(--color-overlay-shadow))}.Toast--warning .Toast-icon{background-color:var(--bgColor-attention-emphasis, var(--color-attention-emphasis))}.Toast--success{color:var(--fgColor-default, var(--color-fg-default));box-shadow:inset 0 0 0 1px var(--borderColor-default, var(--color-border-default)),var(--shadow-floating-legacy, var(--color-overlay-shadow))}.Toast--success .Toast-icon{background-color:var(--bgColor-success-emphasis, var(--color-success-emphasis))}.Toast--animateIn{animation:Toast--animateIn .18s cubic-bezier(0.22, 0.61, 0.36, 1) backwards}@keyframes Toast--animateIn{0%{opacity:0;transform:translateY(100%)}}.Toast--animateOut{animation:Toast--animateOut .18s cubic-bezier(0.55, 0.06, 0.68, 0.19) forwards}@keyframes Toast--animateOut{100%{pointer-events:none;opacity:0;transform:translateY(100%)}}.Toast--spinner{animation:Toast--spinner 1000ms linear infinite}@keyframes Toast--spinner{from{transform:rotate(0deg)}to{transform:rotate(360deg)}} +/*# sourceMappingURL=product.css.map */ \ No newline at end of file diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/trx-preview.js b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/trx-preview.js new file mode 100644 index 00000000..f364bae7 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/assets/trx-preview.js @@ -0,0 +1,768 @@ +(() => { + "use strict"; + + const MAX_RENDERED_RESULTS = 2_000; + const MAX_DETAIL_CHARS = 24_000; + const MAX_HIGHLIGHTED_RAW_CHARS = 4 * 1024 * 1024; + const rawSources = new WeakMap(); + + function escapeHtml(value) { + return String(value ?? "") + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); + } + + function icon(name) { + const safeName = /^[a-z-]+$/.test(name) ? name : "file"; + return ``; + } + + function formatDate(value) { + const date = new Date(value); + return Number.isNaN(date.getTime()) + ? "" + : new Intl.DateTimeFormat(undefined, { + dateStyle: "medium", + timeStyle: "short", + }).format(date); + } + + function elements(parent, localName) { + if (!parent?.getElementsByTagName) return []; + return [...parent.getElementsByTagName("*")].filter( + (element) => element.localName === localName, + ); + } + + function firstElement(parent, localName) { + return elements(parent, localName)[0] ?? null; + } + + function directChild(parent, localName) { + return ( + [...(parent?.children ?? [])].find( + (element) => element.localName === localName, + ) ?? null + ); + } + + function elementText(element) { + return String(element?.textContent ?? "").trim(); + } + + function attribute(element, name) { + return String(element?.getAttribute(name) ?? "").trim(); + } + + function outcomeCategory(outcome) { + const normalized = String(outcome ?? "") + .toLocaleLowerCase() + .replace(/[^a-z]/g, ""); + if (normalized === "passed") return "passed"; + if ( + [ + "failed", + "error", + "timeout", + "aborted", + "passedbutrunaborted", + "disconnected", + ].includes(normalized) + ) { + return "failed"; + } + if ( + ["notexecuted", "notrunnable", "inconclusive", "pending"].includes( + normalized, + ) + ) { + return "skipped"; + } + return "other"; + } + + function outcomeIcon(category) { + if (category === "passed") return "check-circle"; + if (category === "failed") return "x-circle"; + if (category === "skipped") return "clock"; + return "workflow"; + } + + const KNOWN_TRX_OUTCOMES = new Set([ + "passed", "failed", "completed", "error", "warning", + "inconclusive", "aborted", "timeout", "inprogress", "notexecuted", + ]); + + function summarizedRunOutcome(report) { + const normalized = String(report.outcome ?? "").toLowerCase(); + if (!KNOWN_TRX_OUTCOMES.has(normalized) || normalized === "completed") { + if (report.summaryCounts.failed > 0) return "Failed"; + if (report.summaryCounts.passed > 0) return "Passed"; + if (report.summaryCounts.skipped > 0) return "Skipped"; + } + return report.outcome; + } + + function parseDuration(value) { + const duration = String(value ?? "").trim(); + if (!duration) return null; + + const clock = duration.match( + /^(?:(\d+)\.)?(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)$/, + ); + if (clock) { + const [, days = "0", hours, minutes, seconds] = clock; + return ( + Number(days) * 86_400_000 + + Number(hours) * 3_600_000 + + Number(minutes) * 60_000 + + Number(seconds) * 1_000 + ); + } + + const iso = duration.match( + /^P(?:(\d+(?:\.\d+)?)D)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/i, + ); + if (!iso) return null; + const [, days = "0", hours = "0", minutes = "0", seconds = "0"] = iso; + return ( + Number(days) * 86_400_000 + + Number(hours) * 3_600_000 + + Number(minutes) * 60_000 + + Number(seconds) * 1_000 + ); + } + + function formatDuration(durationMs) { + if (!Number.isFinite(durationMs)) return ""; + if (durationMs < 1_000) { + return `${Math.max(0, Math.round(durationMs))} ms`; + } + if (durationMs < 60_000) { + const seconds = durationMs / 1_000; + return `${seconds + .toFixed(seconds < 10 ? 2 : 1) + .replace(/\.0+$|(\.\d*[1-9])0+$/, "$1")} s`; + } + if (durationMs < 3_600_000) { + const minutes = Math.floor(durationMs / 60_000); + const seconds = Math.floor((durationMs % 60_000) / 1_000); + return `${minutes}m ${seconds}s`; + } + const hours = Math.floor(durationMs / 3_600_000); + const minutes = Math.floor((durationMs % 3_600_000) / 60_000); + return `${hours}h ${minutes}m`; + } + + function parseReport(value) { + const source = String(value ?? ""); + if (/ element.localName?.toLocaleLowerCase() === "parsererror", + ); + if (parserError) { + const detail = elementText(parserError).replace(/\s+/g, " ").slice(0, 240); + throw new Error(detail || "The XML document is malformed."); + } + + const root = documentNode.documentElement; + if (root?.localName !== "TestRun") { + throw new Error( + "The XML document does not contain a TRX TestRun root element.", + ); + } + + const definitions = new Map(); + for (const unitTest of elements(root, "UnitTest")) { + const testId = attribute(unitTest, "id").toLocaleLowerCase(); + if (!testId) continue; + const method = firstElement(unitTest, "TestMethod"); + definitions.set(testId, { + name: attribute(unitTest, "name"), + className: attribute(method, "className"), + methodName: attribute(method, "name"), + codeBase: attribute(method, "codeBase"), + adapterTypeName: attribute(method, "adapterTypeName"), + }); + } + + const testLists = new Map(); + for (const testList of elements(root, "TestList")) { + const id = attribute(testList, "id").toLocaleLowerCase(); + if (id) testLists.set(id, attribute(testList, "name")); + } + + const results = []; + const resultsElement = directChild(root, "Results"); + const isResultElement = (element) => + element.localName?.endsWith("TestResult") || + element.localName === "TestResultAggregation"; + + const readResult = (element, depth) => { + const testId = attribute(element, "testId").toLocaleLowerCase(); + const definition = definitions.get(testId) ?? {}; + const output = directChild(element, "Output"); + const errorInfo = firstElement(output, "ErrorInfo"); + const outcome = attribute(element, "outcome") || "Unknown"; + const testListId = attribute(element, "testListId").toLocaleLowerCase(); + const durationValue = attribute(element, "duration"); + const resultFiles = elements(output, "ResultFile") + .map((file) => attribute(file, "path")) + .filter(Boolean); + const properties = elements(output, "Property") + .map((property) => { + const key = + elementText(directChild(property, "Key")) || + attribute(property, "name"); + const propertyValue = + elementText(directChild(property, "Value")) || + attribute(property, "value"); + return key ? { key, value: propertyValue } : null; + }) + .filter(Boolean); + + return { + depth, + outcome, + category: outcomeCategory(outcome), + testName: + attribute(element, "testName") || + definition.name || + definition.methodName || + "Unnamed test", + className: definition.className || attribute(element, "className"), + methodName: definition.methodName, + codeBase: definition.codeBase, + adapterTypeName: definition.adapterTypeName, + computerName: attribute(element, "computerName"), + startTime: attribute(element, "startTime"), + endTime: attribute(element, "endTime"), + durationMs: parseDuration(durationValue), + executionId: attribute(element, "executionId"), + testId: attribute(element, "testId"), + testListName: testLists.get(testListId) ?? "", + dataRowInfo: attribute(element, "dataRowInfo"), + message: elementText(firstElement(errorInfo, "Message")), + stackTrace: elementText(firstElement(errorInfo, "StackTrace")), + stdout: elementText(firstElement(output, "StdOut")), + stderr: elementText(firstElement(output, "StdErr")), + debugTrace: elementText(firstElement(output, "DebugTrace")), + resultFiles, + properties, + }; + }; + + const visitResults = (parent, depth = 0) => { + for (const element of parent?.children ?? []) { + if (!isResultElement(element)) continue; + results.push(readResult(element, depth)); + const innerResults = directChild(element, "InnerResults"); + if (innerResults) visitResults(innerResults, depth + 1); + } + }; + visitResults(resultsElement); + + const countersElement = firstElement(root, "Counters"); + const counters = {}; + for (const counterAttribute of countersElement?.attributes ?? []) { + const number = Number(counterAttribute.value); + if (Number.isFinite(number) && number >= 0) { + counters[counterAttribute.name] = number; + } + } + + const resultCounts = results.reduce( + (counts, result) => { + counts[result.category] += 1; + return counts; + }, + { passed: 0, failed: 0, skipped: 0, other: 0 }, + ); + const sumCounters = (names, fallback) => { + const values = names + .filter((name) => Number.isFinite(counters[name])) + .map((name) => counters[name]); + return values.length > 0 + ? values.reduce((total, number) => total + number, 0) + : fallback; + }; + + const times = firstElement(root, "Times"); + const startTime = attribute(times, "start"); + const finishTime = attribute(times, "finish"); + const startTimestamp = new Date(startTime).getTime(); + const finishTimestamp = new Date(finishTime).getTime(); + const durationMs = + Number.isFinite(startTimestamp) && + Number.isFinite(finishTimestamp) && + finishTimestamp >= startTimestamp + ? finishTimestamp - startTimestamp + : null; + const summaryElement = firstElement(root, "ResultSummary"); + + return { + name: attribute(root, "name") || "Test run", + runId: attribute(root, "id"), + user: attribute(root, "runUser"), + computerName: attribute(root, "computerName"), + outcome: attribute(summaryElement, "outcome") || "Unknown", + startTime, + finishTime, + durationMs, + resultCounts, + summaryCounts: { + total: Number.isFinite(counters.total) ? counters.total : results.length, + passed: sumCounters(["passed"], resultCounts.passed), + failed: sumCounters( + [ + "failed", + "error", + "timeout", + "aborted", + "passedButRunAborted", + "disconnected", + ], + resultCounts.failed, + ), + skipped: sumCounters( + ["notExecuted", "notRunnable", "inconclusive", "pending"], + resultCounts.skipped, + ), + }, + results, + }; + } + + function renderMetadataItem(label, value, { mono = false } = {}) { + if (!value) return ""; + return ` +
+
${escapeHtml(label)}
+ ${escapeHtml(value)} +
+ `; + } + + function truncateDetail(value) { + const text = String(value ?? ""); + if (text.length <= MAX_DETAIL_CHARS) { + return { text, truncated: false }; + } + return { + text: text.slice(0, MAX_DETAIL_CHARS), + truncated: true, + }; + } + + function renderDetailBlock(label, value) { + if (!value) return ""; + const detail = truncateDetail(value); + return ` +
+

${escapeHtml(label)}

+
${escapeHtml(detail.text)}
+ ${ + detail.truncated + ? `

Output truncated after ${MAX_DETAIL_CHARS.toLocaleString()} characters. View the raw XML for the full value.

` + : "" + } +
+ `; + } + + function renderResult(result, open = false) { + const secondaryParts = []; + if (result.className) secondaryParts.push(result.className); + if (result.dataRowInfo) secondaryParts.push(`Data row ${result.dataRowInfo}`); + if (result.category === "failed" && result.message) { + secondaryParts.push( + result.message.length > 180 + ? `${result.message.slice(0, 179)}...` + : result.message, + ); + } + const searchValue = [ + result.testName, + result.className, + result.methodName, + result.outcome, + result.message, + ] + .filter(Boolean) + .join(" ") + .toLocaleLowerCase() + .slice(0, 2_000); + const resultDuration = formatDuration(result.durationMs); + const metadata = [ + renderMetadataItem("Class", result.className, { mono: true }), + renderMetadataItem("Method", result.methodName, { mono: true }), + renderMetadataItem("Duration", resultDuration), + renderMetadataItem("Computer", result.computerName), + renderMetadataItem( + "Started", + result.startTime ? formatDate(result.startTime) : "", + ), + renderMetadataItem( + "Finished", + result.endTime ? formatDate(result.endTime) : "", + ), + renderMetadataItem("Test list", result.testListName), + renderMetadataItem("Adapter", result.adapterTypeName, { mono: true }), + renderMetadataItem("Code base", result.codeBase, { mono: true }), + renderMetadataItem("Test ID", result.testId, { mono: true }), + renderMetadataItem("Execution ID", result.executionId, { mono: true }), + ].join(""); + const resultFiles = + result.resultFiles.length > 0 + ? ` +
+

Result files

+
    ${result.resultFiles + .map((path) => `
  • ${escapeHtml(path)}
  • `) + .join("")}
+
+ ` + : ""; + const properties = + result.properties.length > 0 + ? ` +
+

Properties

+
${result.properties + .map( + ({ key, value }) => ` +
+
${escapeHtml(key)}
+
${escapeHtml(value)}
+
+ `, + ) + .join("")}
+
+ ` + : ""; + const details = [ + metadata ? `` : "", + renderDetailBlock("Error message", result.message), + renderDetailBlock("Stack trace", result.stackTrace), + renderDetailBlock("Standard output", result.stdout), + renderDetailBlock("Standard error", result.stderr), + renderDetailBlock("Debug trace", result.debugTrace), + resultFiles, + properties, + ].join(""); + + return ` +
+ + ${icon("chevron-right")} + ${icon(outcomeIcon(result.category))} + + ${escapeHtml(result.testName)} + ${ + secondaryParts.length > 0 + ? `${escapeHtml(secondaryParts.join(" | "))}` + : "" + } + + ${escapeHtml(resultDuration)} + ${escapeHtml(result.outcome)} + +
+ ${details || '

No additional details were recorded for this result.

'} +
+
+ `; + } + + function renderMetric(label, value, category, iconName) { + return ` +
+ ${icon(iconName)} + ${Number(value).toLocaleString()} + ${escapeHtml(label)} +
+ `; + } + + function renderFilter(label, category, count, pressed = false) { + return ` + + `; + } + + function renderParseError(source, message, highlightXml) { + const highlighted = + source.length <= MAX_HIGHLIGHTED_RAW_CHARS + ? highlightXml(source) + : escapeHtml(source); + return ` +
+ +
+
+
+ Fallback +

Raw XML

+
+
+
${highlighted}
+
+
+ `; + } + + function render(value, preview, helpers = {}) { + const source = String(value ?? ""); + const highlightXml = + typeof helpers.highlightXml === "function" + ? helpers.highlightXml + : escapeHtml; + let report; + try { + report = parseReport(source); + } catch (error) { + preview.innerHTML = renderParseError( + source, + error instanceof Error ? error.message : "The TRX document is invalid.", + highlightXml, + ); + return; + } + + const categoryOrder = { failed: 0, other: 1, skipped: 2, passed: 3 }; + const renderedResults = [...report.results] + .map((result, index) => ({ result, index })) + .sort( + (left, right) => + categoryOrder[left.result.category] - + categoryOrder[right.result.category] || left.index - right.index, + ) + .slice(0, MAX_RENDERED_RESULTS) + .map(({ result }) => result); + const runOutcome = summarizedRunOutcome(report); + const outcome = outcomeCategory(runOutcome); + const limited = renderedResults.length < report.results.length; + const runDuration = formatDuration(report.durationMs); + const runMetadata = [ + renderMetadataItem( + "Started", + report.startTime ? formatDate(report.startTime) : "", + ), + renderMetadataItem( + "Finished", + report.finishTime ? formatDate(report.finishTime) : "", + ), + renderMetadataItem("Duration", runDuration), + renderMetadataItem("Run by", report.user), + renderMetadataItem("Computer", report.computerName), + renderMetadataItem("Run ID", report.runId, { mono: true }), + ].join(""); + + preview.innerHTML = ` +
+
+
+
+
+ Test run +

${escapeHtml(report.name)}

+
+ + ${icon(outcomeIcon(outcome))} + ${escapeHtml(runOutcome)} + +
+
+ ${renderMetric("Total", report.summaryCounts.total, "all", "workflow")} + ${renderMetric("Passed", report.summaryCounts.passed, "passed", "check-circle")} + ${renderMetric("Failed", report.summaryCounts.failed, "failed", "x-circle")} + ${renderMetric("Skipped", report.summaryCounts.skipped, "skipped", "clock")} +
+ ${runMetadata ? `` : ""} +
+ +
+
+
+ Details +

Test results

+

+
+ +
+
+ +
+ ${renderFilter("All", "all", report.results.length, true)} + ${renderFilter("Passed", "passed", report.resultCounts.passed)} + ${renderFilter("Failed", "failed", report.resultCounts.failed)} + ${renderFilter("Skipped", "skipped", report.resultCounts.skipped)} + ${renderFilter("Other", "other", report.resultCounts.other)} +
+
+ ${ + limited + ? ` +
+ ${icon("eye")} + Showing the first ${renderedResults.length.toLocaleString()} of ${report.results.length.toLocaleString()} result records to keep the preview responsive. Raw XML contains the complete report. +
+ ` + : "" + } +
+ ${renderedResults + .map((result, index) => + renderResult( + result, + index === 0 && result.category === "failed", + ), + ) + .join("")} +
+ +
+
+ + +
+ `; + const trxPreview = preview.querySelector(".trx-preview"); + rawSources.set(trxPreview, { source, highlightXml }); + applyFilters(trxPreview); + } + + function showView(preview, viewName) { + if (!preview) return; + if (viewName === "raw") { + const rawSource = rawSources.get(preview); + const code = preview.querySelector("[data-trx-raw-code]"); + if (rawSource && code && code.dataset.trxLoaded !== "true") { + if (rawSource.source.length > MAX_HIGHLIGHTED_RAW_CHARS) { + code.textContent = rawSource.source; + const note = preview.querySelector("[data-trx-raw-note]"); + if (note) note.hidden = false; + } else { + code.innerHTML = rawSource.highlightXml(rawSource.source); + } + code.dataset.trxLoaded = "true"; + } + } + for (const panel of preview.querySelectorAll("[data-trx-panel]")) { + panel.hidden = panel.dataset.trxPanel !== viewName; + } + } + + function applyFilters(preview) { + if (!preview) return; + const query = String( + preview.querySelector("[data-trx-search-input]")?.value ?? "", + ) + .trim() + .toLocaleLowerCase(); + const terms = query.split(/\s+/).filter(Boolean); + const category = + preview.querySelector('[data-trx-filter][aria-pressed="true"]')?.dataset + .trxFilter ?? "all"; + const results = [...preview.querySelectorAll("[data-trx-result]")]; + let visible = 0; + for (const result of results) { + const matchesCategory = + category === "all" || result.dataset.trxCategory === category; + const searchValue = result.dataset.trxSearch ?? ""; + const matchesQuery = terms.every((term) => searchValue.includes(term)); + result.hidden = !(matchesCategory && matchesQuery); + if (!result.hidden) visible += 1; + } + + const total = Number(preview.dataset.trxTotal) || results.length; + const rendered = Number(preview.dataset.trxRendered) || results.length; + const count = preview.querySelector("[data-trx-result-count]"); + if (count) { + count.textContent = + total > rendered + ? `${visible.toLocaleString()} matching in the first ${rendered.toLocaleString()} of ${total.toLocaleString()} results` + : `${visible.toLocaleString()} of ${total.toLocaleString()} results`; + } + const noResults = preview.querySelector("[data-trx-no-results]"); + if (noResults) noResults.hidden = visible > 0; + } + + globalThis.TrxPreview = Object.freeze({ + applyFilters, + parse: parseReport, + render, + showView, + }); +})(); diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/cache-coordinator.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/cache-coordinator.mjs new file mode 100644 index 00000000..4a62cb09 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/cache-coordinator.mjs @@ -0,0 +1,63 @@ +function trackedOperation(callback) { + const operation = Promise.resolve().then(callback); + return { + operation, + // Waiters need ordering only; the initiating maintenance request owns failures. + barrier: operation.then( + () => undefined, + () => undefined, + ), + }; +} + +export class CacheMaintenanceCoordinator { + constructor() { + this.artifactDeletions = new Map(); + this.cacheClear = null; + } + + inspectionBarrier(artifactId) { + return ( + this.cacheClear?.barrier ?? + this.artifactDeletions.get(String(artifactId))?.barrier ?? + null + ); + } + + async deleteArtifact(artifactId, callback) { + const id = String(artifactId); + while (true) { + const existing = this.artifactDeletions.get(id); + if (existing) return existing.operation; + if (!this.cacheClear) break; + await this.cacheClear.barrier; + } + + const tracked = trackedOperation(callback); + this.artifactDeletions.set(id, tracked); + try { + return await tracked.operation; + } finally { + if (this.artifactDeletions.get(id) === tracked) { + this.artifactDeletions.delete(id); + } + } + } + + async clearCache(callback) { + if (this.cacheClear) return this.cacheClear.operation; + + const tracked = trackedOperation(async () => { + await Promise.all( + [...this.artifactDeletions.values()].map((entry) => entry.barrier), + ); + return callback(); + }); + this.cacheClear = tracked; + try { + return await tracked.operation; + } finally { + if (this.cacheClear === tracked) this.cacheClear = null; + } + } +} diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/cache.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/cache.mjs new file mode 100644 index 00000000..58b23a1f --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/cache.mjs @@ -0,0 +1,531 @@ +import { createWriteStream } from "node:fs"; +import { + mkdir, + readFile, + readdir, + rename, + rm, + stat, + statfs, + writeFile, +} from "node:fs/promises"; +import { join } from "node:path"; +import { Readable, Transform } from "node:stream"; +import { pipeline } from "node:stream/promises"; +import { + ARTIFACT_DOWNLOAD_IDLE_TIMEOUT_MS, + CACHE_ROOT, + MAX_ARCHIVE_BYTES, + MAX_INLINE_PREVIEW_BYTES, + MIN_CACHE_FREE_BYTES, +} from "./constants.mjs"; +import { CacheMaintenanceCoordinator } from "./cache-coordinator.mjs"; +import { analyzeArtifact, hasRootIndexHtml } from "./detector.mjs"; +import { getArtifact, openArtifactDownload } from "./github.mjs"; +import { findZipEntry, readEntryPrefix, readZipEntry, readZipIndex } from "./zip.mjs"; + +const downloads = new Map(); +const indexCache = new Map(); +const maintenance = new CacheMaintenanceCoordinator(); +let reservedDownloadBytes = 0; +let metadataWriteSequence = 0; + +function normalizeArtifactId(value) { + const id = Number.parseInt(value, 10); + if (!Number.isSafeInteger(id) || id <= 0) { + throw new Error("Artifact id must be a positive integer."); + } + return String(id); +} + +function pathsFor(value) { + const id = normalizeArtifactId(value); + return { + id, + archive: join(CACHE_ROOT, `${id}.zip`), + metadata: join(CACHE_ROOT, `${id}.json`), + }; +} + +async function writeJsonAtomic(path, value) { + const temporary = `${path}.${process.pid}.${++metadataWriteSequence}.tmp`; + try { + await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, "utf8"); + await rename(temporary, path); + } catch (error) { + await rm(temporary, { force: true }).catch(() => {}); + throw error; + } +} + +async function readMetadataFile(path) { + try { + return JSON.parse(await readFile(path, "utf8")); + } catch (error) { + if (error?.code === "ENOENT") return null; + if (error instanceof SyntaxError) { + throw new Error(`Cached artifact metadata is invalid: ${path}`, { cause: error }); + } + throw error; + } +} + +function formatBytes(value) { + const bytes = Math.max(0, Number(value) || 0); + if (bytes < 1024) return `${bytes} B`; + const units = ["KiB", "MiB", "GiB", "TiB"]; + let size = bytes; + let unit = -1; + do { + size /= 1024; + unit++; + } while (size >= 1024 && unit < units.length - 1); + return `${size.toFixed(size >= 10 ? 0 : 1)} ${units[unit]}`; +} + +async function downloadCapacityBytes() { + const filesystem = await statfs(CACHE_ROOT); + const available = Number(filesystem.bavail) * Number(filesystem.bsize); + return Math.max( + 0, + Math.min( + MAX_ARCHIVE_BYTES, + available - MIN_CACHE_FREE_BYTES - reservedDownloadBytes, + ), + ); +} + +function assertDownloadFits(bytes, capacity) { + if (!Number.isFinite(bytes) || bytes <= 0) return; + if (bytes > MAX_ARCHIVE_BYTES) { + throw new Error("Artifact exceeds the 4 GiB ZIP download limit."); + } + if (bytes > capacity) { + throw new Error( + `Not enough free disk space to cache this artifact. ${formatBytes(bytes)} is required, with ${formatBytes(capacity)} available after the safety reserve.`, + ); + } +} + +async function downloadArchive( + token, + repository, + artifact, + archivePath, + onProgress, + externalSignal = null, +) { + const declaredBytes = Number(artifact.sizeInBytes); + let reservedBytes = 0; + const reserve = async (totalBytes) => { + if (!Number.isFinite(totalBytes) || totalBytes <= reservedBytes) return; + assertDownloadFits(totalBytes, MAX_ARCHIVE_BYTES); + const additionalBytes = totalBytes - reservedBytes; + const capacity = await downloadCapacityBytes(); + if (additionalBytes > capacity) { + throw new Error( + `Not enough free disk space to cache this artifact. ${formatBytes(totalBytes)} is required, with ${formatBytes(capacity + reservedBytes)} available after the safety reserve.`, + ); + } + reservedDownloadBytes += additionalBytes; + reservedBytes = totalBytes; + }; + const releaseReservation = () => { + reservedDownloadBytes = Math.max(0, reservedDownloadBytes - reservedBytes); + reservedBytes = 0; + }; + await reserve(declaredBytes); + + const controller = new AbortController(); + if (externalSignal?.aborted) { + controller.abort(externalSignal.reason); + } else if (externalSignal) { + externalSignal.addEventListener("abort", () => controller.abort(externalSignal.reason), { once: true }); + } + let idleTimer; + const resetIdleTimeout = () => { + clearTimeout(idleTimer); + idleTimer = setTimeout(() => { + controller.abort(new Error("Artifact download stalled for 60 seconds.")); + }, ARTIFACT_DOWNLOAD_IDLE_TIMEOUT_MS); + }; + resetIdleTimeout(); + + let response; + try { + response = await openArtifactDownload(token, repository, artifact.id, { + signal: controller.signal, + }); + } catch (error) { + clearTimeout(idleTimer); + releaseReservation(); + if (controller.signal.aborted && controller.signal.reason instanceof Error) { + throw controller.signal.reason; + } + throw error; + } + + const expectedLength = Number.parseInt(response.headers.get("content-length") ?? "", 10); + try { + await reserve(expectedLength); + } catch (error) { + clearTimeout(idleTimer); + controller.abort(error); + releaseReservation(); + throw error; + } + const totalBytes = + Number.isFinite(expectedLength) && expectedLength > 0 + ? expectedLength + : Number.isFinite(declaredBytes) && declaredBytes > 0 + ? declaredBytes + : null; + + const temporary = `${archivePath}.${process.pid}.${Date.now()}.part`; + let received = 0; + let lastReportAt = 0; + let bytesPerSecond = 0; + const samples = [{ at: Date.now(), bytes: 0 }]; + const report = (force = false) => { + if (!onProgress) return; + const now = Date.now(); + if (!force && now - lastReportAt < 250) return; + samples.push({ at: now, bytes: received }); + while (samples.length > 2 && samples[1].at < now - 5_000) samples.shift(); + const first = samples[0]; + const elapsedSeconds = (now - first.at) / 1_000; + bytesPerSecond = + elapsedSeconds >= 0.2 ? (received - first.bytes) / elapsedSeconds : 0; + const percent = totalBytes + ? Math.min(100, (received / totalBytes) * 100) + : null; + onProgress({ + artifactId: artifact.id, + artifactName: artifact.name, + stage: "downloading", + receivedBytes: received, + totalBytes, + percent, + bytesPerSecond, + etaSeconds: + totalBytes && bytesPerSecond > 0 + ? Math.max(0, (totalBytes - received) / bytesPerSecond) + : null, + }); + lastReportAt = now; + }; + report(true); + + const limiter = new Transform({ + transform(chunk, _encoding, callback) { + received += chunk.length; + if (received > MAX_ARCHIVE_BYTES) { + callback(new Error("Artifact exceeded the 4 GiB ZIP download limit.")); + return; + } + if (reservedBytes > 0 && received > reservedBytes) { + callback(new Error("Artifact download exhausted the available cache space.")); + return; + } + resetIdleTimeout(); + report(); + callback(null, chunk); + }, + }); + + try { + await pipeline( + Readable.fromWeb(response.body), + limiter, + createWriteStream(temporary, { flags: "wx" }), + ); + report(true); + await rename(temporary, archivePath); + } catch (error) { + await rm(temporary, { force: true }); + if (controller.signal.aborted && controller.signal.reason instanceof Error) { + throw controller.signal.reason; + } + throw error; + } finally { + clearTimeout(idleTimer); + releaseReservation(); + } + return { + bytesPerSecond, + receivedBytes: received, + totalBytes: totalBytes ?? received, + }; +} + +async function buildMetadata(token, repository, artifactId, onProgress, signal = null) { + const paths = pathsFor(artifactId); + const artifact = await getArtifact(token, repository, paths.id); + if (artifact.expired) { + throw new Error("This GitHub Actions artifact has expired."); + } + + await mkdir(CACHE_ROOT, { recursive: true }); + onProgress?.({ + artifactId: artifact.id, + artifactName: artifact.name, + stage: "preparing", + receivedBytes: 0, + totalBytes: artifact.sizeInBytes, + percent: 0, + bytesPerSecond: 0, + etaSeconds: null, + }); + const transfer = await downloadArchive( + token, + repository, + artifact, + paths.archive, + onProgress, + signal, + ); + const compressedBytes = transfer.receivedBytes; + try { + onProgress?.({ + artifactId: artifact.id, + artifactName: artifact.name, + stage: "indexing", + receivedBytes: compressedBytes, + totalBytes: transfer.totalBytes, + percent: 100, + bytesPerSecond: transfer.bytesPerSecond, + etaSeconds: 0, + }); + const index = await readZipIndex(paths.archive); + const PREFIX_BYTES = 8 * 1024; + const analysis = await analyzeArtifact(index, async (entry) => { + return readEntryPrefix(paths.archive, entry, PREFIX_BYTES); + }); + const timestamp = new Date().toISOString(); + const metadata = { + artifact, + repository, + downloadedAt: timestamp, + lastAccessedAt: timestamp, + compressedBytes, + analysis, + }; + await writeJsonAtomic(paths.metadata, metadata); + indexCache.set(paths.id, { archivePath: paths.archive, index }); + onProgress?.({ + artifactId: artifact.id, + artifactName: artifact.name, + stage: "ready", + receivedBytes: compressedBytes, + totalBytes: transfer.totalBytes, + percent: 100, + bytesPerSecond: transfer.bytesPerSecond, + etaSeconds: 0, + }); + return metadata; + } catch (error) { + await Promise.all([ + rm(paths.archive, { force: true }), + rm(paths.metadata, { force: true }), + ]); + throw error; + } +} + +async function cachedMetadata(artifactId) { + const paths = pathsFor(artifactId); + const metadata = await readMetadataFile(paths.metadata); + if (!metadata) return null; + try { + const archive = await stat(paths.archive); + if (!archive.isFile()) return null; + } catch (error) { + if (error?.code === "ENOENT") return null; + throw error; + } + return metadata; +} + +export async function inspectArtifact( + token, + repository, + artifactId, + { onProgress } = {}, +) { + const paths = pathsFor(artifactId); + while (true) { + const barrier = maintenance.inspectionBarrier(paths.id); + if (!barrier) break; + await barrier; + } + const key = `${repository}:${paths.id}`; + const active = downloads.get(key); + if (active) { + if (onProgress) active.listeners.add(onProgress); + try { + return await active.operation; + } finally { + if (onProgress) active.listeners.delete(onProgress); + } + } + + const listeners = new Set(onProgress ? [onProgress] : []); + const emit = (progress) => { + for (const listener of listeners) listener(progress); + }; + const externalController = new AbortController(); + const operation = (async () => { + const existing = await cachedMetadata(paths.id); + if (existing?.repository === repository && existing.analysis) { + existing.lastAccessedAt = new Date().toISOString(); + await writeJsonAtomic(paths.metadata, existing); + emit({ + artifactId: Number(paths.id), + artifactName: existing.artifact?.name ?? `Artifact ${paths.id}`, + stage: "ready", + receivedBytes: existing.compressedBytes, + totalBytes: existing.compressedBytes, + percent: 100, + bytesPerSecond: 0, + etaSeconds: 0, + cached: true, + }); + return existing; + } + if (existing) { + indexCache.delete(paths.id); + await removeArtifactFiles(paths); + } + return buildMetadata(token, repository, paths.id, emit, externalController.signal); + })(); + const download = { listeners, operation, abort: () => externalController.abort(new Error("Cache cleared.")) }; + downloads.set(key, download); + try { + return await operation; + } catch (error) { + emit({ + artifactId: Number(paths.id), + stage: "error", + message: error instanceof Error ? error.message : String(error), + }); + throw error; + } finally { + if (downloads.get(key) === download) downloads.delete(key); + listeners.clear(); + } +} + +export async function getCachedArtifact(artifactId, { touch = true } = {}) { + const paths = pathsFor(artifactId); + const metadata = await cachedMetadata(paths.id); + if (!metadata) return null; + if (touch) { + metadata.lastAccessedAt = new Date().toISOString(); + await writeJsonAtomic(paths.metadata, metadata); + } + return metadata; +} + +async function getIndex(artifactId) { + const paths = pathsFor(artifactId); + const cached = indexCache.get(paths.id); + if (cached?.archivePath === paths.archive) return cached.index; + const index = await readZipIndex(paths.archive); + indexCache.set(paths.id, { archivePath: paths.archive, index }); + return index; +} + +export async function getCachedEntry(artifactId, entryPath) { + const paths = pathsFor(artifactId); + const metadata = await cachedMetadata(paths.id); + if (!metadata) throw new Error("Artifact is not cached. Inspect it first."); + const index = await getIndex(paths.id); + const entry = findZipEntry(index, entryPath); + if (!entry) throw new Error(`File was not found in the artifact: ${entryPath}`); + return { metadata, archivePath: paths.archive, index, entry }; +} + +export async function readCachedEntry(artifactId, entryPath, maxBytes = MAX_INLINE_PREVIEW_BYTES) { + const context = await getCachedEntry(artifactId, entryPath); + return { + ...context, + content: await readZipEntry(context.archivePath, context.entry, maxBytes), + }; +} + +async function removeArtifactFiles(paths) { + await Promise.all([ + rm(paths.archive, { force: true }), + rm(paths.metadata, { force: true }), + ]); +} + +export async function deleteCachedArtifact(artifactId) { + const paths = pathsFor(artifactId); + return maintenance.deleteArtifact(paths.id, async () => { + indexCache.delete(paths.id); + const suffix = `:${paths.id}`; + const matches = [...downloads.entries()] + .filter(([key]) => key.endsWith(suffix)) + .map(([, entry]) => entry); + for (const entry of matches) entry.abort?.(); + await Promise.allSettled(matches.map((entry) => entry.operation)); + await removeArtifactFiles(paths); + return { deleted: paths.id }; + }); +} + +export async function clearArtifactCache() { + return maintenance.clearCache(async () => { + const inflight = [...downloads.values()]; + for (const entry of inflight) entry.abort?.(); + await Promise.allSettled(inflight.map((entry) => entry.operation)); + indexCache.clear(); + await rm(CACHE_ROOT, { recursive: true, force: true }); + await mkdir(CACHE_ROOT, { recursive: true }); + return { cleared: true }; + }); +} + +export async function getCacheSummary() { + await mkdir(CACHE_ROOT, { recursive: true }); + const files = await readdir(CACHE_ROOT, { withFileTypes: true }); + const metadataFiles = files.filter( + (entry) => entry.isFile() && /^\d+\.json$/.test(entry.name), + ); + const artifacts = []; + const errors = []; + let totalBytes = 0; + + for (const file of metadataFiles) { + const id = file.name.slice(0, -5); + try { + const metadata = await cachedMetadata(id); + if (!metadata) continue; + const archive = await stat(pathsFor(id).archive); + totalBytes += archive.size; + artifacts.push({ + id, + name: metadata.artifact?.name ?? `Artifact ${id}`, + repository: metadata.repository, + bytes: archive.size, + downloadedAt: metadata.downloadedAt, + lastAccessedAt: metadata.lastAccessedAt, + primary: + ["html", "static-site"].includes(metadata.analysis?.primary?.kind) && + !hasRootIndexHtml(metadata.analysis?.entries ?? []) + ? null + : metadata.analysis?.primary ?? null, + }); + } catch (error) { + errors.push({ + id, + message: error instanceof Error ? error.message : String(error), + }); + } + } + + artifacts.sort((left, right) => + String(right.lastAccessedAt).localeCompare(String(left.lastAccessedAt))); + return { totalBytes, count: artifacts.length, artifacts, errors }; +} diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/constants.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/constants.mjs new file mode 100644 index 00000000..c10c0c84 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/constants.mjs @@ -0,0 +1,24 @@ +import { homedir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +export const EXTENSION_ROOT = dirname(fileURLToPath(import.meta.url)); +export const ASSET_ROOT = join(EXTENSION_ROOT, "assets"); + +const copilotHome = process.env.COPILOT_HOME || join(homedir(), ".copilot"); +export const DATA_ROOT = join(copilotHome, "extensions", "pr-artifact-explorer", "artifacts"); +export const CACHE_ROOT = join(DATA_ROOT, "cache"); +export const PREFS_FILE = join(DATA_ROOT, "preferences.json"); + +export const GITHUB_API = "https://api.github.com"; +export const USER_AGENT = "github-copilot-pr-artifact-explorer"; + +// The indexer supports standard ZIP archives up to the ZIP32 boundary. +export const MAX_ARCHIVE_BYTES = (4 * 1024 * 1024 * 1024) - 2; +export const MIN_CACHE_FREE_BYTES = 512 * 1024 * 1024; +export const ARTIFACT_DOWNLOAD_IDLE_TIMEOUT_MS = 60_000; +export const MAX_CENTRAL_DIRECTORY_BYTES = 64 * 1024 * 1024; +export const MAX_ZIP_ENTRIES = 50_000; +export const MAX_INLINE_PREVIEW_BYTES = 4 * 1024 * 1024; +export const MAX_TRX_PREVIEW_BYTES = 32 * 1024 * 1024; +export const MAX_STREAMED_ENTRY_BYTES = 512 * 1024 * 1024; diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/copilot-extension.json b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/copilot-extension.json new file mode 100644 index 00000000..68595b3e --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/copilot-extension.json @@ -0,0 +1,4 @@ +{ + "name": "pr-artifact-explorer", + "version": 1 +} diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/detector.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/detector.mjs new file mode 100644 index 00000000..43288a59 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/detector.mjs @@ -0,0 +1,218 @@ +import { posix } from "node:path"; + +const MIME_TYPES = Object.freeze({ + ".avif": "image/avif", + ".bmp": "image/bmp", + ".cast": "application/x-asciicast", + ".config": "application/xml; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".csproj": "application/xml; charset=utf-8", + ".csv": "text/csv; charset=utf-8", + ".gif": "image/gif", + ".htm": "text/html; charset=utf-8", + ".html": "text/html; charset=utf-8", + ".ico": "image/x-icon", + ".jpeg": "image/jpeg", + ".jpg": "image/jpeg", + ".js": "text/javascript; charset=utf-8", + ".json": "application/json; charset=utf-8", + ".log": "text/plain; charset=utf-8", + ".map": "application/json; charset=utf-8", + ".md": "text/markdown; charset=utf-8", + ".mjs": "text/javascript; charset=utf-8", + ".nuspec": "application/xml; charset=utf-8", + ".pdf": "application/pdf", + ".png": "image/png", + ".props": "application/xml; charset=utf-8", + ".resx": "application/xml; charset=utf-8", + ".svg": "image/svg+xml", + ".text": "text/plain; charset=utf-8", + ".toml": "text/plain; charset=utf-8", + ".ts": "text/plain; charset=utf-8", + ".tsx": "text/plain; charset=utf-8", + ".targets": "application/xml; charset=utf-8", + ".txt": "text/plain; charset=utf-8", + ".trx": "application/xml; charset=utf-8", + ".webp": "image/webp", + ".xaml": "application/xml; charset=utf-8", + ".xml": "application/xml; charset=utf-8", + ".yaml": "text/yaml; charset=utf-8", + ".yml": "text/yaml; charset=utf-8", + ".zip": "application/zip", +}); + +const TEXT_EXTENSIONS = new Set([ + ".config", ".css", ".csproj", ".csv", ".htm", ".html", ".ini", ".js", + ".json", ".log", ".map", ".md", ".mjs", ".nuspec", ".props", ".ps1", + ".resx", ".sh", ".targets", ".text", ".toml", ".ts", ".tsx", ".txt", + ".trx", ".xaml", ".xml", ".yaml", ".yml", +]); + +const XML_EXTENSIONS = new Set([ + ".config", ".csproj", ".nuspec", ".props", ".resx", ".targets", ".xaml", + ".trx", ".xml", +]); + +export function extensionOf(path) { + const base = posix.basename(path); + const index = base.lastIndexOf("."); + return index > 0 ? base.slice(index).toLowerCase() : ""; +} + +export function mimeForPath(path) { + return MIME_TYPES[extensionOf(path)] ?? "application/octet-stream"; +} + +export function kindForPath(path) { + const extension = extensionOf(path); + if (extension === ".cast") return "asciinema"; + if (extension === ".trx") return "trx"; + if (extension === ".html" || extension === ".htm") return "html"; + if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".bmp", ".svg", ".ico"].includes(extension)) { + return "image"; + } + if (extension === ".pdf") return "pdf"; + if (extension === ".md") return "markdown"; + if (extension === ".json" || extension === ".map") return "json"; + if (XML_EXTENSIONS.has(extension)) return "xml"; + if (TEXT_EXTENSIONS.has(extension)) return "text"; + if ([".zip", ".gz", ".tgz", ".7z", ".tar", ".rar"].includes(extension)) return "archive"; + return "binary"; +} + +export function hasRootIndexHtml(entries) { + return entries.some((entry) => { + if (!entry?.supported) return false; + const path = String(entry?.name ?? entry?.path ?? "") + .replaceAll("\\", "/") + .replace(/^\.\/+/, ""); + return path.toLocaleLowerCase() === "index.html"; + }); +} + +function shallowest(entries) { + return [...entries].sort((left, right) => { + const depth = left.name.split("/").length - right.name.split("/").length; + return depth || left.name.localeCompare(right.name); + })[0] ?? null; +} + +async function looksLikeAsciinema(entry, readHead) { + if (kindForPath(entry.name) === "asciinema") return true; + if ( + entry.uncompressedSize > 8 * 1024 * 1024 || + !/(recording|asciinema|terminal|session)/i.test(posix.basename(entry.name)) + ) { + return false; + } + let head; + try { + head = await readHead(entry); + } catch { + return false; + } + if (!head) return false; + const firstLine = head.toString("utf8").split(/\r?\n/, 1)[0].trim(); + if (!firstLine.startsWith("{")) return false; + try { + const header = JSON.parse(firstLine); + return ( + (header.version === 2 || header.version === 3) && + Number.isFinite(header.width) && + Number.isFinite(header.height) + ); + } catch { + return false; + } +} + +export async function analyzeArtifact(index, readHead) { + const files = index.entries.filter((entry) => !entry.directory); + const previewableFiles = files.filter((entry) => entry.supported); + const castEntries = []; + for (const entry of previewableFiles.slice(0, 2_000)) { + if (await looksLikeAsciinema(entry, readHead)) castEntries.push(entry); + } + + const htmlEntries = previewableFiles.filter( + (entry) => kindForPath(entry.name) === "html", + ); + const indexEntries = htmlEntries.filter( + (entry) => posix.basename(entry.name).toLowerCase() === "index.html", + ); + const rootIndexEntry = indexEntries.find( + (entry) => hasRootIndexHtml([entry]), + ); + const firstTrx = shallowest( + previewableFiles.filter((entry) => kindForPath(entry.name) === "trx"), + ); + const firstImage = shallowest( + previewableFiles.filter((entry) => kindForPath(entry.name) === "image"), + ); + const firstMarkdown = shallowest( + previewableFiles.filter((entry) => kindForPath(entry.name) === "markdown"), + ); + const firstText = shallowest( + previewableFiles.filter((entry) => + ["json", "text", "xml"].includes(kindForPath(entry.name)), + ), + ); + + let primary = null; + if (rootIndexEntry) { + primary = { + kind: "static-site", + path: rootIndexEntry.name, + root: "", + label: "Static HTML site", + }; + } else if (castEntries.length) { + primary = { + kind: "asciinema", + path: castEntries[0].name, + label: castEntries.length === 1 ? "Asciinema recording" : `${castEntries.length} Asciinema recordings`, + }; + } else if (firstTrx) { + primary = { kind: "trx", path: firstTrx.name, label: "Test results" }; + } else if (firstImage) { + primary = { kind: "image", path: firstImage.name, label: "Image preview" }; + } else if (firstMarkdown) { + primary = { kind: "markdown", path: firstMarkdown.name, label: "Markdown document" }; + } else if (firstText) { + const kind = kindForPath(firstText.name); + primary = { + kind, + path: firstText.name, + label: kind === "xml" ? "XML code" : kind === "json" ? "JSON code" : "Text preview", + }; + } + + const counts = {}; + const entries = files.map((entry) => { + const detectedKind = castEntries.includes(entry) ? "asciinema" : kindForPath(entry.name); + counts[detectedKind] = (counts[detectedKind] ?? 0) + 1; + return { + path: entry.name, + name: posix.basename(entry.name), + size: entry.uncompressedSize, + compressedSize: entry.compressedSize, + kind: detectedKind, + mime: detectedKind === "asciinema" ? "application/x-asciicast" : mimeForPath(entry.name), + supported: entry.supported, + modifiedAt: entry.modifiedAt, + }; + }); + + return { + primary, + entries, + counts, + fileCount: files.length, + totalUncompressedBytes: index.totalUncompressedBytes, + zipBacked: true, + }; +} + +export function isInlineTextKind(kind) { + return ["json", "markdown", "text", "trx", "xml"].includes(kind); +} diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/extension.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/extension.mjs new file mode 100644 index 00000000..926acf18 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/extension.mjs @@ -0,0 +1,215 @@ +import { + CanvasError, + createCanvas, + joinSession, +} from "@github/copilot-sdk/extension"; +import { resolveAccounts, invalidateAccounts } from "./accounts.mjs"; +import { + clearArtifactCache, + deleteCachedArtifact, + getCacheSummary, + inspectArtifact, +} from "./cache.mjs"; +import { + broadcastCache, + closeAllPreviews, + navigateInstance, + startInstance, + stopInstance, +} from "./server.mjs"; +import { stopStaticPreviewsForArtifact } from "./preview.mjs"; +import { loadPrefs, normalizeRepository, savePrefs } from "./state.mjs"; + +function positiveInteger(value, label) { + const number = Number.parseInt(value, 10); + if (!Number.isSafeInteger(number) || number <= 0) { + throw new CanvasError("invalid_input", `${label} must be a positive integer.`); + } + return number; +} + +const session = await joinSession({ + canvases: [ + createCanvas({ + id: "pr-artifact-explorer", + displayName: "Artifact Explorer", + description: + "Browse GitHub pull requests and securely preview workflow artifacts, including static sites and asciinema recordings.", + inputSchema: { + type: "object", + properties: { + repository: { + type: "string", + description: "Repository in owner/name format.", + }, + pullNumber: { + type: "integer", + minimum: 1, + description: "Pull request number to open.", + }, + url: { + type: "string", + description: "GitHub pull request URL to open.", + }, + }, + additionalProperties: false, + }, + actions: [ + { + name: "open_pull_request", + description: "Navigate an open explorer canvas to a pull request.", + inputSchema: { + type: "object", + properties: { + repository: { type: "string" }, + pullNumber: { type: "integer", minimum: 1 }, + }, + required: ["repository", "pullNumber"], + additionalProperties: false, + }, + handler: (ctx) => { + const repository = normalizeRepository(ctx.input?.repository); + const pullNumber = positiveInteger(ctx.input?.pullNumber, "pullNumber"); + const route = `#/pull/${encodeURIComponent(repository)}/${pullNumber}`; + return { + navigated: navigateInstance(ctx.instanceId, route), + repository, + pullNumber, + }; + }, + }, + { + name: "inspect_artifact", + description: "Download, index, and smart-detect a GitHub Actions artifact.", + inputSchema: { + type: "object", + properties: { + repository: { type: "string" }, + artifactId: { type: "integer", minimum: 1 }, + }, + required: ["repository", "artifactId"], + additionalProperties: false, + }, + handler: async (ctx) => { + const repository = normalizeRepository(ctx.input?.repository); + const artifactId = positiveInteger(ctx.input?.artifactId, "artifactId"); + const prefs = await loadPrefs(); + const auth = await resolveAccounts({ + preferredId: prefs.account, + repository, + }); + if (!auth.activeToken) { + throw new CanvasError("github_auth_required", "No usable GitHub account was found."); + } + const metadata = await inspectArtifact(auth.activeToken, repository, artifactId); + await broadcastCache(); + navigateInstance( + ctx.instanceId, + `#/artifact/${encodeURIComponent(repository)}/0/${artifactId}`, + ); + const entries = metadata.analysis.entries ?? []; + return { + artifact: metadata.artifact, + analysis: { + ...metadata.analysis, + entries: entries.slice(0, 200), + entriesTruncated: entries.length > 200, + }, + }; + }, + }, + { + name: "cache_status", + description: "Report downloaded artifact cache usage.", + handler: async () => getCacheSummary(), + }, + { + name: "clear_cache", + description: "Delete one downloaded artifact or clear the entire artifact cache.", + inputSchema: { + type: "object", + properties: { + artifactId: { type: "integer", minimum: 1 }, + }, + additionalProperties: false, + }, + handler: async (ctx) => { + if (ctx.input?.artifactId != null) { + await stopStaticPreviewsForArtifact(ctx.input.artifactId); + const result = await deleteCachedArtifact( + positiveInteger(ctx.input.artifactId, "artifactId"), + ); + navigateInstance(ctx.instanceId, "#/cache"); + await broadcastCache({ refresh: true }); + return result; + } + await closeAllPreviews(); + const result = await clearArtifactCache(); + navigateInstance(ctx.instanceId, "#/cache"); + await broadcastCache({ refresh: true }); + return result; + }, + }, + { + name: "accounts", + description: "List GitHub accounts inherited from the Copilot app and GitHub CLI.", + handler: async () => { + const prefs = await loadPrefs(); + const auth = await resolveAccounts({ + preferredId: prefs.account, + repository: prefs.repository, + force: true, + }); + return { active: auth.active, accounts: auth.accounts }; + }, + }, + { + name: "set_account", + description: "Select the GitHub account used by the artifact explorer.", + inputSchema: { + type: "object", + properties: { id: { type: "string" } }, + required: ["id"], + additionalProperties: false, + }, + handler: async (ctx) => { + if (typeof ctx.input?.id !== "string" || !ctx.input.id) { + throw new CanvasError("invalid_account", "Account id is required."); + } + const prefs = await loadPrefs(); + invalidateAccounts(); + const auth = await resolveAccounts({ + preferredId: ctx.input.id, + repository: prefs.repository, + force: true, + }); + if (!auth.active || auth.active.id !== ctx.input.id) { + throw new CanvasError( + "invalid_account", + `Account "${ctx.input.id}" was not found or is unavailable.`, + ); + } + prefs.account = ctx.input.id; + await savePrefs(prefs); + return { active: auth.active, accounts: auth.accounts }; + }, + }, + ], + open: async (ctx) => { + const entry = await startInstance( + ctx.instanceId, + ctx.input, + (message) => session.log(message, { level: "debug" }), + ); + return { + title: "Artifact Explorer", + status: "GitHub pull request artifacts", + url: entry.url, + }; + }, + onClose: async (ctx) => { + await stopInstance(ctx.instanceId); + }, + }), + ], +}); diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/github.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/github.mjs new file mode 100644 index 00000000..55d2670a --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/github.mjs @@ -0,0 +1,712 @@ +import { GITHUB_API, USER_AGENT } from "./constants.mjs"; +import { normalizeRepository } from "./state.mjs"; + +const PULL_ARTIFACT_FILTERS = new Set(["all", "with", "without"]); +const PULL_CI_FILTERS = new Set(["all", "failing", "passing", "pending", "none"]); +const ARTIFACT_PRESENCE_TTL_MS = 5 * 60 * 1_000; +const MISSING_ARTIFACT_TTL_MS = 30 * 1_000; +const ARTIFACT_PRESENCE_CACHE_LIMIT = 1_000; +// Each run fans out into paginated artifact requests, so keep the initial view bounded. +const MAX_PULL_REQUEST_RUNS = 30; +const MAX_PULL_REQUEST_ARTIFACTS = 2_000; +const artifactPresenceCache = new Map(); + +export class GitHubApiError extends Error { + constructor(message, status, details = null) { + super(message); + this.name = "GitHubApiError"; + this.status = status; + this.details = details; + } +} + +function repositoryPath(repository) { + return normalizeRepository(repository).split("/").map(encodeURIComponent).join("/"); +} + +function headersFor(token, extra = {}) { + return { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${token}`, + "User-Agent": USER_AGENT, + "X-GitHub-Api-Version": "2022-11-28", + ...extra, + }; +} + +async function readPayload(response) { + const text = await response.text(); + if (!text) return null; + const type = response.headers.get("content-type") ?? ""; + if (type.includes("json")) { + try { + return JSON.parse(text); + } catch (error) { + throw new GitHubApiError("GitHub returned malformed JSON.", response.status, { + cause: error instanceof Error ? error.message : String(error), + }); + } + } + return text; +} + +export async function githubRequest(token, path, options = {}) { + if (!token) throw new GitHubApiError("No GitHub account is selected.", 401); + if (!path.startsWith("/")) throw new Error("GitHub API paths must be absolute."); + + const response = await fetch(`${GITHUB_API}${path}`, { + method: options.method ?? "GET", + headers: headersFor(token, options.headers), + body: options.body, + signal: options.signal ?? AbortSignal.timeout(30_000), + }); + const payload = await readPayload(response); + if (!response.ok) { + const message = + payload && typeof payload === "object" && payload.message + ? payload.message + : `GitHub API request failed (${response.status}).`; + throw new GitHubApiError(message, response.status, payload); + } + return payload; +} + +function repositorySummary(repository) { + return { + fullName: repository.full_name, + name: repository.name, + owner: { + login: repository.owner?.login ?? "", + avatarUrl: repository.owner?.avatar_url ?? null, + type: repository.owner?.type ?? null, + }, + description: repository.description ?? null, + private: repository.private === true, + archived: repository.archived === true, + visibility: repository.visibility ?? (repository.private ? "private" : "public"), + url: repository.html_url, + updatedAt: repository.updated_at ?? null, + }; +} + +export async function getRepository(token, repository) { + return repositorySummary( + await githubRequest(token, `/repos/${repositoryPath(repository)}`), + ); +} + +async function contributorProfilesByNodeId(token, contributors) { + const ids = contributors + .map((contributor) => contributor.node_id) + .filter(Boolean); + if (!ids.length) return new Map(); + + const payload = await githubRequest(token, "/graphql", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + query: ` + query ContributorProfiles($ids: [ID!]!) { + nodes(ids: $ids) { + id + ... on User { + login + name + } + ... on Bot { + login + } + } + } + `, + variables: { ids }, + }), + }); + if (payload?.errors?.length) { + throw new GitHubApiError( + payload.errors[0]?.message ?? "GitHub could not load contributor profiles.", + 502, + payload.errors, + ); + } + if (!Array.isArray(payload?.data?.nodes)) { + throw new GitHubApiError("GitHub returned invalid contributor profile data.", 502, payload); + } + + return new Map( + payload.data.nodes + .filter((profile) => profile?.id) + .map((profile) => [profile.id, profile]), + ); +} + +export async function listRepositoryContributors(token, repository) { + const contributors = ( + await githubRequest( + token, + `/repos/${repositoryPath(repository)}/contributors?anon=0&per_page=100`, + ) + ).filter((contributor) => contributor?.login); + const profilesById = await contributorProfilesByNodeId(token, contributors); + + return contributors + .map((contributor) => ({ + login: contributor.login, + name: + typeof profilesById.get(contributor.node_id)?.name === "string" + ? profilesById.get(contributor.node_id).name.trim() || null + : null, + avatarUrl: contributor.avatar_url ?? null, + profileUrl: contributor.html_url ?? null, + type: contributor.type ?? null, + contributions: Number(contributor.contributions) || 0, + })); +} + +export async function suggestRepositories(token, query) { + const value = String(query ?? "").trim(); + if (!value || value.length > 100) return []; + + const slash = value.indexOf("/"); + if (slash > 0) { + const owner = value.slice(0, slash).trim(); + const partial = value.slice(slash + 1).trim(); + if (!/^[A-Za-z0-9_.-]+$/.test(owner)) return []; + + const profile = await githubRequest(token, `/users/${encodeURIComponent(owner)}`); + if (partial) { + const qualifier = profile.type === "Organization" ? "org" : "user"; + const result = await githubRequest( + token, + `/search/repositories?q=${encodeURIComponent(`${partial} in:name ${qualifier}:${owner}`)}&sort=updated&order=desc&per_page=12`, + ); + return (result.items ?? []).map(repositorySummary); + } + + const endpoint = + profile.type === "Organization" + ? `/orgs/${encodeURIComponent(owner)}/repos?type=all&sort=updated&direction=desc&per_page=20` + : `/users/${encodeURIComponent(owner)}/repos?type=all&sort=updated&direction=desc&per_page=20`; + return (await githubRequest(token, endpoint)).map(repositorySummary); + } + + if (value.length < 2) return []; + const result = await githubRequest( + token, + `/search/repositories?q=${encodeURIComponent(`${value} in:name`)}&sort=updated&order=desc&per_page=12`, + ); + return (result.items ?? []).map(repositorySummary); +} + +function mapPullRequest(item) { + return { + number: item.number, + title: item.title, + state: item.state, + draft: Boolean(item.draft), + url: item.html_url, + author: item.user?.login ?? "ghost", + authorAvatarUrl: item.user?.avatar_url ?? null, + authorType: item.user?.type ?? null, + authorAssociation: item.author_association ?? null, + comments: Number(item.comments) || 0, + createdAt: item.created_at, + updatedAt: item.updated_at, + closedAt: item.closed_at, + mergedAt: item.merged_at ?? null, + headSha: item.head?.sha ?? null, + headRef: item.head?.ref ?? null, + baseRef: item.base?.ref ?? null, + labels: (item.labels ?? []).map((label) => ({ + name: label.name, + color: label.color, + description: label.description ?? null, + })), + }; +} + +function mapSearchPullRequest(item) { + return { + number: item.number, + title: item.title, + state: item.state, + draft: Boolean(item.draft), + url: item.html_url, + author: item.user?.login ?? "ghost", + authorAvatarUrl: item.user?.avatar_url ?? null, + authorType: item.user?.type ?? null, + authorAssociation: item.author_association ?? null, + comments: Number(item.comments) || 0, + createdAt: item.created_at, + updatedAt: item.updated_at, + closedAt: item.closed_at, + mergedAt: item.pull_request?.merged_at ?? null, + headSha: null, + headRef: null, + baseRef: null, + labels: (item.labels ?? []).map((label) => ({ + name: label.name, + color: label.color, + description: label.description ?? null, + })), + }; +} + +function normalizePullFilters(filters = {}) { + return { + artifacts: PULL_ARTIFACT_FILTERS.has(filters.artifacts) ? filters.artifacts : "all", + ci: PULL_CI_FILTERS.has(filters.ci) ? filters.ci : "all", + }; +} + +function ciStatusForRollup(state) { + if (state === "FAILURE" || state === "ERROR") return "failing"; + if (state === "SUCCESS") return "passing"; + if (state === "PENDING" || state === "EXPECTED") return "pending"; + return "none"; +} + +async function pullRequestSignals(token, repository, pulls) { + if (!pulls.length) return new Map(); + const [owner, name] = normalizeRepository(repository).split("/"); + const selections = pulls + .map( + (pull, index) => ` + pull${index}: pullRequest(number: ${Number(pull.number)}) { + number + isDraft + headRefOid + reviewDecision + totalCommentsCount + commits(last: 1) { + nodes { + commit { + statusCheckRollup { + state + } + } + } + } + }`, + ) + .join("\n"); + const payload = await githubRequest(token, "/graphql", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + query: ` + query PullRequestSignals($owner: String!, $name: String!) { + repository(owner: $owner, name: $name) { + ${selections} + } + } + `, + variables: { owner, name }, + }), + }); + if (payload?.errors?.length) { + throw new GitHubApiError( + payload.errors[0]?.message ?? "GitHub could not load pull request signals.", + 502, + payload.errors, + ); + } + const result = payload?.data?.repository; + if (!result || typeof result !== "object") { + throw new GitHubApiError("GitHub returned invalid pull request signal data.", 502, payload); + } + + return new Map( + pulls.map((pull, index) => { + const signal = result[`pull${index}`]; + if (!signal) { + throw new GitHubApiError( + `GitHub did not return signal data for pull request #${pull.number}.`, + 502, + payload, + ); + } + const rollupState = + signal.commits?.nodes?.[0]?.commit?.statusCheckRollup?.state ?? null; + return [ + Number(pull.number), + { + draft: Boolean(signal.isDraft), + headSha: signal.headRefOid ?? null, + ciStatus: ciStatusForRollup(rollupState), + reviewDecision: signal.reviewDecision ?? null, + comments: Number(signal.totalCommentsCount) || 0, + }, + ]; + }), + ); +} + +async function findArtifactHeads(token, repository) { + const heads = new Set(); + let artifactCount = 0; + for (let page = 1; ; page++) { + const payload = await githubRequest( + token, + `/repos/${repositoryPath(repository)}/actions/artifacts?per_page=100&page=${page}`, + ); + const artifacts = payload.artifacts ?? []; + artifactCount += artifacts.length; + for (const artifact of artifacts) { + const headSha = artifact.workflow_run?.head_sha; + if (headSha) heads.add(headSha); + } + const totalCount = Number(payload.total_count) || 0; + if (artifacts.length < 100 || artifactCount >= totalCount) return heads; + } +} + +function pruneArtifactPresenceCache() { + const now = Date.now(); + for (const [key, entry] of artifactPresenceCache) { + if (entry.positiveExpiresAt <= now) artifactPresenceCache.delete(key); + } + while (artifactPresenceCache.size >= ARTIFACT_PRESENCE_CACHE_LIMIT) { + artifactPresenceCache.delete(artifactPresenceCache.keys().next().value); + } +} + +async function artifactHeadsForPulls(token, repository, headShas) { + const requestedHeads = new Set(headShas.filter(Boolean)); + if (!requestedHeads.size) return new Set(); + const key = normalizeRepository(repository).toLocaleLowerCase(); + const now = Date.now(); + const cached = artifactPresenceCache.get(key); + if (cached?.missingExpiresAt > now) return cached.promise; + if (cached?.positiveExpiresAt > now) { + const cachedHeads = await cached.promise; + if ([...requestedHeads].every((headSha) => cachedHeads.has(headSha))) { + return cachedHeads; + } + } + + pruneArtifactPresenceCache(); + const entry = { + missingExpiresAt: now + MISSING_ARTIFACT_TTL_MS, + positiveExpiresAt: now + ARTIFACT_PRESENCE_TTL_MS, + promise: findArtifactHeads(token, repository), + }; + artifactPresenceCache.set(key, entry); + try { + return await entry.promise; + } catch (error) { + if (artifactPresenceCache.get(key) === entry) { + artifactPresenceCache.delete(key); + } + throw error; + } +} + +export async function listPullRequests(token, repository, state = "open") { + const normalizedState = ["open", "closed", "all"].includes(state) ? state : "open"; + const query = new URLSearchParams({ + state: normalizedState, + sort: "updated", + direction: "desc", + per_page: "50", + }); + const payload = await githubRequest( + token, + `/repos/${repositoryPath(repository)}/pulls?${query}`, + ); + return payload.map(mapPullRequest); +} + +function normalizePullSearchQuery(filter, state) { + const normalizedState = ["open", "closed", "all"].includes(state) ? state : "open"; + let query = String(filter ?? "") + .trim() + .slice(0, 500) + .replace(/(^|\s)(?:repo|org|user):(?:"[^"]+"|\S+)/gi, " ") + .replace(/\s+/g, " ") + .trim(); + if (!/\bis:pr\b/i.test(query)) query = `is:pr ${query}`.trim(); + if ( + normalizedState !== "all" && + !/\bis:(?:open|closed|merged|unmerged)\b/i.test(query) + ) { + query = `${query} is:${normalizedState}`; + } + return query; +} + +export async function searchPullRequestBase( + token, + repository, + filter = "", + state = "open", + filters = {}, + { perPage = 100 } = {}, +) { + const normalizedFilters = normalizePullFilters(filters); + const query = normalizePullSearchQuery(filter, state); + const normalizedPerPage = Math.max(1, Math.min(100, Number(perPage) || 100)); + const parameters = new URLSearchParams({ + q: `repo:${normalizeRepository(repository)} ${query}`, + sort: "updated", + order: "desc", + per_page: String(normalizedPerPage), + }); + const payload = await githubRequest(token, `/search/issues?${parameters}`); + const sourcePulls = (payload.items ?? []).map(mapSearchPullRequest); + return { + query, + totalCount: Number(payload.total_count) || 0, + incomplete: payload.incomplete_results === true, + filters: normalizedFilters, + evaluatedCount: sourcePulls.length, + filtered: false, + pulls: sourcePulls, + }; +} + +export async function enrichPullRequests(token, repository, pulls, filters = {}) { + const normalizedFilters = normalizePullFilters(filters); + let enriched = pulls; + if (pulls.length) { + const signals = await pullRequestSignals(token, repository, pulls); + enriched = pulls.map((pull) => ({ + ...pull, + ...signals.get(Number(pull.number)), + })); + } + if (normalizedFilters.artifacts !== "all") { + const artifactHeads = await artifactHeadsForPulls( + token, + repository, + enriched.map((pull) => pull.headSha), + ); + enriched = enriched.map((pull) => ({ + ...pull, + hasArtifacts: Boolean(pull.headSha && artifactHeads.has(pull.headSha)), + })); + } + return enriched; +} + +export function filterPullRequests(pulls, filters = {}) { + const normalizedFilters = normalizePullFilters(filters); + let filtered = pulls; + if (normalizedFilters.ci !== "all") { + filtered = filtered.filter((pull) => pull.ciStatus === normalizedFilters.ci); + } + if (normalizedFilters.artifacts !== "all") { + const expected = normalizedFilters.artifacts === "with"; + filtered = filtered.filter((pull) => pull.hasArtifacts === expected); + } + return filtered; +} + +export async function searchPullRequests( + token, + repository, + filter = "", + state = "open", + filters = {}, +) { + const base = await searchPullRequestBase( + token, + repository, + filter, + state, + filters, + ); + const enriched = await enrichPullRequests( + token, + repository, + base.pulls, + base.filters, + ); + const derivedFilterActive = + base.filters.artifacts !== "all" || base.filters.ci !== "all"; + return { + ...base, + filtered: derivedFilterActive, + pulls: filterPullRequests(enriched, base.filters), + }; +} + +export async function getPullRequest(token, repository, pullNumber) { + const number = Number.parseInt(pullNumber, 10); + if (!Number.isSafeInteger(number) || number <= 0) { + throw new Error("Pull request number must be a positive integer."); + } + return mapPullRequest( + await githubRequest(token, `/repos/${repositoryPath(repository)}/pulls/${number}`), + ); +} + +async function mapLimit(items, limit, callback) { + const results = new Array(items.length); + let cursor = 0; + async function worker() { + while (cursor < items.length) { + const index = cursor++; + results[index] = await callback(items[index], index); + } + } + await Promise.all( + Array.from({ length: Math.min(limit, items.length) }, () => worker()), + ); + return results; +} + +async function listRunArtifacts(token, repository, runId) { + const artifacts = []; + for (let page = 1; page <= 10; page++) { + const payload = await githubRequest( + token, + `/repos/${repositoryPath(repository)}/actions/runs/${runId}/artifacts?per_page=100&page=${page}`, + ); + const pageArtifacts = payload.artifacts ?? []; + artifacts.push(...pageArtifacts); + if (pageArtifacts.length < 100 || artifacts.length >= Number(payload.total_count ?? 0)) { + break; + } + } + return artifacts; +} + +export async function listPullRequestArtifacts(token, repository, pullNumber) { + const pullRequest = await getPullRequest(token, repository, pullNumber); + if (!pullRequest.headSha) { + return { + pullRequest, + runs: [], + runCount: 0, + runsTruncated: false, + artifacts: [], + artifactCount: 0, + artifactsTruncated: false, + }; + } + + const query = new URLSearchParams({ + head_sha: pullRequest.headSha, + per_page: String(MAX_PULL_REQUEST_RUNS), + }); + const runsPayload = await githubRequest( + token, + `/repos/${repositoryPath(repository)}/actions/runs?${query}`, + ); + const returnedRuns = (runsPayload.workflow_runs ?? []) + .sort((left, right) => new Date(right.created_at) - new Date(left.created_at)); + const runCount = Math.max( + returnedRuns.length, + Number(runsPayload.total_count) || 0, + ); + const runs = returnedRuns.slice(0, MAX_PULL_REQUEST_RUNS); + const runsTruncated = runs.length < runCount; + + const artifactGroups = await mapLimit(runs, 5, async (run) => { + const runArtifacts = await listRunArtifacts(token, repository, run.id); + return runArtifacts.map((artifact) => ({ + id: artifact.id, + name: artifact.name, + sizeInBytes: artifact.size_in_bytes, + expired: Boolean(artifact.expired), + createdAt: artifact.created_at, + updatedAt: artifact.updated_at, + expiresAt: artifact.expires_at, + run: { + id: run.id, + name: run.name, + number: run.run_number, + attempt: run.run_attempt, + event: run.event, + status: run.status, + conclusion: run.conclusion, + createdAt: run.created_at, + url: run.html_url, + }, + })); + }); + + const allArtifacts = [...new Map( + artifactGroups.flat().map((artifact) => [String(artifact.id), artifact]), + ).values()].sort((left, right) => new Date(right.createdAt) - new Date(left.createdAt)); + const artifacts = allArtifacts.slice(0, MAX_PULL_REQUEST_ARTIFACTS); + + return { + pullRequest, + runs: runs.map((run) => ({ + id: run.id, + name: run.name, + number: run.run_number, + attempt: run.run_attempt, + event: run.event, + status: run.status, + conclusion: run.conclusion, + createdAt: run.created_at, + url: run.html_url, + })), + runCount, + runsTruncated, + artifacts, + artifactCount: allArtifacts.length, + artifactsTruncated: + runsTruncated || artifacts.length < allArtifacts.length, + }; +} + +export async function getArtifact(token, repository, artifactId) { + const id = Number.parseInt(artifactId, 10); + if (!Number.isSafeInteger(id) || id <= 0) { + throw new Error("Artifact id must be a positive integer."); + } + const artifact = await githubRequest( + token, + `/repos/${repositoryPath(repository)}/actions/artifacts/${id}`, + ); + return { + id: artifact.id, + name: artifact.name, + sizeInBytes: artifact.size_in_bytes, + expired: Boolean(artifact.expired), + createdAt: artifact.created_at, + updatedAt: artifact.updated_at, + expiresAt: artifact.expires_at, + run: artifact.workflow_run + ? { + id: artifact.workflow_run.id, + headBranch: artifact.workflow_run.head_branch, + headSha: artifact.workflow_run.head_sha, + } + : null, + }; +} + +export async function openArtifactDownload( + token, + repository, + artifactId, + { signal } = {}, +) { + if (!token) throw new GitHubApiError("No GitHub account is selected.", 401); + const id = Number.parseInt(artifactId, 10); + const response = await fetch( + `${GITHUB_API}/repos/${repositoryPath(repository)}/actions/artifacts/${id}/zip`, + { + headers: headersFor(token), + redirect: "follow", + signal, + }, + ); + if (!response.ok) { + const payload = await readPayload(response); + const message = + payload && typeof payload === "object" && payload.message + ? payload.message + : `Artifact download failed (${response.status}).`; + throw new GitHubApiError(message, response.status, payload); + } + if (!response.body) { + throw new GitHubApiError("Artifact download returned an empty response.", response.status); + } + return response; +} diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/memory-cache.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/memory-cache.mjs new file mode 100644 index 00000000..49dd2868 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/memory-cache.mjs @@ -0,0 +1,130 @@ +export class ExpiringPromiseCache { + #entries = new Map(); + #maxEntries; + #ttlMs; + + constructor({ maxEntries, ttlMs }) { + if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) { + throw new TypeError("maxEntries must be a positive integer."); + } + if (!Number.isFinite(ttlMs) || ttlMs <= 0) { + throw new TypeError("ttlMs must be a positive number."); + } + this.#maxEntries = maxEntries; + this.#ttlMs = ttlMs; + } + + clear() { + this.#entries.clear(); + } + + delete(key) { + this.#entries.delete(String(key)); + } + + peek(key) { + const normalizedKey = String(key); + const entry = this.#entries.get(normalizedKey); + if (!entry?.hasValue || entry.expiresAt <= Date.now()) { + if (!entry?.promise) this.#entries.delete(normalizedKey); + return undefined; + } + this.#touch(normalizedKey, entry); + return entry.value; + } + + set(key, value) { + const normalizedKey = String(key); + const entry = { + expiresAt: Date.now() + this.#ttlMs, + forceRefresh: false, + hasValue: true, + promise: null, + value, + }; + this.#entries.set(normalizedKey, entry); + this.#touch(normalizedKey, entry); + this.#enforceLimit(); + return value; + } + + async get(key, loader, { force = false } = {}) { + if (typeof loader !== "function") throw new TypeError("loader must be a function."); + const normalizedKey = String(key); + const now = Date.now(); + let entry = this.#entries.get(normalizedKey); + if (!force && entry?.hasValue && entry.expiresAt > now) { + this.#touch(normalizedKey, entry); + return entry.value; + } + if (entry?.promise && (!force || entry.forceRefresh)) return entry.promise; + + this.#prune(now); + if (force) entry = null; + entry ??= { + expiresAt: 0, + forceRefresh: false, + hasValue: false, + promise: null, + value: undefined, + }; + const promise = Promise.resolve() + .then(loader) + .then( + (value) => { + if ( + this.#entries.get(normalizedKey) === entry && + entry.promise === promise + ) { + entry.expiresAt = Date.now() + this.#ttlMs; + entry.forceRefresh = false; + entry.hasValue = true; + entry.promise = null; + entry.value = value; + this.#touch(normalizedKey, entry); + this.#enforceLimit(); + } + return value; + }, + (error) => { + if ( + this.#entries.get(normalizedKey) === entry && + entry.promise === promise + ) { + entry.forceRefresh = false; + entry.promise = null; + if (!entry.hasValue || entry.expiresAt <= Date.now()) { + this.#entries.delete(normalizedKey); + } + } + throw error; + }, + ); + entry.forceRefresh = force; + entry.promise = promise; + this.#entries.set(normalizedKey, entry); + this.#enforceLimit(); + return promise; + } + + #touch(key, entry) { + this.#entries.delete(key); + this.#entries.set(key, entry); + } + + #prune(now) { + for (const [key, entry] of this.#entries) { + if (!entry.promise && (!entry.hasValue || entry.expiresAt <= now)) { + this.#entries.delete(key); + } + } + } + + #enforceLimit() { + while (this.#entries.size > this.#maxEntries) { + const candidate = [...this.#entries].find(([, entry]) => !entry.promise); + if (!candidate) return; + this.#entries.delete(candidate[0]); + } + } +} diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/preview.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/preview.mjs new file mode 100644 index 00000000..d6f2194f --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/preview.mjs @@ -0,0 +1,298 @@ +import { createServer } from "node:http"; +import { posix } from "node:path"; +import { Transform } from "node:stream"; +import { getCachedEntry } from "./cache.mjs"; +import { mimeForPath } from "./detector.mjs"; +import { isCanonicalHost } from "./security.mjs"; +import { streamZipEntry } from "./zip.mjs"; + +const previewServers = new Map(); +const HTML_INJECTION_SEARCH_BYTES = 64 * 1024; + +const PREVIEW_CSP = [ + "default-src 'self' data: blob:", + "base-uri 'self'", + "connect-src 'self'", + "font-src 'self' data:", + "form-action 'none'", + "frame-src 'self' data: blob:", + "img-src 'self' data: blob:", + "media-src 'self' data: blob:", + "object-src 'none'", + "script-src 'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval' blob:", + "style-src 'self' 'unsafe-inline'", + "worker-src 'self' blob:", +].join("; "); + +function normalizeTheme(value) { + if (value !== "light" && value !== "dark") { + throw new Error("Preview theme must be light or dark."); + } + return value; +} + +function normalizeParentOrigin(value) { + const origin = new URL(value); + if (origin.protocol !== "http:" || origin.hostname !== "127.0.0.1") { + throw new Error("Preview parent must use a loopback origin."); + } + return origin.origin; +} + +function themeBridgeMarkup(theme, parentOrigin) { + const initialTheme = JSON.stringify(theme); + const expectedOrigin = JSON.stringify(parentOrigin); + return ``; +} + +function insertionOffset(buffer, fallback = false) { + const source = buffer.toString("utf8"); + const element = /]*)?>/i.exec(source) ?? /]*)?>/i.exec(source); + if (element) { + return Buffer.byteLength( + source.slice(0, element.index + element[0].length), + "utf8", + ); + } + if (!fallback) return null; + const doctype = /]*>/i.exec(source); + return doctype + ? Buffer.byteLength(source.slice(0, doctype.index + doctype[0].length), "utf8") + : 0; +} + +function createHtmlInjectionTransform(markup) { + const injection = Buffer.from(markup, "utf8"); + let pending = Buffer.alloc(0); + let injected = false; + + return new Transform({ + transform(chunk, _encoding, callback) { + if (injected) { + callback(null, chunk); + return; + } + pending = Buffer.concat([pending, Buffer.from(chunk)]); + const offset = insertionOffset(pending); + if (offset !== null || pending.length >= HTML_INJECTION_SEARCH_BYTES) { + const resolvedOffset = offset ?? insertionOffset(pending, true); + this.push(pending.subarray(0, resolvedOffset)); + this.push(injection); + this.push(pending.subarray(resolvedOffset)); + pending = Buffer.alloc(0); + injected = true; + } + callback(); + }, + flush(callback) { + if (!injected) { + const offset = insertionOffset(pending, true); + this.push(pending.subarray(0, offset)); + this.push(injection); + this.push(pending.subarray(offset)); + } + callback(); + }, + }); +} + +function decodeRequestPath(pathname) { + try { + return pathname + .split("/") + .map((part) => decodeURIComponent(part)) + .join("/") + .replace(/^\/+/, ""); + } catch (error) { + throw Object.assign( + new Error("Preview URL contains invalid encoding.", { cause: error }), + { statusCode: 400 }, + ); + } +} + +function safeRelativePath(path) { + if (!path) return ""; + const normalized = posix.normalize(path.replaceAll("\\", "/")); + if ( + normalized === ".." || + normalized.startsWith("../") || + normalized.startsWith("/") || + /^[A-Za-z]:\//.test(normalized) + ) { + throw Object.assign(new Error("Preview path escapes the artifact root."), { statusCode: 400 }); + } + return normalized; +} + +async function servePreviewRequest( + artifactId, + root, + entryName, + theme, + parentOrigin, + serverEntry, + req, + res, +) { + if (!isCanonicalHost(req, serverEntry.host)) { + res.writeHead(403, { + "Cache-Control": "no-store", + "Content-Type": "text/plain; charset=utf-8", + "Content-Security-Policy": PREVIEW_CSP, + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }); + res.end("Preview host is not allowed."); + return; + } + if (!["GET", "HEAD"].includes(req.method)) { + res.writeHead(405, { Allow: "GET, HEAD" }); + res.end(); + return; + } + + try { + const url = new URL(req.url, "http://127.0.0.1"); + let relative = safeRelativePath(decodeRequestPath(url.pathname)); + if (!relative || relative.endsWith("/")) { + relative = `${relative}${posix.basename(entryName)}`; + } + const requestedEntry = safeRelativePath(root ? posix.join(root, relative) : relative); + const context = await getCachedEntry(artifactId, requestedEntry); + if (!context.entry.supported) { + res.writeHead(415, { "Content-Type": "text/plain; charset=utf-8" }); + res.end("This file uses an unsupported ZIP encoding."); + return; + } + const contentType = mimeForPath(context.entry.name); + const themeBridge = contentType.startsWith("text/html") + ? themeBridgeMarkup(theme, parentOrigin) + : ""; + res.writeHead(200, { + "Cache-Control": "no-store", + "Content-Length": String( + context.entry.uncompressedSize + Buffer.byteLength(themeBridge), + ), + "Content-Security-Policy": PREVIEW_CSP, + "Content-Type": contentType, + "Cross-Origin-Resource-Policy": "same-origin", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }); + if (req.method === "HEAD") { + res.end(); + return; + } + if (themeBridge && context.entry.uncompressedSize === 0) { + res.end(themeBridge); + return; + } + await streamZipEntry( + context.archivePath, + context.entry, + res, + themeBridge ? createHtmlInjectionTransform(themeBridge) : null, + ); + } catch (error) { + if (!res.headersSent) { + const status = error?.statusCode === 400 ? 400 : 404; + res.writeHead(status, { + "Content-Type": "text/plain; charset=utf-8", + "Content-Security-Policy": PREVIEW_CSP, + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }); + } + res.end(error instanceof Error ? error.message : String(error)); + } +} + +export async function startStaticPreview(artifactId, entryPath, options) { + const context = await getCachedEntry(artifactId, entryPath); + if (!context.entry.supported) { + throw new Error("This file uses an unsupported ZIP encoding."); + } + if (!mimeForPath(context.entry.name).startsWith("text/html")) { + throw new Error("Only HTML files can start a static preview."); + } + if ( + posix.dirname(context.entry.name) !== "." || + posix.basename(context.entry.name).toLowerCase() !== "index.html" + ) { + throw new Error("Static previews require the root index.html file."); + } + const rootValue = posix.dirname(context.entry.name); + const root = rootValue === "." ? "" : rootValue; + const theme = normalizeTheme(options?.theme); + const parentOrigin = normalizeParentOrigin(options?.parentOrigin); + const key = JSON.stringify([ + String(artifactId), + root, + posix.basename(context.entry.name), + theme, + parentOrigin, + ]); + const existing = previewServers.get(key); + if (existing) return existing.url; + + const serverEntry = { host: "" }; + const server = createServer((req, res) => { + void servePreviewRequest( + String(artifactId), + root, + context.entry.name, + theme, + parentOrigin, + serverEntry, + req, + res, + ); + }); + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + const address = server.address(); + if (!address || typeof address === "string") { + await new Promise((resolve) => server.close(resolve)); + throw new Error("Static preview server did not receive a TCP port."); + } + serverEntry.host = `127.0.0.1:${address.port}`; + const url = `http://127.0.0.1:${address.port}/`; + previewServers.set(key, { artifactId: String(artifactId), server, url, canvasOrigin: parentOrigin }); + return url; +} + +export async function stopStaticPreviewsForArtifact(artifactId) { + const id = String(artifactId); + const matching = [...previewServers.entries()].filter( + ([, value]) => value.artifactId === id, + ); + await Promise.all( + matching.map(async ([key, value]) => { + previewServers.delete(key); + await new Promise((resolve) => value.server.close(resolve)); + }), + ); +} + +export async function stopStaticPreviewsForOrigin(canvasOrigin) { + const matching = [...previewServers.entries()].filter( + ([, value]) => value.canvasOrigin === canvasOrigin, + ); + await Promise.all( + matching.map(async ([key, value]) => { + previewServers.delete(key); + await new Promise((resolve) => value.server.close(resolve)); + }), + ); +} + +export async function stopAllStaticPreviews() { + const active = [...previewServers.values()]; + previewServers.clear(); + await Promise.all( + active.map((value) => new Promise((resolve) => value.server.close(resolve))), + ); +} diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/render.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/render.mjs new file mode 100644 index 00000000..a43f4292 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/render.mjs @@ -0,0 +1,99 @@ +const CAPABILITY_TOKEN_MARKER = "__PR_ARTIFACT_EXPLORER_CAPABILITY_TOKEN__"; + +function escapeHtmlAttribute(value) { + return String(value) + .replaceAll("&", "&") + .replaceAll('"', """) + .replaceAll("<", "<") + .replaceAll(">", ">"); +} + +export const HTML = ` + + + + + + PR Artifact Explorer + + + + + + + + + + + +
+ +`; + +export function renderHtml(capabilityToken) { + return HTML.replace( + CAPABILITY_TOKEN_MARKER, + escapeHtmlAttribute(capabilityToken), + ); +} diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/review-fixes.test.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/review-fixes.test.mjs new file mode 100644 index 00000000..9ae84729 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/review-fixes.test.mjs @@ -0,0 +1,566 @@ +import assert from "node:assert/strict"; +import { request as httpRequest } from "node:http"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; + +import { + analyzeArtifact, + hasRootIndexHtml, +} from "./detector.mjs"; +import { CacheMaintenanceCoordinator } from "./cache-coordinator.mjs"; +import { + enrichPullRequests, + filterPullRequests, + listPullRequestArtifacts, +} from "./github.mjs"; +import { contentDeliveryMode, startInstance, stopInstance } from "./server.mjs"; +import { + CAPABILITY_TOKEN_HEADER, + hasCapabilityToken, + isCanonicalHost, + isCrossSiteRequest, + requiresCapabilityToken, +} from "./security.mjs"; +import { + DEFAULT_PREFS, + normalizeRepository, + normalizePrefs, + rememberRepository, +} from "./state.mjs"; + +const here = new URL(".", import.meta.url); + +function jsonResponse(payload, status = 200) { + return { + headers: { get: () => "application/json" }, + ok: status >= 200 && status < 300, + status, + text: async () => JSON.stringify(payload), + }; +} + +function rawStatus(url, headers) { + return new Promise((resolve, reject) => { + const request = httpRequest(url, { headers }, (response) => { + response.resume(); + response.on("end", () => resolve(response.statusCode)); + }); + request.on("error", reject); + request.end(); + }); +} + +function deferred() { + let resolve; + const promise = new Promise((complete) => { + resolve = complete; + }); + return { promise, resolve }; +} + +test("malformed recent repository values normalize to an empty list", () => { + for (const recent of [{ unexpected: true }, 42]) { + const preferences = normalizePrefs({ + repository: "github/awesome-copilot", + repositories: { recent }, + }); + assert.deepEqual(preferences.repositories.recent, [ + "github/awesome-copilot", + ]); + } + + const fallback = normalizePrefs({ + repository: "not a repository", + repositories: { recent: 42 }, + }); + assert.equal(fallback.repository, DEFAULT_PREFS.repository); + assert.deepEqual(fallback.repositories.recent, [DEFAULT_PREFS.repository]); + + const preferences = { + repositories: { recent: { unexpected: true } }, + }; + rememberRepository(preferences, "github/awesome-copilot"); + assert.deepEqual(preferences.repositories.recent, [ + "github/awesome-copilot", + ]); +}); + +test("artifact filtering pages repository artifacts once and caches head SHAs", async (t) => { + const originalFetch = globalThis.fetch; + const artifactRequests = []; + t.after(() => { + globalThis.fetch = originalFetch; + }); + + globalThis.fetch = async (url, options = {}) => { + const requestUrl = new URL(url); + if (requestUrl.pathname === "/graphql") { + const body = JSON.parse(options.body); + assert.equal(body.variables.owner, "example"); + assert.equal(body.variables.name, "artifact-cache-test"); + return jsonResponse({ + data: { + repository: { + pull0: { + headRefOid: "head-with-artifact", + isDraft: false, + reviewDecision: null, + totalCommentsCount: 0, + commits: { nodes: [] }, + }, + pull1: { + headRefOid: "head-without-artifact", + isDraft: false, + reviewDecision: null, + totalCommentsCount: 0, + commits: { nodes: [] }, + }, + }, + }, + }); + } + + assert.equal( + requestUrl.pathname, + "/repos/example/artifact-cache-test/actions/artifacts", + ); + artifactRequests.push(requestUrl.search); + const page = Number(requestUrl.searchParams.get("page")); + if (page === 1) { + return jsonResponse({ + total_count: 101, + artifacts: Array.from({ length: 100 }, (_, index) => ({ + id: index + 1, + workflow_run: { head_sha: "unrelated-head" }, + })), + }); + } + assert.equal(page, 2); + return jsonResponse({ + total_count: 101, + artifacts: [ + { + id: 101, + workflow_run: { head_sha: "head-with-artifact" }, + }, + ], + }); + }; + + const pulls = [{ number: 1 }, { number: 2 }]; + const filters = { artifacts: "with" }; + const first = await enrichPullRequests( + "token", + "example/artifact-cache-test", + pulls, + filters, + ); + assert.deepEqual( + first.map((pull) => pull.hasArtifacts), + [true, false], + ); + assert.deepEqual( + filterPullRequests(first, filters).map((pull) => pull.number), + [1], + ); + assert.deepEqual( + filterPullRequests(first, { artifacts: "without" }).map( + (pull) => pull.number, + ), + [2], + ); + + await enrichPullRequests( + "token", + "example/artifact-cache-test", + pulls, + filters, + ); + assert.equal(artifactRequests.length, 2); + assert.match(artifactRequests[0], /per_page=100/); + assert.match(artifactRequests[0], /page=1/); + assert.match(artifactRequests[1], /page=2/); +}); + +test("pull artifact results report the intentional workflow-run cap", async (t) => { + const originalFetch = globalThis.fetch; + const artifactRequests = []; + t.after(() => { + globalThis.fetch = originalFetch; + }); + + globalThis.fetch = async (url) => { + const requestUrl = new URL(url); + if (requestUrl.pathname === "/repos/example/run-cap/pulls/7") { + return jsonResponse({ + number: 7, + title: "Run cap", + state: "open", + head: { sha: "head-sha", ref: "feature" }, + base: { ref: "main" }, + user: { login: "octocat" }, + labels: [], + }); + } + if (requestUrl.pathname === "/repos/example/run-cap/actions/runs") { + assert.equal(requestUrl.searchParams.get("per_page"), "30"); + return jsonResponse({ + total_count: 35, + workflow_runs: Array.from({ length: 30 }, (_, index) => ({ + id: index + 1, + name: "CI", + run_number: index + 1, + run_attempt: 1, + event: "pull_request", + status: "completed", + conclusion: "success", + created_at: new Date(Date.UTC(2026, 0, 1, 0, 0, 35 - index)).toISOString(), + html_url: `https://github.com/example/run-cap/actions/runs/${index + 1}`, + })), + }); + } + const artifactMatch = requestUrl.pathname.match( + /^\/repos\/example\/run-cap\/actions\/runs\/(\d+)\/artifacts$/, + ); + assert.ok(artifactMatch); + artifactRequests.push(Number(artifactMatch[1])); + return jsonResponse({ + total_count: 1, + artifacts: [ + { + id: Number(artifactMatch[1]) * 10, + name: `artifact-${artifactMatch[1]}`, + size_in_bytes: 100, + expired: false, + created_at: "2026-01-01T00:00:00Z", + updated_at: "2026-01-01T00:00:00Z", + expires_at: "2026-04-01T00:00:00Z", + }, + ], + }); + }; + + const result = await listPullRequestArtifacts( + "token", + "example/run-cap", + 7, + ); + assert.equal(artifactRequests.length, 30); + assert.equal(result.runs.length, 30); + assert.equal(result.runCount, 35); + assert.equal(result.runsTruncated, true); + assert.equal(result.artifacts.length, 30); + assert.equal(result.artifactCount, 30); + assert.equal(result.artifactsTruncated, true); +}); + +test("only the archive root index uses static-site preview routing", async () => { + const source = await readFile(new URL("assets/app.js", here), "utf8"); + const helpers = source.slice( + source.indexOf("function artifactEntryKind"), + source.indexOf("function directoryPathsForFile"), + ); + const shouldUseStaticSitePreview = Function( + `${helpers}; return shouldUseStaticSitePreview;`, + )(); + const root = { path: "index.html", kind: "html", supported: true }; + const nested = { + path: "docs/index.html", + kind: "html", + supported: true, + }; + const unsupported = { + path: "index.html", + kind: "html", + supported: false, + }; + const entries = [root, nested]; + + assert.equal(shouldUseStaticSitePreview(root, entries), true); + assert.equal(shouldUseStaticSitePreview(nested, entries), false); + assert.equal(shouldUseStaticSitePreview(nested, [nested]), false); + assert.equal( + shouldUseStaticSitePreview(unsupported, [unsupported]), + false, + ); +}); + +test("completed TRX summaries derive their displayed outcome from counters", async () => { + const source = await readFile( + new URL("assets/trx-preview.js", here), + "utf8", + ); + const helpers = source.slice( + source.indexOf("const KNOWN_TRX_OUTCOMES"), + source.indexOf("function parseDuration"), + ); + const summarizedRunOutcome = Function( + `${helpers}; return summarizedRunOutcome;`, + )(); + + assert.equal( + summarizedRunOutcome({ + outcome: "Completed", + summaryCounts: { failed: 1, passed: 4, skipped: 0 }, + }), + "Failed", + ); + assert.equal( + summarizedRunOutcome({ + outcome: "Completed", + summaryCounts: { failed: 0, passed: 5, skipped: 0 }, + }), + "Passed", + ); + assert.equal( + summarizedRunOutcome({ + outcome: "Completed", + summaryCounts: { failed: 0, passed: 0, skipped: 2 }, + }), + "Skipped", + ); + assert.equal( + summarizedRunOutcome({ + outcome: "Completed", + summaryCounts: { failed: 0, passed: 0, skipped: 0 }, + }), + "Completed", + ); + assert.equal( + summarizedRunOutcome({ + outcome: "Failed", + summaryCounts: { failed: 0, passed: 5, skipped: 0 }, + }), + "Failed", + ); +}); + +test("repository normalization rejects owner and name dot segments", () => { + for (const repository of ["../user", "./repo", "owner/..", "owner/."]) { + assert.throws( + () => normalizeRepository(repository), + /cannot be dot segments/, + ); + } + assert.equal( + normalizeRepository("github/awesome-copilot"), + "github/awesome-copilot", + ); +}); + +test("unsupported entries remain listed but cannot become the primary preview", async () => { + const analysis = await analyzeArtifact( + { + entries: [ + { + name: "index.html", + directory: false, + supported: false, + compressedSize: 10, + uncompressedSize: 20, + }, + { + name: "session.cast", + directory: false, + supported: false, + compressedSize: 10, + uncompressedSize: 20, + }, + { + name: "results.trx", + directory: false, + supported: true, + compressedSize: 10, + uncompressedSize: 20, + }, + ], + totalUncompressedBytes: 60, + }, + async () => { + throw new Error("Unsupported entries must not be probed."); + }, + ); + + assert.equal(analysis.entries.length, 3); + assert.deepEqual(analysis.primary, { + kind: "trx", + path: "results.trx", + label: "Test results", + }); + assert.equal(hasRootIndexHtml(analysis.entries), false); +}); + +test("unsupported entry downloads fall back to the original artifact archive", () => { + assert.equal(contentDeliveryMode({ supported: false }, false), "unsupported"); + assert.equal(contentDeliveryMode({ supported: false }, true), "archive"); + assert.equal(contentDeliveryMode({ supported: true }, true), "entry"); +}); + +test("cache maintenance blocks inspections during artifact deletion", async () => { + const coordinator = new CacheMaintenanceCoordinator(); + const deletionEntered = deferred(); + const releaseDeletion = deferred(); + const deletion = coordinator.deleteArtifact("42", async () => { + deletionEntered.resolve(); + await releaseDeletion.promise; + return { deleted: "42" }; + }); + await deletionEntered.promise; + + let inspectionStarted = false; + const inspection = (async () => { + while (true) { + const barrier = coordinator.inspectionBarrier("42"); + if (!barrier) break; + await barrier; + } + inspectionStarted = true; + })(); + await new Promise((resolve) => setImmediate(resolve)); + assert.equal(inspectionStarted, false); + assert.equal(coordinator.inspectionBarrier("99"), null); + + releaseDeletion.resolve(); + assert.deepEqual(await deletion, { deleted: "42" }); + await inspection; + assert.equal(inspectionStarted, true); +}); + +test("cache clearing waits for deletions and blocks every inspection", async () => { + const coordinator = new CacheMaintenanceCoordinator(); + const deletionEntered = deferred(); + const releaseDeletion = deferred(); + const deletion = coordinator.deleteArtifact("42", async () => { + deletionEntered.resolve(); + await releaseDeletion.promise; + }); + await deletionEntered.promise; + + const clearEntered = deferred(); + const releaseClear = deferred(); + const clear = coordinator.clearCache(async () => { + clearEntered.resolve(); + await releaseClear.promise; + return { cleared: true }; + }); + await new Promise((resolve) => setImmediate(resolve)); + assert.equal(coordinator.inspectionBarrier("99") !== null, true); + + let inspectionStarted = false; + const inspection = (async () => { + while (true) { + const barrier = coordinator.inspectionBarrier("99"); + if (!barrier) break; + await barrier; + } + inspectionStarted = true; + })(); + releaseDeletion.resolve(); + await deletion; + await clearEntered.promise; + assert.equal(inspectionStarted, false); + + releaseClear.resolve(); + assert.deepEqual(await clear, { cleared: true }); + await inspection; + assert.equal(inspectionStarted, true); +}); + +test("artifact truncation messaging names omitted workflow runs", async () => { + const source = await readFile(new URL("assets/app.js", here), "utf8"); + const helperSource = source.slice( + source.indexOf("function artifactResultLimitMessage"), + source.indexOf("function renderPullPayload"), + ); + const artifactResultLimitMessage = Function( + `${helperSource}; return artifactResultLimitMessage;`, + )(); + + assert.equal( + artifactResultLimitMessage({ + runs: Array.from({ length: 30 }), + runCount: 35, + runsTruncated: true, + artifacts: Array.from({ length: 100 }), + artifactCount: 120, + }), + "Showing artifacts from the newest 30 of 35 workflow runs. Showing the newest 100 of 120 artifacts from those runs.", + ); +}); + +test("capability tokens protect loopback APIs, content, and events", () => { + const token = "secret-token"; + const req = (headers = {}) => ({ headers }); + const apiUrl = new URL("http://127.0.0.1:1234/api/bootstrap"); + const contentUrl = new URL( + `http://127.0.0.1:1234/content/1/file.txt?token=${token}`, + ); + const eventsUrl = new URL( + `http://127.0.0.1:1234/events?token=${token}`, + ); + + assert.equal( + isCanonicalHost(req({ host: "127.0.0.1:1234" }), "127.0.0.1:1234"), + true, + ); + assert.equal( + isCanonicalHost(req({ host: "attacker.example:1234" }), "127.0.0.1:1234"), + false, + ); + assert.equal(requiresCapabilityToken("/api/bootstrap"), true); + assert.equal(requiresCapabilityToken("/content/1/file.txt"), true); + assert.equal(requiresCapabilityToken("/events"), true); + assert.equal(requiresCapabilityToken("/assets/app.js"), false); + assert.equal( + hasCapabilityToken(req({ [CAPABILITY_TOKEN_HEADER]: token }), apiUrl, token), + true, + ); + assert.equal(hasCapabilityToken(req(), contentUrl, token), true); + assert.equal(hasCapabilityToken(req(), eventsUrl, token), true); + assert.equal( + hasCapabilityToken( + req(), + new URL(`${apiUrl}?token=${token}`), + token, + ), + false, + ); + assert.equal( + isCrossSiteRequest( + req({ origin: "https://attacker.example" }), + "http://127.0.0.1:1234", + ), + true, + ); +}); + +test("canvas server rejects spoofed hosts and unauthenticated protected routes", async (t) => { + const instanceId = `review-security-${Date.now()}`; + const entry = await startInstance(instanceId, null, () => {}); + t.after(() => stopInstance(instanceId)); + + const root = await fetch(entry.url); + assert.equal(root.status, 200); + assert.match( + await root.text(), + new RegExp(`name="pr-artifact-explorer-token" content="${entry.token}"`), + ); + assert.equal((await fetch(`${entry.origin}/`)).status, 403); + + for (const path of ["api/bootstrap", "content/1/file.txt", "events"]) { + const response = await fetch(`${entry.origin}/${path}`); + assert.equal(response.status, 403); + } + + assert.equal( + await rawStatus(entry.url, { Host: "attacker.example" }), + 403, + ); + const foreignOriginResponse = await fetch(`${entry.origin}/api/bootstrap`, { + headers: { + [CAPABILITY_TOKEN_HEADER]: entry.token, + Origin: "https://attacker.example", + }, + }); + assert.equal(foreignOriginResponse.status, 403); +}); diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/security.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/security.mjs new file mode 100644 index 00000000..6a21f6b6 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/security.mjs @@ -0,0 +1,38 @@ +export const CAPABILITY_TOKEN_HEADER = "x-pr-artifact-explorer-token"; + +export function isCanonicalHost(req, canonicalHost) { + return ( + String(req.headers.host ?? "").toLowerCase() === + String(canonicalHost ?? "").toLowerCase() + ); +} + +export function isCrossSiteRequest(req, canonicalOrigin) { + const origin = req.headers.origin; + if (origin) { + if (origin === canonicalOrigin) return false; + if (origin === "null") return true; + if (/^https?:\/\//i.test(origin)) return true; + return false; + } + const site = req.headers["sec-fetch-site"]; + return site === "cross-site" || site === "same-site"; +} + +export function requiresCapabilityToken(pathname) { + return ( + pathname.startsWith("/api/") || + pathname.startsWith("/content/") || + pathname === "/events" + ); +} + +export function hasCapabilityToken(req, url, expectedToken) { + if (!expectedToken) return false; + const header = req.headers[CAPABILITY_TOKEN_HEADER]; + const headerToken = Array.isArray(header) ? header[0] : header; + if (headerToken === expectedToken) return true; + const allowsQueryToken = + url.pathname.startsWith("/content/") || url.pathname === "/events"; + return allowsQueryToken && url.searchParams.get("token") === expectedToken; +} diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/server.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/server.mjs new file mode 100644 index 00000000..a7179bc7 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/server.mjs @@ -0,0 +1,962 @@ +import { createReadStream } from "node:fs"; +import { stat } from "node:fs/promises"; +import { join, posix } from "node:path"; +import { createServer } from "node:http"; +import { randomBytes, randomUUID } from "node:crypto"; +import { pipeline } from "node:stream/promises"; +import { resolveAccounts, invalidateAccounts } from "./accounts.mjs"; +import { + clearArtifactCache, + deleteCachedArtifact, + getCacheSummary, + getCachedArtifact, + getCachedEntry, + inspectArtifact, + readCachedEntry, +} from "./cache.mjs"; +import { + ASSET_ROOT, + MAX_INLINE_PREVIEW_BYTES, + MAX_STREAMED_ENTRY_BYTES, + MAX_TRX_PREVIEW_BYTES, +} from "./constants.mjs"; +import { + hasRootIndexHtml, + isInlineTextKind, + kindForPath, + mimeForPath, +} from "./detector.mjs"; +import { + enrichPullRequests, + filterPullRequests, + getRepository, + GitHubApiError, + listRepositoryContributors, + listPullRequestArtifacts, + searchPullRequestBase, + searchPullRequests, + suggestRepositories, +} from "./github.mjs"; +import { + startStaticPreview, + stopAllStaticPreviews, + stopStaticPreviewsForArtifact, + stopStaticPreviewsForOrigin, +} from "./preview.mjs"; +import { ExpiringPromiseCache } from "./memory-cache.mjs"; +import { renderHtml } from "./render.mjs"; +import { + hasCapabilityToken, + isCanonicalHost, + isCrossSiteRequest, + requiresCapabilityToken, +} from "./security.mjs"; +import { + loadPrefs, + normalizeRepository, + rememberRepository, + savePrefs, + setExplorerPreferences, + setFavoriteRepository, + setPinnedRepository, + setPlayerPreferences, + setPullFilterPreferences, +} from "./state.mjs"; +import { streamZipEntry } from "./zip.mjs"; + +const servers = new Map(); +const MAX_BODY_BYTES = 1024 * 1024; +const PULL_REQUEST_CACHE_TTL_MS = 2 * 60 * 1_000; +const PROGRESSIVE_PULL_BATCH_SIZE = 25; +const pullRequestCache = new ExpiringPromiseCache({ + maxEntries: 100, + ttlMs: PULL_REQUEST_CACHE_TTL_MS, +}); +const progressivePullRequestCache = new ExpiringPromiseCache({ + maxEntries: 100, + ttlMs: PULL_REQUEST_CACHE_TTL_MS, +}); + +const MAIN_CSP = [ + "default-src 'self'", + "base-uri 'self'", + "connect-src 'self'", + "font-src 'self' data:", + "form-action 'self'", + "frame-src http://127.0.0.1:*", + "img-src 'self' data: https://avatars.githubusercontent.com https://github.com", + "object-src 'none'", + "script-src 'self' 'wasm-unsafe-eval'", + "style-src 'self' 'unsafe-inline'", + "worker-src 'self' blob:", +].join("; "); + +class HttpError extends Error { + constructor(status, message) { + super(message); + this.name = "HttpError"; + this.status = status; + } +} + +function createCapabilityToken() { + return randomBytes(32).toString("base64url"); +} + +function sendJson(res, status, value) { + const body = JSON.stringify(value); + res.writeHead(status, { + "Cache-Control": "no-store", + "Content-Length": Buffer.byteLength(body), + "Content-Type": "application/json; charset=utf-8", + "X-Content-Type-Options": "nosniff", + }); + res.end(body); +} + +function sendText(res, status, value, type = "text/plain; charset=utf-8", headers = {}) { + const body = String(value); + res.writeHead(status, { + "Cache-Control": "no-store", + "Content-Length": Buffer.byteLength(body), + "Content-Type": type, + "X-Content-Type-Options": "nosniff", + ...headers, + }); + res.end(body); +} + +function pullRequestCacheKey(account, kind, values) { + return [ + account?.id ?? account?.login ?? "unknown", + kind, + ...values.map((value) => String(value ?? "").toLocaleLowerCase()), + ].join("|"); +} + +function progressivePullPayload(session, pulls, { + complete = false, + offset = 0, +} = {}) { + const filterActive = + session.base.filters.artifacts !== "all" || + session.base.filters.ci !== "all"; + return { + ...session.base, + filtered: complete && filterActive, + pulls, + progressive: { + batchSize: PROGRESSIVE_PULL_BATCH_SIZE, + complete, + detailedCount: session.completedNumbers.size, + loadedCount: Math.min( + session.base.pulls.length, + offset + pulls.length, + ), + offset, + sessionId: session.id, + sourceCount: session.base.pulls.length, + }, + }; +} + +function completeProgressivePullPayload(session) { + const enriched = session.base.pulls + .map((pull) => session.enrichedPulls.get(Number(pull.number))) + .filter(Boolean); + return progressivePullPayload( + session, + filterPullRequests(enriched, session.base.filters), + { complete: true }, + ); +} + +async function loadProgressivePullDetails( + session, + cacheKey, + token, + repository, + offset, +) { + const completed = session.detailResults.get(offset); + if (completed) return completed; + const existing = session.detailPromises.get(offset); + if (existing) return existing; + const source = session.base.pulls.slice( + offset, + offset + PROGRESSIVE_PULL_BATCH_SIZE, + ); + const promise = (async () => { + const enriched = await enrichPullRequests( + token, + repository, + source, + session.base.filters, + ); + for (const pull of enriched) { + session.enrichedPulls.set(Number(pull.number), pull); + session.completedNumbers.add(Number(pull.number)); + } + const sourceNumbers = source.map((pull) => Number(pull.number)); + const complete = + session.completedNumbers.size >= session.base.pulls.length; + const payload = complete ? completeProgressivePullPayload(session) : null; + if ( + payload && + progressivePullRequestCache.peek(cacheKey) === session + ) { + pullRequestCache.set(cacheKey, payload); + } + const result = { + complete, + completedCount: session.completedNumbers.size, + payload, + pulls: filterPullRequests(enriched, session.base.filters), + sourceNumbers, + }; + session.detailResults.set(offset, result); + return result; + })(); + session.detailPromises.set(offset, promise); + try { + return await promise; + } finally { + if (session.detailPromises.get(offset) === promise) { + session.detailPromises.delete(offset); + } + } +} + +async function sendFile(res, filePath, type) { + const info = await stat(filePath); + if (!info.isFile()) throw new HttpError(404, "Asset not found."); + res.writeHead(200, { + "Cache-Control": "public, max-age=3600", + "Content-Length": String(info.size), + "Content-Type": type, + "X-Content-Type-Options": "nosniff", + }); + await pipeline(createReadStream(filePath), res); +} + +async function readJsonBody(req) { + const type = req.headers["content-type"] ?? ""; + if (!String(type).toLowerCase().startsWith("application/json")) { + throw new HttpError(415, "Request body must be JSON."); + } + const chunks = []; + let length = 0; + for await (const chunk of req) { + length += chunk.length; + if (length > MAX_BODY_BYTES) throw new HttpError(413, "Request body is too large."); + chunks.push(chunk); + } + if (!chunks.length) return {}; + try { + return JSON.parse(Buffer.concat(chunks).toString("utf8")); + } catch (error) { + throw new HttpError(400, "Request body is not valid JSON.", { cause: error }); + } +} + +function broadcast(entry, event, payload) { + const encoded = JSON.stringify(payload); + for (const response of entry.sseClients) { + try { + response.write(`event: ${event}\ndata: ${encoded}\n\n`); + } catch { + entry.sseClients.delete(response); + } + } +} + +async function resolveAuth(repository, force = false) { + const prefs = await loadPrefs(); + const previousRepository = prefs.repository; + const previousRecent = JSON.stringify(prefs.repositories.recent); + const normalizedRepository = rememberRepository(prefs, repository); + const auth = await resolveAccounts({ + preferredId: prefs.account, + repository: normalizedRepository, + force, + }); + let changed = + prefs.repository !== previousRepository || + JSON.stringify(prefs.repositories.recent) !== previousRecent; + if (auth.active && prefs.account !== auth.active.id) { + prefs.account = auth.active.id; + changed = true; + } + if (changed) await savePrefs(prefs); + if (!auth.activeToken) { + throw new HttpError( + 401, + "No usable GitHub account was detected. Sign in through the Copilot app or GitHub CLI.", + ); + } + return { prefs, auth }; +} + +function parseArtifactRoute(pathname) { + return pathname.match(/^\/api\/artifacts\/(\d+)$/); +} + +function decodeEntryPath(encoded) { + try { + return encoded.split("/").map((part) => decodeURIComponent(part)).join("/"); + } catch (error) { + throw new HttpError(400, "File path contains invalid URL encoding.", { cause: error }); + } +} + +export function contentDeliveryMode(entry, download) { + if (entry?.supported) return "entry"; + return download ? "archive" : "unsupported"; +} + +function archiveDownloadName(context, artifactId) { + const rawName = String( + context.metadata?.artifact?.name || `artifact-${artifactId}`, + ).replaceAll("\\", "/"); + const baseName = posix.basename(rawName) || `artifact-${artifactId}`; + return baseName.toLowerCase().endsWith(".zip") + ? baseName + : `${baseName}.zip`; +} + +async function sendArchiveDownload(req, res, context, artifactId) { + const info = await stat(context.archivePath); + const filename = archiveDownloadName(context, artifactId); + res.writeHead(200, { + "Cache-Control": "no-store", + "Content-Disposition": + `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`, + "Content-Length": String(info.size), + "Content-Security-Policy": "default-src 'none'; sandbox", + "Content-Type": "application/zip", + "Cross-Origin-Resource-Policy": "same-origin", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }); + if (req.method === "HEAD") { + res.end(); + return; + } + await pipeline(createReadStream(context.archivePath), res); +} + +async function handleContentRequest(req, res, pathname, searchParams) { + const match = pathname.match(/^\/content\/(\d+)\/(.+)$/); + if (!match) return false; + if (!["GET", "HEAD"].includes(req.method)) { + throw new HttpError(405, "Only GET and HEAD are supported."); + } + const artifactId = match[1]; + const entryPath = decodeEntryPath(match[2]); + const context = await getCachedEntry(artifactId, entryPath); + const download = searchParams.get("download") === "1"; + const deliveryMode = contentDeliveryMode(context.entry, download); + if (deliveryMode === "unsupported") { + throw new HttpError(415, "This ZIP entry cannot be served."); + } + if (deliveryMode === "archive") { + await sendArchiveDownload(req, res, context, artifactId); + return true; + } + if (context.entry.uncompressedSize > MAX_STREAMED_ENTRY_BYTES) { + throw new HttpError( + 413, + "This file exceeds the 512 MiB streaming safety limit.", + ); + } + + const headers = { + "Cache-Control": "no-store", + "Content-Length": String(context.entry.uncompressedSize), + "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "Content-Type": mimeForPath(context.entry.name), + "Cross-Origin-Resource-Policy": "same-origin", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }; + if (download) { + headers["Content-Disposition"] = + `attachment; filename*=UTF-8''${encodeURIComponent(posix.basename(context.entry.name))}`; + } + res.writeHead(200, headers); + if (req.method === "HEAD") { + res.end(); + } else { + await streamZipEntry(context.archivePath, context.entry, res); + } + return true; +} + +async function handleApi(req, res, entry, url) { + const path = url.pathname; + if (req.method === "GET" && path === "/api/bootstrap") { + const prefs = await loadPrefs(); + const repository = + typeof entry.initialInput?.repository === "string" + ? normalizeRepository(entry.initialInput.repository) + : prefs.repository; + const auth = await resolveAccounts({ + preferredId: prefs.account, + repository, + }); + if (auth.active && prefs.account !== auth.active.id) { + prefs.account = auth.active.id; + await savePrefs(prefs); + } + return sendJson(res, 200, { + prefs, + account: auth.active, + accounts: auth.accounts, + cache: await getCacheSummary(), + initialInput: entry.initialInput ?? null, + }); + } + + if (req.method === "GET" && path === "/api/accounts") { + const prefs = await loadPrefs(); + const repository = normalizeRepository( + url.searchParams.get("repo") || prefs.repository, + ); + const { auth } = await resolveAuth(repository, true); + pullRequestCache.clear(); + progressivePullRequestCache.clear(); + return sendJson(res, 200, { + account: auth.active, + accounts: auth.accounts, + prefs: await loadPrefs(), + }); + } + + if (req.method === "POST" && path === "/api/account") { + const body = await readJsonBody(req); + if (typeof body.id !== "string" || !body.id) { + throw new HttpError(400, "Account id is required."); + } + const repository = normalizeRepository(body.repository); + const prefs = await loadPrefs(); + prefs.account = body.id; + await savePrefs(prefs); + invalidateAccounts(); + pullRequestCache.clear(); + progressivePullRequestCache.clear(); + const { auth } = await resolveAuth(repository, true); + broadcast(entry, "refresh", { reason: "account" }); + return sendJson(res, 200, { + account: auth.active, + accounts: auth.accounts, + prefs: await loadPrefs(), + }); + } + + if (req.method === "GET" && path === "/api/repositories/suggest") { + const query = String(url.searchParams.get("q") ?? "").trim(); + const prefs = await loadPrefs(); + const auth = await resolveAccounts({ + preferredId: prefs.account, + repository: prefs.repository, + }); + if (!auth.activeToken) { + throw new HttpError(401, "No usable GitHub account was found."); + } + return sendJson(res, 200, { + repositories: await suggestRepositories(auth.activeToken, query), + }); + } + + if (req.method === "POST" && path === "/api/repositories/validate") { + const body = await readJsonBody(req); + const repository = normalizeRepository(body.repository); + const prefs = await loadPrefs(); + const auth = await resolveAccounts({ + preferredId: prefs.account, + repository, + }); + if (!auth.activeToken) { + throw new HttpError(401, "No usable GitHub account was found."); + } + return sendJson(res, 200, { + repository: await getRepository(auth.activeToken, repository), + }); + } + + if (req.method === "GET" && path === "/api/repositories/authors") { + const repository = normalizeRepository(url.searchParams.get("repo")); + const { auth } = await resolveAuth(repository); + return sendJson(res, 200, { + authors: await listRepositoryContributors(auth.activeToken, repository), + }); + } + + if (req.method === "POST" && path === "/api/repositories/pin") { + const body = await readJsonBody(req); + const prefs = await loadPrefs(); + setPinnedRepository(prefs, body.repository); + return sendJson(res, 200, await savePrefs(prefs)); + } + + if (req.method === "POST" && path === "/api/repositories/favorite") { + const body = await readJsonBody(req); + if (typeof body.favorite !== "boolean") { + throw new HttpError(400, "favorite must be a boolean."); + } + const prefs = await loadPrefs(); + try { + setFavoriteRepository(prefs, body.repository, body.favorite); + } catch (error) { + throw new HttpError( + 400, + error instanceof Error ? error.message : "Repository could not be favorited.", + ); + } + return sendJson(res, 200, await savePrefs(prefs)); + } + + if (req.method === "POST" && path === "/api/preferences") { + const body = await readJsonBody(req); + const prefs = await loadPrefs(); + if (typeof body.repository === "string") { + rememberRepository(prefs, body.repository); + } + if (["open", "closed", "all"].includes(body.pullState)) { + prefs.pullState = body.pullState; + } + if ( + typeof body.repository === "string" && + body.pullFilter && + typeof body.pullFilter === "object" + ) { + setPullFilterPreferences(prefs, body.repository, body.pullFilter); + } + if (body.explorer && typeof body.explorer === "object") { + setExplorerPreferences(prefs, body.explorer); + } + if (body.player && typeof body.player === "object") { + setPlayerPreferences(prefs, body.player); + } + return sendJson(res, 200, await savePrefs(prefs)); + } + + if (req.method === "GET" && path === "/api/pulls") { + const repository = normalizeRepository(url.searchParams.get("repo")); + const state = url.searchParams.get("state") ?? "open"; + const query = url.searchParams.get("q") ?? ""; + const artifacts = url.searchParams.get("artifacts") ?? "all"; + const ci = url.searchParams.get("ci") ?? "all"; + const { auth } = await resolveAuth(repository); + const cacheKey = pullRequestCacheKey(auth.active, "list", [ + repository, + state, + query, + artifacts, + ci, + ]); + const phase = url.searchParams.get("phase"); + const force = url.searchParams.get("refresh") === "1"; + if (phase) { + if (!["initial", "batch", "details"].includes(phase)) { + throw new HttpError(400, "Unknown progressive pull request phase."); + } + if (phase === "initial") { + const cached = force ? undefined : pullRequestCache.peek(cacheKey); + if (cached) { + return sendJson(res, 200, { + repository, + ...cached, + account: auth.active, + progressive: { + batchSize: PROGRESSIVE_PULL_BATCH_SIZE, + complete: true, + detailedCount: cached.evaluatedCount, + loadedCount: cached.pulls.length, + offset: 0, + sourceCount: cached.evaluatedCount, + }, + }); + } + if (force) pullRequestCache.delete(cacheKey); + const session = await progressivePullRequestCache.get( + cacheKey, + async () => ({ + id: randomUUID(), + base: await searchPullRequestBase( + auth.activeToken, + repository, + query, + state, + { artifacts, ci }, + { perPage: 100 }, + ), + completedNumbers: new Set(), + detailPromises: new Map(), + detailResults: new Map(), + enrichedPulls: new Map(), + }), + { force }, + ); + if (!session.base.pulls.length) { + if (progressivePullRequestCache.peek(cacheKey) !== session) { + throw new HttpError( + 409, + "The progressive pull request load expired. Refresh the list.", + ); + } + const payload = completeProgressivePullPayload(session); + pullRequestCache.set(cacheKey, payload); + return sendJson(res, 200, { + repository, + ...payload, + account: auth.active, + }); + } + return sendJson(res, 200, { + repository, + ...progressivePullPayload( + session, + session.base.pulls.slice(0, PROGRESSIVE_PULL_BATCH_SIZE), + ), + account: auth.active, + }); + } + + const session = progressivePullRequestCache.peek(cacheKey); + const requestedSessionId = url.searchParams.get("session"); + if (!session || !requestedSessionId || session.id !== requestedSessionId) { + throw new HttpError( + 409, + "The progressive pull request load expired. Refresh the list.", + ); + } + const offset = Number.parseInt(url.searchParams.get("offset"), 10); + if ( + !Number.isSafeInteger(offset) || + offset < 0 || + offset >= session.base.pulls.length || + offset % PROGRESSIVE_PULL_BATCH_SIZE !== 0 + ) { + throw new HttpError(400, "A valid progressive batch offset is required."); + } + if (phase === "batch") { + return sendJson(res, 200, { + repository, + ...progressivePullPayload( + session, + session.base.pulls.slice( + offset, + offset + PROGRESSIVE_PULL_BATCH_SIZE, + ), + { offset }, + ), + account: auth.active, + }); + } + const details = await loadProgressivePullDetails( + session, + cacheKey, + auth.activeToken, + repository, + offset, + ); + if (progressivePullRequestCache.peek(cacheKey) !== session) { + throw new HttpError( + 409, + "The progressive pull request load expired. Refresh the list.", + ); + } + return sendJson(res, 200, { + repository, + ...details, + account: auth.active, + }); + } + + const result = await pullRequestCache.get( + cacheKey, + () => + searchPullRequests( + auth.activeToken, + repository, + query, + state, + { artifacts, ci }, + ), + { force }, + ); + return sendJson(res, 200, { + repository, + ...result, + account: auth.active, + }); + } + + const pullMatch = path.match(/^\/api\/pulls\/(\d+)$/); + if (req.method === "GET" && pullMatch) { + const repository = normalizeRepository(url.searchParams.get("repo")); + const { auth } = await resolveAuth(repository); + const payload = await pullRequestCache.get( + pullRequestCacheKey(auth.active, "detail", [repository, pullMatch[1]]), + () => + listPullRequestArtifacts( + auth.activeToken, + repository, + pullMatch[1], + ), + { force: url.searchParams.get("refresh") === "1" }, + ); + return sendJson(res, 200, { + repository, + ...payload, + account: auth.active, + }); + } + + if (req.method === "POST" && path === "/api/artifacts/inspect") { + const body = await readJsonBody(req); + const repository = normalizeRepository(body.repository); + const { auth } = await resolveAuth(repository); + const metadata = await inspectArtifact( + auth.activeToken, + repository, + body.artifactId, + { + onProgress: (progress) => + broadcast(entry, "artifact-progress", { repository, ...progress }), + }, + ); + broadcast(entry, "cache", await getCacheSummary()); + return sendJson(res, 200, metadata); + } + + const artifactMatch = parseArtifactRoute(path); + if (req.method === "GET" && artifactMatch) { + const metadata = await getCachedArtifact(artifactMatch[1]); + if (!metadata) throw new HttpError(404, "Artifact is not cached."); + return sendJson(res, 200, metadata); + } + + const textMatch = path.match(/^\/api\/artifacts\/(\d+)\/text$/); + if (req.method === "GET" && textMatch) { + const entryPath = url.searchParams.get("path"); + if (!entryPath) throw new HttpError(400, "File path is required."); + const kind = kindForPath(entryPath); + if (!isInlineTextKind(kind)) { + throw new HttpError(415, "This file is not an inline text preview."); + } + const context = await readCachedEntry( + textMatch[1], + entryPath, + kind === "trx" ? MAX_TRX_PREVIEW_BYTES : MAX_INLINE_PREVIEW_BYTES, + ); + return sendJson(res, 200, { + path: context.entry.name, + kind, + content: context.content.toString("utf8"), + }); + } + + const previewMatch = path.match(/^\/api\/artifacts\/(\d+)\/preview-url$/); + if (req.method === "GET" && previewMatch) { + const entryPath = url.searchParams.get("path"); + if (!entryPath) throw new HttpError(400, "File path is required."); + if (kindForPath(entryPath) !== "html") { + throw new HttpError(415, "Only HTML files can use the site preview."); + } + const metadata = await getCachedArtifact(previewMatch[1], { touch: false }); + if (!metadata) throw new HttpError(404, "Artifact is not cached. Inspect it first."); + if (!hasRootIndexHtml(metadata.analysis?.entries ?? [])) { + throw new HttpError( + 415, + "HTML previews require index.html at the artifact root.", + ); + } + const theme = url.searchParams.get("theme"); + if (theme !== "light" && theme !== "dark") { + throw new HttpError(400, "Preview theme must be light or dark."); + } + return sendJson(res, 200, { + url: await startStaticPreview(previewMatch[1], entryPath, { + theme, + parentOrigin: new URL(entry.url).origin, + }), + }); + } + + if (req.method === "GET" && path === "/api/cache") { + return sendJson(res, 200, await getCacheSummary()); + } + + const cacheMatch = path.match(/^\/api\/cache\/(\d+)$/); + if (req.method === "DELETE" && cacheMatch) { + await stopStaticPreviewsForArtifact(cacheMatch[1]); + const result = await deleteCachedArtifact(cacheMatch[1]); + broadcast(entry, "cache", await getCacheSummary()); + return sendJson(res, 200, result); + } + + if (req.method === "DELETE" && path === "/api/cache") { + await stopAllStaticPreviews(); + const result = await clearArtifactCache(); + broadcast(entry, "cache", await getCacheSummary()); + return sendJson(res, 200, result); + } + + if (req.method === "GET" && path === "/events") { + res.writeHead(200, { + "Cache-Control": "no-cache", + Connection: "keep-alive", + "Content-Type": "text/event-stream", + }); + res.write(": connected\n\n"); + entry.sseClients.add(res); + req.on("close", () => entry.sseClients.delete(res)); + return; + } + + throw new HttpError(404, "API route not found."); +} + +async function handleRequest(req, res, entry) { + const url = new URL(req.url, "http://127.0.0.1"); + try { + if (!isCanonicalHost(req, entry.host)) { + throw new HttpError(403, "Request host is not allowed."); + } + const protectedRoute = requiresCapabilityToken(url.pathname); + if (protectedRoute && isCrossSiteRequest(req, entry.origin)) { + throw new HttpError(403, "Cross-site requests are not allowed."); + } + if (protectedRoute && !hasCapabilityToken(req, url, entry.token)) { + throw new HttpError(403, "Missing or invalid capability token."); + } + + const documentRoute = + req.method === "GET" && + (url.pathname === "/" || url.pathname === "/index.html"); + if (documentRoute && url.searchParams.get("token") !== entry.token) { + throw new HttpError(403, "Missing or invalid capability token."); + } + if (documentRoute) { + return sendText(res, 200, renderHtml(entry.token), "text/html; charset=utf-8", { + "Content-Security-Policy": MAIN_CSP, + "Referrer-Policy": "no-referrer", + }); + } + + const assetTypes = new Map([ + ["/assets/primer-color-modes.css", ["primer-color-modes.css", "text/css; charset=utf-8"]], + ["/assets/primer-core.css", ["primer-core.css", "text/css; charset=utf-8"]], + ["/assets/primer-product.css", ["primer-product.css", "text/css; charset=utf-8"]], + ["/assets/asciinema-player.css", ["asciinema-player.css", "text/css; charset=utf-8"]], + ["/assets/asciinema-player.min.js", ["asciinema-player.min.js", "text/javascript; charset=utf-8"]], + ["/assets/asciinema-player-worker.min.js", ["asciinema-player-worker.min.js", "text/javascript; charset=utf-8"]], + ["/assets/app.css", ["app.css", "text/css; charset=utf-8"]], + ["/assets/trx-preview.js", ["trx-preview.js", "text/javascript; charset=utf-8"]], + ["/assets/app.js", ["app.js", "text/javascript; charset=utf-8"]], + ]); + if (req.method === "GET" && assetTypes.has(url.pathname)) { + const [name, type] = assetTypes.get(url.pathname); + return await sendFile(res, join(ASSET_ROOT, name), type); + } + const iconMatch = url.pathname.match(/^\/assets\/octicons\/([a-z-]+)\.svg$/); + if (req.method === "GET" && iconMatch) { + return await sendFile( + res, + join(ASSET_ROOT, "octicons", `${iconMatch[1]}.svg`), + "image/svg+xml", + ); + } + + if (await handleContentRequest(req, res, url.pathname, url.searchParams)) return; + if (url.pathname.startsWith("/api/") || url.pathname === "/events") { + return await handleApi(req, res, entry, url); + } + throw new HttpError(404, "Route not found."); + } catch (error) { + entry.log?.(`${req.method} ${url.pathname}: ${error instanceof Error ? error.message : String(error)}`); + if (res.headersSent) { + res.end(); + return; + } + const status = + error instanceof HttpError + ? error.status + : error instanceof GitHubApiError + ? error.status + : 500; + sendJson(res, status, { + error: error instanceof Error ? error.message : String(error), + status, + }); + } +} + +export async function startInstance(instanceId, initialInput, log) { + let entry = servers.get(instanceId); + if (entry) { + entry.initialInput = initialInput ?? entry.initialInput; + return entry; + } + + entry = { + host: null, + initialInput: initialInput ?? null, + log, + origin: null, + server: null, + sseClients: new Set(), + token: createCapabilityToken(), + url: null, + }; + const server = createServer((req, res) => { + void handleRequest(req, res, entry); + }); + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + const address = server.address(); + if (!address || typeof address === "string") { + await new Promise((resolve) => server.close(resolve)); + throw new Error("Canvas server did not receive a TCP port."); + } + entry.server = server; + entry.host = `127.0.0.1:${address.port}`; + entry.origin = `http://${entry.host}`; + entry.url = `${entry.origin}/?token=${encodeURIComponent(entry.token)}`; + servers.set(instanceId, entry); + return entry; +} + +export async function stopInstance(instanceId) { + const entry = servers.get(instanceId); + if (!entry) return; + servers.delete(instanceId); + for (const response of entry.sseClients) response.end(); + if (entry.url) { + await stopStaticPreviewsForOrigin(new URL(entry.url).origin); + } + await new Promise((resolve) => entry.server.close(resolve)); +} + +export function navigateInstance(instanceId, route) { + const entry = servers.get(instanceId); + if (!entry) return false; + broadcast(entry, "navigate", { route }); + return true; +} + +export async function broadcastCache({ refresh = false } = {}) { + const summary = await getCacheSummary(); + for (const entry of servers.values()) { + broadcast(entry, "cache", summary); + if (refresh) broadcast(entry, "refresh", { reason: "cache" }); + } +} + +export async function closeAllPreviews() { + await stopAllStaticPreviews(); +} diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/state.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/state.mjs new file mode 100644 index 00000000..f460c14f --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/state.mjs @@ -0,0 +1,290 @@ +import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"; +import { dirname } from "node:path"; +import { setTimeout as delay } from "node:timers/promises"; +import { PREFS_FILE } from "./constants.mjs"; + +const MAX_RECENT_REPOSITORIES = 12; +const MAX_FAVORITE_REPOSITORIES = 3; +const MAX_PULL_FILTER_REPOSITORIES = 50; +const PLAYER_FONT_SIZES = new Set(["12px", "14px", "16px", "18px", "20px", "22px"]); +const PLAYER_FONTS = new Set(["system", "cascadia", "consolas"]); +const PLAYER_SPEEDS = new Set([0.5, 1, 1.5, 2]); +const PULL_ARTIFACT_FILTERS = new Set(["all", "with", "without"]); +const PULL_CI_FILTERS = new Set(["all", "failing", "passing", "pending", "none"]); +let preferenceSaveQueue = Promise.resolve(); +let preferenceSaveSequence = 0; +const WINDOWS_RENAME_RETRY_CODES = new Set(["EACCES", "EBUSY", "EPERM"]); + +export const DEFAULT_PREFS = Object.freeze({ + account: null, + repository: "microsoft/aspire", + pullState: "open", + repositories: { + pinned: null, + favorites: [], + recent: ["microsoft/aspire"], + }, + pullFilters: {}, + explorer: { + sidebarCollapsed: false, + }, + player: { + fontSize: "16px", + fontFamily: "system", + lineHeight: 1.2, + speed: 1, + }, +}); + +export function normalizeRepository(value) { + const repository = String(value ?? "").trim().replace(/^https?:\/\/github\.com\//i, ""); + const match = repository.match(/^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?$/); + if (!match) { + throw new Error("Repository must use the owner/name format."); + } + if ([match[1], match[2]].some((part) => part === "." || part === "..")) { + throw new Error("Repository owner and name cannot be dot segments."); + } + return `${match[1]}/${match[2]}`; +} + +function normalizeStoredRepository(value) { + try { + return typeof value === "string" && value.trim() ? normalizeRepository(value) : null; + } catch { + return null; + } +} + +function normalizeRepositoryList(value, limit) { + const repositories = []; + for (const item of Array.isArray(value) ? value : []) { + const repository = normalizeStoredRepository(item); + if (repository && !repositories.includes(repository)) repositories.push(repository); + if (repositories.length === limit) break; + } + return repositories; +} + +function normalizePlayer(value) { + const lineHeight = Number(value?.lineHeight); + const speed = Number(value?.speed); + return { + fontSize: PLAYER_FONT_SIZES.has(value?.fontSize) + ? value.fontSize + : DEFAULT_PREFS.player.fontSize, + fontFamily: PLAYER_FONTS.has(value?.fontFamily) + ? value.fontFamily + : DEFAULT_PREFS.player.fontFamily, + lineHeight: + Number.isFinite(lineHeight) && lineHeight >= 1 && lineHeight <= 1.6 + ? Math.round(lineHeight * 10) / 10 + : DEFAULT_PREFS.player.lineHeight, + speed: PLAYER_SPEEDS.has(speed) ? speed : DEFAULT_PREFS.player.speed, + }; +} + +function normalizeExplorer(value) { + return { + sidebarCollapsed: value?.sidebarCollapsed === true, + }; +} + +function normalizePullFilter(value) { + const author = + typeof value?.author === "string" && value.author.trim() + ? value.author.trim().slice(0, 100) + : null; + const updatedAt = Number(value?.updatedAt); + return { + state: ["open", "closed", "all"].includes(value?.state) ? value.state : "open", + author, + artifacts: PULL_ARTIFACT_FILTERS.has(value?.artifacts) ? value.artifacts : "all", + ci: PULL_CI_FILTERS.has(value?.ci) ? value.ci : "all", + updatedAt: Number.isFinite(updatedAt) && updatedAt > 0 ? updatedAt : 0, + }; +} + +function normalizePullFilters(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) return {}; + const entries = []; + for (const [key, filter] of Object.entries(value)) { + const repository = normalizeStoredRepository(key); + if (!repository || !filter || typeof filter !== "object") continue; + entries.push([ + repository.toLocaleLowerCase(), + normalizePullFilter(filter), + ]); + } + return Object.fromEntries( + entries + .sort((left, right) => right[1].updatedAt - left[1].updatedAt) + .slice(0, MAX_PULL_FILTER_REPOSITORIES), + ); +} + +export function normalizePrefs(value) { + const prefs = value && typeof value === "object" ? value : {}; + const repository = + normalizeStoredRepository(prefs.repository) ?? DEFAULT_PREFS.repository; + const pinned = normalizeStoredRepository(prefs.repositories?.pinned); + const favorites = normalizeRepositoryList( + prefs.repositories?.favorites, + MAX_FAVORITE_REPOSITORIES, + ).filter((item) => item !== pinned); + return { + account: typeof prefs.account === "string" && prefs.account ? prefs.account : null, + repository, + pullState: ["open", "closed", "all"].includes(prefs.pullState) + ? prefs.pullState + : DEFAULT_PREFS.pullState, + repositories: { + pinned, + favorites, + recent: normalizeRepositoryList( + [ + repository, + ...(Array.isArray(prefs.repositories?.recent) + ? prefs.repositories.recent + : []), + ], + MAX_RECENT_REPOSITORIES, + ), + }, + pullFilters: normalizePullFilters(prefs.pullFilters), + explorer: normalizeExplorer(prefs.explorer), + player: normalizePlayer(prefs.player), + }; +} + +export async function loadPrefs() { + try { + return normalizePrefs(JSON.parse(await readFile(PREFS_FILE, "utf8"))); + } catch (error) { + if (error?.code === "ENOENT") { + return normalizePrefs(); + } + if (error instanceof SyntaxError) { + throw new Error(`Preferences are not valid JSON: ${PREFS_FILE}`, { cause: error }); + } + throw error; + } +} + +async function replacePreferencesFile(temporary) { + let lastError; + for (let attempt = 0; attempt < 7; attempt++) { + try { + await rename(temporary, PREFS_FILE); + return; + } catch (error) { + if (!WINDOWS_RENAME_RETRY_CODES.has(error?.code)) throw error; + lastError = error; + if (attempt === 6) break; + await delay(10 * 2 ** attempt); + } + } + try { + await rm(temporary, { force: true }); + } catch (cleanupError) { + throw new AggregateError( + [lastError, cleanupError], + "Preferences could not be replaced or cleaned up.", + ); + } + throw lastError; +} + +export async function savePrefs(value) { + const prefs = normalizePrefs(value); + const save = async () => { + await mkdir(dirname(PREFS_FILE), { recursive: true }); + const temporary = + `${PREFS_FILE}.${process.pid}.${++preferenceSaveSequence}.tmp`; + await writeFile(temporary, `${JSON.stringify(prefs, null, 2)}\n`, "utf8"); + await replacePreferencesFile(temporary); + return prefs; + }; + const pending = preferenceSaveQueue.then(save, save); + preferenceSaveQueue = pending.then( + () => undefined, + () => undefined, + ); + return pending; +} + +export function rememberRepository(preferences, value) { + const repository = normalizeRepository(value); + preferences.repository = repository; + preferences.repositories ??= {}; + preferences.repositories.recent = normalizeRepositoryList( + [ + repository, + ...(Array.isArray(preferences.repositories.recent) + ? preferences.repositories.recent + : []), + ], + MAX_RECENT_REPOSITORIES, + ); + return repository; +} + +export function setPinnedRepository(preferences, value) { + const repository = value == null || value === "" ? null : normalizeRepository(value); + preferences.repositories ??= {}; + preferences.repositories.pinned = repository; + preferences.repositories.favorites = normalizeRepositoryList( + preferences.repositories.favorites, + MAX_FAVORITE_REPOSITORIES, + ).filter((item) => item !== repository); + return repository; +} + +export function setFavoriteRepository(preferences, value, favorite) { + const repository = normalizeRepository(value); + preferences.repositories ??= {}; + const favorites = normalizeRepositoryList( + preferences.repositories.favorites, + MAX_FAVORITE_REPOSITORIES, + ).filter((item) => item !== repository); + if (favorite) { + if (preferences.repositories.pinned === repository) { + throw new Error("The pinned repository is already available in quick switch."); + } + if (favorites.length >= MAX_FAVORITE_REPOSITORIES) { + throw new Error("You can favorite up to three repositories."); + } + favorites.unshift(repository); + } + preferences.repositories.favorites = favorites; + return favorites; +} + +export function setPullFilterPreferences(preferences, repositoryValue, value) { + const repository = normalizeRepository(repositoryValue).toLocaleLowerCase(); + preferences.pullFilters = normalizePullFilters({ + ...preferences.pullFilters, + [repository]: { + ...preferences.pullFilters?.[repository], + ...value, + updatedAt: Date.now(), + }, + }); + return preferences.pullFilters[repository]; +} + +export function setExplorerPreferences(preferences, value) { + preferences.explorer = normalizeExplorer({ + ...preferences.explorer, + ...value, + }); + return preferences.explorer; +} + +export function setPlayerPreferences(preferences, value) { + preferences.player = normalizePlayer({ + ...preferences.player, + ...value, + }); + return preferences.player; +} diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/third-party-licenses/asciinema-player.txt b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/third-party-licenses/asciinema-player.txt new file mode 100644 index 00000000..15258081 --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/third-party-licenses/asciinema-player.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2011-2021 Marcin Kulik + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/third-party-licenses/octicons.txt b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/third-party-licenses/octicons.txt new file mode 100644 index 00000000..aa1fa80e --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/third-party-licenses/octicons.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 GitHub Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/third-party-licenses/primer-css.txt b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/third-party-licenses/primer-css.txt new file mode 100644 index 00000000..4a03ad6b --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/third-party-licenses/primer-css.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 GitHub Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/zip.mjs b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/zip.mjs new file mode 100644 index 00000000..2aec144d --- /dev/null +++ b/extensions/pr-artifact-explorer/extensions/pr-artifact-explorer/zip.mjs @@ -0,0 +1,401 @@ +import { createReadStream } from "node:fs"; +import { open, stat } from "node:fs/promises"; +import { posix } from "node:path"; +import { pipeline } from "node:stream/promises"; +import { Transform } from "node:stream"; +import { createInflateRaw } from "node:zlib"; +import { + MAX_CENTRAL_DIRECTORY_BYTES, + MAX_STREAMED_ENTRY_BYTES, + MAX_ZIP_ENTRIES, +} from "./constants.mjs"; + +const EOCD_SIGNATURE = 0x06054b50; +const CENTRAL_SIGNATURE = 0x02014b50; +const LOCAL_SIGNATURE = 0x04034b50; +const ZIP64_U16 = 0xffff; +const ZIP64_U32 = 0xffffffff; + +const crcTable = new Uint32Array(256); +for (let index = 0; index < 256; index++) { + let value = index; + for (let bit = 0; bit < 8; bit++) { + value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1; + } + crcTable[index] = value >>> 0; +} + +function crc32(buffer) { + let value = 0xffffffff; + for (const byte of buffer) { + value = crcTable[(value ^ byte) & 0xff] ^ (value >>> 8); + } + return (value ^ 0xffffffff) >>> 0; +} + +async function readExactly(handle, length, position) { + const buffer = Buffer.allocUnsafe(length); + let offset = 0; + while (offset < length) { + const result = await handle.read(buffer, offset, length - offset, position + offset); + if (result.bytesRead === 0) { + throw new Error("ZIP archive ended unexpectedly."); + } + offset += result.bytesRead; + } + return buffer; +} + +function safeEntryPath(rawName) { + if (!rawName || rawName.includes("\0")) { + throw new Error("ZIP archive contains an invalid file name."); + } + const slashed = rawName.replaceAll("\\", "/").replace(/^\.\/+/, ""); + const normalized = posix.normalize(slashed); + if ( + normalized === "." || + normalized === ".." || + normalized.startsWith("../") || + normalized.startsWith("/") || + /^[A-Za-z]:\//.test(normalized) + ) { + throw new Error(`ZIP archive contains an unsafe path: ${rawName}`); + } + if (normalized.length > 2_048) { + throw new Error("ZIP archive contains an excessively long file name."); + } + return normalized; +} + +function dosDateTime(date, time) { + if (!date) return null; + const year = 1980 + ((date >> 9) & 0x7f); + const month = (date >> 5) & 0x0f; + const day = date & 0x1f; + const hour = (time >> 11) & 0x1f; + const minute = (time >> 5) & 0x3f; + const second = (time & 0x1f) * 2; + const value = new Date(Date.UTC(year, Math.max(0, month - 1), day, hour, minute, second)); + return Number.isNaN(value.getTime()) ? null : value.toISOString(); +} + +function decodeName(buffer, utf8) { + // GitHub-generated artifacts use UTF-8. For legacy archives, decoding as + // latin1 preserves a stable byte-for-byte lookup instead of replacing bytes. + return buffer.toString(utf8 ? "utf8" : "latin1"); +} + +export async function readZipIndex(zipPath) { + const archive = await stat(zipPath); + if (!archive.isFile() || archive.size < 22) { + throw new Error("Artifact is not a valid ZIP archive."); + } + + const handle = await open(zipPath, "r"); + try { + const tailLength = Math.min(archive.size, 65_557); + const tailOffset = archive.size - tailLength; + const tail = await readExactly(handle, tailLength, tailOffset); + let eocdOffset = -1; + for (let index = tail.length - 22; index >= 0; index--) { + if (tail.readUInt32LE(index) === EOCD_SIGNATURE) { + if (index + 22 <= tail.length) { + const commentLength = tail.readUInt16LE(index + 20); + if (index + 22 + commentLength === tail.length) { + eocdOffset = index; + break; + } + } + } + } + if (eocdOffset < 0) { + throw new Error("ZIP end-of-directory record was not found."); + } + + const disk = tail.readUInt16LE(eocdOffset + 4); + const centralDisk = tail.readUInt16LE(eocdOffset + 6); + const entriesOnDisk = tail.readUInt16LE(eocdOffset + 8); + const entryCount = tail.readUInt16LE(eocdOffset + 10); + const centralSize = tail.readUInt32LE(eocdOffset + 12); + const centralOffset = tail.readUInt32LE(eocdOffset + 16); + if (disk !== 0 || centralDisk !== 0 || entriesOnDisk !== entryCount) { + throw new Error("Multi-disk ZIP artifacts are not supported."); + } + if ( + entryCount === ZIP64_U16 || + centralSize === ZIP64_U32 || + centralOffset === ZIP64_U32 + ) { + throw new Error("ZIP64 artifacts larger than 4 GiB are not supported."); + } + if (entryCount > MAX_ZIP_ENTRIES) { + throw new Error(`ZIP contains too many entries (${entryCount.toLocaleString()}).`); + } + if (centralSize > MAX_CENTRAL_DIRECTORY_BYTES) { + throw new Error("ZIP central directory exceeds the 64 MiB safety limit."); + } + if (centralOffset + centralSize > archive.size) { + throw new Error("ZIP central directory points outside the archive."); + } + + const central = await readExactly(handle, centralSize, centralOffset); + const entries = []; + let cursor = 0; + while (cursor < central.length) { + if (central.length - cursor < 46 || central.readUInt32LE(cursor) !== CENTRAL_SIGNATURE) { + throw new Error("ZIP central directory is malformed."); + } + const flags = central.readUInt16LE(cursor + 8); + const method = central.readUInt16LE(cursor + 10); + const modifiedTime = central.readUInt16LE(cursor + 12); + const modifiedDate = central.readUInt16LE(cursor + 14); + const expectedCrc32 = central.readUInt32LE(cursor + 16); + const compressedSize = central.readUInt32LE(cursor + 20); + const uncompressedSize = central.readUInt32LE(cursor + 24); + const nameLength = central.readUInt16LE(cursor + 28); + const extraLength = central.readUInt16LE(cursor + 30); + const commentLength = central.readUInt16LE(cursor + 32); + const diskStart = central.readUInt16LE(cursor + 34); + const externalAttributes = central.readUInt32LE(cursor + 38); + const localHeaderOffset = central.readUInt32LE(cursor + 42); + const recordLength = 46 + nameLength + extraLength + commentLength; + if (cursor + recordLength > central.length) { + throw new Error("ZIP central directory entry is truncated."); + } + if ( + compressedSize === ZIP64_U32 || + uncompressedSize === ZIP64_U32 || + localHeaderOffset === ZIP64_U32 || + diskStart === ZIP64_U16 + ) { + throw new Error("ZIP64 entries are not supported."); + } + + const rawName = decodeName( + central.subarray(cursor + 46, cursor + 46 + nameLength), + Boolean(flags & 0x0800), + ); + const name = safeEntryPath(rawName); + const unixMode = externalAttributes >>> 16; + const directory = name.endsWith("/"); + const symlink = (unixMode & 0o170000) === 0o120000; + const encrypted = Boolean(flags & 0x0001); + entries.push({ + name, + directory, + symlink, + encrypted, + supported: !directory && !symlink && !encrypted && (method === 0 || method === 8), + method, + flags, + crc32: expectedCrc32, + compressedSize, + uncompressedSize, + localHeaderOffset, + modifiedAt: dosDateTime(modifiedDate, modifiedTime), + }); + cursor += recordLength; + } + if (entries.length !== entryCount) { + throw new Error( + `ZIP entry count mismatch. Expected ${entryCount}, found ${entries.length}.`, + ); + } + return { + path: zipPath, + size: archive.size, + entries, + totalUncompressedBytes: entries.reduce( + (total, entry) => total + (entry.directory ? 0 : entry.uncompressedSize), + 0, + ), + }; + } finally { + await handle.close(); + } +} + +async function entryDataRange(zipPath, entry) { + const handle = await open(zipPath, "r"); + try { + const header = await readExactly(handle, 30, entry.localHeaderOffset); + if (header.readUInt32LE(0) !== LOCAL_SIGNATURE) { + throw new Error(`ZIP local header is missing for ${entry.name}.`); + } + const nameLength = header.readUInt16LE(26); + const extraLength = header.readUInt16LE(28); + const start = entry.localHeaderOffset + 30 + nameLength + extraLength; + const endExclusive = start + entry.compressedSize; + const archive = await stat(zipPath); + if (endExclusive > archive.size) { + throw new Error(`ZIP entry data points outside the archive: ${entry.name}`); + } + return { start, endExclusive }; + } finally { + await handle.close(); + } +} + +export function findZipEntry(index, requestedName) { + const normalized = safeEntryPath(String(requestedName ?? "")); + return index.entries.find((entry) => entry.name === normalized) ?? null; +} + +export async function readZipEntry(zipPath, entry, maxBytes) { + if (!entry?.supported) { + throw new Error(`ZIP entry cannot be previewed: ${entry?.name ?? "unknown"}`); + } + if (entry.uncompressedSize > maxBytes) { + throw new Error( + `ZIP entry is too large for an inline preview (${entry.uncompressedSize.toLocaleString()} bytes).`, + ); + } + const { start } = await entryDataRange(zipPath, entry); + const handle = await open(zipPath, "r"); + let compressed; + try { + compressed = await readExactly(handle, entry.compressedSize, start); + } finally { + await handle.close(); + } + let output; + if (entry.method === 0) { + output = compressed; + } else { + const cap = entry.uncompressedSize; + output = await new Promise((resolve, reject) => { + const inflate = createInflateRaw(); + const chunks = []; + let total = 0; + inflate.on("data", (chunk) => { + total += chunk.length; + if (total > cap) { + inflate.destroy(new Error(`ZIP entry decompressed past declared size: ${entry.name}`)); + return; + } + chunks.push(chunk); + }); + inflate.once("end", () => resolve(Buffer.concat(chunks))); + inflate.once("error", reject); + inflate.end(compressed); + }); + } + if (output.length !== entry.uncompressedSize) { + throw new Error(`ZIP entry size check failed: ${entry.name}`); + } + if (crc32(output) !== entry.crc32) { + throw new Error(`ZIP entry checksum failed: ${entry.name}`); + } + return output; +} + +function createVerifyTransform(entry) { + const cap = Math.min(entry.uncompressedSize, MAX_STREAMED_ENTRY_BYTES); + let total = 0; + let crcValue = 0xffffffff; + return new Transform({ + transform(chunk, _encoding, callback) { + total += chunk.length; + if (total > cap) { + callback(new Error(`ZIP entry decompressed past declared size: ${entry.name}`)); + return; + } + for (const byte of chunk) { + crcValue = crcTable[(crcValue ^ byte) & 0xff] ^ (crcValue >>> 8); + } + callback(null, chunk); + }, + flush(callback) { + if (total !== entry.uncompressedSize) { + callback(new Error(`ZIP entry size check failed: ${entry.name}`)); + return; + } + const actual = (crcValue ^ 0xffffffff) >>> 0; + if (actual !== entry.crc32) { + callback(new Error(`ZIP entry checksum failed: ${entry.name}`)); + return; + } + callback(); + }, + }); +} + +export async function streamZipEntry(zipPath, entry, writable, transform = null) { + if (!entry?.supported) { + throw new Error(`ZIP entry cannot be served: ${entry?.name ?? "unknown"}`); + } + if (entry.uncompressedSize > MAX_STREAMED_ENTRY_BYTES) { + throw new Error("ZIP entry exceeds the 512 MiB streaming safety limit."); + } + if (entry.compressedSize === 0) { + writable.end(); + return; + } + const { start, endExclusive } = await entryDataRange(zipPath, entry); + const source = createReadStream(zipPath, { start, end: endExclusive - 1 }); + const streams = [source]; + if (entry.method !== 0) streams.push(createInflateRaw()); + streams.push(createVerifyTransform(entry)); + if (transform) streams.push(transform); + streams.push(writable); + await pipeline(...streams); +} + +export async function readEntryPrefix(zipPath, entry, maxBytes) { + if (!entry?.supported) return null; + if (entry.uncompressedSize === 0) return Buffer.alloc(0); + const { start } = await entryDataRange(zipPath, entry); + if (entry.method === 0) { + const readBytes = Math.min(maxBytes, entry.compressedSize); + const handle = await open(zipPath, "r"); + try { + return await readExactly(handle, readBytes, start); + } finally { + await handle.close(); + } + } + const handle = await open(zipPath, "r"); + let compressed; + try { + compressed = await readExactly(handle, entry.compressedSize, start); + } finally { + await handle.close(); + } + return new Promise((resolve, reject) => { + const inflate = createInflateRaw(); + const chunks = []; + let total = 0; + let done = false; + inflate.on("data", (chunk) => { + if (done) return; + const remaining = maxBytes - total; + if (remaining <= 0) { + done = true; + inflate.destroy(); + resolve(Buffer.concat(chunks)); + return; + } + const slice = remaining < chunk.length ? chunk.subarray(0, remaining) : chunk; + chunks.push(slice); + total += slice.length; + if (total >= maxBytes) { + done = true; + inflate.destroy(); + resolve(Buffer.concat(chunks)); + } + }); + inflate.once("end", () => { + if (!done) { + done = true; + resolve(Buffer.concat(chunks)); + } + }); + inflate.once("error", (err) => { + if (!done) { + done = true; + reject(err); + } + }); + inflate.end(compressed); + }); +}
+
+ + + Artifact Explorer + + + + +
+ +
+
+