# 側邊欄選單分組 — 設計說明書（design.md）

| 欄位 | 內容 |
|------|------|
| Feature | 側邊欄選單分組（menu-group）|
| Phase | Phase 1 — Brainstorm Design |
| 版本 | v1.0 |
| 日期 | 2026-05-26 |
| 撰寫者 | Claude（協作對象：raymond）|
| 上游文件 | `docs/features/FR-029-2605-menu-group/requirement-detail.md` |
| 狀態 | 待 user review，review 通過後進 Phase 3 implementation-plan |

---

## 1. 設計目標與背景

### 1.1 問題

`public.ui_routes` 共 39 筆，全部 `pid=0`（一層扁平結構），側邊欄太冗長。

### 1.2 目標

按業務領域分成 **7 個分組**，每個分組是可展開父節點，下面掛對應功能入口。具體 7 個分組與包含項目見 `requirement-detail.md` §2。

### 1.3 設計原則

- **資料驅動**：所有選單結構靠 DB（`ui_routes`）+ 既有 `/user/web-menu` API 即可呈現，FE 不寫死任何項目。
- **最小改動**：DB schema 完全不動，BE 套件只加 1 段剪枝邏輯，主專案 BE 程式碼**零改動**。
- **向後相容**：既有 user 的 role/capabilities 完全不動，看到的選單內容與舊版一致，只是改為分組顯示。
- **權限友善**：分組節點不掛 capability，靠「無子節點自動隱藏」邏輯保持選單乾淨。

---

## 2. 整體 Architecture

三層改動：

```
┌─────────────────────────────────────────────────────────────┐
│  DB (compliance-manager-be)                                  │
│  ───────────────────────────                                 │
│  • 新增 9 筆 ui_routes (7 群組 + 2 個寫死搬遷項)              │
│  • Update 27 筆既有 ui_routes 的 pid / sort                  │
│  • schema 完全不動，只動資料                                  │
└─────────────────────────────────────────────────────────────┘
              ↓ /user/web-menu API
┌─────────────────────────────────────────────────────────────┐
│  BE Package (jedi-auth)                                      │
│  ─────────────────────                                       │
│  • user_service.get_user_web_ui_routes() 末段                │
│    加 prune_empty_groups() 剪枝邏輯                          │
│  • 移除「無 url 且 items 為空」的分組節點                     │
└─────────────────────────────────────────────────────────────┘
              ↓ JSON tree
┌─────────────────────────────────────────────────────────────┐
│  FE (compliance-manager-fe)                                  │
│  ──────────────────────────                                  │
│  • AppMenu.vue: 移除寫死 grcItems 陣列                       │
│  • i18n/zh-tw/menu.json + zh-cn: 補 7 個 group-* key         │
│  • AppMenuItem.vue 不動（既有 t() 邏輯自然 work）            │
└─────────────────────────────────────────────────────────────┘
```

主專案 BE 程式碼**零改動**。

---

## 3. DB Schema 變動

### 3.1 schema 變更

**無**。`public.ui_routes` 表結構完全不動，不加欄位、不加 constraint。

### 3.2 Migration 內容

**檔案位置**：`scripts/sql/2026-XX-XX-menu-group-restructure.sql`（日期由實作時補上）

#### Step 1：新增 9 筆 ui_routes

