From 92abdcd9ad6471b4f4a5b1bbda84a98679b9a94b Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:26:06 +0100 Subject: [PATCH 1/2] Drop the unused Patrol readiness-failure object behind the toggle toast buildPatrolSettingsReadinessFailure built a full PatrolConfigurationFailureInput (code, status, saved, details, mode, budget, readiness, runtime and blocked state) for the Patrol page's inline "needs attention" state. That state lost its render in 27ba85ddc6 (2026-06-23), when the Configure Patrol popover was removed. Since then the only production caller, the Patrol on/off toggle, has read just `.message` for a warning toast, and it always passed the fixed copy "Patrol setting was saved, but Patrol is not ready to run." getPatrolSavedReadinessWarning now returns that message when the saved response reports patrol_readiness not_ready. The PatrolConfigurationFailureInput type goes with it, because its other consumers were already removed. Runtime: production builds before and after differ in one chunk, AIIntelligence.js (the Patrol page), which is 935 bytes smaller. After normalising chunk hashes, SRI strings and minified identifiers, the only change is the removed builder and details helper, replaced by one status check. The toast string is byte-identical. The minifier had already folded `message || readiness.summary || default` to the caller's constant, so the readiness-summary fallback was unreachable. The toggle no longer reads seven Patrol signals to build the discarded object. The bundle-size check passes. The same Playwright script ran against both builds at 1440 and 390 px and gave identical results. It covered the toggle off and on with a not-ready response, a failed and a successful Patrol mode save, and Settings > Patrol and Provider & Models saves with a not-ready response. Each shows only a toast, with no inline alert or Assistant action. Tests: the hook's unit tests now cover the warning helper. The #1789 test pinned `blockedCause: patrolStatus()?.blocked_cause`, which matched only the removed dead field and not the blockedCause memo the test was written for, so it now pins the memo. frontendResourceTypeBoundaries pins the new helper name. Contracts: patrol-intelligence, frontend-primitives and api-contracts still described the retired Patrol schedule and model popover, its inline dialog failure state, and a saved-configuration-issue surface with cause metadata. They now say where Patrol settings live (Settings > Pulse Intelligence > Patrol and Settings > Provider & Models), that every save outcome is a toast, and that the Patrol page's readiness banner carries the not-ready diagnosis. The popover rules apply only if a popover returns. The Assistant drawer's restore path for saved patrol_configuration_failure sessions is unchanged. --- .../v6/internal/subsystems/api-contracts.md | 14 ++- .../subsystems/frontend-primitives.md | 51 +++++----- .../subsystems/patrol-intelligence.md | 23 ++--- .../0f6190e732d7b132.json | 44 +++++++++ .../usePatrolIntelligenceState.test.ts | 59 +++-------- .../patrol/patrolInvestigationContextModel.ts | 21 ---- .../patrol/usePatrolIntelligenceState.ts | 98 +++---------------- .../frontendResourceTypeBoundaries.test.ts | 2 +- 8 files changed, 119 insertions(+), 193 deletions(-) create mode 100644 frontend-modern/browser-verification/0f6190e732d7b132.json diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index f6943a66d2..c2457e7f0f 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -4020,12 +4020,16 @@ the authoritative analysis outcome. `/api/settings/ai/update` may save a selected Patrol provider/model even when that model is not ready for tool-backed Patrol execution, but it must echo `patrol_readiness` with stable `cause` metadata and execution routes - must continue to fail closed before model calls. Frontend Patrol settings + must continue to fail closed before model calls. Frontend settings consumers must surface that saved-but-not-ready response as a saved - configuration issue with the echoed provider, model, cause, and summary - instead of reporting the successful save as a failed save or hiding the - readiness blocker behind a generic notification; and the structured - investigation-record contract, so unified findings may + configuration issue rather than a failed save, and never as inline failure + state: every Pulse Intelligence settings page (Provider & Models, Patrol, + Assistant, Service context) raises a warning notification naming the echoed + summary, provider, and model, while the Patrol page's on/off save raises a + fixed not-ready warning and leaves the diagnosis to the page's own readiness + surfaces, such as the readiness banner that names the summary, provider, + and model while the header offers `Fix setup` when Patrol is active; and + the structured investigation-record contract, so unified findings may expose `investigation_record` only through the shared `aicontracts.InvestigationRecord` payload shape, with frontend API types and backend contract tests updated in the same slice as any field change. diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 7707b99374..85dce4e823 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -3144,33 +3144,30 @@ Agent`), with the plain-language source phrase available through accessible operator action or selected Patrol context, uses distinct monitor labels, and does not become a nested card, generic dashboard strip, trust summary, or duplicate Patrol empty-work list. The - Patrol schedule and model drawer is part of that shared - feature-presentation boundary: it must stay viewport-bounded, expose an - accessible dialog label, keep the four-level control policy on the default - Patrol header, and keep provider model, schedule, trigger tuning, and - readiness validation inside the secondary disclosure. Backend save rejection reasons must pass - through as inline dialog state instead of being replaced with generic toast - copy, and that advanced disclosure must open when the inline state exists. - When the failure includes - Patrol readiness context, the inline state must expose the provider, model, - and readiness summary next to a direct provider-settings action instead of - hiding that diagnosis behind Assistant alone. The provider-model selector in - that popover must stay bound to the shared runtime settings/model catalog - even when the popover mounts after async catalog loading, but the full - catalog must stay behind an explicit change action so the default advanced - drawer leads with the current effective model summary rather than a raw - provider route list. A saved direct-provider Patrol model still renders as - that model instead of visually falling back to the default selection. - Successful provider-model saves that return a not-ready Patrol - readiness snapshot must use that same inline surface with `needs attention` - wording, and any Assistant handoff from that state describes a saved - configuration issue rather than a failed save. When governed fixes are locked, the same Patrol state - owner must clear stale full-mode unlock state before persisting the - monitor-only autonomy payload, so disabled paid controls cannot leak stale - permission into a save. If that inline state opens Assistant, the Patrol - feature must hand off - a source-named, model-only briefing and close the popover so the shared - Assistant drawer is not visually hidden behind feature chrome. When a + Patrol page keeps the four-level control policy on its header and has no + schedule and model drawer: schedule, trigger tuning, and the Patrol model + override live on Settings > Pulse Intelligence > Patrol, and providers and + the shared default model on Settings > Provider & Models. Those saves + report through the shared notification toasts, not inline state: a + rejected save raises an error toast carrying the backend's concrete reason + rather than generic fallback copy, and a saved response whose Patrol + readiness is not ready or degraded raises a warning toast naming the + readiness summary, provider, and model instead of a success toast. On the + Patrol page, an on/off save whose response reports Patrol not ready raises + a fixed warning toast, and a failed Patrol mode save raises an error toast + and restores the previous mode; neither renders inline save-failure state + or offers an Assistant handoff. If a Patrol settings popover or inline + save-failure state returns, it must stay viewport-bounded, expose an + accessible dialog label, bind any model selector to the shared runtime + settings and model catalog, show readiness context (provider, model, + summary) next to a direct provider-settings action rather than behind + Assistant alone, and describe a saved but not ready response as a saved + configuration issue rather than a failed save. An Assistant handoff from it + must send a source-named, model-only briefing and close the popover so the + shared Assistant drawer is not visually hidden behind feature chrome. When + governed fixes are locked, the Patrol state owner must clear stale + full-mode unlock state before persisting the monitor-only autonomy payload, + so disabled paid controls cannot leak stale permission into a save. When a restored Patrol assessment session is attached, the shared Assistant drawer empty state must stay aligned with that source-named briefing and must not render generic diff --git a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md index 92f205d675..cd901c6f4e 100644 --- a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md +++ b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md @@ -1567,17 +1567,18 @@ suppress stale healthy summary headlines such as `Health A ยท 100/100` even if the last summary payload still looks healthy. Legacy `/ai` entry points must redirect into that same Patrol-owned shell rather than preserving a second canonical route. -That same browser proof now covers the Patrol control and advanced-settings -split. The advanced Patrol settings drawer must stay within the desktop -viewport, avoid duplicating the inline Patrol control policy, expose -provider/model, schedule, trigger, and user-level model checks directly, and surface -the backend's concrete license/validation reason when a settings change is -rejected instead of replacing it with a generic `Failed to save advanced -settings` toast. That inline failure no longer -opens Assistant. If it does again, the handoff is model-only explanation -context: raw command, script, credential, and provider-detail payloads stay -redacted, Assistant opens with `autonomousMode:false`, and the Patrol control -panel closes so the operator is not left behind an overlapping popover. +That same browser proof now covers where Patrol settings are saved. The +Patrol page has no settings drawer or popover: Patrol schedule, triggers, and +the Patrol model override live on Settings > Pulse Intelligence > Patrol +(`Save Patrol settings`), and providers and the shared default model on +Settings > Provider & Models. A rejected save there raises an error +notification carrying the backend's concrete license/validation reason +instead of generic fallback copy, and it offers no Assistant handoff. If a +Patrol settings popover or an Assistant handoff from a settings failure +returns, the handoff is model-only explanation context: raw command, script, +credential, and provider-detail payloads stay redacted, Assistant opens with +`autonomousMode:false`, and the popover closes so the operator is not left +behind it. Patrol-control save-failure sessions saved by earlier builds keep the compatible `handoff_metadata.kind=patrol_configuration_failure` plus only the runtime-failure boolean needed for drawer/session presentation, so they restore diff --git a/frontend-modern/browser-verification/0f6190e732d7b132.json b/frontend-modern/browser-verification/0f6190e732d7b132.json new file mode 100644 index 0000000000..bd6ecf244f --- /dev/null +++ b/frontend-modern/browser-verification/0f6190e732d7b132.json @@ -0,0 +1,44 @@ +{ + "version": 1, + "base_sha": "421d1e5423e5b83b7923ea8c3b133ad520cd5f60", + "verified_at": "2026-10-07T15:20:38Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts", + "frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts" + ], + "content_sha256": { + "frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts": "f1e249c36f0f0f9810330a37cc9a80cb43baa1cd123db1defd3f27720d39912a", + "frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts": "cb0a6962ed929238a974a3bbe5f69f4c89961ff30c88c2c94398073b9ef99a1c" + }, + "routes": [ + "/patrol", + "/patrol (Mode and automation disclosure)", + "/settings/pulse-intelligence/patrol", + "/settings/pulse-intelligence" + ], + "viewports": [ + { + "width": 1440, + "height": 900 + }, + { + "width": 390, + "height": 844 + } + ], + "states": [ + "Embedded production builds (vite build into internal/api/frontend-modern/dist, go build) of the parent tree (333e51d2de; rebased onto 421d1e5423, which changes only contract docs) and this tree, each on a private DEMO_MODE mock stack with a fresh data dir; the same Playwright script ran against both and produced identical results on all 31 observed fields at both widths.", + "Patrol on/off save whose PUT /api/settings/ai/update response (page.route) reports patrol_readiness not_ready: switching off and back on each raise only the warning toast 'Patrol setting was saved, but Patrol is not ready to run.' (role=alert inside the Notifications region, close button only); no role=alert outside the toast region, no 'needs attention' text, no readiness summary rendered inline, no Assistant action.", + "Failed Patrol mode save (Pro mode selector unlocked by adding ai_autofix to runtime-capabilities via page.route; PUT /api/ai/patrol/autonomy answered 500 {error}): only the error toast with the server reason; mode badge returns to Watch only; no inline error or 'needs attention' state.", + "Successful Patrol mode save (PUT 200, reconcile GET returns approval): Ask first becomes pressed and the badge reads Ask first; no new toast and no inline alert.", + "Settings > Pulse Intelligence > Patrol save (Schedule -> Every hour) and Settings > Provider & Models save with a not_ready response: only the warning toast ', but Patrol is not ready: The selected model cannot run Patrol tools. \u00b7 Provider: OpenRouter \u00b7 Model: openrouter:deepseek/deepseek-r1'; no inline alert.", + "Patrol status with readiness not_ready (page.route on /api/ai/patrol/status): the page's own 'Patrol model issue' banner names summary, provider and model, and the header shows Fix setup, at both widths.", + "Dist proof: 187 files each side; after normalising chunk hashes and SRI strings only assets/AIIntelligence.js differs (935 bytes smaller): the removed failure builder, details helper and surface callback replaced by one status check, toast string byte-identical; check-bundle-size.mjs passes." + ], + "interactions": [ + "Demo sign-in; on /patrol click Toggle Patrol twice (off, then on) with a not-ready settings response; open 'Mode and automation' and click Ask first with a rejected save, then again with an accepted save; desktop mouse and phone touch (isMobile) contexts.", + "On /settings/pulse-intelligence/patrol select Schedule 'Every hour' and click Save Patrol settings; on /settings/pulse-intelligence click Save provider settings; both with a not-ready response.", + "Screenshots of each toast state captured and inspected at both widths." + ] +} diff --git a/frontend-modern/src/features/patrol/__tests__/usePatrolIntelligenceState.test.ts b/frontend-modern/src/features/patrol/__tests__/usePatrolIntelligenceState.test.ts index 4dcef31829..a7e54782cb 100644 --- a/frontend-modern/src/features/patrol/__tests__/usePatrolIntelligenceState.test.ts +++ b/frontend-modern/src/features/patrol/__tests__/usePatrolIntelligenceState.test.ts @@ -3,7 +3,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { PATROL_MANUAL_SYNC_TIMEOUT_MS, PATROL_REFRESH_TIMEOUT_MS, - buildPatrolSettingsReadinessFailure, + getPatrolSavedReadinessWarning, openPatrolAssistantWorkflowHandoff, patrolStartFailureMessage, recordPatrolControlStarterActivity, @@ -338,24 +338,25 @@ describe('usePatrolIntelligenceState', () => { }); }); - describe('buildPatrolSettingsReadinessFailure', () => { - it('ignores settings snapshots that do not block Patrol readiness', () => { + describe('getPatrolSavedReadinessWarning', () => { + it('stays quiet when the saved settings do not block Patrol readiness', () => { + expect(getPatrolSavedReadinessWarning(null)).toBeNull(); expect( - buildPatrolSettingsReadinessFailure({ - settings: settingsWithReadiness({ + getPatrolSavedReadinessWarning( + settingsWithReadiness({ status: 'warning', ready: true, summary: 'Patrol can run with reduced confidence.', checks: [], }), - }), + ), ).toBeNull(); }); - it('builds a saved configuration issue from a not-ready settings response', () => { + it('warns that a not-ready Patrol setting was still saved', () => { expect( - buildPatrolSettingsReadinessFailure({ - settings: settingsWithReadiness({ + getPatrolSavedReadinessWarning( + settingsWithReadiness({ status: 'not_ready', ready: false, cause: 'model_unsupported_tools', @@ -364,40 +365,8 @@ describe('usePatrolIntelligenceState', () => { model: 'ollama:deepseek-r1:7b', checks: [], }), - autonomyLevel: 'monitor', - fullModeUnlocked: false, - investigationBudget: 15, - investigationTimeoutSec: 300, - runtimeState: 'blocked', - blockedReason: 'Connect a tool-capable Patrol model.', - }), - ).toEqual({ - message: 'The selected model cannot run Patrol tools.', - code: 'patrol_readiness_not_ready', - status: 409, - saved: true, - details: { - status: 'not_ready', - cause: 'model_unsupported_tools', - summary: 'The selected model cannot run Patrol tools.', - provider: 'ollama', - model: 'ollama:deepseek-r1:7b', - }, - autonomyLevel: 'monitor', - fullModeUnlocked: false, - investigationBudget: 15, - investigationTimeoutSec: 300, - readiness: { - status: 'not_ready', - cause: 'model_unsupported_tools', - summary: 'The selected model cannot run Patrol tools.', - provider: 'ollama', - model: 'ollama:deepseek-r1:7b', - }, - runtimeState: 'blocked', - blockedReason: 'Connect a tool-capable Patrol model.', - blockedCause: undefined, - }); + ), + ).toBe('Patrol setting was saved, but Patrol is not ready to run.'); }); }); @@ -410,6 +379,8 @@ describe('usePatrolIntelligenceState', () => { 'provider_not_configured', ); expect(resolvePatrolBlockedActionCause('', '')).toBeUndefined(); - expect(patrolIntelligenceStateSource).toContain('blockedCause: patrolStatus()?.blocked_cause'); + expect(patrolIntelligenceStateSource).toContain( + 'const blockedCause = createMemo(() => patrolStatus()?.blocked_cause);', + ); }); }); diff --git a/frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts b/frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts index ab961032a5..ec242837ed 100644 --- a/frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts +++ b/frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts @@ -182,27 +182,6 @@ export interface PatrolRunAssistantHandoff { context: AIChatContext; } -export interface PatrolConfigurationFailureInput { - message: string; - code?: string; - status?: number; - saved?: boolean; - details?: Record; - autonomyLevel?: string; - fullModeUnlocked?: boolean; - investigationBudget?: number; - investigationTimeoutSec?: number; - readiness?: { - status?: string; - cause?: string; - summary?: string; - provider?: string; - model?: string; - } | null; - runtimeState?: string; - blockedReason?: string; - blockedCause?: string; -} const MAX_PATROL_RUN_HANDOFF_RESOURCES = 8; export function buildPatrolInvestigationRecordPresentation( diff --git a/frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts b/frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts index d2eb5d4dd6..1f242e6c97 100644 --- a/frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts +++ b/frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts @@ -62,7 +62,6 @@ import { patrolAssistantFindingHandoffRequiresApprovalMode, type PatrolAssistantApprovalBriefingInput, type PatrolAssistantFindingHandoff, - type PatrolConfigurationFailureInput, } from './patrolInvestigationContextModel'; import { schedulePatrolRunAcceptanceReconciliation } from './patrolRunAcceptance'; @@ -141,19 +140,6 @@ export const patrolStartFailureMessage = (error: unknown): string => ? `Could not reach Pulse to start Patrol: ${patrolErrorMessage(error, 'network request failed')}` : patrolErrorMessage(error, 'Pulse rejected the Patrol run'); -const buildReadinessDetails = ( - readiness: NonNullable, -): Record => { - const details: Record = { - status: readiness.status, - }; - if (readiness.cause?.trim()) details.cause = readiness.cause.trim(); - if (readiness.summary?.trim()) details.summary = readiness.summary.trim(); - if (readiness.provider?.trim()) details.provider = readiness.provider.trim(); - if (readiness.model?.trim()) details.model = readiness.model.trim(); - return details; -}; - export function resolvePatrolAutonomyLevelForSave( level: PatrolAutonomyLevel, fullModeUnlocked: boolean, @@ -184,52 +170,16 @@ export function resolvePatrolAutonomySettingsForSave({ }; } -export function buildPatrolSettingsReadinessFailure({ - settings, - message, - autonomyLevel, - fullModeUnlocked, - investigationBudget, - investigationTimeoutSec, - runtimeState, - blockedReason, - blockedCause, -}: { - settings: AISettings | null | undefined; - message?: string; - autonomyLevel?: string; - fullModeUnlocked?: boolean; - investigationBudget?: number; - investigationTimeoutSec?: number; - runtimeState?: string; - blockedReason?: string; - blockedCause?: string; -}): PatrolConfigurationFailureInput | null { - const readiness = settings?.patrol_readiness; - if (!readiness || readiness.status !== 'not_ready') return null; - - return { - message: - message || readiness.summary || 'Patrol settings were saved, but Patrol is not ready to run.', - code: 'patrol_readiness_not_ready', - status: 409, - saved: true, - details: buildReadinessDetails(readiness), - autonomyLevel, - fullModeUnlocked, - investigationBudget, - investigationTimeoutSec, - readiness: { - status: readiness.status, - cause: readiness.cause, - summary: readiness.summary, - provider: readiness.provider, - model: readiness.model, - }, - runtimeState, - blockedReason, - blockedCause, - }; +/** + * A Patrol on/off save whose response reports Patrol not ready was still + * saved, so it raises a warning rather than an error. + */ +export function getPatrolSavedReadinessWarning( + settings: AISettings | null | undefined, +): string | null { + return settings?.patrol_readiness?.status === 'not_ready' + ? 'Patrol setting was saved, but Patrol is not ready to run.' + : null; } /** @@ -380,26 +330,6 @@ export function usePatrolIntelligenceState() { applyPatrolAISettings(aiRuntimeSettings()); }); - const surfaceSavedPatrolReadinessIssue = ( - settings: AISettings | null | undefined, - message?: string, - ) => { - const failure = buildPatrolSettingsReadinessFailure({ - settings, - message, - autonomyLevel: autonomyLevel(), - fullModeUnlocked: fullModeUnlocked(), - investigationBudget: investigationBudget(), - investigationTimeoutSec: investigationTimeout(), - runtimeState: runtimeState(), - blockedReason: blockedReason(), - blockedCause: patrolStatus()?.blocked_cause, - }); - if (failure) { - notificationStore.warning(failure.message); - } - }; - async function handleTogglePatrol() { if (isTogglingPatrol()) return; setIsTogglingPatrol(true); @@ -419,10 +349,10 @@ export function usePatrolIntelligenceState() { } else { setPatrolEnabledLocal(newValue); } - surfaceSavedPatrolReadinessIssue( - data, - 'Patrol setting was saved, but Patrol is not ready to run.', - ); + const readinessWarning = getPatrolSavedReadinessWarning(data); + if (readinessWarning) { + notificationStore.warning(readinessWarning); + } if (refetchPatrolStatus) { refetchPatrolStatus(); } diff --git a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts index f7d939d3cb..157792f0ca 100644 --- a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts +++ b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts @@ -3610,7 +3610,7 @@ describe('frontend resource type boundaries', () => { expect(patrolIntelligenceStateSource).toMatch( /import\s*\{[^}]*\bbuildPatrolAssistantFindingHandoffFromUnifiedFinding\b[^}]*\}\s*from '\.\/patrolInvestigationContextModel';/, ); - expect(patrolIntelligenceStateSource).toContain('buildPatrolSettingsReadinessFailure'); + expect(patrolIntelligenceStateSource).toContain('getPatrolSavedReadinessWarning'); expect(patrolIntelligenceStateSource).not.toContain('recent_changes?.length'); expect(patrolIntelligenceStateSource).not.toContain('governed resource${'); expect(patrolInvestigationContextModelSource).toContain( From 2cf213a77d2a50dcd47c0b0ab07c49f97bdd0534 Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:34:21 +0100 Subject: [PATCH 2/2] Describe the Patrol mode selector where it renders today patrol-intelligence still described the Patrol mode selector as living in "that header and configuration dialog", with the constrained configuration dialog using the prominent layout. The configuration dialog was removed in 27ba85ddc6. renderAutonomyPolicyControl has one call site, PatrolIntelligenceHeader's `Mode and automation` disclosure, which uses the default segmented variant, and the only remaining Patrol dialog, the Autopilot acknowledgement, has no mode picker. The clause now says where the header renders the selector. A surface that hosts it in constrained space may still use the shared prominent layout. --- .../v6/internal/subsystems/patrol-intelligence.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md index cd901c6f4e..b96df3b120 100644 --- a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md +++ b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md @@ -1624,12 +1624,13 @@ ready raises a fixed warning notification, a failed Patrol mode save raises an e notification, and neither offers an Assistant handoff. A provider or model save in Settings > Provider & Models reports the readiness summary, provider and model in its own save message. -The Patrol mode selector in that header and configuration dialog must -compose the shared `frontend-modern/src/components/shared/FilterButtonGroup.tsx` -instead of rebuilding a local active-button group. The wide default Patrol -header uses the segmented layout; the constrained configuration dialog may use -the shared prominent layout so all four mode labels remain readable without -inventing a Patrol-local selector. Patrol owns the default visible four-level +The Patrol mode selector in that header must compose the shared +`frontend-modern/src/components/shared/FilterButtonGroup.tsx` instead of +rebuilding a local active-button group. When the header shows Patrol mode +options, it renders the selector in the segmented layout inside its `Mode and +automation` disclosure; a constrained surface that hosts it may use the shared +prominent layout so all four mode labels remain readable without inventing a +Patrol-local selector. Patrol owns the default visible four-level policy presentation (`Watch only`, `Ask first`, `Safe auto-fix`, `Autopilot`), entitlement locks, and the rule that choosing the highest Autopilot level sends `full_mode_unlocked:true` while choosing any lower level clears that