-
Notifications
You must be signed in to change notification settings - Fork 0
feat(console): /ui 改掛 EdgeConsole 完整產品操作台(B 方案落地,GPU 為核心前提) #194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 點。 |
| 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(); | ||
|
|
||
| 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([]); | ||
| }); | ||
| }); | ||
| 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", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
With this config, 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"] } }], | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: monkey1sai/AI-BIM-governance
Length of output: 12668
🏁 Script executed:
Repository: monkey1sai/AI-BIM-governance
Length of output: 115
🏁 Script executed:
Repository: monkey1sai/AI-BIM-governance
Length of output: 4282
🏁 Script executed:
Repository: monkey1sai/AI-BIM-governance
Length of output: 19581
🏁 Script executed:
Repository: monkey1sai/AI-BIM-governance
Length of output: 10322
🏁 Script executed:
Repository: monkey1sai/AI-BIM-governance
Length of output: 11552
🏁 Script executed:
Repository: monkey1sai/AI-BIM-governance
Length of output: 2859
🏁 Script executed:
Repository: monkey1sai/AI-BIM-governance
Length of output: 647
🏁 Script executed:
Repository: monkey1sai/AI-BIM-governance
Length of output: 12255
🏁 Script executed:
Repository: monkey1sai/AI-BIM-governance
Length of output: 3565
🏁 Script executed:
Repository: monkey1sai/AI-BIM-governance
Length of output: 6408
🏁 Script executed:
Repository: monkey1sai/AI-BIM-governance
Length of output: 2367
🏁 Script executed:
Repository: monkey1sai/AI-BIM-governance
Length of output: 7569
Fix
.first()fragility in E2E text assertions (exact matches are safe;/first frame/ineeds refinement)In
web-viewer-sample/e2e/product-console-integration.spec.ts:"DataChannel"/"highlightPrimsRequest", both with{ exact: true }): these strings are each rendered once on#/viewer(Field k="DataChannel"and the command-matrixcmdcell), so.first()is effectively redundant here."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.getByText(/first frame/i).first()on#/sessions):/first frame/ican match multiple nodes (the lead copy and the “Open spectator URL” evidence text). Replace.first()with a more specific selector targeting the intendedField(e.g., the “Open spectator URL” value), or assert presence without picking an arbitrary first match.🤖 Prompt for AI Agents