```sql
-- 7 個分組節點（pid=NULL，無 url，僅作為展開父節點）
INSERT INTO public.ui_routes (uid, pid, name, url, icon, enable, description, sort)
SELECT gen_random_uuid(), NULL, 'group-project-task',     NULL, 'pi pi-briefcase',  1, '專案與任務分組',   10
WHERE NOT EXISTS (SELECT 1 FROM public.ui_routes WHERE name = 'group-project-task');

INSERT INTO public.ui_routes (uid, pid, name, url, icon, enable, description, sort)
SELECT gen_random_uuid(), NULL, 'group-compliance-audit', NULL, 'pi pi-shield',     1, '合規與稽核分組',   20
WHERE NOT EXISTS (SELECT 1 FROM public.ui_routes WHERE name = 'group-compliance-audit');

INSERT INTO public.ui_routes (uid, pid, name, url, icon, enable, description, sort)
SELECT gen_random_uuid(), NULL, 'group-communication',    NULL, 'pi pi-comments',   1, '溝通與互動分組',   30
WHERE NOT EXISTS (SELECT 1 FROM public.ui_routes WHERE name = 'group-communication');

INSERT INTO public.ui_routes (uid, pid, name, url, icon, enable, description, sort)
SELECT gen_random_uuid(), NULL, 'group-tenant-org',       NULL, 'pi pi-building',   1, '租戶與組織分組',   40
WHERE NOT EXISTS (SELECT 1 FROM public.ui_routes WHERE name = 'group-tenant-org');

INSERT INTO public.ui_routes (uid, pid, name, url, icon, enable, description, sort)
SELECT gen_random_uuid(), NULL, 'group-system-admin',     NULL, 'pi pi-cog',        1, '系統管理分組',     50
WHERE NOT EXISTS (SELECT 1 FROM public.ui_routes WHERE name = 'group-system-admin');

INSERT INTO public.ui_routes (uid, pid, name, url, icon, enable, description, sort)
SELECT gen_random_uuid(), NULL, 'group-system-config',    NULL, 'pi pi-sliders-h',  1, '系統設定分組',     60
WHERE NOT EXISTS (SELECT 1 FROM public.ui_routes WHERE name = 'group-system-config');

INSERT INTO public.ui_routes (uid, pid, name, url, icon, enable, description, sort)
SELECT gen_random_uuid(), NULL, 'group-ai-analytics',     NULL, 'pi pi-chart-line', 1, 'AI 與分析分組',    70
WHERE NOT EXISTS (SELECT 1 FROM public.ui_routes WHERE name = 'group-ai-analytics');

-- 2 個寫死搬遷項（pid 指向 group-project-task）
WITH g AS (SELECT id FROM public.ui_routes WHERE name = 'group-project-task')
INSERT INTO public.ui_routes (uid, pid, name, url, icon, enable, description, sort)
SELECT gen_random_uuid(), g.id, 'project-dashboard', '/project/dashboard',  'pi pi-home',         1, '專案儀表板', 10 FROM g
WHERE NOT EXISTS (SELECT 1 FROM public.ui_routes WHERE name = 'project-dashboard');

WITH g AS (SELECT id FROM public.ui_routes WHERE name = 'group-project-task')
INSERT INTO public.ui_routes (uid, pid, name, url, icon, enable, description, sort)
SELECT gen_random_uuid(), g.id, 'my-tasks',          '/project/task-manage','pi pi-check-square', 1, '待辦任務',   20 FROM g
WHERE NOT EXISTS (SELECT 1 FROM public.ui_routes WHERE name = 'my-tasks');
```

#### Step 2：Update 27 筆既有 ui_routes 的 pid / sort

```sql
-- Group 1 (project-task)：sort 30/40/50（前面 10/20 給 project-dashboard / my-tasks 用了）
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-project-task'),     sort = 30 WHERE name = 'project-list';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-project-task'),     sort = 40 WHERE name = 'audit-manage';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-project-task'),     sort = 50 WHERE name = 'report-manage';

-- Group 2 (compliance-audit)：sort 10/20/30/40
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-compliance-audit'), sort = 10 WHERE name = 'module-frame';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-compliance-audit'), sort = 20 WHERE name = 'compliance-framework-manage';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-compliance-audit'), sort = 30 WHERE name = 'tool-plugin-manage';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-compliance-audit'), sort = 40 WHERE name = 'flow-template-manage';

-- Group 3 (communication)：sort 10/20/30/40/50/60
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-communication'),    sort = 10 WHERE name = 'bulletin-list';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-communication'),    sort = 20 WHERE name = 'bulletin-manage';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-communication'),    sort = 30 WHERE name = 'survey-manage';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-communication'),    sort = 40 WHERE name = 'system-feedback-manage';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-communication'),    sort = 50 WHERE name = 'system-feedback-view';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-communication'),    sort = 60 WHERE name = 'issue-integrate-config';

-- Group 4 (tenant-org)：sort 10/20/30/40
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-tenant-org'),       sort = 10 WHERE name = 'tenant-manage';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-tenant-org'),       sort = 20 WHERE name = 'department-manage';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-tenant-org'),       sort = 30 WHERE name = 'user-manage';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-tenant-org'),       sort = 40 WHERE name = 'role-manage';

-- Group 5 (system-admin)：sort 10/20/30/40/50
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-system-admin'),     sort = 10 WHERE name = 'system-menu-manage';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-system-admin'),     sort = 20 WHERE name = 'information-system-manage';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-system-admin'),     sort = 30 WHERE name = 'device-manage';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-system-admin'),     sort = 40 WHERE name = 'storage-config';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-system-admin'),     sort = 50 WHERE name = 'cloud-integrations';

-- Group 6 (system-config)：sort 10/20/30/40
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-system-config'),    sort = 10 WHERE name = 'smtp-config';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-system-config'),    sort = 20 WHERE name = 'ldap-config';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-system-config'),    sort = 30 WHERE name = 'notify-config';
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-system-config'),    sort = 40 WHERE name = 'user-log';

-- Group 7 (ai-analytics)：sort 10
UPDATE public.ui_routes SET pid = (SELECT id FROM public.ui_routes WHERE name = 'group-ai-analytics'),     sort = 10 WHERE name = 'ai-dashboard';
```

