diff --git a/AGENTS.md b/AGENTS.md index ce74f5fe5..d5482a9f4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -677,6 +677,15 @@ Scene interaction 是 browser client 與 Kit runtime 之間的 DataChannel JSON ## 5.4 Collaboration Flow +> **退役狀態(2026-05-21,change `remove-conflict-review-from-fast-mvp`)**:本節 +> collaboration broadcast(highlight / selection / annotation)在 coordinator 與 +> viewer 兩端的 implementation 已從 fast MVP product runtime 移除(`reviewNamespace.ts` +> 內的 `highlightRequest` / `selectionUpdate` / `annotationCreate` Socket.IO event +> handlers、viewer `IssuePanel` / `EventLogPanel` 已刪)。本 sequence 保留作為 +> archive context;viewer Change 2 (`fast-ifc-link-demo-loop`) 將 viewer 主畫面 +> 收斂為「全螢幕 stream + 邊框 HUD」,不含多人協作 UI。若未來重新引入,以新 +> OpenSpec change form ADD requirement 與 viewer slot。 + ```mermaid sequenceDiagram participant WV1 as web-viewer-sample User A @@ -703,6 +712,13 @@ bim-streaming-server 不作為多人協作事件中心。 ## 5.5 Review Result Visualization Flow +> **退役狀態(2026-05-21,change `remove-conflict-review-from-fast-mvp`)**:本節 +> review issue → DataChannel `highlightPrimsRequest` 的「issue 流」入口 +> (viewer `IssuePanel` + coordinator `getReviewIssues` / `review-bootstrap` +> endpoint)已從 fast MVP product runtime 移除。DataChannel `highlightPrimsRequest` +> 本身保留作 mapping highlight 工具(Window.tsx `_onMappingItemClick`),Change 2 +> 重做 viewer 時再評估。若 issue 流要重新引入,以新 OpenSpec change form ADD。 + ```mermaid sequenceDiagram participant BC as _bim-control @@ -803,6 +819,13 @@ mapping runtime usage → web-viewer-sample / bim-streaming-server ## 7.4 Review 資料 +> **退役狀態(2026-05-21,change `remove-conflict-review-from-fast-mvp`)**:issue +> / annotation / review result 的 fast MVP product runtime 已移除(`ReviewIssue` +> interface、`getReviewIssues` / `createAnnotation` / `getReviewBootstrap` / +> `IssuePanel` / `EventLogPanel` 已刪)。本表保留作 archive context,記錄歷史權威 +> 劃分。若 review 流要重新引入,以新 OpenSpec change form ADD requirement 與 +> coordinator / viewer 端配套。 + ```txt issue / annotation / review result ``` diff --git a/bim-review-coordinator/CLAUDE.md b/bim-review-coordinator/CLAUDE.md index 3e0f9f541..8e78e1f84 100644 --- a/bim-review-coordinator/CLAUDE.md +++ b/bim-review-coordinator/CLAUDE.md @@ -5,7 +5,15 @@ ## Role -IFC-ready Intake / Callback Outbox / Session Control Plane — 唯一外部 IFC-ready intake;協調 browser client 與 Kit streaming server 的連線資訊;廣播 presence / selection / annotation 等多人事件;將 streaming conversion 結果放入 metadata-only callback outbox。 +IFC-ready Intake / Callback Outbox / Session Control Plane — 唯一外部 IFC-ready intake;協調 browser client 與 Kit streaming server 的連線資訊;廣播 presence(`joinSession` / `leaveSession` / `presenceUpdated`)等基本 session 事件;將 streaming conversion 結果放入 metadata-only callback outbox。 + +> **退役狀態(2026-05-21,change `remove-conflict-review-from-fast-mvp`)**: +> `highlightRequest` / `selectionUpdate` / `annotationCreate` 等 collaboration +> Socket.IO event handlers 已從本 service 移除(`src/socket/reviewNamespace.ts`); +> `getReviewIssues` / `createAnnotation` / `/api/model-versions/:id/review-bootstrap` +> 也已刪。`/api/review-sessions/:id/events` 與 `/lifecycle-events` 仍保留; +> lifecycle endpoint 排除 collaboration event 的語意 wording 保留作 archive +> compatibility(舊 event log 仍可能含這些 type)。 埠口:`localhost:8004`(含 Socket.IO) diff --git a/bim-review-coordinator/src/app.ts b/bim-review-coordinator/src/app.ts index 0f756e06a..e6db18e45 100644 --- a/bim-review-coordinator/src/app.ts +++ b/bim-review-coordinator/src/app.ts @@ -494,22 +494,6 @@ export function createCoordinatorApp(overrides: Partial = {}) response.json(closed); }); - app.get("/api/model-versions/:modelVersionId/review-bootstrap", async (request, response, next) => { - try { - const [artifacts, issues] = await Promise.all([ - safeArtifacts(bimControlClient, request.params.modelVersionId), - safeIssues(bimControlClient, request.params.modelVersionId), - ]); - response.json({ - model_version_id: request.params.modelVersionId, - artifacts, - issues, - }); - } catch (error) { - next(error); - } - }); - // B-scheme(local-coordinator-ifc-ready-intake-boundary T3): // 唯一對外 IFC-ready intake。caller = 客戶落地端 IFC Worker(落地端內網, // machine-to-machine)。streaming 為 internal-only 轉檔引擎(T4)。 @@ -1030,7 +1014,7 @@ export function createCoordinatorApp(overrides: Partial = {}) response.status(500).json({ detail: error instanceof Error ? error.message : String(error) }); }); - registerReviewNamespace(io, store, eventLog, bimControlClient); + registerReviewNamespace(io, store, eventLog); return { app, server, io, config, store, eventLog }; } @@ -1105,14 +1089,6 @@ async function safeArtifacts(client: BimControlClient, modelVersionId: string): } } -async function safeIssues(client: BimControlClient, modelVersionId: string) { - try { - return await client.getReviewIssues(modelVersionId); - } catch { - return []; - } -} - async function proxyConversionService( response: express.Response, conversionApiBase: string, diff --git a/bim-review-coordinator/src/public/dev-console.html b/bim-review-coordinator/src/public/dev-console.html index 351d89330..e95b17254 100644 --- a/bim-review-coordinator/src/public/dev-console.html +++ b/bim-review-coordinator/src/public/dev-console.html @@ -159,15 +159,13 @@
BIM 審查雲端 Demo|審查協調 (Review Coordinator)
-
步驟 ③ / 5
+
步驟 ③ / 3
@@ -353,47 +351,6 @@

加入即時頻道

-
-
-
- ④ -

標示問題位置

-
coordinator → viewer → streaming server
-
- 需 viewer 開啟 -
-

送出 highlightRequest,示範審查人員點到問題元件後,如何同步到瀏覽器審查端。

-
觀察效果:event feed 出現「送出問題高亮」;5173 viewer 會收到 Socket.IO 事件並可再送 DataChannel 高亮。
- -
- -
-
-
- ⑤ -

建立審查標註

-
coordinator → company-cloud callback outbox
-
- 可操作 -
-

把審查意見變成 annotation event,示範哪些資料會被視為 review metadata。

-
觀察效果:event feed 出現「建立審查標註」與 ack;後續由 outbox 追蹤雲端回寫狀態。
- -
- -
-
-
- ② -

轉檔資料流

-
external IFC Worker → coordinator → streaming
-
- 部分未完成 -
-

展示 IFC 來源檔如何產生 USDC 與 element_mapping.json,並讓產物 URL 回到 session。

-
步驟 ①/② 由外部 IFC Worker 送出 ifc-ready;這裡只展示 session 端如何消費 ready artifact binding。
- -
@@ -465,9 +422,6 @@

Socket.IO /review

- - -

Raw HTTP response

@@ -988,40 +942,6 @@

Socket events

} } - async function guidedHighlightIssue() { - setGuidedOutcome('warn', '正在同步問題高亮', 'coordinator 會廣播 highlightRequest;viewer 可把事件轉成 DataChannel 3D 高亮。'); - try { - await ensureGuidedSocket(); - emitHighlight(); - appendDemoFeed('送出問題高亮', { actor_id: userId.value, target: '/World', issue_id: 'ISSUE-DEMO-001' }); - setGuidedOutcome('ok', '問題高亮事件已送出', '請觀察 Live review events;若 5173 viewer 已開啟,也會在 Socket.IO events 看見 highlightRequest。'); - } catch (error) { - setGuidedOutcome('bad', '問題高亮失敗', error instanceof Error ? error.message : String(error)); - } - } - - async function guidedAnnotation() { - setGuidedOutcome('warn', '正在建立審查標註', 'annotation 會經 coordinator 進入 fake review metadata 流程,作為 Step ⑤ 回寫示範。'); - try { - await ensureGuidedSocket(); - emitAnnotation(); - appendDemoFeed('建立審查標註', { actor_id: userId.value, target: '/World' }); - setGuidedOutcome('ok', '標註事件已送出', 'event feed 會顯示 annotationCreate;若後端 ack 成功,代表 fake metadata 回寫流程可用。'); - } catch (error) { - setGuidedOutcome('bad', '建立標註失敗', error instanceof Error ? error.message : String(error)); - } - } - - async function guidedConversion() { - setGuidedOutcome('warn', '正在展示轉檔資料流', '會用 dev mock conversion 顯示 IFC → USDC + mapping 的階段與產物 URL。'); - try { - await startCoordinatorConversion(null, true); - setGuidedOutcome('ok', '轉檔展示已完成', '請觀察 Step ② 卡片的進度、job id、USDC URL 與 mapping URL;未完成的 storage upload 已明確標示。'); - } catch (error) { - setGuidedOutcome('bad', '轉檔展示失敗', error instanceof Error ? error.message : String(error)); - } - } - function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } @@ -1099,10 +1019,6 @@

Socket events

