diff --git a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx
index 4a5d07dc..9de0c14f 100644
--- a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx
+++ b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx
@@ -212,7 +212,7 @@ export function AutomationInventoryTab() {
const fetchSnapshot = () => {
setLoading(true)
- Promise.all([
+ const requests = [
apiClient.getAiAgentAutomationInventorySnapshot(),
apiClient.getAiAgentAutomationBacklogSnapshot(),
apiClient.getBackupDrTargetInventory(),
@@ -220,16 +220,35 @@ export function AutomationInventoryTab() {
apiClient.getBackupNotificationPolicy(),
apiClient.getOffsiteEscrowReadinessStatus(),
apiClient.getRuntimeSurfaceInventory(),
- ])
- .then(([inventoryData, backlogData, targetData, readinessData, policyData, offsiteEscrowData, runtimeSurfaceData]) => {
- setSnapshot(inventoryData)
- setBacklog(backlogData)
- setBackupTargets(targetData)
- setBackupReadiness(readinessData)
- setBackupPolicy(policyData)
- setOffsiteEscrow(offsiteEscrowData)
- setRuntimeSurface(runtimeSurfaceData)
- setError(false)
+ ] as const
+
+ Promise.allSettled(requests)
+ .then((results) => {
+ const [
+ inventoryResult,
+ backlogResult,
+ targetResult,
+ readinessResult,
+ policyResult,
+ offsiteEscrowResult,
+ runtimeSurfaceResult,
+ ] = results
+
+ setSnapshot(inventoryResult.status === 'fulfilled' ? inventoryResult.value : null)
+ setBacklog(backlogResult.status === 'fulfilled' ? backlogResult.value : null)
+ setBackupTargets(targetResult.status === 'fulfilled' ? targetResult.value : null)
+ setBackupReadiness(readinessResult.status === 'fulfilled' ? readinessResult.value : null)
+ setBackupPolicy(policyResult.status === 'fulfilled' ? policyResult.value : null)
+ setOffsiteEscrow(offsiteEscrowResult.status === 'fulfilled' ? offsiteEscrowResult.value : null)
+ setRuntimeSurface(runtimeSurfaceResult.status === 'fulfilled' ? runtimeSurfaceResult.value : null)
+ setError([
+ inventoryResult,
+ backlogResult,
+ targetResult,
+ readinessResult,
+ policyResult,
+ offsiteEscrowResult,
+ ].some(result => result.status === 'rejected'))
})
.catch(() => setError(true))
.finally(() => setLoading(false))
@@ -327,7 +346,7 @@ export function AutomationInventoryTab() {
)
}
- if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !runtimeSurface) {
+ if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow) {
return (
@@ -374,10 +393,10 @@ export function AutomationInventoryTab() {
const actionRequiredOffsiteCards = offsiteEscrow.rollups.by_readiness.action_required ?? 0
const blockedEscrowCards = offsiteEscrow.rollups.by_readiness.blocked ?? 0
const executionBlockedCards = offsiteEscrow.rollups.execution_blocked_card_ids.length
- const runtimeActionRequired = runtimeSurface.rollups.action_required_surface_ids.length
- const runtimeSecrets = runtimeSurface.rollups.secret_surface_ids.length
- const runtimeLiveMissing = runtimeSurface.rollups.live_check_missing_surface_ids.length
- const runtimeBoundComponents = runtimeSurface.rollups.source_components_with_runtime_binding
+ const runtimeActionRequired = runtimeSurface?.rollups.action_required_surface_ids.length ?? 0
+ const runtimeSecrets = runtimeSurface?.rollups.secret_surface_ids.length ?? 0
+ const runtimeLiveMissing = runtimeSurface?.rollups.live_check_missing_surface_ids.length ?? 0
+ const runtimeBoundComponents = runtimeSurface?.rollups.source_components_with_runtime_binding ?? 0
const backlogProgressPercent = backlog.progress_summary.overall_percent
const explicitApprovalItemCount = backlog.item_approval_boundary_rollup.items_requiring_explicit_approval.length
const taskBoundaryCount = snapshot.task_approval_boundary_rollup.total_tasks
@@ -873,6 +892,7 @@ export function AutomationInventoryTab() {
+ {runtimeSurface && (
@@ -976,6 +996,7 @@ export function AutomationInventoryTab() {
+ )}
diff --git a/apps/web/src/components/layout/page-tabs.tsx b/apps/web/src/components/layout/page-tabs.tsx
index 36a9449e..611b16cc 100644
--- a/apps/web/src/components/layout/page-tabs.tsx
+++ b/apps/web/src/components/layout/page-tabs.tsx
@@ -101,12 +101,15 @@ export function PageTabs({ tabs, defaultTab, syncWithUrl = true }: PageTabsProps
const [activeTab, setActiveTab] = useState(initialTab)
const tabIds = tabs.map(t => t.id).join('|')
const firstTabId = tabs[0]?.id || ''
+ const knownTabIds = useMemo(() => tabIds.split('|'), [tabIds])
+ const urlActiveTab = syncWithUrl && urlTab && knownTabIds.includes(urlTab) ? urlTab : null
+ const resolvedActiveTab = urlActiveTab || activeTab || defaultTab || firstTabId
useEffect(() => {
const nextTab = (syncWithUrl ? urlTab : null) || defaultTab || firstTabId
- if (!nextTab || !tabIds.split('|').includes(nextTab)) return
+ if (!nextTab || !knownTabIds.includes(nextTab)) return
setActiveTab(prev => prev === nextTab ? prev : nextTab)
- }, [defaultTab, firstTabId, syncWithUrl, tabIds, urlTab])
+ }, [defaultTab, firstTabId, knownTabIds, syncWithUrl, urlTab])
// 切換 Tab
const switchTab = useCallback((tabId: string) => {
@@ -126,8 +129,8 @@ export function PageTabs({ tabs, defaultTab, syncWithUrl = true }: PageTabsProps
// 找到目前的 Tab 內容
const activeContent = useMemo(() => {
- return tabs.find(t => t.id === activeTab)?.content ?? tabs[0]?.content
- }, [tabs, activeTab])
+ return tabs.find(t => t.id === resolvedActiveTab)?.content ?? tabs[0]?.content
+ }, [tabs, resolvedActiveTab])
return (
<>
@@ -147,17 +150,17 @@ export function PageTabs({ tabs, defaultTab, syncWithUrl = true }: PageTabsProps
}}
>
{tabs.map(tab => {
- const isActive = tab.id === activeTab
+ const isActive = tab.id === resolvedActiveTab
return (