#### Step 3：執行注意事項

- **Transaction 包覆**：整份 migration 用 `BEGIN; ... COMMIT;` 包，全成或全敗。
- **權限帳號**：用 `cmmgr` 跑（避開 RLS 擋寫；`cm_app` 寫不進系統層級資料）。
- **註解時間標記**：每個語句加 `-- N. 說明 (2026-XX-XX)` 註解（依 CLAUDE.md 規範）。
- **Idempotent**：Step 1 用 `WHERE NOT EXISTS` 保護；Step 2 的 UPDATE 重複跑無害（目標 pid / sort 固定）。

### 3.3 未列項目（12 個 enable=0）— 完全不動

| ui_routes.name | enable | 處置 |
|---------------|--------|------|
| `workflow-setup` | 0 | 不動 pid（保持 0/NULL）、不動 sort、不動 enable |
| `workflow-view` | 0 | 同上 |
| `textbook-execution` | 0 | 同上 |
| `textbook-tested` | 0 | 同上 |
| `textbook-general` | 0 | 同上 |
| `resource-version-manage` | 0 | 同上 |
| `resource-manage` | 0 | 同上 |
| `cruise-project-manage` | 0 | 同上 |
| `cruise-feedback-view` | 0 | 同上 |
| `report-view` | 0 | 同上 |
| `customer-dashboard-editor` | 0 | 同上 |
| `user-guide` | 0 | 同上 |

→ BE 過濾 `enable=1`，這 12 個項目根本不出現在 `/user/web-menu` 回傳結果。不會變成「pid=0 的孤兒葉節點」與分組節點並列。

---

## 4. BE 改動：jedi-auth 套件剪枝邏輯

### 4.1 改動範圍

**檔案**：`~/Projects/Jedicogy/module/jedi-python-package/jedi-auth/jedi_auth/app/service/user_service.py`

**Method**：`get_user_web_ui_routes()`（line 438~528）

### 4.2 改動內容

在現有 `sort_nodes(roots)` 之後新增剪枝 step：

```python
@transaction
def get_user_web_ui_routes(self, user_uid: str, tenant_id: int | None) -> list[dict]:
    """
    取得使用者可檢視的前端路由選單（RBAC，多租戶版）
    ... (既有 docstring 保留)
    """
    # ... (既有邏輯不動：取使用者 capability、build node_map、組樹、sort) ...
    sort_nodes(roots)

    # 新增：剪枝空分組節點（無 url 且 items 為空的純分組節點）
    return self._prune_empty_groups(roots)

def _prune_empty_groups(self, nodes: list[dict]) -> list[dict]:
    """
    遞迴剪枝：移除「無 url 且 items 為空」的分組節點。
    葉節點（有 url）永遠保留；分組節點若還有可見子項也保留。

    使用情境：
    - 某 user 對「租戶與組織」分組下所有功能都無 capability → 該分組整個從選單消失
    - 葉節點 url 為 None（理論上不應發生）也會被當分組處理：實務上葉節點都應有 url
    """
    pruned = []
    for n in nodes:
        if n.get("items"):
            n["items"] = self._prune_empty_groups(n["items"])
        is_group = not n.get("url")
        if is_group and not n.get("items"):
            continue
        pruned.append(n)
    return pruned
```

### 4.3 Unit Test 新增

**檔案**：`~/Projects/Jedicogy/module/jedi-python-package/jedi-auth/tests/unittest/test_user_service.py`

