# Agent Handoff Notes 這個資料夾是新的巡樓後端核心,請優先維持乾淨邊界,不要把舊 Next.js 或 `template-monorepo` 的業務包袱搬進來。 ## 核心原則 - 全系統時間一律 **UTC+0**;寫入 Mongo / API 的時間欄位一律 **unix nanoseconds**(`int64`)。排程的 `timezone` 只用於 cron 解讀與下發 payload,不作為儲存時區。 - 複製模式,不複製舊業務。 - `logic` 做 API 編排,`model/usecase` 做可重複使用能力。 - provider adapter 不讀 setting、不碰 Mongo、不知道 HTTP。 - setting 是通用 key-value model,不依賴 AI 或其他業務。 - token / API key 第一版每次 request 帶入,不寫入 config。 - SSE contract 由本服務 normalize,前端不要讀 provider 原始 chunk。 - JSON API 必須使用 `code/message/data/error` envelope 與 `SSCCCDDD` 錯誤碼。 - 列表 API 必須使用 `page/pageSize` query,並在 `data` 回傳 `pagination/list`。 - Job 狀態轉移必須使用 guarded/conditional update;不要在 API/worker 直接裸 `Update` 覆蓋 job 狀態。 - Redis job lock 的 value 是 `workerID`;release / refresh 必須檢查 owner,長任務必須 heartbeat。 - Auth 目前是 native email/password + JWT,不包含 OAuth / OTP / MFA / Zitadel。不要為了相容 template-monorepo 把重依賴搬進來。 - AI provider token 與會員 JWT 是兩種不同 token;AI token 每次 request header 帶入,會員 JWT 由 `/api/v1/auth/*` 簽發。 ## 設計文件 - `docs/job-system-plan.md`:通用 job system 規劃,包含 template、run、schedule、Redis queue/lock、取消語意與 API 草案。 - `docs/scan-placement-plan.md`:海巡獲客(流程 B)— 知識圖譜、Brave 擴展、雙軌爬取(7 天重點 / 30 天補充)、產品匹配、島民交接。 ## 新增 API 流程 1. 修改 `generate/api/*.api`。 2. 優先使用 `make gen-api` 重新產生 handler/logic/types。 3. 若手寫 handler,仍需遵守 `response.Write` 與 validator 流程。 4. SSE endpoint 不使用 `response.Write`,直接輸出 `text/event-stream`。 5. 更新 `README.md` 的 API 與架構說明。 ## Response / Error Code 錯誤碼格式是 `SSCCCDDD`: ```text SS = scope CCC = category DDD = detail ``` 目前 scope: ```text 10 = Facade 32 = Setting 33 = AI 34 = Job 35 = Auth 36 = Member 37 = Permission ``` 建立錯誤時使用: ```go errs.For(code.AI).InputMissingRequired("缺少 AI provider token") errs.For(code.Setting).ResNotFound("找不到設定") errs.For(code.Job).ResInvalidState("job state changed; update rejected") errs.For(code.Auth).AuthUnauthorized("missing bearer token") ``` 不要直接手寫 `33104000` 這種數字,也不要回傳裸 `error` 給 handler 後讓使用者看到內部錯誤。 ## Pagination 列表 API 使用: ```text ?page=1&pageSize=10 ``` response: ```json { "code": 102000, "message": "SUCCESS", "data": { "pagination": { "total": 100, "page": 1, "pageSize": 10, "totalPages": 10 }, "list": [] } } ``` `page/pageSize` 必須是 server 正規化後的值。不要使用 `offset/limit/items`。 ## 新增 Model 流程 模組放在: ```text internal/model// domain/entity domain/repository domain/usecase repository usecase ``` 依賴方向: ```text handler -> logic -> model/domain/usecase model/usecase -> model/domain/repository model/repository -> Mongo / Redis ``` 不要讓 `logic` import `model//repository`。 ## Auth / Permission 擴充 目前已接: ```text POST /api/v1/auth/register POST /api/v1/auth/login POST /api/v1/auth/refresh POST /api/v1/auth/logout # requires member JWT GET /api/v1/members/me PATCH /api/v1/members/me GET /api/v1/permissions/catalog GET /api/v1/permissions/me ``` Auth matrix(`internal/handler/routes.go`): | 路由 | 需要會員 JWT | |------|----------------| | `GET /api/v1/health` | 否 | | `POST /api/v1/auth/register/login/refresh` | 否 | | `POST /api/v1/auth/logout` | 是(`Authorization`) | | `GET /api/v1/ai/providers` | 否 | | `POST /api/v1/ai/chat/stream/models` | 是(`X-Member-Authorization`)+ provider token(`Authorization`) | | `/api/v1/members/*`、`/api/v1/permissions/me` | 是(`Authorization`) | | `/api/v1/permissions/catalog`、`/api/v1/settings/*`、`/api/v1/jobs*`、`/api/v1/job/*` | 是(`Authorization`) | 規則: - 保護路由用 `internal/middleware.Auth`(`Authorization: Bearer `);AI 變更路由用 `middleware.MemberAuth`(`X-Member-Authorization`),因 `Authorization` 保留給 provider API key。 - logic 從 `authctx.ActorFromContext` 讀 `tenant_id` / `uid`。 - 不要在 handler 直接 parse JWT;token 驗證集中在 `model/auth/usecase`。 - 密碼只存 bcrypt hash,不回傳、不寫 log。 - `members.roles` 第一版是簡化 role key。正式 RBAC 可逐步補 roles collection,但不要破壞 `role_permissions` 的 tenant + role_key contract。 - `Auth.DevHeaderFallback` 只給本機開發,正式環境應關閉。 ## AI Provider 擴充 新增 provider 時: 1. 在 `internal/model/ai/domain/enum` 新增 provider id。 2. 在 `internal/model/ai/provider` 新增 adapter。 3. 在 `internal/model/ai/usecase` registry 註冊 provider 與 models。 4. 確保 adapter 回傳統一 `StreamEvent`。 5. 不要改 `logic/ai` 的 SSE 格式。 ## Job Worker 擴充 新增 job step 時優先註冊 runner handler: ```go runner.RegisterStepHandler("analyze_8d", func(ctx context.Context, step job.StepContext) error { if err := step.Heartbeat(ctx); err != nil { return err } // do work, check cancel via job usecase if needed return nil }) ``` 規則: - Handler 不要直接操作 Mongo / Redis,透過 job usecase 更新進度、完成、失敗或取消。 - 長任務每個 checkpoint 呼叫 `StepContext.Heartbeat` 或 `RefreshRunLock`。 - 收到 cancel signal 後呼叫 `AcknowledgeCancel(jobId, workerID)`,不要自行把狀態改成 `cancelled`。 - release lock 時必須帶 `workerID`;不要新增無 owner 的 release helper。 ## 前端設計規則(`web/`) 巡樓 Console 前端在 `haixun-backend/web/`,視覺為**沉穩田園巡檢台**(動森感:天空、雲朵、奶油卡片、青綠 brand;**不是**任天堂 UI 複製)。 **字體固定** Inter + Taipei Sans TC;圖示僅 `AcIcon` / `AuthDecor` 內原創 SVG 線條圖。**禁止** emoji、貼圖 JPG、咖啡色木質頂欄、Nook / 任天堂命名。樣式集中在 `index.css`(`--hx-*` token + `hx-*` / `ac-*` / `auth-*` class)。 不要把舊 Next.js / `template-monorepo` UI 搬進來,也不要引入重型 UI 框架。 ### 視覺架構(登入前後共用) ```text 全頁背景 .hx-scene 灰藍天空 → 淡草地單一漸層(淺/深各一套,見 index.css) 裝飾層 SceneDecor 雲朵(多朵緩動)+ 淡光暈 + 小葉子;登入與 Layout 共用 奶油卡片 .auth-ticket 2px line 邊框、圓角 2rem、surface 底、可選 .ac-dialog-texture 點陣 頂部品牌列 圖示 .auth-ticket-icon(brand-soft 底)+ ink 標題;不用獨立色塊 ribbon 主內容 表單或 Outlet;內文頁用 PageTitle / Card(綠色 .ac-title-bar 僅內容區小標) 桌面側欄 .ac-pocket-device 掌上終端外框;PATROL PAD 狀態列;固定尺寸 + .ac-pocket-scroll 內捲 手機 .ac-dock 底部最多 4 格 +「更多」sheet ``` | 區域 | 元件 | 關鍵 class | |------|------|------------| | 未登入 | `AuthShell` + `LoginPage` / `RegisterPage` | `hx-scene` `auth-scene` `auth-ticket` `auth-welcome` `auth-shell-form` | | 已登入外殼 | `Layout` | `hx-scene` `ac-app-shell` `ac-app-header` `auth-ticket` `ac-app-main-inner` | | 背景裝飾 | `AuthDecor.tsx` → `SceneDecor` | `hx-scene-deco` `auth-cloud--*` | | 品牌小圖 | `AuthTicketIcon` | `auth-ticket-icon`(小屋+樹,原創 SVG) | | 側欄導覽 | `Layout` + `navApps` | `ac-pocket-device` `ac-app-tile` | | 手機導覽 | `MobileBottomNav` | `ac-dock` | **登入頁刻意不做的事**:上方不要獨立大色塊 header;表單上方**不要**再放 `ac-title-bar`「登入」大牌(品牌已在 `auth-welcome`)。註冊頁同理可省略重複大標。 **已淘汰、勿加回**:`ac-island`(改用 `hx-scene`)、`ac-wood-bar` / 咖啡色木質頂欄、`public/ac/` 貼圖、Nook Phone 文案。 ### 技術棧與指令 ```text web/ src/ api/ # API client(envelope、JWT refresh) auth/ # AuthContext components/ # Layout、AuthShell、AuthDecor、ui、ThemeToggle、MobileBottomNav、AcIcon theme/ # ThemeContext(淺色 / 深色) pages/ # 路由頁面 lib/ # acAssets(導覽 icon key)、jobStatus 等 index.css # 設計 token 與場景樣式唯一來源 ``` ```bash make web-dev # dev server :5173,proxy 到 :8890 make web-build # tsc + vite build ``` ### 字型 | 語言 | 字型 | 載入方式 | |------|------|----------| | 繁體中文 | **台北黑體 Taipei Sans TC** | npm `taipei-sans-tc`,在 `index.css` `@import` Regular + Bold | | 英文 | **Inter**(與 simular.co 相同,Google Fonts 免費) | `web/index.html` link | 規則: - `body` / 中文標題:`Inter` + `Taipei Sans TC` 混排(`--font-sans`)。 - 純英文裝飾字(導覽副標、Hero 小字):加 class `display-en`,使用 `--font-en`。 - 中文 `line-height` 維持 **1.7+**;不要用過細字重當標題(標題用 `font-bold` / `font-black`)。 - 只載入 Taipei Sans TC **Regular + Bold**,不要載入 Light,避免小字過細。 - 不要改回 Noto Sans TC,也不要手寫 `#333` 這類裸色碼當主色。 ### 對比度與字級 - 內文、表頭、表單 label、卡片說明:優先 `text-ink` / `text-ink-secondary`,**不要**拿 `text-muted` 當主要閱讀文字。 - `text-muted` 只給次要提示(筆數、hint、placeholder 用 `text-subtle`)。 - 表單輸入字級 **15px**(`text-[15px]`),輸入框底用 `bg-surface` 白底,確保與背景拉開。 - 淺色 `muted` 約 `#5a6578`、深色約 `#b8c4d6`;改色時以「小字仍可舒適閱讀」為準,不要回到 `#94a3b8` 那種淡灰。 ### 主題(淺色 / 深色) - `ThemeProvider`(`src/theme/ThemeContext.tsx`)包住 App;偏好存 `localStorage` key:`haixun.theme`(`light` | `dark`)。 - `index.html` 內嵌 script 在 React 載入前設定 `data-theme`,避免閃爍。 - 所有顏色必須走 CSS 變數 `--hx-*`,再映射到 Tailwind `@theme`(`bg-canvas`、`text-brand` 等)。 - 切換按鈕用 `ThemeToggle`(`ac-btn-secondary` 樣式);`Layout` 頂欄與 `AuthShell` 右上角都要有。 - **禁止**在元件裡寫死 `bg-slate-*`、`text-emerald-*`、`bg-amber-*` 等 Tailwind 預設色;語意狀態用 `text-success` / `text-warning` / `text-danger` 或 `jobStatus.ts` 的 badge class。 淺色:低飽和灰藍天空 + 灰綠草地 + 奶油 `surface` + **brand 青綠**;深色:黃昏低對比、同一套 token 自動切換。頂欄與卡片內品牌區都用 **surface / ink / brand**,不要再用木色 `#c4a882` 當 header 底。 ### 場景與卡片 class(維護時對照) | Class | 用途 | |-------|------| | `.hx-scene` | 全頁天空→草地漸層(登入 + 已登入根節點) | | `.hx-scene-deco` / `SceneDecor` | 背景雲、光暈、葉子(`pointer-events: none`) | | `.auth-ticket` | 奶油主卡片外框(登入卡、已登入主內容區) | | `.auth-welcome` | 卡片內品牌列:圖示 + 標題 + 一句 tagline,底部分隔線 | | `.ac-app-header` | 已登入 sticky 頂欄:半透明 surface + blur,**非**木色 | | `.ac-title-bar` | 內容區綠色小標題(裝置色漸層);用於 `PageTitle` 等,**不**用於登入頁表單上方大牌 | | `.ac-pocket-device` | 側欄掌上終端;`--pocket-width`(28rem)、`--pocket-screen-height` 固定,內容在 `.ac-pocket-scroll` 捲動 | | `.ac-app-tile` / `.ac-dock` | App 格導覽、手機底欄 | | `.auth-shell-form` | 登入/註冊表單放大字級(僅 auth 頁) | 側欄標示用 **PATROL PAD** 等中性英文裝飾字(`display-en`);圖示僅 `AcIcon` SVG。 ### 色彩 token(語意命名) 開發時只用這些 Tailwind class(值定義在 `web/src/index.css`): | Token | 用途 | |-------|------| | `canvas` | 全頁背景 | | `surface` / `surface-muted` | 卡片、輸入框底 | | `ink` / `ink-secondary` / `muted` | 主文 / 次文 / 輔助 | | `line` | 邊框 | | `brand` / `brand-hover` / `brand-soft` | 主 CTA、active 導覽、連結 hover | | `glow` | 裝飾色塊(`.glow-blob-alt`) | | `success` / `warning` / `danger`(含 `*-soft`) | 狀態、錯誤、Job badge | 主按鈕一律 `Button variant="primary"` → `bg-brand`,不要用全黑按鈕。 ### 圓角與陰影 ```text --radius-sm 0.75rem 小元素、code --radius-md 1.25rem Input / Textarea --radius-lg 1.75rem Card --radius-xl 2.25rem Hero、QuickLink、StatCard --radius-pill 9999px Button、Badge、導覽 pill ``` 陰影用 utility:`shadow-card`(一般卡片)、`shadow-soft`(主按鈕、Hero、`.auth-ticket`)。內容 Hero 可用 `ac-bulletin` + `ac-hero-gradient` token;全頁裝飾雲朵走 `SceneDecor`,不要另加會打架的強色 blob。 ### 共用元件(優先復用) 新頁面必須從 `src/components/ui.tsx` 組裝,不要另寫一套按鈕樣式: | 元件 | 用途 | |------|------| | `PageTitle` | 頁面標題 + 副標 | | `Card` | 內容區塊 | | `Field` + `Input` / `Textarea` | 表單 | | `Button` | `primary` / `ghost` / `danger` / `soft` | | `Badge` | 標籤 pill(`brand` / `sky` / `success` / `warning` / `danger` / `neutral`) | | `StatCard` / `QuickLinkCard` | 總覽統計與快捷入口 | | `ErrorText` / `CopyableId` | 錯誤與可複製 ID | `Button` 必須渲染 `{children}`;文案用**中文動詞**(例:「建立背景任務」「重新載入任務列表」),不要留空白小框。 ### RWD(手機) - `< lg`:隱藏左側欄;**底部固定導覽**最多 **4 格**(總覽 / 任務 / 排程 / **更多**),不要把漢堡或 ⋯ 選單放在左上角。 - 「更多」以底部 sheet 展開:AI、模板、設定、會員、權限、主題切換、登出。 - 主內容加 `layout-main` 底部 padding,避開 tab bar + `safe-area-inset-bottom`。 - 寬表格包 `overflow-x-auto` + `min-w-*`,避免小螢幕擠爆版面。 ### 版面與導覽 - 已登入(桌面):`Layout` = `hx-scene` 背景 + `SceneDecor` + `ac-app-header`(品牌 + 角色 chip + `ThemeToggle`)+ 左 `ac-pocket-device` + 右 `auth-ticket` 主內容 `Outlet`。 - 已登入(手機):同上頂欄;導覽走 `MobileBottomNav`(總覽/任務/排程/更多)。 - 側欄 App 來源:`src/lib/acAssets.ts` 的 `navApps`;圖示 key 對應 `AcIcon`。 - Active 導覽:`ac-app-tile--active`(brand-soft 底 + brand 字色);hover:`bg-brand-soft text-brand`。 - 未登入:`AuthShell` 置中 `auth-ticket` + 右上 `ThemeToggle`;`auth-welcome` 內品牌,表單緊接說明文字。 - 語氣:年輕、直接、短句;可帶「島民」「巡樓」等原創文案,避免企業八股與任天堂用語。 ### API 與狀態 - JSON 一律走 `api/client.ts`(`code/message/data` envelope);需登入加 `{ auth: true }`。 - AI 路由用 `X-Member-Authorization`;provider token 用 `Authorization`(見後端 Auth matrix)。 - Job 狀態中文與 badge 色:`src/lib/jobStatus.ts`(`jobStatusLabel` / `jobStatusBadgeClass`),列表有進行中任務時可每 3 秒 refresh。 - 不要在前端 parse JWT;`uid` / `tenant_id` 從 `AuthContext` 讀。 ### 新增頁面流程 1. 在 `App.tsx` 掛路由(需登入的放在 `Layout` 底下,自動享有 `hx-scene` + 頂欄 + 主內容 `auth-ticket`)。 2. 頁面內用 `PageTitle`(含 `.ac-title-bar` 小標)+ `Card` / `ac-bulletin` + `ui.tsx` 元件;色票只引用 semantic token。 3. 若需新語意色,**先**改 `index.css` 的 `--hx-*` 與 `@theme`,再改元件;不要頁面內硬編色碼。 4. 新導覽項:改 `acAssets.ts` 的 `navApps`,並在 `AcIcon` 補 SVG path。 5. 完成後執行 `make web-build`。 ### 島民頁面互動(可推廣 runtime) 掛在 `Layout` 底下的新頁面**自動**支援島民操作,不需每頁手寫 executor。 模組入口:`web/src/lib/islander/index.ts` | 層 | 職責 | |----|------| | `pageSnapshot` | 掃描 `.ac-app-shell` 內可互動元素,產生 `hx-*` ref | | `islanderActions` | 解析/剝除 `islander-actions` JSON 區塊 | | `actionExecutor` | 執行 navigate/click/fill/select/scroll 等;可 `registerIslanderActionHandler` 擴充 | | `islanderAgent` | 串流回覆 → 執行 action → 回傳結果 → 自動 follow-up | | `buildIslanderContext` | 組裝送給後端的頁面快照 | **零設定(預設)**:路由掛在 `Layout` 即可;島民讀 DOM + `PageTitle` / `h1` 辨識頁面。預設**不**主動介紹這一頁;僅在使用者明確問頁面/操作時才附【可互動元素】(`userWantsPageContext`)。 **可選增強**(擇一): 1. `useIslanderPage({ title, purpose, hints, suggestions })` — 頁面內動態註冊說明 2. `registerIslanderPage(/^\/foo/, { title, ... })` — 在 `siteGuide.ts` 或模組 init 靜態註冊 3. HTML 慣例:`data-islander-label`(元素名稱)、`data-islander-kind`(類型)、`data-islander-ignore`(排除)、`data-islander-page-title`(頁名) Action 協定(AI 回覆末尾): ```islander-actions [{ "type": "navigate", "path": "/settings" }, { "type": "click", "ref": "hx-3" }] ``` ### 前端禁忌 - 不要引入 MUI / Ant Design / Chakra 等大型 UI 庫。 - 不要為單頁新增第三套配色、木質頂欄、或漸層彩虹按鈕。 - 不要在登入/註冊頁加回獨立大牌 `ac-title-bar` 或咖啡色 header ribbon。 - 不要讓 SSE / AI 直接吃 provider 原始 chunk(後端已 normalize)。 - 不要用 `offset/limit` 呼叫列表 API;用 `page` / `pageSize`。 ## 驗證 完成變更後至少執行: ```bash cd haixun-backend go mod tidy make fmt go test ./... ``` 有動到前端時另執行: ```bash make web-build ```