diff --git a/.env.example b/.env.example index 30f126f..8575ca4 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ +# Vite dev proxy 目標後端 URL。 +VITE_PROXY_TARGET=http://192.168.89.54:9002 + # Vite API base URL。 # 使用 Vite dev proxy 時,建議維持相對路徑。 VITE_API_BASE_URL=/service/api diff --git a/.gitignore b/.gitignore index 0384e36..b9676e2 100644 --- a/.gitignore +++ b/.gitignore @@ -36,13 +36,11 @@ output/playwright/ /blob-report/ /playwright/.cache/ /playwright/.auth/ -# Added by code-review-graph -.code-review-graph/ -.codex/config.toml -.agents/ -.claude/ -.ruler/ -.playwright/ -.mcp.json -.opencode.json +.codex +.agents +.claude +.ruler +.playwright +opencode.json +.antigravitycli diff --git a/AGENTS.md b/AGENTS.md index 264ecba..09a228c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,12 +4,34 @@ - Follow the existing code style and patterns. - Use pnpm for running project commands. - Keep code in TypeScript unless migration is required. -- When refactoring or creating new components, review `docs/frontend-layering.md` first and follow its layering and responsibility guidelines. -- When a change affects LLM editing boundaries, page creation flow, layout usage, login-page boundaries, or frontend layering rules, update `docs/llm-development-guide.md` in the same change. +- Before modifying or adding files in a `src/` subdirectory, read the corresponding `src/**/GUIDE.md` to understand the layer's constraints and conventions. Use `docs/llm-development-guide.md` as the index to find which GUIDE applies. +- When the change introduces a new pattern, directory, or convention that affects layer boundaries, create or update the relevant `src/**/GUIDE.md` and ensure `docs/llm-development-guide.md` indexes it. +- When refactoring or creating new components, review `docs/architecture-strategy.md` first and follow its layering and responsibility guidelines. - For UI debugging that spans implementation, state flow, and live browser behavior, use subagents deliberately: one for component contracts and event flow, one for data sources / routing / integration, and one for live browser verification. Do not edit files until the evidence from those threads converges on a root cause. - Treat subagent output as scoped evidence, not as a final answer. Use subagents to narrow the search space, confirm or eliminate hypotheses, and reduce local context load before making edits. - When a problem is directly tied to Vuetify component behavior, props, slots, accessibility output, or generated DOM, consult Vuetify MCP and official Vuetify API documentation before changing the implementation. Prefer supported Vuetify props, slots, and documented extension points over custom replacements unless the documented API is insufficient. +## Naming Generalization Rule +- This project is a **template** intended to be reused across different data domains (student, course, teacher, etc.). +- **Reusable abstractions** (Page Components, Sections, Items, generic composables, base components) **must not contain domain-specific names** (e.g., `Student`, `Course`) in their file names, type names, or export names. +- Domain-specific names are **only allowed** in: + - `src/models/.ts` — domain models + - `src/stores/.ts` — domain stores + - `src/services/modules/.ts` — service modules +- Examples of correct vs. incorrect naming: + - ❌ `PageStudentMaintenance.vue` → ✅ `PageMaintenance.vue` + - ❌ `useStudentMaintenancePage.ts` → ✅ `useMaintenancePage.ts` + - ❌ `ItemStudentRow.vue` → ✅ `ItemDataRow.vue` + - ❌ `useStudentCrudCommands.ts` → ✅ `useCrudCommands.ts` + - ✅ `models/student.ts`, `stores/students.ts` — domain layer, specific names are correct + +## GUIDE.md 寫作規則 + +- `src/**/GUIDE.md` 只保留該層/目錄的**約束、慣例與索引**,不要塞入詳細 API 文件。 +- 當新增 pattern、目錄或慣例影響層邊界時,建立或更新對應的 `src/**/GUIDE.md`,並確保 `docs/llm-development-guide.md` 將其列入索引。 +- 元件的 Props/Slots/Emits 詳細說明放在各子目錄的 `GUIDE.md`(如 `src/components/base/GUIDE.md`、`src/components/sections/GUIDE.md`),不要放在上層 `src/components/GUIDE.md`。 +- **新增 page/section 元件時,必須一併描述「視覺特徵」**:說明畫面上出現哪些元素(如標題卡片、按鈕類型、表格位置),讓 LLM 能從截圖或設計稿判斷該用哪個元件。視覺特徵寫在對應子目錄 `GUIDE.md` 的「視覺特徵」小節。 + ## Stack - Framework: Vue 3 + Vite - UI Library: Vuetify diff --git a/README.md b/README.md index 5e0dd57..28134c3 100644 --- a/README.md +++ b/README.md @@ -67,15 +67,6 @@ pnpm dev cp .env.example .env ``` -常用設定: - -```env -VITE_API_BASE_URL=/service/api -VITE_SKIP_LOGIN=false -VITE_DEV_DEFAULT_USER_ID= -VITE_DEV_DEFAULT_PASSWORD= -``` - `client.ts` 會優先使用 `VITE_API_BASE_URL`,否則預設 `/service/api`。開發模式下,Vite proxy 會將 `/service/*` 轉送到後端。 實際 `.env` 與 `.env.*.local` 不應提交。production API URL 應由使用專案自己的部署環境提供。 diff --git a/docs/analyse-now.md b/docs/analyse-now.md new file mode 100644 index 0000000..879c923 --- /dev/null +++ b/docs/analyse-now.md @@ -0,0 +1,47 @@ +## 二、我們專案的現況診斷 + +### 2.1 App.vue 過度臃腫(~590 行) + +| 職責 | 行數 | 應屬層級 | +|------|------|----------| +| Layout 切換 | ~20 | App Shell | +| Tabs 管理 | ~80 | Page Driver | +| Breadcrumb 組裝 | ~40 | Layout | +| Favorites 管理 | ~60 | Store | +| Search Dialog | ~80 | App Shell / Widget | +| Message Dialog | ~60 | App Shell / Widget | +| Snackbar | ~10 | Global Overlay | +| Logout / Force logout | ~30 | Auth Flow | +| HTTP Toast | ~20 | Service Layer | + +- **問題**:App.vue 同時承擔 App Shell、Page Driver、Global Widget、Auth Flow 四種責任。 +- **對比**:App Store 的 `App.svelte` 只有 161 行,只負責 `Navigation + PageResolver + Footer`。 + +### 2.2 Views 過厚(SingleRecord.vue ~830 行) + +- 混雜:表格呈現、搜尋表單、dialog 模板、表單狀態、CRUD 流程、驗證邏輯、分頁、snackbar。 +- **對比**:App Store 的 `ProductPage.svelte` 只有 77 行,只負責「把 page 轉成 DefaultPageRequirements + 一個 slot override」。 + +### 2.3 缺乏統一的頁面資料門面 + +``` +現況: + view → store → service(直接鏈式呼叫) + view 自己管理 loading / error / dialog visible + +App Store: + UI → jet.dispatch(intent) → runtime → controller → page model + UI 只接收 page model,不管理載入狀態 +``` + +### 2.4 Dialog 狀態與模板內嵌於 View + +- `SingleRecord.vue` 內含 5 個 `ConfirmDialog` 實例 + 1 個大 form overlay。 +- 任何 dialog 更動都需要修改 view 檔案。 + +### 2.5 沒有容器/內容分離的 Section 層 + +- 表格、表單、搜尋區塊都是直接寫在 view 或 page component 中。 +- 缺乏類似 `ShelfItemLayout` 的通用佈局抽象:「這一區是水平捲軸還是網格」應該由容器決定,裡面的內容元件不應該知道。 + +--- diff --git a/docs/architecture-strategy.md b/docs/architecture-strategy.md index d2ab4c7..9159571 100644 --- a/docs/architecture-strategy.md +++ b/docs/architecture-strategy.md @@ -1,139 +1,19 @@ # 資料流與元件分層優化策略 -> 分析標的:`~/code/apps.apple.com-main`(Apple App Store Web — Svelte + Jet 架構) -> 適用目標:`/home/carl/git/skt-vuetify-templates`(Vue 3 + Vuetify + Pinia) -> 原則:不強制遷移既有程式碼,但所有新增功能與重構以本文件為最高準則。 - ---- - ## 一、Apple App Store 專案的核心架構特徵 -### 1.1 單一業務邏輯門面(Jet Facade) +1 單一業務邏輯門面 +2 Intent / Action 分離(查詢與命令) +3 Page Model 驅動 UI(資料驅動) +4 Shelf / Item 分層(容器與內容分離) +5 Svelte Context 作為跨層依賴注入 +6 命令式外殼 + 聲明式 UI -``` -browser.ts → bootstrap → Jet.load → runtime + objectGraph - ↓ - UI 層僅透過 jet.dispatch(intent) / jet.perform(action) 溝通 -``` - -- **Jet** 封裝所有業務邏輯:路由、資料取得、動作分發、metrics。 -- UI 層**不直接**呼叫 API、不直接操作 storage、不直接操作 history。 -- 所有外部依賴(fetch、storage、locale、user)統一注入 `Dependencies`,再組裝成 `ObjectGraph`。 - -### 1.2 Intent / Action 分離(查詢與命令) - -| 類型 | 職責 | 回傳值 | 例子 | -|------|------|--------|------| -| **Intent** | 取得頁面資料(Query) | `Promise` | `RouteUrlIntent` → 回傳 `ProductPage` | -| **Action** | 執行副作用(Command) | `'performed' \| 'unsupported'` | `FlowAction` → 導航到新頁面 | - -- `FlowAction` 是主要導航機制:內含 `destination Intent` + `pageUrl` + `presentationContext`。 -- Action handler 註冊採用**型別註冊制**:`jet.onAction('flowAction', handler)`。 - -### 1.3 Page Model 驅動 UI(資料驅動) - -```ts -// App.svelte 的介面極簡 -export let page: Promise | Page -``` - -- 整個應用由單一 `page` prop 驅動。 -- `PageResolver` 處理 `Promise` 的 loading / error 狀態。 -- `Page.svelte` 用 type guard 分發到對應的 page component:`isProductPage(page)` → ``。 -- **Page 是 union type**,不是 route-based 的硬編碼映射。 - -### 1.4 Shelf / Item 分層(容器與內容分離) - -``` -Page (TodayPage / ProductPage / ...) - └── Shelf[] (水平捲軸 / 網格) - └── ShelfItemLayout (佈局抽象:HorizontalShelf or Grid) - └── Item (BrickItem / LargeLockupItem / ...) -``` - -- **Shelf** = 容器邏輯:決定是水平捲軸還是網格、rowsPerColumn、邊框。 -- **ShelfItemLayout** = 佈局中介:根據 `isHorizontal` 選擇 `HorizontalShelf` 或 `Grid`。 -- **Item** = 純粹內容渲染:只關心單一資料單位的呈現,不知道自己是水平還是網格。 -- **FallbackShelf** = 優雅的降級策略:遇到未實作的 shelf 類型顯示 placeholder,不 crash。 - -### 1.5 Svelte Context 作為跨層依賴注入 - -```ts -// bootstrap.ts -context.set('jet', jet) -context.set('i18n', i18nStore) - -// 深層元件 -const jet = getJet() // 從 Svelte Context 取得 -const i18n = getI18n() // 從 Svelte Context 取得 -``` - -- 避免 props drilling:Jet、i18n、accessibility layout、today-card layout 都透過 context 傳遞。 -- Context 在啟動時注入,生命周期與應用一致,不是用來傳遞 UI 狀態的。 - -### 1.6 命令式外殼 + 聲明式 UI - -```ts -// browser.ts(命令式啟動層) -const app = new App({ target: container, context, hydrate: true }) -registerActionHandlers({ - jet, - updateApp: (props) => app.$set(props), // 橋接命令式 → 聲明式 -}) -``` - -- 導航、歷史管理、scroll 復原由命令式的 action handler 處理。 -- UI 渲染完全聲明式,只接收 `page` 與 `isFirstPage` 兩個 prop。 - ---- +Read only when needed: [what apple do](./what-apple-do.md) ## 二、我們專案的現況診斷 -### 2.1 App.vue 過度臃腫(~590 行) - -| 職責 | 行數 | 應屬層級 | -|------|------|----------| -| Layout 切換 | ~20 | App Shell | -| Tabs 管理 | ~80 | Page Driver | -| Breadcrumb 組裝 | ~40 | Layout | -| Favorites 管理 | ~60 | Store | -| Search Dialog | ~80 | App Shell / Widget | -| Message Dialog | ~60 | App Shell / Widget | -| Snackbar | ~10 | Global Overlay | -| Logout / Force logout | ~30 | Auth Flow | -| HTTP Toast | ~20 | Service Layer | - -- **問題**:App.vue 同時承擔 App Shell、Page Driver、Global Widget、Auth Flow 四種責任。 -- **對比**:App Store 的 `App.svelte` 只有 161 行,只負責 `Navigation + PageResolver + Footer`。 - -### 2.2 Views 過厚(SingleRecord.vue ~830 行) - -- 混雜:表格呈現、搜尋表單、dialog 模板、表單狀態、CRUD 流程、驗證邏輯、分頁、snackbar。 -- **對比**:App Store 的 `ProductPage.svelte` 只有 77 行,只負責「把 page 轉成 DefaultPageRequirements + 一個 slot override」。 - -### 2.3 缺乏統一的頁面資料門面 - -``` -現況: - view → store → service(直接鏈式呼叫) - view 自己管理 loading / error / dialog visible - -App Store: - UI → jet.dispatch(intent) → runtime → controller → page model - UI 只接收 page model,不管理載入狀態 -``` - -### 2.4 Dialog 狀態與模板內嵌於 View - -- `SingleRecord.vue` 內含 5 個 `ConfirmDialog` 實例 + 1 個大 form overlay。 -- 任何 dialog 更動都需要修改 view 檔案。 - -### 2.5 沒有容器/內容分離的 Section 層 - -- 表格、表單、搜尋區塊都是直接寫在 view 或 page component 中。 -- 缺乏類似 `ShelfItemLayout` 的通用佈局抽象:「這一區是水平捲軸還是網格」應該由容器決定,裡面的內容元件不應該知道。 - ---- +Read only when needed: [analyse now](./analyse-now.md) ## 三、優化後的資料流策略 @@ -188,7 +68,7 @@ App Store: ```ts // src/composables/usePageDriver.ts -export function useStudentMaintenancePage() { +export function useMaintenancePage() { const studentStore = useStudentStore() const { records, loading, error, load } = useCrudDriver({ store: studentStore, @@ -264,7 +144,7 @@ src/ │ ├── components/ │ ├── pages/ ← 新增:Page Component 層 -│ │ ├── PageStudentMaintenance.vue +│ │ ├── PageMaintenance.vue │ │ └── PageReport.vue │ │ │ ├── sections/ ← 新增:Section / Shelf 層 @@ -273,7 +153,7 @@ src/ │ │ └── SectionFormPanel.vue │ │ │ ├── items/ ← 新增:Item / Atom 層(領域獨立) -│ │ ├── ItemStudentRow.vue +│ │ ├── ItemDataRow.vue │ │ └── ItemFormField.vue │ │ │ ├── layouts/ ← 維持:App Shell Layout @@ -285,11 +165,11 @@ src/ │ ├── composables/ │ ├── page-drivers/ ← 新增:頁面資料協調 -│ │ └── useStudentMaintenancePage.ts +│ │ └── useMaintenancePage.ts │ ├── commands/ ← 新增:命令流程(對齊 Jet Action) -│ │ └── useStudentCrudCommands.ts +│ │ └── useCrudCommands.ts │ ├── forms/ ← 維持/重組:表單狀態機 -│ │ └── useStudentForm.ts +│ │ └── useForm.ts │ └── layout/ ← 維持 │ ├── models/ ← 新增:領域模型與 Page Union @@ -321,15 +201,18 @@ src/ ```vue ``` @@ -340,7 +223,7 @@ load() - **對齊**:App Store 的 `ProductPage.svelte`、`TodayPage.svelte`、`DefaultPage.svelte`。 ```vue - +