新增 3 個 case（接在現有 `test_get_user_web_ui_routes_*` 系列後）：

1. **`test_get_user_web_ui_routes_prune_empty_group`**
   - 設置：mock `get_viewable_routes_by_user_uid` 回 1 個分組節點（無 url）+ 0 個子節點
   - 預期：回傳空 list（分組被 prune）

2. **`test_get_user_web_ui_routes_keep_group_with_visible_children`**
   - 設置：mock 回 1 個分組節點 + 2 個對應 pid 的子節點（都有 url）
   - 預期：分組節點保留，items 有 2 筆

3. **`test_get_user_web_ui_routes_keep_group_with_partial_visible_children`**
   - 設置：mock 回 1 個分組節點 + 1 個有 url 的子節點（另一個子節點被 capability 過濾掉，不在 mock 結果內）
   - 預期：分組節點保留，items 有 1 筆

### 4.4 套件版本 bump

整 feature 完成 + smoke 通過 + user 明確指示後才做：
- `jedi-auth/pyproject.toml`：`version = "0.0.X"` → `"0.0.X+1"`（patch bump，剪枝邏輯屬 bug-fix-style 增量改進）
- jedi-auth commit + push origin + 推 Nexus
- 主專案 `pyproject.toml` 還原 path → pin 新版本 + `poetry update jedi-auth`

---

## 5. FE 改動

### 5.1 `compliance-manager-fe/src/layout/AppMenu.vue`

```diff
 onMounted(()=> {
-    // GRC / Project module (static — no permission gate needed for demo)
-    const grcItems = [
-        { label: 'project-dashboard',  icon: 'pi pi-home',         to: '/project/dashboard' },
-        // { label: 'project-manage',     icon: 'pi pi-folder-open',  to: '/project/projects' },
-        { label: 'my-tasks',           icon: 'pi pi-check-square', to: '/project/task-manage' },
-    ]
-
-    menu.value = [...grcItems, ...genMenuNode(permissions.value)]
+    menu.value = genMenuNode(permissions.value)
 })
```

### 5.2 `compliance-manager-fe/src/config/locales/i18n/zh-tw/menu.json`

`"menu"` 物件內新增 7 個 key（建議排在現有同類 key 附近，或集中放在檔案末尾並加註解區隔）：

```json
{
    "menu": {
        ...
        "group-project-task":     "專案與任務",
        "group-compliance-audit": "合規與稽核",
        "group-communication":    "溝通與互動",
        "group-tenant-org":       "租戶與組織",
        "group-system-admin":     "系統管理",
        "group-system-config":    "系統設定",
        "group-ai-analytics":     "AI 與分析"
    }
}
```

### 5.3 `compliance-manager-fe/src/config/locales/i18n/zh-cn/menu.json`

對應補簡體版（用語由 FE 側決定，例：「项目与任务 / 合规与稽核 / 沟通与互动 / ...」）。若該 repo 有 `sync-translations.js` 工具，跑該工具自動產生候選翻譯後再 review。

### 5.4 i18n 英文版（en/menu.json）— Phase 1 不要求

若有 `en/menu.json` 且該 locale 啟用中，補英文翻譯（如 `"Projects & Tasks"` 等）；若 en locale 未啟用或未維護，本期不要求補。Phase 3 implementation-plan 階段確認。

### 5.5 `AppMenuItem.vue` — 不動

既有 `t(\`lang.menu.${item.label}\`)` 邏輯天然支援新增的 `group-*` key。**邊界 case**：分組節點 `to` 為 null 時的 render 行為，需在實作前 manual verify（Phase 5 開工前）— 預期既有 PrimeVue Menu 行為會自動 render 成可展開父節點而非可點擊連結。

---

## 6. Edge Cases & 風險

