project-rules
daodao-f2e 專案開發規範,涵蓋 package 使用、API 錯誤處理、命名慣例、程式碼風格
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
daodao-f2e 專案開發規範,涵蓋 package 使用、API 錯誤處理、命名慣例、程式碼風格
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
Push 後收集 PR 上的所有 review feedback(CI、AI Code Review、Gemini Code Assist、人類 reviewer),分析並詢問使用者要修正哪些
在 iOS 模擬器上跑 apps/mobile(Expo)。涵蓋 prebuild 過期、code signing、模擬器選錯、Metro 舊 bundle、i18n key 對不到等已知陷阱與除錯法
daodao-f2e codebase 地圖——monorepo 結構、依賴方向、常見任務觸點、隱藏的 build 依賴。開工前先讀,省去盲目探索
島島 daodao 六個 repo 的系統全景圖——服務職責、呼叫關係、types 同步鏈、跨 repo 變更 SOP。任何變更可能影響其他 repo 時必讀
Push 前 review 整個 branch 的變更,檢查邏輯錯誤、安全問題、效能問題、架構一致性
commit 前執行 lint、typecheck 檢查,自動修復可修的錯誤
| name | project-rules |
| description | daodao-f2e 專案開發規範,涵蓋 package 使用、API 錯誤處理、命名慣例、程式碼風格 |
nvm use 20.19.4)必須用 @daodao/config,禁止 process.env。
import { getEnv, getRequiredEnv } from "@daodao/config";
const apiUrl = getEnv("NEXT_PUBLIC_API_URL", "https://api.example.com");
必須用 @daodao/shared 的 getStorage,禁止直接用 localStorage / sessionStorage。
import { getStorage, StorageEnum } from "@daodao/shared";
const userStorage = getStorage<UserInfo>(StorageEnum.UserInfo);
可用 key 的權威清單在 packages/shared/src/lib/storage.ts 的 StorageEnum(本文件不窮舉,清單會過時——盤點時已有 11 個 key,如 UserInfo、Quiz、ManualPracticeDraft、ActionMaker、AuthSignal 等)。
新增 key:在 StorageEnum 加 key → mapStorageKeyToStorageType 定義 local/session 類型。
注意:getStorage 是 SSR-safe 且靜默失敗(SSR 或例外時回 no-op),呼叫端不可假設寫入成功。
必須用 @daodao/ui,禁止直接用原生 HTML 元素(<button>、<input> 等),除非 @daodao/ui 沒有。
import { Button } from "@daodao/ui/components/button";
必須用 @daodao/shared,禁止重複實作。匯出的權威清單在 packages/shared/src/index.ts(本文件不窮舉)。寫新 hook/util 前先 grep 該檔確認是否已存在。
useScrollLock、useMediaQuery(含 useIsMobile 等 breakpoint 變體)、useQueryState、useFormDraftgetStorage、captureElementAsImage、parseTextLinks、getShareAPI必須用 @daodao/api,禁止 fetch / axios。
App 端只 import domain 匯出與 useMutate(useQuery/client 沒有從 package 對外匯出):
import { useCurrentUser, createPractice, useMutate } from "@daodao/api";
packages/api/src/services/ 內部才用相對路徑取 client 與 hooks 工廠:
import { client } from "../client";
import { useQuery } from "../hooks";
已知例外(歷史債務,不可仿效):action-maker feature 直接 fetch worker、recommendation/showcase hooks 直接 fetch ai-backend。新程式碼一律走 @daodao/api 的模式。
必須用 date-fns,禁止原生 Date 方法(toLocaleDateString()、setDate() 等)。
import { format, addDays, parse } from "date-fns";
所有 API 呼叫必須檢查 response.error。openapi-fetch 不拋異常,錯誤在 response.error 裡。
const response = await createPractice(apiRequest);
if (response.error) {
const errorMessage =
response.error && typeof response.error === "object" && "message" in response.error
? String(response.error.message)
: "操作失敗";
toast.error(errorMessage);
setIsSubmitting(false);
return; // 必須 return
}
// 成功邏輯
clearDraft();
router.push("/success");
response.errorresponse.error.message)toast.error() 顯示return 避免執行成功邏輯console.error() 記錄if ("details" in error && Array.isArray(error.details)) {
const details = error.details as Array<{ path?: string; message?: string }>;
details.forEach((detail) => {
if (detail.path && detail.message) {
form.setError(detail.path as keyof FormValues, {
type: "server",
message: detail.message,
});
}
});
}
放在 apps/product/src/constants/,const object + type 模式:
export const TaskStatus = {
draft: "draft",
notStarted: "not-started",
inProgress: "in-progress",
completed: "completed",
} as const;
export type TaskStatus = typeof TaskStatus[keyof typeof TaskStatus];
禁止直接用字串字面量表示常數值。
現有常數以 apps/product/src/constants/ 目錄為準(本文件不窮舉——盤點時已有十多個,如 MoodType、TaskStatus、PracticeStatus、NotificationType、ReactionType 等)。新增前先 grep 該目錄避免重複。
Type 後綴(TaskStatusType)I 前綴(ICheckInFormData)Props 後綴(ButtonProps)(() => { ... })()順序:Imports → Types → Query Hooks → Mutation Hooks
"use client";
// Imports
import { client } from "../client";
import { useQuery } from "../hooks";
// Types
export type CreateRequestType = components["schemas"]["CreateRequest"];
// Query Hooks
export const useResource = (id: string) => useQuery("/api/v1/resource/{id}", { params: { path: { id } } });
// Mutation Hooks
export const createResource = async (data: CreateRequestType) => client.POST("/api/v1/resource", { body: data });