mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 12:42:49 +00:00
Merge remote-tracking branch 'origin/main' into fix/kubernetes-controller-expansion
This commit is contained in:
+3
-1
@@ -16,8 +16,10 @@ paths = [
|
||||
'''_test\.go$''',
|
||||
'''_test\.ts$''',
|
||||
'''tests/integration/''',
|
||||
# Generated receipt contains required SHA-256 content digests
|
||||
# Generated receipts contain required SHA-256 content digests. The single
|
||||
# shared receipt is retired but remains in history.
|
||||
'''^frontend-modern/browser-verification\.json$''',
|
||||
'''^frontend-modern/browser-verification/[0-9a-f]{16}\.json$''',
|
||||
# tmp/ is gitignored but shows up in --no-git scans
|
||||
'''^tmp/''',
|
||||
]
|
||||
|
||||
+3
-2
@@ -68,8 +68,9 @@ staged_files_match() {
|
||||
}
|
||||
|
||||
# Normalize staged source before any guard hashes or audits it. The browser
|
||||
# verification receipt binds to exact frontend bytes, so formatting after its
|
||||
# guard can produce a commit that passed locally but correctly fails in CI.
|
||||
# verification receipt binds to exact frontend bytes and is named by their
|
||||
# fingerprint, so formatting after its guard can produce a commit that passed
|
||||
# locally but correctly fails in CI.
|
||||
python3 scripts/release_control/format_staged_go.py
|
||||
python3 scripts/release_control/format_staged_frontend.py
|
||||
|
||||
|
||||
@@ -231,10 +231,10 @@ misaligned, off-screen, or otherwise unusable fails this gate. Known browser
|
||||
defects in the changed journey remain part of the current task and block the
|
||||
commit unless the user explicitly narrows the requested scope.
|
||||
|
||||
The durable proof boundary is
|
||||
`frontend-modern/browser-verification.json`. Every commit that changes
|
||||
The durable proof boundary is one receipt file per commit under
|
||||
`frontend-modern/browser-verification/`. Every commit that changes
|
||||
user-visible source under `frontend-modern/src/` or the frontend HTML entry
|
||||
must update that receipt with:
|
||||
must add its own receipt there with:
|
||||
|
||||
1. `base_sha`, equal to the parent revision whose current build was exercised
|
||||
2. the exact changed user-visible frontend source paths and SHA-256 digests of
|
||||
@@ -245,13 +245,55 @@ must update that receipt with:
|
||||
6. the interactions exercised
|
||||
7. a UTC verification timestamp and `result: "passed"`
|
||||
|
||||
The receipt's file name is fixed by what it verified, not chosen: the first 16
|
||||
hexadecimal characters of the SHA-256 of its `content_sha256` map, serialized
|
||||
as compact JSON with sorted keys, followed by `.json`. A commit records exactly
|
||||
one receipt and never edits another commit's.
|
||||
|
||||
Receipts are separate files because a shared one made every pair of open
|
||||
frontend pull requests conflict. Until 2026-10-03 each frontend commit rewrote
|
||||
`frontend-modern/browser-verification.json`, so the first pull request to merge
|
||||
left every other one conflicting, and each conflict cost a rebuild on the new
|
||||
`main` and a fresh browser pass. Files that are only ever added cannot collide.
|
||||
The shared file is retired and deleted. The guard refuses a staged tree or a
|
||||
range tip that carries it, so resolving a merge by keeping the file cannot
|
||||
bring it back. Range mode still accepts it as evidence from a commit that
|
||||
modified it, because that commit was verified while its own parent still
|
||||
carried the file. A pull request that still rewrites it is repaired by merging
|
||||
`main` and removing the file with `git rm`, not by a rebuild.
|
||||
|
||||
`scripts/release_control/browser_verification_guard.py` enforces the receipt
|
||||
against the staged index locally and against the final integration range in
|
||||
canonical governance CI. Canonical contract completion remains checked per commit. A stale receipt, later source edit, incomplete path coverage,
|
||||
missing responsive viewport, or omitted state/interaction evidence is a hard failure. Agents may
|
||||
print a non-passing receipt skeleton with
|
||||
`python3 scripts/release_control/browser_verification_guard.py --print-template`
|
||||
after staging the intended frontend paths.
|
||||
missing responsive viewport, or omitted state/interaction evidence is a hard failure. After
|
||||
staging the intended frontend paths, agents may write a non-passing receipt
|
||||
skeleton to the path the staged content requires with
|
||||
`python3 scripts/release_control/browser_verification_guard.py --write-template`,
|
||||
or print it with `--print-template`, which also names that path. Editing a
|
||||
staged frontend file afterwards changes the fingerprint, so the receipt must be
|
||||
re-verified and moved to the new name.
|
||||
|
||||
Concluding a merge stages everything the other parent brought. The
|
||||
staged-index guard does not count content a merged parent already carries as
|
||||
the commit's own change, so merging current `main` into a branch needs no
|
||||
receipt for the frontend work `main` brought and cannot be asked to vouch for
|
||||
it. A merge commit may not record a receipt. Frontend content that differs from
|
||||
every parent, a conflict resolution or two edits git combined, can be
|
||||
committed, but it has had no browser pass: verify it and record its receipt in
|
||||
a follow-up commit, which range mode requires. Because receipts no longer
|
||||
conflict, two open pull requests that edit different parts of one frontend
|
||||
file can both merge; range mode evaluates the merged result on the pull
|
||||
request when its check runs and again on the push to `main`, where combined
|
||||
content that no receipt verified fails.
|
||||
|
||||
A receipt is read from the commit that recorded it, so once that commit is on
|
||||
`main` its file is dead weight.
|
||||
`python3 scripts/release_control/browser_verification_guard.py --prune`
|
||||
deletes every receipt already on `origin/main`. A change that records a
|
||||
receipt must not delete one: git reads a deleted receipt beside a newly added
|
||||
one as a rename, and that rename conflicts with any other change that deletes
|
||||
the same receipt. Pruning therefore lands only in changes that record no
|
||||
receipt, and the guard enforces that per commit in both modes.
|
||||
|
||||
An integration range that merges reviewed work onto a main that has moved is
|
||||
validated with `--base <range-base> --commit <range-tip>`. The tip of such a
|
||||
|
||||
@@ -2814,6 +2814,33 @@ promotion and rollback boundary unchanged.
|
||||
`brace-expansion` floors to 1.1.21 and 5.0.12 and the reviewed `dompurify`
|
||||
floor to 3.4.16, so a later downgrade is rejected.
|
||||
|
||||
### braces removed from the frontend dependency graph (3 Oct 2026)
|
||||
|
||||
`GHSA-vfj7-8cjw-p6xm` affects every `braces` release (up to 3.0.3) and has no
|
||||
patched version, so the required frontend dependency audit failed with seven
|
||||
high findings on every pull request that changes the dependency graph. All of
|
||||
them were dev-only: Tailwind CSS 3 reached `braces` through `chokidar`,
|
||||
`fast-glob` and `micromatch`, and `jscpd` 4 through `@jscpd/finder` and
|
||||
`fast-glob`. Every `fast-glob` and `micromatch` release depends on `braces`, so
|
||||
an override cannot close it. `frontend-modern` therefore moves to Tailwind CSS
|
||||
4.3.3 through `@tailwindcss/vite` (dropping `postcss.config.js`,
|
||||
`tailwind.config.js`, `autoprefixer` and the direct `postcss` dependency) and to
|
||||
`jscpd` 5.4.0, which has no runtime dependencies; `npm audit` over the full graph
|
||||
then reports no findings. `npm audit --omit=dev` was already clean, so no shipped
|
||||
bundle code changes for security reasons, and the installer, artifact, signing,
|
||||
promotion and rollback boundary is unchanged. The stylesheet keeps the v3
|
||||
rendering: it pins the v3 palette, font stacks and line heights, keeps the v3
|
||||
cascade order, and restores the v3 `space-*`, `divide-*`, hover, border, ring,
|
||||
placeholder and button-cursor behaviour. Tailwind CSS 4 is tested on Safari
|
||||
16.4, Chrome 111 and Firefox 128 and later. The production stylesheet itself
|
||||
needs cascade layers (Safari 15.4, Chrome 99, Firefox 97), because the build
|
||||
lowers media range syntax and nesting and folds palette opacity modifiers to
|
||||
static colors; that is below the container-query floor (Safari 16, Chrome 105,
|
||||
Firefox 110) the platform tables already required.
|
||||
`frontend-modern/src/security/__tests__/dependencySecurity.test.ts` now rejects
|
||||
any locked `braces` copy, so the advisory cannot return through a later
|
||||
dependency, and drops the `autoprefixer` floor because the package is gone.
|
||||
|
||||
### Reviewed @types/node 26.6.2 refresh
|
||||
|
||||
The 2026-09-23 `npm-minor-patch` group (Dependabot #2189) advances the
|
||||
|
||||
@@ -6,7 +6,7 @@ This document outlines the standard UI primitives, tokens, and components that c
|
||||
|
||||
## Core Principles
|
||||
1. **Never hardcode hex values** or use static `gray` / `white` / `slate` labels for structural layout colors.
|
||||
2. Use the **Semantic Tokens**. These resolve dynamically inside `tailwind.config.js` via `index.css` CSS-variables to flawlessly support light/dark transitions without needing a literal `dark:` prefix in the layout classes.
|
||||
2. Use the **Semantic Tokens**. These are declared as Tailwind `@theme` colors in `index.css` and resolve through its CSS variables to flawlessly support light/dark transitions without needing a literal `dark:` prefix in the layout classes.
|
||||
|
||||
## Enforcement
|
||||
- `npm run lint` is a hard gate for design-system regressions in `src/components` and `src/pages`.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Browser verification receipts
|
||||
|
||||
Each commit that changes user-visible frontend source adds one receipt here.
|
||||
The file name is the fingerprint of the content the receipt verified, so no
|
||||
two changes write the same file and open frontend pull requests cannot
|
||||
conflict on their receipts.
|
||||
|
||||
Create the skeleton for the staged change, then fill it in after the browser
|
||||
pass:
|
||||
|
||||
```bash
|
||||
python3 scripts/release_control/browser_verification_guard.py --write-template
|
||||
```
|
||||
|
||||
Do not edit, rename or delete another change's receipt in a commit that adds
|
||||
one. A receipt is read from the commit that recorded it, so receipts whose
|
||||
commits are already on `main` are dead weight; remove them in a change that
|
||||
records none:
|
||||
|
||||
```bash
|
||||
python3 scripts/release_control/browser_verification_guard.py --prune
|
||||
```
|
||||
|
||||
The rules live in the Frontend Browser Verification Gate section of
|
||||
`docs/release-control/v6/internal/CANONICAL_DEVELOPMENT_PROTOCOL.md`.
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "beb6c3f6a6f3fad74a1b9f76745bacdadcab7834",
|
||||
"verified_at": "2026-10-03T18:30:37Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/components/AI/Chat/index.tsx",
|
||||
"frontend-modern/src/components/Settings/AIModelSelectionSection.tsx",
|
||||
"frontend-modern/src/components/Settings/DataHandlingPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/RolesPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/SettingsPageShell.tsx",
|
||||
"frontend-modern/src/components/Workloads/StackedDiskBar.tsx",
|
||||
"frontend-modern/src/features/proxmox/proxmoxBackupsTableShared.tsx",
|
||||
"frontend-modern/src/pages/Actions.tsx"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/components/AI/Chat/index.tsx": "27d3bd9fbdb86c7f6048b29a65d6bac41308f4650ebb7da1b7072a0fec5522e5",
|
||||
"frontend-modern/src/components/Settings/AIModelSelectionSection.tsx": "c94ce3d2b8c4c47bf8b07699bdbd98e9f009feb77bebc7bb81dc24f784248f87",
|
||||
"frontend-modern/src/components/Settings/DataHandlingPanel.tsx": "c0eff8e2c786a3baa45c22d0c7118ed55ad1d52b33f8e06692b7c3222431db96",
|
||||
"frontend-modern/src/components/Settings/RolesPanel.tsx": "f8b1cb3a8e47e2815c3978ef07239699cb60989a8ecacd93024178896f6211de",
|
||||
"frontend-modern/src/components/Settings/SettingsPageShell.tsx": "18a195c4c71a939ae17bba3136854f6419c5cbc6c8acb6e62d003d016b0dec74",
|
||||
"frontend-modern/src/components/Workloads/StackedDiskBar.tsx": "be53a9d0c349a54516b29de58bf8e6fb28af6ea9f8028965591aa3cb960a851d",
|
||||
"frontend-modern/src/features/proxmox/proxmoxBackupsTableShared.tsx": "3c4883dcd79af1ecea3c7e967de8a05797c5ffd360e873dbb8550f0632708438",
|
||||
"frontend-modern/src/pages/Actions.tsx": "9ce58a091378c5edc1469f3ad496194668d6d743a217aa43bed6d7ab9c508dcb"
|
||||
},
|
||||
"routes": [
|
||||
"/settings/security-data-handling",
|
||||
"/settings/security-roles",
|
||||
"/settings/system-ai",
|
||||
"/proxmox",
|
||||
"/proxmox/backups/coverage",
|
||||
"/actions",
|
||||
"/alerts/thresholds",
|
||||
"/patrol"
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"width": 390,
|
||||
"height": 844
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"light and dark color schemes on a private mock stack with a dev Pro licence; each restored class toggled off and on in one page load for before/after",
|
||||
"Resource Privacy at 390px scrolled under the sticky Settings header: body text no longer shows through (bg-surface/95)",
|
||||
"Resource Privacy Internal meter at 59% renders a slate-400 fill instead of an empty track",
|
||||
"Proxmox backups coverage filter chips: Unknown shows a grey status dot like its siblings",
|
||||
"Assistant session picker in dark mode: section header divider is border-border/60 instead of a bright gray-200 line",
|
||||
"Assistant model-route chips and Patrol model readiness card markup in dark mode: border-current borders replace bright gray-200",
|
||||
"Roles table at 390px with rbac capability and roles intercepted: custom-role rows 53px like built-in rows with 44px Edit and Delete targets; 28px buttons on desktop",
|
||||
"Alerts thresholds mobile card: Cancel threshold edits renders 44x44 like Save, Edit and Delay",
|
||||
"Patrol decision detail at 390px: Copy ID 44px tall in a row already 44px from Open resource"
|
||||
],
|
||||
"interactions": [
|
||||
"scrolled the app scroll shell under the Settings phone header",
|
||||
"hovered an Actions row and a node disk segment",
|
||||
"opened the Assistant, sent a message and opened the sessions picker",
|
||||
"opened a Patrol decision and a threshold edit on the phone layout"
|
||||
]
|
||||
}
|
||||
Generated
+726
-1607
File diff suppressed because it is too large
Load Diff
@@ -72,22 +72,21 @@
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@solidjs/testing-library": "^0.8.5",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "^4.3.3",
|
||||
"@testing-library/jest-dom": "^6.5.0",
|
||||
"@types/node": "^26.6.2",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"@typescript-eslint/eslint-plugin": "^8.70.0",
|
||||
"@typescript-eslint/parser": "^8.70.0",
|
||||
"@vitest/coverage-v8": "^4.1.11",
|
||||
"autoprefixer": "^10.6.1",
|
||||
"eslint": "^9.20.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"eslint-plugin-solid": "^0.18.0",
|
||||
"globals": "^17.12.0",
|
||||
"jscpd": "^4.0.8",
|
||||
"jscpd": "^5.4.0",
|
||||
"jsdom": "^24.1.0",
|
||||
"postcss": "^8.5.28",
|
||||
"prettier": "3.9.8",
|
||||
"tailwindcss": "^3.4.18",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"typescript": "^5.3.0",
|
||||
"typescript-eslint": "^8.70.0",
|
||||
"vite": "^6.4.3",
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -265,7 +265,7 @@
|
||||
"patterns": [
|
||||
"<svg",
|
||||
"const icons = {",
|
||||
"flex-shrink-0 text-muted hover:text-base-content hover:bg-surface rounded-md p-1.5 transition-all duration-200"
|
||||
"shrink-0 text-muted hover:text-base-content hover:bg-surface rounded-md p-1.5 transition-all duration-200"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -507,7 +507,7 @@
|
||||
"forbiddenPatterns": [
|
||||
{
|
||||
"path": "src/components/Settings/AIProviderConfigurationSection.tsx",
|
||||
"patterns": ["rounded border border-red-200", "bg-red-50 dark:bg-red-900 px-2 py-1.5"]
|
||||
"patterns": ["rounded-sm border border-red-200", "bg-red-50 dark:bg-red-900/25 px-2 py-1.5"]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/ConnectionEditor/AddressProbeStep.tsx",
|
||||
@@ -549,7 +549,7 @@
|
||||
"path": "src/components/Settings/DiscoverySettingsForm.tsx",
|
||||
"patterns": [
|
||||
"rounded-md border border-amber-200 bg-amber-50/80",
|
||||
"text-amber-900 dark:text-amber-100"
|
||||
"text-amber-900/25 dark:text-amber-100"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -573,8 +573,8 @@
|
||||
"path": "src/components/ErrorBoundary.tsx",
|
||||
"patterns": [
|
||||
"<svg",
|
||||
"bg-red-50 dark:bg-red-900 border border-red-200",
|
||||
"p-4 bg-red-50 dark:bg-red-900 border border-red-200"
|
||||
"bg-red-50 dark:bg-red-900/25 border border-red-200",
|
||||
"p-4 bg-red-50 dark:bg-red-900/25 border border-red-200"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -602,8 +602,8 @@
|
||||
"path": "src/components/UpdateConfirmationModal.tsx",
|
||||
"patterns": [
|
||||
"<svg",
|
||||
"bg-blue-50 dark:bg-blue-900 border border-blue-200",
|
||||
"bg-yellow-50 dark:bg-yellow-900 border border-yellow-200",
|
||||
"bg-blue-50 dark:bg-blue-900/25 border border-blue-200",
|
||||
"bg-yellow-50 dark:bg-yellow-900/25 border border-yellow-200",
|
||||
"rounded-md p-4 border",
|
||||
"px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
||||
]
|
||||
@@ -612,10 +612,10 @@
|
||||
"path": "src/components/UpdateProgressModal.tsx",
|
||||
"patterns": [
|
||||
"<svg",
|
||||
"bg-red-50 dark:bg-red-900 border border-red-200",
|
||||
"bg-blue-50 dark:bg-blue-900 border border-blue-200",
|
||||
"bg-yellow-50 dark:bg-yellow-900 border border-yellow-200",
|
||||
"mt-2 px-3 py-1.5 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded"
|
||||
"bg-red-50 dark:bg-red-900/25 border border-red-200",
|
||||
"bg-blue-50 dark:bg-blue-900/25 border border-blue-200",
|
||||
"bg-yellow-50 dark:bg-yellow-900/25 border border-yellow-200",
|
||||
"mt-2 px-3 py-1.5 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-sm"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -644,14 +644,14 @@
|
||||
"path": "src/features/platformPage/PlatformOutdatedAgentNotice.tsx",
|
||||
"patterns": [
|
||||
"rounded-lg border border-amber-300 bg-amber-50",
|
||||
"text-amber-900 underline-offset-2"
|
||||
"text-amber-900/25 underline-offset-2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/features/platformPage/PlatformOutdatedSensorSetupNotice.tsx",
|
||||
"patterns": [
|
||||
"rounded-lg border border-amber-300 bg-amber-50",
|
||||
"text-amber-900 underline-offset-2"
|
||||
"text-amber-900/25 underline-offset-2"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -676,8 +676,8 @@
|
||||
"patterns": [
|
||||
"<svg",
|
||||
"<button",
|
||||
"bg-blue-50 dark:bg-blue-900 border-b border-blue-200 dark:border-blue-800",
|
||||
"p-1 hover:bg-blue-100 dark:hover:bg-blue-800 rounded text-blue-600"
|
||||
"bg-blue-50 dark:bg-blue-900/25 border-b border-blue-200 dark:border-blue-800",
|
||||
"p-1 hover:bg-blue-100 dark:hover:bg-blue-800 rounded-sm text-blue-600"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -705,9 +705,9 @@
|
||||
"<button",
|
||||
"toneClasses",
|
||||
"buttonClasses",
|
||||
"bg-amber-50 dark:bg-amber-900 border-b border-amber-200",
|
||||
"bg-red-50 dark:bg-red-900 border-b border-red-200",
|
||||
"p-1 rounded transition-colors opacity-70 hover:opacity-100"
|
||||
"bg-amber-50 dark:bg-amber-900/25 border-b border-amber-200",
|
||||
"bg-red-50 dark:bg-red-900/25 border-b border-red-200",
|
||||
"p-1 rounded-sm transition-colors opacity-70 hover:opacity-100"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -1195,20 +1195,20 @@
|
||||
{
|
||||
"path": "src/components/Settings/OrganizationAccessMembersSection.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900/25 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/OrganizationIncomingSharesSection.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-emerald-700 hover:bg-emerald-50 dark:text-emerald-300 dark:hover:bg-emerald-900 disabled:cursor-not-allowed disabled:opacity-60",
|
||||
"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-emerald-700 hover:bg-emerald-50 dark:text-emerald-300 dark:hover:bg-emerald-900/25 disabled:cursor-not-allowed disabled:opacity-60",
|
||||
"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900/25 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/OrganizationOutgoingSharesSection.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900/25 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1255,7 +1255,7 @@
|
||||
{
|
||||
"path": "src/components/Settings/SecurityAuthPanel.tsx",
|
||||
"patterns": [
|
||||
"w-full sm:w-auto px-3 py-2 text-xs font-medium rounded-md border border-amber-300 text-amber-800 bg-amber-100 hover:bg-amber-200 transition-colors dark:border-amber-700 dark:text-amber-200 dark:bg-amber-900 dark:hover:bg-amber-800",
|
||||
"w-full sm:w-auto px-3 py-2 text-xs font-medium rounded-md border border-amber-300 text-amber-800 bg-amber-100 hover:bg-amber-200 transition-colors dark:border-amber-700 dark:text-amber-200 dark:bg-amber-900/25 dark:hover:bg-amber-800",
|
||||
"w-full sm:w-auto min-h-10 sm:min-h-10 px-4 py-2.5 text-sm font-medium bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors",
|
||||
"w-full sm:w-auto min-h-10 sm:min-h-10 px-4 py-2.5 text-sm font-medium border border-border text-base-content rounded-md hover:bg-surface-hover transition-colors"
|
||||
]
|
||||
@@ -1270,7 +1270,7 @@
|
||||
"px-4 py-2 text-sm font-medium text-base-content border border-border rounded-md hover:bg-surface-hover",
|
||||
"px-4 py-2 text-sm font-medium bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50",
|
||||
"px-4 py-2 text-sm font-medium bg-red-600 text-white rounded-md hover:bg-red-700",
|
||||
"px-2 py-1 text-xs font-medium text-muted bg-surface-hover rounded hover:bg-surface-hover flex items-center gap-1"
|
||||
"px-2 py-1 text-xs font-medium text-muted bg-surface-hover rounded-sm hover:bg-surface-hover flex items-center gap-1"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1283,17 +1283,17 @@
|
||||
"path": "src/components/Settings/InfrastructureInstallerSection.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center justify-center rounded-md bg-emerald-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-emerald-700",
|
||||
"inline-flex items-center justify-center rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900 transition-colors hover:bg-emerald-100",
|
||||
"inline-flex items-center justify-center rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900 hover:bg-emerald-100",
|
||||
"inline-flex items-center justify-center rounded-md px-3 py-2 text-sm font-medium text-emerald-900 hover:bg-emerald-100"
|
||||
"inline-flex items-center justify-center rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900/25 transition-colors hover:bg-emerald-100",
|
||||
"inline-flex items-center justify-center rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900/25 hover:bg-emerald-100",
|
||||
"inline-flex items-center justify-center rounded-md px-3 py-2 text-sm font-medium text-emerald-900/25 hover:bg-emerald-100"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/ErrorBoundary.tsx",
|
||||
"patterns": [
|
||||
"px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700",
|
||||
"px-4 py-2 bg-slate-600 text-white rounded hover:bg-slate-700",
|
||||
"text-xs px-2 py-1 bg-red-600 text-white rounded hover:bg-red-700"
|
||||
"px-4 py-2 bg-blue-600 text-white rounded-sm hover:bg-blue-700",
|
||||
"px-4 py-2 bg-slate-600 text-white rounded-sm hover:bg-slate-700",
|
||||
"text-xs px-2 py-1 bg-red-600 text-white rounded-sm hover:bg-red-700"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1306,7 +1306,7 @@
|
||||
{
|
||||
"path": "src/components/UpdateProgressModal.tsx",
|
||||
"patterns": [
|
||||
"mt-2 px-3 py-1.5 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded",
|
||||
"mt-2 px-3 py-1.5 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-sm",
|
||||
"px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
||||
]
|
||||
}
|
||||
@@ -1430,35 +1430,35 @@
|
||||
{
|
||||
"path": "src/components/AI/Chat/index.tsx",
|
||||
"patterns": [
|
||||
"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-surface disabled:hover:text-muted",
|
||||
"flex-shrink-0 p-2 hover:text-base-content rounded-md hover:bg-surface-hover transition-colors",
|
||||
"inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 disabled:cursor-wait disabled:opacity-70 dark:text-blue-200 dark:hover:bg-blue-900/60",
|
||||
"rounded p-1 text-muted opacity-0 transition-opacity hover:bg-blue-100 hover:text-blue-600 focus:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100 dark:hover:bg-blue-900 dark:hover:text-blue-300",
|
||||
"order-2 flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-md hover:text-base-content hover:bg-surface-hover transition-colors sm:order-none",
|
||||
"flex h-7 w-7 items-center justify-center rounded-md border border-amber-200 bg-surface text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900 dark:border-amber-800 dark:bg-amber-950/60 dark:text-amber-200 dark:hover:bg-amber-900",
|
||||
"flex h-7 w-7 items-center justify-center rounded-md text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900 dark:text-amber-200 dark:hover:bg-amber-900",
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 dark:text-blue-200 dark:hover:bg-blue-900/60",
|
||||
"flex h-9 w-9 items-center justify-center rounded-md bg-blue-600 text-white shadow-sm transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-45",
|
||||
"flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
"flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-surface disabled:hover:text-muted",
|
||||
"shrink-0 p-2 hover:text-base-content rounded-md hover:bg-surface-hover transition-colors",
|
||||
"inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 disabled:cursor-wait disabled:opacity-70 dark:text-blue-200 dark:hover:bg-blue-900/60",
|
||||
"rounded-sm p-1 text-muted opacity-0 transition-opacity hover:bg-blue-100 hover:text-blue-600 focus:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100 dark:hover:bg-blue-900/25 dark:hover:text-blue-300",
|
||||
"order-2 flex h-9 w-9 shrink-0 items-center justify-center rounded-md hover:text-base-content hover:bg-surface-hover transition-colors sm:order-0",
|
||||
"flex h-7 w-7 items-center justify-center rounded-md border border-amber-200 bg-surface text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900/25 dark:border-amber-800 dark:bg-amber-950/60 dark:text-amber-200 dark:hover:bg-amber-900/25",
|
||||
"flex h-7 w-7 items-center justify-center rounded-md text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900/25 dark:text-amber-200 dark:hover:bg-amber-900/25",
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 dark:text-blue-200 dark:hover:bg-blue-900/60",
|
||||
"flex h-9 w-9 items-center justify-center rounded-md bg-blue-600 text-white shadow-xs transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-45",
|
||||
"flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus:ring-2 focus:ring-blue-500/30"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/AI/Chat/MessageItem.tsx",
|
||||
"patterns": [
|
||||
"inline-flex h-5 w-5 items-center justify-center rounded text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 focus:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
"inline-flex h-5 w-5 items-center justify-center rounded-sm text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 focus:bg-blue-100 focus:outline-hidden focus:ring-2 focus:ring-blue-500/30"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/features/standalone/AgentsMachinesTable.tsx",
|
||||
"patterns": [
|
||||
"inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60"
|
||||
"inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500/60"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/SSOProvidersPanel.tsx",
|
||||
"patterns": [
|
||||
"p-2 text-slate-500 hover:text-blue-600 hover:bg-surface-hover rounded-md transition-colors",
|
||||
"p-2 text-slate-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900 rounded-md transition-colors",
|
||||
"p-2 text-slate-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/25 rounded-md transition-colors",
|
||||
"class=\"text-slate-400 hover:text-base-content\""
|
||||
]
|
||||
},
|
||||
@@ -1466,7 +1466,7 @@
|
||||
"path": "src/components/Settings/RolesPanel.tsx",
|
||||
"patterns": [
|
||||
"p-1.5 rounded-md text-slate-500 hover:text-blue-600 hover:bg-surface-hover dark:hover:text-blue-300",
|
||||
"p-1.5 rounded-md text-slate-500 hover:text-red-600 hover:bg-red-50 dark:hover:text-red-400 dark:hover:bg-red-900"
|
||||
"p-1.5 rounded-md text-slate-500 hover:text-red-600 hover:bg-red-50 dark:hover:text-red-400 dark:hover:bg-red-900/25"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -1498,8 +1498,8 @@
|
||||
"patterns": [
|
||||
"lucide-solid/icons/copy",
|
||||
"lucide-solid/icons/check';",
|
||||
"mt-1 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border-subtle bg-surface text-muted opacity-0 shadow-sm transition-opacity hover:text-base-content",
|
||||
"ml-auto inline-flex h-7 w-7 items-center justify-center rounded-md border border-border-subtle bg-surface text-muted opacity-0 shadow-sm transition-opacity hover:text-base-content"
|
||||
"mt-1 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border-subtle bg-surface text-muted opacity-0 shadow-xs transition-opacity hover:text-base-content",
|
||||
"ml-auto inline-flex h-7 w-7 items-center justify-center rounded-md border border-border-subtle bg-surface text-muted opacity-0 shadow-xs transition-opacity hover:text-base-content"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1507,7 +1507,7 @@
|
||||
"patterns": [
|
||||
"lucide-solid/icons/copy",
|
||||
"lucide-solid/icons/check';",
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded border border-border-subtle bg-surface text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-sm border border-border-subtle bg-surface text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus:ring-2 focus:ring-blue-500/30"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1574,7 +1574,7 @@
|
||||
{
|
||||
"path": "src/components/Settings/ProLicensePlanSection.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center gap-1 mt-3 min-h-10 sm:min-h-9 rounded-md border border-current/20 px-3 py-2 text-xs font-medium hover:bg-black/5 dark:hover:bg-white/5",
|
||||
"inline-flex items-center gap-1 mt-3 min-h-10 sm:min-h-9 rounded-md border px-3 py-2 text-xs font-medium hover:bg-black/5 dark:hover:bg-white/5",
|
||||
"inline-flex items-center gap-1 mt-4 min-h-10 sm:min-h-9 rounded-md border border-border px-3 py-2 text-xs font-medium text-base-content hover:bg-surface-hover"
|
||||
]
|
||||
}
|
||||
@@ -1657,7 +1657,7 @@
|
||||
"path": "src/components/Discovery/DiscoveryTab.tsx",
|
||||
"patterns": [
|
||||
"const CopyableCodeRow",
|
||||
"flex items-start gap-2 rounded bg-surface-alt px-2 py-1.5"
|
||||
"flex items-start gap-2 rounded-sm bg-surface-alt px-2 py-1.5"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -2022,7 +2022,7 @@
|
||||
{
|
||||
"path": "src/components/AI/FindingsPanel.tsx",
|
||||
"patterns": [
|
||||
"px-1.5 py-0.5 border text-[10px] font-medium rounded",
|
||||
"px-1.5 py-0.5 border text-[10px] font-medium rounded-sm",
|
||||
"getFindingStatusBadgeClasses",
|
||||
"getFindingSourceBadgeClasses",
|
||||
"getFindingLoopStateBadgeClasses",
|
||||
@@ -2034,51 +2034,51 @@
|
||||
{
|
||||
"path": "src/components/patrol/ApprovalBanner.tsx",
|
||||
"patterns": [
|
||||
"px-1.5 py-0.5 text-[10px] font-medium rounded",
|
||||
"px-1.5 py-0.5 text-[10px] font-medium rounded-sm",
|
||||
"firstApprovalRisk()!.badgeClass"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/patrol/ApprovalSection.tsx",
|
||||
"patterns": [
|
||||
"px-1.5 py-0.5 text-[10px] font-medium rounded",
|
||||
"px-1.5 py-0.5 text-[10px] font-medium rounded-sm",
|
||||
"approvalRisk.badgeClass",
|
||||
"fixRisk.badgeClass",
|
||||
"bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300"
|
||||
"bg-amber-100 text-amber-700 dark:bg-amber-900/25 dark:text-amber-300"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/patrol/InvestigationSection.tsx",
|
||||
"patterns": [
|
||||
"px-1.5 py-0.5 border text-[10px] font-medium rounded",
|
||||
"px-1.5 py-0.5 border text-[10px] font-medium rounded-sm",
|
||||
"getInvestigationStatusBadgeClasses",
|
||||
"getInvestigationOutcomeBadgeClasses",
|
||||
"rounded border border-border bg-surface px-1.5 py-0.5 text-[10px] font-medium text-muted",
|
||||
"rounded bg-surface px-1.5 py-0.5 text-[10px] font-medium text-muted"
|
||||
"rounded-sm border border-border bg-surface px-1.5 py-0.5 text-[10px] font-medium text-muted",
|
||||
"rounded-sm bg-surface px-1.5 py-0.5 text-[10px] font-medium text-muted"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/patrol/RunHistoryEntry.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium",
|
||||
"inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-blue-50"
|
||||
"inline-flex items-center gap-1 px-1.5 py-0.5 rounded-sm bg-blue-50"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/patrol/RunToolCallTrace.tsx",
|
||||
"patterns": [
|
||||
"px-1.5 py-0.5 rounded text-[10px] font-medium",
|
||||
"px-1.5 py-0.5 rounded-sm text-[10px] font-medium",
|
||||
"getToolCallResultBadgeClass"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/features/proxmox/proxmoxBackupsTableShared.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center rounded-sm px-1.5 py-0.5 text-[10px] font-semibold",
|
||||
"inline-flex items-center rounded-xs px-1.5 py-0.5 text-[10px] font-semibold",
|
||||
"presentation().badgeClassName",
|
||||
"rounded-sm bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold",
|
||||
"rounded-sm bg-amber-100 px-1.5 py-0.5 text-[10px] font-semibold",
|
||||
"rounded-sm bg-emerald-100 px-1.5 py-0.5 text-[10px] font-semibold"
|
||||
"rounded-xs bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold",
|
||||
"rounded-xs bg-amber-100 px-1.5 py-0.5 text-[10px] font-semibold",
|
||||
"rounded-xs bg-emerald-100 px-1.5 py-0.5 text-[10px] font-semibold"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3226,7 +3226,7 @@
|
||||
"patterns": [
|
||||
"const viewButtonClass",
|
||||
"inline-flex items-center gap-1 rounded-md border border-border bg-surface p-1",
|
||||
"inline-flex min-h-8 items-center gap-1.5 rounded-sm px-3 text-xs font-medium transition-colors"
|
||||
"inline-flex min-h-8 items-center gap-1.5 rounded-xs px-3 text-xs font-medium transition-colors"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -3251,7 +3251,7 @@
|
||||
"patterns": [
|
||||
"inline-flex min-h-10 sm:min-h-10 items-center rounded-full border px-3 py-2 text-sm font-semibold transition",
|
||||
"min-h-10 sm:min-h-10 rounded-full border px-3 py-2 text-sm font-semibold transition",
|
||||
"border-blue-500 bg-blue-600 text-white shadow-sm",
|
||||
"border-blue-500 bg-blue-600 text-white shadow-xs",
|
||||
"hover:border-blue-400 hover:text-blue-600 dark:hover:border-blue-400 dark:hover:text-blue-200"
|
||||
]
|
||||
}
|
||||
@@ -3385,7 +3385,7 @@
|
||||
"scopes": ["src/features/standalone/AgentsMachinesTable.tsx"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60"
|
||||
"inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500/60"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Standalone machine action triggers compose ActionIconButton for shared icon-action size, tone, focus, title, and accessible-label behavior.",
|
||||
"allowedPaths": [],
|
||||
@@ -3408,7 +3408,7 @@
|
||||
"scopes": ["src/components/AI/Chat/MessageItem.tsx"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-5 w-5 items-center justify-center rounded text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 focus:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
"inline-flex h-5 w-5 items-center justify-center rounded-sm text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 focus:bg-blue-100 focus:outline-hidden focus:ring-2 focus:ring-blue-500/30"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. AI Chat queued message icon actions belong to ActionIconButton.",
|
||||
"allowedPaths": [],
|
||||
@@ -3431,7 +3431,7 @@
|
||||
"scopes": ["src/components/AI/Chat"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content"
|
||||
"flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. AI Chat header icon actions belong to ActionIconButton.",
|
||||
"allowedPaths": [],
|
||||
@@ -3457,7 +3457,7 @@
|
||||
"scopes": ["src/components/AI/Chat"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950"
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. AI Chat accent icon actions belong to ActionIconButton.",
|
||||
"allowedPaths": [],
|
||||
@@ -3483,7 +3483,7 @@
|
||||
"scopes": ["src/components/AI/Chat"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"flex h-9 w-9 items-center justify-center rounded-md bg-blue-600 text-white shadow-sm transition-colors hover:bg-blue-700"
|
||||
"flex h-9 w-9 items-center justify-center rounded-md bg-blue-600 text-white shadow-xs transition-colors hover:bg-blue-700"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. AI Chat primary icon actions belong to ActionIconButton.",
|
||||
"allowedPaths": [],
|
||||
@@ -3562,7 +3562,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-7 w-7",
|
||||
"border border-border-subtle bg-surface text-muted opacity-0 shadow-sm transition-opacity hover:text-base-content",
|
||||
"border border-border-subtle bg-surface text-muted opacity-0 shadow-xs transition-opacity hover:text-base-content",
|
||||
"CopyIcon class=\"h-3.5 w-3.5\""
|
||||
],
|
||||
"legacyReason": "Retired migration debt. AI Chat message copy actions belong to CopyValueButton.",
|
||||
@@ -3586,7 +3586,7 @@
|
||||
"scopes": ["src/components/AI/Chat/ToolExecutionBlock.tsx"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded border border-border-subtle bg-surface text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500/30",
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-sm border border-border-subtle bg-surface text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus:ring-2 focus:ring-blue-500/30",
|
||||
"CopyIcon class=\"h-3 w-3\""
|
||||
],
|
||||
"legacyReason": "Retired migration debt. AI Chat tool copy actions belong to CopyValueButton.",
|
||||
@@ -4027,7 +4027,7 @@
|
||||
},
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".ts", ".tsx"],
|
||||
"allPatterns": ["rounded border border-border bg-surface p-3 shadow-sm"],
|
||||
"allPatterns": ["rounded-sm border border-border bg-surface p-3 shadow-xs"],
|
||||
"legacyReason": "Retired migration debt. Repeated compact information card frames must use InfoCardFrame; local card-shell copies are not allowed.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
|
||||
@@ -5141,7 +5141,7 @@
|
||||
},
|
||||
"scopes": ["src/components/AI"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["px-1.5 py-0.5 border text-[10px] font-medium rounded"],
|
||||
"allPatterns": ["px-1.5 py-0.5 border text-[10px] font-medium rounded-sm"],
|
||||
"legacyReason": "Retired migration debt. Patrol finding row state, source, severity, investigation, confidence, and regression chips use MetadataBadge for shared shell, sizing, tone, and outline behavior.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
|
||||
@@ -5161,7 +5161,7 @@
|
||||
},
|
||||
"scopes": ["src/components/patrol"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["px-1.5 py-0.5 border text-[10px] font-medium rounded"],
|
||||
"allPatterns": ["px-1.5 py-0.5 border text-[10px] font-medium rounded-sm"],
|
||||
"legacyReason": "Retired migration debt. Patrol investigation status, outcome, durable-record, and tool chips use MetadataBadge for shared shell, sizing, tone, and outline behavior.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
|
||||
@@ -5181,7 +5181,7 @@
|
||||
},
|
||||
"scopes": ["src/components/patrol"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["px-1.5 py-0.5", "text-[10px] font-medium rounded"],
|
||||
"allPatterns": ["px-1.5 py-0.5", "text-[10px] font-medium rounded-sm"],
|
||||
"legacyReason": "Retired migration debt. Patrol approval risk, approval state, and tool-call result chips use MetadataBadge for shared shell, sizing, tone, and whitespace behavior.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
|
||||
@@ -5222,7 +5222,7 @@
|
||||
},
|
||||
"scopes": ["src/features/proxmox"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["rounded-sm px-1.5 py-0.5 text-[10px] font-semibold"],
|
||||
"allPatterns": ["rounded-xs px-1.5 py-0.5 text-[10px] font-semibold"],
|
||||
"legacyReason": "Retired migration debt. Proxmox backup source/state chips use MetadataBadge for shared shell, sizing, tone vocabulary, and whitespace behavior.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
|
||||
@@ -5456,7 +5456,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex items-center gap-1 rounded-md border border-border bg-surface p-1",
|
||||
"inline-flex min-h-8 items-center gap-1.5 rounded-sm px-3 text-xs font-medium transition-colors"
|
||||
"inline-flex min-h-8 items-center gap-1.5 rounded-xs px-3 text-xs font-medium transition-colors"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Feature table view toggles must use FilterButtonGroup; local bordered segmented-view selector forks are not allowed.",
|
||||
"allowedPaths": [],
|
||||
@@ -5478,7 +5478,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"rounded-full border px-3 py-2 text-sm font-semibold transition",
|
||||
"border-blue-500 bg-blue-600 text-white shadow-sm",
|
||||
"border-blue-500 bg-blue-600 text-white shadow-xs",
|
||||
"hover:border-blue-400 hover:text-blue-600"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Pressed/unpressed pill selectors belong to SelectablePillButton.",
|
||||
@@ -5550,7 +5550,7 @@
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-8 min-h-11 min-w-11 items-center gap-1.5 rounded border border-border bg-surface px-2 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
|
||||
"inline-flex h-8 min-h-11 min-w-11 items-center gap-1.5 rounded-sm border border-border bg-surface px-2 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Drawer header Assistant and context-copy actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -5572,7 +5572,7 @@
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-8 w-8 min-h-11 min-w-11 items-center justify-center rounded-md hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
|
||||
"inline-flex h-8 w-8 min-h-11 min-w-11 items-center justify-center rounded-md hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Drawer header close and icon actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -5642,7 +5642,7 @@
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex min-h-7 min-w-7 shrink-0 items-center justify-center rounded border border-border bg-surface px-2 text-muted transition-colors hover:bg-surface-hover hover:text-base-content",
|
||||
"inline-flex min-h-7 min-w-7 shrink-0 items-center justify-center rounded-sm border border-border bg-surface px-2 text-muted transition-colors hover:bg-surface-hover hover:text-base-content",
|
||||
"fallback={<CopyIcon class=\"h-3.5 w-3.5\" />}"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Neutral copy-value icon buttons belong to CopyValueButton.",
|
||||
@@ -5686,7 +5686,7 @@
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex min-h-7 min-w-7 shrink-0 items-center justify-center rounded text-blue-700 transition-colors hover:bg-blue-100 dark:text-blue-200 dark:hover:bg-blue-950",
|
||||
"inline-flex min-h-7 min-w-7 shrink-0 items-center justify-center rounded-sm text-blue-700 transition-colors hover:bg-blue-100 dark:text-blue-200 dark:hover:bg-blue-950/25",
|
||||
"fallback={<CopyIcon class=\"h-3.5 w-3.5\" />}"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Accent copy-value icon buttons belong to CopyValueButton.",
|
||||
@@ -5708,7 +5708,7 @@
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"flex items-start gap-2 rounded bg-surface-alt px-2 py-1.5",
|
||||
"flex items-start gap-2 rounded-sm bg-surface-alt px-2 py-1.5",
|
||||
"break-all font-mono text-xs text-base-content"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Copyable code rows belong to CopyableCodeRow so code styling and copy behavior stay canonical.",
|
||||
@@ -6012,7 +6012,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"px-3 py-1.5 bg-green-600 hover:bg-green-700",
|
||||
"text-white text-xs font-medium rounded"
|
||||
"text-white text-xs font-medium rounded-sm"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Patrol approval execute actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -6035,7 +6035,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"px-3 py-1.5 bg-amber-600 hover:bg-amber-700",
|
||||
"text-white text-xs font-medium rounded"
|
||||
"text-white text-xs font-medium rounded-sm"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Patrol approval warning actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -6058,7 +6058,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"px-3 py-1.5 bg-blue-600 hover:bg-blue-700",
|
||||
"text-white text-xs font-medium rounded"
|
||||
"text-white text-xs font-medium rounded-sm"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Patrol Assistant handoff actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -6103,7 +6103,7 @@
|
||||
"scopes": ["src/components/Settings", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900"
|
||||
"rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900/25"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Positive outlined settings actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -6124,7 +6124,7 @@
|
||||
},
|
||||
"scopes": ["src/components/Settings", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["rounded-md px-3 py-2 text-sm font-medium text-emerald-900"],
|
||||
"allPatterns": ["rounded-md px-3 py-2 text-sm font-medium text-emerald-900/25"],
|
||||
"legacyReason": "Retired migration debt. Positive ghost settings actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
|
||||
@@ -6146,7 +6146,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"rounded-md px-2 py-1 text-xs font-medium text-emerald-700",
|
||||
"hover:bg-emerald-50 dark:text-emerald-300 dark:hover:bg-emerald-900"
|
||||
"hover:bg-emerald-50 dark:text-emerald-300 dark:hover:bg-emerald-900/25"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Positive row actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -6255,7 +6255,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"rounded-md px-2 py-1 text-xs font-medium text-red-600",
|
||||
"hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900"
|
||||
"hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900/25"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Destructive row actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
|
||||
@@ -136,9 +136,9 @@ export function sessionHasSettingsAccess(scopes: string[] | undefined): boolean
|
||||
}
|
||||
const NAV_TAB_ICON_CLASS = 'w-4 h-4 shrink-0';
|
||||
const AI_CHAT_MOBILE_LAUNCHER_BUTTON_CLASS =
|
||||
'group relative flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-full bg-surface-hover text-blue-600 transition-colors hover:bg-border hover:text-blue-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:text-blue-400 dark:hover:text-blue-300';
|
||||
'group relative flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-surface-hover text-blue-600 transition-colors hover:bg-border hover:text-blue-700 focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:text-blue-400 dark:hover:text-blue-300';
|
||||
const AI_CHAT_DESKTOP_LAUNCHER_BUTTON_CLASS =
|
||||
'fixed right-0 top-1/2 z-40 flex min-h-9 min-w-10 -translate-y-1/2 items-center justify-center rounded-l-lg border border-r-0 border-border bg-surface px-2.5 py-2.5 text-blue-600 transition-colors duration-200 hover:bg-surface-hover hover:text-blue-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:text-blue-400 dark:hover:text-blue-300';
|
||||
'fixed right-0 top-1/2 z-40 flex min-h-9 min-w-10 -translate-y-1/2 items-center justify-center rounded-l-lg border border-r-0 border-border bg-surface px-2.5 py-2.5 text-blue-600 transition-colors duration-200 hover:bg-surface-hover hover:text-blue-700 focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:text-blue-400 dark:hover:text-blue-300';
|
||||
|
||||
function getDesktopUtilityTabAriaLabel(tab: UtilityTab): string {
|
||||
const count = tab.count ?? 0;
|
||||
@@ -208,7 +208,7 @@ export function ConnectionStatusBadge(props: {
|
||||
aria-label={status().detail}
|
||||
>
|
||||
<Show when={showSpinner()}>
|
||||
<svg class="animate-spin h-3 w-3 flex-shrink-0" fill="none" viewBox="0 0 24 24">
|
||||
<svg class="animate-spin h-3 w-3 shrink-0" fill="none" viewBox="0 0 24 24">
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
@@ -225,7 +225,7 @@ export function ConnectionStatusBadge(props: {
|
||||
</svg>
|
||||
</Show>
|
||||
<Show when={!showSpinner()}>
|
||||
<span class={`h-2.5 w-2.5 rounded-full flex-shrink-0 ${indicatorClass()}`} />
|
||||
<span class={`h-2.5 w-2.5 rounded-full shrink-0 ${indicatorClass()}`} />
|
||||
</Show>
|
||||
<span
|
||||
class={`whitespace-nowrap overflow-hidden transition-all duration-500 ${
|
||||
@@ -470,7 +470,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
title={assistantPageContext().title}
|
||||
aria-label={assistantPageContext().ariaLabel}
|
||||
>
|
||||
<SparklesIcon class="h-5 w-5 flex-shrink-0" />
|
||||
<SparklesIcon class="h-5 w-5 shrink-0" />
|
||||
</button>
|
||||
);
|
||||
createEffect(() => {
|
||||
@@ -691,7 +691,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
|
||||
const className = () => {
|
||||
if (isActive()) {
|
||||
return `${baseClasses} bg-surface text-blue-600 dark:text-blue-400 border-border border-b border-b-surface shadow-sm font-semibold`;
|
||||
return `${baseClasses} bg-surface text-blue-600 dark:text-blue-400 border-border border-b border-b-surface shadow-xs font-semibold`;
|
||||
}
|
||||
if (needsSetup()) {
|
||||
return `${baseClasses} text-muted opacity-70 bg-base hover:bg-surface-hover`;
|
||||
@@ -718,7 +718,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<span class="hidden xs:inline-flex items-center gap-1">
|
||||
<span>{tab.label}</span>
|
||||
<Show when={tab.badge}>
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted bg-surface-hover rounded">
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted bg-surface-hover rounded-sm">
|
||||
{tab.badge}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -751,7 +751,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<div
|
||||
class={`header mb-1 flex flex-wrap items-center gap-1 sm:flex-nowrap sm:mb-3 sm:gap-2 ${
|
||||
kioskMode()
|
||||
? 'fixed top-0 left-0 right-0 z-50 justify-end bg-surface shadow-sm'
|
||||
? 'fixed top-0 left-0 right-0 z-50 justify-end bg-surface shadow-xs'
|
||||
: 'justify-between sm:grid sm:grid-cols-[1fr_auto_1fr] sm:items-center sm:gap-0'
|
||||
}`}
|
||||
style={
|
||||
@@ -797,7 +797,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<img
|
||||
src={logoDataUrl()}
|
||||
alt={customBrandName() ? `${customBrandName()} logo` : 'Custom logo'}
|
||||
class="max-h-8 max-w-[12rem] object-contain"
|
||||
class="max-h-8 max-w-48 object-contain"
|
||||
data-testid="custom-brand-logo"
|
||||
/>
|
||||
)}
|
||||
@@ -808,7 +808,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={props.versionInfo()?.channel === 'rc'}>
|
||||
<span class="text-xs px-1.5 py-0.5 bg-orange-700 text-white rounded font-bold">
|
||||
<span class="text-xs px-1.5 py-0.5 bg-orange-700 text-white rounded-sm font-bold">
|
||||
Preview
|
||||
</span>
|
||||
</Show>
|
||||
@@ -834,9 +834,9 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleKioskMode}
|
||||
class={`group relative flex h-9 w-9 items-center justify-center rounded-full text-xs transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 sm:h-10 sm:w-10 ${
|
||||
class={`group relative flex h-9 w-9 items-center justify-center rounded-full text-xs transition focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 sm:h-10 sm:w-10 ${
|
||||
kioskMode()
|
||||
? 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900 dark:text-blue-300 dark:hover:bg-blue-800'
|
||||
? 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/25 dark:text-blue-300 dark:hover:bg-blue-800'
|
||||
: 'bg-surface-hover text-base-content hover:bg-border'
|
||||
}`}
|
||||
title={
|
||||
@@ -847,8 +847,8 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
aria-label={kioskMode() ? 'Exit kiosk mode' : 'Enter kiosk mode'}
|
||||
aria-pressed={kioskMode()}
|
||||
>
|
||||
<Show when={kioskMode()} fallback={<Maximize2Icon class="h-4 w-4 flex-shrink-0" />}>
|
||||
<Minimize2Icon class="h-4 w-4 flex-shrink-0" />
|
||||
<Show when={kioskMode()} fallback={<Maximize2Icon class="h-4 w-4 shrink-0" />}>
|
||||
<Minimize2Icon class="h-4 w-4 shrink-0" />
|
||||
</Show>
|
||||
</button>
|
||||
<Show when={props.proxyAuthInfo()?.username}>
|
||||
@@ -857,12 +857,12 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.handleLogout}
|
||||
class="group relative flex h-9 w-9 items-center justify-center rounded-full bg-surface-hover text-xs text-base-content transition hover:bg-border focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 sm:h-10 sm:w-10"
|
||||
class="group relative flex h-9 w-9 items-center justify-center rounded-full bg-surface-hover text-xs text-base-content transition hover:bg-border focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 sm:h-10 sm:w-10"
|
||||
title="Logout"
|
||||
aria-label="Logout"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4 flex-shrink-0"
|
||||
class="h-4 w-4 shrink-0"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -878,7 +878,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
<ConnectionStatusBadge connectionStatus={props.connectionStatus} class="flex-shrink-0" />
|
||||
<ConnectionStatusBadge connectionStatus={props.connectionStatus} class="shrink-0" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -901,7 +901,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
|
||||
const className = () => {
|
||||
if (isActive()) {
|
||||
return `${baseClasses} bg-surface text-blue-600 dark:text-blue-400 border-border border-b border-b-surface shadow-sm font-semibold`;
|
||||
return `${baseClasses} bg-surface text-blue-600 dark:text-blue-400 border-border border-b border-b-surface shadow-xs font-semibold`;
|
||||
}
|
||||
return `${baseClasses} text-muted hover:text-base-content hover:bg-surface-hover`;
|
||||
};
|
||||
@@ -960,7 +960,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={tab.badge === 'pro' && !presentationPolicyHidesUpgradePrompts()}>
|
||||
<span class="ml-1.5 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-blue-700 dark:text-blue-300 bg-blue-100 dark:bg-blue-900 rounded">
|
||||
<span class="ml-1.5 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-blue-700 dark:text-blue-300 bg-blue-100 dark:bg-blue-900/25 rounded-sm">
|
||||
Pro
|
||||
</span>
|
||||
</Show>
|
||||
@@ -976,7 +976,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<main
|
||||
id="main"
|
||||
tabindex="-1"
|
||||
class="tab-content mb-1 block rounded-b rounded-tl rounded-tr bg-surface shadow sm:mb-2"
|
||||
class="tab-content mb-1 block rounded-b rounded-tl rounded-tr bg-surface shadow-sm sm:mb-2"
|
||||
>
|
||||
<div class="pulse-panel">
|
||||
<Suspense fallback={<div class="p-6 text-sm text-muted">Loading view...</div>}>
|
||||
@@ -1004,7 +1004,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
href="https://github.com/rcourtman/Pulse/releases"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex min-h-8 items-center break-all rounded px-1 py-1 text-blue-600 hover:underline dark:text-blue-400 sm:min-h-9"
|
||||
class="inline-flex min-h-8 items-center break-all rounded-sm px-1 py-1 text-blue-600 hover:underline dark:text-blue-400 sm:min-h-9"
|
||||
>
|
||||
{props.versionInfo()?.version || 'loading...'}
|
||||
</a>
|
||||
@@ -1023,7 +1023,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<Show when={isPro()}>
|
||||
<a
|
||||
href={`mailto:support@pulserelay.pro?subject=${encodeURIComponent(`Support Request - Pulse ${props.versionInfo()?.version || ''}`)}`}
|
||||
class="inline-flex min-h-8 items-center rounded px-1 py-1 text-blue-600 hover:underline dark:text-blue-400 sm:min-h-9"
|
||||
class="inline-flex min-h-8 items-center rounded-sm px-1 py-1 text-blue-600 hover:underline dark:text-blue-400 sm:min-h-9"
|
||||
>
|
||||
Get Support
|
||||
</a>
|
||||
|
||||
@@ -639,7 +639,7 @@ describe('App architecture', () => {
|
||||
expect(appLayoutSource).toContain('animate-pulse-brand');
|
||||
expect(appLayoutSource).toContain('pulse-brand-wordmark');
|
||||
expect(appLayoutSource).toContain("'pb-safe-or-14 xl:pb-0'");
|
||||
expect(appStylesSource).toContain('.pb-safe-or-14');
|
||||
expect(appStylesSource).toContain('@utility pb-safe-or-14');
|
||||
expect(appStylesSource).toContain('.pulse-shell--full-width');
|
||||
expect(appStylesSource).toContain('.pulse-wide-data-surface.space-y-3');
|
||||
expect(appStylesSource).toContain('.filter-bar > div > div:first-child button');
|
||||
|
||||
@@ -721,26 +721,26 @@ export const investigationOutcomeLabels: Record<InvestigationOutcome, string> =
|
||||
*/
|
||||
export const investigationOutcomeColors: Record<InvestigationOutcome, string> = {
|
||||
resolved:
|
||||
'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900 dark:text-green-300',
|
||||
'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900/25 dark:text-green-300',
|
||||
fix_queued:
|
||||
'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-900 dark:text-blue-300',
|
||||
'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-900/25 dark:text-blue-300',
|
||||
fix_executed:
|
||||
'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900 dark:text-green-300',
|
||||
'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900/25 dark:text-green-300',
|
||||
fix_failed:
|
||||
'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-900 dark:text-red-300',
|
||||
'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-900/25 dark:text-red-300',
|
||||
fix_rejected:
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-300',
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-300',
|
||||
needs_attention:
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-300',
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-300',
|
||||
cannot_fix: 'border-slate-200 bg-slate-50 text-slate-600',
|
||||
timed_out:
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-300',
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-300',
|
||||
fix_verified:
|
||||
'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900 dark:text-green-300',
|
||||
'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900/25 dark:text-green-300',
|
||||
fix_verification_failed:
|
||||
'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-900 dark:text-red-300',
|
||||
'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-900/25 dark:text-red-300',
|
||||
fix_verification_unknown:
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-300',
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-300',
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -287,10 +287,10 @@ export const AICostDashboard: Component = () => {
|
||||
|
||||
return (
|
||||
<Card padding="none" class="overflow-hidden border border-border" border={false}>
|
||||
<div class="bg-blue-50 dark:bg-blue-900 px-4 py-4 border-b border-border sm:px-6">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/25 px-4 py-4 border-b border-border sm:px-6">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<div class="flex min-w-0 items-start gap-3 sm:flex-1 sm:items-center">
|
||||
<div class="shrink-0 p-2 bg-blue-100 dark:bg-blue-900 rounded-md">
|
||||
<div class="shrink-0 p-2 bg-blue-100 dark:bg-blue-900/25 rounded-md">
|
||||
<svg
|
||||
class="w-5 h-5 text-blue-600 dark:text-blue-300"
|
||||
fill="none"
|
||||
@@ -340,14 +340,14 @@ export const AICostDashboard: Component = () => {
|
||||
</Show>
|
||||
|
||||
<Show when={summary()?.truncated}>
|
||||
<div class="text-xs px-3 py-2 rounded border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900 text-blue-900 dark:text-blue-100">
|
||||
<div class="text-xs px-3 py-2 rounded-sm border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/25 text-blue-900/25 dark:text-blue-100">
|
||||
Showing the last {summary()?.effective_days} days due to a {summary()?.retention_days}
|
||||
-day retention window.
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={isOverBudget()}>
|
||||
<div class="text-xs px-3 py-2 rounded border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900 text-red-900 dark:text-red-100">
|
||||
<div class="text-xs px-3 py-2 rounded-sm border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/25 text-red-900/25 dark:text-red-100">
|
||||
Estimated spend ({formatUSD(estimatedTotalUSD() ?? 0)}) is above your budget (
|
||||
{formatUSD(budgetForRange() ?? 0)}).
|
||||
</div>
|
||||
@@ -357,14 +357,14 @@ export const AICostDashboard: Component = () => {
|
||||
<div
|
||||
role="alert"
|
||||
aria-live="polite"
|
||||
class="flex items-center justify-between gap-3 text-xs px-3 py-2 rounded border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900 text-amber-900 dark:text-amber-100"
|
||||
class="flex items-center justify-between gap-3 text-xs px-3 py-2 rounded-sm border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900/25 text-amber-900/25 dark:text-amber-100"
|
||||
>
|
||||
<div class="truncate">Couldn’t refresh. Showing last loaded data. {loadError()}</div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={loading()}
|
||||
onClick={() => loadSummary(days())}
|
||||
class={`shrink-0 px-2 py-1 rounded border border-amber-300 dark:border-amber-700 hover:bg-amber-100 dark:hover:bg-amber-900 ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
class={`shrink-0 px-2 py-1 rounded-sm border border-amber-300 dark:border-amber-700 hover:bg-amber-100 dark:hover:bg-amber-900/25 ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
@@ -522,7 +522,7 @@ export const AICostDashboard: Component = () => {
|
||||
type="button"
|
||||
disabled={loading()}
|
||||
onClick={() => downloadExport('csv')}
|
||||
class={`min-h-10 sm:min-h-9 px-2.5 py-2 text-sm rounded border border-border hover:bg-surface-hover ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
class={`min-h-10 sm:min-h-9 px-2.5 py-2 text-sm rounded-sm border border-border hover:bg-surface-hover ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
Export CSV
|
||||
</button>
|
||||
@@ -530,7 +530,7 @@ export const AICostDashboard: Component = () => {
|
||||
type="button"
|
||||
disabled={loading()}
|
||||
onClick={() => downloadExport('json')}
|
||||
class={`min-h-10 sm:min-h-9 px-2.5 py-2 text-sm rounded border border-border hover:bg-surface-hover ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
class={`min-h-10 sm:min-h-9 px-2.5 py-2 text-sm rounded-sm border border-border hover:bg-surface-hover ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
Export JSON
|
||||
</button>
|
||||
@@ -538,7 +538,7 @@ export const AICostDashboard: Component = () => {
|
||||
type="button"
|
||||
disabled={loading()}
|
||||
onClick={resetHistory}
|
||||
class={`min-h-10 sm:min-h-9 px-2.5 py-2 text-sm rounded border border-border hover:bg-surface-hover ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
class={`min-h-10 sm:min-h-9 px-2.5 py-2 text-sm rounded-sm border border-border hover:bg-surface-hover ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
{AI_COST_RESET_HISTORY_LABEL}
|
||||
</button>
|
||||
|
||||
@@ -26,11 +26,11 @@ const confidenceLabel = (level?: string) => {
|
||||
const confidenceClasses = (level?: string) => {
|
||||
switch (level) {
|
||||
case 'verified':
|
||||
return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-200';
|
||||
return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/25 dark:text-emerald-200';
|
||||
case 'partial':
|
||||
return 'bg-sky-100 text-sky-700 dark:bg-sky-900 dark:text-sky-200';
|
||||
return 'bg-sky-100 text-sky-700 dark:bg-sky-900/25 dark:text-sky-200';
|
||||
case 'inferred':
|
||||
return 'bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-200';
|
||||
return 'bg-amber-100 text-amber-700 dark:bg-amber-900/25 dark:text-amber-200';
|
||||
default:
|
||||
return 'bg-base-200 text-base-content';
|
||||
}
|
||||
@@ -51,18 +51,18 @@ const formatExpiry = (expiresAt?: string) => {
|
||||
|
||||
export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
return (
|
||||
<div class="rounded-md border border-amber-300 dark:border-amber-700 overflow-hidden shadow-sm">
|
||||
<div class="rounded-md border border-amber-300 dark:border-amber-700 overflow-hidden shadow-xs">
|
||||
{/* Header */}
|
||||
<div class="px-3 py-2 text-xs font-medium flex items-center gap-2 bg-amber-50 dark:bg-amber-900 text-amber-800 dark:text-amber-200 border-b border-amber-200 dark:border-amber-800">
|
||||
<div class="p-1 rounded bg-amber-100 dark:bg-amber-800">
|
||||
<div class="px-3 py-2 text-xs font-medium flex items-center gap-2 bg-amber-50 dark:bg-amber-900/25 text-amber-800 dark:text-amber-200 border-b border-amber-200 dark:border-amber-800">
|
||||
<div class="p-1 rounded-sm bg-amber-100 dark:bg-amber-800">
|
||||
<AlertTriangleIcon class="w-3.5 h-3.5" />
|
||||
</div>
|
||||
<span class="font-semibold">Approval Required</span>
|
||||
<span class="px-1.5 py-0.5 bg-amber-200 dark:bg-amber-800 rounded text-[10px] font-bold uppercase tracking-wider">
|
||||
<span class="px-1.5 py-0.5 bg-amber-200 dark:bg-amber-800 rounded-sm text-[10px] font-bold uppercase tracking-wider">
|
||||
{riskLabel(props.approval.risk)}
|
||||
</span>
|
||||
<Show when={props.approval.runOnHost}>
|
||||
<span class="px-1.5 py-0.5 bg-amber-200 dark:bg-amber-800 rounded text-[10px] font-bold uppercase tracking-wider">
|
||||
<span class="px-1.5 py-0.5 bg-amber-200 dark:bg-amber-800 rounded-sm text-[10px] font-bold uppercase tracking-wider">
|
||||
Agent
|
||||
</span>
|
||||
</Show>
|
||||
@@ -74,9 +74,9 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
</div>
|
||||
|
||||
{/* Command */}
|
||||
<div class="px-3 py-3 bg-amber-50 dark:bg-amber-900">
|
||||
<div class="px-3 py-3 bg-amber-50 dark:bg-amber-900/25">
|
||||
<Show when={props.approval.description}>
|
||||
<p class="mb-3 text-xs leading-relaxed text-amber-900 dark:text-amber-100">
|
||||
<p class="mb-3 text-xs leading-relaxed text-amber-900/25 dark:text-amber-100">
|
||||
{props.approval.description}
|
||||
</p>
|
||||
</Show>
|
||||
@@ -100,7 +100,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 p-2 bg-surface rounded border border-amber-200 dark:border-amber-700">
|
||||
<div class="mb-3 p-2 bg-surface rounded-sm border border-amber-200 dark:border-amber-700">
|
||||
<code class="text-xs font-mono text-base-content break-all">
|
||||
{props.approval.command}
|
||||
</code>
|
||||
@@ -131,7 +131,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<div class="uppercase font-semibold text-amber-700 dark:text-amber-300">
|
||||
Approval
|
||||
</div>
|
||||
<div class="text-base-content break-words">
|
||||
<div class="text-base-content wrap-break-word">
|
||||
{props.approval.plan?.approval_policy || 'admin approval'}
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,7 +139,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<div class="uppercase font-semibold text-amber-700 dark:text-amber-300">
|
||||
Blast radius
|
||||
</div>
|
||||
<div class="text-base-content break-words">
|
||||
<div class="text-base-content wrap-break-word">
|
||||
{props.approval.plan?.blast_radius || 'target resource state'}
|
||||
</div>
|
||||
</div>
|
||||
@@ -170,7 +170,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<span class="text-[11px] font-semibold uppercase text-amber-700 dark:text-amber-300">
|
||||
Preflight
|
||||
</span>
|
||||
<span class="px-1.5 py-0.5 rounded bg-base-200 text-[10px] font-bold uppercase text-base-content">
|
||||
<span class="px-1.5 py-0.5 rounded-sm bg-base-200 text-[10px] font-bold uppercase text-base-content">
|
||||
{props.approval.preflight?.dry_run_available ? 'Dry run' : 'No dry run'}
|
||||
</span>
|
||||
</div>
|
||||
@@ -181,7 +181,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<div class="uppercase font-semibold text-amber-700 dark:text-amber-300">
|
||||
Target
|
||||
</div>
|
||||
<div class="text-base-content break-words">
|
||||
<div class="text-base-content wrap-break-word">
|
||||
{props.approval.preflight?.target}
|
||||
</div>
|
||||
</div>
|
||||
@@ -191,7 +191,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<div class="uppercase font-semibold text-amber-700 dark:text-amber-300">
|
||||
Intended change
|
||||
</div>
|
||||
<div class="text-base-content break-words">
|
||||
<div class="text-base-content wrap-break-word">
|
||||
{props.approval.preflight?.intended_change}
|
||||
</div>
|
||||
</div>
|
||||
@@ -215,9 +215,9 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<div class="text-[11px] font-semibold uppercase text-amber-700 dark:text-amber-300">
|
||||
Safety checks
|
||||
</div>
|
||||
<ul class="mt-1 space-y-1 text-[11px] text-base-content/80">
|
||||
<ul class="mt-1 space-y-1 text-[11px]">
|
||||
<For each={props.approval.preflight?.safety_checks || []}>
|
||||
{(item) => <li class="break-words">{item}</li>}
|
||||
{(item) => <li class="wrap-break-word">{item}</li>}
|
||||
</For>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -228,9 +228,9 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<div class="text-[11px] font-semibold uppercase text-amber-700 dark:text-amber-300">
|
||||
Verification
|
||||
</div>
|
||||
<ul class="mt-1 space-y-1 text-[11px] text-base-content/80">
|
||||
<ul class="mt-1 space-y-1 text-[11px]">
|
||||
<For each={props.approval.preflight?.verification_steps || []}>
|
||||
{(item) => <li class="break-words">{item}</li>}
|
||||
{(item) => <li class="wrap-break-word">{item}</li>}
|
||||
</For>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -256,9 +256,9 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={(props.approval.contextConfidence?.evidence || []).length > 0}>
|
||||
<ul class="mt-1.5 space-y-1 text-[11px] text-base-content/80">
|
||||
<ul class="mt-1.5 space-y-1 text-[11px]">
|
||||
<For each={props.approval.contextConfidence?.evidence || []}>
|
||||
{(item) => <li class="break-words">{item}</li>}
|
||||
{(item) => <li class="wrap-break-word">{item}</li>}
|
||||
</For>
|
||||
</ul>
|
||||
</Show>
|
||||
@@ -266,7 +266,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
</Show>
|
||||
|
||||
<Show when={props.approval.auditId || props.approval.plan?.plan_hash}>
|
||||
<div class="pt-2 border-t border-amber-200 dark:border-amber-700 text-[11px] text-base-content/80 break-all">
|
||||
<div class="pt-2 border-t border-amber-200 dark:border-amber-700 text-[11px] break-all">
|
||||
<Show when={props.approval.auditId}>
|
||||
<span>Audit {props.approval.auditId}</span>
|
||||
</Show>
|
||||
@@ -288,7 +288,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
class={`flex-1 px-3 py-2 text-xs font-semibold rounded-md transition-all ${
|
||||
props.approval.isExecuting
|
||||
? 'bg-green-400 text-white cursor-wait'
|
||||
: 'bg-green-500 hover:bg-green-600 text-white shadow-sm hover:shadow-sm'
|
||||
: 'bg-green-500 hover:bg-green-600 text-white shadow-xs hover:shadow-xs'
|
||||
}`}
|
||||
>
|
||||
<Show
|
||||
|
||||
@@ -112,7 +112,7 @@ export function AssistantCommandHelpDialog(props: AssistantCommandHelpDialogProp
|
||||
isOpen={true}
|
||||
onClose={props.onClose}
|
||||
layout="mobile-sheet"
|
||||
panelClass="max-w-[30rem] shadow-xl"
|
||||
panelClass="max-w-120 shadow-xl"
|
||||
ariaLabel={AI_CHAT_COMMAND_HELP_TITLE}
|
||||
>
|
||||
<>
|
||||
@@ -139,7 +139,7 @@ export function AssistantCommandHelpDialog(props: AssistantCommandHelpDialogProp
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="max-h-[25rem] overflow-y-auto p-2"
|
||||
class="max-h-100 overflow-y-auto p-2"
|
||||
role="listbox"
|
||||
aria-label={AI_CHAT_COMMAND_HELP_TITLE}
|
||||
>
|
||||
@@ -172,7 +172,7 @@ export function AssistantCommandHelpDialog(props: AssistantCommandHelpDialogProp
|
||||
role="option"
|
||||
aria-selected={item.index === selectedCommandIndex()}
|
||||
aria-disabled={command.disabled ? 'true' : undefined}
|
||||
class={`flex w-full items-start gap-3 rounded-md px-3 py-2.5 text-left transition-colors focus:outline-none ${
|
||||
class={`flex w-full items-start gap-3 rounded-md px-3 py-2.5 text-left transition-colors focus:outline-hidden ${
|
||||
command.disabled
|
||||
? 'cursor-help opacity-55'
|
||||
: 'hover:bg-surface-hover focus:bg-surface-hover'
|
||||
|
||||
@@ -286,7 +286,7 @@ export const ChatMessages: Component<ChatMessagesProps> = (props) => {
|
||||
{(prompt) => (
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-left text-sm text-base-content transition-colors hover:border-blue-300 hover:bg-surface-alt focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-left text-sm text-base-content transition-colors hover:border-blue-300 hover:bg-surface-alt focus:outline-hidden focus:ring-2 focus:ring-blue-500/30"
|
||||
onClick={() => props.onSuggestedPrompt?.(prompt)}
|
||||
data-testid="assistant-suggested-prompt"
|
||||
>
|
||||
@@ -310,7 +310,7 @@ export const ChatMessages: Component<ChatMessagesProps> = (props) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-left transition-colors hover:border-blue-300 hover:bg-surface-alt focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-left transition-colors hover:border-blue-300 hover:bg-surface-alt focus:outline-hidden focus:ring-2 focus:ring-blue-500/30"
|
||||
onClick={() => props.onLoadSession?.(session.id)}
|
||||
aria-label={`Resume ${session.title || 'Untitled Assistant session'}`}
|
||||
>
|
||||
|
||||
@@ -184,7 +184,7 @@ export function MentionAutocomplete(props: MentionAutocompleteProps) {
|
||||
return (
|
||||
<Show when={props.visible && filteredResources().length > 0}>
|
||||
<div
|
||||
class="absolute z-50 w-[calc(100vw-36px)] min-w-[280px] max-w-[400px] overflow-hidden rounded-md border border-border bg-surface shadow-sm"
|
||||
class="absolute z-50 w-[calc(100vw-36px)] min-w-[280px] max-w-[400px] overflow-hidden rounded-md border border-border bg-surface shadow-xs"
|
||||
style={{
|
||||
bottom: `${props.position.top}px`,
|
||||
left: `${props.position.left}px`,
|
||||
@@ -247,11 +247,11 @@ export function MentionAutocomplete(props: MentionAutocompleteProps) {
|
||||
</For>
|
||||
</div>
|
||||
<div class="px-3 py-1.5 border-t border-border text-xs text-muted flex items-center gap-2">
|
||||
<span class="px-1.5 py-0.5 bg-surface-hover rounded text-[10px]">↑↓</span>
|
||||
<span class="px-1.5 py-0.5 bg-surface-hover rounded-sm text-[10px]">↑↓</span>
|
||||
navigate
|
||||
<span class="px-1.5 py-0.5 bg-surface-hover rounded text-[10px]">↵</span>
|
||||
<span class="px-1.5 py-0.5 bg-surface-hover rounded-sm text-[10px]">↵</span>
|
||||
select
|
||||
<span class="px-1.5 py-0.5 bg-surface-hover rounded text-[10px]">esc</span>
|
||||
<span class="px-1.5 py-0.5 bg-surface-hover rounded-sm text-[10px]">esc</span>
|
||||
close
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -73,7 +73,7 @@ interface MessageItemProps {
|
||||
}
|
||||
|
||||
const markdownClass =
|
||||
'text-sm prose prose-slate prose-sm dark:prose-invert max-w-none prose-p:leading-relaxed prose-p:my-2 prose-pre:bg-slate-900 prose-pre:text-slate-100 prose-pre:rounded-md prose-pre:text-xs prose-pre:border prose-pre:border-slate-800 prose-code:[overflow-wrap:anywhere] prose-code:text-blue-700 dark:prose-code:text-blue-300 prose-code:bg-blue-50 dark:prose-code:bg-blue-900 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:font-mono prose-code:text-[0.9em] prose-code:border prose-code:border-blue-100 dark:prose-code:border-blue-800 prose-code:before:content-none prose-code:after:content-none prose-headings:font-semibold prose-hr:border-slate-200 dark:prose-hr:border-slate-700 prose-ul:my-2 prose-ol:my-2 prose-li:my-1';
|
||||
'text-sm prose prose-slate prose-sm dark:prose-invert max-w-none prose-p:leading-relaxed prose-p:my-2 prose-pre:bg-slate-900 prose-pre:text-slate-100 prose-pre:rounded-md prose-pre:text-xs prose-pre:border prose-pre:border-slate-800 prose-code:wrap-anywhere prose-code:text-blue-700 dark:prose-code:text-blue-300 prose-code:bg-blue-50 dark:prose-code:bg-blue-900/25 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:font-mono prose-code:text-[0.9em] prose-code:border prose-code:border-blue-100 dark:prose-code:border-blue-800 prose-code:before:content-none prose-code:after:content-none prose-headings:font-semibold prose-hr:border-slate-200 dark:prose-hr:border-slate-700 prose-ul:my-2 prose-ol:my-2 prose-li:my-1';
|
||||
|
||||
const TEXT_RENDER_PACE_MS = 24;
|
||||
const TEXT_RENDER_SNAP = /[\s.,!?;:)\]]/;
|
||||
@@ -547,7 +547,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
title="Edit and resend"
|
||||
tone="outline"
|
||||
size="sm"
|
||||
class="mt-1 opacity-0 shadow-sm transition-opacity focus:opacity-100 group-hover:opacity-100"
|
||||
class="mt-1 opacity-0 shadow-xs transition-opacity focus:opacity-100 group-hover:opacity-100"
|
||||
>
|
||||
<PencilIcon class="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</ActionIconButton>
|
||||
@@ -560,13 +560,13 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
label={copyButtonLabel()}
|
||||
size="md"
|
||||
stopPropagation
|
||||
class="mt-1 opacity-0 shadow-sm transition-opacity focus:opacity-100 group-hover:opacity-100"
|
||||
class="mt-1 opacity-0 shadow-xs transition-opacity focus:opacity-100 group-hover:opacity-100"
|
||||
/>
|
||||
</Show>
|
||||
<div
|
||||
class={`min-w-0 px-4 py-2.5 rounded-md rounded-br-sm shadow-sm ${
|
||||
class={`min-w-0 px-4 py-2.5 rounded-md rounded-br-sm shadow-xs ${
|
||||
isQueuedUserMessage()
|
||||
? 'border border-blue-200 bg-blue-50 text-blue-950 dark:border-blue-900/60 dark:bg-blue-950/30 dark:text-blue-100'
|
||||
? 'border border-blue-200 bg-blue-50 text-blue-950/25 dark:border-blue-900/60 dark:bg-blue-950/30 dark:text-blue-100'
|
||||
: 'bg-blue-600 text-white'
|
||||
}`}
|
||||
>
|
||||
@@ -581,7 +581,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
<Show when={queuedModelRouteLabel()}>
|
||||
{(label) => (
|
||||
<span
|
||||
class="inline-flex max-w-[12rem] items-center rounded border border-blue-200 bg-white/80 px-1.5 py-0.5 text-[10px] font-semibold text-blue-800 dark:border-blue-800 dark:bg-blue-950/40 dark:text-blue-200"
|
||||
class="inline-flex max-w-48 items-center rounded-sm border border-blue-200 bg-white/80 px-1.5 py-0.5 text-[10px] font-semibold text-blue-800 dark:border-blue-800 dark:bg-blue-950/40 dark:text-blue-200"
|
||||
title={props.message.request?.model}
|
||||
>
|
||||
<span class="truncate">{label()}</span>
|
||||
@@ -617,7 +617,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
{/* Assistant message */}
|
||||
<Show when={!isUser()}>
|
||||
<div class="group flex w-full min-w-0 gap-3 px-1 py-2">
|
||||
<div class="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border-subtle bg-surface-alt text-blue-600 shadow-sm dark:text-blue-400">
|
||||
<div class="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border-subtle bg-surface-alt text-blue-600 shadow-xs dark:text-blue-400">
|
||||
<SparklesIcon class="h-3.5 w-3.5" />
|
||||
</div>
|
||||
|
||||
@@ -628,7 +628,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
</span>
|
||||
<Show when={messageModelLabel()}>
|
||||
<span
|
||||
class="max-w-[12rem] truncate rounded border border-border-subtle bg-surface-alt px-1.5 py-0.5 text-[10px] font-medium text-muted"
|
||||
class="max-w-48 truncate rounded-sm border border-border-subtle bg-surface-alt px-1.5 py-0.5 text-[10px] font-medium text-muted"
|
||||
title={props.message.model}
|
||||
>
|
||||
{messageModelLabel()}
|
||||
@@ -636,7 +636,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
</Show>
|
||||
<Show when={messageDurationLabel()}>
|
||||
<span
|
||||
class="inline-flex shrink-0 items-center gap-1 rounded border border-border-subtle bg-surface-alt px-1.5 py-0.5 text-[10px] font-medium text-muted"
|
||||
class="inline-flex shrink-0 items-center gap-1 rounded-sm border border-border-subtle bg-surface-alt px-1.5 py-0.5 text-[10px] font-medium text-muted"
|
||||
title="Turn duration"
|
||||
aria-label={`Turn duration ${messageDurationLabel()}`}
|
||||
>
|
||||
@@ -669,7 +669,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
label={copyButtonLabel()}
|
||||
size="md"
|
||||
stopPropagation
|
||||
class="ml-auto opacity-0 shadow-sm transition-opacity focus:opacity-100 group-hover:opacity-100"
|
||||
class="ml-auto opacity-0 shadow-xs transition-opacity focus:opacity-100 group-hover:opacity-100"
|
||||
/>
|
||||
</Show>
|
||||
<Show when={props.onRegenerate}>
|
||||
@@ -679,7 +679,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
title="Regenerate response"
|
||||
tone="outline"
|
||||
size="sm"
|
||||
class={`${canCopy() ? '' : 'ml-auto '}opacity-0 shadow-sm transition-opacity focus:opacity-100 group-hover:opacity-100`}
|
||||
class={`${canCopy() ? '' : 'ml-auto '}opacity-0 shadow-xs transition-opacity focus:opacity-100 group-hover:opacity-100`}
|
||||
>
|
||||
<RotateCcwIcon class="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</ActionIconButton>
|
||||
@@ -931,7 +931,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
}
|
||||
aria-label={modelRouteRecoveryButtonLabel()}
|
||||
title={alternative().label}
|
||||
class="inline-flex max-w-[14rem] items-center gap-1.5 rounded-md border border-red-300 bg-white/80 px-2 py-1 text-xs font-medium text-red-700 transition-colors hover:bg-red-100 dark:border-red-800 dark:bg-red-950/20 dark:text-red-300 dark:hover:bg-red-900/40"
|
||||
class="inline-flex max-w-56 items-center gap-1.5 rounded-md border border-red-300 bg-white/80 px-2 py-1 text-xs font-medium text-red-700 transition-colors hover:bg-red-100 dark:border-red-800 dark:bg-red-950/20 dark:text-red-300 dark:hover:bg-red-900/40"
|
||||
>
|
||||
<CpuIcon class="h-3.5 w-3.5" />
|
||||
<span class="truncate">{modelRouteRecoveryButtonLabel()}</span>
|
||||
|
||||
@@ -98,7 +98,7 @@ export const ModelSelector: Component<ModelSelectorProps> = (props) => {
|
||||
onManageProviders={props.onManageProviders}
|
||||
manageProvidersLabel="Open Assistant provider settings"
|
||||
align="left"
|
||||
buttonClass="flex flex-shrink-0 items-center gap-1.5 rounded-md border border-border bg-surface px-2.5 py-1.5 text-[11px] text-muted transition-colors hover:border-border hover:text-base-content"
|
||||
buttonClass="flex shrink-0 items-center gap-1.5 rounded-md border border-border bg-surface px-2.5 py-1.5 text-[11px] text-muted transition-colors hover:border-border hover:text-base-content"
|
||||
buttonLabelClass="max-w-[90px] truncate font-medium sm:max-w-[180px]"
|
||||
dropdownClass="w-80 max-w-[calc(100vw-2rem)]"
|
||||
/>
|
||||
|
||||
@@ -40,10 +40,10 @@ export const QuestionCard: Component<QuestionCardProps> = (props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="rounded-md border border-blue-300 dark:border-blue-700 overflow-hidden shadow-sm">
|
||||
<div class="rounded-md border border-blue-300 dark:border-blue-700 overflow-hidden shadow-xs">
|
||||
{/* Header */}
|
||||
<div class="px-3 py-2 text-xs font-medium flex items-center gap-2 bg-blue-50 dark:bg-blue-900 text-blue-800 dark:text-blue-200 border-b border-blue-200 dark:border-blue-800">
|
||||
<div class="p-1 rounded bg-blue-100 dark:bg-blue-800">
|
||||
<div class="px-3 py-2 text-xs font-medium flex items-center gap-2 bg-blue-50 dark:bg-blue-900/25 text-blue-800 dark:text-blue-200 border-b border-blue-200 dark:border-blue-800">
|
||||
<div class="p-1 rounded-sm bg-blue-100 dark:bg-blue-800">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@@ -57,7 +57,7 @@ export const QuestionCard: Component<QuestionCardProps> = (props) => {
|
||||
</div>
|
||||
|
||||
{/* Questions */}
|
||||
<div class="px-3 py-3 bg-blue-50 dark:bg-blue-900 space-y-4">
|
||||
<div class="px-3 py-3 bg-blue-50 dark:bg-blue-900/25 space-y-4">
|
||||
<For each={props.question.questions}>
|
||||
{(q) => (
|
||||
<div class="space-y-2">
|
||||
@@ -71,7 +71,7 @@ export const QuestionCard: Component<QuestionCardProps> = (props) => {
|
||||
type="text"
|
||||
value={answers()[q.id] || ''}
|
||||
onInput={(e) => handleInputChange(q.id, e.currentTarget.value)}
|
||||
class="w-full px-3 py-2 text-sm border border-blue-200 dark:border-blue-700 rounded-md bg-surface text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
class="w-full px-3 py-2 text-sm border border-blue-200 dark:border-blue-700 rounded-md bg-surface text-base-content focus:outline-hidden focus:ring-2 focus:ring-blue-500"
|
||||
placeholder={AI_CHAT_QUESTION_CARD_PLACEHOLDER}
|
||||
disabled={props.question.isAnswering}
|
||||
/>
|
||||
@@ -88,7 +88,7 @@ export const QuestionCard: Component<QuestionCardProps> = (props) => {
|
||||
class={`w-full px-3 py-2 text-sm text-left rounded-md border transition-colors ${
|
||||
answers()[q.id] === option.value
|
||||
? 'bg-blue-100 dark:bg-blue-800 border-blue-400 dark:border-blue-600 text-blue-800 dark:text-blue-200'
|
||||
: 'bg-surface border-blue-200 dark:border-blue-700 text-base-content hover:bg-blue-50 dark:hover:bg-blue-900'
|
||||
: 'bg-surface border-blue-200 dark:border-blue-700 text-base-content hover:bg-blue-50 dark:hover:bg-blue-900/25'
|
||||
} ${props.question.isAnswering ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
@@ -117,7 +117,7 @@ export const QuestionCard: Component<QuestionCardProps> = (props) => {
|
||||
? 'bg-blue-400 text-white cursor-wait'
|
||||
: !isValid()
|
||||
? 'bg-surface-hover text-muted cursor-not-allowed'
|
||||
: 'bg-blue-500 hover:bg-blue-600 text-white shadow-sm hover:shadow-sm'
|
||||
: 'bg-blue-500 hover:bg-blue-600 text-white shadow-xs hover:shadow-xs'
|
||||
}`}
|
||||
>
|
||||
<Show
|
||||
|
||||
@@ -166,7 +166,7 @@ export function SlashCommandAutocomplete(props: SlashCommandAutocompleteProps) {
|
||||
return (
|
||||
<Show when={props.visible}>
|
||||
<div
|
||||
class="absolute z-50 w-[calc(100vw-36px)] min-w-[280px] max-w-[420px] overflow-hidden rounded-md border border-border bg-surface shadow-sm"
|
||||
class="absolute z-50 w-[calc(100vw-36px)] min-w-[280px] max-w-[420px] overflow-hidden rounded-md border border-border bg-surface shadow-xs"
|
||||
style={{
|
||||
bottom: `${props.position.top}px`,
|
||||
left: `${props.position.left}px`,
|
||||
|
||||
@@ -63,7 +63,7 @@ export const ThinkingBlock: Component<ThinkingBlockProps> = (props) => {
|
||||
/>
|
||||
<span class="min-w-0 truncate">
|
||||
{statusText()}
|
||||
<span class="text-muted/80">
|
||||
<span class="">
|
||||
{durationLabel()
|
||||
? props.isStreaming
|
||||
? ` (${durationLabel()})`
|
||||
|
||||
@@ -156,7 +156,7 @@ const ToolInputSummary: Component<ToolInputSummaryProps> = (props) => {
|
||||
const isShellSummary = createMemo(() => props.summary.trim().startsWith('$ '));
|
||||
const className = createMemo(
|
||||
() =>
|
||||
`mt-1 block whitespace-pre-wrap break-words leading-5 text-base-content ${
|
||||
`mt-1 block whitespace-pre-wrap wrap-break-word leading-5 text-base-content ${
|
||||
isShellSummary() ? 'font-mono text-[11px]' : 'text-[12px] font-medium'
|
||||
}`,
|
||||
);
|
||||
@@ -210,7 +210,7 @@ const VisibleToolCommandPreview: Component<ToolCommandPreviewProps> = (props) =>
|
||||
<code
|
||||
data-testid="tool-command-preview"
|
||||
aria-label="Tool command"
|
||||
class="min-w-0 flex-1 whitespace-pre-wrap break-words font-mono text-[11px] leading-5 text-muted"
|
||||
class="min-w-0 flex-1 whitespace-pre-wrap wrap-break-word font-mono text-[11px] leading-5 text-muted"
|
||||
>
|
||||
{props.preview}
|
||||
</code>
|
||||
@@ -254,7 +254,7 @@ const detailCopyTarget = (kind: ToolDetailKind) => {
|
||||
};
|
||||
|
||||
const detailPreClass = (kind: ToolDetailKind) =>
|
||||
`${kind === 'output' ? 'max-h-72' : 'max-h-36'} overflow-auto rounded bg-surface-alt p-2 font-mono text-[10px] leading-5 text-muted whitespace-pre-wrap break-words`;
|
||||
`${kind === 'output' ? 'max-h-72' : 'max-h-36'} overflow-auto rounded-sm bg-surface-alt p-2 font-mono text-[10px] leading-5 text-muted whitespace-pre-wrap wrap-break-word`;
|
||||
|
||||
const ToolDetailsPanel: Component<ToolDetailsPanelProps> = (props) => {
|
||||
const [copiedDetail, setCopiedDetail] = createSignal<ToolDetailKind | null>(null);
|
||||
@@ -450,7 +450,7 @@ export const ToolExecutionBlock: Component<ToolExecutionBlockProps> = (props) =>
|
||||
</Show>
|
||||
<Show when={hiddenOutputBadgeSummary()}>
|
||||
<span
|
||||
class="shrink-0 rounded border border-border-subtle bg-surface px-1.5 py-0.5 text-[9px] font-medium"
|
||||
class="shrink-0 rounded-sm border border-border-subtle bg-surface px-1.5 py-0.5 text-[9px] font-medium"
|
||||
title="Open the completed turn to inspect tool output"
|
||||
aria-label={`Tool output available: ${hiddenOutputBadgeSummary()}`}
|
||||
>
|
||||
@@ -461,14 +461,14 @@ export const ToolExecutionBlock: Component<ToolExecutionBlockProps> = (props) =>
|
||||
}
|
||||
>
|
||||
<div
|
||||
class="my-2 overflow-hidden rounded-md border border-border-subtle bg-surface text-[11px] shadow-sm"
|
||||
class="my-2 overflow-hidden rounded-md border border-border-subtle bg-surface text-[11px] shadow-xs"
|
||||
role={settlingFastCompletion() ? 'status' : undefined}
|
||||
aria-label={settlingFastCompletion() ? 'Assistant tool running' : undefined}
|
||||
>
|
||||
<div
|
||||
class={`flex min-w-0 items-start gap-2 px-2.5 py-2 ${
|
||||
hasDetails()
|
||||
? 'cursor-pointer transition-colors hover:bg-surface-hover focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:ring-inset'
|
||||
? 'cursor-pointer transition-colors hover:bg-surface-hover focus:outline-hidden focus:ring-2 focus:ring-blue-500/30 focus:ring-inset'
|
||||
: ''
|
||||
}`}
|
||||
role={hasDetails() ? 'button' : undefined}
|
||||
@@ -512,7 +512,7 @@ export const ToolExecutionBlock: Component<ToolExecutionBlockProps> = (props) =>
|
||||
{toolLabel()}
|
||||
</span>
|
||||
<span
|
||||
class={`shrink-0 rounded border px-1.5 py-0.5 text-[9px] font-medium ${statusPillClass()}`}
|
||||
class={`shrink-0 rounded-sm border px-1.5 py-0.5 text-[9px] font-medium ${statusPillClass()}`}
|
||||
>
|
||||
{statusLabel()}
|
||||
</span>
|
||||
@@ -536,7 +536,7 @@ export const ToolExecutionBlock: Component<ToolExecutionBlockProps> = (props) =>
|
||||
</Show>
|
||||
<Show when={hiddenOutputBadgeSummary()}>
|
||||
<span
|
||||
class="shrink-0 rounded border border-border-subtle bg-surface-alt px-1.5 py-0.5 text-[9px] font-medium text-muted"
|
||||
class="shrink-0 rounded-sm border border-border-subtle bg-surface-alt px-1.5 py-0.5 text-[9px] font-medium text-muted"
|
||||
title="Open tool details to inspect output"
|
||||
aria-label={`Tool output available: ${hiddenOutputBadgeSummary()}`}
|
||||
>
|
||||
@@ -570,7 +570,7 @@ export const ToolExecutionBlock: Component<ToolExecutionBlockProps> = (props) =>
|
||||
Output preview
|
||||
</div>
|
||||
<pre
|
||||
class="px-3 pb-2 pt-1 font-mono text-[11px] leading-5 text-base-content whitespace-pre-wrap break-words"
|
||||
class="px-3 pb-2 pt-1 font-mono text-[11px] leading-5 text-base-content whitespace-pre-wrap wrap-break-word"
|
||||
aria-label="Tool output preview"
|
||||
>
|
||||
{outputPreview()}
|
||||
@@ -676,7 +676,7 @@ export const PendingToolBlock: Component<PendingToolBlockProps> = (props) => {
|
||||
<div
|
||||
class={`px-2.5 py-2 ${
|
||||
hasDetails()
|
||||
? 'cursor-pointer transition-colors hover:bg-blue-100/60 focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:ring-inset dark:hover:bg-blue-950/30'
|
||||
? 'cursor-pointer transition-colors hover:bg-blue-100/60 focus:outline-hidden focus:ring-2 focus:ring-blue-500/30 focus:ring-inset dark:hover:bg-blue-950/30'
|
||||
: ''
|
||||
}`}
|
||||
role={hasDetails() ? 'button' : undefined}
|
||||
@@ -731,8 +731,8 @@ export const PendingToolBlock: Component<PendingToolBlockProps> = (props) => {
|
||||
</div>
|
||||
|
||||
<Show when={progressText()}>
|
||||
<div class="mt-1 min-w-0 pl-[calc(0.875rem+0.5rem)] text-[10px] leading-snug text-muted">
|
||||
<span class="block whitespace-pre-wrap break-words" title={progressText()}>
|
||||
<div class="mt-1 min-w-0 pl-5.5 text-[10px] leading-snug text-muted">
|
||||
<span class="block whitespace-pre-wrap wrap-break-word" title={progressText()}>
|
||||
{progressText()}
|
||||
</span>
|
||||
</div>
|
||||
@@ -799,7 +799,7 @@ export const ToolCancellationBlock: Component<ToolCancellationBlockProps> = (pro
|
||||
<div
|
||||
class={`flex min-w-0 items-start gap-2 px-2.5 py-2 ${
|
||||
hasDetails()
|
||||
? 'cursor-pointer transition-colors hover:bg-amber-100/60 focus:outline-none focus:ring-2 focus:ring-amber-500/30 focus:ring-inset dark:hover:bg-amber-950/30'
|
||||
? 'cursor-pointer transition-colors hover:bg-amber-100/60 focus:outline-hidden focus:ring-2 focus:ring-amber-500/30 focus:ring-inset dark:hover:bg-amber-950/30'
|
||||
: ''
|
||||
}`}
|
||||
role={hasDetails() ? 'button' : undefined}
|
||||
@@ -822,7 +822,7 @@ export const ToolCancellationBlock: Component<ToolCancellationBlockProps> = (pro
|
||||
<span class="shrink-0 font-mono text-[9px] font-semibold uppercase tracking-wider text-muted">
|
||||
{toolLabel()}
|
||||
</span>
|
||||
<span class="shrink-0 rounded border border-amber-200 bg-amber-100 px-1.5 py-0.5 text-[9px] font-medium text-amber-800 dark:border-amber-900/60 dark:bg-amber-950/40 dark:text-amber-200">
|
||||
<span class="shrink-0 rounded-sm border border-amber-200 bg-amber-100 px-1.5 py-0.5 text-[9px] font-medium text-amber-800 dark:border-amber-900/60 dark:bg-amber-950/40 dark:text-amber-200">
|
||||
skipped
|
||||
</span>
|
||||
<Show when={hasDetails()}>
|
||||
@@ -839,7 +839,7 @@ export const ToolCancellationBlock: Component<ToolCancellationBlockProps> = (pro
|
||||
<ToolCommandPreview preview={commandPreview()} />
|
||||
</Show>
|
||||
<div class="mt-1 min-w-0 text-[10px] leading-snug text-muted">
|
||||
<span class="block whitespace-pre-wrap break-words">{reason()}</span>
|
||||
<span class="block whitespace-pre-wrap wrap-break-word">{reason()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -879,7 +879,7 @@ export const PendingToolsList: Component<PendingToolsListProps> = (props) => {
|
||||
<Show when={shouldCollapse() && !expanded()}>
|
||||
<button
|
||||
onClick={() => setExpanded(true)}
|
||||
class="w-full mt-0.5 py-1 text-[10px] text-muted hover:text-base-content hover:bg-surface-hover rounded text-center font-medium"
|
||||
class="w-full mt-0.5 py-1 text-[10px] text-muted hover:text-base-content hover:bg-surface-hover rounded-sm text-center font-medium"
|
||||
>
|
||||
+ {hiddenCount()} more tools running...
|
||||
</button>
|
||||
@@ -920,7 +920,7 @@ export const ToolExecutionsList: Component<ToolExecutionsListProps> = (props) =>
|
||||
<Show when={shouldCollapse() && !showAll()}>
|
||||
<button
|
||||
onClick={() => setShowAll(true)}
|
||||
class="w-full mt-0.5 py-1 text-[10px] text-muted hover:bg-surface-hover rounded text-center font-medium"
|
||||
class="w-full mt-0.5 py-1 text-[10px] text-muted hover:bg-surface-hover rounded-sm text-center font-medium"
|
||||
>
|
||||
+ {hiddenCount()} more tools ({stats().success} ✓ / {stats().failed} ✗)
|
||||
</button>
|
||||
|
||||
@@ -665,16 +665,16 @@ describe('AIChat', () => {
|
||||
);
|
||||
expect(aiChatSource).toContain('<ActionIconButton');
|
||||
for (const retiredActionIconShell of [
|
||||
'flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-surface disabled:hover:text-muted',
|
||||
'flex-shrink-0 p-2 hover:text-base-content rounded-md hover:bg-surface-hover transition-colors',
|
||||
'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 disabled:cursor-wait disabled:opacity-70 dark:text-blue-200 dark:hover:bg-blue-900/60',
|
||||
'rounded p-1 text-muted opacity-0 transition-opacity hover:bg-blue-100 hover:text-blue-600 focus:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100 dark:hover:bg-blue-900 dark:hover:text-blue-300',
|
||||
'order-2 flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-md hover:text-base-content hover:bg-surface-hover transition-colors sm:order-none',
|
||||
'flex h-7 w-7 items-center justify-center rounded-md border border-amber-200 bg-surface text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900 dark:border-amber-800 dark:bg-amber-950/60 dark:text-amber-200 dark:hover:bg-amber-900',
|
||||
'flex h-7 w-7 items-center justify-center rounded-md text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900 dark:text-amber-200 dark:hover:bg-amber-900',
|
||||
'inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 dark:text-blue-200 dark:hover:bg-blue-900/60',
|
||||
'flex h-9 w-9 items-center justify-center rounded-md bg-blue-600 text-white shadow-sm transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-45',
|
||||
'flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500/30',
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-surface disabled:hover:text-muted',
|
||||
'shrink-0 p-2 hover:text-base-content rounded-md hover:bg-surface-hover transition-colors',
|
||||
'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 disabled:cursor-wait disabled:opacity-70 dark:text-blue-200 dark:hover:bg-blue-900/60',
|
||||
'rounded-sm p-1 text-muted opacity-0 transition-opacity hover:bg-blue-100 hover:text-blue-600 focus:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100 dark:hover:bg-blue-900/25 dark:hover:text-blue-300',
|
||||
'order-2 flex h-9 w-9 shrink-0 items-center justify-center rounded-md hover:text-base-content hover:bg-surface-hover transition-colors sm:order-0',
|
||||
'flex h-7 w-7 items-center justify-center rounded-md border border-amber-200 bg-surface text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900/25 dark:border-amber-800 dark:bg-amber-950/60 dark:text-amber-200 dark:hover:bg-amber-900/25',
|
||||
'flex h-7 w-7 items-center justify-center rounded-md text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900/25 dark:text-amber-200 dark:hover:bg-amber-900/25',
|
||||
'inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 dark:text-blue-200 dark:hover:bg-blue-900/60',
|
||||
'flex h-9 w-9 items-center justify-center rounded-md bg-blue-600 text-white shadow-xs transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-45',
|
||||
'flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus:ring-2 focus:ring-blue-500/30',
|
||||
'disabled:opacity-45',
|
||||
]) {
|
||||
expect(aiChatSource).not.toContain(retiredActionIconShell);
|
||||
@@ -2040,12 +2040,12 @@ describe('AIChat', () => {
|
||||
const panel = screen.getByText('Pulse Assistant').closest('[data-layout-mode]');
|
||||
expect(panel).toHaveAttribute('data-layout-mode', 'overlay');
|
||||
expect(panel).toHaveClass('top-0');
|
||||
expect(panel).toHaveClass('bottom-[var(--pulse-mobile-nav-height)]');
|
||||
expect(panel).toHaveClass('bottom-(--pulse-mobile-nav-height)');
|
||||
expect(panel).toHaveClass('h-auto');
|
||||
expect(panel).not.toHaveClass('inset-y-0');
|
||||
expect(panel).not.toHaveClass('h-full');
|
||||
const backdrop = screen.getByRole('button', { name: 'Close Pulse Assistant backdrop' });
|
||||
expect(backdrop).toHaveClass('bottom-[var(--pulse-mobile-nav-height)]');
|
||||
expect(backdrop).toHaveClass('bottom-(--pulse-mobile-nav-height)');
|
||||
expect(backdrop).not.toHaveClass('inset-0');
|
||||
expect(screen.queryByTitle('Collapse Pulse Assistant')).not.toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Close Pulse Assistant')).toBeInTheDocument();
|
||||
|
||||
@@ -134,10 +134,10 @@ describe('MessageItem', () => {
|
||||
expect(messageItemSource).not.toContain('lucide-solid/icons/copy');
|
||||
expect(messageItemSource).not.toContain("lucide-solid/icons/check';");
|
||||
expect(messageItemSource).not.toContain(
|
||||
'mt-1 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border-subtle bg-surface text-muted opacity-0 shadow-sm transition-opacity hover:text-base-content',
|
||||
'mt-1 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border-subtle bg-surface text-muted opacity-0 shadow-xs transition-opacity hover:text-base-content',
|
||||
);
|
||||
expect(messageItemSource).not.toContain(
|
||||
'inline-flex h-5 w-5 items-center justify-center rounded text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 focus:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-blue-500/30',
|
||||
'inline-flex h-5 w-5 items-center justify-center rounded-sm text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 focus:bg-blue-100 focus:outline-hidden focus:ring-2 focus:ring-blue-500/30',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ describe('ToolExecutionBlock', () => {
|
||||
expect(toolExecutionBlockSource).not.toContain('lucide-solid/icons/copy');
|
||||
expect(toolExecutionBlockSource).not.toContain("lucide-solid/icons/check';");
|
||||
expect(toolExecutionBlockSource).not.toContain(
|
||||
'inline-flex h-6 w-6 shrink-0 items-center justify-center rounded border border-border-subtle bg-surface text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500/30',
|
||||
'inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-sm border border-border-subtle bg-surface text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus:ring-2 focus:ring-blue-500/30',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -432,7 +432,7 @@ describe('ToolExecutionBlock', () => {
|
||||
.closest('[data-testid="tool-input-summary"]');
|
||||
expect(summary).toBeInTheDocument();
|
||||
expect(summary?.className).toContain('whitespace-pre-wrap');
|
||||
expect(summary?.className).toContain('break-words');
|
||||
expect(summary?.className).toContain('wrap-break-word');
|
||||
expect(summary?.className).not.toContain('truncate');
|
||||
});
|
||||
|
||||
@@ -755,7 +755,7 @@ describe('ToolExecutionBlock', () => {
|
||||
expect(detailBlocks.length).toBeGreaterThanOrEqual(2);
|
||||
for (const block of detailBlocks) {
|
||||
expect(block.className).toContain('whitespace-pre-wrap');
|
||||
expect(block.className).toContain('break-words');
|
||||
expect(block.className).toContain('wrap-break-word');
|
||||
expect(block.className).not.toContain('break-all');
|
||||
}
|
||||
});
|
||||
@@ -1022,7 +1022,7 @@ describe('PendingToolBlock', () => {
|
||||
.closest('[data-testid="tool-input-summary"]');
|
||||
expect(summary).toBeInTheDocument();
|
||||
expect(summary?.className).toContain('whitespace-pre-wrap');
|
||||
expect(summary?.className).toContain('break-words');
|
||||
expect(summary?.className).toContain('wrap-break-word');
|
||||
expect(summary?.className).not.toContain('truncate');
|
||||
});
|
||||
|
||||
@@ -1142,7 +1142,7 @@ describe('PendingToolBlock', () => {
|
||||
expect(progress).toBeInTheDocument();
|
||||
expect(progress).toHaveAttribute('title', 'Running command.');
|
||||
expect(progress.className).not.toContain('hidden');
|
||||
expect(progress.className).toContain('break-words');
|
||||
expect(progress.className).toContain('wrap-break-word');
|
||||
expect(progress.className).not.toContain('truncate');
|
||||
});
|
||||
|
||||
|
||||
@@ -2275,14 +2275,14 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
onCleanup(stopPanelResize);
|
||||
const rootClassName = createMemo(() => {
|
||||
if (isOverlayLayout()) {
|
||||
return `fixed top-0 right-0 bottom-[var(--pulse-mobile-nav-height)] z-50 flex h-auto w-full flex-col bg-surface transition-transform duration-300 sm:w-[560px] sm:max-w-[calc(100vw-1rem)] ${
|
||||
return `fixed top-0 right-0 bottom-(--pulse-mobile-nav-height) z-50 flex h-auto w-full flex-col bg-surface transition-transform duration-300 sm:w-[560px] sm:max-w-[calc(100vw-1rem)] ${
|
||||
isOpen()
|
||||
? 'translate-x-0 overflow-visible border-l border-border shadow-2xl'
|
||||
: 'translate-x-full overflow-hidden border-l-0'
|
||||
}`;
|
||||
}
|
||||
|
||||
return `relative flex h-full flex-shrink-0 flex-col bg-surface transition-all duration-300 ${
|
||||
return `relative flex h-full shrink-0 flex-col bg-surface transition-all duration-300 ${
|
||||
isOpen()
|
||||
? 'w-full overflow-visible border-l border-border sm:w-[560px]'
|
||||
: 'w-0 overflow-hidden border-l-0'
|
||||
@@ -2758,7 +2758,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
const currentStatusKind = createMemo(() => currentStatus()?.type);
|
||||
const currentStatusRowClass = createMemo(() => {
|
||||
if (currentStatusKind() === 'retrying') {
|
||||
return 'bg-amber-50/80 text-amber-900 dark:bg-amber-950/25 dark:text-amber-100';
|
||||
return 'bg-amber-50/80 text-amber-900/25 dark:bg-amber-950/25 dark:text-amber-100';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
@@ -4274,7 +4274,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={isOpen() && isOverlayLayout()}>
|
||||
<button
|
||||
type="button"
|
||||
class="fixed inset-x-0 top-0 bottom-[var(--pulse-mobile-nav-height)] z-40 bg-slate-950/45 backdrop-blur-[1px]"
|
||||
class="fixed inset-x-0 top-0 bottom-(--pulse-mobile-nav-height) z-40 bg-slate-950/45 backdrop-blur-[1px]"
|
||||
onClick={props.onClose}
|
||||
aria-label="Close Pulse Assistant backdrop"
|
||||
/>
|
||||
@@ -4287,7 +4287,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={isOpen()}>
|
||||
{/* Drag handle to resize the panel (desktop widths only) */}
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 z-50 hidden w-1.5 cursor-col-resize touch-none hover:bg-blue-400/40 focus:bg-blue-400/60 focus:outline-none sm:block"
|
||||
class="absolute inset-y-0 left-0 z-50 hidden w-1.5 cursor-col-resize touch-none hover:bg-blue-400/40 focus:bg-blue-400/60 focus:outline-hidden sm:block"
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label="Resize Assistant panel"
|
||||
@@ -4308,7 +4308,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
aria-label={AI_CHAT_COLLAPSE_TITLE}
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 flex-shrink-0"
|
||||
class="h-5 w-5 shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -4336,14 +4336,14 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
</h2>
|
||||
|
||||
<div
|
||||
class="order-3 flex w-full min-w-0 flex-wrap items-center gap-1.5 sm:order-none sm:w-auto sm:flex-none"
|
||||
class="order-3 flex w-full min-w-0 flex-wrap items-center gap-1.5 sm:order-0 sm:w-auto sm:flex-none"
|
||||
data-testid="assistant-header-actions"
|
||||
>
|
||||
{/* New chat */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleNewConversation}
|
||||
class="flex flex-shrink-0 items-center gap-1.5 px-2.5 py-1.5 text-[11px] text-muted hover:text-base-content rounded-md border border-border hover:border-border bg-surface transition-colors"
|
||||
class="flex shrink-0 items-center gap-1.5 px-2.5 py-1.5 text-[11px] text-muted hover:text-base-content rounded-md border border-border hover:border-border bg-surface transition-colors"
|
||||
title={AI_CHAT_NEW_SESSION_BUTTON_TITLE}
|
||||
aria-label={AI_CHAT_NEW_SESSION_BUTTON_TITLE}
|
||||
>
|
||||
@@ -4507,7 +4507,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
|
||||
<Show when={showSessions()}>
|
||||
<div
|
||||
class="fixed w-[min(20rem,calc(100vw-1rem))] bg-surface rounded-md shadow-sm border border-border z-[9999] overflow-hidden"
|
||||
class="fixed w-[min(20rem,calc(100vw-1rem))] bg-surface rounded-md shadow-xs border border-border z-9999 overflow-hidden"
|
||||
role="dialog"
|
||||
aria-label={AI_CHAT_SESSION_MENU_TITLE}
|
||||
style={{
|
||||
@@ -4519,7 +4519,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleNewConversation}
|
||||
class="w-full px-3 py-2.5 text-left text-sm flex items-center gap-2 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900 border-b border-border"
|
||||
class="w-full px-3 py-2.5 text-left text-sm flex items-center gap-2 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/25 border-b border-border"
|
||||
aria-label={AI_CHAT_NEW_SESSION_MENU_ARIA_LABEL}
|
||||
>
|
||||
<PlusIcon class="h-4 w-4" aria-hidden="true" />
|
||||
@@ -4575,7 +4575,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<div
|
||||
class={`group relative flex items-start gap-2 px-3 py-2.5 hover:bg-surface-hover focus-within:bg-surface-hover ${
|
||||
isSessionCurrent(session.id)
|
||||
? 'bg-blue-50 dark:bg-blue-900'
|
||||
? 'bg-blue-50 dark:bg-blue-900/25'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
@@ -4590,7 +4590,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
role="option"
|
||||
aria-selected={isSessionCurrent(session.id)}
|
||||
aria-label={getSessionPickerOptionLabel(session)}
|
||||
class="min-w-0 flex-1 text-left focus:outline-none"
|
||||
class="min-w-0 flex-1 text-left focus:outline-hidden"
|
||||
onClick={() => handleLoadSession(session.id)}
|
||||
onKeyDown={(event) =>
|
||||
handleSessionOptionKeyDown(event, session.id)
|
||||
@@ -4601,7 +4601,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
{session.title || 'Untitled'}
|
||||
</div>
|
||||
<Show when={isSessionWorking(session.id)}>
|
||||
<span class="inline-flex shrink-0 items-center gap-1 rounded border border-blue-200 bg-blue-100 px-1.5 py-0.5 text-[10px] font-semibold text-blue-700 dark:border-blue-800 dark:bg-blue-950/60 dark:text-blue-200">
|
||||
<span class="inline-flex shrink-0 items-center gap-1 rounded-sm border border-blue-200 bg-blue-100 px-1.5 py-0.5 text-[10px] font-semibold text-blue-700 dark:border-blue-800 dark:bg-blue-950/60 dark:text-blue-200">
|
||||
<LoaderCircleIcon class="h-3 w-3 animate-spin" />
|
||||
Working
|
||||
</span>
|
||||
@@ -4612,7 +4612,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
!isSessionWorking(session.id)
|
||||
}
|
||||
>
|
||||
<span class="shrink-0 rounded border border-blue-200 bg-blue-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-blue-700 dark:border-blue-800 dark:bg-blue-950/60 dark:text-blue-200">
|
||||
<span class="shrink-0 rounded-sm border border-blue-200 bg-blue-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-blue-700 dark:border-blue-800 dark:bg-blue-950/60 dark:text-blue-200">
|
||||
Current
|
||||
</span>
|
||||
</Show>
|
||||
@@ -4635,15 +4635,15 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={session.handoff_summary}>
|
||||
{(summary) => (
|
||||
<div class="mt-1 flex max-w-full flex-wrap gap-1.5">
|
||||
<span class="rounded border border-blue-200 bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-200">
|
||||
<span class="rounded-sm border border-blue-200 bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 dark:border-blue-800 dark:bg-blue-950/25 dark:text-blue-200">
|
||||
{getSessionHandoffSourceLabel(summary())}
|
||||
</span>
|
||||
<span class="rounded border border-border bg-surface-alt px-1.5 py-0.5 text-[10px] text-muted">
|
||||
<span class="rounded-sm border border-border bg-surface-alt px-1.5 py-0.5 text-[10px] text-muted">
|
||||
{getSessionHandoffBadgeLabel(summary())}
|
||||
</span>
|
||||
<Show when={formatSessionHandoffStatus(summary())}>
|
||||
{(status) => (
|
||||
<span class="max-w-full truncate rounded border border-border bg-surface-alt px-1.5 py-0.5 text-[10px] text-muted">
|
||||
<span class="max-w-full truncate rounded-sm border border-border bg-surface-alt px-1.5 py-0.5 text-[10px] text-muted">
|
||||
{status()}
|
||||
</span>
|
||||
)}
|
||||
@@ -4672,7 +4672,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
maxLength={AI_CHAT_SESSION_TITLE_MAX_LENGTH}
|
||||
disabled={sessionRenameSaving()}
|
||||
aria-label={`New title for ${session.title || 'Untitled'}`}
|
||||
class="min-w-0 flex-1 rounded border border-blue-300 bg-surface px-2 py-1 text-sm font-medium text-base-content outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 disabled:opacity-70 dark:border-blue-800"
|
||||
class="min-w-0 flex-1 rounded-sm border border-blue-300 bg-surface px-2 py-1 text-sm font-medium text-base-content outline-hidden focus:border-blue-500 focus:ring-1 focus:ring-blue-500 disabled:opacity-70 dark:border-blue-800"
|
||||
/>
|
||||
<ActionIconButton
|
||||
type="submit"
|
||||
@@ -4775,7 +4775,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
}}
|
||||
tone="neutral"
|
||||
size="lg"
|
||||
class="order-2 sm:order-none"
|
||||
class="order-2 sm:order-0"
|
||||
title={AI_CHAT_CLOSE_LABEL}
|
||||
label={AI_CHAT_CLOSE_LABEL}
|
||||
data-testid="assistant-close-button"
|
||||
@@ -4787,7 +4787,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={transcriptCopyFallback()}>
|
||||
{(fallback) => (
|
||||
<section
|
||||
class="border-b border-amber-200 bg-amber-50 px-4 py-3 text-amber-950 dark:border-amber-900 dark:bg-amber-950/30 dark:text-amber-100"
|
||||
class="border-b border-amber-200 bg-amber-50 px-4 py-3 text-amber-950/25 dark:border-amber-900/25 dark:bg-amber-950/30 dark:text-amber-100"
|
||||
aria-label={AI_CHAT_TRANSCRIPT_FALLBACK_TITLE}
|
||||
>
|
||||
<div class="mb-2 flex items-center justify-between gap-2">
|
||||
@@ -4815,7 +4815,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
</div>
|
||||
<textarea
|
||||
ref={transcriptFallbackTextareaRef}
|
||||
class="h-36 w-full resize-y rounded-md border border-amber-200 bg-surface px-2 py-2 font-mono text-[11px] leading-relaxed text-base-content outline-none focus:border-amber-400 dark:border-amber-800 dark:bg-surface"
|
||||
class="h-36 w-full resize-y rounded-md border border-amber-200 bg-surface px-2 py-2 font-mono text-[11px] leading-relaxed text-base-content outline-hidden focus:border-amber-400 dark:border-amber-800 dark:bg-surface"
|
||||
readonly
|
||||
value={fallback().transcript}
|
||||
aria-label={AI_CHAT_TRANSCRIPT_FALLBACK_TEXTAREA_LABEL}
|
||||
@@ -4829,17 +4829,17 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<section
|
||||
class={`border-b px-4 py-2.5 text-[11px] ${
|
||||
presentation().tone === 'checking'
|
||||
? 'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-200'
|
||||
? 'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950/25 dark:text-blue-200'
|
||||
: presentation().tone === 'ready'
|
||||
? 'border-emerald-200 bg-emerald-50 text-emerald-800 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-100'
|
||||
: 'border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-100'
|
||||
? 'border-emerald-200 bg-emerald-50 text-emerald-800 dark:border-emerald-800 dark:bg-emerald-950/25 dark:text-emerald-100'
|
||||
: 'border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-950/25 dark:text-amber-100'
|
||||
}`}
|
||||
aria-label="Assistant selected model route status"
|
||||
>
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div class="flex min-w-0 items-start gap-2.5">
|
||||
<span
|
||||
class={`mt-1 h-2 w-2 flex-shrink-0 rounded-full ${
|
||||
class={`mt-1 h-2 w-2 shrink-0 rounded-full ${
|
||||
presentation().tone === 'checking'
|
||||
? 'bg-blue-500 dark:bg-blue-300'
|
||||
: presentation().tone === 'ready'
|
||||
@@ -4879,7 +4879,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={switchToProviderReadinessAlternative}
|
||||
class="inline-flex max-w-[11rem] items-center gap-1.5 rounded-md border border-current/20 bg-surface px-2 py-1 text-[10px] font-medium text-base-content hover:bg-surface-hover"
|
||||
class="inline-flex max-w-44 items-center gap-1.5 rounded-md border border-current/20 bg-surface px-2 py-1 text-[10px] font-medium text-base-content hover:bg-surface-hover"
|
||||
aria-label={providerReadinessAlternativeButtonLabel()}
|
||||
title={alternative().label}
|
||||
>
|
||||
@@ -4926,10 +4926,10 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
|
||||
{/* Discovery hint - show when discovery is disabled */}
|
||||
<Show when={discoveryEnabled() === false && !discoveryHintDismissed()}>
|
||||
<div class="px-4 py-2 border-b border-cyan-200 dark:border-cyan-800 bg-cyan-50 dark:bg-cyan-900 flex items-center justify-between gap-3 text-[11px] text-cyan-700 dark:text-cyan-200">
|
||||
<div class="px-4 py-2 border-b border-cyan-200 dark:border-cyan-800 bg-cyan-50 dark:bg-cyan-900/25 flex items-center justify-between gap-3 text-[11px] text-cyan-700 dark:text-cyan-200">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
class="w-4 h-4 text-cyan-500 dark:text-cyan-400 flex-shrink-0"
|
||||
class="w-4 h-4 text-cyan-500 dark:text-cyan-400 shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -4984,7 +4984,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={contextBriefing()!.actionHref && contextBriefing()!.actionLabel}>
|
||||
<a
|
||||
href={contextBriefing()!.actionHref}
|
||||
class="mt-2 inline-flex rounded border border-border bg-surface px-2 py-1 text-[11px] font-medium text-base-content hover:bg-surface-alt"
|
||||
class="mt-2 inline-flex rounded-sm border border-border bg-surface px-2 py-1 text-[11px] font-medium text-base-content hover:bg-surface-alt"
|
||||
>
|
||||
{contextBriefing()!.actionLabel}
|
||||
</a>
|
||||
@@ -5038,7 +5038,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
}
|
||||
>
|
||||
<div
|
||||
class="mb-2 overflow-hidden rounded-md border border-border bg-surface-alt text-base-content shadow-sm"
|
||||
class="mb-2 overflow-hidden rounded-md border border-border bg-surface-alt text-base-content shadow-xs"
|
||||
data-testid="assistant-activity-dock"
|
||||
>
|
||||
<Show when={currentStatus()}>
|
||||
@@ -5130,7 +5130,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => updateControlLevel('controlled')}
|
||||
class="inline-flex shrink-0 items-center rounded-md border border-red-200 bg-surface px-2 py-1 text-[10px] font-medium text-red-700 transition-colors hover:bg-red-50 hover:text-red-900 dark:border-red-800 dark:bg-surface dark:text-red-200 dark:hover:bg-red-950/40"
|
||||
class="inline-flex shrink-0 items-center rounded-md border border-red-200 bg-surface px-2 py-1 text-[10px] font-medium text-red-700 transition-colors hover:bg-red-50 hover:text-red-900/25 dark:border-red-800 dark:bg-surface dark:text-red-200 dark:hover:bg-red-950/40"
|
||||
aria-label={AI_CHAT_SWITCH_TO_APPROVAL_LABEL}
|
||||
>
|
||||
Switch to Ask first
|
||||
@@ -5194,7 +5194,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
};
|
||||
return (
|
||||
<div
|
||||
class="flex min-h-7 items-center gap-2 rounded-md bg-white/70 px-2 py-1 text-xs text-blue-900 outline-none transition-colors focus:bg-white focus:ring-2 focus:ring-blue-500/40 dark:bg-blue-900/30 dark:text-blue-100 dark:focus:bg-blue-900/50"
|
||||
class="flex min-h-7 items-center gap-2 rounded-md bg-white/70 px-2 py-1 text-xs text-blue-900/25 outline-hidden transition-colors focus:bg-white focus:ring-2 focus:ring-blue-500/40 dark:bg-blue-900/30 dark:text-blue-100 dark:focus:bg-blue-900/50"
|
||||
classList={{
|
||||
'bg-blue-50 ring-2 ring-blue-500/60 dark:bg-blue-800/50':
|
||||
queuedFollowUpCommandTargetId() === queued.id,
|
||||
@@ -5372,7 +5372,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
class="relative"
|
||||
>
|
||||
<div
|
||||
class={`relative flex min-h-[56px] items-end rounded-lg border bg-surface-alt shadow-sm transition-colors ${
|
||||
class={`relative flex min-h-[56px] items-end rounded-lg border bg-surface-alt shadow-xs transition-colors ${
|
||||
mentionActive() || slashCommandActive()
|
||||
? 'border-blue-400 ring-2 ring-blue-500/20'
|
||||
: 'border-border focus-within:border-blue-500 focus-within:ring-2 focus-within:ring-blue-500/20'
|
||||
@@ -5402,7 +5402,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
: undefined
|
||||
}
|
||||
rows={1}
|
||||
class="max-h-40 min-h-[54px] flex-1 resize-none bg-transparent px-3.5 py-3.5 pr-14 text-sm leading-5 text-base-content placeholder-slate-400 focus:outline-none"
|
||||
class="max-h-40 min-h-[54px] flex-1 resize-none bg-transparent px-3.5 py-3.5 pr-14 text-sm leading-5 text-base-content placeholder-slate-400 focus:outline-hidden"
|
||||
/>
|
||||
<div data-mention-autocomplete>
|
||||
<MentionAutocomplete
|
||||
@@ -5469,7 +5469,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
aria-label="Assistant selected model route health"
|
||||
aria-live="polite"
|
||||
title={health().title}
|
||||
class={`inline-flex h-7 max-w-[11rem] shrink-0 items-center gap-1.5 rounded-md border px-2 text-[10px] font-medium ${health().className}`}
|
||||
class={`inline-flex h-7 max-w-44 shrink-0 items-center gap-1.5 rounded-md border px-2 text-[10px] font-medium ${health().className}`}
|
||||
data-testid="assistant-provider-route-health"
|
||||
>
|
||||
<span
|
||||
@@ -5487,7 +5487,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
aria-label="Assistant capability availability"
|
||||
aria-live="polite"
|
||||
title={health().title}
|
||||
class={`inline-flex h-7 max-w-[9rem] shrink-0 items-center gap-1.5 rounded-md border px-2 text-[10px] font-medium ${health().className}`}
|
||||
class={`inline-flex h-7 max-w-36 shrink-0 items-center gap-1.5 rounded-md border px-2 text-[10px] font-medium ${health().className}`}
|
||||
data-testid="assistant-surface-tools-health"
|
||||
>
|
||||
<WrenchIcon
|
||||
@@ -5537,7 +5537,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
ref={controlModeButtonRef}
|
||||
onClick={toggleControlMenu}
|
||||
onKeyDown={handleControlModeTriggerKeyDown}
|
||||
class={`flex flex-shrink-0 items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium rounded-md border transition-colors ${controlPresentation().pillClassName} ${controlSaving() ? 'opacity-70 cursor-wait' : 'hover:opacity-90'}`}
|
||||
class={`flex shrink-0 items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium rounded-md border transition-colors ${controlPresentation().pillClassName} ${controlSaving() ? 'opacity-70 cursor-wait' : 'hover:opacity-90'}`}
|
||||
title={AI_CHAT_CONTROL_MODE_LABEL}
|
||||
aria-label={`${AI_CHAT_CONTROL_MODE_LABEL}: ${controlPresentation().label}`}
|
||||
aria-haspopup="menu"
|
||||
@@ -5560,7 +5560,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
|
||||
<Show when={showControlMenu()}>
|
||||
<div
|
||||
class="absolute bottom-full left-0 z-50 mb-2 w-60 overflow-hidden rounded-md border border-border bg-surface shadow-sm"
|
||||
class="absolute bottom-full left-0 z-50 mb-2 w-60 overflow-hidden rounded-md border border-border bg-surface shadow-xs"
|
||||
role="menu"
|
||||
aria-label={AI_CHAT_CONTROL_MODE_MENU_LABEL}
|
||||
>
|
||||
@@ -5629,7 +5629,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={lastAssistantTurnSummary()}>
|
||||
{(summary) => (
|
||||
<div
|
||||
class="flex h-5 min-w-0 items-center justify-end self-end text-[10px] font-medium text-muted sm:h-7 sm:max-w-[14rem] sm:shrink-0 sm:self-auto"
|
||||
class="flex h-5 min-w-0 items-center justify-end self-end text-[10px] font-medium text-muted sm:h-7 sm:max-w-56 sm:shrink-0 sm:self-auto"
|
||||
aria-label={summary().title}
|
||||
title={summary().title}
|
||||
>
|
||||
|
||||
@@ -1037,7 +1037,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
class={`p-3 transition-colors ${
|
||||
isPatrolFindingsSource()
|
||||
? `border-l-2 ${patrolSeverityAccentClass(finding.severity)} ${
|
||||
expandedId() === finding.id ? 'bg-surface-alt/80' : 'hover:bg-surface-hover'
|
||||
expandedId() === finding.id ? '' : 'hover:bg-surface-hover'
|
||||
}`
|
||||
: ''
|
||||
} ${
|
||||
@@ -1065,7 +1065,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
toggleExpanded();
|
||||
}
|
||||
}}
|
||||
class="min-w-0 flex-1 cursor-pointer rounded text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
|
||||
class="min-w-0 flex-1 cursor-pointer rounded-sm text-left focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary/40"
|
||||
>
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
{/* Status badge for non-active findings */}
|
||||
@@ -1318,7 +1318,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleExpanded}
|
||||
class="rounded bg-amber-600 px-2 py-1 text-xs font-semibold text-white transition-colors hover:bg-amber-700"
|
||||
class="rounded-sm bg-amber-600 px-2 py-1 text-xs font-semibold text-white transition-colors hover:bg-amber-700"
|
||||
title={action().detail}
|
||||
>
|
||||
{action().label}
|
||||
@@ -1330,7 +1330,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<a
|
||||
href={action().href}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
class="inline-flex items-center rounded bg-blue-600 px-2 py-1 text-xs font-semibold text-white transition-colors hover:bg-blue-700"
|
||||
class="inline-flex items-center rounded-sm bg-blue-600 px-2 py-1 text-xs font-semibold text-white transition-colors hover:bg-blue-700"
|
||||
>
|
||||
{action().label}
|
||||
</a>
|
||||
@@ -1406,7 +1406,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
aria-expanded={manageOpenId() === finding.id}
|
||||
aria-controls={`finding-${finding.id}-manage`}
|
||||
onClick={toggleFindingOptions}
|
||||
class="inline-flex min-h-11 items-center rounded border border-border bg-surface px-2 py-1 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 sm:min-h-0"
|
||||
class="inline-flex min-h-11 items-center rounded-sm border border-border bg-surface px-2 py-1 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary/40 sm:min-h-0"
|
||||
title="Resolve, dismiss, remember as expected, or create a rule"
|
||||
>
|
||||
Finding options
|
||||
@@ -1422,7 +1422,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
aria-expanded={expandedId() === finding.id}
|
||||
aria-controls={`finding-${finding.id}-details`}
|
||||
onClick={toggleExpanded}
|
||||
class="inline-flex min-h-11 items-center gap-1 rounded border border-border bg-surface px-2 py-1 text-xs font-medium text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 sm:min-h-0"
|
||||
class="inline-flex min-h-11 items-center gap-1 rounded-sm border border-border bg-surface px-2 py-1 text-xs font-medium text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary/40 sm:min-h-0"
|
||||
title={
|
||||
isPatrolFindingsSource()
|
||||
? expandedId() === finding.id
|
||||
@@ -1536,7 +1536,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={finding.previousResolvedFixSummary}>
|
||||
<p class="text-sm text-base-content mt-2 px-2 py-1 rounded border border-emerald-200 bg-emerald-50/40 dark:border-emerald-800 dark:bg-emerald-950/30">
|
||||
<p class="text-sm text-base-content mt-2 px-2 py-1 rounded-sm border border-emerald-200 bg-emerald-50/40 dark:border-emerald-800 dark:bg-emerald-950/30">
|
||||
<span class="font-medium text-emerald-800 dark:text-emerald-300">
|
||||
Last time this resolved:
|
||||
</span>{' '}
|
||||
@@ -1554,7 +1554,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
</Show>
|
||||
|
||||
<Show when={relatedFindings.length > 0}>
|
||||
<div class="mt-3 rounded border border-border bg-surface-alt p-2">
|
||||
<div class="mt-3 rounded-sm border border-border bg-surface-alt p-2">
|
||||
<div class="text-xs font-medium text-base-content">Related issues</div>
|
||||
<div class="mt-2 space-y-1">
|
||||
<For each={relatedFindings}>
|
||||
@@ -1584,7 +1584,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
</Show>
|
||||
|
||||
<Show when={shouldShowFindingLifecycle(finding)}>
|
||||
<div class="mt-3 p-2 rounded border border-border bg-surface-alt">
|
||||
<div class="mt-3 p-2 rounded-sm border border-border bg-surface-alt">
|
||||
<div class="text-xs font-medium text-base-content mb-2">Lifecycle</div>
|
||||
<div class="space-y-1">
|
||||
<For each={[...(finding.lifecycle || [])].slice(-6).reverse()}>
|
||||
@@ -1630,11 +1630,11 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
{/* User note display / editor */}
|
||||
<Show when={editingNoteId() === finding.id}>
|
||||
<div
|
||||
class="mt-3 p-2 rounded border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900"
|
||||
class="mt-3 p-2 rounded-sm border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/25"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<textarea
|
||||
class="w-full text-sm rounded border border-border bg-surface text-base-content px-2 py-1.5 resize-none focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="w-full text-sm rounded-sm border border-border bg-surface text-base-content px-2 py-1.5 resize-none focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
rows={3}
|
||||
value={noteText()}
|
||||
onInput={(e) => setNoteText(e.currentTarget.value)}
|
||||
@@ -1644,7 +1644,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleSaveNote(finding, e)}
|
||||
class="px-3 py-1 text-xs font-medium rounded bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50"
|
||||
class="px-3 py-1 text-xs font-medium rounded-sm bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Save
|
||||
@@ -1652,7 +1652,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCancelNote}
|
||||
class="px-3 py-1 text-xs font-medium rounded border border-border hover:bg-surface-hover"
|
||||
class="px-3 py-1 text-xs font-medium rounded-sm border border-border hover:bg-surface-hover"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -1660,9 +1660,9 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={editingNoteId() !== finding.id && finding.userNote}>
|
||||
<div class="mt-3 p-2 rounded border border-border bg-surface-alt flex items-start gap-2">
|
||||
<div class="mt-3 p-2 rounded-sm border border-border bg-surface-alt flex items-start gap-2">
|
||||
<svg
|
||||
class="w-4 h-4 text-muted mt-0.5 flex-shrink-0"
|
||||
class="w-4 h-4 text-muted mt-0.5 shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -1678,7 +1678,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleStartEditNote(finding, e)}
|
||||
class="p-1 hover:text-base-content flex-shrink-0"
|
||||
class="p-1 hover:text-base-content shrink-0"
|
||||
title="Edit note"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -1702,7 +1702,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<a
|
||||
href={action().href}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
class="inline-flex items-center gap-1 rounded bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700"
|
||||
class="inline-flex items-center gap-1 rounded-sm bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700"
|
||||
>
|
||||
{action().label}
|
||||
</a>
|
||||
@@ -1715,7 +1715,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
e.stopPropagation();
|
||||
void openFindingInAssistant(finding);
|
||||
}}
|
||||
class="inline-flex items-center gap-1 rounded bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700"
|
||||
class="inline-flex items-center gap-1 rounded-sm bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700"
|
||||
title={getPrimaryAssistantFindingAction(finding).title}
|
||||
>
|
||||
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -1745,7 +1745,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
type="button"
|
||||
onClick={(e) => handleInvestigate(finding, e)}
|
||||
disabled={actionLoading() === finding.id}
|
||||
class="inline-flex items-center gap-1 rounded bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
class="inline-flex items-center gap-1 rounded-sm bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
title="Have Patrol investigate this finding"
|
||||
>
|
||||
{actionLoading() === finding.id ? 'Investigating…' : 'Investigate'}
|
||||
@@ -1773,7 +1773,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
props.onPatrolModeNudgeAction?.();
|
||||
}}
|
||||
disabled={props.patrolModeNudgeBusy}
|
||||
class="inline-flex items-center gap-1 rounded bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
class="inline-flex items-center gap-1 rounded-sm bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
title="Switch Patrol to Ask first so it can investigate and prepare fixes for your approval"
|
||||
>
|
||||
{props.patrolModeNudgeBusy ? 'Switching…' : modeNudge!.actionLabel}
|
||||
@@ -1788,10 +1788,10 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onToggle={(event) => setManageOpenId(event.currentTarget.open ? finding.id : null)}
|
||||
>
|
||||
<summary class="list-none cursor-pointer rounded border border-border bg-surface px-3 py-1.5 font-medium text-base-content hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40">
|
||||
<summary class="list-none cursor-pointer rounded-sm border border-border bg-surface px-3 py-1.5 font-medium text-base-content hover:bg-surface-hover focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary/40">
|
||||
Resolve or dismiss
|
||||
</summary>
|
||||
<div class="mt-1 flex min-w-48 flex-col gap-1 rounded border border-border bg-surface p-1 shadow-sm">
|
||||
<div class="mt-1 flex min-w-48 flex-col gap-1 rounded-sm border border-border bg-surface p-1 shadow-xs">
|
||||
<Show when={shouldShowAssistantManageAction}>
|
||||
<button
|
||||
type="button"
|
||||
@@ -1799,7 +1799,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
e.stopPropagation();
|
||||
void openFindingInAssistant(finding);
|
||||
}}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover"
|
||||
title={getPrimaryAssistantFindingAction(finding).title}
|
||||
>
|
||||
{getPrimaryAssistantFindingAction(finding).label}
|
||||
@@ -1809,7 +1809,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleStartEditNote(finding, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover"
|
||||
>
|
||||
Add Note
|
||||
</button>
|
||||
@@ -1817,7 +1817,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleCopyFindingSummary(finding, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover"
|
||||
>
|
||||
Copy summary
|
||||
</button>
|
||||
@@ -1826,7 +1826,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleAcknowledge(finding, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Acknowledge
|
||||
@@ -1836,7 +1836,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleResolve(finding, e)}
|
||||
class="rounded px-2 py-1 text-left text-emerald-700 hover:bg-emerald-50 disabled:opacity-50 dark:text-emerald-300 dark:hover:bg-emerald-900"
|
||||
class="rounded-sm px-2 py-1 text-left text-emerald-700 hover:bg-emerald-50 disabled:opacity-50 dark:text-emerald-300 dark:hover:bg-emerald-900/25"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Mark resolved
|
||||
@@ -1846,7 +1846,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleSnooze(finding, 1, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Snooze 1h
|
||||
@@ -1854,7 +1854,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleSnooze(finding, 24, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Snooze 24h
|
||||
@@ -1862,7 +1862,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleSnooze(finding, 168, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Snooze 7d
|
||||
@@ -1872,7 +1872,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleStartDismiss(finding, 'not_an_issue', e)}
|
||||
class="rounded px-2 py-1 text-left text-red-600 hover:bg-red-50 disabled:opacity-50 dark:text-red-400 dark:hover:bg-red-900"
|
||||
class="rounded-sm px-2 py-1 text-left text-red-600 hover:bg-red-50 disabled:opacity-50 dark:text-red-400 dark:hover:bg-red-900/25"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Dismiss: Not an issue
|
||||
@@ -1880,7 +1880,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleStartDismiss(finding, 'expected_behavior', e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Remember as expected
|
||||
@@ -1888,7 +1888,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleStartDismiss(finding, 'will_fix_later', e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Dismiss: Later
|
||||
@@ -1896,7 +1896,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleStartCreateRule(finding, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={
|
||||
actionLoading() === finding.id ||
|
||||
!getFindingSuppressionRuleScope(finding).canCreate
|
||||
@@ -1915,7 +1915,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleReopen(finding, e)}
|
||||
class="rounded px-2 py-1 text-left text-blue-700 hover:bg-blue-50 disabled:opacity-50 dark:text-blue-300 dark:hover:bg-blue-900"
|
||||
class="rounded-sm px-2 py-1 text-left text-blue-700 hover:bg-blue-50 disabled:opacity-50 dark:text-blue-300 dark:hover:bg-blue-900/25"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Reopen finding
|
||||
@@ -1931,7 +1931,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
visually but uses neutral surface styling — this isn't a
|
||||
dismissal, it's a permanent commitment. */}
|
||||
<Show when={creatingRuleForId() === finding.id}>
|
||||
<div class="mt-2 p-2 rounded border border-border bg-surface-alt">
|
||||
<div class="mt-2 p-2 rounded-sm border border-border bg-surface-alt">
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="text-xs font-medium text-base-content">
|
||||
Create suppression rule for{' '}
|
||||
@@ -1947,7 +1947,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
suppressions management surface.
|
||||
</p>
|
||||
<textarea
|
||||
class="w-full text-xs px-2 py-1.5 rounded border border-border bg-surface text-base-content resize-none focus:outline-none focus:ring-1 focus:ring-blue-400"
|
||||
class="w-full text-xs px-2 py-1.5 rounded-sm border border-border bg-surface text-base-content resize-none focus:outline-hidden focus:ring-1 focus:ring-blue-400"
|
||||
rows={2}
|
||||
value={createRuleDescription()}
|
||||
onInput={(e) => setCreateRuleDescription(e.currentTarget.value)}
|
||||
@@ -1958,7 +1958,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleConfirmCreateRule(finding, e)}
|
||||
class="px-3 py-1 text-xs font-medium rounded bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50"
|
||||
class="px-3 py-1 text-xs font-medium rounded-sm bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id || !createRuleDescription().trim()}
|
||||
>
|
||||
Create rule
|
||||
@@ -1966,7 +1966,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCancelCreateRule}
|
||||
class="px-3 py-1 text-xs font-medium rounded border border-border hover:bg-surface-hover"
|
||||
class="px-3 py-1 text-xs font-medium rounded-sm border border-border hover:bg-surface-hover"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -1978,7 +1978,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
"Pulse should know this is expected"), "Dismiss as" for the
|
||||
other reasons (past-looking, "make this go away"). */}
|
||||
<Show when={dismissingId() === finding.id}>
|
||||
<div class="mt-2 p-2 rounded border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900">
|
||||
<div class="mt-2 p-2 rounded-sm border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/25">
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="text-xs font-medium text-red-700 dark:text-red-300">
|
||||
{dismissReason() === 'expected_behavior'
|
||||
@@ -2024,7 +2024,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
</p>
|
||||
</Show>
|
||||
<textarea
|
||||
class="w-full text-xs px-2 py-1.5 rounded border border-border bg-surface text-base-content resize-none focus:outline-none focus:ring-1 focus:ring-red-400"
|
||||
class="w-full text-xs px-2 py-1.5 rounded-sm border border-border bg-surface text-base-content resize-none focus:outline-hidden focus:ring-1 focus:ring-red-400"
|
||||
rows={2}
|
||||
placeholder="Optional note (for learning context)..."
|
||||
value={dismissNote()}
|
||||
@@ -2036,14 +2036,14 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
type="button"
|
||||
onClick={(e) => handleConfirmDismiss(finding.id, e)}
|
||||
disabled={actionLoading() === finding.id}
|
||||
class="px-2.5 py-1 text-xs font-medium text-white bg-red-600 hover:bg-red-700 disabled:bg-red-400 rounded transition-colors"
|
||||
class="px-2.5 py-1 text-xs font-medium text-white bg-red-600 hover:bg-red-700 disabled:bg-red-400 rounded-sm transition-colors"
|
||||
>
|
||||
Confirm Dismiss
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCancelDismiss}
|
||||
class="px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded transition-colors"
|
||||
class="px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded-sm transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -2099,21 +2099,21 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<div class="mt-3 pt-3 border-t border-border-subtle">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<AlertCircleIcon class="h-4 w-4 flex-shrink-0 text-muted" />
|
||||
<AlertCircleIcon class="h-4 w-4 shrink-0 text-muted" />
|
||||
<span class="text-sm font-medium text-base-content">Assistant context</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleOpenPlanInAssistant(finding, plan(), e)}
|
||||
class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-xs font-medium rounded flex items-center justify-center gap-1.5"
|
||||
class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-xs font-medium rounded-sm flex items-center justify-center gap-1.5"
|
||||
>
|
||||
Ask Assistant
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleDismissPlan(plan(), e)}
|
||||
class="px-3 py-1.5 hover:bg-surface-hover text-muted text-xs font-medium rounded"
|
||||
class="px-3 py-1.5 hover:bg-surface-hover text-muted text-xs font-medium rounded-sm"
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
@@ -2145,7 +2145,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
fieldBaseClass="contents"
|
||||
value={sortBy()}
|
||||
onChange={(e) => setSortBy(e.currentTarget.value as 'severity' | 'time')}
|
||||
selectBaseClass="min-h-11 rounded border border-border bg-surface px-2 py-1 text-xs sm:min-h-0"
|
||||
selectBaseClass="min-h-11 rounded-sm border border-border bg-surface px-2 py-1 text-xs sm:min-h-0"
|
||||
>
|
||||
<option value="severity">By Severity</option>
|
||||
<option value="time">By Time</option>
|
||||
@@ -2243,7 +2243,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
class="divide-y divide-border-subtle"
|
||||
aria-label={`${group.label}: ${getPatrolFindingIssueCountLabel(group.findings.length)}`}
|
||||
>
|
||||
<div class="flex flex-wrap items-center justify-between gap-2 bg-surface-alt/60 px-3 py-2 text-xs">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2 px-3 py-2 text-xs">
|
||||
<span class="font-semibold text-base-content">{group.label}</span>
|
||||
<span class="text-muted">
|
||||
{getPatrolFindingIssueCountLabel(group.findings.length)}
|
||||
@@ -2261,8 +2261,8 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
)}
|
||||
</For>
|
||||
<Show when={alertMirroredFindings().length > 0}>
|
||||
<details class="bg-surface-alt/40">
|
||||
<summary class="flex min-h-11 cursor-pointer flex-wrap items-center justify-between gap-2 px-3 py-2 text-xs focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500">
|
||||
<details class="">
|
||||
<summary class="flex min-h-11 cursor-pointer flex-wrap items-center justify-between gap-2 px-3 py-2 text-xs focus:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500">
|
||||
<span class="font-semibold text-base-content">
|
||||
{alertMirroredFindings().length}{' '}
|
||||
{alertMirroredFindings().length === 1 ? 'finding mirrors' : 'findings mirror'}{' '}
|
||||
@@ -2293,9 +2293,9 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<aside
|
||||
id={`finding-${finding().id}-details`}
|
||||
aria-label={`Review ${title().label}`}
|
||||
class="overflow-hidden rounded-md border border-border bg-surface shadow-sm xl:sticky xl:top-4"
|
||||
class="overflow-hidden rounded-md border border-border bg-surface shadow-xs xl:sticky xl:top-4"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3 border-b border-border-subtle bg-surface-alt/60 px-4 py-3">
|
||||
<div class="flex items-start justify-between gap-3 border-b border-border-subtle px-4 py-3">
|
||||
<div class="min-w-0">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<MetadataBadge
|
||||
@@ -2319,7 +2319,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
type="button"
|
||||
aria-label={`Close review panel for ${title().label}`}
|
||||
onClick={closeReviewPanel}
|
||||
class="rounded p-1.5 text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
|
||||
class="rounded-sm p-1.5 text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary/40"
|
||||
>
|
||||
<XIcon class="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
@@ -178,7 +178,9 @@ describe('FindingsPanel assistant handoff', () => {
|
||||
// row body itself opens the same details panel for normal scanning.
|
||||
expect(findingsPanelSource).toContain('role="button"');
|
||||
expect(findingsPanelSource).toContain('tabIndex={0}');
|
||||
expect(findingsPanelSource).toContain('class="min-w-0 flex-1 cursor-pointer rounded text-left');
|
||||
expect(findingsPanelSource).toContain(
|
||||
'class="min-w-0 flex-1 cursor-pointer rounded-sm text-left',
|
||||
);
|
||||
expect(findingsPanelSource).toContain('aria-expanded={expandedId() === finding.id}');
|
||||
expect(findingsPanelSource).toContain(
|
||||
"aria-label={`${expandedId() === finding.id ? 'Close issue details' : 'Open issue details'} for ${title.label}`}",
|
||||
@@ -563,7 +565,7 @@ describe('aiFindingPresentation', () => {
|
||||
).toEqual({
|
||||
label: 'Runtime issue',
|
||||
badgeClasses:
|
||||
'border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-800 dark:bg-sky-900 dark:text-sky-300',
|
||||
'border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-800 dark:bg-sky-900/25 dark:text-sky-300',
|
||||
badgeTone: 'sky',
|
||||
uppercase: false,
|
||||
});
|
||||
@@ -671,7 +673,7 @@ describe('aiFindingPresentation', () => {
|
||||
kind: 'runtime',
|
||||
label: 'Patrol runtime',
|
||||
badgeClasses:
|
||||
'border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-800 dark:bg-sky-900 dark:text-sky-300',
|
||||
'border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-800 dark:bg-sky-900/25 dark:text-sky-300',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -944,10 +946,10 @@ describe('aiFindingPresentation', () => {
|
||||
|
||||
it('keeps Patrol review and sort controls touch-sized on phones', () => {
|
||||
expect(findingsPanelSource).toContain(
|
||||
'class="inline-flex min-h-11 items-center gap-1 rounded border border-border',
|
||||
'class="inline-flex min-h-11 items-center gap-1 rounded-sm border border-border',
|
||||
);
|
||||
expect(findingsPanelSource).toContain(
|
||||
'selectBaseClass="min-h-11 rounded border border-border',
|
||||
'selectBaseClass="min-h-11 rounded-sm border border-border',
|
||||
);
|
||||
expect(findingsPanelSource).toContain('sm:min-h-0');
|
||||
});
|
||||
@@ -1093,7 +1095,7 @@ describe('aiFindingPresentation', () => {
|
||||
expect(findingsPanelSource).toContain('severityPresentation.badgeTone');
|
||||
expect(findingsPanelSource).toContain('severityPresentation.label');
|
||||
expect(findingsPanelSource).not.toContain(
|
||||
'px-1.5 py-0.5 border text-[10px] font-medium rounded',
|
||||
'px-1.5 py-0.5 border text-[10px] font-medium rounded-sm',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ export const renderMarkdown = (content: unknown): string => {
|
||||
for (const table of template.content.querySelectorAll('table')) {
|
||||
const region = document.createElement('div');
|
||||
region.className =
|
||||
'max-w-full overflow-x-auto focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-500';
|
||||
'max-w-full overflow-x-auto focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-blue-500';
|
||||
region.tabIndex = 0;
|
||||
region.setAttribute('role', 'region');
|
||||
region.setAttribute('aria-label', 'Scrollable table');
|
||||
|
||||
@@ -96,7 +96,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
class={`inline-flex items-center justify-center px-2 py-0.5 text-xs font-medium rounded transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-1 ${config.className} ${disabled ? 'opacity-60 cursor-not-allowed pointer-events-none' : ''}`.trim()}
|
||||
class={`inline-flex items-center justify-center px-2 py-0.5 text-xs font-medium rounded-sm transition-colors duration-150 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-1 ${config.className} ${disabled ? 'opacity-60 cursor-not-allowed pointer-events-none' : ''}`.trim()}
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
@@ -195,7 +195,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
});
|
||||
}}
|
||||
onChange={(e) => props.toggleAll(e.currentTarget.checked)}
|
||||
class="rounded text-sky-600 focus:ring-sky-500 transition-shadow cursor-pointer"
|
||||
class="rounded-sm text-sky-600 focus:ring-sky-500 transition-shadow cursor-pointer"
|
||||
aria-label="Select all resources"
|
||||
/>
|
||||
</TableHead>
|
||||
@@ -209,7 +209,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
<For each={props.table.columns}>
|
||||
{(column) => (
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind(getAlertResourceColumnKind(column))} whitespace-normal break-words`}
|
||||
class={`${getPlatformTableHeadClassForKind(getAlertResourceColumnKind(column))} whitespace-normal wrap-break-word`}
|
||||
title={getAlertResourceColumnHeaderTooltip(column)}
|
||||
>
|
||||
{column}
|
||||
@@ -260,7 +260,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
props.table.onToggleGlobalDisable?.();
|
||||
props.table.setHasUnsavedChanges?.(true);
|
||||
}}
|
||||
class="my-[1px]"
|
||||
class="my-px"
|
||||
title="Global alerts toggle - disable all alerts for this resource type"
|
||||
ariaLabel="Global alerts toggle"
|
||||
/>
|
||||
@@ -271,7 +271,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-semibold text-base-content">Global Defaults</span>
|
||||
<Show when={props.hasCustomGlobalDefaults()}>
|
||||
<span class="text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded">
|
||||
<span class="text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 rounded-sm">
|
||||
{getAlertResourceTableCustomBadgeLabel()}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -328,7 +328,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
<Show when={isOff()}>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute inset-0 w-full rounded cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400"
|
||||
class="absolute inset-0 w-full rounded-sm cursor-pointer focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-400"
|
||||
onClick={() => {
|
||||
props.table.setGlobalDefaults?.((prev) => ({
|
||||
...prev,
|
||||
@@ -501,7 +501,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
min="0"
|
||||
value={overrideDelay !== undefined ? overrideDelay : ''}
|
||||
placeholder={String(typeDefaultDelay)}
|
||||
class="w-16 rounded border border-border bg-surface px-2 py-0.5 text-sm text-center text-base-content focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
|
||||
class="w-16 rounded-sm border border-border bg-surface px-2 py-0.5 text-sm text-center text-base-content focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
|
||||
onInput={(e) => {
|
||||
const raw = e.currentTarget.value;
|
||||
if (raw === '') {
|
||||
|
||||
@@ -121,7 +121,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
class={`inline-flex min-h-11 items-center justify-center px-2 py-0.5 text-xs font-medium rounded transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-1 sm:min-h-0 ${config.className} ${disabled ? 'opacity-60 cursor-not-allowed pointer-events-none' : ''}`.trim()}
|
||||
class={`inline-flex min-h-11 items-center justify-center px-2 py-0.5 text-xs font-medium rounded-sm transition-colors duration-150 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-1 sm:min-h-0 ${config.className} ${disabled ? 'opacity-60 cursor-not-allowed pointer-events-none' : ''}`.trim()}
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
@@ -179,13 +179,13 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
>
|
||||
<Card
|
||||
padding="sm"
|
||||
class="border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900"
|
||||
class="border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/25"
|
||||
>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-semibold text-sm">Global Defaults</span>
|
||||
<Show when={props.hasCustomGlobalDefaults()}>
|
||||
<span class="text-[10px] px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded">
|
||||
<span class="text-[10px] px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 rounded-sm">
|
||||
{getAlertResourceTableCustomBadgeLabel()}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -218,7 +218,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
const isOff = () => isAlertResourceMetricOff(value());
|
||||
|
||||
return (
|
||||
<div class="p-2 bg-surface rounded border border-border-subtle flex flex-col gap-1">
|
||||
<div class="p-2 bg-surface rounded-sm border border-border-subtle flex flex-col gap-1">
|
||||
<span class="text-[10px] uppercase text-slate-500 font-medium">{column}</span>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div class="relative flex-1">
|
||||
@@ -230,7 +230,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
value={isOff() ? '' : value()}
|
||||
placeholder={getAlertResourceTableMetricPlaceholder(isOff())}
|
||||
disabled={isOff()}
|
||||
class={`min-h-11 w-full rounded border p-1 text-center text-sm sm:min-h-0 ${isOff() ? 'bg-surface-hover' : ' border-border'}`}
|
||||
class={`min-h-11 w-full rounded-sm border p-1 text-center text-sm sm:min-h-0 ${isOff() ? 'bg-surface-hover' : ' border-border'}`}
|
||||
onInput={(e) => {
|
||||
const nextValue = parseFloat(e.currentTarget.value);
|
||||
// An empty box is mid-edit, not a disable request:
|
||||
@@ -284,7 +284,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
}}
|
||||
</For>
|
||||
<Show when={props.table.showOfflineAlertsColumn}>
|
||||
<div class="p-2 bg-surface rounded border border-border-subtle flex flex-col gap-1">
|
||||
<div class="p-2 bg-surface rounded-sm border border-border-subtle flex flex-col gap-1">
|
||||
<span class="text-[10px] uppercase text-slate-500 font-medium">Offline alerts</span>
|
||||
<div class="flex items-center min-h-11 sm:min-h-0">
|
||||
<Show
|
||||
@@ -473,7 +473,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
label="Override note"
|
||||
labelClass="sr-only"
|
||||
fieldBaseClass="w-full"
|
||||
textareaBaseClass="w-full text-xs p-2 rounded border border-border bg-surface-alt"
|
||||
textareaBaseClass="w-full text-xs p-2 rounded-sm border border-border bg-surface-alt"
|
||||
rows={2}
|
||||
placeholder={getAlertResourceTableEditNotePlaceholder()}
|
||||
value={props.table.editingNote()}
|
||||
@@ -505,7 +505,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
const titlePrefix = metric === 'backup' ? 'Backup' : 'Snapshot';
|
||||
|
||||
return (
|
||||
<div class="flex justify-between items-center p-1.5 bg-surface-alt rounded">
|
||||
<div class="flex justify-between items-center p-1.5 bg-surface-alt rounded-sm">
|
||||
<span class="text-[10px] uppercase font-bold tracking-wider">
|
||||
{column}
|
||||
</span>
|
||||
@@ -524,7 +524,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
const bounds = getAlertResourceMetricBounds(metric);
|
||||
|
||||
return (
|
||||
<div class="flex justify-between items-center p-1.5 bg-surface-alt rounded">
|
||||
<div class="flex justify-between items-center p-1.5 bg-surface-alt rounded-sm">
|
||||
<span class="text-[10px] uppercase font-bold tracking-wider">
|
||||
{column.replace(/mb\/s|%|°c/gi, '').trim()}
|
||||
</span>
|
||||
@@ -535,7 +535,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => startEditing(resource, metric, e)}
|
||||
class="min-h-11 min-w-11 font-mono text-xs font-medium cursor-pointer rounded px-1 -mx-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400"
|
||||
class="min-h-11 min-w-11 font-mono text-xs font-medium cursor-pointer rounded-sm px-1 -mx-1 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-400"
|
||||
aria-label={`Edit ${column} threshold for ${getAlertResourceLabel(resource)}`}
|
||||
>
|
||||
<MetricValueWithHeat
|
||||
@@ -554,7 +554,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
max={bounds.max}
|
||||
value={isDisabled() ? '' : (thresholds()?.[metric] ?? '')}
|
||||
placeholder={getAlertResourceTableMetricPlaceholder(isDisabled())}
|
||||
class="min-h-11 w-16 text-right text-xs p-1 rounded border border-border bg-surface"
|
||||
class="min-h-11 w-16 text-right text-xs p-1 rounded-sm border border-border bg-surface"
|
||||
onInput={(e) => {
|
||||
const nextValue = parseFloat(e.currentTarget.value);
|
||||
if (
|
||||
@@ -601,7 +601,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
const disabledGlobally = () => props.table.globalDisableFlag?.() ?? false;
|
||||
|
||||
return (
|
||||
<div class="flex justify-between items-center p-1.5 bg-surface-alt rounded">
|
||||
<div class="flex justify-between items-center p-1.5 bg-surface-alt rounded-sm">
|
||||
<span class="text-[10px] uppercase font-bold tracking-wider">Offline</span>
|
||||
<Show
|
||||
when={supportsTriState}
|
||||
|
||||
@@ -203,7 +203,7 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
class={`inline-flex items-center justify-center px-2 py-0.5 text-xs font-medium rounded transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-1 ${config.className} ${disabled ? 'opacity-60 cursor-not-allowed pointer-events-none' : ''}`.trim()}
|
||||
class={`inline-flex items-center justify-center px-2 py-0.5 text-xs font-medium rounded-sm transition-colors duration-150 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-1 ${config.className} ${disabled ? 'opacity-60 cursor-not-allowed pointer-events-none' : ''}`.trim()}
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
@@ -228,7 +228,7 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
type="checkbox"
|
||||
checked={props.selected}
|
||||
onChange={(e) => props.onToggleSelection?.(e.currentTarget.checked)}
|
||||
class="rounded border-border text-sky-600 focus:ring-sky-500 transition-shadow cursor-pointer"
|
||||
class="rounded-sm border-border text-sky-600 focus:ring-sky-500 transition-shadow cursor-pointer"
|
||||
aria-label={`Select ${resourceLabel()}`}
|
||||
/>
|
||||
</TableCell>
|
||||
@@ -246,7 +246,7 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
checked={isChecked}
|
||||
disabled={globallyDisabled}
|
||||
onToggle={() => !globallyDisabled && props.onToggleDisabled?.(props.resource.id)}
|
||||
class="my-[1px]"
|
||||
class="my-px"
|
||||
title={
|
||||
globallyDisabled
|
||||
? 'Alerts disabled globally'
|
||||
@@ -289,14 +289,14 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
title={`Open ${resourceLabel()} web interface`}
|
||||
/>
|
||||
<Show when={props.resource.clusterName}>
|
||||
<span class="rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300">
|
||||
<span class="rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-blue-100 text-blue-700 dark:bg-blue-900/25 dark:text-blue-300">
|
||||
{props.resource.clusterName}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={props.resource.type === 'storage' && props.resource.node}>
|
||||
<span class="rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300">
|
||||
<span class="rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-amber-100 text-amber-700 dark:bg-amber-900/25 dark:text-amber-300">
|
||||
{props.resource.node}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -304,7 +304,7 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
<span class="text-xs text-muted">{props.resource.subtitle as string}</span>
|
||||
</Show>
|
||||
<Show when={props.resource.hasOverride || props.resource.disableConnectivity}>
|
||||
<span class="text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded">
|
||||
<span class="text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 rounded-sm">
|
||||
{getAlertResourceTableCustomBadgeLabel()}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -315,7 +315,7 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
label="Override note"
|
||||
labelClass="sr-only"
|
||||
fieldBaseClass="mt-2 w-full"
|
||||
textareaBaseClass="w-full rounded border border-border bg-surface px-2 py-1 text-xs text-base-content focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
textareaBaseClass="w-full rounded-sm border border-border bg-surface px-2 py-1 text-xs text-base-content focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
rows={2}
|
||||
placeholder={getAlertResourceTableOverrideNotePlaceholder()}
|
||||
value={props.editingNote()}
|
||||
@@ -323,7 +323,9 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
/>
|
||||
</Show>
|
||||
<Show when={!isEditing() && props.resource.note}>
|
||||
<p class="mt-2 text-xs italic text-muted break-words">{props.resource.note as string}</p>
|
||||
<p class="mt-2 text-xs italic text-muted wrap-break-word">
|
||||
{props.resource.note as string}
|
||||
</p>
|
||||
</Show>
|
||||
</TableCell>
|
||||
|
||||
@@ -394,7 +396,7 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
openMetricEditor();
|
||||
}
|
||||
}}
|
||||
class="cursor-pointer hover:bg-surface-hover rounded px-1 py-0.5 transition-colors"
|
||||
class="cursor-pointer hover:bg-surface-hover rounded-sm px-1 py-0.5 transition-colors"
|
||||
title={getAlertResourceTableEditMetricTitle()}
|
||||
aria-label={getAlertResourceTableEditMetricTitle()}
|
||||
>
|
||||
|
||||
@@ -96,7 +96,7 @@ export function BulkEditDialog(props: BulkEditDialogProps) {
|
||||
|
||||
return (
|
||||
<Dialog isOpen={props.isOpen} onClose={props.onClose} ariaLabel={getAlertBulkEditOpenLabel()}>
|
||||
<div class="fixed inset-0 min-h-screen z-[100] flex items-center justify-center pointer-events-none">
|
||||
<div class="fixed inset-0 min-h-screen z-100 flex items-center justify-center pointer-events-none">
|
||||
<div class="bg-surface rounded-xl shadow-2xl ring-1 ring-border max-w-lg w-full p-6 max-h-[90vh] flex flex-col pointer-events-auto">
|
||||
<h2 class="text-xl font-semibold text-base-content mb-2">
|
||||
{ALERT_BULK_EDIT_DIALOG_TITLE}
|
||||
@@ -163,7 +163,7 @@ export function BulkEditDialog(props: BulkEditDialogProps) {
|
||||
<input
|
||||
type="number"
|
||||
aria-label={`${column} threshold`}
|
||||
class="w-full h-9 rounded-md border border-border bg-surface px-3 py-1 text-sm shadow-sm transition-colors focus:border-sky-500 focus:outline-none focus:ring-1 focus:ring-sky-500"
|
||||
class="w-full h-9 rounded-md border border-border bg-surface px-3 py-1 text-sm shadow-xs transition-colors focus:border-sky-500 focus:outline-hidden focus:ring-1 focus:ring-sky-500"
|
||||
min={bounds.min}
|
||||
max={bounds.max}
|
||||
step={bounds.step}
|
||||
@@ -209,14 +209,14 @@ export function BulkEditDialog(props: BulkEditDialogProps) {
|
||||
<div class="mt-4 flex justify-end gap-3 pt-4 border-t border-border shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
class="px-5 py-2 text-sm font-medium text-base-content bg-surface border border-border hover:bg-surface-hover rounded-md transition-colors shadow-sm"
|
||||
class="px-5 py-2 text-sm font-medium text-base-content bg-surface border border-border hover:bg-surface-hover rounded-md transition-colors shadow-xs"
|
||||
onClick={props.onClose}
|
||||
>
|
||||
{ALERT_BULK_EDIT_CANCEL_LABEL}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="px-5 py-2 text-sm font-medium text-white bg-sky-600 hover:bg-sky-500 rounded-md shadow-sm transition-colors"
|
||||
class="px-5 py-2 text-sm font-medium text-white bg-sky-600 hover:bg-sky-500 rounded-md shadow-xs transition-colors"
|
||||
onClick={handleSave}
|
||||
>
|
||||
{getAlertBulkEditApplyLabel(props.selectedIds.length)}
|
||||
|
||||
@@ -65,7 +65,7 @@ export function EmailProviderSelect(props: EmailProviderSelectProps) {
|
||||
minimumSeverity: `${fieldIdPrefix}-minimum-severity`,
|
||||
};
|
||||
const instructionBoxClass =
|
||||
'mt-2 rounded border border-blue-200 bg-blue-50 px-3 py-2 text-xs leading-relaxed text-blue-900 dark:border-blue-700 dark:bg-blue-900 dark:text-blue-200';
|
||||
'mt-2 rounded-sm border border-blue-200 bg-blue-50 px-3 py-2 text-xs leading-relaxed text-blue-900/25 dark:border-blue-700 dark:bg-blue-900/25 dark:text-blue-200';
|
||||
|
||||
return (
|
||||
<div class="space-y-4 text-sm overflow-hidden">
|
||||
@@ -386,7 +386,7 @@ export function EmailProviderSelect(props: EmailProviderSelectProps) {
|
||||
type="button"
|
||||
onClick={props.onTest}
|
||||
disabled={props.testing || !props.config.enabled}
|
||||
class="min-h-11 rounded border border-blue-500 px-3 py-1.5 text-xs font-medium text-blue-600 transition-colors hover:bg-blue-50 disabled:opacity-50 disabled:cursor-not-allowed sm:min-h-0 dark:border-blue-400 dark:text-blue-300 dark:hover:bg-blue-900"
|
||||
class="min-h-11 rounded-sm border border-blue-500 px-3 py-1.5 text-xs font-medium text-blue-600 transition-colors hover:bg-blue-50 disabled:opacity-50 disabled:cursor-not-allowed sm:min-h-0 dark:border-blue-400 dark:text-blue-300 dark:hover:bg-blue-900/25"
|
||||
>
|
||||
{getAlertEmailTestButtonLabel(Boolean(props.testing))}
|
||||
</button>
|
||||
|
||||
@@ -36,7 +36,7 @@ export function IncidentAssistantHandoffButton(props: IncidentAssistantHandoffBu
|
||||
type="button"
|
||||
class={
|
||||
props.class ||
|
||||
'inline-flex shrink-0 items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs font-medium text-blue-600 transition-colors hover:bg-surface-hover hover:text-blue-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 dark:text-blue-400 dark:hover:text-blue-300'
|
||||
'inline-flex shrink-0 items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs font-medium text-blue-600 transition-colors hover:bg-surface-hover hover:text-blue-700 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500 dark:text-blue-400 dark:hover:text-blue-300'
|
||||
}
|
||||
title="Discuss this incident with Pulse Assistant"
|
||||
aria-label={`Discuss incident ${props.incident.id} with Pulse Assistant`}
|
||||
|
||||
@@ -43,10 +43,10 @@ export function IncidentTimelineEventCard(props: IncidentTimelineEventCardProps)
|
||||
<Show when={props.event.evidence}>
|
||||
{(evidence) => (
|
||||
<details class="text-xs text-muted mt-2">
|
||||
<summary class="cursor-pointer rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2">
|
||||
<summary class="cursor-pointer rounded-sm focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2">
|
||||
{INCIDENT_EVIDENCE_DETAILS}
|
||||
</summary>
|
||||
<dl class="mt-2 grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1 break-words">
|
||||
<dl class="mt-2 grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1 wrap-break-word">
|
||||
<dt>Observed</dt>
|
||||
<dd>
|
||||
{formatIncidentEvidenceTime(evidence().observedAt) ?? INCIDENT_TIME_UNAVAILABLE}
|
||||
|
||||
@@ -163,7 +163,7 @@ export function InvestigateAlertButton(props: InvestigateAlertButtonProps) {
|
||||
const baseButtonClass = `
|
||||
inline-flex items-center justify-center
|
||||
rounded-md transition-all duration-200
|
||||
focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500
|
||||
focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
`;
|
||||
|
||||
@@ -191,7 +191,7 @@ export function InvestigateAlertButton(props: InvestigateAlertButtonProps) {
|
||||
onClick={handleOpenAssistant}
|
||||
class="flex w-full items-center gap-2 px-3 py-2 text-left text-xs text-base-content hover:bg-surface-hover"
|
||||
>
|
||||
<SparklesIcon class="h-4 w-4 flex-shrink-0 text-blue-600 dark:text-blue-400" />
|
||||
<SparklesIcon class="h-4 w-4 shrink-0 text-blue-600 dark:text-blue-400" />
|
||||
<span class="flex flex-col">
|
||||
<span class="font-medium">{t('alerts.assistant.explain.menuLabel')}</span>
|
||||
<span class="text-[10px] text-muted">{t('alerts.assistant.explain.menuHint')}</span>
|
||||
@@ -274,7 +274,7 @@ export function InvestigateAlertButton(props: InvestigateAlertButtonProps) {
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
<ShieldCheckIcon class="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<ShieldCheckIcon class="h-3.5 w-3.5 shrink-0" />
|
||||
</Show>
|
||||
<span class="text-xs font-medium">
|
||||
{usesPatrolPrimary()
|
||||
@@ -304,7 +304,7 @@ export function InvestigateAlertButton(props: InvestigateAlertButtonProps) {
|
||||
bg-blue-600
|
||||
hover:bg-blue-700
|
||||
text-white font-medium
|
||||
shadow-sm hover:shadow-sm
|
||||
shadow-xs hover:shadow-xs
|
||||
gap-2
|
||||
${showPatrolOption() ? 'rounded-r-none' : ''}
|
||||
${isLocked() ? 'opacity-60 cursor-not-allowed hover:bg-blue-600' : ''}
|
||||
@@ -325,7 +325,7 @@ export function InvestigateAlertButton(props: InvestigateAlertButtonProps) {
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
<ShieldCheckIcon class="h-4 w-4 flex-shrink-0" />
|
||||
<ShieldCheckIcon class="h-4 w-4 shrink-0" />
|
||||
</Show>
|
||||
<span>
|
||||
{usesPatrolPrimary()
|
||||
|
||||
@@ -341,7 +341,7 @@ describe('ResourceTable', () => {
|
||||
expect(alertResourceTableMobileSource).toContain('FormTextarea');
|
||||
expect(alertResourceTableMobileSource).toContain('ActionIconButton');
|
||||
expect(alertResourceTableMobileSource).toContain(
|
||||
'min-h-11 w-full rounded border p-1 text-center text-sm sm:min-h-0',
|
||||
'min-h-11 w-full rounded-sm border p-1 text-center text-sm sm:min-h-0',
|
||||
);
|
||||
expect(alertResourceTableMobileSource).not.toContain(['<', 'textarea'].join(''));
|
||||
expect(alertResourceTableMobileSource).not.toContain(rawSvgTag);
|
||||
|
||||
@@ -145,14 +145,14 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
</Show>
|
||||
|
||||
<Show when={selectedIds().size > 0 && props.onBulkEdit}>
|
||||
<div class="fixed bottom-8 left-1/2 -translate-x-1/2 bg-base border border-border shadow-2xl rounded-full px-5 py-3 flex items-center gap-6 z-[100] animate-in slide-in-from-bottom-5">
|
||||
<div class="fixed bottom-8 left-1/2 -translate-x-1/2 bg-base border border-border shadow-2xl rounded-full px-5 py-3 flex items-center gap-6 z-100 animate-in slide-in-from-bottom-5">
|
||||
<span class="text-sm font-medium text-white">
|
||||
{selectedIds().size} <span class="text-slate-400">selected</span>
|
||||
</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="bg-blue-600 hover:bg-blue-500 text-white rounded-full px-5 py-1.5 text-sm font-medium transition-colors shadow-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
||||
class="bg-blue-600 hover:bg-blue-500 text-white rounded-full px-5 py-1.5 text-sm font-medium transition-colors shadow-xs focus:ring-2 focus:ring-blue-500 focus:outline-hidden"
|
||||
onClick={() => {
|
||||
if (props.onBulkEdit) {
|
||||
props.onBulkEdit(Array.from(selectedIds()));
|
||||
|
||||
@@ -95,37 +95,37 @@ export const CollapsibleSection: Component<CollapsibleSectionProps> = (props) =>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleToggle}
|
||||
class="flex min-h-11 w-full min-w-0 items-center gap-3 px-4 py-3 text-left cursor-pointer select-none focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-500"
|
||||
class="flex min-h-11 w-full min-w-0 items-center gap-3 px-4 py-3 text-left cursor-pointer select-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-500"
|
||||
aria-expanded={!isCollapsed()}
|
||||
aria-controls={`section-content-${props.id}`}
|
||||
>
|
||||
{/* Left side: Chevron + Icon + Title + Count */}
|
||||
<span class="flex-shrink-0 text-muted transition-transform duration-200">
|
||||
<span class="shrink-0 text-muted transition-transform duration-200">
|
||||
<Show when={isCollapsed()} fallback={<ChevronDown class="w-5 h-5" />}>
|
||||
<ChevronRight class="w-5 h-5" />
|
||||
</Show>
|
||||
</span>
|
||||
|
||||
<Show when={props.icon}>
|
||||
<span class="flex-shrink-0 text-muted">{props.icon}</span>
|
||||
<span class="shrink-0 text-muted">{props.icon}</span>
|
||||
</Show>
|
||||
|
||||
<span class="min-w-0">
|
||||
<span class="flex items-center gap-2">
|
||||
<span class="truncate font-semibold text-base-content">{props.title}</span>
|
||||
<Show when={props.resourceCount !== undefined}>
|
||||
<span class="flex-shrink-0 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-surface-alt text-muted">
|
||||
<span class="shrink-0 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-surface-alt text-muted">
|
||||
{props.resourceCount}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={props.isGloballyDisabled}>
|
||||
<span class="flex-shrink-0 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-400">
|
||||
<span class="shrink-0 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-700 dark:bg-yellow-900/25 dark:text-yellow-400">
|
||||
{getAlertThresholdsSectionDisabledLabel()}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={props.hasChanges}>
|
||||
<span
|
||||
class="flex-shrink-0 w-2 h-2 rounded-full bg-blue-500"
|
||||
class="shrink-0 w-2 h-2 rounded-full bg-blue-500"
|
||||
title={getAlertThresholdsSectionUnsavedChangesTitle()}
|
||||
/>
|
||||
</Show>
|
||||
@@ -138,7 +138,7 @@ export const CollapsibleSection: Component<CollapsibleSectionProps> = (props) =>
|
||||
</h3>
|
||||
|
||||
<Show when={props.headerActions}>
|
||||
<div class="flex flex-shrink-0 items-center gap-2 pr-4">{props.headerActions}</div>
|
||||
<div class="flex shrink-0 items-center gap-2 pr-4">{props.headerActions}</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -197,9 +197,9 @@ export const SectionActionButton: Component<SectionActionButtonProps> = (props)
|
||||
const variantClasses = {
|
||||
default: 'text-slate-600 hover:text-base-content hover:bg-slate-100',
|
||||
primary:
|
||||
'text-blue-600 hover:text-blue-700 hover:bg-blue-50 dark:text-blue-400 dark:hover:text-blue-300 dark:hover:bg-blue-900',
|
||||
'text-blue-600 hover:text-blue-700 hover:bg-blue-50 dark:text-blue-400 dark:hover:text-blue-300 dark:hover:bg-blue-900/25',
|
||||
danger:
|
||||
'text-red-600 hover:text-red-700 hover:bg-red-50 dark:text-red-400 dark:hover:text-red-300 dark:hover:bg-red-900',
|
||||
'text-red-600 hover:text-red-700 hover:bg-red-50 dark:text-red-400 dark:hover:text-red-300 dark:hover:bg-red-900/25',
|
||||
};
|
||||
return (
|
||||
<button
|
||||
@@ -247,7 +247,7 @@ export const NestedGroupHeader: Component<NestedGroupHeaderProps> = (props) => {
|
||||
>
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<Show when={props.onToggle}>
|
||||
<div class="flex-shrink-0 text-slate-400">
|
||||
<div class="shrink-0 text-slate-400">
|
||||
<Show when={props.collapsed} fallback={<ChevronDown class="w-4 h-4" />}>
|
||||
<ChevronRight class="w-4 h-4" />
|
||||
</Show>
|
||||
@@ -256,7 +256,7 @@ export const NestedGroupHeader: Component<NestedGroupHeaderProps> = (props) => {
|
||||
|
||||
<Show when={props.status}>
|
||||
<StatusDot
|
||||
class="flex-shrink-0"
|
||||
class="shrink-0"
|
||||
variant={statusIndicator().variant}
|
||||
title={statusIndicator().label}
|
||||
ariaLabel={statusIndicator().label}
|
||||
|
||||
@@ -89,11 +89,11 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
/>
|
||||
|
||||
<Show when={!state.helpBannerDismissed()}>
|
||||
<div class="rounded-md border border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-900 p-3 relative group">
|
||||
<div class="rounded-md border border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-900/25 p-3 relative group">
|
||||
<button
|
||||
type="button"
|
||||
onClick={state.dismissHelpBanner}
|
||||
class="absolute right-1 top-1 inline-flex min-h-11 min-w-11 items-center justify-center rounded-md p-1 text-blue-500 transition-colors hover:bg-blue-100 hover:text-blue-700 dark:text-blue-400 dark:hover:bg-blue-900 dark:hover:text-blue-200 sm:right-2 sm:top-2 sm:min-h-0 sm:min-w-0 sm:opacity-0 sm:group-hover:opacity-100"
|
||||
class="absolute right-1 top-1 inline-flex min-h-11 min-w-11 items-center justify-center rounded-md p-1 text-blue-500 transition-colors hover:bg-blue-100 hover:text-blue-700 dark:text-blue-400 dark:hover:bg-blue-900/25 dark:hover:text-blue-200 sm:right-2 sm:top-2 sm:min-h-0 sm:min-w-0 sm:opacity-0 sm:group-hover:opacity-100"
|
||||
title={state.getAlertThresholdsHelpDismissLabel()}
|
||||
aria-label={state.getAlertThresholdsHelpDismissLabel()}
|
||||
>
|
||||
@@ -108,7 +108,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
</button>
|
||||
<div class="flex items-start gap-2 pr-6">
|
||||
<svg
|
||||
class="w-5 h-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5"
|
||||
class="w-5 h-5 text-blue-600 dark:text-blue-400 shrink-0 mt-0.5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -120,7 +120,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<div class="text-sm text-blue-900 dark:text-blue-100">
|
||||
<div class="text-sm text-blue-900/25 dark:text-blue-100">
|
||||
<p class="font-medium">{state.getAlertThresholdsHelpBanner().title}</p>
|
||||
<p class="mt-1">{state.getAlertThresholdsHelpBanner().toggleGuidance}</p>
|
||||
<p>{state.getAlertThresholdsHelpBanner().inheritanceGuidance}</p>
|
||||
@@ -152,12 +152,12 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
{(item) => (
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex min-h-11 items-center gap-2 rounded-full border border-sky-200 bg-surface px-3 py-1.5 text-xs font-medium text-base-content transition-colors hover:border-sky-400 hover:bg-sky-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-500 sm:min-h-0 dark:border-sky-700 dark:hover:bg-sky-900"
|
||||
class="inline-flex min-h-11 items-center gap-2 rounded-full border border-sky-200 bg-surface px-3 py-1.5 text-xs font-medium text-base-content transition-colors hover:border-sky-400 hover:bg-sky-100 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-sky-500 sm:min-h-0 dark:border-sky-700 dark:hover:bg-sky-900/25"
|
||||
onClick={() => state.revealCustomOverrides(item.key)}
|
||||
aria-label={`Show ${item.label}: ${state.getAlertThresholdsOverrideCountLabel(item.overrides)}`}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
<span class="rounded-full bg-sky-100 px-2 py-0.5 text-sky-700 dark:bg-sky-900 dark:text-sky-200">
|
||||
<span class="rounded-full bg-sky-100 px-2 py-0.5 text-sky-700 dark:bg-sky-900/25 dark:text-sky-200">
|
||||
{state.getAlertThresholdsOverrideCountLabel(item.overrides)}
|
||||
</span>
|
||||
</button>
|
||||
@@ -173,7 +173,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={state.expandAll}
|
||||
class="min-h-11 rounded px-2 py-1 text-xs transition-colors hover:bg-surface-hover hover:text-muted sm:min-h-0"
|
||||
class="min-h-11 rounded-sm px-2 py-1 text-xs transition-colors hover:bg-surface-hover hover:text-muted sm:min-h-0"
|
||||
>
|
||||
Expand all
|
||||
</button>
|
||||
@@ -181,7 +181,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={state.collapseAll}
|
||||
class="min-h-11 rounded px-2 py-1 text-xs transition-colors hover:bg-surface-hover hover:text-muted sm:min-h-0"
|
||||
class="min-h-11 rounded-sm px-2 py-1 text-xs transition-colors hover:bg-surface-hover hover:text-muted sm:min-h-0"
|
||||
>
|
||||
Collapse all
|
||||
</button>
|
||||
|
||||
@@ -105,7 +105,7 @@ export function ThresholdsTableAgentsResourcesSection(props: ThresholdsTableSect
|
||||
}));
|
||||
tableProps.setHasUnsavedChanges(true);
|
||||
}}
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-hidden focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ export function ThresholdsTableDockerIgnoredPrefixesSection(props: ThresholdsTab
|
||||
}}
|
||||
placeholder={state.dockerIgnoredPrefixesPresentation.placeholder}
|
||||
rows={4}
|
||||
textareaBaseClass="mt-4 w-full rounded-md border border-border bg-surface p-3 text-sm text-base-content focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
textareaBaseClass="mt-4 w-full rounded-md border border-border bg-surface p-3 text-sm text-base-content focus:border-sky-500 focus:outline-hidden focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -57,7 +57,7 @@ export function ThresholdsTableDockerServiceGapSection(props: ThresholdsTableSec
|
||||
}));
|
||||
tableProps.setHasUnsavedChanges(true);
|
||||
}}
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-hidden focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
{state.dockerServicePresentation.warningGapDescription}
|
||||
@@ -85,7 +85,7 @@ export function ThresholdsTableDockerServiceGapSection(props: ThresholdsTableSec
|
||||
}));
|
||||
tableProps.setHasUnsavedChanges(true);
|
||||
}}
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-hidden focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
{state.dockerServicePresentation.criticalGapDescription}
|
||||
|
||||
@@ -80,7 +80,7 @@ export function ThresholdsTableDockerUpdateAlertsSection(props: ThresholdsTableS
|
||||
}));
|
||||
tableProps.setHasUnsavedChanges(true);
|
||||
}}
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-hidden focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-muted">{state.dockerUpdatePresentation.delayDescription}</p>
|
||||
</div>
|
||||
|
||||
@@ -100,7 +100,7 @@ export function WebhookConfigForm(props: WebhookConfigFormProps) {
|
||||
onClick={() => props.selectService(service.id)}
|
||||
class={`min-h-11 px-2 py-1.5 text-left border transition-colors text-xs sm:min-h-0 ${
|
||||
props.formData().service === service.id
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900'
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/25'
|
||||
: 'border-border hover:bg-surface-hover'
|
||||
}`}
|
||||
>
|
||||
@@ -114,7 +114,7 @@ export function WebhookConfigForm(props: WebhookConfigFormProps) {
|
||||
|
||||
<Show when={props.currentTemplate()?.instructions}>
|
||||
<div class="mb-3 border-l-2 border-blue-300 pl-3 text-xs leading-relaxed text-blue-800 dark:border-blue-700 dark:text-blue-200">
|
||||
<h4 class="text-sm font-medium text-blue-900 dark:text-blue-100 mb-2">
|
||||
<h4 class="text-sm font-medium text-blue-900/25 dark:text-blue-100 mb-2">
|
||||
{getAlertWebhookSetupInstructionsTitle()}
|
||||
</h4>
|
||||
{props.currentTemplate()!.instructions}
|
||||
@@ -411,7 +411,7 @@ export function WebhookConfigForm(props: WebhookConfigFormProps) {
|
||||
<div class="flex justify-end gap-2 text-xs">
|
||||
<button
|
||||
onClick={props.cancelForm}
|
||||
class="min-h-11 px-3 py-1.5 border border-border rounded text-xs hover:bg-surface-hover sm:min-h-0"
|
||||
class="min-h-11 px-3 py-1.5 border border-border rounded-sm text-xs hover:bg-surface-hover sm:min-h-0"
|
||||
>
|
||||
{ALERT_WEBHOOK_CANCEL_LABEL}
|
||||
</button>
|
||||
@@ -419,7 +419,7 @@ export function WebhookConfigForm(props: WebhookConfigFormProps) {
|
||||
<button
|
||||
onClick={props.testWebhookForm}
|
||||
disabled={props.testing === (props.editingId() || 'temp-new-webhook')}
|
||||
class="min-h-11 px-3 py-1.5 border border-border rounded text-xs hover:bg-slate-100 sm:min-h-0"
|
||||
class="min-h-11 px-3 py-1.5 border border-border rounded-sm text-xs hover:bg-slate-100 sm:min-h-0"
|
||||
>
|
||||
{getAlertWebhookTestLabel(
|
||||
props.testing === (props.editingId() || 'temp-new-webhook'),
|
||||
@@ -430,7 +430,7 @@ export function WebhookConfigForm(props: WebhookConfigFormProps) {
|
||||
<button
|
||||
onClick={props.saveWebhook}
|
||||
disabled={!props.formData().name || !props.formData().url}
|
||||
class="min-h-11 px-3 py-1.5 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed sm:min-h-0"
|
||||
class="min-h-11 px-3 py-1.5 bg-blue-600 text-white text-xs rounded-sm hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed sm:min-h-0"
|
||||
>
|
||||
{getAlertWebhookSubmitLabel(Boolean(props.editingId()))}
|
||||
</button>
|
||||
|
||||
@@ -29,7 +29,7 @@ interface WebhookConfigListProps {
|
||||
export function WebhookConfigList(props: WebhookConfigListProps) {
|
||||
return (
|
||||
<div class="space-y-3 w-full">
|
||||
<div class="flex flex-col gap-2 rounded border border-border px-3 py-3 text-xs sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex flex-col gap-2 rounded-sm border border-border px-3 py-3 text-xs sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="text-muted sm:text-sm">
|
||||
{getAlertWebhookSummaryLabel(
|
||||
props.webhooks.filter((webhook) => webhook.enabled).length,
|
||||
@@ -40,14 +40,14 @@ export function WebhookConfigList(props: WebhookConfigListProps) {
|
||||
<button
|
||||
onClick={() => props.toggleAllWebhooks(false)}
|
||||
disabled={!props.someEnabled()}
|
||||
class="min-h-11 w-full rounded border px-3 py-1 text-xs transition-colors hover:bg-surface-hover sm:min-h-0 sm:w-auto"
|
||||
class="min-h-11 w-full rounded-sm border px-3 py-1 text-xs transition-colors hover:bg-surface-hover sm:min-h-0 sm:w-auto"
|
||||
>
|
||||
{getAlertWebhookToggleAllLabel(false)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => props.toggleAllWebhooks(true)}
|
||||
disabled={props.allEnabled()}
|
||||
class="min-h-11 w-full rounded border border-green-500 px-3 py-1 text-xs text-green-700 transition-colors hover:bg-green-50 sm:min-h-0 sm:w-auto dark:border-green-600 dark:text-green-400 dark:hover:bg-green-900"
|
||||
class="min-h-11 w-full rounded-sm border border-green-500 px-3 py-1 text-xs text-green-700 transition-colors hover:bg-green-50 sm:min-h-0 sm:w-auto dark:border-green-600 dark:text-green-400 dark:hover:bg-green-900/25"
|
||||
>
|
||||
{getAlertWebhookToggleAllLabel(true)}
|
||||
</button>
|
||||
@@ -61,22 +61,22 @@ export function WebhookConfigList(props: WebhookConfigListProps) {
|
||||
<span class="font-medium text-base-content">{webhook.name}</span>
|
||||
<button
|
||||
onClick={() => props.onToggleWebhook(webhook)}
|
||||
class={`min-h-11 rounded border px-3 py-1 text-xs font-medium transition-colors sm:min-h-0 ${webhook.enabled ? 'border-green-500 text-green-700 hover:bg-green-50 dark:border-green-600 dark:text-green-400 dark:hover:bg-green-900' : 'border-border text-muted hover:bg-surface-hover'}`}
|
||||
class={`min-h-11 rounded-sm border px-3 py-1 text-xs font-medium transition-colors sm:min-h-0 ${webhook.enabled ? 'border-green-500 text-green-700 hover:bg-green-50 dark:border-green-600 dark:text-green-400 dark:hover:bg-green-900/25' : 'border-border text-muted hover:bg-surface-hover'}`}
|
||||
>
|
||||
{getAlertWebhookToggleLabel(webhook.enabled)}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-wrap gap-2 text-[11px] text-muted sm:text-xs">
|
||||
<span class="rounded bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
<span class="rounded-sm bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
{getAlertWebhookServiceLabelFromTemplates(
|
||||
webhook.service || 'generic',
|
||||
props.templates(),
|
||||
)}
|
||||
</span>
|
||||
<span class="rounded bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
<span class="rounded-sm bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
{webhook.method}
|
||||
</span>
|
||||
<span class="rounded bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
<span class="rounded-sm bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
{getAlertDestinationSeverityLabel(
|
||||
webhook.minimumSeverity === 'critical' || webhook.minimumSeverity === 'warning'
|
||||
? webhook.minimumSeverity
|
||||
@@ -85,13 +85,13 @@ export function WebhookConfigList(props: WebhookConfigListProps) {
|
||||
</span>
|
||||
<For each={webhook.tagFilter ?? []}>
|
||||
{(tag) => (
|
||||
<span class="rounded bg-sky-50 px-2 py-0.5 text-sky-700 dark:bg-sky-900 dark:text-sky-300">
|
||||
<span class="rounded-sm bg-sky-50 px-2 py-0.5 text-sky-700 dark:bg-sky-900/25 dark:text-sky-300">
|
||||
{tag}
|
||||
</span>
|
||||
)}
|
||||
</For>
|
||||
<Show when={(webhook.tagFilter?.length ?? 0) > 1}>
|
||||
<span class="rounded bg-surface-alt px-2 py-0.5 text-muted">
|
||||
<span class="rounded-sm bg-surface-alt px-2 py-0.5 text-muted">
|
||||
Match {webhook.tagFilterMode === 'any' ? 'any' : 'all'}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -101,19 +101,19 @@ export function WebhookConfigList(props: WebhookConfigListProps) {
|
||||
<button
|
||||
onClick={() => props.onTestWebhook(webhook)}
|
||||
disabled={props.testing === webhook.id || !webhook.enabled}
|
||||
class="min-h-11 rounded border px-3 py-1 text-xs text-base-content transition-colors hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
class="min-h-11 rounded-sm border px-3 py-1 text-xs text-base-content transition-colors hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
{getAlertWebhookTestLabel(props.testing === webhook.id)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => props.onEditWebhook(webhook)}
|
||||
class="min-h-11 rounded border border-blue-300 px-3 py-1 text-xs text-blue-600 transition-colors hover:bg-blue-50 sm:min-h-0 dark:border-blue-500 dark:text-blue-300 dark:hover:bg-blue-900"
|
||||
class="min-h-11 rounded-sm border border-blue-300 px-3 py-1 text-xs text-blue-600 transition-colors hover:bg-blue-50 sm:min-h-0 dark:border-blue-500 dark:text-blue-300 dark:hover:bg-blue-900/25"
|
||||
>
|
||||
{ALERT_WEBHOOK_EDIT_LABEL}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => props.onDeleteWebhook(webhook)}
|
||||
class="min-h-11 rounded border border-red-300 px-3 py-1 text-xs text-red-600 transition-colors hover:bg-red-50 sm:min-h-0 dark:border-red-500 dark:text-red-300 dark:hover:bg-red-900"
|
||||
class="min-h-11 rounded-sm border border-red-300 px-3 py-1 text-xs text-red-600 transition-colors hover:bg-red-50 sm:min-h-0 dark:border-red-500 dark:text-red-300 dark:hover:bg-red-900/25"
|
||||
>
|
||||
{ALERT_WEBHOOK_DELETE_LABEL}
|
||||
</button>
|
||||
|
||||
@@ -33,7 +33,7 @@ export const AppBootstrapStatus: Component<AppBootstrapStatusProps> = (props) =>
|
||||
class="flex min-h-screen items-center justify-center bg-base px-4 py-8 text-base-content"
|
||||
aria-labelledby="app-bootstrap-title"
|
||||
>
|
||||
<div class="w-full max-w-md rounded-md border border-border bg-surface p-6 text-center shadow-sm sm:p-8">
|
||||
<div class="w-full max-w-md rounded-md border border-border bg-surface p-6 text-center shadow-xs sm:p-8">
|
||||
<LoadingSpinner size="xl" tone="info" class="mb-4" />
|
||||
<h1 id="app-bootstrap-title" class="text-lg font-semibold tracking-tight">
|
||||
Connecting to Pulse
|
||||
|
||||
@@ -37,7 +37,7 @@ export const PulseBrandMark: Component<PulseBrandMarkProps> = (props) => {
|
||||
</Show>
|
||||
<circle class="pulse-bg fill-blue-600 dark:fill-blue-500" cx="128" cy="128" r="122" />
|
||||
<circle
|
||||
class="pulse-ring fill-none stroke-white dark:stroke-[#dbeafe] stroke-[14] opacity-[0.92]"
|
||||
class="pulse-ring fill-none stroke-white dark:stroke-[#dbeafe] stroke-14 opacity-[0.92]"
|
||||
cx="128"
|
||||
cy="128"
|
||||
r="84"
|
||||
|
||||
@@ -222,13 +222,13 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
<Show when={proposal()}>
|
||||
{(current) => (
|
||||
<section
|
||||
class="rounded-lg border border-blue-300 bg-blue-50/70 p-4 shadow-sm dark:border-blue-800 dark:bg-blue-950/30"
|
||||
class="rounded-lg border border-blue-300 bg-blue-50/70 p-4 shadow-xs dark:border-blue-800 dark:bg-blue-950/30"
|
||||
aria-label="Suggested service verification"
|
||||
>
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h3 class="text-sm font-semibold text-blue-950 dark:text-blue-100">
|
||||
<h3 class="text-sm font-semibold text-blue-950/25 dark:text-blue-100">
|
||||
Suggested service verification
|
||||
</h3>
|
||||
<span class="rounded-full border border-blue-300 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-blue-700 dark:border-blue-700 dark:text-blue-300">
|
||||
@@ -274,7 +274,7 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
}
|
||||
>
|
||||
<div class="mt-4 grid gap-2 sm:grid-cols-2">
|
||||
<div class="rounded-md border border-blue-200 bg-white/70 p-3 dark:border-blue-900 dark:bg-black/10">
|
||||
<div class="rounded-md border border-blue-200 bg-white/70 p-3 dark:border-blue-900/25 dark:bg-black/10">
|
||||
<p class="text-[10px] font-medium uppercase tracking-wide text-blue-600 dark:text-blue-400">
|
||||
Endpoint · inferred
|
||||
</p>
|
||||
@@ -282,7 +282,7 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
{endpointLabel(current())}
|
||||
</code>
|
||||
</div>
|
||||
<div class="rounded-md border border-blue-200 bg-white/70 p-3 dark:border-blue-900 dark:bg-black/10">
|
||||
<div class="rounded-md border border-blue-200 bg-white/70 p-3 dark:border-blue-900/25 dark:bg-black/10">
|
||||
<p class="text-[10px] font-medium uppercase tracking-wide text-blue-600 dark:text-blue-400">
|
||||
Expected behavior · reviewed default
|
||||
</p>
|
||||
@@ -344,7 +344,7 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
<Show when={duplicate()}>
|
||||
{(match) => (
|
||||
<div
|
||||
class="mt-3 rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-900 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-200"
|
||||
class="mt-3 rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-900/25 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-200"
|
||||
role="status"
|
||||
>
|
||||
{match().kind === 'endpoint'
|
||||
@@ -359,7 +359,7 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={targets.error}>
|
||||
<div class="mt-3 rounded-md border border-red-300 bg-red-50 px-3 py-2 text-xs text-red-900 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200">
|
||||
<div class="mt-3 rounded-md border border-red-300 bg-red-50 px-3 py-2 text-xs text-red-900/25 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200">
|
||||
<p role="alert">
|
||||
Pulse could not check for existing active checks. Creating a check is unavailable
|
||||
until that safety check succeeds.
|
||||
@@ -380,8 +380,8 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
<div
|
||||
class={`mt-3 rounded-md border px-3 py-2 text-xs ${
|
||||
result().success
|
||||
? 'border-emerald-300 bg-emerald-50 text-emerald-900 dark:border-emerald-800 dark:bg-emerald-950/30 dark:text-emerald-200'
|
||||
: 'border-red-300 bg-red-50 text-red-900 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200'
|
||||
? 'border-emerald-300 bg-emerald-50 text-emerald-900/25 dark:border-emerald-800 dark:bg-emerald-950/30 dark:text-emerald-200'
|
||||
: 'border-red-300 bg-red-50 text-red-900/25 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200'
|
||||
}`}
|
||||
role={result().success ? 'status' : 'alert'}
|
||||
>
|
||||
@@ -392,7 +392,7 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
<Show when={createdTarget()}>
|
||||
{(created) => (
|
||||
<div
|
||||
class="mt-3 flex items-center gap-2 rounded-md border border-emerald-300 bg-emerald-50 px-3 py-2 text-xs text-emerald-900 dark:border-emerald-800 dark:bg-emerald-950/30 dark:text-emerald-200"
|
||||
class="mt-3 flex items-center gap-2 rounded-md border border-emerald-300 bg-emerald-50 px-3 py-2 text-xs text-emerald-900/25 dark:border-emerald-800 dark:bg-emerald-950/30 dark:text-emerald-200"
|
||||
role="status"
|
||||
>
|
||||
<CheckCircleIcon class="h-4 w-4" aria-hidden="true" />
|
||||
@@ -478,7 +478,7 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
</Show>
|
||||
<Show when={machineDiscoveries.error}>
|
||||
<p
|
||||
class="rounded-md border border-red-300 bg-red-50 p-4 text-sm text-red-900 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200"
|
||||
class="rounded-md border border-red-300 bg-red-50 p-4 text-sm text-red-900/25 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200"
|
||||
role="alert"
|
||||
>
|
||||
Pulse could not load this machine’s assurance suggestions. Close this review and
|
||||
|
||||
@@ -148,9 +148,9 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
return (
|
||||
<div class="space-y-4 whitespace-normal">
|
||||
<Show when={discoveryFeatureKnownDisabled()}>
|
||||
<div class="rounded border border-amber-200 bg-amber-50/80 p-3 shadow-sm dark:border-amber-800/50 dark:bg-amber-900/20">
|
||||
<div class="rounded-sm border border-amber-200 bg-amber-50/80 p-3 shadow-xs dark:border-amber-800/50 dark:bg-amber-900/20">
|
||||
<div class="flex items-start gap-2.5">
|
||||
<TriangleAlertIcon class="mt-0.5 h-4 w-4 flex-shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<TriangleAlertIcon class="mt-0.5 h-4 w-4 shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<div class="text-xs text-amber-800 dark:text-amber-200">
|
||||
<p class="mb-1 font-medium">Service identification is off</p>
|
||||
<p class="text-amber-700 dark:text-amber-300">
|
||||
@@ -211,9 +211,9 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
Surfaces the "enabled but no provider" dead end that previously only
|
||||
showed for agents, so Discovery is never silently on-but-useless. */}
|
||||
<Show when={!discoveryInfo.loading && discoveryReadiness().status === 'needs_ai_provider'}>
|
||||
<div class="rounded border border-amber-200 bg-amber-50/80 p-3 shadow-sm dark:border-amber-800/50 dark:bg-amber-900/20">
|
||||
<div class="rounded-sm border border-amber-200 bg-amber-50/80 p-3 shadow-xs dark:border-amber-800/50 dark:bg-amber-900/20">
|
||||
<div class="flex items-start gap-2.5">
|
||||
<TriangleAlertIcon class="mt-0.5 h-4 w-4 flex-shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<TriangleAlertIcon class="mt-0.5 h-4 w-4 shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<div class="text-xs text-amber-800 dark:text-amber-200">
|
||||
<p class="mb-1 font-medium">AI provider not configured</p>
|
||||
<p class="text-amber-700 dark:text-amber-300">
|
||||
@@ -236,7 +236,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
type="button"
|
||||
onClick={() => handleTriggerDiscovery(true)}
|
||||
disabled={isScanning() || !canTriggerDiscovery()}
|
||||
class="inline-flex min-h-9 items-center justify-center gap-1.5 rounded border border-border bg-surface px-3 py-1.5 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="inline-flex min-h-9 items-center justify-center gap-1.5 rounded-sm border border-border bg-surface px-3 py-1.5 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<Show
|
||||
when={isScanning()}
|
||||
@@ -269,11 +269,11 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* "What Discovery Does" explanation - shown when no discovery exists yet */}
|
||||
<Show when={!discovery() && !isScanning() && !showLoadingSpinner() && showExplanation()}>
|
||||
<div class="rounded border border-amber-200 bg-amber-50 p-3 shadow-sm dark:border-amber-800 dark:bg-amber-900">
|
||||
<div class="rounded-sm border border-amber-200 bg-amber-50 p-3 shadow-xs dark:border-amber-800 dark:bg-amber-900/25">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex items-start gap-2.5">
|
||||
<svg
|
||||
class="w-4 h-4 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5"
|
||||
class="w-4 h-4 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -292,7 +292,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowExplanation(false)}
|
||||
class="text-amber-500 hover:text-amber-700 dark:text-amber-400 dark:hover:text-amber-300 flex-shrink-0"
|
||||
class="text-amber-500 hover:text-amber-700 dark:text-amber-400 dark:hover:text-amber-300 shrink-0"
|
||||
title="Dismiss"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -320,7 +320,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
}
|
||||
>
|
||||
<details
|
||||
class="rounded border border-border bg-surface shadow-sm"
|
||||
class="rounded-sm border border-border bg-surface shadow-xs"
|
||||
open={showCommandsPreview()}
|
||||
>
|
||||
<summary
|
||||
@@ -351,7 +351,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
{(cmd) => (
|
||||
<div class="text-xs">
|
||||
<div class="flex items-start gap-2">
|
||||
<code class="text-[10px] px-1.5 py-0.5 rounded bg-surface-hover text-base-content font-mono break-all">
|
||||
<code class="text-[10px] px-1.5 py-0.5 rounded-sm bg-surface-hover text-base-content font-mono break-all">
|
||||
{cmd.command}
|
||||
</code>
|
||||
</div>
|
||||
@@ -374,7 +374,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* Scan Progress Bar */}
|
||||
<Show when={scanProgress() && isScanning()}>
|
||||
<div class="rounded border border-blue-200 bg-blue-50 p-3 shadow-sm dark:border-blue-800 dark:bg-blue-900">
|
||||
<div class="rounded-sm border border-blue-200 bg-blue-50 p-3 shadow-xs dark:border-blue-800 dark:bg-blue-900/25">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="animate-spin h-4 w-4 border-2 border-blue-500 border-t-transparent rounded-full"></div>
|
||||
@@ -409,7 +409,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* Scanning state without WebSocket progress - show live timer */}
|
||||
<Show when={isScanning() && !scanProgress()}>
|
||||
<div class="rounded border border-blue-200 bg-blue-50 p-3 shadow-sm dark:border-blue-800 dark:bg-blue-900">
|
||||
<div class="rounded-sm border border-blue-200 bg-blue-50 p-3 shadow-xs dark:border-blue-800 dark:bg-blue-900/25">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div class="animate-spin h-4 w-4 border-2 border-blue-500 border-t-transparent rounded-full"></div>
|
||||
<span class="text-sm font-medium text-blue-700 dark:text-blue-300">
|
||||
@@ -430,7 +430,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
<div
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
class="mb-4 rounded-md border border-green-200 bg-green-50 p-4 dark:border-green-800 dark:bg-green-900"
|
||||
class="mb-4 rounded-md border border-green-200 bg-green-50 p-4 dark:border-green-800 dark:bg-green-900/25"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
@@ -455,10 +455,10 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* Scan Error */}
|
||||
<Show when={scanError()}>
|
||||
<div class="mb-4 rounded-md border border-red-200 bg-red-50 p-4 dark:border-red-800 dark:bg-red-900">
|
||||
<div class="mb-4 rounded-md border border-red-200 bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/25">
|
||||
<div class="flex items-start gap-3">
|
||||
<svg
|
||||
class="w-5 h-5 text-red-500 dark:text-red-400 flex-shrink-0 mt-0.5"
|
||||
class="w-5 h-5 text-red-500 dark:text-red-400 shrink-0 mt-0.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -535,10 +535,10 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
}
|
||||
>
|
||||
<Show when={discoveryReadiness().status === 'needs_commands'}>
|
||||
<div class="mb-4 mx-auto max-w-md rounded-md border border-amber-200 bg-amber-50 p-3 text-left dark:border-amber-800 dark:bg-amber-900">
|
||||
<div class="mb-4 mx-auto max-w-md rounded-md border border-amber-200 bg-amber-50 p-3 text-left dark:border-amber-800 dark:bg-amber-900/25">
|
||||
<div class="flex items-start gap-2">
|
||||
<svg
|
||||
class="w-4 h-4 text-amber-500 dark:text-amber-400 flex-shrink-0 mt-0.5"
|
||||
class="w-4 h-4 text-amber-500 dark:text-amber-400 shrink-0 mt-0.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -566,10 +566,10 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={discoveryReadiness().status === 'needs_connected_agent'}>
|
||||
<div class="mb-4 mx-auto max-w-md rounded-md border border-amber-200 bg-amber-50 p-3 text-left dark:border-amber-800 dark:bg-amber-900">
|
||||
<div class="mb-4 mx-auto max-w-md rounded-md border border-amber-200 bg-amber-50 p-3 text-left dark:border-amber-800 dark:bg-amber-900/25">
|
||||
<div class="flex items-start gap-2">
|
||||
<svg
|
||||
class="w-4 h-4 text-amber-500 dark:text-amber-400 flex-shrink-0 mt-0.5"
|
||||
class="w-4 h-4 text-amber-500 dark:text-amber-400 shrink-0 mt-0.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -588,7 +588,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
<p class="text-amber-700 dark:text-amber-300 mt-0.5">
|
||||
Commands are enabled, but the agent isn't connected via WebSocket. Check
|
||||
that the API token has the{' '}
|
||||
<code class="px-1 py-0.5 bg-amber-100 dark:bg-amber-800 rounded">
|
||||
<code class="px-1 py-0.5 bg-amber-100 dark:bg-amber-800 rounded-sm">
|
||||
agent:exec
|
||||
</code>{' '}
|
||||
scope in{' '}
|
||||
@@ -606,10 +606,10 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
green "connected" claim must require a genuinely connected
|
||||
agent with commands explicitly enabled. */}
|
||||
<Show when={props.commandsEnabled === true && hasConnectedAgent()}>
|
||||
<div class="mb-4 mx-auto max-w-md rounded-md border border-green-200 bg-green-50 p-3 text-left dark:border-green-800 dark:bg-green-900">
|
||||
<div class="mb-4 mx-auto max-w-md rounded-md border border-green-200 bg-green-50 p-3 text-left dark:border-green-800 dark:bg-green-900/25">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
class="w-4 h-4 text-green-500 dark:text-green-400 flex-shrink-0"
|
||||
class="w-4 h-4 text-green-500 dark:text-green-400 shrink-0"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -728,9 +728,9 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
{(d) => (
|
||||
<div class="space-y-4">
|
||||
<Show when={isStaleDiscovery()}>
|
||||
<div class="rounded border border-amber-200 bg-amber-50/80 p-3 shadow-sm dark:border-amber-800/50 dark:bg-amber-900/20">
|
||||
<div class="rounded-sm border border-amber-200 bg-amber-50/80 p-3 shadow-xs dark:border-amber-800/50 dark:bg-amber-900/20">
|
||||
<div class="flex items-start gap-2">
|
||||
<TriangleAlertIcon class="mt-0.5 h-4 w-4 flex-shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<TriangleAlertIcon class="mt-0.5 h-4 w-4 shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<p class="text-xs text-amber-800 dark:text-amber-200">
|
||||
This was discovered by an earlier version of Pulse. Re-run discovery for
|
||||
improved identification and access details.
|
||||
@@ -781,11 +781,11 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
("Last run: X ago") whenever it's rendered; only repeat it
|
||||
here when that block is absent. */}
|
||||
<Show when={d().updated_at && !showManualRunAction()}>
|
||||
<span class="rounded border border-border bg-surface-alt px-2 py-0.5">
|
||||
<span class="rounded-sm border border-border bg-surface-alt px-2 py-0.5">
|
||||
Last observed {formatDiscoveryAge(d().updated_at)}
|
||||
</span>
|
||||
</Show>
|
||||
<span class="rounded border border-border bg-surface-alt px-2 py-0.5">
|
||||
<span class="rounded-sm border border-border bg-surface-alt px-2 py-0.5">
|
||||
Available to Pulse Assistant
|
||||
</span>
|
||||
</div>
|
||||
@@ -863,7 +863,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* CLI Access */}
|
||||
<Show when={d().cli_access}>
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="rounded-sm border border-border p-3 shadow-xs">
|
||||
<div class="mb-2 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>CLI Access</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
@@ -899,7 +899,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
d().log_paths?.length > 0
|
||||
}
|
||||
>
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="rounded-sm border border-border p-3 shadow-xs">
|
||||
<Show when={d().config_paths?.length > 0}>
|
||||
<div class="mb-3">
|
||||
<div class="mb-1 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
@@ -967,7 +967,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
Docker resources. The host source is where to actually edit or
|
||||
back up files; the container destination is what the app sees. */}
|
||||
<Show when={(d().docker_mounts?.length ?? 0) > 0}>
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="rounded-sm border border-border p-3 shadow-xs">
|
||||
<div class="mb-2 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>Bind Mounts</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
@@ -997,7 +997,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* Ports */}
|
||||
<Show when={d().ports?.length > 0}>
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="rounded-sm border border-border p-3 shadow-xs">
|
||||
<div class="mb-2 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>Listening Ports</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
@@ -1028,7 +1028,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* Key Facts */}
|
||||
<Show when={d().facts?.length > 0}>
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="rounded-sm border border-border p-3 shadow-xs">
|
||||
<div class="mb-2 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>Discovered Facts</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
@@ -1069,7 +1069,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
</Show>
|
||||
|
||||
{/* User Notes */}
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="rounded-sm border border-border p-3 shadow-xs">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
Your Notes
|
||||
@@ -1104,7 +1104,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
value={notesText()}
|
||||
onInput={(e) => setNotesText(e.currentTarget.value)}
|
||||
placeholder="Add notes about this resource (API tokens, passwords, important info)..."
|
||||
class="w-full h-24 px-2 py-1.5 text-xs border border-border rounded bg-surface text-base-content focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="w-full h-24 px-2 py-1.5 text-xs border border-border rounded-sm bg-surface text-base-content focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
<Show when={saveError()}>
|
||||
<p
|
||||
@@ -1118,13 +1118,13 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
onClick={handleSaveNotes}
|
||||
class="px-3 py-1 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 transition-colors"
|
||||
class="px-3 py-1 bg-blue-600 text-white text-xs rounded-sm hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setEditingNotes(false)}
|
||||
class="px-3 py-1 bg-surface-hover text-base-content text-xs rounded hover:bg-slate-300 transition-colors"
|
||||
class="px-3 py-1 bg-surface-hover text-base-content text-xs rounded-sm hover:bg-slate-300 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -1135,7 +1135,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* Analysis reasoning (collapsible) */}
|
||||
<Show when={d().ai_reasoning}>
|
||||
<details class="rounded border shadow-sm">
|
||||
<details class="rounded-sm border shadow-xs">
|
||||
<summary class="p-3 text-[11px] font-medium uppercase tracking-wide text-base-content cursor-pointer hover:bg-surface-hover">
|
||||
{DISCOVERY_ANALYSIS_REASONING_LABEL}
|
||||
</summary>
|
||||
@@ -1149,7 +1149,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
<Show
|
||||
when={d().raw_command_output && Object.keys(d().raw_command_output!).length > 0}
|
||||
>
|
||||
<details class="rounded border shadow-sm">
|
||||
<details class="rounded-sm border shadow-xs">
|
||||
<summary class="p-3 text-[11px] font-medium uppercase tracking-wide text-base-content cursor-pointer hover:bg-surface-hover">
|
||||
Scan Details ({Object.keys(d().raw_command_output!).length} commands)
|
||||
</summary>
|
||||
@@ -1158,7 +1158,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
{([cmdName, output]) => (
|
||||
<div>
|
||||
<div class="text-xs font-medium text-base-content mb-1">{cmdName}</div>
|
||||
<pre class="text-[10px] bg-surface-alt rounded p-2 overflow-x-auto text-muted max-h-32 overflow-y-auto">
|
||||
<pre class="text-[10px] bg-surface-alt rounded-sm p-2 overflow-x-auto text-muted max-h-32 overflow-y-auto">
|
||||
{output || '(no output)'}
|
||||
</pre>
|
||||
</div>
|
||||
@@ -1192,7 +1192,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
<button
|
||||
onClick={() => handleTriggerDiscovery(true)}
|
||||
disabled={isScanning() || !canTriggerDiscovery()}
|
||||
class="px-3 py-1.5 bg-surface-hover text-base-content text-xs rounded hover:bg-surface-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-1.5"
|
||||
class="px-3 py-1.5 bg-surface-hover text-base-content text-xs rounded-sm hover:bg-surface-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-1.5"
|
||||
>
|
||||
<Show
|
||||
when={isScanning()}
|
||||
|
||||
@@ -199,12 +199,12 @@ export const SwarmServicesDrawer: Component<{ cluster: string; swarm?: SwarmInfo
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="w-[12rem]">
|
||||
<div class="w-48">
|
||||
<SearchInput
|
||||
value={search}
|
||||
onChange={setSearch}
|
||||
placeholder={drawerPresentation.searchPlaceholder}
|
||||
inputClass="py-1 text-xs font-medium shadow-sm"
|
||||
inputClass="py-1 text-xs font-medium shadow-xs"
|
||||
typeToSearch
|
||||
clearOnEscape
|
||||
/>
|
||||
@@ -227,17 +227,17 @@ export const SwarmServicesDrawer: Component<{ cluster: string; swarm?: SwarmInfo
|
||||
>
|
||||
<div class="mt-2 flex flex-wrap gap-2 text-[11px]">
|
||||
<Show when={asTrimmedString(swarm()?.nodeRole)}>
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
{formatSwarmRoleLabel(asTrimmedString(swarm()?.nodeRole))}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={asTrimmedString(swarm()?.localState)}>
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
{formatSwarmStateLabel(asTrimmedString(swarm()?.localState))}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={typeof swarm()?.controlAvailable === 'boolean'}>
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
{formatSwarmControlLabel(swarm()?.controlAvailable)}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -245,7 +245,7 @@ export const SwarmServicesDrawer: Component<{ cluster: string; swarm?: SwarmInfo
|
||||
</Show>
|
||||
|
||||
<Show when={asTrimmedString(swarm()?.error)}>
|
||||
<div class="mt-2 rounded border border-amber-200 bg-amber-50 px-2 py-1.5 text-[10px] text-amber-800 dark:border-amber-700 dark:bg-amber-900 dark:text-amber-200">
|
||||
<div class="mt-2 rounded-sm border border-amber-200 bg-amber-50 px-2 py-1.5 text-[10px] text-amber-800 dark:border-amber-700 dark:bg-amber-900/25 dark:text-amber-200">
|
||||
{asTrimmedString(swarm()?.error)}
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
@@ -15,7 +15,7 @@ interface ErrorBoundaryProps {
|
||||
const DefaultErrorFallback: Component<{ error: Error; reset: () => void }> = (props) => {
|
||||
return (
|
||||
<div class="min-h-screen flex items-center justify-center bg-base p-4">
|
||||
<div class="max-w-md w-full bg-surface rounded-md shadow-sm p-6">
|
||||
<div class="max-w-md w-full bg-surface rounded-md shadow-xs p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<AlertTriangleIcon class="mr-3 h-12 w-12 text-red-500" aria-hidden="true" />
|
||||
<div>
|
||||
@@ -91,7 +91,7 @@ export const RouteErrorBoundary: Component<{ children: JSX.Element }> = (props)
|
||||
<ErrorBoundary
|
||||
fallback={(_error, reset) => (
|
||||
<div class="flex min-h-[60vh] items-center justify-center p-4">
|
||||
<div class="max-w-md w-full bg-surface rounded-md shadow-sm p-6">
|
||||
<div class="max-w-md w-full bg-surface rounded-md shadow-xs p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<AlertTriangleIcon class="mr-3 h-10 w-10 text-red-500" aria-hidden="true" />
|
||||
<SectionHeader
|
||||
|
||||
@@ -108,7 +108,7 @@ export function GitHubStarBanner() {
|
||||
return (
|
||||
<Show when={showPrompt()}>
|
||||
<section
|
||||
class="fixed left-4 right-20 bottom-[var(--pulse-mobile-nav-height)] z-30 max-w-sm overflow-hidden rounded-lg border border-border bg-surface text-base-content shadow-lg md:right-auto md:bottom-4"
|
||||
class="fixed left-4 right-20 bottom-(--pulse-mobile-nav-height) z-30 max-w-sm overflow-hidden rounded-lg border border-border bg-surface text-base-content shadow-lg md:right-auto md:bottom-4"
|
||||
aria-labelledby="github-star-title"
|
||||
aria-live="polite"
|
||||
>
|
||||
|
||||
@@ -152,13 +152,13 @@ export function AvailabilityProbeStatusCard(props: AvailabilityProbeStatusCardPr
|
||||
<InfoCardFrame data-testid="availability-probe-status">
|
||||
<div class="flex items-center justify-between gap-2 mb-2">
|
||||
<div class="flex min-w-0 items-center gap-1.5">
|
||||
<Activity class="h-3.5 w-3.5 text-base-content/60" aria-hidden="true" />
|
||||
<Activity class="h-3.5 w-3.5" aria-hidden="true" />
|
||||
<h3 class="truncate text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
Availability
|
||||
</h3>
|
||||
</div>
|
||||
<span
|
||||
class="shrink-0 rounded px-1.5 py-0.5 text-[10px] font-semibold"
|
||||
class="shrink-0 rounded-sm px-1.5 py-0.5 text-[10px] font-semibold"
|
||||
classList={{
|
||||
'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300':
|
||||
isFreshUp(),
|
||||
@@ -215,7 +215,7 @@ export function AvailabilityProbeStatusCard(props: AvailabilityProbeStatusCardPr
|
||||
</div>
|
||||
<For each={props.availability.locations}>
|
||||
{(location) => (
|
||||
<div class="flex items-start justify-between gap-3 rounded bg-base-200/60 px-2 py-1.5">
|
||||
<div class="flex items-start justify-between gap-3 rounded-sm bg-base-200/60 px-2 py-1.5">
|
||||
<div class="min-w-0">
|
||||
<div
|
||||
class="truncate font-medium text-base-content"
|
||||
@@ -253,11 +253,7 @@ export function AvailabilityProbeStatusCard(props: AvailabilityProbeStatusCardPr
|
||||
valueTitle={targetAddr()}
|
||||
/>
|
||||
<Show when={lastChecked()}>
|
||||
<InfoCardKeyValueRow
|
||||
label="Checked"
|
||||
value={lastChecked()}
|
||||
valueClass="text-base-content/70"
|
||||
/>
|
||||
<InfoCardKeyValueRow label="Checked" value={lastChecked()} valueClass="" />
|
||||
</Show>
|
||||
<InfoCardKeyValueRow
|
||||
label="Freshness"
|
||||
|
||||
@@ -62,7 +62,7 @@ export const ClusterDeployBanner: Component<ClusterDeployBannerProps> = (props)
|
||||
e.stopPropagation();
|
||||
props.onDeploy(info().cluster, info().cluster);
|
||||
}}
|
||||
class="inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] font-medium text-blue-700 bg-blue-100 hover:bg-blue-200 dark:text-blue-300 dark:bg-blue-900 dark:hover:bg-blue-800 transition-colors"
|
||||
class="inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-[10px] font-medium text-blue-700 bg-blue-100 hover:bg-blue-200 dark:text-blue-300 dark:bg-blue-900/25 dark:hover:bg-blue-800 transition-colors"
|
||||
>
|
||||
<RocketIcon class="w-2.5 h-2.5" />
|
||||
Review & Deploy
|
||||
|
||||
@@ -42,9 +42,9 @@ const STATUS_LABELS: Record<MaintenanceVerificationStatus, string> = {
|
||||
|
||||
const STATUS_CLASSES: Record<MaintenanceVerificationStatus, string> = {
|
||||
pending: 'bg-surface-hover text-base-content',
|
||||
healthy: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-300',
|
||||
needs_review: 'bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300',
|
||||
failed_verification: 'bg-rose-100 text-rose-700 dark:bg-rose-900 dark:text-rose-300',
|
||||
healthy: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/25 dark:text-emerald-300',
|
||||
needs_review: 'bg-amber-100 text-amber-700 dark:bg-amber-900/25 dark:text-amber-300',
|
||||
failed_verification: 'bg-rose-100 text-rose-700 dark:bg-rose-900/25 dark:text-rose-300',
|
||||
};
|
||||
|
||||
export const MaintenanceVerificationSection: Component<MaintenanceVerificationSectionProps> = (
|
||||
@@ -111,7 +111,7 @@ export const MaintenanceVerificationSection: Component<MaintenanceVerificationSe
|
||||
|
||||
return (
|
||||
<section
|
||||
class="rounded border border-border bg-surface p-3 text-sm"
|
||||
class="rounded-sm border border-border bg-surface p-3 text-sm"
|
||||
data-testid="maintenance-verification-section"
|
||||
aria-labelledby="maintenance-verification-heading"
|
||||
>
|
||||
@@ -127,7 +127,7 @@ export const MaintenanceVerificationSection: Component<MaintenanceVerificationSe
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-border bg-surface-hover px-2 py-1 text-xs font-medium hover:bg-surface"
|
||||
class="rounded-sm border border-border bg-surface-hover px-2 py-1 text-xs font-medium hover:bg-surface"
|
||||
disabled={rerunning() || !props.resourceId}
|
||||
onClick={handleRerun}
|
||||
data-testid="maintenance-verification-rerun"
|
||||
@@ -139,7 +139,7 @@ export const MaintenanceVerificationSection: Component<MaintenanceVerificationSe
|
||||
<Show
|
||||
when={hasReports()}
|
||||
fallback={
|
||||
<div class="mt-3 rounded border border-dashed border-border bg-surface-hover p-3 text-xs text-muted">
|
||||
<div class="mt-3 rounded-sm border border-dashed border-border bg-surface-hover p-3 text-xs text-muted">
|
||||
No verification reports yet. A report is written automatically the next time this
|
||||
resource exits a maintenance window.
|
||||
</div>
|
||||
@@ -149,13 +149,13 @@ export const MaintenanceVerificationSection: Component<MaintenanceVerificationSe
|
||||
<For each={reports()}>
|
||||
{(report) => (
|
||||
<li
|
||||
class="rounded border border-border bg-surface-hover p-2"
|
||||
class="rounded-sm border border-border bg-surface-hover p-2"
|
||||
data-testid="maintenance-verification-report"
|
||||
>
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span
|
||||
class={`inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium ${STATUS_CLASSES[report.status]}`}
|
||||
class={`inline-flex items-center rounded-sm px-1.5 py-0.5 text-[10px] font-medium ${STATUS_CLASSES[report.status]}`}
|
||||
data-testid={`maintenance-verification-status-${report.status}`}
|
||||
>
|
||||
{STATUS_LABELS[report.status]}
|
||||
@@ -168,7 +168,7 @@ export const MaintenanceVerificationSection: Component<MaintenanceVerificationSe
|
||||
<Show when={!report.userOutcome}>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-border bg-surface px-2 py-1 text-[11px] font-medium hover:bg-surface-hover"
|
||||
class="rounded-sm border border-border bg-surface px-2 py-1 text-[11px] font-medium hover:bg-surface-hover"
|
||||
disabled={reviewingId() === report.id}
|
||||
onClick={() => handleReview(report)}
|
||||
data-testid="maintenance-verification-review"
|
||||
|
||||
@@ -28,7 +28,7 @@ export const ReportMergeModal: Component<ReportMergeModalProps> = (props) => {
|
||||
label: badge?.label || getSourcePlatformLabel(source),
|
||||
classes:
|
||||
badge?.classes ||
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-base-content',
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-base-content',
|
||||
};
|
||||
}),
|
||||
);
|
||||
@@ -125,7 +125,7 @@ export const ReportMergeModal: Component<ReportMergeModalProps> = (props) => {
|
||||
value={notes()}
|
||||
onInput={(event) => setNotes(event.currentTarget.value)}
|
||||
rows={3}
|
||||
textareaBaseClass="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
textareaBaseClass="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
placeholder="Example: Agent running on a different host with same hostname."
|
||||
/>
|
||||
|
||||
@@ -133,7 +133,7 @@ export const ReportMergeModal: Component<ReportMergeModalProps> = (props) => {
|
||||
<div
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
class="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700 dark:border-red-900 dark:bg-red-900 dark:text-red-200"
|
||||
class="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700 dark:border-red-900/25 dark:bg-red-900/25 dark:text-red-200"
|
||||
>
|
||||
{error()}
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
const compensation = () => props.audit.result?.actionResultV2?.compensation;
|
||||
|
||||
return (
|
||||
<div class="rounded border border-border bg-surface-hover px-2 py-1.5 text-[10px]">
|
||||
<div class="rounded-sm border border-border bg-surface-hover px-2 py-1.5 text-[10px]">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="font-medium text-base-content">
|
||||
@@ -46,7 +46,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<span class={`shrink-0 rounded border px-1.5 py-0.5 font-medium ${state().className}`}>
|
||||
<span class={`shrink-0 rounded-sm border px-1.5 py-0.5 font-medium ${state().className}`}>
|
||||
{state().label}
|
||||
</span>
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
<InfoCardKeyValueRow
|
||||
label="Intent"
|
||||
value={preflight()?.intendedChange}
|
||||
valueClass="break-words"
|
||||
valueClass="wrap-break-word"
|
||||
/>
|
||||
</Show>
|
||||
<Show when={(preflight()?.safetyChecks || []).length > 0}>
|
||||
@@ -86,7 +86,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
{(() => {
|
||||
const presentation = resultPresentation()!;
|
||||
return (
|
||||
<div class={`rounded border px-2 py-1 text-[10px] ${presentation.className}`}>
|
||||
<div class={`rounded-sm border px-2 py-1 text-[10px] ${presentation.className}`}>
|
||||
<div class="font-medium">{presentation.label}</div>
|
||||
<Show when={presentation.kind === 'refusal'}>
|
||||
<div class="mt-0.5 font-medium">Refused before dispatch</div>
|
||||
@@ -110,14 +110,14 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
<Show when={apt()?.facts.length}>
|
||||
<div
|
||||
data-testid="resource-apt-action-facts"
|
||||
class="grid gap-1 rounded border border-border bg-surface px-2 py-1.5 sm:grid-cols-2"
|
||||
class="grid gap-1 rounded-sm border border-border bg-surface px-2 py-1.5 sm:grid-cols-2"
|
||||
>
|
||||
<For each={apt()?.facts ?? []}>
|
||||
{(fact) => (
|
||||
<InfoCardKeyValueRow
|
||||
label={fact.label}
|
||||
value={fact.value}
|
||||
valueClass="break-words"
|
||||
valueClass="wrap-break-word"
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
@@ -127,7 +127,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
{(() => {
|
||||
const outcome = verificationOutcome()!;
|
||||
return (
|
||||
<div class={`rounded border px-2 py-1 text-[10px] ${outcome.className}`}>
|
||||
<div class={`rounded-sm border px-2 py-1 text-[10px] ${outcome.className}`}>
|
||||
<div class="font-medium">{outcome.label}</div>
|
||||
<div class="mt-0.5 opacity-80">{outcome.detail}</div>
|
||||
<Show when={outcome.evidenceSummary}>
|
||||
@@ -144,7 +144,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
{(recovery) => (
|
||||
<div
|
||||
data-testid="resource-action-recovery-truth"
|
||||
class="rounded border border-border bg-surface px-2 py-1 text-[10px] text-base-content"
|
||||
class="rounded-sm border border-border bg-surface px-2 py-1 text-[10px] text-base-content"
|
||||
>
|
||||
<div class="font-medium">
|
||||
Recovery: {formatActionCapabilityLabel(recovery().status)}
|
||||
@@ -162,7 +162,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
{(presentation) => (
|
||||
<div
|
||||
data-testid="resource-apt-action-next-step"
|
||||
class="rounded border border-blue-200 bg-blue-50 px-2 py-1 text-[10px] text-blue-900 dark:border-blue-800 dark:bg-blue-950/40 dark:text-blue-200"
|
||||
class="rounded-sm border border-blue-200 bg-blue-50 px-2 py-1 text-[10px] text-blue-900/25 dark:border-blue-800 dark:bg-blue-950/40 dark:text-blue-200"
|
||||
>
|
||||
<span class="font-medium">Next: </span>
|
||||
{presentation().nextStep}
|
||||
@@ -176,7 +176,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
? 'border-emerald-200 bg-emerald-50 text-emerald-800 dark:border-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300'
|
||||
: 'border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-300';
|
||||
return (
|
||||
<div class={`rounded border px-2 py-1 text-[10px] ${toneClass}`}>
|
||||
<div class={`rounded-sm border px-2 py-1 text-[10px] ${toneClass}`}>
|
||||
<div class="font-medium">
|
||||
{v.success
|
||||
? 'Legacy check passed (source unclassified)'
|
||||
@@ -186,7 +186,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
<div class="mt-0.5 font-mono text-[10px] opacity-80">{v.command}</div>
|
||||
</Show>
|
||||
<Show when={v.output}>
|
||||
<div class="mt-0.5 whitespace-pre-wrap break-words">{v.output}</div>
|
||||
<div class="mt-0.5 whitespace-pre-wrap wrap-break-word">{v.output}</div>
|
||||
</Show>
|
||||
<Show when={v.note}>
|
||||
<div class="mt-0.5 italic">{v.note}</div>
|
||||
@@ -215,7 +215,7 @@ export const ResourceActionHistory: Component<ResourceActionHistoryProps> = (pro
|
||||
</div>
|
||||
|
||||
<Show when={props.error}>
|
||||
<div class="mt-2 rounded border border-amber-200 bg-amber-50 px-2 py-1.5 text-[10px] text-amber-700 dark:border-amber-700 dark:bg-amber-900 dark:text-amber-200">
|
||||
<div class="mt-2 rounded-sm border border-amber-200 bg-amber-50 px-2 py-1.5 text-[10px] text-amber-700 dark:border-amber-700 dark:bg-amber-900/25 dark:text-amber-200">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<span>{props.error}</span>
|
||||
<button
|
||||
@@ -232,7 +232,7 @@ export const ResourceActionHistory: Component<ResourceActionHistoryProps> = (pro
|
||||
<Show
|
||||
when={props.audits.length > 0}
|
||||
fallback={
|
||||
<div class="mt-3 rounded border border-dashed border-border bg-surface-hover px-2 py-2 text-[10px] text-muted">
|
||||
<div class="mt-3 rounded-sm border border-dashed border-border bg-surface-hover px-2 py-2 text-[10px] text-muted">
|
||||
No actions yet.
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -92,36 +92,36 @@ export const ResourceCorrelationSummary: Component<ResourceCorrelationSummaryPro
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div class="rounded bg-surface px-2 py-1">
|
||||
<div class="rounded-sm bg-surface px-2 py-1">
|
||||
<div class="flex flex-wrap items-center gap-1 text-[10px] text-base-content">
|
||||
{sourceHref ? (
|
||||
<a
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
href={sourceHref}
|
||||
aria-label={`Open source resource ${sourceLabel} in Infrastructure`}
|
||||
>
|
||||
{sourceLabel}
|
||||
</a>
|
||||
) : (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{sourceLabel}
|
||||
</span>
|
||||
)}
|
||||
<span class="text-muted">→</span>
|
||||
{targetHref ? (
|
||||
<a
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
href={targetHref}
|
||||
aria-label={`Open target resource ${targetLabel} in Infrastructure`}
|
||||
>
|
||||
{targetLabel}
|
||||
</a>
|
||||
) : (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{targetLabel}
|
||||
</span>
|
||||
)}
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[9px] text-muted">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[9px] text-muted">
|
||||
{typeLabel}
|
||||
</span>
|
||||
</div>
|
||||
@@ -153,14 +153,14 @@ export const ResourceCorrelationSummary: Component<ResourceCorrelationSummaryPro
|
||||
const href = buildResourceHref(dependency);
|
||||
return href ? (
|
||||
<a
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
href={href}
|
||||
aria-label={`Open dependency resource ${label} in Infrastructure`}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
) : (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
@@ -180,14 +180,14 @@ export const ResourceCorrelationSummary: Component<ResourceCorrelationSummaryPro
|
||||
const href = buildResourceHref(dependent);
|
||||
return href ? (
|
||||
<a
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
href={href}
|
||||
aria-label={`Open dependent resource ${label} in Infrastructure`}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
) : (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
@@ -218,36 +218,36 @@ export const ResourceCorrelationSummary: Component<ResourceCorrelationSummaryPro
|
||||
})
|
||||
: '';
|
||||
return (
|
||||
<div class="rounded bg-surface px-2 py-1" title={headline}>
|
||||
<div class="rounded-sm bg-surface px-2 py-1" title={headline}>
|
||||
<div class="flex flex-wrap items-center gap-1 text-[10px] text-base-content">
|
||||
{sourceHref ? (
|
||||
<a
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
href={sourceHref}
|
||||
aria-label={`Open source resource ${sourceLabel} in Infrastructure`}
|
||||
>
|
||||
{sourceLabel}
|
||||
</a>
|
||||
) : (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{sourceLabel}
|
||||
</span>
|
||||
)}
|
||||
<span class="text-muted">→</span>
|
||||
{targetHref ? (
|
||||
<a
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
href={targetHref}
|
||||
aria-label={`Open target resource ${targetLabel} in Infrastructure`}
|
||||
>
|
||||
{targetLabel}
|
||||
</a>
|
||||
) : (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{targetLabel}
|
||||
</span>
|
||||
)}
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[9px] text-muted">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[9px] text-muted">
|
||||
{patternLabel}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@ interface ResourceDetailDrawerProps {
|
||||
}
|
||||
|
||||
const TabAvailabilityNotice: Component<{ message: string }> = (props) => (
|
||||
<div class="rounded border border-dashed border-border bg-surface-hover p-4 text-sm text-muted">
|
||||
<div class="rounded-sm border border-dashed border-border bg-surface-hover p-4 text-sm text-muted">
|
||||
{props.message}
|
||||
</div>
|
||||
);
|
||||
@@ -84,7 +84,7 @@ const DrawerContent: Component<ResourceDetailDrawerProps> = (props) => {
|
||||
/>
|
||||
<h2
|
||||
id={headingId()}
|
||||
class="text-sm font-semibold text-base-content min-w-0 [overflow-wrap:anywhere] m-0"
|
||||
class="text-sm font-semibold text-base-content min-w-0 wrap-anywhere m-0"
|
||||
title={drawer.displayName()}
|
||||
>
|
||||
{drawer.displayName()}
|
||||
@@ -101,7 +101,7 @@ const DrawerContent: Component<ResourceDetailDrawerProps> = (props) => {
|
||||
<Show when={drawer.healthIssue()}>
|
||||
{(issue) => (
|
||||
<span
|
||||
class="inline-flex max-w-full items-center rounded bg-amber-100 px-1.5 py-0.5 text-[10px] font-medium text-amber-700 dark:bg-amber-900 dark:text-amber-300"
|
||||
class="inline-flex max-w-full items-center rounded-sm bg-amber-100 px-1.5 py-0.5 text-[10px] font-medium text-amber-700 dark:bg-amber-900/25 dark:text-amber-300"
|
||||
title={issue().title}
|
||||
>
|
||||
{issue().compactLabel}
|
||||
|
||||
@@ -149,7 +149,7 @@ const HostDetailsDisclosure: Component<{
|
||||
class={props.class}
|
||||
contentClass={
|
||||
props.contentClass ??
|
||||
'mt-3 flex flex-wrap gap-3 [&>*]:flex-1 [&>*]:basis-[calc(50%-0.375rem)] [&>*]:min-w-[220px] [&>*]:max-w-full [&>*]:overflow-hidden'
|
||||
'mt-3 flex flex-wrap gap-3 *:flex-1 *:basis-[calc(50%-0.375rem)] *:min-w-[220px] *:max-w-full *:overflow-hidden'
|
||||
}
|
||||
dataTestId="resource-host-details-section"
|
||||
>
|
||||
@@ -270,7 +270,7 @@ export const ResourceAccessDisclosure: Component<{
|
||||
<a
|
||||
href={link.href}
|
||||
aria-label={link.ariaLabel}
|
||||
class="inline-flex items-center rounded border border-blue-200 bg-blue-50 px-2.5 py-1 text-[11px] font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900 dark:text-blue-200 dark:hover:bg-blue-900"
|
||||
class="inline-flex items-center rounded-sm border border-blue-200 bg-blue-50 px-2.5 py-1 text-[11px] font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900/25 dark:text-blue-200 dark:hover:bg-blue-900/25"
|
||||
>
|
||||
{link.compactLabel}
|
||||
</a>
|
||||
@@ -398,7 +398,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
</TechnicalDetailsSection>
|
||||
|
||||
<Show when={resource.availability || (resource.availabilityChecks?.length ?? 0) > 0}>
|
||||
<div class="flex flex-wrap gap-3 [&>*]:min-w-[240px] [&>*]:flex-1">
|
||||
<div class="flex flex-wrap gap-3 *:min-w-[240px] *:flex-1">
|
||||
<AvailabilityProbeStatusCards
|
||||
availability={resource.availability}
|
||||
checks={resource.availabilityChecks}
|
||||
@@ -460,7 +460,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
label="Change kind"
|
||||
fieldBaseClass="space-y-1 text-[10px]"
|
||||
labelClass="text-muted"
|
||||
selectBaseClass="w-full rounded border border-border bg-base px-2 py-1 text-[11px] text-base-content"
|
||||
selectBaseClass="w-full rounded-sm border border-border bg-base px-2 py-1 text-[11px] text-base-content"
|
||||
value={drawer.timelineKindFilter()}
|
||||
onChange={(event) =>
|
||||
drawer.setTimelineKindFilter(
|
||||
@@ -476,7 +476,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
label="Source type"
|
||||
fieldBaseClass="space-y-1 text-[10px]"
|
||||
labelClass="text-muted"
|
||||
selectBaseClass="w-full rounded border border-border bg-base px-2 py-1 text-[11px] text-base-content"
|
||||
selectBaseClass="w-full rounded-sm border border-border bg-base px-2 py-1 text-[11px] text-base-content"
|
||||
value={drawer.timelineSourceTypeFilter()}
|
||||
onChange={(event) =>
|
||||
drawer.setTimelineSourceTypeFilter(
|
||||
@@ -492,7 +492,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
label="Source adapter"
|
||||
fieldBaseClass="space-y-1 text-[10px]"
|
||||
labelClass="text-muted"
|
||||
selectBaseClass="w-full rounded border border-border bg-base px-2 py-1 text-[11px] text-base-content"
|
||||
selectBaseClass="w-full rounded-sm border border-border bg-base px-2 py-1 text-[11px] text-base-content"
|
||||
value={drawer.timelineSourceAdapterFilter()}
|
||||
onChange={(event) =>
|
||||
drawer.setTimelineSourceAdapterFilter(
|
||||
@@ -525,7 +525,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
</Show>
|
||||
|
||||
<Show when={drawer.facetBundleError()}>
|
||||
<div class="mt-2 rounded border border-amber-200 bg-amber-50 px-2 py-1.5 text-[10px] text-amber-700 dark:border-amber-700 dark:bg-amber-900 dark:text-amber-200">
|
||||
<div class="mt-2 rounded-sm border border-amber-200 bg-amber-50 px-2 py-1.5 text-[10px] text-amber-700 dark:border-amber-700 dark:bg-amber-900/25 dark:text-amber-200">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<span>{drawer.facetBundleError()}</span>
|
||||
<button
|
||||
@@ -542,7 +542,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<Show
|
||||
when={drawer.sortedResourceTimeline().length > 0}
|
||||
fallback={
|
||||
<div class="mt-3 rounded border border-dashed border-border bg-surface-hover px-2 py-2 text-[10px] text-muted">
|
||||
<div class="mt-3 rounded-sm border border-dashed border-border bg-surface-hover px-2 py-2 text-[10px] text-muted">
|
||||
No events yet.
|
||||
</div>
|
||||
}
|
||||
@@ -559,7 +559,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div class="rounded border border-border bg-surface-hover px-2 py-1.5 text-[10px]">
|
||||
<div class="rounded-sm border border-border bg-surface-hover px-2 py-1.5 text-[10px]">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="font-medium text-base-content">
|
||||
@@ -588,28 +588,28 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<InfoCardKeyValueRow
|
||||
label="Actor"
|
||||
value={change.actor}
|
||||
valueClass="break-words"
|
||||
valueClass="wrap-break-word"
|
||||
/>
|
||||
</Show>
|
||||
<Show when={change.from || change.to}>
|
||||
<InfoCardKeyValueRow
|
||||
label="Transition"
|
||||
value={`${change.from || '—'} → ${change.to || '—'}`}
|
||||
valueClass="break-words"
|
||||
valueClass="wrap-break-word"
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={change.reason}>
|
||||
<div class="mt-1 rounded border border-border bg-base px-2 py-1 text-[10px] text-base-content">
|
||||
<div class="mt-1 rounded-sm border border-border bg-base px-2 py-1 text-[10px] text-base-content">
|
||||
{change.reason}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={hasMetadataEntries(change.metadata)}>
|
||||
<details class="mt-1 rounded border border-border bg-base px-2 py-1">
|
||||
<details class="mt-1 rounded-sm border border-border bg-base px-2 py-1">
|
||||
<summary class="cursor-pointer list-none text-[10px] font-medium text-muted">
|
||||
Metadata
|
||||
</summary>
|
||||
<pre class="mt-2 overflow-auto whitespace-pre-wrap break-words text-[10px] text-base-content">
|
||||
<pre class="mt-2 overflow-auto whitespace-pre-wrap wrap-break-word text-[10px] text-base-content">
|
||||
{JSON.stringify(change.metadata ?? {}, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
@@ -621,7 +621,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
{(relatedResource) => {
|
||||
const label = drawer.resolveResourceLabel(relatedResource);
|
||||
return (
|
||||
<span class="inline-flex rounded bg-surface px-1.5 py-0.5 text-[10px] text-base-content">
|
||||
<span class="inline-flex rounded-sm bg-surface px-1.5 py-0.5 text-[10px] text-base-content">
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
@@ -668,7 +668,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
>
|
||||
<div
|
||||
data-testid="resource-support-sections"
|
||||
class="flex flex-wrap gap-3 [&>*]:flex-1 [&>*]:basis-[calc(50%-0.375rem)] [&>*]:min-w-[260px] [&>*]:max-w-full [&>*]:overflow-hidden"
|
||||
class="flex flex-wrap gap-3 *:flex-1 *:basis-[calc(50%-0.375rem)] *:min-w-[260px] *:max-w-full *:overflow-hidden"
|
||||
>
|
||||
<Show when={drawer.hasTrueNASDetails()}>
|
||||
<TrueNASDetailsDisclosure drawer={drawer} class="h-full" />
|
||||
@@ -707,7 +707,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
dataTestId="resource-service-details-section"
|
||||
>
|
||||
<Show when={resource.type === 'docker-host'}>
|
||||
<div class="rounded border border-sky-200 bg-sky-50 p-3 dark:border-sky-700 dark:bg-sky-900">
|
||||
<div class="rounded-sm border border-sky-200 bg-sky-50 p-3 dark:border-sky-700 dark:bg-sky-900/25">
|
||||
<div class="mb-2 flex items-center justify-between gap-2">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-sky-700 dark:text-sky-300">
|
||||
Docker runtime
|
||||
@@ -751,7 +751,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
drawer.dockerHostCommand()?.status
|
||||
}
|
||||
>
|
||||
<div class="rounded border border-sky-200 bg-surface px-2 py-1.5 text-[10px] dark:border-sky-700">
|
||||
<div class="rounded-sm border border-sky-200 bg-surface px-2 py-1.5 text-[10px] dark:border-sky-700">
|
||||
<InfoCardKeyValueRow
|
||||
label="Action"
|
||||
value={formatIdentifierLabel(drawer.dockerHostCommand()?.type, {
|
||||
@@ -790,12 +790,12 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
</Show>
|
||||
|
||||
<Show when={drawer.dockerActionError()}>
|
||||
<div class="rounded border border-red-200 bg-red-50 px-2 py-1.5 text-[10px] text-red-700 dark:border-red-700 dark:bg-red-900 dark:text-red-200">
|
||||
<div class="rounded-sm border border-red-200 bg-red-50 px-2 py-1.5 text-[10px] text-red-700 dark:border-red-700 dark:bg-red-900/25 dark:text-red-200">
|
||||
{drawer.dockerActionError()}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={drawer.dockerActionNote()}>
|
||||
<div class="rounded border border-sky-200 bg-surface px-2 py-1.5 text-[10px] text-base-content dark:border-sky-700">
|
||||
<div class="rounded-sm border border-sky-200 bg-surface px-2 py-1.5 text-[10px] text-base-content dark:border-sky-700">
|
||||
{drawer.dockerActionNote()}
|
||||
</div>
|
||||
</Show>
|
||||
@@ -864,7 +864,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
pbs().connectionHealth,
|
||||
);
|
||||
return (
|
||||
<div class="rounded border border-indigo-200 bg-indigo-50 p-3 dark:border-indigo-700 dark:bg-indigo-900">
|
||||
<div class="rounded-sm border border-indigo-200 bg-indigo-50 p-3 dark:border-indigo-700 dark:bg-indigo-900/25">
|
||||
<div class="mb-2 flex items-center justify-between gap-2">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-indigo-700 dark:text-indigo-300">
|
||||
PBS
|
||||
@@ -903,20 +903,20 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<Show when={drawer.showPbsJobDetail()}>
|
||||
<div class="space-y-1.5 border-t border-indigo-200 pt-2 dark:border-indigo-700">
|
||||
<div class="grid grid-cols-2 gap-2 md:grid-cols-3">
|
||||
<div class="rounded border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700">
|
||||
<div class="rounded-sm border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700">
|
||||
<div class="text-[10px] text-muted">Datastores</div>
|
||||
<div class="text-sm font-semibold text-base-content">
|
||||
{formatInteger(pbs().datastoreCount)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700">
|
||||
<div class="rounded-sm border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700">
|
||||
<div class="text-[10px] text-muted">Jobs</div>
|
||||
<div class="text-sm font-semibold text-base-content">
|
||||
{formatInteger(drawer.pbsJobTotal())}
|
||||
</div>
|
||||
</div>
|
||||
<Show when={drawer.pbsActiveTaskCount() > 0}>
|
||||
<div class="rounded border border-emerald-200 bg-emerald-50 px-2 py-1.5 dark:border-emerald-700 dark:bg-emerald-950/40">
|
||||
<div class="rounded-sm border border-emerald-200 bg-emerald-50 px-2 py-1.5 dark:border-emerald-700 dark:bg-emerald-950/40">
|
||||
<div class="text-[10px] text-muted">Active</div>
|
||||
<div class="text-sm font-semibold text-emerald-700 dark:text-emerald-300">
|
||||
{formatInteger(drawer.pbsActiveTaskCount())}
|
||||
@@ -927,7 +927,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<Show when={drawer.pbsActiveTaskCount() > 0}>
|
||||
<div
|
||||
data-testid="pbs-active-tasks"
|
||||
class="rounded border border-emerald-200 bg-surface px-2 py-1.5 dark:border-emerald-700"
|
||||
class="rounded-sm border border-emerald-200 bg-surface px-2 py-1.5 dark:border-emerald-700"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="text-[10px] font-medium uppercase tracking-wide text-muted">
|
||||
@@ -977,7 +977,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<Show when={pbsJobHealthEvidence().evidenceCount > 0}>
|
||||
<div
|
||||
data-testid="pbs-job-health-evidence"
|
||||
class="rounded border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700"
|
||||
class="rounded-sm border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700"
|
||||
>
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<span class="text-[10px] font-medium uppercase tracking-wide text-muted">
|
||||
@@ -990,7 +990,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<div class="mt-2 space-y-2 border-t border-indigo-200 pt-2 dark:border-indigo-700">
|
||||
<For each={pbsJobHealthEvidence().entries}>
|
||||
{(entry) => (
|
||||
<div class="min-w-0 rounded border border-border bg-surface-hover px-2 py-1.5 text-[10px]">
|
||||
<div class="min-w-0 rounded-sm border border-border bg-surface-hover px-2 py-1.5 text-[10px]">
|
||||
<div class="flex flex-wrap items-start justify-between gap-2">
|
||||
<div class="min-w-0">
|
||||
<div
|
||||
@@ -1071,7 +1071,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
<details class="rounded border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700">
|
||||
<details class="rounded-sm border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700">
|
||||
<summary class="flex cursor-pointer list-none items-center justify-between text-[10px] font-medium text-muted">
|
||||
<span>Types</span>
|
||||
<span class="text-muted">
|
||||
@@ -1113,7 +1113,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
pmg().connectionHealth,
|
||||
);
|
||||
return (
|
||||
<div class="rounded border border-rose-200 bg-rose-50 p-3 dark:border-rose-700 dark:bg-rose-900">
|
||||
<div class="rounded-sm border border-rose-200 bg-rose-50 p-3 dark:border-rose-700 dark:bg-rose-900/25">
|
||||
<div class="mb-2 flex items-center justify-between gap-2">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-rose-700 dark:text-rose-300">
|
||||
PMG
|
||||
@@ -1145,7 +1145,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<Show when={drawer.showPmgMailFlowDetail()}>
|
||||
<div class="space-y-1.5 border-t border-rose-200 pt-2 dark:border-rose-700">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="rounded border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<div class="rounded-sm border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<div class="text-[10px] text-muted">Queue</div>
|
||||
<div
|
||||
class={`text-sm font-semibold ${drawer.pmgQueueBacklog() > 0 ? 'text-amber-600 dark:text-amber-400' : 'text-base-content'}`}
|
||||
@@ -1153,7 +1153,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
{formatInteger(pmg().queueTotal)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<div class="rounded-sm border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<div class="text-[10px] text-muted">Backlog</div>
|
||||
<div
|
||||
class={`text-sm font-semibold ${drawer.pmgQueueBacklog() > 0 ? 'text-amber-600 dark:text-amber-400' : 'text-base-content'}`}
|
||||
@@ -1165,7 +1165,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<Show when={pmg().nodeCount || drawer.pmgUpdatedRelative()}>
|
||||
<div
|
||||
data-testid="pmg-support-context"
|
||||
class="space-y-1.5 rounded border border-dashed border-rose-200 bg-surface px-2 py-1.5 text-[10px] dark:border-rose-700"
|
||||
class="space-y-1.5 rounded-sm border border-dashed border-rose-200 bg-surface px-2 py-1.5 text-[10px] dark:border-rose-700"
|
||||
>
|
||||
<Show when={pmg().nodeCount}>
|
||||
<InfoCardKeyValueRow
|
||||
@@ -1186,7 +1186,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<details class="rounded border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<details class="rounded-sm border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<summary class="cursor-pointer list-none text-[10px] font-medium text-muted">
|
||||
Queue detail
|
||||
</summary>
|
||||
@@ -1204,7 +1204,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
</For>
|
||||
</div>
|
||||
</details>
|
||||
<details class="rounded border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<details class="rounded-sm border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<summary class="cursor-pointer list-none text-[10px] font-medium text-muted">
|
||||
Mail detail
|
||||
</summary>
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ export const ResourceDetailDrawerSupportDisclosure: Component<
|
||||
return (
|
||||
<div
|
||||
data-testid={props.dataTestId}
|
||||
class={`rounded border border-border bg-surface px-2 py-1.5 ${props.class ?? ''}`}
|
||||
class={`rounded-sm border border-border bg-surface px-2 py-1.5 ${props.class ?? ''}`}
|
||||
>
|
||||
<div class="flex min-w-0 items-center justify-between gap-2">
|
||||
<div class="flex min-w-0 flex-1 items-baseline gap-2">
|
||||
|
||||
@@ -172,7 +172,7 @@ const dockerSection = (docker: NonNullable<Resource['docker']>): DetailSection =
|
||||
<For each={labelEntries}>
|
||||
{([key, value]) => (
|
||||
<span
|
||||
class="inline-flex max-w-full items-center truncate rounded bg-surface-alt px-1.5 py-0.5 text-[10px]"
|
||||
class="inline-flex max-w-full items-center truncate rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]"
|
||||
title={value ? `${key}: ${value}` : key}
|
||||
>
|
||||
{key}
|
||||
@@ -203,7 +203,7 @@ export const InlineResourceSummaryTables: Component<ResourceSummaryPresentationP
|
||||
<For each={props.drawer.identityIpValues()}>
|
||||
{(ip) => (
|
||||
<span
|
||||
class="inline-flex items-center rounded bg-blue-100 px-1.5 py-0.5 text-[10px] text-blue-700 dark:bg-blue-900 dark:text-blue-200"
|
||||
class="inline-flex items-center rounded-sm bg-blue-100 px-1.5 py-0.5 text-[10px] text-blue-700 dark:bg-blue-900/25 dark:text-blue-200"
|
||||
title={ip}
|
||||
>
|
||||
{ip}
|
||||
@@ -228,7 +228,7 @@ export const InlineResourceSummaryTables: Component<ResourceSummaryPresentationP
|
||||
<For each={props.drawer.aliasPreviewValues()}>
|
||||
{(value) => (
|
||||
<span
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]"
|
||||
title={value}
|
||||
>
|
||||
{value}
|
||||
@@ -236,7 +236,7 @@ export const InlineResourceSummaryTables: Component<ResourceSummaryPresentationP
|
||||
)}
|
||||
</For>
|
||||
<Show when={props.drawer.hasAliasOverflow()}>
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-muted">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-muted">
|
||||
+
|
||||
{props.drawer.identityAliasValues().length -
|
||||
props.drawer.aliasPreviewValues().length}
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface ResourceFacetSummaryProps {
|
||||
}
|
||||
|
||||
const badgeBase =
|
||||
'inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium whitespace-nowrap';
|
||||
'inline-flex items-center rounded-sm px-1.5 py-0.5 text-[10px] font-medium whitespace-nowrap';
|
||||
|
||||
const countLabel = (count: number, singular: string, plural = `${singular}s`) =>
|
||||
`${count} ${count === 1 ? singular : plural}`;
|
||||
@@ -42,7 +42,7 @@ const buildFacetBadges = (
|
||||
badges.push({
|
||||
label: `Timeline ${changeCount}`,
|
||||
title: countLabel(changeCount, 'timeline event'),
|
||||
className: `${badgeBase} bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300`,
|
||||
className: `${badgeBase} bg-amber-100 text-amber-700 dark:bg-amber-900/25 dark:text-amber-300`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ const redactionLabelsRow = (labels: string[]): DetailRow | null => {
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<For each={labels}>
|
||||
{(label) => (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{label}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -575,7 +575,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</header>
|
||||
|
||||
<Show when={activeMaintenanceWindow()}>
|
||||
<div class="rounded border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-200">
|
||||
<div class="rounded-sm border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-200">
|
||||
<span class="font-semibold">Maintenance window active.</span> Attention on this resource
|
||||
<Show when={activeMaintenanceWindow()!.maintenanceScope === 'resource_and_descendants'}>
|
||||
{' '}
|
||||
@@ -594,7 +594,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</Show>
|
||||
|
||||
<Show when={scheduledMaintenanceWindow() && !activeMaintenanceWindow()}>
|
||||
<div class="rounded border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-800 dark:border-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
||||
<div class="rounded-sm border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-800 dark:border-blue-800 dark:bg-blue-900/25 dark:text-blue-200">
|
||||
<span class="font-semibold">Maintenance window scheduled.</span> Attention will be paused
|
||||
from {new Date(scheduledMaintenanceWindow()!.maintenanceStartAt!).toLocaleString()} until{' '}
|
||||
{new Date(scheduledMaintenanceWindow()!.maintenanceEndAt!).toLocaleString()}.
|
||||
@@ -607,7 +607,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</Show>
|
||||
|
||||
<Show when={persisted()?.maintenanceRecurrence && !activeMaintenanceWindow()}>
|
||||
<div class="rounded border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-800 dark:border-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
||||
<div class="rounded-sm border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-800 dark:border-blue-800 dark:bg-blue-900/25 dark:text-blue-200">
|
||||
<span class="font-semibold">Recurring maintenance configured.</span>{' '}
|
||||
{persisted()!
|
||||
.maintenanceRecurrence!.weekdays.map((weekday) => weekday.slice(0, 3))
|
||||
@@ -672,7 +672,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleOpenScheduler}
|
||||
disabled={saving()}
|
||||
class="min-h-11 self-start rounded border border-border px-2.5 py-1 text-xs font-medium text-base-content hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
class="min-h-11 self-start rounded-sm border border-border px-2.5 py-1 text-xs font-medium text-base-content hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
Schedule window
|
||||
</button>
|
||||
@@ -683,7 +683,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleOpenScheduler}
|
||||
disabled={saving()}
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-base-content border border-border rounded hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-base-content border border-border rounded-sm hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
Edit window
|
||||
</button>
|
||||
@@ -691,7 +691,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleClearMaintenanceWindow}
|
||||
disabled={saving()}
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-amber-700 border border-amber-200 rounded hover:bg-amber-50 dark:text-amber-300 dark:border-amber-800 dark:hover:bg-amber-900 disabled:opacity-50 sm:min-h-0"
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-amber-700 border border-amber-200 rounded-sm hover:bg-amber-50 dark:text-amber-300 dark:border-amber-800 dark:hover:bg-amber-900/25 disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
Cancel window
|
||||
</button>
|
||||
@@ -701,20 +701,20 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</Show>
|
||||
|
||||
<Show when={schedulerOpen()}>
|
||||
<div class="rounded border border-border bg-surface-alt/40 px-3 py-3 space-y-2">
|
||||
<div class="rounded-sm border border-border px-3 py-3 space-y-2">
|
||||
<div class="text-xs font-semibold text-base-content">Schedule maintenance window</div>
|
||||
<div class="inline-flex gap-0.5 rounded border border-border bg-surface p-0.5 text-xs">
|
||||
<div class="inline-flex gap-0.5 rounded-sm border border-border bg-surface p-0.5 text-xs">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setScheduleKind('once')}
|
||||
class={`rounded px-2.5 py-1 ${scheduleKind() === 'once' ? 'bg-blue-600 text-white' : 'text-muted hover:bg-surface-hover'}`}
|
||||
class={`rounded-sm px-2.5 py-1 ${scheduleKind() === 'once' ? 'bg-blue-600 text-white' : 'text-muted hover:bg-surface-hover'}`}
|
||||
>
|
||||
One time
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setScheduleKind('recurring')}
|
||||
class={`rounded px-2.5 py-1 ${scheduleKind() === 'recurring' ? 'bg-blue-600 text-white' : 'text-muted hover:bg-surface-hover'}`}
|
||||
class={`rounded-sm px-2.5 py-1 ${scheduleKind() === 'recurring' ? 'bg-blue-600 text-white' : 'text-muted hover:bg-surface-hover'}`}
|
||||
>
|
||||
Recurring
|
||||
</button>
|
||||
@@ -728,7 +728,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="datetime-local"
|
||||
value={scheduleStart()}
|
||||
onInput={(e) => setScheduleStart(e.currentTarget.value)}
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded border border-border bg-surface px-2 py-1 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded-sm border border-border bg-surface px-2 py-1 text-base-content focus:outline-hidden focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
disabled={saving()}
|
||||
/>
|
||||
</label>
|
||||
@@ -738,7 +738,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="datetime-local"
|
||||
value={scheduleEnd()}
|
||||
onInput={(e) => setScheduleEnd(e.currentTarget.value)}
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded border border-border bg-surface px-2 py-1 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded-sm border border-border bg-surface px-2 py-1 text-base-content focus:outline-hidden focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
disabled={saving()}
|
||||
/>
|
||||
</label>
|
||||
@@ -751,7 +751,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={() => applyPresetDuration(hours)}
|
||||
disabled={saving()}
|
||||
class="min-h-11 min-w-11 px-1.5 py-0.5 rounded border border-border hover:bg-surface-hover disabled:opacity-50 sm:min-h-0 sm:min-w-0"
|
||||
class="min-h-11 min-w-11 px-1.5 py-0.5 rounded-sm border border-border hover:bg-surface-hover disabled:opacity-50 sm:min-h-0 sm:min-w-0"
|
||||
>
|
||||
{hours}h
|
||||
</button>
|
||||
@@ -771,7 +771,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
aria-pressed={scheduleWeekdays().includes(value)}
|
||||
onClick={() => toggleMaintenanceWeekday(value)}
|
||||
class={`min-h-9 rounded border px-2 text-xs ${scheduleWeekdays().includes(value) ? 'border-blue-600 bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-200' : 'border-border text-muted hover:bg-surface-hover'}`}
|
||||
class={`min-h-9 rounded-sm border px-2 text-xs ${scheduleWeekdays().includes(value) ? 'border-blue-600 bg-blue-50 text-blue-700 dark:bg-blue-950/25 dark:text-blue-200' : 'border-border text-muted hover:bg-surface-hover'}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
@@ -786,7 +786,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="time"
|
||||
value={scheduleRecurringStart()}
|
||||
onInput={(event) => setScheduleRecurringStart(event.currentTarget.value)}
|
||||
class="mt-0.5 min-h-11 w-full rounded border border-border bg-surface px-2 text-xs text-base-content sm:min-h-0"
|
||||
class="mt-0.5 min-h-11 w-full rounded-sm border border-border bg-surface px-2 text-xs text-base-content sm:min-h-0"
|
||||
/>
|
||||
</label>
|
||||
<label class="block">
|
||||
@@ -795,7 +795,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="time"
|
||||
value={scheduleRecurringEnd()}
|
||||
onInput={(event) => setScheduleRecurringEnd(event.currentTarget.value)}
|
||||
class="mt-0.5 min-h-11 w-full rounded border border-border bg-surface px-2 text-xs text-base-content sm:min-h-0"
|
||||
class="mt-0.5 min-h-11 w-full rounded-sm border border-border bg-surface px-2 text-xs text-base-content sm:min-h-0"
|
||||
/>
|
||||
</label>
|
||||
<label class="block">
|
||||
@@ -804,7 +804,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="text"
|
||||
value={scheduleTimezone()}
|
||||
onInput={(event) => setScheduleTimezone(event.currentTarget.value)}
|
||||
class="mt-0.5 min-h-11 w-full rounded border border-border bg-surface px-2 text-xs text-base-content sm:min-h-0"
|
||||
class="mt-0.5 min-h-11 w-full rounded-sm border border-border bg-surface px-2 text-xs text-base-content sm:min-h-0"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -835,7 +835,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
value={scheduleReason()}
|
||||
onInput={(e) => setScheduleReason(e.currentTarget.value)}
|
||||
placeholder="e.g. Q3 storage upgrade, kernel patch reboot"
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded border border-border bg-surface px-2 py-1 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded-sm border border-border bg-surface px-2 py-1 text-base-content focus:outline-hidden focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
disabled={saving()}
|
||||
maxLength={200}
|
||||
/>
|
||||
@@ -850,7 +850,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={() => setSchedulerOpen(false)}
|
||||
disabled={saving()}
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded transition-colors disabled:opacity-50 sm:min-h-0"
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded-sm transition-colors disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -858,7 +858,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleScheduleSave}
|
||||
disabled={saving() || Boolean(scheduleValidationError())}
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 rounded transition-colors sm:min-h-0"
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 rounded-sm transition-colors sm:min-h-0"
|
||||
>
|
||||
{saving() ? 'Saving…' : 'Save window'}
|
||||
</button>
|
||||
@@ -886,7 +886,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</div>
|
||||
|
||||
<Show when={autoRemediationEnabled()}>
|
||||
<fieldset class="space-y-2 rounded border border-border bg-surface-alt/40 px-3 py-2.5">
|
||||
<fieldset class="space-y-2 rounded-sm border border-border px-3 py-2.5">
|
||||
<legend class="px-1 text-xs font-semibold text-base-content">
|
||||
Actions allowed by this limit
|
||||
</legend>
|
||||
@@ -945,7 +945,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
value={autoWindowStart()}
|
||||
onInput={(event) => setAutoWindowStart(event.currentTarget.value)}
|
||||
disabled={saving()}
|
||||
class="mt-0.5 w-full rounded border border-border bg-surface px-2 py-1 text-xs text-base-content"
|
||||
class="mt-0.5 w-full rounded-sm border border-border bg-surface px-2 py-1 text-xs text-base-content"
|
||||
/>
|
||||
</label>
|
||||
<label class="block text-[11px] text-muted">
|
||||
@@ -955,7 +955,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
value={autoWindowEnd()}
|
||||
onInput={(event) => setAutoWindowEnd(event.currentTarget.value)}
|
||||
disabled={saving()}
|
||||
class="mt-0.5 w-full rounded border border-border bg-surface px-2 py-1 text-xs text-base-content"
|
||||
class="mt-0.5 w-full rounded-sm border border-border bg-surface px-2 py-1 text-xs text-base-content"
|
||||
/>
|
||||
</label>
|
||||
<label class="block text-[11px] text-muted">
|
||||
@@ -965,7 +965,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
value={autoWindowTimezone()}
|
||||
onInput={(event) => setAutoWindowTimezone(event.currentTarget.value)}
|
||||
disabled={saving()}
|
||||
class="mt-0.5 w-full rounded border border-border bg-surface px-2 py-1 text-xs text-base-content"
|
||||
class="mt-0.5 w-full rounded-sm border border-border bg-surface px-2 py-1 text-xs text-base-content"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -1014,7 +1014,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</div>
|
||||
|
||||
<Show when={lifecycleState() === 'retired'}>
|
||||
<div class="rounded border border-border bg-surface-alt px-3 py-2 text-xs text-base-content">
|
||||
<div class="rounded-sm border border-border bg-surface-alt px-3 py-2 text-xs text-base-content">
|
||||
Retired resources remain in {inventoryOwnership().ownerLabel} inventory and keep their
|
||||
Pulse history. All alert attention and automated remediation are disabled until the
|
||||
lifecycle returns to Active.
|
||||
@@ -1041,7 +1041,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</div>
|
||||
|
||||
<Show when={confirmingLock()}>
|
||||
<div class="rounded border border-red-300 bg-red-50 px-3 py-2.5 text-xs text-red-900 dark:border-red-800 dark:bg-red-950 dark:text-red-100">
|
||||
<div class="rounded-sm border border-red-300 bg-red-50 px-3 py-2.5 text-xs text-red-900/25 dark:border-red-800 dark:bg-red-950/25 dark:text-red-100">
|
||||
<p class="font-semibold">Lock this resource against all automated remediation?</p>
|
||||
<p class="mt-1 leading-relaxed">
|
||||
Pulse will refuse every dispatch targeting this resource, including approved actions
|
||||
@@ -1051,14 +1051,14 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
<button
|
||||
type="button"
|
||||
onClick={confirmLockToggle}
|
||||
class="rounded border border-red-400 bg-white px-2 py-1 text-xs font-medium text-red-900 hover:bg-red-100 dark:border-red-700 dark:bg-red-900 dark:text-red-100 dark:hover:bg-red-800"
|
||||
class="rounded-sm border border-red-400 bg-white px-2 py-1 text-xs font-medium text-red-900/25 hover:bg-red-100 dark:border-red-700 dark:bg-red-900/25 dark:text-red-100 dark:hover:bg-red-800"
|
||||
>
|
||||
Lock this resource
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={cancelLockToggle}
|
||||
class="rounded border border-border bg-surface px-2 py-1 text-xs font-medium text-muted hover:bg-surface-hover"
|
||||
class="rounded-sm border border-border bg-surface px-2 py-1 text-xs font-medium text-muted hover:bg-surface-hover"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -1072,7 +1072,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleClear}
|
||||
disabled={saving()}
|
||||
class="px-2.5 py-1 text-xs font-medium text-muted hover:text-base-content hover:bg-surface-hover rounded transition-colors disabled:opacity-50"
|
||||
class="px-2.5 py-1 text-xs font-medium text-muted hover:text-base-content hover:bg-surface-hover rounded-sm transition-colors disabled:opacity-50"
|
||||
>
|
||||
Clear all overrides
|
||||
</button>
|
||||
@@ -1082,7 +1082,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleDiscard}
|
||||
disabled={saving()}
|
||||
class="px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded transition-colors disabled:opacity-50"
|
||||
class="px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded-sm transition-colors disabled:opacity-50"
|
||||
>
|
||||
Discard
|
||||
</button>
|
||||
@@ -1090,7 +1090,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleSave}
|
||||
disabled={saving() || Boolean(autoPolicyValidationError())}
|
||||
class="px-2.5 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 rounded transition-colors"
|
||||
class="px-2.5 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 rounded-sm transition-colors"
|
||||
>
|
||||
{saving() ? 'Saving…' : 'Save overrides'}
|
||||
</button>
|
||||
|
||||
@@ -62,7 +62,7 @@ export const ResourcePolicySummary: Component<ResourcePolicySummaryProps> = (pro
|
||||
<div class="mt-2 flex flex-wrap gap-1">
|
||||
<For each={redactionSummaries()}>
|
||||
{(item) => (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{item.label} {item.count}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -350,7 +350,7 @@ export const UnifiedResourceHostTableCard: Component<UnifiedResourceHostTableCar
|
||||
'cursor-pointer transition-all duration-200 relative group hover:bg-surface-hover';
|
||||
|
||||
if (isExpanded()) {
|
||||
return 'cursor-pointer transition-all duration-200 relative z-10 group bg-blue-50 dark:bg-blue-900';
|
||||
return 'cursor-pointer transition-all duration-200 relative z-10 group bg-blue-50 dark:bg-blue-900/25';
|
||||
}
|
||||
|
||||
let className = baseHover;
|
||||
@@ -433,7 +433,7 @@ export const UnifiedResourceHostTableCard: Component<UnifiedResourceHostTableCar
|
||||
<Show when={healthIssue()}>
|
||||
{(issue) => (
|
||||
<span
|
||||
class="hidden shrink-0 whitespace-nowrap rounded bg-amber-100 px-1 text-[9px] font-medium text-amber-700 dark:bg-amber-900 dark:text-amber-300 lg:inline"
|
||||
class="hidden shrink-0 whitespace-nowrap rounded-sm bg-amber-100 px-1 text-[9px] font-medium text-amber-700 dark:bg-amber-900/25 dark:text-amber-300 lg:inline"
|
||||
title={issue().title}
|
||||
>
|
||||
{issue().compactLabel}
|
||||
@@ -443,7 +443,7 @@ export const UnifiedResourceHostTableCard: Component<UnifiedResourceHostTableCar
|
||||
<Show when={availabilityProbe()}>
|
||||
{(probe) => (
|
||||
<span
|
||||
class={`hidden shrink-0 whitespace-nowrap rounded px-1 text-[9px] font-medium lg:inline ${probe().toneClassName}`}
|
||||
class={`hidden shrink-0 whitespace-nowrap rounded-sm px-1 text-[9px] font-medium lg:inline ${probe().toneClassName}`}
|
||||
title={probe().detailLabel}
|
||||
>
|
||||
{probe().methodLabel}
|
||||
@@ -470,7 +470,7 @@ export const UnifiedResourceHostTableCard: Component<UnifiedResourceHostTableCar
|
||||
recentChanges={resource.recentChanges}
|
||||
counts={resource.facetCounts}
|
||||
maxVisibleBadges={1}
|
||||
class="hidden max-w-[48%] shrink-0 flex-nowrap overflow-hidden lg:flex"
|
||||
class="hidden max-w-[48%] shrink-0 flex-nowrap! overflow-hidden lg:flex"
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -142,10 +142,10 @@ export const UnifiedResourcePBSTableSection: Component<UnifiedResourcePBSTableSe
|
||||
|
||||
const rowClass = createMemo(() => {
|
||||
const baseBorder = 'border-b border-border-subtle';
|
||||
const baseHover = `cursor-pointer transition-all duration-200 relative hover:shadow-sm group ${baseBorder}`;
|
||||
const baseHover = `cursor-pointer transition-all duration-200 relative hover:shadow-xs group ${baseBorder}`;
|
||||
|
||||
if (isExpanded()) {
|
||||
return `cursor-pointer transition-all duration-200 relative hover:shadow-sm z-10 group bg-blue-50 dark:bg-blue-900 ${baseBorder}`;
|
||||
return `cursor-pointer transition-all duration-200 relative hover:shadow-xs z-10 group bg-blue-50 dark:bg-blue-900/25 ${baseBorder}`;
|
||||
}
|
||||
|
||||
let className = baseHover;
|
||||
@@ -209,7 +209,7 @@ export const UnifiedResourcePBSTableSection: Component<UnifiedResourcePBSTableSe
|
||||
recentChanges={resource.recentChanges}
|
||||
counts={resource.facetCounts}
|
||||
maxVisibleBadges={1}
|
||||
class="hidden max-w-[48%] shrink-0 flex-nowrap overflow-hidden lg:flex"
|
||||
class="hidden max-w-[48%] shrink-0 flex-nowrap! overflow-hidden lg:flex"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -315,7 +315,7 @@ export const UnifiedResourcePBSTableSection: Component<UnifiedResourcePBSTableSe
|
||||
{(link) => (
|
||||
<a
|
||||
href={link().href}
|
||||
class="inline-flex items-center rounded border border-blue-200 bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900 dark:text-blue-200 dark:hover:bg-blue-800"
|
||||
class="inline-flex items-center rounded-sm border border-blue-200 bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900/25 dark:text-blue-200 dark:hover:bg-blue-800"
|
||||
title={link().label}
|
||||
aria-label={link().ariaLabel}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
|
||||
@@ -156,10 +156,10 @@ export const UnifiedResourcePMGTableSection: Component<UnifiedResourcePMGTableSe
|
||||
|
||||
const rowClass = createMemo(() => {
|
||||
const baseBorder = 'border-b border-border-subtle';
|
||||
const baseHover = `cursor-pointer transition-all duration-200 relative hover:shadow-sm group ${baseBorder}`;
|
||||
const baseHover = `cursor-pointer transition-all duration-200 relative hover:shadow-xs group ${baseBorder}`;
|
||||
|
||||
if (isExpanded()) {
|
||||
return `cursor-pointer transition-all duration-200 relative hover:shadow-sm z-10 group bg-blue-50 dark:bg-blue-900 ${baseBorder}`;
|
||||
return `cursor-pointer transition-all duration-200 relative hover:shadow-xs z-10 group bg-blue-50 dark:bg-blue-900/25 ${baseBorder}`;
|
||||
}
|
||||
|
||||
let className = baseHover;
|
||||
@@ -223,7 +223,7 @@ export const UnifiedResourcePMGTableSection: Component<UnifiedResourcePMGTableSe
|
||||
recentChanges={resource.recentChanges}
|
||||
counts={resource.facetCounts}
|
||||
maxVisibleBadges={1}
|
||||
class="hidden max-w-[48%] shrink-0 flex-nowrap overflow-hidden lg:flex"
|
||||
class="hidden max-w-[48%] shrink-0 flex-nowrap! overflow-hidden lg:flex"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -355,7 +355,7 @@ export const UnifiedResourcePMGTableSection: Component<UnifiedResourcePMGTableSe
|
||||
{(link) => (
|
||||
<a
|
||||
href={link().href}
|
||||
class="inline-flex items-center rounded border border-blue-200 bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900 dark:text-blue-200 dark:hover:bg-blue-800"
|
||||
class="inline-flex items-center rounded-sm border border-blue-200 bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900/25 dark:text-blue-200 dark:hover:bg-blue-800"
|
||||
title={link().label}
|
||||
aria-label={link().ariaLabel}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
|
||||
@@ -58,7 +58,7 @@ export const UnifiedResourceSourceBadgeCell: Component<UnifiedResourceSourceBadg
|
||||
</For>
|
||||
<Show when={hiddenBadgeCount() > 0}>
|
||||
<span
|
||||
class="inline-flex min-w-0 max-w-full items-center overflow-hidden rounded bg-surface-alt px-1 py-0.5 text-[10px] font-medium text-muted"
|
||||
class="inline-flex min-w-0 max-w-full items-center overflow-hidden rounded-sm bg-surface-alt px-1 py-0.5 text-[10px] font-medium text-muted"
|
||||
aria-label={`Additional systems: ${hiddenBadges()
|
||||
.map((badge) => badge.title ?? badge.label)
|
||||
.join(', ')}`}
|
||||
|
||||
+1
-1
@@ -610,7 +610,7 @@ describe('ResourceDetailDrawer change history section', () => {
|
||||
await within(contextSection).findByText('Analysis');
|
||||
expect(contextSection.querySelector('table')).toBeTruthy();
|
||||
expect(contextSection.querySelector('tbody')).toBeTruthy();
|
||||
expect(contextSection.querySelectorAll('tbody[class*="shadow-sm"]')).toHaveLength(1);
|
||||
expect(contextSection.querySelectorAll('tbody[class*="shadow-xs"]')).toHaveLength(1);
|
||||
expect(screen.getByText('Health')).toBeInTheDocument();
|
||||
expect(screen.getByText('A · 92/100')).toBeInTheDocument();
|
||||
expect(screen.getByText('Trend')).toBeInTheDocument();
|
||||
|
||||
+2
-2
@@ -348,7 +348,7 @@ describe('ResourceDetailDrawer runtime and identity cards', () => {
|
||||
expect(
|
||||
getByTestId('resource-host-details-section')
|
||||
.querySelector('.mt-3.flex.flex-wrap')
|
||||
?.classList.contains('[&>*]:min-w-[220px]'),
|
||||
?.classList.contains('*:min-w-[220px]'),
|
||||
).toBe(true);
|
||||
expect(getByText('Network')).toBeInTheDocument();
|
||||
expect(getByText('Disks')).toBeInTheDocument();
|
||||
@@ -977,7 +977,7 @@ describe('ResourceDetailDrawer runtime and identity cards', () => {
|
||||
const contextSection = getByTestId('resource-investigation-context');
|
||||
expect(contextSection.querySelector('table')).toBeTruthy();
|
||||
expect(contextSection.querySelector('tbody')).toBeTruthy();
|
||||
expect(contextSection.querySelectorAll('tbody[class*="shadow-sm"]')).toHaveLength(1);
|
||||
expect(contextSection.querySelectorAll('tbody[class*="shadow-xs"]')).toHaveLength(1);
|
||||
expect(getByText('Health')).toBeInTheDocument();
|
||||
expect(getByText('A · 92/100')).toBeInTheDocument();
|
||||
expect(getByText('Trend')).toBeInTheDocument();
|
||||
|
||||
+1
-1
@@ -250,7 +250,7 @@ describe('ResourceDetailDrawer TrueNAS details', () => {
|
||||
expect(getByTestId('resource-technical-summary-section').querySelector('table')).toBeTruthy();
|
||||
expect(() => getByTestId('resource-current-state-section')).toThrow();
|
||||
expect(getByTestId('resource-identity-section').tagName).toBe('TBODY');
|
||||
expect(summary.querySelector('[class*="shadow-sm"]')).not.toBeNull();
|
||||
expect(summary.querySelector('[class*="shadow-xs"]')).not.toBeNull();
|
||||
expect(getByRole('button', { name: 'Hide TrueNAS' })).toBeInTheDocument();
|
||||
const trueNASSection = getByTestId('resource-truenas-details-section');
|
||||
expect(
|
||||
|
||||
+1
-1
@@ -200,7 +200,7 @@ describe('ResourceOperatorStateSection', () => {
|
||||
expect(sectionSource).toContain(
|
||||
'flex flex-col items-stretch justify-between gap-3 border-t border-border-subtle pt-2 sm:flex-row sm:items-center',
|
||||
);
|
||||
expect(sectionSource).toContain('min-h-11 self-start rounded border border-border');
|
||||
expect(sectionSource).toContain('min-h-11 self-start rounded-sm border border-border');
|
||||
expect(sectionSource).toContain(
|
||||
'min-h-11 px-2.5 py-1 text-xs font-medium text-white bg-blue-600',
|
||||
);
|
||||
|
||||
+1
-1
@@ -823,7 +823,7 @@ describe('UnifiedResourceTable performance contract', () => {
|
||||
expect(source).toContain('createSummaryInteractiveRowPreviewHandlers');
|
||||
expect(source).toContain('SummaryRowActionButton');
|
||||
expect(source).toContain('hideWhenRowTappableOnMobile');
|
||||
expect(source).not.toContain('bg-blue-50 dark:bg-blue-900 ring-1 ring-blue-300');
|
||||
expect(source).not.toContain('bg-blue-50 dark:bg-blue-900/25 ring-1 ring-blue-300');
|
||||
expect(source).not.toContain('bg-blue-100 dark:bg-blue-800');
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -16,9 +16,9 @@ import type { Resource } from '@/types/resource';
|
||||
// Mirrors the private badge-class constants in resourceDetailDrawerOperationalModel.ts
|
||||
// so the assertions here stay brittle to drift in those exact Tailwind tokens.
|
||||
const SUPPORTED_BADGE_CLASS =
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400';
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900/25 dark:text-cyan-400';
|
||||
const UNSUPPORTED_BADGE_CLASS =
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-muted';
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-muted';
|
||||
|
||||
type SourceStatusMap = NonNullable<PlatformData['sourceStatus']>;
|
||||
|
||||
|
||||
+5
-5
@@ -40,31 +40,31 @@ describe('resourceDetailDrawerOperationalModel', () => {
|
||||
{
|
||||
label: 'K8s Node CPU/Memory',
|
||||
classes:
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400',
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900/25 dark:text-cyan-400',
|
||||
title: 'Node CPU and memory metrics are available.',
|
||||
},
|
||||
{
|
||||
label: 'Node Telemetry (Agent)',
|
||||
classes:
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400',
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900/25 dark:text-cyan-400',
|
||||
title: 'Linked Pulse agent provides node uptime, temperature, disk, network, and disk I/O.',
|
||||
},
|
||||
{
|
||||
label: 'Pod CPU/Memory',
|
||||
classes:
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400',
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900/25 dark:text-cyan-400',
|
||||
title: 'Pod CPU and memory metrics are available.',
|
||||
},
|
||||
{
|
||||
label: 'Pod Ephemeral Disk',
|
||||
classes:
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400',
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900/25 dark:text-cyan-400',
|
||||
title: 'Pod ephemeral storage usage is available.',
|
||||
},
|
||||
{
|
||||
label: 'Pod Disk I/O Unsupported',
|
||||
classes:
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-muted',
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-muted',
|
||||
title:
|
||||
'Pod disk read/write throughput is not collected by the Kubernetes integration path today.',
|
||||
},
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ describe('unifiedResourceTableStateModel', () => {
|
||||
expect(getUnifiedResourceTableShellClass('mobile')).not.toContain('min-w-[640px]');
|
||||
expect(getUnifiedResourceTableShellClass('compact')).toContain('min-w-[640px]');
|
||||
expect(getUnifiedResourceTableShellClass('wide')).toContain('min-w-[640px]');
|
||||
expect(getUnifiedResourceTableShellClass('wide')).not.toContain('min-w-[max-content]');
|
||||
expect(getUnifiedResourceTableShellClass('wide')).not.toContain('min-w-max');
|
||||
// Mobile keeps the prioritized column set but preserves a readable width
|
||||
// floor inside the shared horizontal-scroll shell. Wider modes keep all
|
||||
// host columns visible while compressing their tracks before any
|
||||
|
||||
+2
-2
@@ -19,9 +19,9 @@ export type ResourceDetailDrawerOperationalLink = ServiceDetailLink;
|
||||
type SourceStatusMap = NonNullable<PlatformData['sourceStatus']>;
|
||||
|
||||
const SUPPORTED_BADGE_CLASS =
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400';
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900/25 dark:text-cyan-400';
|
||||
const UNSUPPORTED_BADGE_CLASS =
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-muted';
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-muted';
|
||||
|
||||
export const buildKubernetesCapabilityBadges = (
|
||||
capabilities?: KubernetesMetricCapabilities,
|
||||
|
||||
@@ -186,12 +186,12 @@ export const K8sDeploymentsDrawer: Component<{
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="w-[12rem]">
|
||||
<div class="w-48">
|
||||
<SearchInput
|
||||
value={search}
|
||||
onChange={setSearch}
|
||||
placeholder={drawerPresentation.searchPlaceholder}
|
||||
inputClass="py-1 text-xs font-medium shadow-sm"
|
||||
inputClass="py-1 text-xs font-medium shadow-xs"
|
||||
typeToSearch
|
||||
clearOnEscape
|
||||
/>
|
||||
@@ -204,7 +204,7 @@ export const K8sDeploymentsDrawer: Component<{
|
||||
fieldBaseClass={filterGroupClass}
|
||||
labelClass={filterLabelClass}
|
||||
selectBaseClass={filterSelectClass}
|
||||
selectClass="min-w-[10rem]"
|
||||
selectClass="min-w-40"
|
||||
value={namespace()}
|
||||
aria-label={drawerPresentation.namespaceFilterLabel}
|
||||
onChange={(e) => setNamespace(e.currentTarget.value)}
|
||||
@@ -217,7 +217,7 @@ export const K8sDeploymentsDrawer: Component<{
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openPods(namespace() || undefined)}
|
||||
class="rounded-md border border-border px-3 py-1 text-xs font-semibold shadow-sm hover:bg-surface-hover"
|
||||
class="rounded-md border border-border px-3 py-1 text-xs font-semibold shadow-xs hover:bg-surface-hover"
|
||||
>
|
||||
{drawerPresentation.openPodsLabel}
|
||||
</button>
|
||||
@@ -330,7 +330,7 @@ export const K8sDeploymentsDrawer: Component<{
|
||||
type="button"
|
||||
aria-label={drawerPresentation.viewPodsLabel}
|
||||
onClick={() => openPods(dep.kubernetes?.namespace)}
|
||||
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-sm hover:bg-surface-hover"
|
||||
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-xs hover:bg-surface-hover"
|
||||
>
|
||||
<PlatformResponsiveTableLabel
|
||||
compact="Pods"
|
||||
|
||||
@@ -95,12 +95,12 @@ export const K8sNamespacesDrawer: Component<{
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="w-[12rem]">
|
||||
<div class="w-48">
|
||||
<SearchInput
|
||||
value={search}
|
||||
onChange={setSearch}
|
||||
placeholder={drawerPresentation.searchPlaceholder}
|
||||
inputClass="py-1 text-xs font-medium shadow-sm"
|
||||
inputClass="py-1 text-xs font-medium shadow-xs"
|
||||
typeToSearch
|
||||
clearOnEscape
|
||||
/>
|
||||
@@ -108,7 +108,7 @@ export const K8sNamespacesDrawer: Component<{
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openPods(null)}
|
||||
class="rounded-md border border-border bg-surface px-3 py-1 text-xs font-semibold shadow-sm hover:bg-surface-hover"
|
||||
class="rounded-md border border-border bg-surface px-3 py-1 text-xs font-semibold shadow-xs hover:bg-surface-hover"
|
||||
>
|
||||
{drawerPresentation.openAllPodsLabel}
|
||||
</button>
|
||||
@@ -222,7 +222,7 @@ export const K8sNamespacesDrawer: Component<{
|
||||
type="button"
|
||||
aria-label={drawerPresentation.openPodsLabel}
|
||||
onClick={() => openPods(row.namespace)}
|
||||
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-sm hover:bg-surface-hover"
|
||||
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-xs hover:bg-surface-hover"
|
||||
>
|
||||
<PlatformResponsiveTableLabel
|
||||
compact="Pods"
|
||||
@@ -234,7 +234,7 @@ export const K8sNamespacesDrawer: Component<{
|
||||
type="button"
|
||||
aria-label={drawerPresentation.viewDeploymentsLabel}
|
||||
onClick={() => props.onOpenDeployments?.(row.namespace)}
|
||||
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-sm hover:bg-surface-hover"
|
||||
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-xs hover:bg-surface-hover"
|
||||
>
|
||||
<PlatformResponsiveTableLabel
|
||||
compact="Deployments"
|
||||
|
||||
@@ -442,9 +442,9 @@ const LoginForm: Component<{
|
||||
<div class="max-w-md w-full space-y-8">
|
||||
{/* Demo Credentials Banner */}
|
||||
<Show when={demoModeEnabled}>
|
||||
<div class="bg-surface rounded-md p-4 shadow-sm border border-blue-200 dark:border-blue-800 animate-fade-in">
|
||||
<div class="bg-surface rounded-md p-4 shadow-xs border border-blue-200 dark:border-blue-800 animate-fade-in">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex-shrink-0 w-10 h-10 rounded-full bg-blue-600 flex items-center justify-center">
|
||||
<div class="shrink-0 w-10 h-10 rounded-full bg-blue-600 flex items-center justify-center">
|
||||
<svg
|
||||
class="w-5 h-5 text-white"
|
||||
fill="none"
|
||||
@@ -466,11 +466,11 @@ const LoginForm: Component<{
|
||||
fallback={
|
||||
<div class="text-sm text-muted">
|
||||
Login with{' '}
|
||||
<code class="bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 px-1.5 py-0.5 rounded font-mono text-xs">
|
||||
<code class="bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 px-1.5 py-0.5 rounded-sm font-mono text-xs">
|
||||
demo
|
||||
</code>{' '}
|
||||
/{' '}
|
||||
<code class="bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 px-1.5 py-0.5 rounded font-mono text-xs">
|
||||
<code class="bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 px-1.5 py-0.5 rounded-sm font-mono text-xs">
|
||||
demo
|
||||
</code>
|
||||
</div>
|
||||
@@ -503,7 +503,7 @@ const LoginForm: Component<{
|
||||
</Show>
|
||||
</div>
|
||||
<form
|
||||
class="mt-8 space-y-6 bg-surface rounded-md p-8 shadow-sm border border-border animate-slide-up"
|
||||
class="mt-8 space-y-6 bg-surface rounded-md p-8 shadow-xs border border-border animate-slide-up"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{/* Multi-Provider SSO Section */}
|
||||
@@ -513,7 +513,7 @@ const LoginForm: Component<{
|
||||
{(provider) => (
|
||||
<button
|
||||
type="button"
|
||||
class={`w-full inline-flex items-center justify-center gap-2 px-4 py-3 rounded-md border border-blue-500 text-blue-600 hover:bg-blue-50 transition dark:border-blue-400 dark:text-blue-200 dark:hover:bg-blue-900 ${oidcLoading() ? 'opacity-75 cursor-wait' : ''}`}
|
||||
class={`w-full inline-flex items-center justify-center gap-2 px-4 py-3 rounded-md border border-blue-500 text-blue-600 hover:bg-blue-50 transition dark:border-blue-400 dark:text-blue-200 dark:hover:bg-blue-900/25 ${oidcLoading() ? 'opacity-75 cursor-wait' : ''}`}
|
||||
disabled={oidcLoading()}
|
||||
onClick={() => {
|
||||
window.location.href = provider.loginUrl;
|
||||
@@ -538,12 +538,12 @@ const LoginForm: Component<{
|
||||
)}
|
||||
</For>
|
||||
<Show when={oidcError()}>
|
||||
<div class="rounded-md bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-800 px-3 py-2 text-sm text-red-600 dark:text-red-300">
|
||||
<div class="rounded-md bg-red-50 dark:bg-red-900/25 border border-red-200 dark:border-red-800 px-3 py-2 text-sm text-red-600 dark:text-red-300">
|
||||
{oidcError()}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={oidcMessage()}>
|
||||
<div class="rounded-md bg-green-50 dark:bg-green-900 border border-green-200 dark:border-green-700 px-3 py-2 text-sm text-green-600 dark:text-green-300">
|
||||
<div class="rounded-md bg-green-50 dark:bg-green-900/25 border border-green-200 dark:border-green-700 px-3 py-2 text-sm text-green-600 dark:text-green-300">
|
||||
{oidcMessage()}
|
||||
</div>
|
||||
</Show>
|
||||
@@ -581,7 +581,7 @@ const LoginForm: Component<{
|
||||
type="text"
|
||||
autocomplete="username"
|
||||
required
|
||||
class="appearance-none relative block w-full pl-10 pr-3 py-3 border border-border placeholder-gray-500 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all sm:text-sm"
|
||||
class="appearance-none relative block w-full pl-10 pr-3 py-3 border border-border placeholder-gray-500 rounded-md focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all sm:text-sm"
|
||||
placeholder="Username"
|
||||
value={username()}
|
||||
onInput={(e) => setUsername(e.currentTarget.value)}
|
||||
@@ -607,7 +607,7 @@ const LoginForm: Component<{
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
class="appearance-none relative block w-full pl-10 pr-3 py-3 border border-border placeholder-gray-500 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all sm:text-sm"
|
||||
class="appearance-none relative block w-full pl-10 pr-3 py-3 border border-border placeholder-gray-500 rounded-md focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all sm:text-sm"
|
||||
placeholder="Password"
|
||||
value={password()}
|
||||
onInput={(e) => setPassword(e.currentTarget.value)}
|
||||
@@ -620,7 +620,7 @@ const LoginForm: Component<{
|
||||
type="checkbox"
|
||||
checked={rememberMe()}
|
||||
onChange={(e) => setRememberMe(e.currentTarget.checked)}
|
||||
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-border rounded cursor-pointer"
|
||||
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-border rounded-sm cursor-pointer"
|
||||
/>
|
||||
<label
|
||||
for="remember-me"
|
||||
@@ -638,11 +638,11 @@ const LoginForm: Component<{
|
||||
class={`rounded-md p-4 ${
|
||||
error().includes('locked')
|
||||
? 'bg-orange-50 dark:bg-orange-900'
|
||||
: 'bg-red-50 dark:bg-red-900'
|
||||
: 'bg-red-50 dark:bg-red-900/25'
|
||||
}`}
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="shrink-0">
|
||||
<Show
|
||||
when={error().includes('locked')}
|
||||
fallback={
|
||||
@@ -689,7 +689,7 @@ const LoginForm: Component<{
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading()}
|
||||
class="group relative w-full flex justify-center py-3 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
class="group relative w-full flex justify-center py-3 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-hidden focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
<Show when={loading()}>
|
||||
<LoadingSpinner size="button" tone="inverse" class="-ml-1 mr-3" />
|
||||
@@ -703,7 +703,7 @@ const LoginForm: Component<{
|
||||
<div class="border-t border-border pt-4 text-center">
|
||||
<a
|
||||
href={TROUBLESHOOTING_DOC_URL}
|
||||
class="inline-flex min-h-10 items-center rounded px-1 text-xs font-medium text-blue-700 underline-offset-2 transition-colors hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60 dark:text-blue-300 sm:min-h-9"
|
||||
class="inline-flex min-h-10 items-center rounded-sm px-1 text-xs font-medium text-blue-700 underline-offset-2 transition-colors hover:underline focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500/60 dark:text-blue-300 sm:min-h-9"
|
||||
>
|
||||
Can’t sign in?
|
||||
</a>
|
||||
|
||||
@@ -34,7 +34,7 @@ export function OrgSwitcher(props: OrgSwitcherProps) {
|
||||
label="Organization"
|
||||
labelClass="sr-only"
|
||||
fieldBaseClass="contents"
|
||||
selectBaseClass="h-7 max-w-44 rounded-md border border-border bg-surface px-2 text-xs text-base-content shadow-sm transition-colors focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
selectBaseClass="h-7 max-w-44 rounded-md border border-border bg-surface px-2 text-xs text-base-content shadow-xs transition-colors focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
aria-label="Organization"
|
||||
value={props.selectedOrgId}
|
||||
disabled={Boolean(props.loading)}
|
||||
|
||||
@@ -128,7 +128,7 @@ export const SecurityWarning: Component = () => {
|
||||
return (
|
||||
<Show when={shouldShow()}>
|
||||
<div
|
||||
class={`relative z-20 border-b shadow-sm ${warningPresentation().background} ${warningPresentation().border}`}
|
||||
class={`relative z-20 border-b shadow-xs ${warningPresentation().background} ${warningPresentation().border}`}
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
@@ -149,7 +149,7 @@ export const SecurityWarning: Component = () => {
|
||||
</span>
|
||||
}
|
||||
size="sm"
|
||||
class="flex-1 min-w-[12rem]"
|
||||
class="flex-1 min-w-48"
|
||||
titleClass="text-base-content"
|
||||
/>
|
||||
<button
|
||||
@@ -246,7 +246,7 @@ export const SecurityWarning: Component = () => {
|
||||
>
|
||||
Dismiss ▼
|
||||
</button>
|
||||
<div class="absolute left-0 top-full mt-1 bg-surface rounded shadow-sm border border-border opacity-0 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-auto transition-opacity">
|
||||
<div class="absolute left-0 top-full mt-1 bg-surface rounded-sm shadow-xs border border-border opacity-0 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-auto transition-opacity">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDismiss('day')}
|
||||
|
||||
@@ -92,7 +92,7 @@ export const AIChatMaintenanceSection: Component<AIChatMaintenanceSectionProps>
|
||||
fieldBaseClass="contents"
|
||||
value={state.selectedSessionId()}
|
||||
onChange={(e) => state.setSelectedSessionId(e.currentTarget.value)}
|
||||
selectBaseClass="w-full min-h-10 sm:min-h-9 px-2 py-2 text-sm border border-border rounded"
|
||||
selectBaseClass="w-full min-h-10 sm:min-h-9 px-2 py-2 text-sm border border-border rounded-sm"
|
||||
disabled={state.saving()}
|
||||
>
|
||||
<For each={state.chatSessions()}>
|
||||
@@ -116,7 +116,7 @@ export const AIChatMaintenanceSection: Component<AIChatMaintenanceSectionProps>
|
||||
type="button"
|
||||
onClick={state.handleSessionSummarize}
|
||||
disabled={!state.selectedSessionId() || state.sessionActionLoading() !== null}
|
||||
class="w-full sm:w-auto min-h-10 sm:min-h-9 px-3 py-2 text-sm font-medium rounded border border-border bg-surface text-base-content hover:bg-surface-hover disabled:opacity-50"
|
||||
class="w-full sm:w-auto min-h-10 sm:min-h-9 px-3 py-2 text-sm font-medium rounded-sm border border-border bg-surface text-base-content hover:bg-surface-hover disabled:opacity-50"
|
||||
>
|
||||
{state.sessionActionLoading() === 'summarize'
|
||||
? 'Summarizing...'
|
||||
|
||||
@@ -132,13 +132,13 @@ export const PatrolModelReadinessControl: Component<{ state: AISettingsState }>
|
||||
const toneClasses = () => {
|
||||
switch (tone()) {
|
||||
case 'success':
|
||||
return 'border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900 text-green-700 dark:text-green-300';
|
||||
return 'border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900/25 text-green-700 dark:text-green-300';
|
||||
case 'warning':
|
||||
return 'border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900 text-amber-700 dark:text-amber-300';
|
||||
return 'border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900/25 text-amber-700 dark:text-amber-300';
|
||||
case 'neutral':
|
||||
return 'border-border bg-surface-alt text-base-content';
|
||||
case 'error':
|
||||
return 'border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900 text-red-700 dark:text-red-300';
|
||||
return 'border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/25 text-red-700 dark:text-red-300';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
@@ -268,8 +268,8 @@ export const PatrolModelReadinessControl: Component<{ state: AISettingsState }>
|
||||
disabled={state.saving()}
|
||||
class={`inline-flex min-h-9 items-center rounded-md px-3 py-1.5 text-sm disabled:opacity-50 whitespace-nowrap ${
|
||||
state.patrolModelReadinessRunning()
|
||||
? 'bg-amber-100 dark:bg-amber-900 text-amber-700 dark:text-amber-300 hover:bg-amber-200 dark:hover:bg-amber-800'
|
||||
: 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800'
|
||||
? 'bg-amber-100 dark:bg-amber-900/25 text-amber-700 dark:text-amber-300 hover:bg-amber-200 dark:hover:bg-amber-800'
|
||||
: 'bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800'
|
||||
}`}
|
||||
>
|
||||
{state.patrolModelReadinessRunning() ? 'Cancel check' : 'Check Patrol model'}
|
||||
@@ -282,7 +282,7 @@ export const PatrolModelReadinessControl: Component<{ state: AISettingsState }>
|
||||
</Show>
|
||||
<Show when={result()}>
|
||||
{(r) => (
|
||||
<div class={`rounded border px-3 py-2 ${toneClasses()}`}>
|
||||
<div class={`rounded-sm border px-3 py-2 ${toneClasses()}`}>
|
||||
<div class="flex items-baseline justify-between gap-2">
|
||||
<p class="text-xs font-medium">{headline()}</p>
|
||||
<Show when={r().duration_ms > 0}>
|
||||
@@ -295,7 +295,7 @@ export const PatrolModelReadinessControl: Component<{ state: AISettingsState }>
|
||||
<div class="mt-2 grid gap-1 sm:grid-cols-2">
|
||||
<For each={dimensionRows()}>
|
||||
{(dimension) => (
|
||||
<div class="rounded bg-white/40 px-2 py-1 dark:bg-black/10">
|
||||
<div class="rounded-sm bg-white/40 px-2 py-1 dark:bg-black/10">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="text-[11px]">{dimension.label}</span>
|
||||
<span
|
||||
@@ -378,11 +378,11 @@ export const PatrolModelReadinessControl: Component<{ state: AISettingsState }>
|
||||
const patrolCostToneClasses = (tone: PatrolCostTone) => {
|
||||
switch (tone) {
|
||||
case 'positive':
|
||||
return 'border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900 text-green-800 dark:text-green-200';
|
||||
return 'border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900/25 text-green-800 dark:text-green-200';
|
||||
case 'warning':
|
||||
return 'border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900 text-amber-800 dark:text-amber-200';
|
||||
return 'border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900/25 text-amber-800 dark:text-amber-200';
|
||||
case 'danger':
|
||||
return 'border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900 text-red-800 dark:text-red-200';
|
||||
return 'border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/25 text-red-800 dark:text-red-200';
|
||||
default:
|
||||
return 'border-border bg-surface-alt text-base-content';
|
||||
}
|
||||
@@ -411,7 +411,7 @@ export const PatrolCostPreview: Component<{ state: AISettingsState }> = (props)
|
||||
<Show when={presentation()}>
|
||||
{(view) => (
|
||||
<div
|
||||
class={`mt-2 rounded border px-3 py-2 ${patrolCostToneClasses(view().tone)}`}
|
||||
class={`mt-2 rounded-sm border px-3 py-2 ${patrolCostToneClasses(view().tone)}`}
|
||||
data-testid="patrol-cost-preview"
|
||||
role="status"
|
||||
>
|
||||
@@ -583,12 +583,7 @@ export const AIModelOverrideField: Component<{
|
||||
<SelectedModelGuidanceNote annotation={selectedAnnotation} />
|
||||
<Show when={selectedModel() && !isModelProviderConfigured(selectedModel(), state.settings())}>
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1 flex items-center gap-1">
|
||||
<svg
|
||||
class="w-3.5 h-3.5 flex-shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
@@ -701,12 +696,7 @@ export const AIModelSelectionSection: Component<AIModelSelectionSectionProps> =
|
||||
<SelectedModelGuidanceNote annotation={sharedSelectedAnnotation} />
|
||||
<Show when={state.modelsError()}>
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1 flex items-center gap-1">
|
||||
<svg
|
||||
class="w-3.5 h-3.5 flex-shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
@@ -722,12 +712,7 @@ export const AIModelSelectionSection: Component<AIModelSelectionSectionProps> =
|
||||
when={state.form.model && !isModelProviderConfigured(state.form.model, state.settings())}
|
||||
>
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1 flex items-center gap-1">
|
||||
<svg
|
||||
class="w-3.5 h-3.5 flex-shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
|
||||
@@ -78,7 +78,7 @@ export const AIProviderConfigurationSection: Component<AIProviderConfigurationSe
|
||||
type="button"
|
||||
onClick={() => void props.runProviderPreflight()}
|
||||
disabled={props.preflightRunning() || props.saving()}
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center rounded-md px-3 py-2 text-sm bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800 disabled:opacity-50"
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center rounded-md px-3 py-2 text-sm bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800 disabled:opacity-50"
|
||||
>
|
||||
{props.preflightRunning() ? 'Checking...' : 'Run Preflight'}
|
||||
</button>
|
||||
@@ -163,13 +163,13 @@ export const AIProviderConfigurationSection: Component<AIProviderConfigurationSe
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-sm">{config.title}</span>
|
||||
<Show when={configured()}>
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-semibold bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 rounded">
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-semibold bg-green-100 dark:bg-green-900/25 text-green-700 dark:text-green-300 rounded-sm">
|
||||
{config.configuredLabel}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={configured()}>
|
||||
<span
|
||||
class={`px-1.5 py-0.5 text-[10px] font-semibold rounded ${getAIProviderHealthPresentation(health().status).badgeClass}`}
|
||||
class={`px-1.5 py-0.5 text-[10px] font-semibold rounded-sm ${getAIProviderHealthPresentation(health().status).badgeClass}`}
|
||||
>
|
||||
{getAIProviderHealthPresentation(health().status).label}
|
||||
</span>
|
||||
@@ -235,7 +235,7 @@ export const AIProviderConfigurationSection: Component<AIProviderConfigurationSe
|
||||
}
|
||||
disabled={props.saving()}
|
||||
aria-label={`Enable ${getAIProviderDisplayName(config.provider)}`}
|
||||
class="mt-0.5 h-4 w-4 rounded border-border"
|
||||
class="mt-0.5 h-4 w-4 rounded-sm border-border"
|
||||
/>
|
||||
<span>
|
||||
Use this machine's existing subscription login. Pulse stores no OAuth
|
||||
@@ -324,7 +324,7 @@ export const AIProviderConfigurationSection: Component<AIProviderConfigurationSe
|
||||
type="button"
|
||||
onClick={() => void props.handleTestProvider(config.provider)}
|
||||
disabled={props.testingProvider() === config.provider || props.saving()}
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center rounded-md px-3 py-2 text-sm bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800 disabled:opacity-50"
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center rounded-md px-3 py-2 text-sm bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800 disabled:opacity-50"
|
||||
>
|
||||
{props.testingProvider() === config.provider ? 'Testing...' : 'Test'}
|
||||
</button>
|
||||
@@ -332,7 +332,7 @@ export const AIProviderConfigurationSection: Component<AIProviderConfigurationSe
|
||||
type="button"
|
||||
onClick={() => void props.handleClearProvider(config.provider)}
|
||||
disabled={props.saving()}
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center rounded-md px-3 py-2 text-sm bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300 hover:bg-red-200 dark:hover:bg-red-800 disabled:opacity-50"
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center rounded-md px-3 py-2 text-sm bg-red-100 dark:bg-red-900/25 text-red-700 dark:text-red-300 hover:bg-red-200 dark:hover:bg-red-800 disabled:opacity-50"
|
||||
title={config.clearTitle}
|
||||
>
|
||||
Remove
|
||||
|
||||
@@ -42,7 +42,7 @@ export const AIDiscoveryControlsSection: Component<AIRuntimeControlsSectionProps
|
||||
<div class="rounded-md border border-blue-200 dark:border-blue-800 overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
class="w-full min-h-10 sm:min-h-9 px-3 py-2.5 flex items-center justify-between bg-blue-50 dark:bg-blue-900 hover:bg-blue-100 dark:hover:bg-blue-900 transition-colors text-left"
|
||||
class="w-full min-h-10 sm:min-h-9 px-3 py-2.5 flex items-center justify-between bg-blue-50 dark:bg-blue-900/25 hover:bg-blue-100 dark:hover:bg-blue-900/25 transition-colors text-left"
|
||||
onClick={() => state.setShowDiscoverySettings(!state.showDiscoverySettings())}
|
||||
aria-expanded={state.showDiscoverySettings()}
|
||||
aria-controls="ai-discovery-controls-panel"
|
||||
@@ -63,14 +63,14 @@ export const AIDiscoveryControlsSection: Component<AIRuntimeControlsSectionProps
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-base-content">Service Context</span>
|
||||
<Show when={state.form.discoveryEnabled}>
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-medium bg-blue-100 dark:bg-blue-800 text-blue-700 dark:text-blue-300 rounded">
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-medium bg-blue-100 dark:bg-blue-800 text-blue-700 dark:text-blue-300 rounded-sm">
|
||||
{state.form.discoveryIntervalHours > 0
|
||||
? `Auto ${state.form.discoveryIntervalHours}h`
|
||||
: 'Manual only'}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={!state.form.discoveryEnabled}>
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-medium bg-surface-hover text-muted rounded">
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-medium bg-surface-hover text-muted rounded-sm">
|
||||
Off
|
||||
</span>
|
||||
</Show>
|
||||
@@ -119,8 +119,8 @@ export const AIDiscoveryControlsSection: Component<AIRuntimeControlsSectionProps
|
||||
disabled={state.saving()}
|
||||
fieldBaseClass="flex"
|
||||
fieldClass="items-center gap-3"
|
||||
labelClass="text-xs font-medium text-muted w-32 flex-shrink-0"
|
||||
selectBaseClass="flex-1 px-2 py-1 text-sm border border-border rounded bg-surface"
|
||||
labelClass="text-xs font-medium text-muted w-32 shrink-0"
|
||||
selectBaseClass="flex-1 px-2 py-1 text-sm border border-border rounded-sm bg-surface"
|
||||
>
|
||||
<option value="0">Manual only</option>
|
||||
<option value="6">Every 6 hours</option>
|
||||
@@ -137,7 +137,7 @@ export const AIDiscoveryControlsSection: Component<AIRuntimeControlsSectionProps
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div class="flex flex-col gap-2 rounded border border-border bg-surface-alt px-3 py-2 sm:flex-row sm:items-center sm:justify-between sm:gap-3">
|
||||
<div class="flex flex-col gap-2 rounded-sm border border-border bg-surface-alt px-3 py-2 sm:flex-row sm:items-center sm:justify-between sm:gap-3">
|
||||
<p class="text-[10px] text-muted sm:flex-1">
|
||||
{state.form.discoveryEnabled && state.form.discoveryIntervalHours > 0
|
||||
? 'Runs the same scan used by the schedule.'
|
||||
@@ -147,7 +147,7 @@ export const AIDiscoveryControlsSection: Component<AIRuntimeControlsSectionProps
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex min-h-10 items-center gap-1.5 self-start rounded-md border border-border px-3 py-1.5 text-xs font-medium text-base-content hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-9 sm:self-auto sm:flex-shrink-0"
|
||||
class="inline-flex min-h-10 items-center gap-1.5 self-start rounded-md border border-border px-3 py-1.5 text-xs font-medium text-base-content hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-9 sm:self-auto sm:shrink-0"
|
||||
onClick={() => void state.handleRunDiscoveryRefresh()}
|
||||
disabled={state.saving() || state.discoveryRunRunning()}
|
||||
>
|
||||
@@ -177,7 +177,7 @@ export const AIProviderRuntimeControlsSection: Component<AIRuntimeControlsSectio
|
||||
<div class="grid gap-3 lg:grid-cols-2">
|
||||
<div class="flex items-center gap-3 p-3 rounded-md border border-border bg-surface-alt">
|
||||
<svg
|
||||
class="w-4 h-4 text-muted flex-shrink-0"
|
||||
class="w-4 h-4 text-muted shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -192,12 +192,12 @@ export const AIProviderRuntimeControlsSection: Component<AIRuntimeControlsSectio
|
||||
<label for="ai-cost-budget" class="text-xs font-medium text-base-content">
|
||||
30-day Budget
|
||||
</label>
|
||||
<div class="relative flex-shrink-0">
|
||||
<div class="relative shrink-0">
|
||||
<span class="absolute left-2 top-1/2 -translate-y-1/2 text-muted text-xs">$</span>
|
||||
<input
|
||||
id="ai-cost-budget"
|
||||
type="number"
|
||||
class="w-24 min-h-10 sm:min-h-9 pl-5 pr-2 py-2 text-sm border border-border rounded bg-surface"
|
||||
class="w-24 min-h-10 sm:min-h-9 pl-5 pr-2 py-2 text-sm border border-border rounded-sm bg-surface"
|
||||
value={state.form.costBudgetUSD30d}
|
||||
onInput={(e) => state.setForm('costBudgetUSD30d', e.currentTarget.value)}
|
||||
min={0}
|
||||
@@ -219,7 +219,7 @@ export const AIProviderRuntimeControlsSection: Component<AIRuntimeControlsSectio
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center gap-3 p-3 rounded-md border border-border bg-surface-alt">
|
||||
<svg
|
||||
class="w-4 h-4 text-muted flex-shrink-0"
|
||||
class="w-4 h-4 text-muted shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -237,7 +237,7 @@ export const AIProviderRuntimeControlsSection: Component<AIRuntimeControlsSectio
|
||||
<input
|
||||
id="ai-request-timeout"
|
||||
type="number"
|
||||
class="w-20 min-h-10 sm:min-h-9 px-2 py-2 text-sm border border-border rounded bg-surface"
|
||||
class="w-20 min-h-10 sm:min-h-9 px-2 py-2 text-sm border border-border rounded-sm bg-surface"
|
||||
value={state.form.requestTimeoutSeconds}
|
||||
onInput={(e) => {
|
||||
const value = parseInt(e.currentTarget.value, 10);
|
||||
@@ -302,7 +302,7 @@ export const AIAssistantCommandAccessSection: Component<AIRuntimeControlsSection
|
||||
</span>
|
||||
<Show when={state.form.controlLevel !== 'read_only'}>
|
||||
<span
|
||||
class={`px-1.5 py-0.5 text-[10px] font-medium rounded ${getAIControlLevelBadgeClass(state.form.controlLevel)}`}
|
||||
class={`px-1.5 py-0.5 text-[10px] font-medium rounded-sm ${getAIControlLevelBadgeClass(state.form.controlLevel)}`}
|
||||
>
|
||||
{assistantCommandAccessBadgeLabel()}
|
||||
</span>
|
||||
@@ -321,8 +321,8 @@ export const AIAssistantCommandAccessSection: Component<AIRuntimeControlsSection
|
||||
disabled={state.saving()}
|
||||
fieldBaseClass="flex flex-col gap-2 sm:flex-row"
|
||||
fieldClass="sm:items-center sm:gap-3"
|
||||
labelClass="text-xs font-medium text-muted sm:w-28 sm:flex-shrink-0"
|
||||
selectBaseClass="w-full min-w-0 min-h-10 px-2 py-2 text-sm border border-border rounded bg-surface sm:min-h-9 sm:flex-1"
|
||||
labelClass="text-xs font-medium text-muted sm:w-28 sm:shrink-0"
|
||||
selectBaseClass="w-full min-w-0 min-h-10 px-2 py-2 text-sm border border-border rounded-sm bg-surface sm:min-h-9 sm:flex-1"
|
||||
>
|
||||
<option value="read_only">Observe only - Assistant cannot take chat actions</option>
|
||||
<option value="controlled">Ask first - Assistant asks before chat-only actions</option>
|
||||
@@ -332,11 +332,11 @@ export const AIAssistantCommandAccessSection: Component<AIRuntimeControlsSection
|
||||
</option>
|
||||
</Show>
|
||||
</FormSelect>
|
||||
<p class="text-[10px] text-muted sm:ml-[7.5rem]">
|
||||
<p class="text-[10px] text-muted sm:ml-30">
|
||||
{getAIControlLevelDescription(state.form.controlLevel)}
|
||||
</p>
|
||||
<Show when={state.form.controlLevel === 'autonomous'}>
|
||||
<div class="p-2 bg-amber-100 dark:bg-amber-900 rounded border border-amber-200 dark:border-amber-800 text-[10px] text-amber-800 dark:text-amber-200">
|
||||
<div class="p-2 bg-amber-100 dark:bg-amber-900/25 rounded-sm border border-amber-200 dark:border-amber-800 text-[10px] text-amber-800 dark:text-amber-200">
|
||||
<strong>Important:</strong> Assistant may take eligible chat-only actions from this
|
||||
mode. Infrastructure changes stay with Patrol mode. Keep protected guests set for
|
||||
anything Assistant must not touch. See{' '}
|
||||
@@ -368,7 +368,7 @@ export const AIAssistantCommandAccessSection: Component<AIRuntimeControlsSection
|
||||
<div class="flex items-start gap-3 pt-2 border-t border-blue-200 dark:border-blue-700">
|
||||
<label
|
||||
for="ai-protected-guests"
|
||||
class="text-xs font-medium text-muted w-28 flex-shrink-0 pt-1"
|
||||
class="text-xs font-medium text-muted w-28 shrink-0 pt-1"
|
||||
>
|
||||
Protected guests
|
||||
</label>
|
||||
@@ -379,7 +379,7 @@ export const AIAssistantCommandAccessSection: Component<AIRuntimeControlsSection
|
||||
value={state.form.protectedGuests}
|
||||
onInput={(e) => state.setForm('protectedGuests', e.currentTarget.value)}
|
||||
placeholder="e.g., 100, 101, prod-db"
|
||||
class="w-full min-h-10 sm:min-h-9 px-2 py-2 text-sm border border-border rounded"
|
||||
class="w-full min-h-10 sm:min-h-9 px-2 py-2 text-sm border border-border rounded-sm"
|
||||
disabled={state.saving()}
|
||||
/>
|
||||
<p class="text-[10px] text-muted mt-1">
|
||||
|
||||
@@ -142,7 +142,7 @@ const PatrolSettingsContent: Component<{ state: ReturnType<typeof useAISettingsS
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex min-h-10 items-center justify-center rounded-md border border-blue-300 px-3 py-2 text-sm font-medium text-blue-700 hover:bg-blue-50 dark:border-blue-700 dark:text-blue-300 dark:hover:bg-blue-900 sm:min-h-9"
|
||||
class="inline-flex min-h-10 items-center justify-center rounded-md border border-blue-300 px-3 py-2 text-sm font-medium text-blue-700 hover:bg-blue-50 dark:border-blue-700 dark:text-blue-300 dark:hover:bg-blue-900/25 sm:min-h-9"
|
||||
onClick={() => navigate(PATROL_PATH)}
|
||||
>
|
||||
Open Patrol
|
||||
@@ -342,7 +342,7 @@ const AssistantSettingsContent: Component<{ state: ReturnType<typeof useAISettin
|
||||
<AIModelOverrideField state={props.state} kind="assistant" />
|
||||
<AIAssistantCommandAccessSection state={props.state} />
|
||||
<AIChatMaintenanceSection state={props.state} />
|
||||
<details class="overflow-hidden rounded-md border border-border bg-surface-alt/40">
|
||||
<details class="overflow-hidden rounded-md border border-border">
|
||||
<summary class="cursor-pointer list-none px-4 py-3 text-sm font-semibold text-base-content hover:bg-surface-hover">
|
||||
Service identification
|
||||
<span class="ml-2 font-normal text-muted">
|
||||
@@ -459,7 +459,7 @@ export const AISettings: Component<{ page?: AISettingsPage }> = (props) => {
|
||||
>
|
||||
<div class="flex items-center gap-2 text-sm text-red-700 dark:text-red-300">
|
||||
<svg
|
||||
class="h-4 w-4 flex-shrink-0"
|
||||
class="h-4 w-4 shrink-0"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -475,7 +475,7 @@ export const AISettings: Component<{ page?: AISettingsPage }> = (props) => {
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="flex-shrink-0 px-3 py-1.5 text-sm font-medium text-red-700 dark:text-red-300 border border-red-300 dark:border-red-700 rounded-md hover:bg-red-100 dark:hover:bg-red-900/50"
|
||||
class="shrink-0 px-3 py-1.5 text-sm font-medium text-red-700 dark:text-red-300 border border-red-300 dark:border-red-700 rounded-md hover:bg-red-100 dark:hover:bg-red-900/50"
|
||||
onClick={() => state.loadSettings()}
|
||||
>
|
||||
{getAISettingsRetryLabel()}
|
||||
|
||||
@@ -45,7 +45,7 @@ export const AISettingsStatusAndActions: Component<AISettingsStatusAndActionsPro
|
||||
<Show when={props.showConnectionControls && state.settings()?.configured}>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full sm:w-auto min-h-10 sm:min-h-9 px-4 py-2.5 text-sm border border-blue-300 dark:border-blue-700 text-blue-700 dark:text-blue-300 rounded-md hover:bg-blue-50 dark:hover:bg-blue-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="w-full sm:w-auto min-h-10 sm:min-h-9 px-4 py-2.5 text-sm border border-blue-300 dark:border-blue-700 text-blue-700 dark:text-blue-300 rounded-md hover:bg-blue-50 dark:hover:bg-blue-900/25 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={state.handleTest}
|
||||
disabled={state.testing() || state.saving() || state.loading()}
|
||||
>
|
||||
|
||||
@@ -217,7 +217,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
<span
|
||||
class={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${
|
||||
isWildcard
|
||||
? 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200'
|
||||
? 'bg-amber-100 text-amber-800 dark:bg-amber-900/25 dark:text-amber-200'
|
||||
: 'bg-surface-alt text-base-content'
|
||||
}`}
|
||||
title={scope.value}
|
||||
@@ -259,7 +259,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
>
|
||||
<span class="text-muted">{usageSummary}</span>
|
||||
<Show when={agentUsageEntry && agentUsageEntry.count > 1}>
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-semibold text-amber-800 dark:bg-amber-900 dark:text-amber-200">
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-semibold text-amber-800 dark:bg-amber-900/25 dark:text-amber-200">
|
||||
<svg class="h-3 w-3" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
@@ -288,7 +288,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
data-api-token-action="edit-scopes"
|
||||
onClick={(event) => openScopeEditor(token, event.currentTarget)}
|
||||
disabled={!canManage()}
|
||||
class={`inline-flex min-h-10 items-center justify-center rounded-md px-2.5 py-1.5 text-sm font-semibold text-blue-600 transition hover:bg-blue-50 hover:text-blue-700 disabled:cursor-not-allowed disabled:opacity-60 dark:text-blue-300 dark:hover:bg-blue-900 dark:hover:text-blue-200 ${
|
||||
class={`inline-flex min-h-10 items-center justify-center rounded-md px-2.5 py-1.5 text-sm font-semibold text-blue-600 transition hover:bg-blue-50 hover:text-blue-700 disabled:cursor-not-allowed disabled:opacity-60 dark:text-blue-300 dark:hover:bg-blue-900/25 dark:hover:text-blue-200 ${
|
||||
compact ? 'flex-1 border border-blue-200 dark:border-blue-800' : 'sm:min-h-9'
|
||||
}`}
|
||||
>
|
||||
@@ -300,7 +300,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
data-api-token-action="rename"
|
||||
onClick={(event) => openRenameDialog(token, event.currentTarget)}
|
||||
disabled={!canManage()}
|
||||
class={`inline-flex min-h-10 items-center justify-center rounded-md px-2.5 py-1.5 text-sm font-semibold text-blue-600 transition hover:bg-blue-50 hover:text-blue-700 disabled:cursor-not-allowed disabled:opacity-60 dark:text-blue-300 dark:hover:bg-blue-900 dark:hover:text-blue-200 ${
|
||||
class={`inline-flex min-h-10 items-center justify-center rounded-md px-2.5 py-1.5 text-sm font-semibold text-blue-600 transition hover:bg-blue-50 hover:text-blue-700 disabled:cursor-not-allowed disabled:opacity-60 dark:text-blue-300 dark:hover:bg-blue-900/25 dark:hover:text-blue-200 ${
|
||||
compact ? 'flex-1 border border-blue-200 dark:border-blue-800' : 'sm:min-h-9'
|
||||
}`}
|
||||
>
|
||||
@@ -312,8 +312,8 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
data-api-token-action="revoke"
|
||||
onClick={(event) => openRevokeDialog(token, event.currentTarget)}
|
||||
disabled={!canManage()}
|
||||
class={`inline-flex min-h-10 items-center justify-center rounded-md px-2.5 py-1.5 text-sm font-semibold text-red-600 transition hover:bg-red-50 hover:text-red-700 disabled:cursor-not-allowed disabled:opacity-60 dark:text-red-400 dark:hover:bg-red-900 dark:hover:text-red-300 ${
|
||||
compact ? 'flex-1 border border-red-200 dark:border-red-900' : 'sm:min-h-9'
|
||||
class={`inline-flex min-h-10 items-center justify-center rounded-md px-2.5 py-1.5 text-sm font-semibold text-red-600 transition hover:bg-red-50 hover:text-red-700 disabled:cursor-not-allowed disabled:opacity-60 dark:text-red-400 dark:hover:bg-red-900/25 dark:hover:text-red-300 ${
|
||||
compact ? 'flex-1 border border-red-200 dark:border-red-900/25' : 'sm:min-h-9'
|
||||
}`}
|
||||
>
|
||||
Revoke
|
||||
@@ -323,7 +323,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
|
||||
return (
|
||||
<div class="space-y-5">
|
||||
<Card padding="none" class="border border-border shadow-sm">
|
||||
<Card padding="none" class="border border-border shadow-xs">
|
||||
<div class="flex flex-col gap-6 p-4 sm:p-6 lg:p-8">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
@@ -342,7 +342,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
type="button"
|
||||
onClick={focusCreateSection}
|
||||
disabled={!canManage()}
|
||||
class="inline-flex min-h-10 sm:min-h-10 items-center gap-2 rounded-md bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-blue-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1 focus-visible:ring-offset-white"
|
||||
class="inline-flex min-h-10 sm:min-h-10 items-center gap-2 rounded-md bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-xs transition hover:bg-blue-700 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1 focus-visible:ring-offset-white"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
@@ -369,14 +369,14 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
</Show>
|
||||
|
||||
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div class="rounded-md border border-border p-4 text-sm shadow-sm">
|
||||
<div class="rounded-md border border-border p-4 text-sm shadow-xs">
|
||||
<div class="text-[0.7rem] font-semibold uppercase tracking-wide text-muted">
|
||||
Total tokens
|
||||
</div>
|
||||
<div class="mt-1 text-2xl font-semibold text-base-content">{totalTokens()}</div>
|
||||
<p class="mt-1 text-xs text-muted">Stored credentials across all agents</p>
|
||||
</div>
|
||||
<div class="rounded-md border border-border p-4 text-sm shadow-sm">
|
||||
<div class="rounded-md border border-border p-4 text-sm shadow-xs">
|
||||
<div class="text-[0.7rem] font-semibold uppercase tracking-wide text-muted">
|
||||
Scoped tokens
|
||||
</div>
|
||||
@@ -384,7 +384,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
<p class="mt-1 text-xs text-muted">Limited access tokens with defined scopes</p>
|
||||
</div>
|
||||
<div
|
||||
class={`rounded-md border p-4 text-sm shadow-sm ${hasWildcardTokens() ? 'border-amber-300 bg-amber-50 text-amber-900 dark:border-amber-700 dark:bg-amber-900 dark:text-amber-100' : 'border-border bg-surface text-base-content'}`}
|
||||
class={`rounded-md border p-4 text-sm shadow-xs ${hasWildcardTokens() ? 'border-amber-300 bg-amber-50 text-amber-900/25 dark:border-amber-700 dark:bg-amber-900/25 dark:text-amber-100' : 'border-border bg-surface text-base-content'}`}
|
||||
>
|
||||
<div
|
||||
class={`text-[0.7rem] font-semibold uppercase tracking-wide ${
|
||||
@@ -440,7 +440,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
<button
|
||||
onClick={reopenTokenDialog}
|
||||
disabled={!canManage()}
|
||||
class="font-medium underline decoration-green-500 underline-offset-2 hover:text-green-900 dark:hover:text-green-100"
|
||||
class="font-medium underline decoration-green-500 underline-offset-2 hover:text-green-900/25 dark:hover:text-green-100"
|
||||
>
|
||||
Show
|
||||
</button>
|
||||
@@ -448,7 +448,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
onClick={() => {
|
||||
dismissNewToken();
|
||||
}}
|
||||
class="font-medium underline decoration-green-500 underline-offset-2 hover:text-green-900 dark:hover:text-green-100"
|
||||
class="font-medium underline decoration-green-500 underline-offset-2 hover:text-green-900/25 dark:hover:text-green-100"
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
@@ -461,20 +461,20 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
newTokenRecord()?.scopes?.[0] === MONITORING_READ_SCOPE
|
||||
}
|
||||
>
|
||||
<div class="rounded-md border border-blue-200 bg-blue-50 p-4 text-sm text-blue-900 shadow-sm dark:border-blue-800 dark:bg-blue-900 dark:text-blue-100">
|
||||
<div class="rounded-md border border-blue-200 bg-blue-50 p-4 text-sm text-blue-900/25 shadow-xs dark:border-blue-800 dark:bg-blue-900/25 dark:text-blue-100">
|
||||
<div class="mb-2 font-semibold">Magic Kiosk Link</div>
|
||||
<p class="mb-3 text-xs text-blue-700 dark:text-blue-300">
|
||||
Use this link to open Pulse directly in Kiosk mode without logging in. Perfect for
|
||||
wall displays and digital signage.
|
||||
</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<code class="flex-1 rounded border border-blue-200 bg-surface px-3 py-2 font-mono text-xs text-blue-800 dark:border-blue-800 dark:bg-black dark:text-blue-200 break-all">
|
||||
<code class="flex-1 rounded-sm border border-blue-200 bg-surface px-3 py-2 font-mono text-xs text-blue-800 dark:border-blue-800 dark:bg-black dark:text-blue-200 break-all">
|
||||
{newMonitoringKioskLink()}
|
||||
</code>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void copyNewMonitoringKioskLink()}
|
||||
class="flex-shrink-0 rounded-md bg-blue-600 px-3 py-2 text-xs font-semibold text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 dark:bg-blue-600 dark:hover:bg-blue-500"
|
||||
class="shrink-0 rounded-md bg-blue-600 px-3 py-2 text-xs font-semibold text-white shadow-xs hover:bg-blue-700 focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 dark:bg-blue-600 dark:hover:bg-blue-500"
|
||||
>
|
||||
Copy Link
|
||||
</button>
|
||||
@@ -516,7 +516,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
type="button"
|
||||
onClick={focusCreateSection}
|
||||
disabled={!canManage()}
|
||||
class="inline-flex min-h-10 sm:min-h-10 items-center gap-2 rounded-md border border-blue-200 px-3 py-2 text-sm font-semibold text-blue-600 transition hover:bg-blue-50 dark:border-blue-700 dark:text-blue-200 dark:hover:bg-blue-900"
|
||||
class="inline-flex min-h-10 sm:min-h-10 items-center gap-2 rounded-md border border-blue-200 px-3 py-2 text-sm font-semibold text-blue-600 transition hover:bg-blue-50 dark:border-blue-700 dark:text-blue-200 dark:hover:bg-blue-900/25"
|
||||
>
|
||||
Generate new
|
||||
</button>
|
||||
@@ -535,7 +535,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
>
|
||||
<div class="flex min-w-0 items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<h5 class="break-words text-sm font-semibold text-base-content">
|
||||
<h5 class="wrap-break-word text-sm font-semibold text-base-content">
|
||||
{token.name || 'Untitled'}
|
||||
</h5>
|
||||
<div class="mt-1 font-mono text-xs text-muted">{tokenHint(token)}</div>
|
||||
@@ -641,7 +641,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
id={API_TOKEN_CREATE_ANCHOR}
|
||||
padding="none"
|
||||
class={`border border-border transition-shadow ${
|
||||
createHighlight() ? 'ring-2 ring-blue-500 shadow-sm' : ''
|
||||
createHighlight() ? 'ring-2 ring-blue-500 shadow-xs' : ''
|
||||
}`}
|
||||
ref={setCreateSectionRef}
|
||||
>
|
||||
@@ -661,7 +661,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
? undefined
|
||||
: 'Choose a scope preset or custom scope before generating a token.'
|
||||
}
|
||||
class="inline-flex min-h-11 w-full items-center justify-center rounded-md bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60 sm:min-h-10 sm:w-auto dark:bg-blue-500 dark:hover:bg-blue-400"
|
||||
class="inline-flex min-h-11 w-full items-center justify-center rounded-md bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-xs transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60 sm:min-h-10 sm:w-auto dark:bg-blue-500 dark:hover:bg-blue-400"
|
||||
>
|
||||
{isGenerating() ? 'Generating…' : 'Generate'}
|
||||
</button>
|
||||
@@ -682,7 +682,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
onInput={(e) => setNameInput(e.currentTarget.value)}
|
||||
placeholder={API_TOKEN_NAME_PLACEHOLDER}
|
||||
disabled={!canManage()}
|
||||
class="w-full min-h-10 sm:min-h-10 rounded-md border border-border bg-surface px-3 py-2.5 text-sm text-base-content shadow-sm transition focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:focus:border-blue-400 dark:focus:ring-blue-500"
|
||||
class="w-full min-h-10 sm:min-h-10 rounded-md border border-border bg-surface px-3 py-2.5 text-sm text-base-content shadow-xs transition focus:border-blue-500 focus:outline-hidden focus:ring-2 focus:ring-blue-200 dark:focus:border-blue-400 dark:focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -44,16 +44,16 @@ export const APITokenManagerDialogs: Component<APITokenManagerDialogsProps> = (p
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label class="flex cursor-pointer items-start gap-3 rounded-md border border-amber-300 bg-amber-50 p-3 text-sm dark:border-amber-700 dark:bg-amber-900">
|
||||
<label class="flex cursor-pointer items-start gap-3 rounded-md border border-amber-300 bg-amber-50 p-3 text-sm dark:border-amber-700 dark:bg-amber-900/25">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={props.editScopes().includes('*')}
|
||||
onChange={() => props.onToggleEditScope('*')}
|
||||
disabled={props.updatingTokenId() !== null}
|
||||
class="mt-0.5 h-4 w-4 rounded border-border text-amber-600 focus:ring-amber-500"
|
||||
class="mt-0.5 h-4 w-4 rounded-sm border-border text-amber-600 focus:ring-amber-500"
|
||||
/>
|
||||
<span>
|
||||
<span class="block font-semibold text-amber-900 dark:text-amber-100">
|
||||
<span class="block font-semibold text-amber-900/25 dark:text-amber-100">
|
||||
Full access
|
||||
</span>
|
||||
<span class="text-amber-800 dark:text-amber-200">
|
||||
@@ -78,7 +78,7 @@ export const APITokenManagerDialogs: Component<APITokenManagerDialogsProps> = (p
|
||||
checked={props.editScopes().includes(option.value)}
|
||||
onChange={() => props.onToggleEditScope(option.value)}
|
||||
disabled={props.updatingTokenId() !== null}
|
||||
class="mt-0.5 h-4 w-4 rounded border-border text-blue-600 focus:ring-blue-500"
|
||||
class="mt-0.5 h-4 w-4 rounded-sm border-border text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span>
|
||||
<span class="block font-medium text-base-content">{option.label}</span>
|
||||
@@ -151,7 +151,7 @@ export const APITokenManagerDialogs: Component<APITokenManagerDialogsProps> = (p
|
||||
value={props.renameInput()}
|
||||
onInput={(event) => props.onRenameInput(event.currentTarget.value)}
|
||||
disabled={props.isRenaming()}
|
||||
class="w-full min-h-10 rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200"
|
||||
class="w-full min-h-10 rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-2 focus:ring-blue-200"
|
||||
autofocus
|
||||
/>
|
||||
</label>
|
||||
|
||||
@@ -204,7 +204,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
title="External agents"
|
||||
noPadding
|
||||
class={`scroll-mt-20 transition-shadow ${
|
||||
panelHighlight() ? 'ring-2 ring-blue-500 shadow-sm' : ''
|
||||
panelHighlight() ? 'ring-2 ring-blue-500 shadow-xs' : ''
|
||||
}`}
|
||||
>
|
||||
<span id={PULSE_MCP_SETUP_ANCHOR} class="sr-only" aria-hidden="true" />
|
||||
@@ -294,7 +294,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
</li>
|
||||
</ol>
|
||||
<details
|
||||
class="rounded-md border border-border bg-surface-alt/40 p-3"
|
||||
class="rounded-md border border-border p-3"
|
||||
onToggle={(event) => setInstallerCommandsOpen(event.currentTarget.open)}
|
||||
>
|
||||
<summary class="cursor-pointer text-sm font-semibold text-base-content">
|
||||
@@ -326,7 +326,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
</Show>
|
||||
</details>
|
||||
<details
|
||||
class="rounded-md border border-border bg-surface-alt/40 p-3"
|
||||
class="rounded-md border border-border p-3"
|
||||
onToggle={(event) => setClientConfigOpen(event.currentTarget.open)}
|
||||
>
|
||||
<summary class="cursor-pointer text-sm font-semibold text-base-content">
|
||||
@@ -379,7 +379,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
</details>
|
||||
<Show when={hasClientBuilderDetails()}>
|
||||
<details
|
||||
class="rounded-md border border-border bg-surface-alt/40 p-3"
|
||||
class="rounded-md border border-border p-3"
|
||||
onToggle={(event) => setAdvancedClientDetailsOpen(event.currentTarget.open)}
|
||||
>
|
||||
<summary class="cursor-pointer text-sm font-semibold text-base-content">
|
||||
@@ -405,7 +405,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
/>
|
||||
<span>
|
||||
External agents expose {posture().label}{' '}
|
||||
<span class="text-muted/80">through Patrol mode</span>
|
||||
<span class="">through Patrol mode</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -420,7 +420,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
Built-in Pulse views and connected clients all sit behind the same
|
||||
Patrol policy. Connected agents do not get separate powers.
|
||||
</p>
|
||||
<ul class="divide-y divide-border rounded-md border border-border bg-surface-alt/40">
|
||||
<ul class="divide-y divide-border rounded-md border border-border">
|
||||
<For each={surfaceContractEntries()}>
|
||||
{(entry) => (
|
||||
<li class="space-y-1 p-3">
|
||||
@@ -460,7 +460,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
build clients against this instance. They do not change Patrol mode.
|
||||
</p>
|
||||
<Show when={workflowPrompts().length > 0}>
|
||||
<details class="rounded-md border border-border bg-surface-alt/40 p-3">
|
||||
<details class="rounded-md border border-border p-3">
|
||||
<summary class="cursor-pointer text-sm font-semibold text-base-content">
|
||||
<span>Agent starting points</span>{' '}
|
||||
<span class="ml-2 text-xs font-normal text-muted">
|
||||
@@ -481,7 +481,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
prompt.name === AGENT_WORKFLOW_PROMPT_OPERATIONS_LOOP
|
||||
}
|
||||
>
|
||||
<span class="rounded-full border border-emerald-200 bg-emerald-50 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-300">
|
||||
<span class="rounded-full border border-emerald-200 bg-emerald-50 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950/25 dark:text-emerald-300">
|
||||
Patrol
|
||||
</span>
|
||||
</Show>
|
||||
@@ -532,7 +532,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={errorCodeSummaries().length > 0}>
|
||||
<details class="rounded-md border border-border bg-surface-alt/40 p-3 text-xs text-muted">
|
||||
<details class="rounded-md border border-border p-3 text-xs text-muted">
|
||||
<summary class="cursor-pointer text-sm font-semibold text-base-content">
|
||||
Failure codes{' '}
|
||||
<span class="ml-2 text-xs font-normal text-muted">
|
||||
@@ -571,7 +571,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
design notes.
|
||||
</p>
|
||||
<Show when={manifest() && grouped().length > 0}>
|
||||
<details class="rounded-md border border-border bg-surface-alt/40 p-3">
|
||||
<details class="rounded-md border border-border p-3">
|
||||
<summary class="cursor-pointer text-sm font-semibold text-base-content">
|
||||
Agent capabilities{' '}
|
||||
<span class="ml-2 text-xs font-normal text-muted">
|
||||
|
||||
@@ -87,7 +87,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
<Show when={showUpgradePrompts()}>
|
||||
<Card padding="lg" class="space-y-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-md bg-amber-100 dark:bg-amber-900">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-md bg-amber-100 dark:bg-amber-900/25">
|
||||
<Crown class="w-5 h-5 text-amber-600 dark:text-amber-400" />
|
||||
</div>
|
||||
<div>
|
||||
@@ -198,7 +198,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleEdit(profile)}
|
||||
class="inline-flex min-h-11 min-w-11 items-center justify-center rounded-md p-1.5 hover:text-blue-600 hover:bg-blue-50 sm:min-h-0 sm:min-w-0 dark:hover:text-blue-400 dark:hover:bg-blue-900"
|
||||
class="inline-flex min-h-11 min-w-11 items-center justify-center rounded-md p-1.5 hover:text-blue-600 hover:bg-blue-50 sm:min-h-0 sm:min-w-0 dark:hover:text-blue-400 dark:hover:bg-blue-900/25"
|
||||
title="Edit profile"
|
||||
>
|
||||
<Pencil class="w-4 h-4" />
|
||||
@@ -206,7 +206,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDelete(profile)}
|
||||
class="inline-flex min-h-11 min-w-11 items-center justify-center rounded-md p-1.5 hover:text-red-600 hover:bg-red-50 sm:min-h-0 sm:min-w-0 dark:hover:text-red-400 dark:hover:bg-red-900"
|
||||
class="inline-flex min-h-11 min-w-11 items-center justify-center rounded-md p-1.5 hover:text-red-600 hover:bg-red-50 sm:min-h-0 sm:min-w-0 dark:hover:text-red-400 dark:hover:bg-red-900/25"
|
||||
title="Delete profile"
|
||||
>
|
||||
<Trash2 class="w-4 h-4" />
|
||||
@@ -268,7 +268,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
fieldBaseClass="contents"
|
||||
value={assignment()?.profile_id || ''}
|
||||
onChange={(e) => handleAssign(agent.assignmentId, e.currentTarget.value)}
|
||||
selectBaseClass="min-h-10 sm:min-h-9 w-full sm:max-w-xs rounded-md border border-border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
selectBaseClass="min-h-10 sm:min-h-9 w-full sm:max-w-xs rounded-md border border-border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">No profile</option>
|
||||
<Show
|
||||
@@ -369,7 +369,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
value={formName()}
|
||||
onInput={(e) => setFormName(e.currentTarget.value)}
|
||||
placeholder="e.g., Production Servers"
|
||||
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:focus:border-blue-400 dark:focus:ring-blue-800"
|
||||
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-2 focus:ring-blue-200 dark:focus:border-blue-400 dark:focus:ring-blue-800"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -386,7 +386,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
onInput={(e) => setFormDescription(e.currentTarget.value)}
|
||||
placeholder="What is this profile for?"
|
||||
rows={2}
|
||||
textareaBaseClass="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:focus:border-blue-400 dark:focus:ring-blue-800 resize-none"
|
||||
textareaBaseClass="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-2 focus:ring-blue-200 dark:focus:border-blue-400 dark:focus:ring-blue-800 resize-none"
|
||||
/>
|
||||
|
||||
{/* Settings */}
|
||||
@@ -447,7 +447,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
onChange={(e) =>
|
||||
updateSetting(setting.key, e.currentTarget.value || undefined)
|
||||
}
|
||||
selectBaseClass="min-h-10 sm:min-h-9 rounded-md border border-border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
selectBaseClass="min-h-10 sm:min-h-9 rounded-md border border-border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">Default</option>
|
||||
<For each={(setting as SelectSetting).options}>
|
||||
@@ -464,7 +464,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
updateSetting(setting.key, e.currentTarget.value || undefined)
|
||||
}
|
||||
placeholder="30s"
|
||||
class="min-h-10 sm:min-h-9 w-24 rounded-md border border-border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="min-h-10 sm:min-h-9 w-24 rounded-md border border-border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
</Show>
|
||||
<Show when={setting.type === 'string'}>
|
||||
@@ -476,7 +476,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
updateSetting(setting.key, e.currentTarget.value || undefined)
|
||||
}
|
||||
placeholder={(setting as StringSetting).placeholder || ''}
|
||||
class="min-h-10 sm:min-h-9 w-40 rounded-md border bg-surface px-2.5 py-1.5 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="min-h-10 sm:min-h-9 w-40 rounded-md border bg-surface px-2.5 py-1.5 text-sm shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
@@ -493,7 +493,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
</p>
|
||||
<For each={unknownKeys()}>
|
||||
{(key) => (
|
||||
<div class="rounded-md border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900 p-3 mb-2">
|
||||
<div class="rounded-md border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900/25 p-3 mb-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm font-medium text-base-content font-mono">
|
||||
{key}
|
||||
@@ -512,12 +512,12 @@ export const AgentProfilesPanel: Component = () => {
|
||||
updateSetting(key, val || undefined);
|
||||
}
|
||||
}}
|
||||
class="min-h-10 sm:min-h-9 w-32 rounded-md border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="min-h-10 sm:min-h-9 w-32 rounded-md border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => updateSetting(key, undefined)}
|
||||
class="inline-flex min-h-10 min-w-10 sm:min-h-9 sm:min-w-9 items-center justify-center rounded text-slate-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900"
|
||||
class="inline-flex min-h-10 min-w-10 sm:min-h-9 sm:min-w-9 items-center justify-center rounded-sm text-slate-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/25"
|
||||
title="Remove this setting"
|
||||
>
|
||||
<svg
|
||||
|
||||
@@ -324,7 +324,7 @@ export default function AuditLogPanel() {
|
||||
<div
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
class="p-4 bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-800 rounded-md text-red-700 dark:text-red-300"
|
||||
class="p-4 bg-red-50 dark:bg-red-900/25 border border-red-200 dark:border-red-800 rounded-md text-red-700 dark:text-red-300"
|
||||
>
|
||||
{error()}
|
||||
</div>
|
||||
@@ -402,7 +402,7 @@ export default function AuditLogPanel() {
|
||||
render: (event) => (
|
||||
<div class="min-w-0">
|
||||
<span
|
||||
class={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${getAuditEventTypeBadgeClass(event.event)}`}
|
||||
class={`inline-flex items-center px-2 py-0.5 rounded-sm text-xs font-medium ${getAuditEventTypeBadgeClass(event.event)}`}
|
||||
>
|
||||
{event.event}
|
||||
</span>
|
||||
@@ -454,7 +454,7 @@ export default function AuditLogPanel() {
|
||||
when={isVerifying}
|
||||
fallback={
|
||||
<span
|
||||
class={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${badge.className}`}
|
||||
class={`inline-flex items-center px-2 py-0.5 rounded-sm text-xs font-medium ${badge.className}`}
|
||||
>
|
||||
{badge.label}
|
||||
</span>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user