| Case | 預期行為 | 風險評估 |
|------|--------|---------|
| 既有 user（有 role + capabilities）| 看到完整 7 分組 + 對應可見葉節點，與舊版內容一致 | 無 — 葉節點 capability 沒動 |
| 新進 user（無 role）| 看到「專案儀表板 / 待辦任務」2 項（不掛 capability）+ 沒有任何分組標題（都被 prune）| 無 — 符合「2 項 always visible」設計 |
| Admin（super-admin role）| 看到完整 7 分組 + 全部葉節點 | 無 |
| FE i18n key 漏譯 | AppMenuItem 顯示 `lang.menu.group-xxx` 字串 | 低 — PR 前 verify zh-tw + zh-cn 都補齊即可 |
| AppMenuItem 對「無 url 父節點」處理 | 預期 render 為可展開父節點 | 中 — Phase 5 manual verify 後若不 work 需 wrap 條件判斷 |
| Migration 中斷 | DB 處於部分 update 狀態 | 低 — `BEGIN; ... COMMIT;` 全成或全敗 |
| 既有外部資料引用 sort 數值 | sort 改動影響該外部資料 | 已確認無 — `scripts/sql/` grep 過、BE 只用 sort 做排序、不依賴具體數值 |
| FE 早於 BE/DB 部署 | `/user/web-menu` 仍回扁平 tree，FE 拿掉寫死 → user 暫時看不到「專案儀表板 / 待辦任務」| 中 — 部署順序強制 BE/DB 先、FE 後 |
| BE 部署但 DB migration 未跑 | jedi-auth 剪枝邏輯 wrong tree（扁平 tree 無分組）→ 不影響 | 無 — 剪枝對扁平 tree 是 no-op |
| Rollback 需求 | 移除 9 筆新節點 + 還原 27 筆原 sort 值 | 中 — 需事先 dump 既有 sort 備份；Rollback SQL 見附錄 C |

---

## 7. 部署順序

### 7.1 Dev 階段（feature 開發中）

1. 確認 working branch 為 `feature/menu-group`（user 自行切換，Claude 不切 branch）。
2. 主專案 `pyproject.toml`：把 `jedi-auth` 改成 path dependency（**此改動不 commit**）。
3. 改 jedi-auth source code（剪枝邏輯 + tests）— jedi-auth 內 commit + push origin（不 bump version、不推 Nexus）。
4. 寫主專案 SQL migration、FE 檔案。
5. 跑 migration（`cmmgr` 帳號）+ BE 重啟 + smoke test：
   - 用一般 user 登入確認看到完整分組
   - 用 admin 登入確認看到 7 分組 + 全部葉節點
   - 用新進無 role user 確認只看到 2 項固定入口
   - zh-tw / zh-cn 切換確認分組標題正確翻譯

### 7.2 Feature 完成 + smoke 通過後（user 明確下指令才做）

1. jedi-auth `pyproject.toml` bump version + commit + push origin。
2. jedi-auth 推 Nexus。
3. 主專案 `pyproject.toml` 還原 path 為 pin 新版本。
4. 主專案跑 `poetry update jedi-auth`。
5. 主專案 commit (含 migration + pyproject.toml + poetry.lock)。
6. FE commit。
7. user 明確指示後 push。

### 7.3 正式環境部署

**順序強制**：BE → DB migration → FE

| 步驟 | 動作 | 為何這個順序 |
|------|------|-------------|
| 1 | 部署 BE（含新 jedi-auth）| 剪枝邏輯先就緒。即使 DB 還沒跑，扁平 tree 對剪枝是 no-op，不影響舊行為 |
| 2 | DB migration | 既有 user 立刻看到分組結構（BE 已備好剪枝邏輯）|
| 3 | 部署 FE | AppMenu.vue 拿掉寫死 + i18n 翻譯生效 |

**為何 FE 不能先部署**：若 FE 先拿掉 grcItems 但 BE/DB 仍回扁平 tree → user 暫時看不到「專案儀表板 / 待辦任務」。

---

## 8. 測試

### 8.1 jedi-auth 套件 unit test

依 §4.3 新增 3 個 case。

### 8.2 主專案 BE smoke test（manual）

```sql
-- 跑完 migration 後驗證
SELECT name, pid, sort FROM public.ui_routes WHERE name LIKE 'group-%' ORDER BY sort;
-- 預期 7 row

SELECT name, pid, sort FROM public.ui_routes WHERE pid IS NOT NULL ORDER BY pid, sort;
-- 預期 29 row（27 既有 + 2 新增搬遷項）
```

### 8.3 FE manual E2E（部署前 verify）

