From e71223507cc352b6476077531ab0a2a735d205bf Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Sun, 6 Sep 2026 02:28:08 +0200 Subject: [PATCH] web: customs archive reads the generated types, annotations page follows nextLink through the typed builder, dead PaginatedResponse removed (audit package 5, stage 4a) The five hand-written customs-archive response types in lib/api.ts duplicated CustomsArchiveRunListResponse, CustomsArchiveEntryListResponse, CustomsArchiveRunSummary, CustomsArchiveEntrySummary and TenantOffboardingCustomsArchiveEntry from the manifest; the two pages import those. The annotations page built its next-page URL from API_BASE_URL + nextLink instead of the scoped builder plus extractContinuation. PaginatedResponse had no importer. types/diagnostics.ts states why it stays hand-written (no wire DTO for GET diagnostics/paths). Co-Authored-By: Claude Fable 5.1 --- .../app/admin/customs-archive/detail/page.tsx | 17 +++----- .../app/admin/customs-archive/page.tsx | 10 ++--- .../app/annotations/page.tsx | 10 ++++- src/Web/autopilot-monitor-web/lib/api.ts | 42 ------------------- .../lib/paginationLink.ts | 10 ----- .../types/diagnostics.ts | 2 + 6 files changed, 19 insertions(+), 72 deletions(-) diff --git a/src/Web/autopilot-monitor-web/app/admin/customs-archive/detail/page.tsx b/src/Web/autopilot-monitor-web/app/admin/customs-archive/detail/page.tsx index 4b282810..a864d475 100644 --- a/src/Web/autopilot-monitor-web/app/admin/customs-archive/detail/page.tsx +++ b/src/Web/autopilot-monitor-web/app/admin/customs-archive/detail/page.tsx @@ -3,17 +3,12 @@ import { Suspense, useCallback, useEffect, useState } from "react"; import Link from "next/link"; import { useSearchParams } from "next/navigation"; -import { - api, - type CustomsArchiveEntrySummary, - type CustomsArchiveFullEntry, - type CustomsArchiveListEntriesResponse, -} from "@/lib/api"; +import { api } from "@/lib/api"; import { apiErrorText, fetchJson, fetchOk } from "@/lib/apiClient"; import { useAdminConfig } from "../../AdminConfigContext"; import { AdminNotifications } from "../../AdminNotifications"; import { DeleteConfirmModal } from "../components/DeleteConfirmModal"; -import type { CustomsArchiveEntryResponse } from "@/utils/wire-types.generated"; +import type { CustomsArchiveEntryListResponse, CustomsArchiveEntryResponse, CustomsArchiveEntrySummary, TenantOffboardingCustomsArchiveEntry } from "@/utils/wire-types.generated"; export default function CustomsArchiveDetailPage() { // useSearchParams() in CustomsArchiveDetailContent requires a Suspense boundary @@ -33,7 +28,7 @@ function CustomsArchiveDetailContent() { const { getAccessToken, setError, setSuccessMessage } = useAdminConfig(); const [entries, setEntries] = useState([]); const [loading, setLoading] = useState(false); - const [expanded, setExpanded] = useState>({}); + const [expanded, setExpanded] = useState>({}); const [deletingRk, setDeletingRk] = useState(null); const [pendingDelete, setPendingDelete] = useState(null); @@ -41,7 +36,7 @@ function CustomsArchiveDetailContent() { try { setLoading(true); const url = api.customsArchive.listEntries(tenantId, historyRowKey); - const body = await fetchJson(url, getAccessToken); + const body = await fetchJson(url, getAccessToken); setEntries(body.entries ?? []); } catch (err) { setError(apiErrorText(err)); @@ -75,7 +70,7 @@ function CustomsArchiveDetailContent() { api.customsArchive.getEntry(tenantId, historyRowKey, entry.rowKey), getAccessToken, ); - setExpanded((prev) => ({ ...prev, [key]: body.entry as CustomsArchiveFullEntry })); + setExpanded((prev) => ({ ...prev, [key]: body.entry })); } catch (err) { setExpanded((prev) => { const next = { ...prev }; @@ -153,7 +148,7 @@ function CustomsArchiveDetailContent() { const detail = expanded[entry.rowKey]; const isOpen = !!detail; const isLoadingFull = detail === "loading"; - const fullEntry = typeof detail === "object" ? (detail as CustomsArchiveFullEntry) : null; + const fullEntry = typeof detail === "object" ? (detail as TenantOffboardingCustomsArchiveEntry) : null; return (
(url, getAccessToken); + const body = await fetchJson(url, getAccessToken); setRuns(body.runs ?? []); } catch (err) { setError(apiErrorText(err)); diff --git a/src/Web/autopilot-monitor-web/app/annotations/page.tsx b/src/Web/autopilot-monitor-web/app/annotations/page.tsx index 26787cde..ed9cd73f 100644 --- a/src/Web/autopilot-monitor-web/app/annotations/page.tsx +++ b/src/Web/autopilot-monitor-web/app/annotations/page.tsx @@ -4,7 +4,6 @@ import { useCallback, useEffect, useState } from "react"; import Link from "next/link"; import { sessionUrl } from "@/lib/routes"; import { scopedApi } from "@/lib/scopedApi"; -import { API_BASE_URL } from "@/utils/config"; import { ProtectedRoute } from "@/components/ProtectedRoute"; import { useAuth } from "@/contexts/AuthContext"; import { useAdminMode, useAggregatedAdminScope } from "@/hooks"; @@ -23,6 +22,7 @@ import { import { DocsLink } from "@/components/DocsLink"; import { DOCS_PATHS } from "@/lib/docsPaths"; import { fetchJson, nullOn404 } from "@/lib/apiClient"; +import { extractContinuation } from "@/lib/paginationLink"; /** * Annotations overview: every annotated session in one list, so a judged session can be @@ -294,7 +294,13 @@ export default function AnnotationsPage() { onClick={() => { setLoading(true); setLoadError(null); - fetchPage(`${API_BASE_URL}${nextLink}`, true); + fetchPage( + scopedApi.annotationsList( + { routeGlobal, selectedTenantId, effectiveTenantId }, + { verdict: verdictFilter || undefined, lane: laneFilter || undefined, q: submittedQuery || undefined, continuation: extractContinuation(nextLink) ?? undefined }, + ), + true, + ); }} className="px-4 py-1.5 text-sm font-medium bg-white border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50" > diff --git a/src/Web/autopilot-monitor-web/lib/api.ts b/src/Web/autopilot-monitor-web/lib/api.ts index e55f2601..cbe549f0 100644 --- a/src/Web/autopilot-monitor-web/lib/api.ts +++ b/src/Web/autopilot-monitor-web/lib/api.ts @@ -784,48 +784,6 @@ export const api = { }, }; -// ── Customs Archive response types ──────────────────────────────────────── -// Shared by /admin/customs-archive (list runs) and -// /admin/customs-archive/[tenantId]/[historyRowKey] (list/inspect entries). -export interface CustomsArchiveRunSummary { - partitionKey: string; - tenantId: string; - historyRowKey: string; - archivedAt: string; - gatherRulesCount: number; - analyzeRulesCount: number; - imeLogPatternsCount: number; -} - -export interface CustomsArchiveListRunsResponse { - success: boolean; - count: number; - runs: CustomsArchiveRunSummary[]; -} - -export interface CustomsArchiveEntrySummary { - partitionKey: string; - rowKey: string; - originalTable: string; - originalRowKey: string; - archivedAt: string; - entityJsonPreview: string; -} - -export interface CustomsArchiveListEntriesResponse { - success: boolean; - count: number; - entries: CustomsArchiveEntrySummary[]; -} - -export interface CustomsArchiveFullEntry extends CustomsArchiveEntrySummary { - tenantId: string; - originalPartitionKey: string; - entityJson: string; - historyRowKey: string; - archivedBy: string; -} - // ── Backups response types (PR1 + PR2) ──────────────────────────────────── export interface BackupListResponse { diff --git a/src/Web/autopilot-monitor-web/lib/paginationLink.ts b/src/Web/autopilot-monitor-web/lib/paginationLink.ts index a4a1fec1..20db5f5f 100644 --- a/src/Web/autopilot-monitor-web/lib/paginationLink.ts +++ b/src/Web/autopilot-monitor-web/lib/paginationLink.ts @@ -8,16 +8,6 @@ * url builder. */ -export interface PaginatedResponse { - count: number; - nextLink?: string | null; - /** legacy collection key — varies per endpoint (events / sessions / logs / reports) */ - events?: T[]; - sessions?: T[]; - logs?: T[]; - reports?: T[]; -} - /** * Extracts the `continuation` query param from a backend-supplied nextLink. * Returns null when the input is empty or carries no continuation. diff --git a/src/Web/autopilot-monitor-web/types/diagnostics.ts b/src/Web/autopilot-monitor-web/types/diagnostics.ts index 051db502..38c11ba6 100644 --- a/src/Web/autopilot-monitor-web/types/diagnostics.ts +++ b/src/Web/autopilot-monitor-web/types/diagnostics.ts @@ -1,3 +1,5 @@ +// GET /api/diagnostics/paths has no wire DTO in the shared manifest (the payload is assembled +// in GetDiagnosticsPathsFunction); this module is the client-owned shape of that response. export interface DiagnosticsLogPath { path: string; description: string;