refactor(cypher): 第一刀拆分——UI 搬 CF Pages,ingest 從必爆變穩定

根因(leo 2026-07-21 三個判斷全被數字證實):
- 「cypher 展開上萬行,當時我就懷疑這樣跑得動嗎」→ 15,446 行、bundle 748KB
- 「不相信 CF 連讀文字檔都會撞牆」→ 不是 CF 的問題,是 cypher 太肥
- 「它違反了樂高化的原則」→ 零件 4KB,調度它們的 cypher 是巨石

定位錯誤(比效能更根本):console.ts:1 自陳「Mira Console」,而 wiki 明載
「Mira 是 Arcrun 的使用者,不是開發者」→ 使用者的前端寫進了框架的執行引擎。
CLI/MCP 都已拆成獨立薄殼,唯獨 UI 沒有。責任誠實記:console.ts 標
「2026-07-04 總管派工」,是總管當初選了「就近寫在 cypher」的方便路。

本刀:
- console.ts(1623行) + portal-ui.ts(1390行) 移出 → console-ui/ CF Pages 專案
  (單檔 HTML + 原生 JS,零 build step,保持原特性)
- index.ts 移除兩個路由掛載、修 implicit any
- console-dashboard.ts 只留 API(UI 部分移出)

實測驗收(leo 指定判準:不看 /health,看真實任務):
| 測試 | 拆分前 | 拆分後 |
|---|---|---|
| 連灌 10 張新卡(無間隔) | 從未成功 | 10 張全 create、73 條三元組 |
| 同卡連跑 6 次 | 前 2 過、後 4 全 1102 | 6 次全過 |
| Bundle | 747.9 KB | 528.0 KB(-29%) |

Pages 已上線:arcrun-console-ui.pages.dev(console/portal 皆 200)
cypher API 正常:/health、/console/dashboard-data 皆 200

未做(第二刀):console-dashboard/portal/portal-data 尚未拆,cypher 仍 528KB。
但 ingest 已穩定,急迫性下降。

註:部署撞到三個既有 self-hosted 陷阱(KV/D1 id 是官方帳號的、arcrun.dev route
leo21c 無該 zone),用 wrangler.leo21c.toml 繞過,未改原始設定檔。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-21 18:55:31 +08:00
parent 1b687fedb0
commit 5a164843ef
10 changed files with 655 additions and 543 deletions
+17 -6
View File
@@ -20,19 +20,32 @@ import { resumeRouter } from './routes/resume';
import { executionsRouter } from './routes/executions';
import { initSeedRouter } from './routes/init-seed';
import { kbdbProxyRouter } from './routes/kbdb-proxy';
import { consoleRouter } from './routes/console';
import { consoleAuthRouter } from './routes/console-auth';
import { consoleDashboardRouter } from './routes/console-dashboard';
import { portalRouter } from './routes/portal';
import { portalDataRouter } from './routes/portal-data';
import { portalUiRouter } from './routes/portal-ui';
const app = new Hono<{ Bindings: Bindings }>();
// 全域 CORS(允許 arcrun.dev landing page 帶 credentials 存取)
//
// 2026-07-21 cypher-ui-splitconsole/portal UI 搬到 Cloudflare Pages 後,前端與本 API
// **不再同源**,故 UI 站的 origin 必須進白名單,否則所有 fetch 會被瀏覽器擋。
// 允許來源=上面兩個 landing + UI_ORIGINSwrangler.toml [vars] 逗號分隔,實例自填
// 自己的 Pages 網域,如 https://arcrun-console-ui.pages.dev)。
// 刻意不用萬用字元:credentials:true 與 `*` 在 CORS 規格上互斥,且 Authorization/
// X-Arcrun-API-Key 是憑證等級標頭,開全域等於誰都能代打。
const STATIC_ORIGINS = ['https://arcrun.dev', 'https://www.arcrun.dev'];
app.use('*', cors({
origin: ['https://arcrun.dev', 'https://www.arcrun.dev'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
origin: (origin, c) => {
const extra = (c.env.UI_ORIGINS || '')
.split(',')
.map((s: string) => s.trim())
.filter(Boolean);
return [...STATIC_ORIGINS, ...extra].includes(origin) ? origin : null;
},
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization', 'X-Arcrun-API-Key'],
credentials: true,
}));
@@ -54,12 +67,10 @@ app.route('/', resumeRouter);
app.route('/', executionsRouter); // LI SDD M2.1: /executions/* + /workflows/:name/executions
app.route('/', initSeedRouter); // 薄殼原則:seed recipe 是 API 行為(rule 07,壓測 §4.1
app.route('/', kbdbProxyRouter); // kbdb-base 9.5KBDB 資料層 proxy(讓 CLI 透過 cypher 達 KBDB,純轉發)
app.route('/', consoleRouter); // Arcrun#3:搜尋/控制台頁 v0(單檔 HTML+原生 JS,薄殼)
app.route('/', consoleAuthRouter); // Arcrun#3 發現②:console 專用簡單 email+password 登入(單一管理員帳密,非多租戶)
app.route('/', consoleDashboardRouter); // T-cockpit ②:駕駛艙 dashboard(聚合 KBDB dash_* entries,無需登入唯讀)
app.route('/', portalRouter); // portal-auth P2#24/#25):RAG Portal 多人授權——用戶模型+認證 API
app.route('/', portalDataRouter); // portal-auth P3/portal/data/* server-side enforceowner_idlibrary 注入,安全核心)
app.route('/', portalUiRouter); // portal-auth P3GET /portal 單檔 HTML 殼(登入/搜尋/設定;獨立於 console)
// Worker 導出(fetch + scheduled
// scheduled handler 對應 wrangler.toml [triggers].crons,每分鐘 tick