| Scenario | 預期 |
|----------|------|
| admin user 登入 | 看到 7 個分組標題 + 全部葉節點 |
| 一般 user 登入 | 看到對應 role 的可見分組 + 葉節點 |
| 新進無 role user 登入 | 只看到「專案儀表板 / 待辦任務」2 項、沒有任何分組標題 |
| zh-tw 切換到 zh-cn | 分組標題顯示簡體 |
| 點開可展開分組 | 顯示底下葉節點清單 |
| 點葉節點 | 正常跳轉到對應路由 |
| 點分組節點本身（無 url）| 不跳轉，僅 toggle 展開/收合 |

### 8.4 重型自動化 E2E — 不需要

本期屬 UI 重組，BE 邏輯改動範圍小（單個套件函式末段 prune），不需要在 `compliance-manager-test` repo 加 Cucumber/Playwright scenarios。

---

## §11 Implementation Reality（2026-05-26 實作後補記）

### §11.1 AppMenuItem.vue 必須修改（與 §5.5 預測不符）

**設計預測（§5.5）**：「AppMenuItem.vue 不動，既有 PrimeVue Menu 行為自然 work」

**實際情況**：AppMenuItem.vue template 只有單一 `<router-link>`，沒有遞迴 render sub-items 的區塊。分組結構上線後葉節點完全看不到、分組節點因 `router-link :to=undefined` 導向首頁。

**修正內容**（commit `0d1ca8b`）：
- 分組節點改用 `<a @click.prevent>` 取代 `<router-link>`
- 加 `<ul v-show="isActiveMenu"><app-menu-item>` 遞迴區塊

**教訓**：設計文件標注「需 Phase 5 manual verify」的邊界 case，實作前必須先手動確認，不能交由 subagent 假設。

---

## 9. Phase 3 implementation-plan 預計拆 task 結構

供後續 Phase 3 plan 參考（**不在本 design.md 詳列實作步驟**）：

1. **Task A：jedi-auth 套件改動**（剪枝邏輯 + 3 unit tests）
2. **Task B：主專案 path dependency dev 模式 setup**（不 commit）
3. **Task C：主專案 DB migration SQL 撰寫 + dry-run**
4. **Task D：FE AppMenu.vue 改 + i18n menu.json 補 key**
5. **Task E：跨 repo smoke test**（一般 / admin / 新進 user × zh-tw / zh-cn）
6. **Task F：收尾 — jedi-auth bump + Nexus 推送 + 主專案 pin 還原 + commit**

---

## 附錄 A：完整對照表（最終 ui_routes 狀態）