if (m === 'disconnect') return '即時頻道中斷'; if (m.startsWith('joinSession')) return '使用者加入審查會議'; if (m.startsWith('leaveSession')) return '使用者離開審查會議'; - if (m.startsWith('highlightRequest')) return '送出問題高亮'; - if (m.startsWith('selectionUpdate')) return '同步元件選取'; - if (m.startsWith('annotationCreate')) return '建立審查標註'; - if (m.startsWith('annotationCreated')) return '標註已寫入主資料庫'; if (m.startsWith('presenceUpdated')) return '與會者列表更新'; if (m.startsWith('heartbeat')) return '心跳'; if (m === 'clientWarning') return '前端提示'; diff --git a/bim-review-coordinator/src/public/dev-console.js b/bim-review-coordinator/src/public/dev-console.js index 04b0a5573..cfabf910b 100644 --- a/bim-review-coordinator/src/public/dev-console.js +++ b/bim-review-coordinator/src/public/dev-console.js @@ -135,43 +135,6 @@ function emitLeave() { emit("leaveSession", baseSocketPayload()); } -function emitHighlight() { - emit("highlightRequest", { - session_id: sessionId.value || "review_session_demo_001", - actor_id: userId.value, - items: [ - { - prim_path: "/World", - ifc_guid: "2VJ3sK9L000fake001", - color: [1, 0, 0, 1], - label: "示範:從 coordinator 協作控制台送出的高亮", - source: "coordinator_dev_console", - issue_id: "ISSUE-DEMO-001" - } - ] - }); -} - -function emitSelection() { - emit("selectionUpdate", { - session_id: sessionId.value || "review_session_demo_001", - actor_id: userId.value, - selected_paths: ["/World"] - }); -} - -function emitAnnotation() { - emit("annotationCreate", { - session_id: sessionId.value || "review_session_demo_001", - actor_id: userId.value, - text: "從 coordinator UI 建立的示範標註", - target: { - usd_prim_path: "/World", - ifc_guid: "2VJ3sK9L000fake001" - } - }); -} - function emitHeartbeat() { emit("heartbeat", { session_id: sessionId.value || "review_session_demo_001", actor_id: userId.value }); } diff --git a/bim-review-coordinator/src/services/bimControlClient.ts b/bim-review-coordinator/src/services/bimControlClient.ts index 23f9b2c64..bc1f6096d 100644 --- a/bim-review-coordinator/src/services/bimControlClient.ts +++ b/bim-review-coordinator/src/services/bimControlClient.ts @@ -1,4 +1,4 @@ -import type { Artifact, ReviewIssue } from "../types.js"; +import type { Artifact } from "../types.js"; const DEFAULT_REQUEST_TIMEOUT_MS = 3000; @@ -24,27 +24,6 @@ export class BimControlClient { return asItems(payload, "artifacts"); } - async getReviewIssues(modelVersionId: string): Promise { - const payload = await this.getJson(`/api/model-versions/${modelVersionId}/review-issues`); - return asItems(payload); - } - - async createAnnotation(sessionId: string, payload: unknown): Promise { - this.ensureConfigured(); - const response = await this.fetchWithTimeout( - `${this.baseUrl}/api/review-sessions/${sessionId}/annotations`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }, - ); - if (!response.ok) { - throw new Error(`external control-plane annotation save failed: ${response.status}`); - } - return response.json(); - } - private async getJson(path: string): Promise { this.ensureConfigured(); const response = await this.fetchWithTimeout(`${this.baseUrl}${path}`, { @@ -57,9 +36,6 @@ export class BimControlClient { } private async fetchWithTimeout(url: string, init: RequestInit = {}): Promise { - // The external control-plane may be unavailable in local B-scheme runs. Without an explicit - // timeout, a hung connection (firewalled host, SYN drop, etc.) would - // tie up an entire request and make tests / live endpoints flaky. return fetch(url, { ...init, signal: AbortSignal.timeout(this.requestTimeoutMs) }); } diff --git a/bim-review-coordinator/src/socket/reviewNamespace.ts b/bim-review-coordinator/src/socket/reviewNamespace.ts index bbf0e831c..a4eb1a428 100644 --- a/bim-review-coordinator/src/socket/reviewNamespace.ts +++ b/bim-review-coordinator/src/socket/reviewNamespace.ts @@ -1,5 +1,4 @@ -import type { Server, Socket } from "socket.io"; -import type { BimControlClient } from "../services/bimControlClient.js"; +import type { Server } from "socket.io"; import type { EventLog } from "../services/eventLog.js"; import { isSafeSessionId, isSessionMutable } from "../services/sessionStore.js"; import type { SessionStore } from "../services/sessionStore.js"; @@ -11,16 +10,14 @@ interface SessionPayload { [key: string]: unknown; } -type AckResponse = - | { ok: true; [key: string]: unknown } - | { ok: false; error: string }; - export function registerReviewNamespace( io: Server, store: SessionStore, eventLog: EventLog, - bimControlClient: BimControlClient, ): void { + // eventLog 保留為 future lifecycle audit 拓展,join/leave/heartbeat 路徑暫未直接寫入。 + void eventLog; + const namespace = io.of("/review"); namespace.on("connection", (socket) => { @@ -44,34 +41,10 @@ export function registerReviewNamespace( ack?.({ ok: true, session }); }); - socket.on("highlightRequest", (payload: SessionPayload, ack?: (response: unknown) => void) => { - ack?.(broadcastSessionEvent(socket, store, eventLog, "highlightRequest", payload)); - }); - - socket.on("selectionUpdate", (payload: SessionPayload, ack?: (response: unknown) => void) => { - ack?.(broadcastSessionEvent(socket, store, eventLog, "selectionUpdate", payload)); - }); - socket.on("heartbeat", (payload: SessionPayload, ack?: (response: unknown) => void) => { ack?.({ ok: true, received_at: new Date().toISOString(), session_id: payload.session_id }); }); - socket.on("annotationCreate", async (payload: SessionPayload, ack?: (response: unknown) => void) => { - try { - const sessionCheck = validateExistingSession(store, payload); - if (!sessionCheck.ok) { - ack?.(sessionCheck); - return; - } - const sessionId = sessionCheck.sessionId; - const saved = await bimControlClient.createAnnotation(sessionId, payload); - recordAndBroadcast(socket, eventLog, sessionId, "annotationCreated", { ...payload, saved }); - ack?.({ ok: true, saved }); - } catch (error) { - ack?.({ ok: false, error: error instanceof Error ? error.message : String(error) }); - } - }); - socket.on("leaveSession", (payload: SessionPayload, ack?: (response: unknown) => void) => { const sessionCheck = validateExistingSession(store, payload); if (!sessionCheck.ok) { @@ -91,30 +64,6 @@ export function registerReviewNamespace( }); } -function broadcastSessionEvent( - socket: Socket, - store: SessionStore, - eventLog: EventLog, - type: string, - payload: SessionPayload, -): AckResponse { - const sessionCheck = validateExistingSession(store, payload); - if (!sessionCheck.ok) return sessionCheck; - recordAndBroadcast(socket, eventLog, sessionCheck.sessionId, type, payload); - return { ok: true }; -} - -function recordAndBroadcast( - socket: Socket, - eventLog: EventLog, - sessionId: string, - type: string, - payload: SessionPayload, -): void { - eventLog.append(sessionId, type, payload); - socket.to(sessionId).emit(type, payload); -} - function validateExistingSession( store: SessionStore, payload: SessionPayload, diff --git a/bim-review-coordinator/src/types.ts b/bim-review-coordinator/src/types.ts index 969372c3f..8f487689f 100644 --- a/bim-review-coordinator/src/types.ts +++ b/bim-review-coordinator/src/types.ts @@ -114,14 +114,6 @@ export interface Artifact { quality_metrics_summary?: ConversionQualityMetricsSummary | null; } -export interface ReviewIssue { - issue_id: string; - severity: string; - title: string; - usd_prim_path?: string | null; - ifc_guid?: string | null; -} - /** * B-scheme(local-coordinator-ifc-ready-intake-boundary T3):外部落地端 IFC * Worker → coordinator `POST /api/external/ifc-ready` 的事件。契約權威: diff --git a/bim-review-coordinator/tests/dev-console.test.ts b/bim-review-coordinator/tests/dev-console.test.ts index 87e255b36..6014e0595 100644 --- a/bim-review-coordinator/tests/dev-console.test.ts +++ b/bim-review-coordinator/tests/dev-console.test.ts @@ -47,7 +47,11 @@ describe("coordinator dev console", () => { expect(ui.text).toContain("/api/external/ifc-ready"); expect(ui.text).toContain("getLatestConversionReviewPayload"); expect(ui.text).toContain("互動效果實驗室"); - expect(ui.text).toContain("guidedHighlightIssue"); + // remove-conflict-review-from-fast-mvp:衝突檢討(guidedHighlightIssue / guidedAnnotation / guidedConversion)已自 fast MVP 移除 + expect(ui.text).not.toContain("guidedHighlightIssue"); + expect(ui.text).not.toContain("guidedAnnotation"); + expect(ui.text).not.toContain("emitHighlight()"); + expect(ui.text).toContain("步驟 ③ / 3"); expect(ui.text).toContain("工程參數與 Raw API / Socket controls"); expect(consolePage.status).toBe(200); expect(consolePage.text).toContain("/api/review-sessions"); diff --git a/bim-review-coordinator/tests/sessions.test.ts b/bim-review-coordinator/tests/sessions.test.ts index 211820d17..c3260427a 100644 --- a/bim-review-coordinator/tests/sessions.test.ts +++ b/bim-review-coordinator/tests/sessions.test.ts @@ -514,48 +514,29 @@ describe("bim-review-coordinator", () => { expect(malformed.status).toBe(400); }); - it("rejects socket session operations when the session id is missing, invalid, or unknown", async () => { + it("rejects socket joinSession when the session id is missing, invalid, or unknown", async () => { const app = makeApp(); const client = await connectReviewSocket(await listen(app)); - const missingHighlight = await emitWithAck<{ ok: boolean; error?: string }>(client, "highlightRequest", { + const missingJoin = await emitWithAck<{ ok: boolean; error?: string }>(client, "joinSession", { user_id: "dev_user_001", }); - expect(missingHighlight).toEqual({ ok: false, error: "Missing session_id" }); + expect(missingJoin).toEqual({ ok: false, error: "Missing session_id" }); - const invalidSelection = await emitWithAck<{ ok: boolean; error?: string }>(client, "selectionUpdate", { + const invalidJoin = await emitWithAck<{ ok: boolean; error?: string }>(client, "joinSession", { session_id: "..\\secrets", user_id: "dev_user_001", }); - expect(invalidSelection).toEqual({ ok: false, error: "Invalid review session id." }); - - const missingJoin = await emitWithAck<{ ok: boolean; error?: string }>(client, "joinSession", { - session_id: "review_session_missing", - user_id: "dev_user_001", - }); - expect(missingJoin).toEqual({ ok: false, error: "Review session not found." }); + expect(invalidJoin).toEqual({ ok: false, error: "Invalid review session id." }); - const missingHighlightSession = await emitWithAck<{ ok: boolean; error?: string }>(client, "highlightRequest", { + const unknownJoin = await emitWithAck<{ ok: boolean; error?: string }>(client, "joinSession", { session_id: "review_session_missing", user_id: "dev_user_001", }); - expect(missingHighlightSession).toEqual({ ok: false, error: "Review session not found." }); + expect(unknownJoin).toEqual({ ok: false, error: "Review session not found." }); expect(fs.existsSync(path.join(activeRoot as string, "events", "review_session_missing.jsonl"))).toBe(false); }); - it("rejects socket annotation persistence for unknown sessions before calling downstream APIs", async () => { - const app = makeApp(); - const client = await connectReviewSocket(await listen(app)); - - const response = await emitWithAck<{ ok: boolean; error?: string }>(client, "annotationCreate", { - session_id: "review_session_missing", - user_id: "dev_user_001", - text: "檢查消防區劃", - }); - - expect(response).toEqual({ ok: false, error: "Review session not found." }); - }); - it("rejects socket joins for closed sessions", async () => { const app = makeApp(); const created = await request(app.app) diff --git a/compose.host-kit.yml b/compose.host-kit.yml index 3f3523825..13972edca 100644 --- a/compose.host-kit.yml +++ b/compose.host-kit.yml @@ -24,4 +24,4 @@ services: VITE_COORDINATOR_API_BASE: "${WEB_VIEWER_COORDINATOR_API_BASE:-http://127.0.0.1:${COORDINATOR_PORT:-8004}}" VITE_COORDINATOR_SOCKET_URL: "${WEB_VIEWER_COORDINATOR_SOCKET_URL:-http://127.0.0.1:${COORDINATOR_PORT:-8004}}" ports: !override - - "${VIEWER_PORT:-5173}:5173" + - "127.0.0.1:${VIEWER_PORT:-5173}:5173" diff --git a/docs/superpowers/specs/2026-05-21-fast-mvp-loop-overall-design.md b/docs/superpowers/specs/2026-05-21-fast-mvp-loop-overall-design.md new file mode 100644 index 000000000..230151ff4 --- /dev/null +++ b/docs/superpowers/specs/2026-05-21-fast-mvp-loop-overall-design.md @@ -0,0 +1,365 @@ +# Fast MVP Loop — Overall Design (Brainstorming Spec) + +> **文件性質**:brainstorming `superpowers/brainstorming` skill 階段的 design artifact,不是 OpenSpec spec(authoritative spec 在 `openspec/changes//`)。 +> **日期**:2026-05-21 +> **發起人**:使用者(`xshiujj@gmail.com`) +> **brainstorming session ID**:`0bd5b305-8e62-4efe-9de3-96f575c93367` +> **執行模式**:`/goal` 驅動,使用者授權 implementation 階段全自動(memory `agent-full-automation-during-implementation`) +> **語言**:繁體中文(技術 identifier / 程式碼 / log / parser header 保留原文) + +--- + +## 0. Goal in one sentence + +> 接 IFC ifc-ready API → coordinator 同步下載 IFC + 派工轉檔 → 轉檔成功 → 對外輸出一條 viewer 連結 → client 點連結直接全螢幕看 3D stream。同時把現有「衝突檢討 / 標示問題位置 / 建立審查標註」功能移除。 + +--- + +## 1. Architecture & Flow + +### 1.1 Happy path 全圖 + +``` +Postman ──POST ifc-ready ─→ coordinator ─GET MinIO ─→ Storage + | (sync) | + | ← bytes ── | + | save → /workspace/storage/ifc-cache//source.ifc + | + 200 { ifc_ready_job_id, download_status:"downloaded", + message:"IFC 已下載", conversion_job_id, conversion_status:"queued" } + | + dispatch internal /api/conversions { local_path, host_local_path } + ↓ + streaming-server (host-native, 49101) + open same path, IFC → USDC, callback ready + ↓ + coordinator ingestConversionReport (status=ready) + ├─ enqueue metadata-only callback outbox (沿用既有) + └─ autoCreateOrActivateSession → web_view_session_id (lwv_*) + ↓ + GET /api/external/ifc-ready/{jobId} (Postman polls) + ↓ + 200 { download_status:"downloaded", conversion_status:"ready", + viewer_url:"http://192.168.10.105:8004/ui/open?session=lwv_abc", + web_view_session_id:"lwv_abc" } + | + click viewer_url + ↓ + coordinator GET /ui/open?session=lwv_abc + → server-side redirect to http://127.0.0.1:5173/?session=lwv_abc + (同台 host 上的瀏覽器才連得到 5173,LAN 用 8004 入口) + ↓ + viewer 解析 ?session= → auto-attach,跳過 NVIDIA Forms + → GET coordinator /api/local-web-view/sessions/{id} → stream config + → WebRTC to streaming-server 49100 → render + → 全螢幕 3D stream + 邊框 HUD +``` + +### 1.2 Edge cases + +- **下載失敗**:coordinator → 502 `download_failed`,job state `download_status:"failed"`,不 dispatch +- **下載 timeout**:預設 600s,可由 env `IFC_DOWNLOAD_TIMEOUT_SECONDS` 調 +- **轉檔失敗**:`conversion_status:"failed"`,viewer_url 不會出現,polling 看得到 failure 原因 +- **Idempotent replay**:同 `idempotency_key` 或 worker compat 派生 key 重打 → 200 reuse,不重下、不重派工 +- **Kit busy(後到取代前到)**:Kit signaling 1:1 自然處理;HUD 顯示「另有 viewer 已接管」 +- **MinIO 不可達(192.168.20.234 dev 機可能斷)**:implementation 用本機 fixture `storage/許良宇圖書館建築_2026.ifc`,Postman env 切換 `ifc_path` 變數 + +--- + +## 2. Approach B — 兩個小 change 序列依賴 + +``` +┌──────────────────────────────────────────────────┐ +│ Change 1: remove-conflict-review-from-fast-mvp │ predecessor +│ ── 純減法 + 一行 compose ── │ +└──────────────────────────────────────────────────┘ + merge & archive + ↓ NoSuccessorWhilePredecessorOpen gate cleared +┌──────────────────────────────────────────────────┐ +│ Change 2: fast-ifc-link-demo-loop │ successor +│ ── 純加法 + UI 重設 + Postman collection ── │ +└──────────────────────────────────────────────────┘ +``` + +理由: +1. 「刪 conflict review」獨立性高、blast radius 清晰可審;baseline 變乾淨,successor 在乾淨 map 上開發。 +2. 「fast link handoff」是純加法 + UI 重設,Reviewer 容易判斷加了什麼。 +3. 符合 `AGENTS.md` OpenSpec workflow NoSuccessorWhilePredecessorOpen gate。 +4. 若中途翻案,predecessor 已 land 不需回滾整個故事。 + +--- + +## 3. Change 1 — remove-conflict-review-from-fast-mvp + +完整 spec 見 `openspec/changes/remove-conflict-review-from-fast-mvp/`(同 worktree 內): +- `proposal.md`:why / what changes / capabilities / impact +- `design.md`:刪 code vs feature flag 取捨、grep 命令、verification 5 級、blast radius +- `tasks.md`:14 章節、~80 個 task checkbox,`/goal` 視為參考路徑 +- `specs/review-session-request-lifecycle/spec.md`:## REMOVED Requirements / ## MODIFIED Requirements(實際 requirement 名 implementation 階段 grep 填入) + +### 3.1 範圍摘要 + +| 區塊 | 動作 | +|---|---| +| coordinator `src/socket/reviewNamespace.ts` | 刪 highlight/selection/annotation/issueFocus event handlers | +| coordinator `src/services/sessionStore.ts` | 刪 issue-related fields | +| coordinator `src/public/dev-console.html` + `.js` | step bar 5 步改 3 步、刪三張 guided card、刪 emit 按鈕 + functions | +| viewer `src/components/IssuePanel.tsx` + `EventLogPanel.tsx` + `types/issues.ts` | 整檔刪 | +| viewer `src/clients/bimControlClient.ts` + `reviewSocket.ts` + `types/streamMessages.ts` | issue / annotation / highlight 相關移除 | +| viewer `src/components/DemoControlPanel.tsx` | 移除 issue 樹 + IssuePanel/EventLogPanel slot | +| `compose.host-kit.yml` `viewer.ports` | `"5173:5173"` → `"127.0.0.1:5173:5173"` | +| `AGENTS.md` §5.4 / §5.5 / §7.4 / §8.x | 標記「已退役 - fast MVP 不包含」 | +| `bim-review-coordinator/CLAUDE.md` | review session 內 issue/annotation 描述同步 | + +### 3.2 OpenSpec impact + +- MODIFIED `review-session-request-lifecycle`:## REMOVED Requirements 移除 issue/highlight/annotation/selection scenarios +- 沒有 ADD / removed capability + +--- + +## 4. Change 2 — fast-ifc-link-demo-loop(草圖) + +完整 spec **待 Change 1 archived 後**才落地到 `openspec/changes/fast-ifc-link-demo-loop/`。本節為 successor design 草圖,供整體 brainstorming 引用。 + +### 4.1 API contract + +#### `POST /api/external/ifc-ready` — 行為改變(202 → 同步 200) + +```json +// Request (worker compat,Postman 用) +{ + "status": "ifc_ready", + "ifc_path": "http://192.168.20.234:9000/bim-control/899/xxx/model.ifc", + "project_id": "899", + "version": "xxx", + "task_id": "task_001" +} + +// Response 200(等下載完才回) +{ + "ifc_ready_job_id": "ifr_2026_...", + "download_status": "downloaded", + "message": "IFC 已下載至本地共享卷,轉檔已派工", + "local_path": "/workspace/storage/ifc-cache/ifr_2026_.../source.ifc", + "conversion_job_id": "stream_conv_...", + "conversion_status": "queued", + "correlation_id": "...", + "idempotent_replay": false +} +``` + +- timeout 預設 600s +- 失敗:502 `download_failed`,job 標 `download_status:"failed"`,不 dispatch +- Idempotent replay 既存 job 直接 200 reuse,不重下 + +#### `GET /api/external/ifc-ready/:jobId` — 新增欄位 + +```json +{ + "ifc_ready_job_id": "...", + "download_status": "pending|downloading|downloaded|failed", + "conversion_job_id": "...", + "conversion_status": "queued|running|ready|failed", + "viewer_url": null | "http://192.168.10.105:8004/ui/open?session=lwv_abc", + "web_view_session_id": null | "lwv_abc", + "...既有欄位" +} +``` + +conversion ready 時 coordinator 自動 spawn local-web-view session(沿用既有 `autoCreateOrActivateSession`,在 `ingestConversionReport` ready 分支內 hook),並把 `viewer_url` 寫進 job state。 + +#### 新 endpoint `GET /ui/open?session=` + +server-side redirect 到 `http://127.0.0.1:5173/?session=`。理由:viewer 已綁 127.0.0.1(Change 1 完成),LAN 連不到;coordinator 在同 host 收到 LAN client 的 redirect 後,讓 client 端 browser 改連 viewer。最終使用者必須**在同台 host 開 browser**(fast MVP 邊界,不做反向代理)。 + +### 4.2 Shared volume + +``` +compose.runtime-manager.yml (coordinator 已有 ./storage:/workspace/storage) + coordinator 寫:/workspace/storage/ifc-cache//source.ifc + +streaming-server (host-native) 從 host 角度讀: + C:\Repos\active\iot\AI-BIM-governance\storage\ifc-cache\\source.ifc + +coordinator → streaming dispatch payload: +{ + "ifc_ready_job_id": "...", + "external_model_version_id": "...", + "local_path": "/workspace/storage/ifc-cache//source.ifc", + "host_local_path": "C:\\Repos\\...\\storage\\ifc-cache\\\\source.ifc", + "source_ifc_ref": "..." // fallback +} +``` + +streaming-server 設 `STORAGE_HOST_ROOT` env 把 container path 轉成 host path。 + +### 4.3 邊界文字 carve-out(`AGENTS.md` §3.4 + coordinator CLAUDE.md MUST NOT) + +``` +> 例外(2026-05-21 fast-ifc-link-demo-loop):允許 coordinator 在 ifc-ready intake +> 同步階段,將外部 IFC 下載至本地 shared volume 路徑 +> `storage/ifc-cache//source.ifc`,作為 dispatch streaming-server +> 前的臨時通道快取。coordinator 不視為該 IFC bytes 的資料權威;權威仍屬外部公司 +> 雲端 control-plane(`external_model_version_id` 參照),streaming-server 為 +> conversion authority。 +``` + +### 4.4 `/ui` 重做(3 卡片單欄垂直) + +``` ++--------------------------------------------------------------+ +| BIM 審查雲端 / 快速 Demo | ++--------------------------------------------------------------+ +| ① 提交 IFC source(模擬外部 ifc-ready) | +| ifc_path [_________________________________________] | +| project_id [____] version [____] task_id [____] | +| [ 送出 ifc-ready ] | ++--------------------------------------------------------------+ +| ② 下載 + 轉檔進度(每 5s 自動 polling) | +| ● download_status: downloaded | +| ● conversion_status: running (45s) | +| ● viewer_open_ready: false | ++--------------------------------------------------------------+ +| ③ 開啟 viewer | +| viewer_url: http://192.168.10.105:8004/ui/open?... | +| [ 複製 ] [ 開啟 viewer ] | ++--------------------------------------------------------------+ +``` + +完全替換現有 5 卡 + 互動實驗室 + raw API/Socket 區。 + +### 4.5 viewer 重做(query-string auto-attach + 全螢幕) + +`web-viewer-sample/src/main.tsx`:解析 `?session=lwv_xxx` → 跳過 NVIDIA Forms,直接走 auto-attach helper → GET coordinator `/api/local-web-view/sessions/{id}` → 拿 stream config → render ``。 + +`App.tsx` / `AppStream.tsx`:全螢幕版面 +- headerHeight 36px,bottom 36px HUD +- video element 填中央 +- top HUD:project name + session id + 重連 +- bottom HUD:kit instance id + WebRTC status + fps + diagnostic + +### 4.6 Postman collection + +`docs/postman/fast-ifc-link-demo.postman_collection.json`(v2.1) + +``` +├── Submit ifc-ready POST {{coordinator_base_url}}/api/external/ifc-ready +│ Body: worker compat +│ Tests: assert 200; capture ifc_ready_job_id +│ Timeout: 600s +├── Poll ifc-ready job GET .../api/external/ifc-ready/{{ifc_ready_job_id}} +│ Tests: viewer_url null → setNextRequest(self) + 5s sleep +│ else capture viewer_url + stop loop +└── Open viewer (info only) GET {{viewer_url}} + Pre-request: console.log viewer_url + (使用者手動打開,或 mcp__claude-in-chrome 接力) +``` + +Environment:`coordinator_base_url`、`webhook_secret`、`ifc_path`、`project_id`、`version`、`task_id` + +`docs/postman/README.md`:導入步驟、環境設定。 + +### 4.7 OpenSpec spec deltas(預估) + +- `local-coordinator-ifc-ready-intake-boundary` MODIFIED:sync-download requirement + response body fields + viewer_url surfacing in job GET +- `conversion-webhook-lifecycle` MODIFIED:dispatch payload includes `local_path` / `host_local_path` +- `demo-fast-mvp-orchestration` MODIFIED:3 步 runbook + Postman collection +- `documentation-source-of-truth` MODIFIED:邊界 carve-out 寫入 + +--- + +## 5. /goal Acceptance condition + +```yaml +goal_id: fast-mvp-loop # 涵蓋兩個 change +predecessor_chain: + - Change 1: remove-conflict-review-from-fast-mvp (predecessor) + - Change 2: fast-ifc-link-demo-loop (successor, gated) + +acceptance (all true): + Change 1 archived: + - PR merged + squash commit on main + - openspec/changes/archive/-remove-conflict-review-from-fast-mvp/ exists + - openspec/specs/review-session-request-lifecycle/spec.md merged delta + - npx openspec validate --specs --strict → all pass + - docs/plans/AI-BIM-governance-saas-roadmap-2026-05.md updated (AGENTS.md §1.6) + + Change 2 land + archived: + - PR merged + - openspec/changes/archive/-fast-ifc-link-demo-loop/ exists + - openspec/specs/ 對應 capability 更新 + - npx openspec validate --specs --strict → all pass + - roadmap synced + + end-to-end happy path (Change 2 後): + - L1: 所有 npm/python test 綠 + - L2: openspec strict 綠 + - L3: gitnexus_impact 全 LOW/MEDIUM、detect_changes 影響面 = expected + - L4: docker compose up 後 viewer container 只 listen 127.0.0.1:5173, + coordinator 0.0.0.0:8004,49100/49101 host-native 仍 listen + - L5 (mcp__claude-in-chrome 自動化): + - navigate /ui → 看到 3 卡單欄 + - 填 ifc_path + project_id + version + task_id,點「送出 ifc-ready」 + - 卡 2 polling 顯示 downloaded → ready + - 卡 3 viewer_url 出現,點「開啟 viewer」自動跳轉 + - viewer 進入全螢幕 stream + HUD + - console 無 unhandled error + - gif_creator 錄整段 → 附 PR description + - Postman collection runner 獨立跑通一次 + +stop_conditions (中斷給人類): + - GitNexus impact HIGH/CRITICAL + - 邊界 carve-out 引發 reviewer 異議 + - 連續 N 次 auto-fix 失敗無明確 root cause + - 真實 IFC 來源 (192.168.20.x) 不可達且使用者沒授權替代 fixture +``` + +`/goal` 預期使用方式(SKILL.md 未找到,以下為假設;真實 schema 在 implementation 階段 align): + +``` +/goal fast-mvp-loop +# 或 +/goal "從 docs/superpowers/specs/2026-05-21-fast-mvp-loop-overall-design.md §5 acceptance 為終止條件" +``` + +--- + +## 6. Memory references + +- `kit-gpu-render-needs-windows-native`:streaming-server 必須 host-native +- `webrtc-1on1-entrypoint-via-coordinator-ui`:viewer 是 1:1 endpoint 不可暴露 → 本 design 落實為 `compose.host-kit.yml` 127.0.0.1 bind + viewer_url 走 coordinator redirect +- `agent-full-automation-during-implementation`:implementation 階段 agent 一路操作,deny 換等價路徑 +- `opsx-skill-placeholder-bug`:部分 opsx skill 的 bash hook 壞掉 → 手動跑底層 CLI +- `opsx-worktree-closeout-gotchas`:gh pr merge 從 worktree exit=1 但 merge 成功;GitNexus detect-changes 看不到 linked worktree staged → 用 git diff --stat 佐證 +- `gitbash-windows-bat-invocation`:.bat 走 .ps1 wrapper + Start-Process 完整路徑 + +--- + +## 7. Brainstorming session 紀錄 + +- Visual Companion offer:使用者打開 viewer 截圖,但實際 visual companion 未啟用;設計全程文字 / ASCII 進行 +- 提出的關鍵設計問題與選擇: + 1. ifc-ready 下載:**A 同步下載完才回 200**(其他選項:async + GET polling、streaming-server 自己拉) + 2. viewer URL 取得:**A Postman 輪詢 GET job URL 到 viewer_url 出現**(其他:callback URL、長 hold sync) + 3. 衝突檢討刪除:**A 直接刪 code 乾淨不可逆**(其他:feature flag、archive folder) + 4. viewer 介面:**A 全螢幕 stream + 邊框 HUD**(其他:三欄保留 minus 衝突檢討、極簡無 HUD) + 5. `/ui` 介面:**A 單欄垂直流程 + 試控台 + polling + viewer 連結卡**(其他:現有卡片 minus 衝突檢討、儀表板) + 6. IFC handoff:**A Shared volume**(其他:stream-through、coordinator 不下載) + 7. 預設 design 段(viewer URL token、viewer 127.0.0.1 bind、bim-control 真實對接):皆採 fast MVP 簡化版,review 時可推翻 +- Approach 選擇:**B 兩個小 change 序列依賴** +- Spec storage:**OpenSpec change folder**(本檔為 brainstorming 副產品) + +--- + +## 8. Next steps + +- [x] 8.1 brainstorming Section 1 / 2 / 3 設計核可 +- [x] 8.2 spec storage 決定:OpenSpec change folder +- [x] 8.3 Change 1 OpenSpec change folder 落地(proposal / design / tasks / spec delta) +- [x] 8.4 本檔 overall design doc 落地 +- [ ] 8.5 等使用者 review 整份 brainstorming spec(本檔 + Change 1 OpenSpec change folder) +- [ ] 8.6 brainstorming skill 規定 → invoke `writing-plans` skill(brainstorming terminal state) +- [ ] 8.7 `/goal` 啟動 Change 1 implementation(由 agent 全程自動跑到 archive) +- [ ] 8.8 Change 1 archived → 切 Change 2 worktree → 重複 brainstorming → writing-plans → `/goal` +- [ ] 8.9 兩 change 全 archived + roadmap synced + fast MVP loop end-to-end demo 通 → goal 達成 diff --git a/openspec/changes/remove-conflict-review-from-fast-mvp/design.md b/openspec/changes/remove-conflict-review-from-fast-mvp/design.md new file mode 100644 index 000000000..27ba76776 --- /dev/null +++ b/openspec/changes/remove-conflict-review-from-fast-mvp/design.md @@ -0,0 +1,134 @@ +# Design — remove-conflict-review-from-fast-mvp + +> Brainstorming 整體 design 見同 worktree `docs/superpowers/specs/2026-05-21-fast-mvp-loop-overall-design.md`。本檔聚焦此 change 的技術決策。 + +## 1. 決策:刪 code(不 feature flag、不 archive folder) + +| 選項 | 取捨 | 結論 | +|---|---|---| +| A. 直接刪 code | 乾淨,不可逆;diff 大但 reviewer 容易判斷;baseline 變乾淨,successor 在乾淨 map 上開發 | **採用** | +| B. Feature flag(env `ISSUE_REVIEW_ENABLED=false`) | 可逆,但死碼留著;successor UI 重做要繞著走;test 要 split feature on/off | 不採用 | +| C. 移到 `docs/archive/`(`git mv`) | git history 保留;source tree 不留;需要拿回時 grep 看得到 | 不採用(成本介於 A/B 之間但沒有 B 的可逆性優勢) | + +理由:使用者明確指示「現階段只需要能做到接 API 轉檔 + 看畫面,衝突檢討先移除」,且 successor 將重設 viewer / UI,留任何死碼都會增加 successor 的 blast radius。OpenSpec spec 內的 REMOVED requirement 已記下歷史,需要時可從 git history `git log -- web-viewer-sample/src/components/IssuePanel.tsx` 找回。 + +## 2. 刪除範圍 — implementation 階段 grep 命令(精確化) + +設計階段不憑空猜實際 handler / method / field 名;以下命令 implementation 階段執行,grep 結果寫進 `tasks.md` 對應 item: + +```bash +# coordinator side +grep -rn -E "highlight|selection|annotation|issueFocus" bim-review-coordinator/src +grep -rn -E "highlight|selection|annotation|issue" bim-review-coordinator/tests + +# viewer side +grep -rn -E "IssuePanel|EventLogPanel|highlightRequest|annotationCreate|issueFocus" web-viewer-sample/src +grep -rn -E "issue|annotation|highlight" web-viewer-sample/src/types +grep -rn -E "issue|annotation|highlight" web-viewer-sample/tests + +# docs +grep -rn -E "issue|annotation|highlight|collaboration" AGENTS.md +grep -rn -E "issue|annotation" bim-review-coordinator/CLAUDE.md web-viewer-sample/CLAUDE.md +``` + +## 3. Compose `viewer.ports` 改 127.0.0.1 bind + +```diff + viewer: + environment: !override + VITE_COORDINATOR_API_BASE: "${WEB_VIEWER_COORDINATOR_API_BASE:-http://127.0.0.1:${COORDINATOR_PORT:-8004}}" + VITE_COORDINATOR_SOCKET_URL: "${WEB_VIEWER_COORDINATOR_SOCKET_URL:-http://127.0.0.1:${COORDINATOR_PORT:-8004}}" + ports: !override +- - "${VIEWER_PORT:-5173}:5173" ++ - "127.0.0.1:${VIEWER_PORT:-5173}:5173" +``` + +驗收:rebuild + up viewer container 後 `netstat -ano | grep :5173` 應只見 `127.0.0.1:5173`,**不**見 `0.0.0.0:5173` / `[::]:5173`(Docker backend `com.docker.backend.exe` listening pattern 對應 loopback 而非 wildcard)。 + +## 4. OpenSpec ## REMOVED Requirements 寫法 + +`openspec/specs/review-session-request-lifecycle/spec.md` 既有 requirement 名稱以 implementation 階段 grep 為準。本 change 內 `specs/review-session-request-lifecycle/spec.md` 用以下 parser-friendly format(預估;確切 requirement 名由 grep 該 spec 命中後寫入): + +```markdown +## REMOVED Requirements + +### Requirement: <既有 requirement 名稱> + +**Reason:** fast MVP demo 不展示衝突檢討功能,viewer / `/ui` 重做後無 UI 插槽承接;本 requirement 隨 conflict review 功能整體退役。 + +**Migration:** 無 — 沒有外部 consumer 依賴 issue/highlight/annotation Socket.IO event 與 viewer IssuePanel。如未來重新引入,以新 OpenSpec change form ADD requirements。 + +(可能多筆 ### Requirement,看 spec 命中數) +``` + +## 5. Verification 5 級(`/goal` acceptance) + +``` +L1 unit: + cd bim-review-coordinator && npm run verify + cd web-viewer-sample && npm run build && npm run test:session-first + python -m pytest tests -p no:cacheprovider + cd bim-streaming-server && python -m pytest tests/test_conversion_authority_api.py -q +L2 spec: + npx openspec validate --specs --strict # 全綠 +L3 graph: + gitnexus_impact 對所有改動 symbol # 無 HIGH/CRITICAL + gitnexus_detect_changes() # 影響面 = expected +L4 container & network: + docker compose -f compose.runtime-manager.yml -f compose.host-kit.yml \ + --env-file .env.web-plane.host-kit.example up -d --build viewer + netstat -ano | grep :5173 # 只 127.0.0.1 + netstat -ano | grep :8004 # 仍 0.0.0.0(coordinator 對 LAN) + docker exec ai-bim-web-plane-host-kit-coordinator-1 node -e \ + "fetch('http://127.0.0.1:8004/health').then(r=>r.text()).then(console.log)" + # → "ok" +L5 真實 UI / client 操作(mcp__claude-in-chrome): + (A) coordinator /ui 視覺驗收 + - navigate http://127.0.0.1:8004/ui + - read_page 確認 step bar = 3 顆數字(不再有「標記問題」「紀錄回寫」) + - read_page 全文 grep「標示問題」「建立審查標註」「紀錄回寫」= 0 hit + - find/click「建立示範審查會議」按鈕 → 卡片更新、session 出現 + - read_console_messages pattern「highlight|annotation|issue」= 0 hit + (B) viewer 載入驗收 + - navigate http://127.0.0.1:5173/ + - javascript_tool document.querySelectorAll('[data-testid="issue-panel"]').length === 0 + - read_console_messages 無 import error / undefined module + (C) LAN 隔離 spot-check + - netstat 已視為強證據;外部 IP host-to-host probe 為 best-effort + (D) gif_creator 錄 A 全程,命名 remove-conflict-review-ui-walkthrough.gif → 附 PR description +``` + +## 6. Blast radius / Risk + +| 風險 | 評估 | 緩解 | +|---|---|---| +| `registerReviewNamespace` 改動影響啟動流程 | LOW | direct caller 僅 `createCoordinatorApp`;介面不變只移除內部 handler | +| `sessionStore` schema 變更 | LOW | 移除的是 issue 子欄位,主 schema (`session_id` / `kit_instance` / `artifact_bindings`) 不動 | +| `DemoControlPanel` 移除 IssuePanel/EventLogPanel 插槽後 layout 變空 | LOW | successor 重做整個 viewer 主畫面為全螢幕 stream,空插槽會被覆蓋 | +| OpenSpec validate `--strict` 失敗 | MEDIUM | REMOVED parser header 必須 `## REMOVED Requirements` 不能拼錯;scenario format 保留 `#### Scenario: ...` 結構 | +| 既有 archive(`2026-05-12-coordinator-session-lifecycle-events-audit`)曾鎖定 issue 相關 lifecycle event | LOW | 該 archive 是 audit endpoint 寫入,不阻擋 issue handler 刪除;event log 仍可記錄非 issue 事件 | +| GitNexus index stale | LOW | commit 後 PostToolUse hook 自動 `npx gitnexus analyze`;若 stale 跑 `--embeddings` 重新 build | +| `_bim-control` / `_worker` 已退役但 `bimControlClient` 仍存在 | LOW | 不全刪 client,只刪 issue methods;tests/fakes 內 `cloud_bim_control_api.py` 不動 | + +## 7. Predecessor / Successor coupling + +- 本 change = predecessor;merge + archive 完成後 NoSuccessorWhilePredecessorOpen gate 才開 +- successor = `fast-ifc-link-demo-loop`(brainstorming `2026-05-21-fast-mvp-loop-overall-design.md` Section 3) +- successor 假設本 change 已 archive:viewer 已無 IssuePanel/EventLogPanel slot、coordinator socket 已無 issue handlers、`/ui` 步驟 ④⑤ 已刪、compose viewer 已 127.0.0.1 bind +- 如本 change 中途取消,successor 也要對應調整(回到舊 baseline 上做 net add + delete) + +## 8. 文件對齊範圍(implementation 階段定稿) + +`AGENTS.md` 預估命中段落(以實際 grep 為準): + +- §5.4 Collaboration Flow(annotation event 廣播 sequence) +- §5.5 Review Result Visualization Flow(issue → DataChannel highlight) +- §7.4 Review 資料(`_bim-control` 為 review issue / annotation 權威) +- §8.x「不應做的事」內提到 annotation / issue 段落 + +`bim-review-coordinator/CLAUDE.md`:`review session lifecycle 事件` 段落內若含 annotation/issue 字眼 + +文字策略:**標記「已退役 - fast MVP 不包含」並收斂**,而非整段刪。理由: +- 保留歷史 context,給未來重新引入時參考 +- successor 若重建協作機制以新 OpenSpec change ADD,可在那時 unmark +- 避免 AGENTS.md 章節編號連動變化 diff --git a/openspec/changes/remove-conflict-review-from-fast-mvp/proposal.md b/openspec/changes/remove-conflict-review-from-fast-mvp/proposal.md new file mode 100644 index 000000000..36997d9aa --- /dev/null +++ b/openspec/changes/remove-conflict-review-from-fast-mvp/proposal.md @@ -0,0 +1,87 @@ +## Why + +`AI-BIM-governance` 2026-05-21 後收斂為 fast MVP demo path:外部送 `ifc-ready` → coordinator 同步下載 + dispatch → 轉檔 ready → 一條 viewer 連結點開看 3D stream(對應 successor change `fast-ifc-link-demo-loop`)。在這條 happy path 之外,目前 repo 仍承載「衝突檢討 / 標示問題位置 / 建立審查標註」協作功能,跨 coordinator Socket.IO、viewer `IssuePanel` / `EventLogPanel`、`bimControlClient` issue methods、tests fixture。這套機制: + +- fast MVP 不展示、現場 demo 不使用 +- viewer 即將改為「query-string auto-attach + 全螢幕 stream + 邊框 HUD」(見 successor design),`IssuePanel` / `EventLogPanel` 等元件將失去插槽 +- `/ui` 即將收斂為 3 卡單欄垂直流程(① 提交 / ② 進度 / ③ 連結),目前步驟 ④/⑤ 對應的衝突檢討卡片必須移除 +- 維持留著 = 死碼,viewer/coordinator 重做 UI 時還要繞著它走,blast radius 反而變大 + +同時 `compose.host-kit.yml` viewer.ports 目前綁 `0.0.0.0:5173`,違反「viewer 是 Kit 1:1 endpoint,不可直接暴露給 LAN」邊界(見 memory `webrtc-1on1-entrypoint-via-coordinator-ui`)。本 change 一併把 viewer.ports 改 `127.0.0.1:5173:5173`,在進入 successor 之前清掉這個邊界落差。 + +本 change **純減法 + 一行 compose 修正 + 文件對齊**;不新增任何 production behaviour,blast radius 預估 LOW。 + +## What Changes + +### 刪除 — coordinator (`bim-review-coordinator/`) + +- `src/socket/reviewNamespace.ts`:Socket.IO `/review` namespace 內 `highlight*` / `selection*` / `annotation*` / `issueFocus*` event handlers +- `src/services/sessionStore.ts`:issue-related fields(實際欄位由 implementation 階段 grep 列出) +- `src/types.ts`:對應 type 刪除 +- `src/public/dev-console.html`: + - step bar 由 5 步改 3 步(刪 ④「標記問題 (Mark)」、⑤「紀錄回寫 (Record)」) + - guided cards 刪「標示問題位置」「建立審查標註」「轉檔資料流」三張 + - Raw HTTP/Socket panel 內 `emitHighlight` / `emitSelection` / `emitAnnotation` 按鈕 +- `src/public/dev-console.js`:`guidedHighlightIssue` / `guidedAnnotation` / `emitHighlight` / `emitSelection` / `emitAnnotation` functions 與相關 state +- `tests/`:含 `highlight` / `selection` / `annotation` / `issue` 關鍵字的 spec + +### 刪除 — viewer (`web-viewer-sample/`) + +- `src/components/IssuePanel.tsx`(整檔) +- `src/components/EventLogPanel.tsx`(整檔;dev-console raw 區仍可 debug socket events) +- `src/types/issues.ts`(整檔) +- `src/clients/bimControlClient.ts`:issue-related methods(實際 method 名由 implementation 階段 grep 列出) +- `src/clients/reviewSocket.ts`:highlight/selection/annotation event subscriptions +- `src/types/streamMessages.ts`:issue/annotation DataChannel message types +- `src/components/DemoControlPanel.tsx`:`IssuePanel` / `EventLogPanel` slot + issue 樹 state/handlers +- `src/App.tsx` / `src/AppStream.tsx`:若有 import `IssuePanel` / `EventLogPanel` / `types/issues` 則同步移除 +- `tests/` (含 `__tests__`):同 coordinator 規則 + +### 修改 — Docker compose + +- `compose.host-kit.yml` `viewer.ports`: + - 由 `"${VIEWER_PORT:-5173}:5173"`(等於 `0.0.0.0:5173`,LAN 可達) + - 改 `"127.0.0.1:${VIEWER_PORT:-5173}:5173"`(只 loopback 可達) + +### 修改 — 文件(對齊新邊界) + +- `AGENTS.md`:§5.4 Collaboration Flow、§5.5 Review Result Visualization Flow、§7.4 Review 資料、§8(各 repo「不應做的事」內 annotation/issue 段落)— 標記「已退役 - fast MVP 不包含」並收斂內文;若 successor 將重建協作機制,以新 change 形式重新引入 +- `bim-review-coordinator/CLAUDE.md`:review session 內 issue/annotation 描述同步 +- `web-viewer-sample/CLAUDE.md`(若有命中關鍵字):同上 + +### 明確排除(本 change 不做) + +- 不動 `/api/external/ifc-ready` / conversion / callback outbox / session create 路徑(留給 successor) +- 不動 `bim-streaming-server/` / Kit 任何 source +- 不引入新 production dependency +- 不接真實外部 `bim-control` 雲端(`tests/fakes` 不動,只移除 issue 相關 test fixture) +- 不重設 `/ui` 內容為 3 卡片版面(留給 successor `fast-ifc-link-demo-loop`) +- 不改 viewer 主畫面成全螢幕 stream + HUD(同上) +- 不刪除 `bim-control-revit-intake-facade` / `worker-rvt-ifc-bridge` / `worker-artifact-pipeline` 等已退役 capability(它們已是 archive context,不在本 change 範圍) + +## Capabilities + +### New Capabilities + +- None. + +### Modified Capabilities + +- `review-session-request-lifecycle`:REMOVED issue / highlight / annotation handoff scenarios(實際 requirement 名 implementation 階段 grep 確認後填入 `specs/review-session-request-lifecycle/spec.md` ## REMOVED Requirements 區段) + +### Removed Capabilities + +- None(僅移除 capability 內部分 requirement,capability 本身保留作 fast MVP `joinSession` / `leaveSession` / `presence` / `heartbeat` / `sessionCreated` 使用) + +## Impact + +- Owner repo/folder:`bim-review-coordinator/src/`、`web-viewer-sample/src/`、repo root `compose.host-kit.yml`、`AGENTS.md`、`bim-review-coordinator/CLAUDE.md`(可能 `web-viewer-sample/CLAUDE.md`) +- API:無外部 API contract 變更;Socket.IO `/review` namespace 內部 event 移除(目前無外部 production consumer,僅 repo 內自循使用) +- Data structure:`session.*` 內 issue / annotation fields 移除(grep 列出實際欄位後再決定要不要保留 schema 但停止寫入,還是直接刪) +- Affected integration:none(`tests/fakes` 外部契約面不變) +- Affected symbols(apply 前需 GitNexus impact analysis):`registerReviewNamespace`、`IssuePanel`、`EventLogPanel`、`DemoControlPanel`、`bimControlClient` issue methods、`reviewSocket` subscriptions;預期 GitNexus `risk_level = LOW`(direct caller = `createCoordinatorApp` / `DemoControlPanel`,不改外部 API surface) +- Tests/contracts:`tests/contracts/` 不動;刪 issue / annotation / highlight 相關 test spec +- Dependencies:無新增 / 無移除 npm / python 套件 +- Predecessor / Successor:本 change 為 successor `fast-ifc-link-demo-loop` 的 predecessor;merge + archive 完成後再開 successor branch,維持 NoSuccessorWhilePredecessorOpen gate +- Acceptance verification:5 級(L1 unit / L2 spec validate / L3 GitNexus / L4 container & netstat / L5 真實 UI by `mcp__claude-in-chrome`),詳見 `design.md` §5 +- Brainstorming source-of-truth:`docs/superpowers/specs/2026-05-21-fast-mvp-loop-overall-design.md`(同 worktree 內)Section 2 diff --git a/openspec/changes/remove-conflict-review-from-fast-mvp/specs/review-session-request-lifecycle/spec.md b/openspec/changes/remove-conflict-review-from-fast-mvp/specs/review-session-request-lifecycle/spec.md new file mode 100644 index 000000000..a11c8ae4a --- /dev/null +++ b/openspec/changes/remove-conflict-review-from-fast-mvp/specs/review-session-request-lifecycle/spec.md @@ -0,0 +1,37 @@ +# review-session-request-lifecycle — Spec Delta (remove-conflict-review-from-fast-mvp) + +> Delta against `openspec/specs/review-session-request-lifecycle/spec.md`(本檔僅含本 change 的差異)。 +> 既有 spec 內**沒有專屬於 conflict review / issue handoff 的 requirement**;`highlightRequest` / `selectionUpdate` / `annotationCreated` / `finalReviewEvent` 等 collaboration event 名詞只出現在 lifecycle audit endpoint 的「排除清單」中。因此本 change 採 MODIFIED 方式,在「Coordinator exposes lifecycle event audit log」requirement 內加入 implementation status note,記錄 collaboration events 已不再由 coordinator 產生;排除清單文字保留,以維持 archive / historical compatibility(舊 event log 可能仍含這些 type)。 + +## MODIFIED Requirements + +### Requirement: Coordinator exposes lifecycle event audit log + +The coordinator SHALL expose `GET /api/review-sessions/{session_id}/lifecycle-events` for review session lifecycle audit events. The response MUST contain an `items` array sorted by append order and `sequence`. This endpoint MUST return lifecycle audit events only and MUST NOT include generic collaboration events such as `highlightRequest`, `selectionUpdate`, `annotationCreated`, or `finalReviewEvent`. + +The lifecycle event audit log MUST include at least these lifecycle event types when the corresponding transition occurs: `sessionCreated`, `sessionActive`, `sessionClosing`, `sessionClosed`, and `kitInstanceReleased`. + +> **Implementation status (2026-05-21 fast-mvp loop)**: change `remove-conflict-review-from-fast-mvp` removed the coordinator `highlightRequest` / `selectionUpdate` / `annotationCreate` Socket.IO event handlers (in `bim-review-coordinator/src/socket/reviewNamespace.ts`) and the viewer `IssuePanel` / `EventLogPanel` / `bimControlClient.getReviewIssues` / `coordinatorClient.getReviewBootstrap` paths. New generic collaboration events of those types are no longer produced. The exclusion wording covering `highlightRequest`, `selectionUpdate`, `annotationCreated`, and `finalReviewEvent` from lifecycle audit is preserved for archive / historical compatibility — existing event logs may still contain those event types from earlier runs, and the lifecycle endpoint MUST still exclude them. The companion `compose.host-kit.yml` change pinned `viewer.ports` to `127.0.0.1:5173:5173` so the viewer is no longer addressable from LAN, aligning with the Kit-1:1 boundary that excludes broadcast collaboration. If conflict review is re-introduced under a future OpenSpec change, that change SHALL add back the corresponding collaboration ADD requirements and viewer slots. + +#### Scenario: Lifecycle audit events are returned in append order + +- **WHEN** a client requests `GET /api/review-sessions/{session_id}/lifecycle-events` for an existing review session +- **THEN** the coordinator returns lifecycle audit events sorted by increasing `sequence` +- **AND** every item includes `event_id`, `session_id`, `type`, `sequence`, `created_at`, and `payload` + +#### Scenario: Closing a session records lifecycle events + +- **WHEN** a close request is accepted for an active review session +- **THEN** the coordinator appends `sessionClosing`, `sessionClosed`, and `kitInstanceReleased` lifecycle audit events +- **AND** the `kitInstanceReleased` payload identifies released `kit_instance_bindings` + +#### Scenario: Collaboration events are excluded from lifecycle audit + +- **WHEN** a session record still contains historical generic events such as highlight, selection, annotation, or final review events (from earlier runs before this change, or from re-introduction under a future ADD) +- **THEN** `GET /api/review-sessions/{session_id}/events` MAY continue to return those generic events +- **AND** `GET /api/review-sessions/{session_id}/lifecycle-events` excludes those generic events + +#### Scenario: Unknown session lifecycle events are not returned + +- **WHEN** a client requests lifecycle events for an unknown or invalid review session id +- **THEN** the coordinator returns the same not-found or validation behavior used by the review session event APIs diff --git a/openspec/changes/remove-conflict-review-from-fast-mvp/tasks.md b/openspec/changes/remove-conflict-review-from-fast-mvp/tasks.md new file mode 100644 index 000000000..2821b7512 --- /dev/null +++ b/openspec/changes/remove-conflict-review-from-fast-mvp/tasks.md @@ -0,0 +1,154 @@ +# Tasks — remove-conflict-review-from-fast-mvp + +> `/goal` 視這份 tasks.md 為**參考路徑**;acceptance condition 見 `proposal.md` § Impact / `design.md` §5。任一 task 失敗 stop 給人類。 + +## 0. Pre-implementation setup + +- [x] 0.1 切 worktree + branch(`codex/openspec/remove-conflict-review-from-fast-mvp` from `origin/main`)— 已完成,本 worktree 即是 +- [x] 0.2 寫 proposal / design / tasks / specs delta — 完成本 task 之同時 +- [x] 0.3 寫 brainstorming overall design — `docs/superpowers/specs/2026-05-21-fast-mvp-loop-overall-design.md` +- [ ] 0.4 Commit design phase(本 task 完成後)— message:`chore(openspec): scaffold remove-conflict-review-from-fast-mvp design + overall fast-mvp brainstorming spec` +- [ ] 0.5 等使用者 review brainstorming design + OpenSpec proposal,核可後才進 §1 + +## 1. Grep 列實際刪除 symbol(implementation 開頭執行) + +- [ ] 1.1 coordinator side conflict-review code: + ```bash + grep -rn -E "highlight|selection|annotation|issueFocus" bim-review-coordinator/src + grep -rn -E "highlight|selection|annotation|issue" bim-review-coordinator/tests + ``` + 把命中 handler / function / field 名寫進 §3 / §4 對應 task description +- [ ] 1.2 viewer side conflict-review code: + ```bash + grep -rn -E "IssuePanel|EventLogPanel|highlightRequest|annotationCreate|issueFocus" web-viewer-sample/src + grep -rn -E "issue|annotation|highlight" web-viewer-sample/src/types web-viewer-sample/tests + ``` +- [ ] 1.3 docs 命中段: + ```bash + grep -rn -E "issue|annotation|highlight|collaboration" AGENTS.md + grep -rn -E "issue|annotation" bim-review-coordinator/CLAUDE.md web-viewer-sample/CLAUDE.md + ``` + +## 2. GitNexus pre-change impact analysis + +- [ ] 2.1 `gitnexus_impact({target:"registerReviewNamespace", direction:"upstream"})` +- [ ] 2.2 `gitnexus_impact({target:"IssuePanel", direction:"upstream"})` +- [ ] 2.3 `gitnexus_impact({target:"DemoControlPanel", direction:"upstream"})` +- [ ] 2.4 任一回 HIGH/CRITICAL → stop,回報後等使用者裁定 +- [ ] 2.5 `gitnexus_context({name:"sessionStore"})` 確認 issue 欄位 callers + +## 3. Coordinator deletions + +- [ ] 3.1 `src/socket/reviewNamespace.ts`:刪 §1.1 grep 命中的 highlight/selection/annotation/issueFocus event handlers;保留 join/leave/presence/heartbeat/sessionCreated +- [ ] 3.2 `src/services/sessionStore.ts`:刪 §1.1 命中的 issue fields 與 operation methods +- [ ] 3.3 `src/types.ts`:刪對應 type +- [ ] 3.4 `src/public/dev-console.html`: + - step bar 由 5 改 3(刪 ④/⑤ `` 元素;改 header 「步驟 ③ / 5」 → 「步驟 ③ / 3」) + - 刪 guided cards 三張:「標示問題位置」/「建立審查標註」/「轉檔資料流」 + - 刪 Raw HTTP/Socket panel 內 `emitHighlight` / `emitSelection` / `emitAnnotation` 按鈕及對應 `
{/* USD Asset Selector */} diff --git a/web-viewer-sample/src/clients/bimControlClient.ts b/web-viewer-sample/src/clients/bimControlClient.ts index cc9473bc1..d069b99a1 100644 --- a/web-viewer-sample/src/clients/bimControlClient.ts +++ b/web-viewer-sample/src/clients/bimControlClient.ts @@ -1,5 +1,4 @@ import type { ReviewArtifact } from "../types/artifacts"; -import type { ReviewIssue } from "../types/issues"; import type { ReviewSessionRequest } from "../types/review"; function readItems(payload: unknown, alternateKey?: string): T[] { @@ -22,11 +21,6 @@ export class BimControlClient { return readItems(response, "artifacts"); } - async getReviewIssues(modelVersionId: string): Promise { - const response = await this.request(`/api/model-versions/${modelVersionId}/review-issues`); - return readItems(response); - } - async getReviewSessionRequest(reviewRequestId: string): Promise { return this.request(`/api/review-session-requests/${reviewRequestId}`); } diff --git a/web-viewer-sample/src/clients/coordinatorClient.ts b/web-viewer-sample/src/clients/coordinatorClient.ts index d5dc52bae..21f95f838 100644 --- a/web-viewer-sample/src/clients/coordinatorClient.ts +++ b/web-viewer-sample/src/clients/coordinatorClient.ts @@ -1,4 +1,4 @@ -import type { ReviewBootstrap, ReviewSession, ReviewStreamConfig } from "../types/review"; +import type { ReviewSession, ReviewStreamConfig } from "../types/review"; import type { ArtifactBinding } from "../types/artifacts"; export interface CreateReviewSessionInput { @@ -69,10 +69,6 @@ export class CoordinatorClient { return this.request(`/api/review-sessions/${sessionId}/stream-config`); } - async getReviewBootstrap(modelVersionId: string): Promise { - return this.request(`/api/model-versions/${modelVersionId}/review-bootstrap`); - } - private async request(path: string, init?: RequestInit): Promise { const response = await this.fetchImpl(`${this.baseUrl}${path}`, { headers: { Accept: "application/json", ...(init?.headers || {}) }, diff --git a/web-viewer-sample/src/clients/demoDefaults.ts b/web-viewer-sample/src/clients/demoDefaults.ts index f32fa1928..fd14760e6 100644 --- a/web-viewer-sample/src/clients/demoDefaults.ts +++ b/web-viewer-sample/src/clients/demoDefaults.ts @@ -1,16 +1,2 @@ -import type { HighlightItem } from "../types/streamMessages"; - export const demoPrimPath = "/World"; -export const demoIssueId = "ISSUE-DEMO-001"; export const demoIfcGuid = "2VJ3sK9L000fake001"; - -export function buildDemoHighlightItem(source: string): HighlightItem { - return { - prim_path: demoPrimPath, - ifc_guid: demoIfcGuid, - color: [1, 0, 0, 1], - label: "示範:從 Web Viewer Demo 面板送出的高亮", - source, - issue_id: demoIssueId, - }; -} diff --git a/web-viewer-sample/src/clients/reviewSocket.ts b/web-viewer-sample/src/clients/reviewSocket.ts index 3e4406f1c..fa67fb655 100644 --- a/web-viewer-sample/src/clients/reviewSocket.ts +++ b/web-viewer-sample/src/clients/reviewSocket.ts @@ -1,5 +1,4 @@ import { io, type Socket } from "socket.io-client"; -import type { HighlightItem } from "../types/streamMessages"; export interface ReviewSocketHandlers { onEvent?: (event: string, payload: unknown) => void; @@ -8,9 +7,6 @@ export interface ReviewSocketHandlers { export interface ReviewSocketClient { join(sessionId: string, userId: string, displayName: string): void; - emitHighlight(sessionId: string, userId: string, issueId: string, items: HighlightItem[]): void; - emitSelection(sessionId: string, userId: string, paths: string[]): void; - emitAnnotation(sessionId: string, userId: string, text: string): void; heartbeat(sessionId: string, userId: string): void; disconnect(): void; } @@ -29,39 +25,6 @@ export function connectReviewSocket(baseUrl: string, handlers: ReviewSocketHandl join(sessionId: string, userId: string, displayName: string) { socket.emit("joinSession", { session_id: sessionId, user_id: userId, display_name: displayName }); }, - emitHighlight(sessionId: string, userId: string, issueId: string, items: HighlightItem[]) { - socket.emit("highlightRequest", { - session_id: sessionId, - user_id: userId, - source: "issue_panel", - issue_id: issueId, - items: items.map((item) => ({ - prim_path: item.prim_path, - usd_prim_path: item.prim_path, - ifc_guid: item.ifc_guid, - color: item.color, - label: item.label, - })), - }); - }, - emitSelection(sessionId: string, userId: string, paths: string[]) { - socket.emit("selectionUpdate", { - session_id: sessionId, - actor_id: userId, - selected_paths: paths, - }); - }, - emitAnnotation(sessionId: string, userId: string, text: string) { - socket.emit("annotationCreate", { - session_id: sessionId, - actor_id: userId, - text, - target: { - usd_prim_path: "/World", - ifc_guid: "2VJ3sK9L000fake001", - }, - }); - }, heartbeat(sessionId: string, userId: string) { socket.emit("heartbeat", { session_id: sessionId, actor_id: userId }); }, diff --git a/web-viewer-sample/src/components/DemoControlPanel.tsx b/web-viewer-sample/src/components/DemoControlPanel.tsx index 5d65773ec..c3bea88ce 100644 --- a/web-viewer-sample/src/components/DemoControlPanel.tsx +++ b/web-viewer-sample/src/components/DemoControlPanel.tsx @@ -27,16 +27,17 @@ interface DemoControlPanelProps { */ conversionSummaryFetchFallback?: (conversionJobId: string) => Promise; onCreateOrLoadSession: () => void; - onLoadBootstrap: () => void; + // fast-mvp loop: optional, no-op default; 對應 button 等 Change 2 viewer 重做時整段刪 + onLoadBootstrap?: () => void; onConnectSocket: () => void; onOpenStage: () => void; onLoadingState: () => void; onGetChildren: () => void; - onHighlightWorld: () => void; + onHighlightWorld?: () => void; onFocusWorld: () => void; onClearHighlight: () => void; - onEmitCoordinatorHighlight: () => void; - onCreateAnnotation: () => void; + onEmitCoordinatorHighlight?: () => void; + onCreateAnnotation?: () => void; onLoadMapping: () => void; onSelectMappingIndex: (index: number) => void; onHighlightSelectedMapping: () => void; @@ -219,16 +220,16 @@ export default function DemoControlPanel(props: DemoControlPanelProps) { smokeBlockerHint, conversionSummaryFetchFallback, onCreateOrLoadSession, - onLoadBootstrap, + onLoadBootstrap = () => undefined, onConnectSocket, onOpenStage, onLoadingState, onGetChildren, - onHighlightWorld, + onHighlightWorld = () => undefined, onFocusWorld, onClearHighlight, - onEmitCoordinatorHighlight, - onCreateAnnotation, + onEmitCoordinatorHighlight = () => undefined, + onCreateAnnotation = () => undefined, onLoadMapping, onSelectMappingIndex, onHighlightSelectedMapping, diff --git a/web-viewer-sample/src/components/EventLogPanel.tsx b/web-viewer-sample/src/components/EventLogPanel.tsx deleted file mode 100644 index 14def0d96..000000000 --- a/web-viewer-sample/src/components/EventLogPanel.tsx +++ /dev/null @@ -1,96 +0,0 @@ -interface EventLogPanelProps { - events: string[]; - width: number; -} - -export default function EventLogPanel({ events, width }: EventLogPanelProps) { - const recent = events.slice(-12).reverse(); - return ( -
-
- 即時審查事件 (Live events) - {events.length} 筆 -
- -
- {recent.length === 0 && ( -
尚未有事件。建立或加入審查會議後,操作會出現在這裡。
- )} - {recent.map((event, index) => { - const friendly = humanize(event); - return ( -
- {friendly.label} - {friendly.tag} -
- ); - })} -
- - {recent.length > 0 && ( -
- - 展開技術細節 (Show technical details) - -
-                        {recent.join("\n")}
-                    
-
- )} -
- ); -} - -function humanize(raw: string): { label: string; tag: string } { - const s = String(raw); - if (/connect|connected|已連線/i.test(s) && !/disconnect/i.test(s)) return { label: "已連上即時頻道", tag: "connect" }; - if (/disconnect|斷線|斷開/i.test(s)) return { label: "即時頻道中斷", tag: "disconnect" }; - if (/joinSession/i.test(s)) return { label: "使用者加入審查會議", tag: "join" }; - if (/leaveSession/i.test(s)) return { label: "使用者離開審查會議", tag: "leave" }; - if (/highlightPrimsResult/i.test(s)) return { label: "模型已完成高亮", tag: "highlight-result" }; - if (/highlightPrimsRequest|highlightRequest/i.test(s)) return { label: "送出問題高亮", tag: "highlight" }; - if (/clearHighlight/i.test(s)) return { label: "清除高亮", tag: "clear" }; - if (/focusPrim/i.test(s)) return { label: "聚焦元件", tag: "focus" }; - if (/openStage|openedStage/i.test(s)) return { label: "開啟可審查模型", tag: "stage" }; - if (/loadingState/i.test(s)) return { label: "查詢模型載入狀態", tag: "loading" }; - if (/getChildren/i.test(s)) return { label: "讀取模型結構", tag: "tree" }; - if (/selectPrims|selectionUpdate|stageSelectionChanged/i.test(s)) return { label: "同步元件選取", tag: "select" }; - if (/annotationCreated/i.test(s)) return { label: "標註已寫入主資料庫", tag: "annotation" }; - if (/annotationCreate/i.test(s)) return { label: "建立審查標註", tag: "annotation" }; - if (/presenceUpdated/i.test(s)) return { label: "與會者列表更新", tag: "presence" }; - if (/heartbeat/i.test(s)) return { label: "心跳", tag: "heartbeat" }; - return { label: s.slice(0, 60), tag: "" }; -} diff --git a/web-viewer-sample/src/components/IssuePanel.tsx b/web-viewer-sample/src/components/IssuePanel.tsx deleted file mode 100644 index d9cb6438a..000000000 --- a/web-viewer-sample/src/components/IssuePanel.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { ReviewIssue } from "../types/issues"; - -interface IssuePanelProps { - issues: ReviewIssue[]; - width: number; - onIssueClick: (issue: ReviewIssue) => void; -} - -export default function IssuePanel({ issues, width, onIssueClick }: IssuePanelProps) { - return ( -
-
審查問題
-
- {issues.map((issue) => ( - - ))} - {issues.length === 0 &&
目前沒有審查問題
} -
-
- ); -} diff --git a/web-viewer-sample/src/types/issues.ts b/web-viewer-sample/src/types/issues.ts deleted file mode 100644 index 3092e9188..000000000 --- a/web-viewer-sample/src/types/issues.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface ReviewIssue { - issue_id: string; - project_id?: string; - model_version_id?: string; - source?: string; - severity: "info" | "warning" | "error" | string; - status?: string; - title: string; - description?: string; - ifc_guid?: string | null; - usd_prim_path?: string | null; -} diff --git a/web-viewer-sample/src/types/review.ts b/web-viewer-sample/src/types/review.ts index 45bc504e6..6467adf63 100644 --- a/web-viewer-sample/src/types/review.ts +++ b/web-viewer-sample/src/types/review.ts @@ -1,5 +1,4 @@ import type { ArtifactBinding, ReviewArtifact } from "./artifacts"; -import type { ReviewIssue } from "./issues"; /** * Additive, read-only pass-through of streaming conversion quality metrics. @@ -93,12 +92,6 @@ export interface ReviewStreamConfig { }; } -export interface ReviewBootstrap { - model_version_id: string; - artifacts: ReviewArtifact[]; - issues: ReviewIssue[]; -} - export interface ReviewSessionRequest { review_request_id: string; requested_by: string;