GitHub
07/05/2026, 10:59 AMCarveDetailPage)
• Summary band above the carved-files table: Files (count + archived count), Total size (sum of carve_size), Blocks (completed/total + %), and Max block size (max(f.block_size) across the carve's files). An operator can read the carve's state at a glance instead of scanning rows.
• Expanded files table — added Path and Archived columns. The redundant per-row Block size column was removed (block size is a per-carve value); it now lives once as Max block size in the summary. The table is wrapped in an overflow-x-auto container with a min-w so it fills the available width and scrolls only when genuinely narrow. Columns: Node · Path · Status · Size · Blocks · Completed · Archived · Download.
• Expires added to the carve header (was missing entirely) — shows the relative expiration or never for a no-expiration carve.
• Refresh button added to the "Carved files" header, mirroring the query detail page. It invalidates the carve, the carves list, and the node lookup so an operator watching blocks land can pull the latest state now instead of waiting for the 15s poll. Disabled while a fetch is in flight, with a "refreshing…" indicator.
Query detail (QueryDetailPage)
• Refresh now refreshes everything. The Refresh button previously invalidated only the query and its results; it now calls qc.invalidateQueries() with no key, refetching every active query on the page (query, results, queries list, and any other live data) so an operator watching a query land gets a fully fresh view.
• Expiration is always shown. The detail previously rendered "Expires" only when a real expiration existed, so a no-expiration query showed nothing — an operator couldn't tell unset from hidden. It now always renders Expires, showing never for the zero/`0001` case (timestamp in the tooltip). The set side was already correct (run page sends `exp_hours`; backend maps 0 → time.Time{} and otherwise now + hours), so only the display changed.
Behavior / risk notes
• Pure view-layer changes; no backend, routing, auth, or persistence changes.
• The carve files table is wider (8 columns + download) and intentionally scrolls horizontally on narrow viewports rather than collapsing columns or truncating data.
• Max block size is derived client-side from the existing CarveFile.block_size field — no new API field.
Validation
• tsc --noEmit clean.
• New CarveDetailPage.test.tsx cases: the Refresh button reloads the carve; a 2-file carve renders the summary band (Files / Total size / Blocks / Max block size), the Expires: never header, and the new Path / Archived columns. Passes.
• New QueryDetailPage.test.tsx cases: the Refresh button reloads both the query and its results (refresh everything); a no-expiration query shows Expires: never. Passes.
• Existing carve/query detail tests still pass (8). Full vitest: 126 passed; the only failures are the pre-existing `LoginPage`/`ProfilePage` ones (jsdom localStorage), unchanged by this PR.
Files
• frontend/src/features/carves/CarveDetailPage.tsx — summary band, expanded table, Expires header, refresh button.
• frontend/src/features/carves/CarveDetailPage.test.tsx — summary/columns/expiration + refresh tests.
• frontend/src/features/queries/QueryDetailPage.tsx — refresh-everything, always-visible expiration.
• frontend/src/features/queries/QueryDetailPage.test.tsx — refresh + expiration tests.
jmpsec/osctrlGitHub
07/05/2026, 11:16 AM