13 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 草案。
新增 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/,風格參考 simular.co:明亮、年輕、圓角多、配色克制。不要把舊 Next.js / template-monorepo UI 搬進來,也不要引入重型 UI 框架。
技術棧與指令
web/
src/
api/ # API client(envelope、JWT refresh)
auth/ # AuthContext
components/ # Layout、ui、ThemeToggle、AuthShell
theme/ # ThemeContext(淺色 / 深色)
pages/ # 路由頁面
lib/ # 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 Light / 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;Layout 頂欄與AuthShell都要有。 - 禁止在元件裡寫死
bg-slate-*、text-emerald-*、bg-amber-*等 Tailwind 預設色;語意狀態用text-success/text-warning/text-danger或jobStatus.ts的 badge class。
淺色預設明亮藍白底;深色為深藍黑底。兩套都只允許 一個主色 brand(靛藍) + success / warning / danger 語意色,不要再加褐色、墨綠、多種 accent 亂配。
色彩 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)。Hero 背景用 class hero-panel;裝飾 blob 用 glow-blob / glow-blob-alt。
共用元件(優先復用)
新頁面必須從 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= 左側欄 + 頂部 sticky bar(UID +ThemeToggle)+Outlet。 - 已登入(手機):頂欄品牌 + 主題切換;導覽走
MobileBottomNav。 - 側欄分組:工作區(總覽、背景任務、排程、AI)、管理(模板、設定、會員、權限)。
- Active 導覽:
bg-brand text-white shadow-soft;hover:bg-brand-soft text-brand。 - 未登入:
AuthShell置中卡片 + 右上主題切換;背景用柔和 blob,不要花俏插圖牆。 - 語氣:年輕、直接、短句;Hero 可有一句主標 + brand 色強調詞,避免長篇企業八股。
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底下)。 - 頁面用
PageTitle+Card+ 既有元件;色票只引用 semantic token。 - 若需新語意色,先改
index.css的--hx-*與@theme,再改元件;不要頁面內硬編色碼。 - 完成後執行
make web-build。
前端禁忌
- 不要引入 MUI / Ant Design / Chakra 等大型 UI 庫。
- 不要為單頁新增第三套配色或漸層彩虹按鈕。
- 不要讓 SSE / AI 直接吃 provider 原始 chunk(後端已 normalize)。
- 不要用
offset/limit呼叫列表 API;用page/pageSize。
驗證
完成變更後至少執行:
cd haixun-backend
go mod tidy
make fmt
go test ./...
有動到前端時另執行:
make web-build