From 21f5142d0816a6b2bbf119c10b9c29130c1e6810 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 1 Jun 2026 00:51:28 +0800 Subject: [PATCH] feat(web): add IwoooS focus deck --- apps/web/messages/en.json | 124 ++++++++--- apps/web/messages/zh-TW.json | 47 ++++ apps/web/src/app/[locale]/iwooos/page.tsx | 205 +++++++++++++++++- docs/LOGBOOK.md | 27 +++ .../iwooos-posture-projection.snapshot.json | 65 ++++++ ...ecurity-mirror-status-rollup.snapshot.json | 12 + .../security-mirror-progress-guard.py | 193 ++++++++++++++++- 7 files changed, 634 insertions(+), 39 deletions(-) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 2205d7374..0231d5f06 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -4625,6 +4625,25 @@ "tool": "工具", "scope": "範圍", "blockers": "卡點", + "legacy": { + "only": "自建 MCP 已觀測", + "total": "自建 MCP", + "success": "自建成功", + "failed": "自建失敗", + "topTool": "自建工具" + }, + "evidence": { + "firstClassTitle": "AwoooP Gateway MCP", + "selfBuiltTitle": "自建 MCP / 舊版 Audit", + "observed": "已觀測", + "notObserved": "未觀測", + "firstClassEmpty": "此 Run 尚未留下 AwoooP Gateway 一級 MCP 呼叫。", + "selfBuiltEmpty": "此 Run 尚未透過 Incident 串到自建 MCP audit。", + "agentScope": "{agent} / {scope}", + "counts": "ok {success} / fail {failed} / block {blocked}", + "legacyCounts": "ok {success} / fail {failed}", + "noError": "無錯誤" + }, "metrics": { "firstClass": "一級入口", "policy": "政策已套用", @@ -4987,35 +5006,35 @@ "verification": "Final verification" } }, - "gateway": { - "title": "MCP 閘道", - "emptyState": "尚無紀錄", - "agent": "Agent", - "tool": "工具", - "scope": "範圍", - "blockers": "卡點", - "legacy": { - "only": "自建 MCP 已觀測", - "total": "自建 MCP", - "success": "自建成功", - "failed": "自建失敗", - "topTool": "自建工具" - }, - "evidence": { - "firstClassTitle": "AwoooP Gateway MCP", - "selfBuiltTitle": "自建 MCP / 舊版 Audit", - "observed": "已觀測", - "notObserved": "未觀測", - "firstClassEmpty": "此 Run 尚未留下 AwoooP Gateway 一級 MCP 呼叫。", - "selfBuiltEmpty": "此 Run 尚未透過 Incident 串到自建 MCP audit。", - "agentScope": "{agent} / {scope}", - "counts": "ok {success} / fail {failed} / block {blocked}", - "legacyCounts": "ok {success} / fail {failed}", - "noError": "無錯誤" - }, - "metrics": { - "firstClass": "第一級", - "policy": "政策已套用", + "gateway": { + "title": "MCP 閘道", + "emptyState": "尚無紀錄", + "agent": "Agent", + "tool": "工具", + "scope": "範圍", + "blockers": "卡點", + "legacy": { + "only": "自建 MCP 已觀測", + "total": "自建 MCP", + "success": "自建成功", + "failed": "自建失敗", + "topTool": "自建工具" + }, + "evidence": { + "firstClassTitle": "AwoooP Gateway MCP", + "selfBuiltTitle": "自建 MCP / 舊版 Audit", + "observed": "已觀測", + "notObserved": "未觀測", + "firstClassEmpty": "此 Run 尚未留下 AwoooP Gateway 一級 MCP 呼叫。", + "selfBuiltEmpty": "此 Run 尚未透過 Incident 串到自建 MCP audit。", + "agentScope": "{agent} / {scope}", + "counts": "ok {success} / fail {failed} / block {blocked}", + "legacyCounts": "ok {success} / fail {failed}", + "noError": "無錯誤" + }, + "metrics": { + "firstClass": "第一級", + "policy": "政策已套用", "approvalExecutor": "審批執行器", "legacyBridge": "舊橋接" } @@ -5314,6 +5333,53 @@ "state": "只讀鏡像 / 先觀測", "detail": "只顯示態勢與缺口;掃描、修復、更新、阻擋仍未開閘。" }, + "focusDeck": { + "eyebrow": "首層焦點導覽", + "title": "先選一條線,不用讀完整頁", + "subtitle": "把 IwoooS 的長頁面壓成五個可跳轉焦點;每個焦點只做導覽與證據定位,不是執行按鈕。", + "boundaryTitle": "焦點導覽邊界", + "summary": { + "items": { + "label": "焦點", + "value": "5" + }, + "runtime": { + "label": "執行期", + "value": "Gate 0" + }, + "mode": { + "label": "模式", + "value": "只讀" + } + }, + "items": { + "workMap": { + "label": "先看總圖", + "title": "資安工作地圖", + "body": "用六條工作線快速理解目前資安網的方向。" + }, + "unlockPath": { + "label": "目前卡點", + "title": "61% 解鎖路徑", + "body": "真正能推動進度的是 S4.9 負責人回覆與脫敏證據。" + }, + "productScope": { + "label": "覆蓋範圍", + "title": "全產品資安範圍", + "body": "看 AwoooI、AwoooP、IwoooS、網站、VibeWork 與主機納管。" + }, + "hostTools": { + "label": "主機工具", + "title": "Kali 與工具鏈", + "body": "看 112 / 111 / 168 與監控、MCP、Ansible、KM 的只讀鏈路。" + }, + "sourceControl": { + "label": "版本來源", + "title": "GitHub / Gitea 邊界", + "body": "看 GitHub primary、Gitea 清冊、refs 與 workflow 還缺哪些證據。" + } + } + }, "visualCommandDashboard": { "eyebrow": "視覺化資安指揮板", "title": "先看圖,再展開證據", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 55cb3f1d7..0231d5f06 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -5333,6 +5333,53 @@ "state": "只讀鏡像 / 先觀測", "detail": "只顯示態勢與缺口;掃描、修復、更新、阻擋仍未開閘。" }, + "focusDeck": { + "eyebrow": "首層焦點導覽", + "title": "先選一條線,不用讀完整頁", + "subtitle": "把 IwoooS 的長頁面壓成五個可跳轉焦點;每個焦點只做導覽與證據定位,不是執行按鈕。", + "boundaryTitle": "焦點導覽邊界", + "summary": { + "items": { + "label": "焦點", + "value": "5" + }, + "runtime": { + "label": "執行期", + "value": "Gate 0" + }, + "mode": { + "label": "模式", + "value": "只讀" + } + }, + "items": { + "workMap": { + "label": "先看總圖", + "title": "資安工作地圖", + "body": "用六條工作線快速理解目前資安網的方向。" + }, + "unlockPath": { + "label": "目前卡點", + "title": "61% 解鎖路徑", + "body": "真正能推動進度的是 S4.9 負責人回覆與脫敏證據。" + }, + "productScope": { + "label": "覆蓋範圍", + "title": "全產品資安範圍", + "body": "看 AwoooI、AwoooP、IwoooS、網站、VibeWork 與主機納管。" + }, + "hostTools": { + "label": "主機工具", + "title": "Kali 與工具鏈", + "body": "看 112 / 111 / 168 與監控、MCP、Ansible、KM 的只讀鏈路。" + }, + "sourceControl": { + "label": "版本來源", + "title": "GitHub / Gitea 邊界", + "body": "看 GitHub primary、Gitea 清冊、refs 與 workflow 還缺哪些證據。" + } + } + }, "visualCommandDashboard": { "eyebrow": "視覺化資安指揮板", "title": "先看圖,再展開證據", diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index 1d349fc1c..a7ef384fb 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -173,6 +173,14 @@ type IwoooSCommandMapItem = { tone: 'steady' | 'warn' | 'locked' } +type IwoooSFocusDeckItem = { + key: string + href: string + metric: string + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + type AwoooPReadOnlyLandingReadinessItem = { key: string item: string @@ -2285,6 +2293,27 @@ const iwooosCommandMapBoundaries = [ 'not_authorization=true', ] as const +const iwooosFocusDeckItems: IwoooSFocusDeckItem[] = [ + { key: 'workMap', href: '#iwooos-command-map-board', metric: '6', icon: Radar, tone: 'warn' }, + { key: 'unlockPath', href: '#iwooos-first-progress-unlock-path-board', metric: 'S4.9', icon: ListChecks, tone: 'warn' }, + { key: 'productScope', href: '#iwooos-global-security-mesh-matrix-board', metric: '8/8', icon: ShieldCheck, tone: 'steady' }, + { key: 'hostTools', href: '#iwooos-host-tool-evidence-chain-board', metric: '3', icon: Activity, tone: 'warn' }, + { key: 'sourceControl', href: '#iwooos-command-map-source-control', metric: '0', icon: GitBranch, tone: 'locked' }, +] + +const iwooosFocusDeckBoundaries = [ + 'iwooos_focus_deck_first_layer=true', + 'iwooos_focus_deck_item_count=5', + 'iwooos_focus_deck_anchor_navigation_allowed=true', + 'iwooos_focus_deck_execution_action_buttons_allowed=false', + 'iwooos_focus_deck_runtime_gate_count=0', + 'iwooos_focus_deck_above_command_map=true', + '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' }, @@ -4706,6 +4735,7 @@ function IwoooSGlobalSecurityMeshMatrixBoard() { return (
@@ -4878,6 +4908,7 @@ function IwoooSHostToolEvidenceChainBoard() { return (
@@ -8902,7 +8933,11 @@ function IwoooSFirstProgressUnlockPathBoard() { const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } return ( -
+
+
+
+
+
+ + {t('eyebrow')} +
+

{t('title')}

+

+ {t('subtitle')} +

+
+ {['items', 'runtime', 'mode'].map(key => ( +
+
{t(`summary.${key}.label` as never)}
+
+ {t(`summary.${key}.value` as never)} +
+
+ ))} +
+
+ + +
+ +
+ + {t('boundaryTitle')} + +
+ {iwooosFocusDeckBoundaries.map(item => ( + + {item} + + ))} +
+
+
+
+ ) +} + function IwoooSCommandMapBoard() { const t = useTranslations('iwooos.commandMap') const [activeKey, setActiveKey] = useState('unlock') @@ -9042,7 +9231,11 @@ function IwoooSCommandMapBoard() { const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } return ( -
+
@@ -9072,6 +9265,7 @@ function IwoooSCommandMapBoard() { return (
+ @@ -14220,7 +14415,11 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) { -
+

{t('sourceControlReadiness.title')}

diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index a678c1673..fc6074914 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,30 @@ +## 2026-06-01|IwoooS 首層焦點導覽 + +**背景**: + +- 使用者持續指出 IwoooS 頁面內容太多、太像長文字清單,不符合專業資安產品常見的圖表、分流、互動式理解方式。 +- 本輪不新增資安執行能力,只降低第一屏理解成本:把長頁面壓成五個頁內焦點,讓使用者先選線索,再跳到既有看板。 + +**本次調整**: + +- `apps/web/src/app/[locale]/iwooos/page.tsx`: + - 新增 `IwoooSFocusDeckBoard`,放在 hero 後、資安工作地圖前。 + - 五個焦點卡:資安工作地圖、61% 解鎖路徑、全產品資安範圍、Kali 與工具鏈、GitHub / Gitea 邊界。 + - 焦點卡只做頁內 anchor navigation,不是 action button,不觸發掃描、修復、部署、SSH、Kali `/execute` 或版本來源變更。 + - 替工作地圖、解鎖路徑、全產品範圍、主機工具鏈與 source-control readiness 補上穩定 anchor id。 +- `apps/web/messages/zh-TW.json` / `apps/web/messages/en.json`: + - 新增 `iwooos.focusDeck` 繁中文案;`en.json` 維持繁中鏡像。 +- `docs/security/iwooos-posture-projection.snapshot.json` / `security-mirror-status-rollup.snapshot.json`: + - 補上 `focus_deck_first_layer=true`、`focus_deck_item_count=5`、`focus_deck_anchor_navigation_allowed=true`、`focus_deck_execution_action_buttons_allowed=false`、`focus_deck_runtime_gate_count=0`。 + - 新增 `S2.145` 進度 ledger;headline 不增加,因為這是 UX / framework 可理解度,不是 runtime 授權。 +- `scripts/security/security-mirror-progress-guard.py`: + - 新增 guard,鎖住焦點導覽的位置、五個 anchor、snapshot 欄位與 runtime false 邊界。 + +**進度邊界**: + +- 整體維持 `61%`。 +- 本輪屬於第一屏 UX / 導覽 / evidence 可理解度推進;runtime gate、Kali / SSH、掃描、修復、部署按鈕、GitHub primary 切換、Gitea 停用仍維持 `false / 0`。 + ## 2026-06-01|IwoooS 命令面板資安入口收斂 **背景**: diff --git a/docs/security/iwooos-posture-projection.snapshot.json b/docs/security/iwooos-posture-projection.snapshot.json index 3539f8bf6..8fbd155c8 100644 --- a/docs/security/iwooos-posture-projection.snapshot.json +++ b/docs/security/iwooos-posture-projection.snapshot.json @@ -60,6 +60,12 @@ "command_map_navigation_controls_allowed": true, "command_map_execution_action_buttons_allowed": false, "command_map_runtime_gate_count": 0, + "focus_deck_first_layer": true, + "focus_deck_item_count": 5, + "focus_deck_anchor_navigation_allowed": true, + "focus_deck_execution_action_buttons_allowed": false, + "focus_deck_runtime_gate_count": 0, + "focus_deck_above_command_map": true, "long_form_sections_default_collapsed": true, "owner_response_validation_received_count": 0, "owner_response_validation_accepted_count": 0, @@ -299,6 +305,65 @@ "not_authorization": true } ], + "focus_deck_items": [ + { + "item_id": "workMap", + "display_order": 1, + "target_anchor": "#iwooos-command-map-board", + "display_mode": "first_layer_focus_deck", + "anchor_navigation_allowed": true, + "execution_action_button_allowed": false, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "not_authorization": true + }, + { + "item_id": "unlockPath", + "display_order": 2, + "target_anchor": "#iwooos-first-progress-unlock-path-board", + "display_mode": "first_layer_focus_deck", + "anchor_navigation_allowed": true, + "execution_action_button_allowed": false, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "not_authorization": true + }, + { + "item_id": "productScope", + "display_order": 3, + "target_anchor": "#iwooos-global-security-mesh-matrix-board", + "display_mode": "first_layer_focus_deck", + "anchor_navigation_allowed": true, + "execution_action_button_allowed": false, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "not_authorization": true + }, + { + "item_id": "hostTools", + "display_order": 4, + "target_anchor": "#iwooos-host-tool-evidence-chain-board", + "display_mode": "first_layer_focus_deck", + "anchor_navigation_allowed": true, + "execution_action_button_allowed": false, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "not_authorization": true + }, + { + "item_id": "sourceControl", + "display_order": 5, + "target_anchor": "#iwooos-command-map-source-control", + "display_mode": "first_layer_focus_deck", + "anchor_navigation_allowed": true, + "execution_action_button_allowed": false, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "source_control_mutation_authorized": false, + "github_primary_switch_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 31e77713b..ef089b478 100644 --- a/docs/security/security-mirror-status-rollup.snapshot.json +++ b/docs/security/security-mirror-status-rollup.snapshot.json @@ -2209,6 +2209,18 @@ "runtime_delta": false, "execution_authorized": false, "not_authorization": true + }, + { + "delta_id": "s2_145_iwooos_first_layer_focus_deck", + "display_order": 174, + "completed_stage": "S2.145 IwoooS 首層焦點導覽", + "progress_axis": "framework_detail", + "headline_percent_delta": 0, + "framework_delta_visible": true, + "why_headline_unchanged": "IwoooS 只新增首層焦點導覽,把工作地圖、61% 解鎖路徑、全產品範圍、Kali / 主機工具鏈與 GitHub / Gitea 版本來源五個焦點壓成頁內 anchor navigation;iwooos_focus_deck_item_count=5、iwooos_focus_deck_anchor_navigation_allowed=true、iwooos_focus_deck_execution_action_buttons_allowed=false、iwooos_focus_deck_runtime_gate_count=0、iwooos_focus_deck_above_command_map=true、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 94c0d6c07..a22570805 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -609,6 +609,7 @@ def validate(root: Path) -> None: "s2_142_iwooos_first_unlock_path_first_layer", "s2_143_iwooos_command_map_first_layer", "s2_144_iwooos_command_palette_security_entry_unified", + "s2_145_iwooos_first_layer_focus_deck", ] assert_equal( "progress_delta_ledger.delta_ids", @@ -1136,13 +1137,14 @@ def validate(root: Path) -> None: ]: assert_text_not_contains("web_messages.zh-TW.awooop.runs_wording", zh_awooop_runs_text, forbidden) - zh_awooop_run_detail_text = json.dumps( - { - "incidentEvidence": web_messages_zh["awooop"]["incidentEvidence"], - "runDetail": web_messages_zh["awooop"]["runDetail"], - "approvalDecision": web_messages_zh["awooop"]["approvalDecision"], - }, - ensure_ascii=False, + zh_awooop_run_detail_text = "\n".join( + collect_string_values( + { + "incidentEvidence": web_messages_zh["awooop"]["incidentEvidence"], + "runDetail": web_messages_zh["awooop"]["runDetail"], + "approvalDecision": web_messages_zh["awooop"]["approvalDecision"], + } + ) ) for forbidden in [ "Trace ID", @@ -1512,6 +1514,32 @@ def validate(root: Path) -> None: iwooos_projection["summary"]["command_map_runtime_gate_count"], 0, ) + assert_true( + "iwooos_projection.summary.focus_deck_first_layer", + iwooos_projection["summary"]["focus_deck_first_layer"], + ) + assert_equal( + "iwooos_projection.summary.focus_deck_item_count", + iwooos_projection["summary"]["focus_deck_item_count"], + 5, + ) + assert_true( + "iwooos_projection.summary.focus_deck_anchor_navigation_allowed", + iwooos_projection["summary"]["focus_deck_anchor_navigation_allowed"], + ) + assert_false( + "iwooos_projection.summary.focus_deck_execution_action_buttons_allowed", + iwooos_projection["summary"]["focus_deck_execution_action_buttons_allowed"], + ) + assert_equal( + "iwooos_projection.summary.focus_deck_runtime_gate_count", + iwooos_projection["summary"]["focus_deck_runtime_gate_count"], + 0, + ) + assert_true( + "iwooos_projection.summary.focus_deck_above_command_map", + iwooos_projection["summary"]["focus_deck_above_command_map"], + ) assert_true( "iwooos_projection.summary.all_product_coverage_snapshot_detail_ledger_collapsed", iwooos_projection["summary"]["all_product_coverage_snapshot_detail_ledger_collapsed"], @@ -11785,6 +11813,50 @@ def validate(root: Path) -> None: f"iwooos_projection.first_progress_unlock_path_steps.{item['step_id']}.not_authorization", item["not_authorization"], ) + assert_text_contains( + "iwooos_page.focus_deck_testid", + iwooos_projection_page, + 'data-testid="iwooos-focus-deck-board"', + ) + assert_text_contains( + "iwooos_page.focus_deck_component", + iwooos_projection_page, + "IwoooSFocusDeckBoard", + ) + assert_text_before( + "iwooos_page.focus_deck_before_command_map", + iwooos_projection_page, + "", + "", + ) + assert_text_contains( + "iwooos_page.focus_deck_boundaries", + iwooos_projection_page, + 'data-testid="iwooos-focus-deck-boundaries"', + ) + for text in [ + "href: '#iwooos-command-map-board'", + "href: '#iwooos-first-progress-unlock-path-board'", + "href: '#iwooos-global-security-mesh-matrix-board'", + "href: '#iwooos-host-tool-evidence-chain-board'", + "href: '#iwooos-command-map-source-control'", + "iwooos-command-map-source-control", + "iwooos_focus_deck_first_layer=true", + "iwooos_focus_deck_item_count=5", + "iwooos_focus_deck_anchor_navigation_allowed=true", + "iwooos_focus_deck_execution_action_buttons_allowed=false", + "iwooos_focus_deck_runtime_gate_count=0", + "iwooos_focus_deck_above_command_map=true", + "runtime_execution_authorized=false", + "active_runtime_gate_count=0", + "action_buttons_allowed=false", + "not_authorization=true", + ]: + assert_text_contains( + "iwooos_page.focus_deck_boundary", + iwooos_projection_page, + text, + ) assert_text_contains( "iwooos_page.command_map_testid", iwooos_projection_page, @@ -11896,6 +11968,59 @@ def validate(root: Path) -> None: "iwooos_projection.command_map_items.boundary.host_mutation_authorized", boundary_item["host_mutation_authorized"], ) + expected_focus_deck_item_ids = [ + "workMap", + "unlockPath", + "productScope", + "hostTools", + "sourceControl", + ] + focus_deck_items = iwooos_projection["focus_deck_items"] + assert_equal( + "iwooos_projection.focus_deck_items.ids", + [item["item_id"] for item in focus_deck_items], + expected_focus_deck_item_ids, + ) + assert_equal( + "iwooos_projection.focus_deck_items.display_order", + [item["display_order"] for item in focus_deck_items], + list(range(1, len(expected_focus_deck_item_ids) + 1)), + ) + for item in focus_deck_items: + assert_equal( + f"iwooos_projection.focus_deck_items.{item['item_id']}.display_mode", + item["display_mode"], + "first_layer_focus_deck", + ) + assert_true( + f"iwooos_projection.focus_deck_items.{item['item_id']}.anchor_navigation_allowed", + item["anchor_navigation_allowed"], + ) + assert_false( + f"iwooos_projection.focus_deck_items.{item['item_id']}.execution_action_button_allowed", + item["execution_action_button_allowed"], + ) + assert_false( + f"iwooos_projection.focus_deck_items.{item['item_id']}.runtime_gate_opened", + item["runtime_gate_opened"], + ) + assert_false( + f"iwooos_projection.focus_deck_items.{item['item_id']}.runtime_execution_authorized", + item["runtime_execution_authorized"], + ) + assert_true( + f"iwooos_projection.focus_deck_items.{item['item_id']}.not_authorization", + item["not_authorization"], + ) + source_control_focus = next(item for item in focus_deck_items if item["item_id"] == "sourceControl") + assert_false( + "iwooos_projection.focus_deck_items.sourceControl.source_control_mutation_authorized", + source_control_focus["source_control_mutation_authorized"], + ) + assert_false( + "iwooos_projection.focus_deck_items.sourceControl.github_primary_switch_authorized", + source_control_focus["github_primary_switch_authorized"], + ) for text in [ "iwooos_first_unlock_path_step_count=5", "iwooos_first_unlock_path_current_focus=s4_9_owner_response", @@ -12024,6 +12149,60 @@ def validate(root: Path) -> None: list(web_messages_en["iwooos"]["commandMap"]["items"][key].keys()), field, ) + assert_contains( + "web_messages.zh-TW.iwooos.focusDeck", + list(web_messages_zh["iwooos"].keys()), + "focusDeck", + ) + assert_contains( + "web_messages.en.iwooos.focusDeck", + list(web_messages_en["iwooos"].keys()), + "focusDeck", + ) + for key in ["eyebrow", "title", "subtitle", "summary", "items", "boundaryTitle"]: + assert_contains( + "web_messages.zh-TW.iwooos.focusDeck.keys", + list(web_messages_zh["iwooos"]["focusDeck"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.focusDeck.keys", + list(web_messages_en["iwooos"]["focusDeck"].keys()), + key, + ) + for key in ["items", "runtime", "mode"]: + assert_contains( + "web_messages.zh-TW.iwooos.focusDeck.summary", + list(web_messages_zh["iwooos"]["focusDeck"]["summary"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.focusDeck.summary", + list(web_messages_en["iwooos"]["focusDeck"]["summary"].keys()), + key, + ) + for key in expected_focus_deck_item_ids: + assert_contains( + "web_messages.zh-TW.iwooos.focusDeck.items", + list(web_messages_zh["iwooos"]["focusDeck"]["items"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.focusDeck.items", + list(web_messages_en["iwooos"]["focusDeck"]["items"].keys()), + key, + ) + for field in ["label", "title", "body"]: + assert_contains( + f"web_messages.zh-TW.iwooos.focusDeck.items.{key}", + list(web_messages_zh["iwooos"]["focusDeck"]["items"][key].keys()), + field, + ) + assert_contains( + f"web_messages.en.iwooos.focusDeck.items.{key}", + list(web_messages_en["iwooos"]["focusDeck"]["items"][key].keys()), + field, + ) assert_text_contains( "iwooos_page.first_unlock_evidence_packet_testid", iwooos_projection_page,