9.7 KiB
9.7 KiB
src/AGENTS.md
Purpose
src/ 係全疊應用程式碼:Astro 路由、React/Chakra 前台 UI、D1/Drizzle 資料層、基礎工具,以及 theme 同 layout。
Ownership
- 擁有
src/全層,亦直接擁有以下冇獨立 child doc 的部分:theme/system.ts— Chakra system(tokens / semanticTokens / fonts / globalCss),前台顏色同字體的唯一來源。現行設計:editorial 極簡風(暖米白#FAF8F4底、琥珀金brandaccent、墨黑ink、Noto Serif HK 標題),詳見docs/superpowers/specs/2026-09-11-editorial-redesign-design.md。layouts/Base.astro— 前台 SEO head(canonical / OG / Twitter / theme-color / JSON-LDjsonLdprop / sitemap link)+ Noto Sans HK(非阻塞載入,media="print" onload+<noscript>fallback)+ 同意後先載入嘅 analytics(gaId/metaPixelIdprops,見下面 Analytics & consent)。layouts/AdminLayout.astro— 後台外框:墨黑左側 sidebar(/admin總覽、內容管理、系統分組)+米白內容區,純 CSS、色值集中喺 CSS variables(--ink/--brand/--bg等),手機用純 CSS checkbox 漢堡開合;同時保留 R2 上傳 script(ImageField依賴.image-url/.image-preview/.image-status)。config.ts— build-time 靜態頁用的SITE_NAME/SITE_TAGLINE(可管理內容一律放 D1,唔放呢度)。middleware.ts— 保護/admin:設locals.isAdmin,無ADMIN_PASSWORD回 503,未登入導向/admin/login。env.d.ts—App.Locals.isAdmin型別宣告。schemas/— Zod 驗證層:admin 表單輸入(post / content / case / settings / ai / keyword)同 AI 輸出格式;配合lib/form.ts的parseForm同lib/ai.ts的生成流程使用(lib/細節由lib/AGENTS.md擁有)。pages/— 路由層同/admin後台(詳見下面 Routes & SSR / Admin,因 Astro 限制冇獨立 child doc)。components/admin/ImageField.astro— 後台圖片欄位(URL text input + 揀檔上傳/預覽);client 端縮圖同 fetch/admin/upload嘅 script 喺layouts/AdminLayout.astro。
Local Contracts
- 架構:Astro 7(
output: "static"+ per-page SSR)+@astrojs/react+ Chakra UI v3(Emotion),單一 Cloudflare Worker。 - 環境變數:一律
getEnv()(src/lib/env.ts)。Astro v6 起已移除Astro.locals.runtime.env。 - 資料流:
.astro頁面 →getDb(getEnv().DB)→src/data/content.ts查詢 → props 傳畀 React island。元件唔直接讀 DB。 - 語言:所有 UI 文案、註解、後台文案都係繁體中文(廣東話),網站唔做雙語。
- 產生檔唔好手改:
dist/、.astro/、.wrangler/、worker-configuration.d.ts。
Routes & SSR(pages/)
- 預設靜態:
astro.config.mjs係output: "static"。要讀 D1 或 request-time 資料的頁面/endpoint 必須在檔案內寫export const prerender = false。- 目前 SSR:
index.astro、about.astro、privacy.astro、blog/index.astro、blog/[slug].astro、admin/**、media/[...key].ts、sitemap.xml.ts。 - 保持靜態:
robots.txt.ts。
- 目前 SSR:
- 環境變數:只用
getEnv(),且只可喺prerender = false的頁面/endpoint 用。 - 前台動態頁:讀 D1 → 傳 props 畀單一 React island(
client:idle,內容仍 SSR 出 HTML),並設邊緣快取Cache-Control: public, s-maxage=60, stale-while-revalidate=300。 - 404:
blog/[slug].astro找唔到文章 → 設Astro.response.status = 404並 render noindex 頁。 - Endpoint:用
APIRoute;sitemap.xml.ts由 D1 讀已發布文章(updatedAt做<lastmod>)並設s-maxage=3600。 - 圖片 endpoints:
/admin/upload(POST,受 middleware 保護,寫入 R2MEDIA);/media/[...key](GET,公開讀 R2 出圖,設Cache-Control: public, max-age=31536000, immutable同 ETag/304)。 - AI 生成(同步):
/admin/ai的生成喺 admin POST 內同步await完成先 redirect。如日後要改做背景處理,可用Astro.locals.cfContext(CloudflareExecutionContext)嘅waitUntil,唔使改現有流程。 - SEO:用
Base.astro;canonical / OG / sitemap 全部靠astro.config.mjs的site(現為佔位https://example.com,上線前要改)。所有頁面都要有單一h1,section 標題用h2、項目用h3,唔可以跳級(heading 語意一律用 Chakraas,唔可以淨靠字級)。JSON-LD 由lib/schema-org.ts砌,經Base.astro嘅jsonLdprop 輸出:首頁LocalBusiness+FAQPage、文章BlogPosting+BreadcrumbList。
Analytics & consent
- ID 放 D1:GA4 / Meta Pixel 的 ID 存
site_settings(ga4_measurement_id/meta_pixel_id,admin/admin/settings可改);空字串=唔載入,冇enabledboolean。因為係通用 key-value,唔使 migration,只需scripts/seed.sql有 default。 - 注入方式:
Base.astro收 optional propsgaId/metaPixelId;只有 SSR 頁讀到 D1 再傳落去(靜態頁要傳就必須轉prerender = false)。GA4 / Meta script 一律喺 consent 之後由 inline JS 動態 append,未同意前唔會有任何追蹤請求或 Cookie(亦冇 Meta<noscript>pixel)。 - Consent:單一接受/拒絕,存
localStoragekeycookie_consent(granted/denied);banner 同載入邏輯都喺Base.astro(純 HTML/CSS +is:inline,唔用 React)。已同意者每次載入即追蹤。 /privacy:pages/privacy.astro(SSR)記錄 Cookie / GA4 / Meta 用途,並提供「重設 Cookie 偏好」清除localStorage。- 免維護原則:唔使抄參考專案嘅 singleton 表/API/React 動態注入(本站係 MPA,每次載入=一次 pageview)。
Admin(pages/admin/)
- 所有
/admin路由必須export const prerender = false。 - 視覺:全部用
AdminLayout.astro的墨黑 sidebar shell,色值一律用其 CSS variables(唔好硬編色)。/admin係總覽 dashboard。 - 認證由
middleware.ts統一處理;登入喺admin/login.astro(checkPassword+createSession),登出喺logout.ts。 - UI:全部用
AdminLayout.astro,純 Astro SSR 表單(POST+formData),唔引入 React / Chakra。 - 流程:每個 POST 處理完
Astro.redirect返對應列表頁;/admin/ai用 POST/Redirect/Get +?ok=<key>+okMessages顯示成功提示(避免重複提交)。成功提示用.notice(唔好再用.badge.published)。 - 總覽:
index.astro(/admin)係 dashboard,資料由data/content.ts的getDashboardData(db)提供(統計卡/待辦提示/最近更新文章/系統狀態);AI secrets 由頁面層getEnv()讀。 - 表單驗證:所有 admin 表單經
parseForm(form, schema)(src/schemas/),失敗時以errors逐欄顯示,唔好手寫逐欄檢查。 - 通用動作:
add/save/delete/up/down(排序以交換sortOrder實作)。 - 通用內容編輯器:
content/[kind].astro,kind ∈ service | feature | step | faq(見db/schema.ts的CONTENT_KINDS),欄位標籤由檔案內META定義。 - 網站設定:
settings.astro用data/settings-fields.ts的SETTINGS_GROUPS/ALL_SETTING_KEYS產生表單,逐 key upsert。 - 文章:列表喺
posts.astro(/admin/posts)、編輯喺post/[id].astro(id === "new"為新增);slug 自動slugify並用uniqueSlug去重。文章存/刪後 redirect 去/admin/posts。 - AI 生成:
ai.astro(/admin/ai)管 AI 設定 + 關鍵字佇列,同步呼叫generateBlogPost;成功會 redirect 去新草稿/admin/post/<id>。生成按鈕用 inlineonsubmit顯示「生成中…」。 - 圖片:圖片欄位(settings 的
hero_image/og_image、cases.imageUrl、posts.coverImage)用components/admin/ImageField.astro,可揀檔即時上傳去 R2(bindingMEDIA),亦可貼 URL;client 端縮到最大寬 1600px 並轉 WebP 先上傳。上傳 endpoint/admin/upload(受 middleware 保護,scope ∈ settings | cases | posts、JPG/PNG/WebP/GIF、上限 8MB),出圖 endpoint/media/[...key](公開、immutablecache)。換圖/刪除時由lib/media.ts的deleteMedia清舊 R2 檔。 - 前台可見性靠
status(published/draft);列表頁顯示全部,前台只顯示 published。
Work Guidance
- 改前台視覺先睇
theme/system.ts的 tokens,優先重用語意 token,唔好散落硬編色值。 - 新增 endpoint 或 SSR 頁後,確認
prerender = false同相應快取 header 都有。 - 新增一個內容欄位:先改
db/schema.ts→npm run db:generate,再改對應schemas/輸入 schema、admin 表單同data/content.ts。 - 唔好喺
src/pages/放.md文件(例如 AGENTS.md):Astro 會將佢變成公開路由(例如/AGENTS、/admin/AGENTS)。Astro 只支援用_前綴豁免,但同 DOX 的AGENTS.md命名衝突,所以pages/的合約一律寫喺本文件。其餘src/子目錄的AGENTS.md(data/db/lib/components)唔會被路由。
Verification
npm run build(唯一的 build/type 驗證;冇 test/lint/typecheck script)。npm run dev(:4321)目測前台/Blog/後台;測/blog/[slug]404、登入/登出流程。
Child DOX Index
| Path | Scope |
|---|---|
components/site/AGENTS.md |
前台 React island 與 Chakra UI 元件 |
data/AGENTS.md |
D1 讀取查詢層與後台設定欄位定義 |
db/AGENTS.md |
Drizzle schema(migration 的唯一來源) |
lib/AGENTS.md |
auth / env / db / form / media / ai / markdown 基礎工具 |