From 1afd7e9e9fec61d379875d814943eb0f5179289c Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 1 Jun 2026 02:02:59 +0800 Subject: [PATCH] feat(web): add IwoooS visual mesh --- apps/web/messages/en.json | 49 ++++ apps/web/messages/zh-TW.json | 49 ++++ apps/web/src/app/[locale]/iwooos/page.tsx | 254 ++++++++++++++++++ docs/LOGBOOK.md | 29 ++ .../iwooos-posture-projection.snapshot.json | 73 +++++ ...ecurity-mirror-status-rollup.snapshot.json | 12 + .../security-mirror-progress-guard.py | 193 +++++++++++++ 7 files changed, 659 insertions(+) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index aa43791b8..1f45b3e27 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -5395,6 +5395,55 @@ } } }, + "immediateVisualMesh": { + "eyebrow": "首屏資安網視覺模型", + "title": "先看資安網怎麼接起來", + "subtitle": "把產品、主機、版本來源、監控工具、AwoooP 真相鏈與 Gate 0 放在同一張圖;使用者先看關係,再往下查證據。", + "center": { + "label": "中心節點", + "title": "IwoooS 資安網", + "body": "目前只做可視化與證據定位,沒有開啟掃描、修復、主機更新或版本來源變更。" + }, + "stats": { + "assetScope": { + "label": "資產" + }, + "hostScope": { + "label": "主機" + }, + "runtimeGate": { + "label": "執行閘" + } + }, + "nodes": { + "products": { + "title": "產品與網站", + "body": "AwoooI、AwoooP、IwoooS、公開網站與 VibeWork 先進入同一張只讀視圖。" + }, + "hosts": { + "title": "Kali 與開發主機", + "body": "112、111、168 只呈現觀測與證據窗口,不直接連線執行。" + }, + "sourceControl": { + "title": "GitHub / Gitea", + "body": "版本主來源仍等 S4.9 負責人回覆與脫敏證據。" + }, + "monitoring": { + "title": "監控與工具鏈", + "body": "MCP、Ansible、KM、告警與監控資料先做證據接線。" + }, + "awooop": { + "title": "AwoooP 真相鏈", + "body": "跨 Session 狀態、work item 與部署證據維持同步。" + }, + "runtimeGate": { + "title": "Gate 0", + "body": "掃描、修復、部署、主機變更與 source-control mutation 全部仍鎖住。" + } + }, + "boundaryTitle": "視覺模型邊界", + "boundaryIntro": "這張圖是首屏理解模型,不是操作面板;所有高風險動作仍需人工批准與後續 runtime gate。" + }, "visualCommandDashboard": { "eyebrow": "視覺化資安指揮板", "title": "先看圖,再展開證據", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index aa43791b8..1f45b3e27 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -5395,6 +5395,55 @@ } } }, + "immediateVisualMesh": { + "eyebrow": "首屏資安網視覺模型", + "title": "先看資安網怎麼接起來", + "subtitle": "把產品、主機、版本來源、監控工具、AwoooP 真相鏈與 Gate 0 放在同一張圖;使用者先看關係,再往下查證據。", + "center": { + "label": "中心節點", + "title": "IwoooS 資安網", + "body": "目前只做可視化與證據定位,沒有開啟掃描、修復、主機更新或版本來源變更。" + }, + "stats": { + "assetScope": { + "label": "資產" + }, + "hostScope": { + "label": "主機" + }, + "runtimeGate": { + "label": "執行閘" + } + }, + "nodes": { + "products": { + "title": "產品與網站", + "body": "AwoooI、AwoooP、IwoooS、公開網站與 VibeWork 先進入同一張只讀視圖。" + }, + "hosts": { + "title": "Kali 與開發主機", + "body": "112、111、168 只呈現觀測與證據窗口,不直接連線執行。" + }, + "sourceControl": { + "title": "GitHub / Gitea", + "body": "版本主來源仍等 S4.9 負責人回覆與脫敏證據。" + }, + "monitoring": { + "title": "監控與工具鏈", + "body": "MCP、Ansible、KM、告警與監控資料先做證據接線。" + }, + "awooop": { + "title": "AwoooP 真相鏈", + "body": "跨 Session 狀態、work item 與部署證據維持同步。" + }, + "runtimeGate": { + "title": "Gate 0", + "body": "掃描、修復、部署、主機變更與 source-control mutation 全部仍鎖住。" + } + }, + "boundaryTitle": "視覺模型邊界", + "boundaryIntro": "這張圖是首屏理解模型,不是操作面板;所有高風險動作仍需人工批准與後續 runtime gate。" + }, "visualCommandDashboard": { "eyebrow": "視覺化資安指揮板", "title": "先看圖,再展開證據", diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index 927fe80d9..03a78d00f 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -181,6 +181,13 @@ type IwoooSFocusDeckItem = { tone: 'steady' | 'warn' | 'locked' } +type IwoooSImmediateVisualMeshNode = { + key: string + metric: string + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + type AwoooPReadOnlyLandingReadinessItem = { key: string item: string @@ -2314,6 +2321,38 @@ const iwooosFocusDeckBoundaries = [ 'not_authorization=true', ] as const +const iwooosImmediateVisualMeshNodes: IwoooSImmediateVisualMeshNode[] = [ + { key: 'products', metric: '8', icon: ShieldCheck, tone: 'steady' }, + { key: 'hosts', metric: '112 / 111 / 168', icon: Activity, tone: 'warn' }, + { key: 'sourceControl', metric: 'S4.9', icon: GitBranch, tone: 'warn' }, + { key: 'monitoring', metric: '6', icon: Radar, tone: 'steady' }, + { key: 'awooop', metric: '已接線', icon: Bell, tone: 'steady' }, + { key: 'runtimeGate', metric: 'Gate 0', icon: Lock, tone: 'locked' }, +] + +const iwooosImmediateVisualMeshStats = [ + { key: 'assetScope', value: '8', icon: Radar, tone: 'steady' }, + { key: 'hostScope', value: '3', icon: Activity, tone: 'warn' }, + { key: 'runtimeGate', value: '0', icon: Lock, tone: 'locked' }, +] as const + +const iwooosImmediateVisualMeshBoundaries = [ + 'iwooos_immediate_visual_mesh_first_layer=true', + 'iwooos_immediate_visual_mesh_node_count=7', + 'iwooos_immediate_visual_mesh_link_count=6', + 'iwooos_immediate_visual_mesh_above_command_map=true', + 'iwooos_immediate_visual_mesh_anchor_navigation_allowed=false', + 'iwooos_immediate_visual_mesh_execution_action_buttons_allowed=false', + 'iwooos_immediate_visual_mesh_runtime_gate_count=0', + 'iwooos_immediate_visual_mesh_scan_authorized=false', + 'iwooos_immediate_visual_mesh_host_change_authorized=false', + 'iwooos_immediate_visual_mesh_source_control_mutation_authorized=false', + 'runtime_execution_authorized=false', + 'active_runtime_gate_count=0', + 'action_buttons_allowed=false', + 'not_authorization=true', +] as const + const iwooosFirstUnlockEvidencePacketSlots = [ { key: 'ownerDecisionMetadata', slot: '01', state: '待補', icon: ClipboardCheck, tone: 'warn' }, { key: 'scopeEvidenceRefs', slot: '02', state: '待補', icon: GitBranch, tone: 'warn' }, @@ -9238,6 +9277,220 @@ function IwoooSFocusDeckBoard() { ) } +function IwoooSImmediateVisualMeshBoard() { + const t = useTranslations('iwooos.immediateVisualMesh') + const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } + const meshBodyRef = useRef(null) + const [isCompactMesh, setIsCompactMesh] = useState(false) + + useEffect(() => { + const node = meshBodyRef.current + if (!node) return + + const updateLayout = () => setIsCompactMesh(node.getBoundingClientRect().width < 760) + updateLayout() + + const observer = new ResizeObserver(updateLayout) + observer.observe(node) + return () => observer.disconnect() + }, []) + + return ( +
+
+
+
+
+ + {t('eyebrow')} +
+

{t('title')}

+

+ {t('subtitle')} +

+
+ +
+ {iwooosImmediateVisualMeshStats.map(item => { + const Icon = item.icon + return ( +
+
+ {t(`stats.${item.key}.label` as never)} + +
+
+ {item.value} +
+
+ ) + })} +
+
+ +
+
+
+
+ +
+
+
+
{t('center.label')}
+

+ {t('center.title')} +

+

+ {t('center.body')} +

+
+
+ +
+ {iwooosImmediateVisualMeshNodes.map(item => { + const Icon = item.icon + return ( +
+
+ + {item.metric} +
+
+

+ {t(`nodes.${item.key}.title` as never)} +

+

+ {t(`nodes.${item.key}.body` as never)} +

+
+
+ ) + })} +
+
+ +
+ + {t('boundaryTitle')} + +

+ {t('boundaryIntro')} +

+
+ {iwooosImmediateVisualMeshBoundaries.map(item => ( + + {item} + + ))} +
+
+
+
+ ) +} + function IwoooSCommandMapBoard() { const t = useTranslations('iwooos.commandMap') const [activeKey, setActiveKey] = useState('unlock') @@ -14139,6 +14392,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) { + diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 772dd24e5..19bfd95e3 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,32 @@ +## 2026-06-01|IwoooS 首屏資安網視覺模型 + +**背景**: + +- 使用者指出 IwoooS 頁面仍偏長文字,專業資安產品應該先用圖、表與互動式結構讓使用者理解整體資安網。 +- 本輪不提高資安限制、不開啟 Kali / SSH / 掃描 / 修復 / 主機更新 / source-control mutation;只把既有框架轉成更直覺的第一屏視覺模型。 + +**本次調整**: + +- `apps/web/src/app/[locale]/iwooos/page.tsx`: + - 新增 `IwoooSImmediateVisualMeshBoard`,放在 `IwoooSFocusDeckBoard` 後、`IwoooSCommandMapBoard` 前。 + - 第一屏直接呈現產品與網站、Kali 112 / 開發主機 111 / 168、GitHub / Gitea、監控工具、AwoooP 真相鏈與 Gate 0 的關係。 + - 以視覺網格、核心節點、狀態卡與可收合邊界取代單純文字堆疊;手機與窄版畫面自動切換單欄,避免文字被擠壓。 + - 保持只讀,不新增任何執行按鈕、掃描、修復、部署、SSH 或版本來源變更。 +- `apps/web/messages/zh-TW.json` / `apps/web/messages/en.json`: + - 新增 `iwooos.immediateVisualMesh` 繁中文案;`en.json` 維持繁中鏡像,確保所有網站內容仍以繁體中文呈現。 +- `docs/security/iwooos-posture-projection.snapshot.json` / `security-mirror-status-rollup.snapshot.json`: + - 補上 `immediate_visual_mesh_first_layer=true`、`node_count=7`、`link_count=6`、`above_command_map=true`、`runtime_gate_count=0`。 + - 新增 `S2.146` 進度 ledger;headline 不增加,因為這是 UX / framework 可理解度,不是 runtime 授權。 +- `scripts/security/security-mirror-progress-guard.py`: + - 新增 guard,鎖住首屏視覺模型的位置、頁面 test id、snapshot 節點、繁中文案與 runtime false 邊界。 + +**進度邊界**: + +- 整體維持 `61%`。 +- 框架 / 治理 / 文件 / schema / read-only evidence 約 `86-88%`。 +- runtime ingestion / GitHub primary / AwoooP production landing 約 `40-45%`。 +- 本輪沒有連入 Kali 主機執行更新,沒有掃描、修復、部署按鈕、主機變更、GitHub primary 切換或 Gitea 停用。 + ## 2026-06-01|IwoooS 首層焦點導覽 **背景**: diff --git a/docs/security/iwooos-posture-projection.snapshot.json b/docs/security/iwooos-posture-projection.snapshot.json index 8fbd155c8..4a53f388c 100644 --- a/docs/security/iwooos-posture-projection.snapshot.json +++ b/docs/security/iwooos-posture-projection.snapshot.json @@ -66,6 +66,11 @@ "focus_deck_execution_action_buttons_allowed": false, "focus_deck_runtime_gate_count": 0, "focus_deck_above_command_map": true, + "immediate_visual_mesh_first_layer": true, + "immediate_visual_mesh_node_count": 7, + "immediate_visual_mesh_link_count": 6, + "immediate_visual_mesh_above_command_map": true, + "immediate_visual_mesh_runtime_gate_count": 0, "long_form_sections_default_collapsed": true, "owner_response_validation_received_count": 0, "owner_response_validation_accepted_count": 0, @@ -364,6 +369,74 @@ "not_authorization": true } ], + "immediate_visual_mesh_nodes": [ + { + "node_id": "core", + "display_order": 1, + "display_mode": "first_screen_visual_mesh", + "read_only": true, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "not_authorization": true + }, + { + "node_id": "products", + "display_order": 2, + "display_mode": "first_screen_visual_mesh", + "read_only": true, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "not_authorization": true + }, + { + "node_id": "hosts", + "display_order": 3, + "display_mode": "first_screen_visual_mesh", + "read_only": true, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "host_change_authorized": false, + "not_authorization": true + }, + { + "node_id": "sourceControl", + "display_order": 4, + "display_mode": "first_screen_visual_mesh", + "read_only": true, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "source_control_mutation_authorized": false, + "not_authorization": true + }, + { + "node_id": "monitoring", + "display_order": 5, + "display_mode": "first_screen_visual_mesh", + "read_only": true, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "not_authorization": true + }, + { + "node_id": "awooop", + "display_order": 6, + "display_mode": "first_screen_visual_mesh", + "read_only": true, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "not_authorization": true + }, + { + "node_id": "runtimeGate", + "display_order": 7, + "display_mode": "first_screen_visual_mesh", + "read_only": true, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "scan_authorized": false, + "not_authorization": true + } + ], "posture_pillars": [ { "pillar_id": "exposure_posture", diff --git a/docs/security/security-mirror-status-rollup.snapshot.json b/docs/security/security-mirror-status-rollup.snapshot.json index ef089b478..015df12f5 100644 --- a/docs/security/security-mirror-status-rollup.snapshot.json +++ b/docs/security/security-mirror-status-rollup.snapshot.json @@ -2221,6 +2221,18 @@ "runtime_delta": false, "execution_authorized": false, "not_authorization": true + }, + { + "delta_id": "s2_146_iwooos_immediate_visual_mesh", + "display_order": 175, + "completed_stage": "S2.146 IwoooS 首屏資安網視覺模型", + "progress_axis": "framework_detail", + "headline_percent_delta": 0, + "framework_delta_visible": true, + "why_headline_unchanged": "IwoooS 只新增首屏資安網視覺模型,把產品、主機、版本來源、監控工具、AwoooP 真相鏈與 Gate 0 放在焦點導覽後、工作地圖前;iwooos_immediate_visual_mesh_node_count=7、iwooos_immediate_visual_mesh_link_count=6、iwooos_immediate_visual_mesh_above_command_map=true、iwooos_immediate_visual_mesh_execution_action_buttons_allowed=false、iwooos_immediate_visual_mesh_runtime_gate_count=0、runtime_execution_authorized=false、active_runtime_gate_count=0,不把視覺模型當掃描、修復、部署、主機更新、source-control mutation、GitHub primary 切換、Gitea 停用或 runtime gate。", + "runtime_delta": false, + "execution_authorized": false, + "not_authorization": true } ], "next_safe_actions": [ diff --git a/scripts/security/security-mirror-progress-guard.py b/scripts/security/security-mirror-progress-guard.py index a22570805..2897b7648 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -610,6 +610,7 @@ def validate(root: Path) -> None: "s2_143_iwooos_command_map_first_layer", "s2_144_iwooos_command_palette_security_entry_unified", "s2_145_iwooos_first_layer_focus_deck", + "s2_146_iwooos_immediate_visual_mesh", ] assert_equal( "progress_delta_ledger.delta_ids", @@ -1540,6 +1541,29 @@ def validate(root: Path) -> None: "iwooos_projection.summary.focus_deck_above_command_map", iwooos_projection["summary"]["focus_deck_above_command_map"], ) + assert_true( + "iwooos_projection.summary.immediate_visual_mesh_first_layer", + iwooos_projection["summary"]["immediate_visual_mesh_first_layer"], + ) + assert_equal( + "iwooos_projection.summary.immediate_visual_mesh_node_count", + iwooos_projection["summary"]["immediate_visual_mesh_node_count"], + 7, + ) + assert_equal( + "iwooos_projection.summary.immediate_visual_mesh_link_count", + iwooos_projection["summary"]["immediate_visual_mesh_link_count"], + 6, + ) + assert_true( + "iwooos_projection.summary.immediate_visual_mesh_above_command_map", + iwooos_projection["summary"]["immediate_visual_mesh_above_command_map"], + ) + assert_equal( + "iwooos_projection.summary.immediate_visual_mesh_runtime_gate_count", + iwooos_projection["summary"]["immediate_visual_mesh_runtime_gate_count"], + 0, + ) assert_true( "iwooos_projection.summary.all_product_coverage_snapshot_detail_ledger_collapsed", iwooos_projection["summary"]["all_product_coverage_snapshot_detail_ledger_collapsed"], @@ -11857,6 +11881,54 @@ def validate(root: Path) -> None: iwooos_projection_page, text, ) + for text in [ + 'data-testid="iwooos-immediate-visual-mesh-board"', + 'data-testid="iwooos-immediate-visual-mesh-canvas"', + 'data-testid="iwooos-immediate-visual-mesh-boundaries"', + "IwoooSImmediateVisualMeshBoard", + "iwooosImmediateVisualMeshNodes", + "iwooosImmediateVisualMeshStats", + "iwooosImmediateVisualMeshBoundaries", + "conic-gradient", + ]: + assert_text_contains( + "iwooos_page.immediate_visual_mesh", + iwooos_projection_page, + text, + ) + assert_text_before( + "iwooos_page.focus_deck_before_immediate_visual_mesh", + iwooos_projection_page, + "", + "", + ) + assert_text_before( + "iwooos_page.immediate_visual_mesh_before_command_map", + iwooos_projection_page, + "", + "", + ) + for text in [ + "iwooos_immediate_visual_mesh_first_layer=true", + "iwooos_immediate_visual_mesh_node_count=7", + "iwooos_immediate_visual_mesh_link_count=6", + "iwooos_immediate_visual_mesh_above_command_map=true", + "iwooos_immediate_visual_mesh_anchor_navigation_allowed=false", + "iwooos_immediate_visual_mesh_execution_action_buttons_allowed=false", + "iwooos_immediate_visual_mesh_runtime_gate_count=0", + "iwooos_immediate_visual_mesh_scan_authorized=false", + "iwooos_immediate_visual_mesh_host_change_authorized=false", + "iwooos_immediate_visual_mesh_source_control_mutation_authorized=false", + "runtime_execution_authorized=false", + "active_runtime_gate_count=0", + "action_buttons_allowed=false", + "not_authorization=true", + ]: + assert_text_contains( + "iwooos_page.immediate_visual_mesh_boundary", + iwooos_projection_page, + text, + ) assert_text_contains( "iwooos_page.command_map_testid", iwooos_projection_page, @@ -12021,6 +12093,63 @@ def validate(root: Path) -> None: "iwooos_projection.focus_deck_items.sourceControl.github_primary_switch_authorized", source_control_focus["github_primary_switch_authorized"], ) + expected_immediate_visual_mesh_node_ids = [ + "core", + "products", + "hosts", + "sourceControl", + "monitoring", + "awooop", + "runtimeGate", + ] + immediate_visual_mesh_nodes = iwooos_projection["immediate_visual_mesh_nodes"] + assert_equal( + "iwooos_projection.immediate_visual_mesh_nodes.ids", + [item["node_id"] for item in immediate_visual_mesh_nodes], + expected_immediate_visual_mesh_node_ids, + ) + assert_equal( + "iwooos_projection.immediate_visual_mesh_nodes.display_order", + [item["display_order"] for item in immediate_visual_mesh_nodes], + list(range(1, len(expected_immediate_visual_mesh_node_ids) + 1)), + ) + for item in immediate_visual_mesh_nodes: + assert_equal( + f"iwooos_projection.immediate_visual_mesh_nodes.{item['node_id']}.display_mode", + item["display_mode"], + "first_screen_visual_mesh", + ) + assert_true( + f"iwooos_projection.immediate_visual_mesh_nodes.{item['node_id']}.read_only", + item["read_only"], + ) + assert_false( + f"iwooos_projection.immediate_visual_mesh_nodes.{item['node_id']}.runtime_gate_opened", + item["runtime_gate_opened"], + ) + assert_false( + f"iwooos_projection.immediate_visual_mesh_nodes.{item['node_id']}.runtime_execution_authorized", + item["runtime_execution_authorized"], + ) + assert_true( + f"iwooos_projection.immediate_visual_mesh_nodes.{item['node_id']}.not_authorization", + item["not_authorization"], + ) + hosts_mesh_node = next(item for item in immediate_visual_mesh_nodes if item["node_id"] == "hosts") + assert_false( + "iwooos_projection.immediate_visual_mesh_nodes.hosts.host_change_authorized", + hosts_mesh_node["host_change_authorized"], + ) + source_control_mesh_node = next(item for item in immediate_visual_mesh_nodes if item["node_id"] == "sourceControl") + assert_false( + "iwooos_projection.immediate_visual_mesh_nodes.sourceControl.source_control_mutation_authorized", + source_control_mesh_node["source_control_mutation_authorized"], + ) + runtime_gate_mesh_node = next(item for item in immediate_visual_mesh_nodes if item["node_id"] == "runtimeGate") + assert_false( + "iwooos_projection.immediate_visual_mesh_nodes.runtimeGate.scan_authorized", + runtime_gate_mesh_node["scan_authorized"], + ) for text in [ "iwooos_first_unlock_path_step_count=5", "iwooos_first_unlock_path_current_focus=s4_9_owner_response", @@ -12203,6 +12332,70 @@ def validate(root: Path) -> None: list(web_messages_en["iwooos"]["focusDeck"]["items"][key].keys()), field, ) + assert_contains( + "web_messages.zh-TW.iwooos.immediateVisualMesh", + list(web_messages_zh["iwooos"].keys()), + "immediateVisualMesh", + ) + assert_contains( + "web_messages.en.iwooos.immediateVisualMesh", + list(web_messages_en["iwooos"].keys()), + "immediateVisualMesh", + ) + for key in ["eyebrow", "title", "subtitle", "center", "stats", "nodes", "boundaryTitle", "boundaryIntro"]: + assert_contains( + "web_messages.zh-TW.iwooos.immediateVisualMesh.keys", + list(web_messages_zh["iwooos"]["immediateVisualMesh"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.immediateVisualMesh.keys", + list(web_messages_en["iwooos"]["immediateVisualMesh"].keys()), + key, + ) + for key in ["assetScope", "hostScope", "runtimeGate"]: + assert_contains( + "web_messages.zh-TW.iwooos.immediateVisualMesh.stats", + list(web_messages_zh["iwooos"]["immediateVisualMesh"]["stats"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.immediateVisualMesh.stats", + list(web_messages_en["iwooos"]["immediateVisualMesh"]["stats"].keys()), + key, + ) + assert_contains( + f"web_messages.zh-TW.iwooos.immediateVisualMesh.stats.{key}", + list(web_messages_zh["iwooos"]["immediateVisualMesh"]["stats"][key].keys()), + "label", + ) + assert_contains( + f"web_messages.en.iwooos.immediateVisualMesh.stats.{key}", + list(web_messages_en["iwooos"]["immediateVisualMesh"]["stats"][key].keys()), + "label", + ) + for key in ["products", "hosts", "sourceControl", "monitoring", "awooop", "runtimeGate"]: + assert_contains( + "web_messages.zh-TW.iwooos.immediateVisualMesh.nodes", + list(web_messages_zh["iwooos"]["immediateVisualMesh"]["nodes"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.immediateVisualMesh.nodes", + list(web_messages_en["iwooos"]["immediateVisualMesh"]["nodes"].keys()), + key, + ) + for field in ["title", "body"]: + assert_contains( + f"web_messages.zh-TW.iwooos.immediateVisualMesh.nodes.{key}", + list(web_messages_zh["iwooos"]["immediateVisualMesh"]["nodes"][key].keys()), + field, + ) + assert_contains( + f"web_messages.en.iwooos.immediateVisualMesh.nodes.{key}", + list(web_messages_en["iwooos"]["immediateVisualMesh"]["nodes"][key].keys()), + field, + ) assert_text_contains( "iwooos_page.first_unlock_evidence_packet_testid", iwooos_projection_page,