diff --git a/docs/features/compose-doctor.mdx b/docs/features/compose-doctor.mdx
index b12d04ee..66ddffc3 100644
--- a/docs/features/compose-doctor.mdx
+++ b/docs/features/compose-doctor.mdx
@@ -58,9 +58,9 @@ The summary card at the top of the Doctor tab reflects the overall outcome of th
| **High risk** | Highest active finding is high risk | Amber card with a count of active findings by severity |
| **Warning** | Highest active finding is a warning | Blue card with a count of active findings by severity |
| **Info** | All active findings are informational | Muted card with a count of active findings by severity |
-| **Acknowledged** | Every finding from the last run has been acknowledged | Muted card noting all findings are acknowledged |
+| **All clear · findings acknowledged** | Model rendered; every finding from the last run has been acknowledged | Green card with "No active findings remain. One or more detected issues were reviewed and acknowledged by an authorized operator." |
-The summary card also shows when the run happened and who triggered it: "ran 5 minutes ago by admin". If any findings are acknowledged, the line adds a count: "3 active (1 blocker · 2 warning) · 2 acknowledged".
+The summary card also shows when the run happened and who triggered it: "ran 5 minutes ago by admin". When active findings remain alongside acknowledgements, the line adds a count: "3 active (1 blocker · 2 warning) · 2 acknowledged".
A small colored dot appears on the **Doctor** tab label when the last run's active findings include a blocker (red) or a high-risk finding (amber). Click the **X** on the summary card to dismiss it; this also clears the tab dot. The dismissal is remembered per stack and node, and clears itself automatically the moment the finding set changes (a rule clearing, a new rule firing, or a severity change), so a stale dismissal can never hide a genuinely new problem. It never clears just by opening the tab or re-running preflight to the same result, and it does not sync across browsers or teammates: it is stored in your browser only.
diff --git a/frontend/src/components/stack/PreflightPanel.test.tsx b/frontend/src/components/stack/PreflightPanel.test.tsx
index 5406da48..962461f3 100644
--- a/frontend/src/components/stack/PreflightPanel.test.tsx
+++ b/frontend/src/components/stack/PreflightPanel.test.tsx
@@ -81,7 +81,65 @@ describe('PreflightPanel', () => {
render();
const status = await screen.findByTestId('preflight-status');
expect(status).toHaveAttribute('data-status', 'pass');
- expect(status).toHaveTextContent(/all clear/i);
+ expect(status).toHaveTextContent('all clear');
+ expect(status).not.toHaveTextContent(/findings acknowledged/i);
+ expect(status).toHaveTextContent('No issues found in the effective model.');
+ });
+
+ it('renders all-clear · findings acknowledged when every finding is acknowledged', async () => {
+ vi.mocked(apiFetch).mockResolvedValue(jsonRes(report({
+ status: 'pass',
+ acknowledgedCount: 1,
+ findings: [{
+ ruleId: 'privileged',
+ severity: 'high',
+ title: 'Privileged container',
+ message: 'runs privileged',
+ service: 'web',
+ acknowledged: true,
+ }],
+ })));
+ render();
+ const status = await screen.findByTestId('preflight-status');
+ expect(status).toHaveAttribute('data-status', 'pass');
+ expect(status).toHaveTextContent('all clear · findings acknowledged');
+ expect(status).toHaveTextContent(
+ 'No active findings remain. One or more detected issues were reviewed and acknowledged by an authorized operator.',
+ );
+ expect(status.className).toContain('border-success/40');
+ expect(status.className).toContain('bg-success/[0.06]');
+ expect(status.className).toContain('text-success');
+ expect(status.querySelector('svg.lucide-check')).not.toBeNull();
+ expect(status.querySelector('svg.lucide-shield-check')).toBeNull();
+ expect(screen.getByTestId('preflight-acknowledged-section')).toBeInTheDocument();
+ });
+
+ it('keeps the severity summary when active findings remain alongside acknowledgements', async () => {
+ vi.mocked(apiFetch).mockResolvedValue(jsonRes(report({
+ status: 'high',
+ highestSeverity: 'high',
+ acknowledgedCount: 1,
+ findings: [
+ { ruleId: 'privileged', severity: 'high', title: 'Privileged container', message: 'runs privileged', service: 'web' },
+ {
+ ruleId: 'image-latest',
+ severity: 'warning',
+ title: 'Image uses a moving tag',
+ message: 'latest tag',
+ service: 'web',
+ acknowledged: true,
+ },
+ ],
+ })));
+ render();
+ const status = await screen.findByTestId('preflight-status');
+ expect(status).toHaveAttribute('data-status', 'high');
+ expect(status).toHaveTextContent('high risk');
+ expect(status).not.toHaveTextContent(/all clear/i);
+ expect(status).toHaveTextContent('1 active');
+ expect(status).toHaveTextContent('1 acknowledged');
+ expect(screen.getByText('Privileged container')).toBeInTheDocument();
+ expect(screen.getByTestId('preflight-acknowledged-section')).toBeInTheDocument();
});
it('keeps All Clear when only inherited-healthcheck notes remain', async () => {
diff --git a/frontend/src/components/stack/PreflightPanel.tsx b/frontend/src/components/stack/PreflightPanel.tsx
index 6a5662b8..df274e93 100644
--- a/frontend/src/components/stack/PreflightPanel.tsx
+++ b/frontend/src/components/stack/PreflightPanel.tsx
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from 'react';
import {
Check, TriangleAlert, ShieldAlert, Info, RefreshCw, Stethoscope, X,
- ChevronDown, ChevronRight, ShieldCheck, type LucideIcon,
+ ChevronDown, ChevronRight, type LucideIcon,
} from 'lucide-react';
import { apiFetch } from '@/lib/api';
import { cn } from '@/lib/utils';
@@ -83,8 +83,16 @@ function summaryMeta(
line: report.renderError ?? 'Sencho could not render the effective Compose model.',
};
}
- if (report.activeCount === 0 && report.acknowledgedCount === 0) {
- return { label: 'all clear', icon: Check, tone: 'border-success/40 bg-success/[0.06] text-success', line: 'No issues found in the effective model.' };
+ if (report.activeCount === 0) {
+ const acknowledgedOnly = report.acknowledgedCount > 0;
+ return {
+ label: acknowledgedOnly ? 'all clear · findings acknowledged' : 'all clear',
+ icon: Check,
+ tone: 'border-success/40 bg-success/[0.06] text-success',
+ line: acknowledgedOnly
+ ? 'No active findings remain. One or more detected issues were reviewed and acknowledged by an authorized operator.'
+ : 'No issues found in the effective model.',
+ };
}
const meta = SEVERITY_META[report.activeHighestSeverity ?? 'info'];
const activeParts = GROUP_ORDER
@@ -95,7 +103,7 @@ function summaryMeta(
const line = report.acknowledgedCount > 0
? `${report.activeCount} active${activeParts ? ` (${activeParts})` : ''} · ${report.acknowledgedCount} acknowledged`
: (activeParts || `${report.activeCount} active`);
- return { label: report.activeCount === 0 ? 'acknowledged' : meta.label, icon: report.activeCount === 0 ? ShieldCheck : meta.icon, tone: report.activeCount === 0 ? 'border-muted bg-card/40 text-stat-subtitle' : meta.tone, line };
+ return { label: meta.label, icon: meta.icon, tone: meta.tone, line };
}
function expiryComboboxOptions(finding: PreflightFinding): ComboboxOption[] {