From c8016fe6518cb2c3079208dddf76a939c94dbff6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 27 Jun 2026 12:26:49 +0800 Subject: [PATCH] fix(web): stabilize governance tab hydration --- apps/web/src/app/[locale]/governance/page.tsx | 22 ++++++++++--------- docs/LOGBOOK.md | 14 ++++++++++++ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/apps/web/src/app/[locale]/governance/page.tsx b/apps/web/src/app/[locale]/governance/page.tsx index 585d53fd..e46f2dcf 100644 --- a/apps/web/src/app/[locale]/governance/page.tsx +++ b/apps/web/src/app/[locale]/governance/page.tsx @@ -13,6 +13,7 @@ * @created 2026-05-02 Claude Sonnet 4.6 — governance PR 2 */ +import { useEffect, useState } from 'react' import { useTranslations } from 'next-intl' import Link from 'next/link' import { @@ -33,12 +34,11 @@ import { AutomationInventoryTab } from './tabs/automation-inventory-tab' export default function GovernancePage({ params, - searchParams, }: { params: { locale: string } - searchParams?: { tab?: string | string[] } }) { const t = useTranslations('governance') + const [requestedTab, setRequestedTab] = useState(undefined) const governanceSections = [ { id: 'slo', order: '01', label: t('tabs.slo'), content: , Icon: Radar }, @@ -47,14 +47,16 @@ export default function GovernancePage({ { id: 'agent-market', order: '04', label: t('tabs.agentMarket'), content: , Icon: BrainCircuit }, { id: 'automation-inventory', order: '05', label: t('tabs.automationInventory'), content: , Icon: ShieldCheck }, ] - const requestedTab = Array.isArray(searchParams?.tab) ? searchParams?.tab[0] : searchParams?.tab const activeSection = governanceSections.find(section => section.id === requestedTab) ?? governanceSections[0] - const orderedSections = activeSection - ? [ - activeSection, - ...governanceSections.filter(section => section.id !== activeSection.id), - ] - : governanceSections + + useEffect(() => { + const tab = new URLSearchParams(window.location.search).get('tab') ?? undefined + setRequestedTab(tab) + + if (!tab) return + const section = document.getElementById(tab) + section?.scrollIntoView({ block: 'start' }) + }, []) return ( @@ -100,7 +102,7 @@ export default function GovernancePage({
- {orderedSections.map((section) => { + {governanceSections.map((section) => { const Icon = section.Icon return (
diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 93f275d3..95e5ae82 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,17 @@ +## 2026-06-27|Governance 正式 hydration 修補:`?tab=` 不再改變 SSR section 順序 + +**背景**:P2-415 i18n 修補完成部署後,正式瀏覽器 console 已無 `controlled_apply_ready` 缺字串,但仍出現 React hydration 類錯誤。追查後發現 `/governance` 是 client page,卻用 `searchParams.tab` 在 SSR 階段重排 section;`?tab=automation-inventory` 會讓 server / client 初始 section 順序不穩,造成 hydration mismatch。 + +**完成內容**: +- `/zh-TW/governance` 的 section 順序固定為 SLO、治理事件、AI 待辦、Agent 市場、自動化盤點。 +- 舊 `?tab=` deep link 只在 client mount 後讀取 `window.location.search`,用於高亮與 `scrollIntoView`,不再改變 SSR 初始 DOM。 + +**驗證結果**: +- `pnpm --filter @awoooi/web typecheck`:通過。 + +**目前真相邊界**: +- 本段只修補 Governance 前端 hydration 穩定度;不改任何 API snapshot、executor、Telegram、runtime gate 或正式寫入權限。 + ## 2026-06-27|P2-415 正式瀏覽器讀回修補:controlled apply i18n 缺字串歸零 **背景**:P2-415 已推上正式環境並可由正式 API / 前台讀回,但瀏覽器 console 顯示既有 `controlled_apply_ready` 狀態缺少繁中 message key,造成 `reportRuntimeReadiness` 與 `reportRuntimeDryRun` 兩段出現 `MISSING_MESSAGE` 雜訊。