fix(web): stabilize governance tab hydration
Some checks failed
Code Review / ai-code-review (push) Successful in 15s
CD Pipeline / tests (push) Successful in 1m39s
CD Pipeline / build-and-deploy (push) Successful in 8m54s
CD Pipeline / post-deploy-checks (push) Successful in 1m47s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
Some checks failed
Code Review / ai-code-review (push) Successful in 15s
CD Pipeline / tests (push) Successful in 1m39s
CD Pipeline / build-and-deploy (push) Successful in 8m54s
CD Pipeline / post-deploy-checks (push) Successful in 1m47s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
* @created 2026-05-02 Claude Sonnet 4.6 — governance PR 2
|
* @created 2026-05-02 Claude Sonnet 4.6 — governance PR 2
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslations } from 'next-intl'
|
import { useTranslations } from 'next-intl'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import {
|
import {
|
||||||
@@ -33,12 +34,11 @@ import { AutomationInventoryTab } from './tabs/automation-inventory-tab'
|
|||||||
|
|
||||||
export default function GovernancePage({
|
export default function GovernancePage({
|
||||||
params,
|
params,
|
||||||
searchParams,
|
|
||||||
}: {
|
}: {
|
||||||
params: { locale: string }
|
params: { locale: string }
|
||||||
searchParams?: { tab?: string | string[] }
|
|
||||||
}) {
|
}) {
|
||||||
const t = useTranslations('governance')
|
const t = useTranslations('governance')
|
||||||
|
const [requestedTab, setRequestedTab] = useState<string | undefined>(undefined)
|
||||||
|
|
||||||
const governanceSections = [
|
const governanceSections = [
|
||||||
{ id: 'slo', order: '01', label: t('tabs.slo'), content: <SloTab />, Icon: Radar },
|
{ id: 'slo', order: '01', label: t('tabs.slo'), content: <SloTab />, Icon: Radar },
|
||||||
@@ -47,14 +47,16 @@ export default function GovernancePage({
|
|||||||
{ id: 'agent-market', order: '04', label: t('tabs.agentMarket'), content: <AgentMarketTab />, Icon: BrainCircuit },
|
{ id: 'agent-market', order: '04', label: t('tabs.agentMarket'), content: <AgentMarketTab />, Icon: BrainCircuit },
|
||||||
{ id: 'automation-inventory', order: '05', label: t('tabs.automationInventory'), content: <AutomationInventoryTab />, Icon: ShieldCheck },
|
{ id: 'automation-inventory', order: '05', label: t('tabs.automationInventory'), content: <AutomationInventoryTab />, Icon: ShieldCheck },
|
||||||
]
|
]
|
||||||
const requestedTab = Array.isArray(searchParams?.tab) ? searchParams?.tab[0] : searchParams?.tab
|
|
||||||
const activeSection = governanceSections.find(section => section.id === requestedTab) ?? governanceSections[0]
|
const activeSection = governanceSections.find(section => section.id === requestedTab) ?? governanceSections[0]
|
||||||
const orderedSections = activeSection
|
|
||||||
? [
|
useEffect(() => {
|
||||||
activeSection,
|
const tab = new URLSearchParams(window.location.search).get('tab') ?? undefined
|
||||||
...governanceSections.filter(section => section.id !== activeSection.id),
|
setRequestedTab(tab)
|
||||||
]
|
|
||||||
: governanceSections
|
if (!tab) return
|
||||||
|
const section = document.getElementById(tab)
|
||||||
|
section?.scrollIntoView({ block: 'start' })
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout locale={params.locale}>
|
<AppLayout locale={params.locale}>
|
||||||
@@ -100,7 +102,7 @@ export default function GovernancePage({
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div className="grid min-w-0 gap-4">
|
<div className="grid min-w-0 gap-4">
|
||||||
{orderedSections.map((section) => {
|
{governanceSections.map((section) => {
|
||||||
const Icon = section.Icon
|
const Icon = section.Icon
|
||||||
return (
|
return (
|
||||||
<section key={section.id} id={section.id} className="min-w-0 scroll-mt-24">
|
<section key={section.id} id={section.id} className="min-w-0 scroll-mt-24">
|
||||||
|
|||||||
@@ -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 缺字串歸零
|
## 2026-06-27|P2-415 正式瀏覽器讀回修補:controlled apply i18n 缺字串歸零
|
||||||
|
|
||||||
**背景**:P2-415 已推上正式環境並可由正式 API / 前台讀回,但瀏覽器 console 顯示既有 `controlled_apply_ready` 狀態缺少繁中 message key,造成 `reportRuntimeReadiness` 與 `reportRuntimeDryRun` 兩段出現 `MISSING_MESSAGE` 雜訊。
|
**背景**:P2-415 已推上正式環境並可由正式 API / 前台讀回,但瀏覽器 console 顯示既有 `controlled_apply_ready` 狀態缺少繁中 message key,造成 `reportRuntimeReadiness` 與 `reportRuntimeDryRun` 兩段出現 `MISSING_MESSAGE` 雜訊。
|
||||||
|
|||||||
Reference in New Issue
Block a user