Merge remote-tracking branch 'origin/main' into fix/anthropic-cache-usage-accounting

This commit is contained in:
rcourtman
2026-10-03 21:47:09 +01:00
5 changed files with 86 additions and 2 deletions
@@ -3433,6 +3433,15 @@ Patrol is off or its model needs setup, while retaining the existing setup task
above it. Desktop and narrow browser checks must show the task and attention
list together, then allow a selected decision to open and return without
changing the disabled Patrol controls or overflowing the viewport.
The selected-detail header (title, queue position, Back, and the phone
Previous and Next issue controls) is `sticky top-0` with an opaque
`bg-surface/95` background, and the `Patrol decision inbox` section clips its
rounded corners with `overflow-clip`, never `overflow-hidden`. An
`overflow-hidden` ancestor becomes the sticky scroll container, so on phones
the header scrolled away with the page and took the queue controls with it.
Below `lg` the header pins against `.app-scroll-shell`; at `lg` it pins inside
the detail panel's own scroller. The class contract is pinned in
`frontend-modern/src/features/patrol/__tests__/PatrolAttentionWorkbench.test.tsx`.
The objective brief and optional-context fields in `PatrolObjectivesPanel`
share the same `FormTextarea` ownership contract.
@@ -1260,6 +1260,12 @@ proof, including failed verification and reduced motion, remains
The durable decision and proof record is
`internal/records/operational-trust-governed-docker-restart-2026-07-19.md`.
Reviewing a long decision on a phone must not lose the queue controls: the
selected-detail header with Back, Previous, and Next issue stays pinned while
the detail scrolls. The shell-level rule (opaque sticky header, `overflow-clip`
inbox section) is owned by the frontend-primitives attention workbench shell
contract.
## Current State
The Patrol 2.0 frontend now uses a one-task-at-a-time, decision-first
@@ -0,0 +1,42 @@
{
"version": 1,
"base_sha": "beb6c3f6a6f3fad74a1b9f76745bacdadcab7834",
"verified_at": "2026-10-03T18:34:00Z",
"result": "passed",
"changed_paths": [
"frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx"
],
"content_sha256": {
"frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx": "5d7c824c96e90ee8c4b8a8a92ac34e42ecd3f053925512de6fa0408f6bd0370e"
},
"routes": [
"/patrol"
],
"viewports": [
{
"width": 1440,
"height": 900
},
{
"width": 1024,
"height": 768
},
{
"width": 390,
"height": 844
}
],
"states": [
"mock stack, decision inbox with 50 decisions, light and dark color schemes",
"390px touch: selected decision detail with Back to list, Previous and Next issue in the header; header top 484/284/84/4/4 while .app-scroll-shell scrolls 0/200/400/600/max, staying topmost at the shell padding edge",
"390px: header background resolves to surface at 95% opacity in light and dark, scrolled content no longer shows through",
"1440px: Evidence and history expanded so the detail panel overflows (1165/832); header stays at the panel top while the panel scrolls 0/300/333, opaque in both themes",
"inbox section computed overflow clip with 12px corner radius, rounded corners still clipped at 1440"
],
"interactions": [
"clicked Start review to open the highest-priority decision",
"scrolled .app-scroll-shell at 390px and checked the header stays the topmost element at its position",
"expanded Evidence and history and scrolled the lg detail panel at 1440px and 1024px",
"stepped through all 50 decisions with Next decision at 1440px"
]
}
@@ -384,7 +384,7 @@ export function PatrolAttentionWorkbench(
return (
<section
aria-label="Patrol decision inbox"
class="overflow-hidden rounded-xl border border-border bg-surface shadow-xs"
class="overflow-clip rounded-xl border border-border bg-surface shadow-xs"
>
<div class="border-b border-border px-4 py-4 sm:px-6">
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
@@ -877,7 +877,7 @@ function AttentionDetail(props: {
aria-labelledby="attention-detail-title"
aria-busy={props.loading}
>
<div class="sticky top-0 z-20 border-b border-border px-4 py-3 backdrop-blur-sm sm:px-5">
<div class="sticky top-0 z-20 border-b border-border bg-surface/95 px-4 py-3 backdrop-blur-sm sm:px-5">
<div class="flex items-center justify-between gap-3">
<button
type="button"
@@ -479,6 +479,33 @@ describe('PatrolAttentionWorkbench', () => {
expect(window.location.search).toBe('?attention=record-1');
});
it('keeps the detail header and queue controls pinned while a long decision scrolls', async () => {
const active = item();
apiMocks.getList.mockResolvedValue(
listResponse([active], summary({ activeCount: 1, openCount: 1, calm: false })),
);
apiMocks.getDetail.mockResolvedValue(detail(active));
renderWorkbench();
fireEvent.click(
await screen.findByRole('button', { name: 'Open Database VM · Disk pressure' }),
);
const detailRegion = await screen.findByRole('complementary', {
name: 'Database VM · Disk pressure',
});
const header = within(detailRegion)
.getByRole('button', { name: 'Back to attention list' })
.closest('div.sticky');
expect(header).toHaveClass('top-0', 'bg-surface/95');
// overflow-hidden would make the inbox section the sticky scroll container,
// so on phones the header scrolled away with the page; overflow-clip keeps the
// rounded-corner clipping without creating a scroll container.
const inbox = screen.getByRole('region', { name: 'Patrol decision inbox' });
expect(inbox).toHaveClass('overflow-clip', 'rounded-xl');
expect(inbox).not.toHaveClass('overflow-hidden');
});
it('uses plain scan-row language while keeping unknown trust metadata in detail', async () => {
const unknownTrust = item({
state: 'unknown',