18 KiB
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/errorenvelope 與SSCCCDDD錯誤碼。 - 列表 API 必須使用
page/pageSizequery,並在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 流程
- 修改
generate/api/*.api。 - 優先使用
make gen-api重新產生 handler/logic/types。 - 若手寫 handler,仍需遵守
response.Write與 validator 流程。 - SSE endpoint 不使用
response.Write,直接輸出text/event-stream。 - 更新
README.md的 API 與架構說明。
Response / Error Code
錯誤碼格式是 SSCCCDDD:
SS = scope
CCC = category
DDD = detail
目前 scope:
10 = Facade
32 = Setting
33 = AI
34 = Job
35 = Auth
36 = Member
37 = Permission
建立錯誤時使用:
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 使用:
?page=1&pageSize=10
response:
{
"code": 102000,
"message": "SUCCESS",
"data": {
"pagination": {
"total": 100,
"page": 1,
"pageSize": 10,
"totalPages": 10
},
"list": []
}
}
page/pageSize 必須是 server 正規化後的值。不要使用 offset/limit/items。
新增 Model 流程
模組放在:
internal/model/<module>/
domain/entity
domain/repository
domain/usecase
repository
usecase
依賴方向:
handler -> logic -> model/domain/usecase
model/usecase -> model/domain/repository
model/repository -> Mongo / Redis
不要讓 logic import model/<module>/repository。
Auth / Permission 擴充
目前已接:
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 <access_token>);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 時:
- 在
internal/model/ai/domain/enum新增 provider id。 - 在
internal/model/ai/provider新增 adapter。 - 在
internal/model/ai/usecaseregistry 註冊 provider 與 models。 - 確保 adapter 回傳統一
StreamEvent。 - 不要改
logic/ai的 SSE 格式。
Job Worker 擴充
新增 job step 時優先註冊 runner handler:
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 框架。
視覺架構(登入前後共用)
全頁背景 .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 文案。
技術棧與指令
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 與場景樣式唯一來源
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;偏好存localStoragekey: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,不要用全黑按鈕。
圓角與陰影
--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/dataenvelope);需登入加{ 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讀。
新增頁面流程
- 在
App.tsx掛路由(需登入的放在Layout底下,自動享有hx-scene+ 頂欄 + 主內容auth-ticket)。 - 頁面內用
PageTitle(含.ac-title-bar小標)+Card/ac-bulletin+ui.tsx元件;色票只引用 semantic token。 - 若需新語意色,先改
index.css的--hx-*與@theme,再改元件;不要頁面內硬編色碼。 - 新導覽項:改
acAssets.ts的navApps,並在AcIcon補 SVG path。 - 完成後執行
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)。
可選增強(擇一):
useIslanderPage({ title, purpose, hints, suggestions })— 頁面內動態註冊說明registerIslanderPage(/^\/foo/, { title, ... })— 在siteGuide.ts或模組 init 靜態註冊- HTML 慣例:
data-islander-label(元素名稱)、data-islander-kind(類型)、data-islander-ignore(排除)、data-islander-page-title(頁名)
Action 協定(AI 回覆末尾):
[{ "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。
驗證
完成變更後至少執行:
cd haixun-backend
go mod tidy
make fmt
go test ./...
有動到前端時另執行:
make web-build