Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions docs/superpowers/plans/2026-06-09-edge-console-product-shell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# EdgeConsole Product Shell 上線(B 方案 prototype 操作台落地)Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans,逐步打勾。

**Goal:** 把 `/ui` 的主控台從 `OperatorConsole`(Option A 六頁)換成 `EdgeConsole` 的 **完整 prototype 操作台**(B 方案):今天要做什麼首頁、A1 五步引導、逐 A1–A10 的 3D Viewer 呈現頁、轉檔/Session/機隊/MinIO 治理頁;同時修正「零 GPU / 無 Omniverse 依賴」框架字眼為 **GPU 為核心前提**,並對未建功能誠實標記。

**Architecture:** 純前端。基底直接取用已驗證(2026-06-08:44 tests + build:ui + Docker :8004/ui E2E pass)的 `stash@{0}`(product-governance-console WIP),用 `git stash apply` 套到新 branch;丟棄 stash 的 AGENTS.md/CLAUDE.md 舊改(main 已有四套工具契約),合併 console.test.tsx / pages.tsx(保 PR-1 LifecycleTab + PR-2 ProvLegend),疊上 GPU-premise 框架修正 + 保留 `#/kit`、`#/demo-control` 兩條 operator-tool 路由(非 silently 砍)。

**Tech Stack:** React 18 + TS + Vitest(renderToString)+ repo 內建 Playwright(本機無 bun)。