| Group | id（runtime） | name | url | sort | source |
|-------|--------------|------|-----|------|--------|
| —     | new | `group-project-task`         | NULL | 10 | 本期新增 |
| —     | new | `group-compliance-audit`     | NULL | 20 | 本期新增 |
| —     | new | `group-communication`        | NULL | 30 | 本期新增 |
| —     | new | `group-tenant-org`           | NULL | 40 | 本期新增 |
| —     | new | `group-system-admin`         | NULL | 50 | 本期新增 |
| —     | new | `group-system-config`        | NULL | 60 | 本期新增 |
| —     | new | `group-ai-analytics`         | NULL | 70 | 本期新增 |
| 1     | new | `project-dashboard`          | `/project/dashboard` | 10 | 本期搬遷 |
| 1     | new | `my-tasks`                   | `/project/task-manage` | 20 | 本期搬遷 |
| 1     | 8   | `project-list`               | `/project/projects` | 30 | 既有 update pid/sort |
| 1     | 37  | `audit-manage`               | `/project/audit-manage` | 40 | 既有 update pid/sort |
| 1     | 15  | `report-manage`              | `/project-summary-report` | 50 | 既有 update pid/sort |
| 2     | 24  | `module-frame`               | `/module-frame/module-frame` | 10 | 既有 update pid/sort |
| 2     | 33  | `compliance-framework-manage`| `/compliance-framework/compliance-framework-manage` | 20 | 既有 update pid/sort |
| 2     | 25  | `tool-plugin-manage`         | `/plugin/tool-plugin-manage` | 30 | 既有 update pid/sort |
| 2     | 40  | `flow-template-manage`       | `/flow/template-manage` | 40 | 既有 update pid/sort |
| 3     | 6   | `bulletin-list`              | `/bulletin/bulletin-list` | 10 | 既有 update pid/sort |
| 3     | 7   | `bulletin-manage`            | `/bulletin/bulletin-manage` | 20 | 既有 update pid/sort |
| 3     | 11  | `survey-manage`              | `/survey/manage` | 30 | 既有 update pid/sort |
| 3     | 12  | `system-feedback-manage`     | `/feedback/system-feedback-manage` | 40 | 既有 update pid/sort |
| 3     | 13  | `system-feedback-view`       | `/feedback/system-feedback-view` | 50 | 既有 update pid/sort |
| 3     | 32  | `issue-integrate-config`     | `/system/issue-integrate-config` | 60 | 既有 update pid/sort |
| 4     | 31  | `tenant-manage`              | `/tenant/tenant-manage` | 10 | 既有 update pid/sort |
| 4     | 29  | `department-manage`          | `/department/department-manage` | 20 | 既有 update pid/sort |
| 4     | 2   | `user-manage`                | `/auth/user-manage` | 30 | 既有 update pid/sort |
| 4     | 3   | `role-manage`                | `/auth/role-manage` | 40 | 既有 update pid/sort |
| 5     | 21  | `system-menu-manage`         | `/system/system-menu-manage` | 10 | 既有 update pid/sort |
| 5     | 36  | `information-system-manage`  | `/information-system/manage` | 20 | 既有 update pid/sort |
| 5     | 30  | `device-manage`              | `/device/device-manage` | 30 | 既有 update pid/sort |
| 5     | 34  | `storage-config`             | `/system/storage-config` | 40 | 既有 update pid/sort |
| 5     | 38  | `cloud-integrations`         | `/settings/cloud-integrations` | 50 | 既有 update pid/sort |
| 6     | 27  | `smtp-config`                | `/system/smtp-config-manage` | 10 | 既有 update pid/sort |
| 6     | 28  | `ldap-config`                | `/system/ldap-config` | 20 | 既有 update pid/sort |
| 6     | 39  | `notify-config`              | `/system/notify-config` | 30 | 既有 update pid/sort |
| 6     | 4   | `user-log`                   | `/log/user-log` | 40 | 既有 update pid/sort |
| 7     | 35  | `ai-dashboard`               | `/dashboard/ai-dashboard` | 10 | 既有 update pid/sort |

**未列項目 12 筆（enable=0、不動）**：見 §3.3。

---

## 附錄 B：既有 sort 值備份 dump 命令（migration 前必跑）

```bash
PGPASSWORD='<password>' psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev \
  -At -F',' \
  -c "SELECT id, name, pid, sort FROM public.ui_routes ORDER BY id;" \
  > scripts/sql/backup/2026-XX-XX-ui_routes-pre-menu-group.csv
```

備份 CSV 用於 rollback 還原。

---

## 附錄 C：Rollback SQL

```sql
BEGIN;

-- 1. 還原既有 27 筆的 pid / sort（依附錄 B CSV 還原；以下為示意）
-- 實作時用 psql \copy 或寫 generator 從 CSV 產 UPDATE 語句

-- 2. 移除本期新增 9 筆
DELETE FROM public.ui_routes WHERE name IN (
    'group-project-task', 'group-compliance-audit', 'group-communication',
    'group-tenant-org', 'group-system-admin', 'group-system-config', 'group-ai-analytics',
    'project-dashboard', 'my-tasks'
);

COMMIT;
```

**Rollback 限制**：jedi-auth 套件的剪枝邏輯若已透過 Nexus 進版部署，rollback DB 後對線上行為**無影響**（扁平 tree 對剪枝是 no-op，舊行為自然恢復）。FE 若已部署則 rollback FE commit 即可。

---

## 附錄 D：相關文件索引

| 文件 | 路徑 |
|------|------|
| 上游草稿 | `docs/draft_requirement_spec/menu-group/requirement.md`（user 原稿）|
| 上游白話需求 | `docs/features/FR-029-2605-menu-group/requirement-detail.md` |
| jedi-auth source | `~/Projects/Jedicogy/module/jedi-python-package/jedi-auth/jedi_auth/app/service/user_service.py` |
| FE AppMenu.vue | `~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/layout/AppMenu.vue` |
| FE i18n menu.json | `~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/config/locales/i18n/zh-tw/menu.json` |
| 權限系統設計 | `docs/system-design/permission/Permission-System-權限與選單系統設計說明書.md` |
| Feature workflow SOP | `docs/claude/feature-development-workflow.md` |