**硬約束(四套工具契約 / 邊界):**
1. 不在 main 開發 → branch `feat/edge-console-product-shell` → PR。
2. 改 symbol 前 GitNexus impact 已跑:`isOperatorConsolePath`(LOW, caller=main.tsx)、`EdgeConsole`(LOW, 0 caller)。
3. 邊界已審:MinioDataPage(靜態 tree,非 S3 browser)、KitGpuFleetPage(靜態機隊,明寫 restart 須送 audited intent 給 Kit Manager)、Session/Conversion(走 coordinator proxy、危險動作 disabled+p1)。全唯讀、無直連 MinIO、無 Kit 控制。
4. 未建一律標 p1/p4/demo;不做假按鈕。
5. 完成證據 = Playwright 截 `:8004/ui`(home + #/a1 + #/viewer)存 `artifacts/e2e/`。
6. commit 前 GitNexus detect_changes 驗 scope。

---

## Task 1:建 branch + 套用 stash 基底

**Files:** 全 console 檔(apply)。

- [ ] **Step 1:** `git switch -c feat/edge-console-product-shell`
- [ ] **Step 2:** `git stash apply stash@{0}`(保留 stash 當備援,不 pop)
- [ ] **Step 3:** 解衝突
- `git checkout --ours -- AGENTS.md CLAUDE.md`(保 main 的四套工具契約,丟 stash 舊 2 行)
- `console.test.tsx`:保 main 的 LifecycleTab/ProvLegend 測試 + 併入 stash 的 EdgeConsole/page 測試
- `pages.tsx`:保 PR-2 的 ProvLegend import + CoordinatorPage `<ProvLegend/>` + 併入 stash 的 +327 新頁
- [ ] **Step 4:** 乾淨套用的檔(無 PR-1/PR-2 衝突):EdgeConsole.tsx / data.ts / routing.ts / main.tsx / coordinatorClient.ts / governanceClient.ts / edge-console.css 應自動 apply。

### Task 2:GPU-premise 框架修正(使用者明確指正)

**Files:** `data.ts`, `EdgeConsole.tsx`, `pages.tsx`

- [ ] **Step 1:** `data.ts` NAV_GROUPS:`core` group `sub` 從 `"CORE · 無 Omniverse 依賴"` → `"CORE · 語意 / 規則 / 問題"`。
- [ ] **Step 2:** `EdgeConsole.tsx` header chip:`GPU 未取得` → `GPU · 依 session 派發`(保 ec-demo tone)。
- [ ] **Step 3:** `pages.tsx` A1 lead:`3D 高亮是加值,A1 core 不依賴 GPU` → `3D 高亮需 GPU viewport(依 review session 派發);規則檢核在 governance-service 完成`。
- [ ] **Step 4:** grep `零 GPU|無 GPU|無 Omniverse|no.?gpu` 全 console 掃,逐一修成 GPU-premise 語氣(不誤導為「不需 GPU」)。

### Task 3:保留 operator-tool 路由(非回歸)

**Files:** `EdgeConsole.tsx`

- [ ] **Step 1:** import `KitConsolePage`、`RealIfcConsolePage`。
- [ ] **Step 2:** renderBody 加 `case "kit": return <KitConsolePage />;` 與 `case "demo-control": return <RealIfcConsolePage />;`(route 已在 PRODUCT_CONSOLE_ROUTES;補 render,否則落到 HomePage)。

### Task 4:更新測試 / e2e

**Files:** `console.test.tsx`, `routing.test.ts`(驗不需改), `e2e/unified-console-routes.spec.ts`

- [ ] **Step 1:** `routing.test.ts`:所有斷言(#/kit、#/demo-control、#/nope→false)在新 PRODUCT_CONSOLE_ROUTES(超集)下仍成立 → 不需改,跑過確認。
- [ ] **Step 2:** `unified-console-routes.spec.ts`:原斷言 OperatorConsole(op-page、op-nav-*、IntakeSelectPage「模型進件」)已不成立;改為驗 EdgeConsole 保留的 `#/kit`→`kit-proxy-panel`、`#/demo-control`→`real-ifc-demo-control`,移除 op-* 與 IntakeSelectPage 斷言。`product-console-integration.spec.ts`(已存在)為 B 操作台主驗收。

### Task 5:驗證(最小→完整)

- [ ] **Step 1:** `cd web-viewer-sample; npx vitest run src/console`(全綠;含 EdgeConsole/page SSR + PR-1/PR-2)。
- [ ] **Step 2:** `npm run build`(tsc + vite 綠)。
- [ ] **Step 3:** `npm run build:ui`(dist-ui 重建——`:8004/ui` 入口;deploy.ps1 不會自動跑 build:ui)。
- [ ] **Step 4:** 重啟 coordinator 容器讓 dist-ui 生效(bind-mount)。
- [ ] **Step 5:** Playwright 截 `:8004/ui`(home「今天要做什麼」+ `#/a1` 五步 + `#/viewer` 3D Viewer)存 `artifacts/e2e/pr3-edge-console-*.png`。
- [ ] **Step 6:** GitNexus `detect_changes(scope=all)`,記風險;HIGH/CRITICAL 先回報。

### Task 6:commit + PR

- [ ] **Step 1:** `git diff --cached --check`(擋 trailing whitespace)。
- [ ] **Step 2:** commit + push + `gh pr create`(繁中標題/描述 + Frontend Verification table + gstack 證據路徑)。
- [ ] **Step 3:** merge(require-gstack-evidence 閘應放行——已有 e2e png)。

## Self-Review
- **Spec 覆蓋**:使用者 ① A1–A10 總覽=prototype 操作模型(HomePage + NAV_GROUPS + A1 五步)✓ ② 逐 A1–A10 的 3D Viewer 呈現(ViewerPresentationPage)✓ ③ GPU 為前提(Task 2 框架修正)✓ ④ 誠實 NOT BUILT(p1/p4/demo 標記沿用)✓。
- **邊界**:無新增直連 MinIO / Kit 控制;敏感頁全唯讀展示已審 ✓。
- **回歸**:保 `#/kit`、`#/demo-control`(Task 3);routing.test.ts 向後相容(Task 4-1)✓。
- **Placeholder**:無;套用既有已驗證 code + 明確 edit 點。
51 changes: 51 additions & 0 deletions web-viewer-sample/e2e/product-console-integration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { expect, test } from "@playwright/test";

const COORDINATOR = process.env.E2E_COORDINATOR_BASE_URL || "http://127.0.0.1:8004";

test.describe("Product AI-BIM Governance console integration", () => {
test("operator can navigate prototype-derived product console pages", async ({ page }) => {
const severeConsole: string[] = [];
page.on("console", (msg) => {
if (["error"].includes(msg.type())) severeConsole.push(msg.text());
});
page.on("pageerror", (err) => severeConsole.push(err.message));

await page.goto(`${COORDINATOR}/ui`);
await expect(page.getByRole("heading", { name: /今天要做什麼/ })).toBeVisible();
await expect(page.getByText("核心治理")).toBeVisible();
await expect(page.getByText("OMNIVERSE RUNTIME")).toBeVisible();
await expect(page.getByText("落地端控制台")).toBeVisible();
await expect(page.getByText("Chat USD Agent")).toBeVisible();

await page.goto(`${COORDINATOR}/ui#/a1`);
await expect(page.getByRole("heading", { name: /A1/ })).toBeVisible();
await expect(page.getByText("A1 五步引導式流程")).toBeVisible();
await expect(page.locator("main").getByText("governance-service :49102", { exact: true })).toBeVisible();

await page.goto(`${COORDINATOR}/ui#/viewer`);
await expect(page.getByRole("heading", { name: /3D Viewer/ })).toBeVisible();
await expect(page.locator("main").getByText("DataChannel", { exact: true }).first()).toBeVisible();
await expect(page.locator("main").getByText("highlightPrimsRequest", { exact: true }).first()).toBeVisible();
Comment on lines +27 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if the text patterns appear multiple times in the rendered pages

# Search for "DataChannel" in viewer-related components
rg -n -C2 'DataChannel' web-viewer-sample/src/console/pages.tsx

# Search for "mapping coverage" in conversion-related components
rg -n -C2 'mapping coverage' web-viewer-sample/src/console/pages.tsx

# Search for "first frame" in session-related components
rg -n -C2 'first frame' web-viewer-sample/src/console/pages.tsx

Repository: monkey1sai/AI-BIM-governance

Length of output: 12668


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="web-viewer-sample/e2e/product-console-integration.spec.ts"

# Show the relevant lines around the reported .first() usage
nl -ba "$FILE" | sed -n '1,120p'

# Locate all getByText usages for the affected strings in this spec file
rg -n 'getByText\("DataChannel"|getByText\("highlightPrimsRequest"' "$FILE"

# Locate all .first() usages in this spec file (to ensure we cover the same lines)
rg -n '\.first\(\)' "$FILE"

Repository: monkey1sai/AI-BIM-governance

Length of output: 115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="web-viewer-sample/e2e/product-console-integration.spec.ts"

# Confirm file exists and show a reasonable slice near the start
ls -la "$FILE"
echo "---- top of file ----"
cat -n "$FILE" | sed -n '1,140p'

# Find the exact assertions mentioned in the original comment
echo "---- DataChannel assertions ----"
rg -n "getByText\\(\"DataChannel\"" "$FILE" -n
echo "---- highlightPrimsRequest assertions ----"
rg -n "getByText\\(\"highlightPrimsRequest\"" "$FILE" -n

# List all .first() in the spec to check whether there are other relevant ones
echo "---- all .first() ----"
rg -n "\\.first\\(" "$FILE"

Repository: monkey1sai/AI-BIM-governance

Length of output: 4282


🏁 Script executed:

#!/bin/bash
set -euo pipefail

PAGES="web-viewer-sample/src/console/pages.tsx"

echo "---- occurrences of exact DataChannel label ----"
rg -n 'k="DataChannel"' "$PAGES" -n
rg -n '"DataChannel"' "$PAGES" -n | head -n 80

echo "---- occurrences of highlightPrimsRequest ----"
rg -n 'highlightPrimsRequest' "$PAGES" -n | head -n 120

echo "---- inspect SemanticViewerPage render ----"
rg -n 'function SemanticViewerPage|export function SemanticViewerPage' "$PAGES" -n
# Print a slice around the SemanticViewerPage definition
LINE=$(rg -n 'export function SemanticViewerPage' "$PAGES" | head -n1 | cut -d: -f1)
START=$((LINE-60))
END=$((LINE+260))
sed -n "${START},${END}p" "$PAGES" | cat -n

# Find Field component implementation (so we know what element gets rendered for k="...")
rg -n 'function Field|const Field|export function Field' web-viewer-sample/src/console -S || true
fd -a "Field" web-viewer-sample/src/console | head -n 20 || true

Repository: monkey1sai/AI-BIM-governance

Length of output: 19581


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect Field component rendering
FILE="web-viewer-sample/src/console/components.tsx"
echo "---- Field component ----"
sed -n '1,140p' "$FILE" | cat -n

# 2) Broader search for exact strings across console code
echo "---- all occurrences of literal \"DataChannel\" in web-viewer-sample/src ----"
rg -n '"DataChannel"' web-viewer-sample/src | head -n 200

echo "---- all occurrences of highlightPrimsRequest in web-viewer-sample/src ----"
rg -n 'highlightPrimsRequest' web-viewer-sample/src | head -n 200

# 3) Confirm those strings only appear in the viewer page (best-effort)
echo "---- occurrences within pages.tsx only (context) ----"
rg -n 'DataChannel|highlightPrimsRequest' web-viewer-sample/src/console/pages.tsx | head -n 200

Repository: monkey1sai/AI-BIM-governance

Length of output: 10322


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="web-viewer-sample/src/console/pages.tsx"

# show around the highlighted area in the earlier rg output
echo "---- around viewer command matrix (expected highlightPrimsRequest exact label) ----"
sed -n '200,310p' "$FILE" | cat -n

echo "---- around Field k=\"DataChannel\" ----"
sed -n '220,270p' "$FILE" | cat -n

echo "---- around viewer page component where Field/DataChannel is used ----"
# Try to locate the viewer page export by searching for "/viewer" or viewer page title
rg -n '3D Viewer|`#/viewer`|Viewer command matrix|SemanticViewerPage' "$FILE" -n | head -n 50

Repository: monkey1sai/AI-BIM-governance

Length of output: 11552


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="web-viewer-sample/src/console/pages.tsx"

start=$(rg -n "export function ViewerPresentationPage" "$FILE" | head -n1 | cut -d: -f1)
end=$(rg -n "export function ConversionSchedulingPage" "$FILE" | head -n1 | cut -d: -f1)
echo "ViewerPresentationPage lines: $start..$((end-1))"
sed -n "${start},$((end-1))p" "$FILE" | cat -n

echo "---- sanity: exact string occurrences within ViewerPresentationPage source ----"
rg -n '"DataChannel"|highlightPrimsRequest' "$FILE" -n | rg -n "($start|$end)" || true

Repository: monkey1sai/AI-BIM-governance

Length of output: 2859


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where the hash route '`#/viewer`' is wired to ViewerPresentationPage
rg -n "`#/viewer`|\\bviewer\\b.*ViewerPresentationPage|ViewerPresentationPage\\b" web-viewer-sample/src | head -n 200

Repository: monkey1sai/AI-BIM-governance

Length of output: 647


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect viewer route rendering
FILE1="web-viewer-sample/src/console/EdgeConsole.tsx"
echo "---- EdgeConsole.tsx ----"
sed -n '1,120p' "$FILE1" | cat -n

echo "---- around viewer switch in EdgeConsole.tsx ----"
rg -n 'case "viewer"|viewer' "$FILE1"
line=$(rg -n 'case "viewer"' "$FILE1" | head -n1 | cut -d: -f1)
start=$((line-40))
end=$((line+40))
sed -n "${start},${end}p" "$FILE1" | cat -n

# Inspect routing.ts if it changes mounted content
FILE2="web-viewer-sample/src/console/routing.ts"
echo "---- routing.ts ----"
sed -n '1,120p' "$FILE2" | cat -n

Repository: monkey1sai/AI-BIM-governance

Length of output: 12255


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "49102|governance-service :49102" web-viewer-sample/src web-viewer-sample/e2e || true

rg -n "severeConsole|page\.on\\(\"console\"|msg\.type\\(\\)" web-viewer-sample/e2e/product-console-integration.spec.ts || true

# Also check if any other place has the exact label "governance-service :49102"
rg -n '"governance-service :49102"' . || true

Repository: monkey1sai/AI-BIM-governance

Length of output: 3565


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="web-viewer-sample/src/console/data.ts"
sed -n '80,160p' "$FILE" | cat -n

rg -n '49102|governance-service :49102|port: "127\.0\.0\.1:49102"' "$FILE" -n

Repository: monkey1sai/AI-BIM-governance

Length of output: 6408


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="web-viewer-sample/src/console/pages.tsx"

echo "---- exact \"mapping coverage\" occurrences ----"
rg -n '"mapping coverage"' "$FILE" || true

echo "---- exact \"1 GPU = 1 Kit stream\" occurrences ----"
rg -n '"1 GPU = 1 Kit stream"' "$FILE" || true

echo "---- occurrences of /first frame/i text ----"
rg -n 'first frame' "$FILE" || true

echo "---- viewer command matrix highlightPrimsRequest exact command cell ----"
rg -n '"highlightPrimsRequest"' "$FILE" || true

echo "---- exact \"DataChannel\" occurrences ----"
rg -n '"DataChannel"' "$FILE" || true

# Also check EdgeConsole/global layout might contain same exact strings in main
echo "---- exact string occurrences across web-viewer-sample/src/console (broader) ----"
rg -n '"mapping coverage"|\"1 GPU = 1 Kit stream\"|\"DataChannel\"|first frame|highlightPrimsRequest' web-viewer-sample/src/console | head -n 200

Repository: monkey1sai/AI-BIM-governance

Length of output: 2367


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="web-viewer-sample/src/console/pages.tsx"

# Locate component blocks
rg -n "export function SessionManagementPage|export function KitGpuFleetPage" "$FILE"

# Print the whole SessionManagementPage and KitGpuFleetPage sections with line numbers (bounded)
s1=$(rg -n "export function SessionManagementPage" "$FILE" | head -n1 | cut -d: -f1)
e1=$(rg -n "export function .*Page" "$FILE" | awk -F: -v s="$s1" '$1>s{print $1}' | sort -n | head -n1)
s2=$(rg -n "export function KitGpuFleetPage" "$FILE" | head -n1 | cut -d: -f1)
e2=$(rg -n "export function .*Page" "$FILE" | awk -F: -v s="$s2" '$1>s{print $1}' | sort -n | head -n1)

echo "---- SessionManagementPage $s1..$((e1-1)) ----"
sed -n "${s1},$((e1-1))p" "$FILE" | cat -n

echo "---- first frame occurrences inside SessionManagementPage ----"
sed -n "${s1},$((e1-1))p" "$FILE" | rg -n "first frame" || true

echo "---- KitGpuFleetPage $s2..$((e2-1)) ----"
sed -n "${s2},$((e2-1))p" "$FILE" | cat -n

echo "---- exact \"1 GPU = 1 Kit stream\" inside KitGpuFleetPage ----"
sed -n "${s2},$((e2-1))p" "$FILE" | rg -n "\"1 GPU = 1 Kit stream\"" || true

# Also confirm which components contain other .first candidates in the e2e file
cat -n web-viewer-sample/e2e/product-console-integration.spec.ts | sed -n '20,60p'

Repository: monkey1sai/AI-BIM-governance

Length of output: 7569


Fix .first() fragility in E2E text assertions (exact matches are safe; /first frame/i needs refinement)

In web-viewer-sample/e2e/product-console-integration.spec.ts:

  • Lines 27-28 ("DataChannel" / "highlightPrimsRequest", both with { exact: true }): these strings are each rendered once on #/viewer (Field k="DataChannel" and the command-matrix cmd cell), so .first() is effectively redundant here.
  • Line 32 ("mapping coverage", { exact: true }) and line 41 ("1 GPU = 1 Kit stream", { exact: true }): same story—exact single matches, so .first() doesn’t add safety.
  • Line 36 (getByText(/first frame/i).first() on #/sessions): /first frame/i can match multiple nodes (the lead copy and the “Open spectator URL” evidence text). Replace .first() with a more specific selector targeting the intended Field (e.g., the “Open spectator URL” value), or assert presence without picking an arbitrary first match.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-viewer-sample/e2e/product-console-integration.spec.ts` around lines 27 -
28, Tests use .first() where it’s redundant or fragile; remove unnecessary
.first() calls for exact single matches (e.g., the getByText("DataChannel") and
getByText("highlightPrimsRequest") assertions) and replace the fragile
getByText(/first frame/i).first() with a more specific locator that targets the
intended Field value (e.g., narrow the locator to the “Open spectator URL” Field
or assert existence of that explicit text) so you stop relying on arbitrary
first matches; update the assertions in product-console-integration.spec.ts to
use the exact getByText(...) (no .first()) for unique strings and a scoped
selector for the `/first frame/i` case.


await page.goto(`${COORDINATOR}/ui#/conv`);
await expect(page.getByRole("heading", { name: /IFC→USD/ })).toBeVisible();
await expect(page.locator("main").getByText("mapping coverage", { exact: true }).first()).toBeVisible();

await page.goto(`${COORDINATOR}/ui#/sessions`);
await expect(page.getByRole("heading", { name: /Session 管理/ })).toBeVisible();
await expect(page.locator("main").getByText(/first frame/i).first()).toBeVisible();
await expect(page.getByRole("button", { name: /Reclaim stale spectator/ })).toBeVisible();

await page.goto(`${COORDINATOR}/ui#/instances`);
await expect(page.getByRole("heading", { name: /Kit \/ GPU 機隊/ })).toBeVisible();
await expect(page.locator("main").getByText("1 GPU = 1 Kit stream", { exact: true }).first()).toBeVisible();

await page.goto(`${COORDINATOR}/ui#/minio`);
await expect(page.getByRole("heading", { name: /MinIO 資料/ })).toBeVisible();
await expect(page.locator("main .ec-tree").getByText("bim-control/", { exact: true })).toBeVisible();
await expect(page.locator("main .ec-tree").getByText("model.usdc", { exact: true })).toBeVisible();

await page.screenshot({ path: "../artifacts/e2e/product-governance-console-integration.png", fullPage: true });
expect(severeConsole).toEqual([]);
});
});
52 changes: 19 additions & 33 deletions web-viewer-sample/e2e/unified-console-routes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,44 @@
import { test, expect } from "@playwright/test";

// CH-E:coordinator :8004/ui 服務 React UnifiedConsole(vite base=/ui/,gated on CONSOLE_DIST_DIR)。
// 驗六個 hash 路由各自掛對應 operator 頁,且 nav 點擊可切換。/ui/console 301、/ui/open 302 凍結由
// ui-open-regression.spec 另行覆蓋;本 spec 專注「六頁可達 + nav 切換」這條前端可操作鏈。
// B 方案:coordinator :8004/ui 改掛 EdgeConsole 產品操作台(取代 OperatorConsole 六頁)。
// 本 spec 專注「EdgeConsole 殼層可達 + 換台後仍保留的 operator-tool 路由(#/kit、#/demo-control)」。
// 完整 prototype 頁(home /「今天要做什麼」、#/a1、#/viewer、#/conv、#/sessions、#/instances、#/minio)
// 由 product-console-integration.spec.ts 覆蓋。/ui/console 301、/ui/open 302 凍結由 ui-open-regression.spec 覆蓋。
const COORDINATOR = process.env.E2E_COORDINATOR_BASE_URL || "http://127.0.0.1:8004";

test.describe("CH-E:統一治理控制台六路由(:8004/ui React console)", () => {
test("/ui 直接導航:六 hash 路由各渲染對應 operator 頁(含 #/kit、#/demo-control)", async ({ page }) => {
// /ui 無 hash → 預設 coordinator 頁;nav 六鍵皆在(前端真實可見入口)。
test.describe("B 操作台::8004/ui EdgeConsole 路由 + 保留 operator-tool 路由", () => {
test("/ui 掛 EdgeConsole 殼層;保留 #/kit、#/demo-control、#/review、#/runtime 可達", async ({ page }) => {
// /ui 無 hash → EdgeConsole HomePage(今天要做什麼)。
await page.goto(`${COORDINATOR}/ui`);
await expect(page.getByTestId("op-page")).toBeVisible({ timeout: 20_000 });
for (const k of ["coordinator", "intake", "demo-control", "review", "runtime", "kit"]) {
await expect(page.getByTestId(`op-nav-${k}`)).toBeVisible();
}
await expect(page.getByRole("heading", { name: /Coordinator Console/ })).toBeVisible();
await expect(page.getByRole("heading", { name: /今天要做什麼/ })).toBeVisible({ timeout: 20_000 });

// #/kit → Kit 模型台(/api/kit proxy 面板)。
// #/kit → Kit 模型台(/api/kit proxy 面板)——換 EdgeConsole 後仍保留(非 silently 砍)。
await page.goto(`${COORDINATOR}/ui#/kit`);
await expect(page.getByTestId("kit-proxy-panel")).toBeVisible({ timeout: 15_000 });

// #/demo-control → 真實 IFC 進件垂直切片。
// #/demo-control → 真實 IFC 進件垂直切片——保留(真實 IFC E2E 入口)。
await page.goto(`${COORDINATOR}/ui#/demo-control`);
await expect(page.getByTestId("real-ifc-demo-control")).toBeVisible({ timeout: 15_000 });

// #/review → Review Room(審查室)。
await page.goto(`${COORDINATOR}/ui#/review`);
await expect(page.getByRole("heading", { name: /審查室/ })).toBeVisible({ timeout: 15_000 });

// #/intake → 模型進件(選取現成模型)。
await page.goto(`${COORDINATOR}/ui#/intake`);
await expect(page.getByRole("heading", { name: /模型進件/ })).toBeVisible({ timeout: 15_000 });

// #/runtime → Runtime Dashboard。
// #/runtime → Runtime 監控。
await page.goto(`${COORDINATOR}/ui#/runtime`);
await expect(page.getByRole("heading", { name: /Runtime Dashboard/ })).toBeVisible({ timeout: 15_000 });
await expect(page.getByRole("heading", { name: /Runtime/ })).toBeVisible({ timeout: 15_000 });

await page.screenshot({ path: "../artifacts/e2e/unified-console-routes.png", fullPage: true });
});

test("nav 點擊切換頁面(hash 更新 + 內容切換,零依賴 hash 路由)", async ({ page }) => {
await page.goto(`${COORDINATOR}/ui#/coordinator`);
await expect(page.getByTestId("op-page")).toBeVisible({ timeout: 20_000 });

await page.getByTestId("op-nav-kit").click();
await expect(page).toHaveURL(/#\/kit$/);
await expect(page.getByTestId("kit-proxy-panel")).toBeVisible();

await page.getByTestId("op-nav-demo-control").click();
await expect(page).toHaveURL(/#\/demo-control$/);
await expect(page.getByTestId("real-ifc-demo-control")).toBeVisible();
test("EdgeConsole nav 點擊切換頁面(hash 更新 + 內容切換,零依賴 hash 路由)", async ({ page }) => {
await page.goto(`${COORDINATOR}/ui#/a1`);
await expect(page.getByRole("heading", { name: /A1 · 治理與模型檢核/ })).toBeVisible({ timeout: 20_000 });

await page.getByTestId("op-nav-review").click();
await expect(page).toHaveURL(/#\/review$/);
await expect(page.getByRole("heading", { name: /審查室/ })).toBeVisible();
// 點 nav 切到 A2 版本差異(core 治理 group 內的另一頁)。
await page.getByRole("button", { name: /Version Diff/ }).first().click();
await expect(page).toHaveURL(/#\/?a2$/);
await expect(page.getByRole("heading", { name: /版本差異/ })).toBeVisible({ timeout: 15_000 });

await page.screenshot({ path: "../artifacts/e2e/unified-console-nav.png", fullPage: true });
});
Expand Down
26 changes: 26 additions & 0 deletions web-viewer-sample/playwright.product-console.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { defineConfig, devices } from "@playwright/test";

// Product console E2E targets an already-served coordinator /ui bundle.
// It intentionally avoids the viewer webServer from playwright.config.ts.
export default defineConfig({
testDir: "./e2e",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Limit the product-console Playwright config

With this config, npx playwright test -c playwright.product-console.config.ts --list selects all 14 files under e2e, including viewer harness tests such as viewer-harness.spec.ts that rely on the normal config's baseURL and webServer. Since this product config intentionally omits those, the product-console evidence command will either fail on relative navigations like page.goto("/?harness=1") or run unrelated suites. Please add a testMatch/grep for the product-console routes, or move these specs under a dedicated directory.

Useful? React with 👍 / 👎.

// 僅跑產品操作台 evidence specs;不收 viewer-harness 等需 webServer/baseURL 的 spec(避免相對導航失敗或誤跑無關套件)。
testMatch: ["product-console-integration.spec.ts", "unified-console-routes.spec.ts"],
outputDir: "../artifacts/e2e/_product_console_output",
timeout: 60_000,
expect: { timeout: 15_000 },
fullyParallel: false,
workers: 1,
retries: 0,
reporter: [
["list"],
["html", { outputFolder: "../artifacts/e2e/product-console-report", open: "never" }],
],
use: {
trace: "on",
screenshot: "on",
video: "retain-on-failure",
viewport: { width: 1440, height: 900 },
},
projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
});
Loading
Loading