# Bug B + Parties Stepper UX Handoff — 2026-05-25

| 項目 | 內容 |
|---|---|
| 緣由 | Phase 4 完工 + E option ship 後，user 發現 docx import 還是少 parties 資料（「責任單位 / 責任人員」tab 空白）；我（前一 session）在 Phase 4 一直把 Bug B 標為「caveat」叫 user 手動 accept，迴避修正。User 點出問題後，因 session 注意力分散，handoff 給下個 fresh session。 |
| Branch | `feature/ssp-oscal-alignment`（兩 repo 一致）|
| 接手前必讀 | 本文件 → `2026-05-25-phase4-COMPLETE-SUMMARY.md`（Phase 4 完工狀態）→ `2026-05-25-phase1-phase2-FINAL-SUMMARY.md` §8 L2（Bug B 原始記錄）|
| 預估時間 | 1-2 小時（含 read FE flow + fix + verify + commit） |
| 不在本 handoff scope | L7 以外的 follow-ups（L8 進版 / L9 版號對齊）— 看 phase4-COMPLETE-SUMMARY |

---

## 1. Bug B 完整 diagnosis

### 1.1 症狀

User 在 MF `/module-frame/<uid>/template-edit` 進 docx import (`亞航-CMMC-SSP-20260520-1會議討論版.docx`)：
- 預覽 stepper 顯示 5 個 parties
- User 點確認 confirm
- 後端 DB 內 `oscal.parties` + `oscal.responsible_parties` 對應 SSP shell 261 是空的
- MF `/template-edit` 「責任單位」「責任人員」tab 空白

### 1.2 BE log 證據（已 confirmed）

```
[2026-05-24 23:46:34,183] [ssp-docx-import] _load_current_parties returned EMPTY for module_frame=6492b07a-2039-49e9-86b5-a9334299cfd5
[2026-05-24 23:46:36,539] [ssp-confirm] _filter_parties_for_write parties_decisions count=5 map={
    'new-1628102bb960bfc8': 'skip',
    'new-66dcffaee4b6081f': 'skip',
    'new-689443e94eb4d6c9': 'skip',
    'new-925c9cffb607ba86': 'skip',
    'new-9500c754dd80a637': 'skip'
}
[2026-05-24 23:46:36,549] [ssp-confirm] annotated party uid=new-1628102bb960bfc8 diff_status=added decision=skip
... (重複 5 次)
[2026-05-24 23:46:36,619] [ssp-confirm] no parties marked use_docx — nothing to write
[2026-05-24 23:46:36,630] [ssp-confirm] skipping strategy.write_parties — parsed_parties=0 hasattr=True
```

**重點觀察**：
1. BE 端 docx parser **成功 extract 出 5 parties**（FE diff stepper 顯示出來）
2. 全部 5 parties 的 `diff_status=added`（全新，不是 conflict / unchanged）
3. FE 送來的 confirm payload **每一筆都帶 decision='skip'**
4. BE 正確地 skip 寫入（per decision contract）
5. 結果：0 parties 寫進 DB

**結論**：BE 行為正確。**Root cause 在 FE — diff stepper 預設 decision=skip 而不是 accept**。

### 1.3 不確定點（要 fresh session 確認）

| 不確定點 | 推測 | 怎麼 verify |
|---|---|---|
| FE diff stepper 預設 decision 的初始值在哪設？ | `useSspDocxDraft.js` composable 或 `sspDocxImportStore.js` 內，可能在 `initDefaultDecisions` 之類 function | grep `decision.*skip\|use_docx\|initDecisions` 在 FE `src/composables/useSspDocxDraft.js` + `src/stores/sspDocxImportStore.js` |
| `added` / `conflict` / `unchanged` 三種 diff_status 各該預設什麼？ | 推測：**added 該預設 accept**（新 party，無風險）/ conflict 預設 skip（user 確認）/ unchanged 預設 skip（不重寫）| 看 FE diff stepper component 對三個 diff_status 是否分流 |
| FE diff stepper UI 是否顯示「電話/職稱/地址」欄位？ | 推測：**沒顯示**，user 看不到完整 party info 所以不敢 accept | grep `SspDocxImportPage.vue` 內 parties step 欄位列表（電話 / 職稱 / 地址 / address / telephone / job_title）|
| 是否同時要修「parties FE preview 缺欄位」（Issue 1）？ | **是**，跟 Bug B 同一條 stepper 元件，一起改成本更低 | 改 stepper 時順手加欄位 render |

---

## 2. 該 read 的檔案（接手 fresh session 開工順序）

### 2.1 BE side（已 verify 正確，read 是為了避免 regression）

| 檔 | 為何 read |
|---|---|
| `domain/oscal/parser/docx_section_extractors.py:255-321`（party fields）| 確認 BE 已 extract 5 fields (name/title/address/telephone/email)；line 318 `extract_party_tables()` |
| `app/oscal/service/ssp_docx_import_app_service.py:847+` 區域（`_filter_parties_for_write`） | 確認 decision filter logic — fix 後 BE 應正確接 decision='use_docx' 寫入 |

### 2.2 FE side（**主修改區**）

| 檔 | Read 目的 |
|---|---|
| `src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue` | 主 docx import page，看 parties step 在哪 render + 哪個 stepper component |
| `src/composables/useSspDocxDraft.js` | docx draft state composable — 推測 default decisions 初始化在這 |
| `src/stores/sspDocxImportStore.js` | docx import pinia store — `initDefaultDecisions` 或類似 method 預設 'skip' |
| `src/service/SspDocxImportService.js` | service 看 confirm payload shape — verify decisions field |
| FE docx import diff stepper 對應 component（從 SspDocxImportPage 內部 import 推） | parties step render — 加電話/職稱/地址欄位 + 改預設 decision |

### 2.3 Reference（前期相關 commit）

- Phase 1+2 Bug A fix commit `ba0cdd7a`（docx confirm source_type=module_frame 補 ensure_shell）— **跟 Bug B 不相關**，但同檔案修過
- Phase 3 Bug C fix commit `8e8d5624`（v2-bundle confirm 套 content_overrides）— 同檔案修過

---

## 3. Fix plan（兩個 option）

### Option A: 全部 diff_status 預設 accept（最簡單）

```javascript
// 在 FE store / composable 內 initDefaultDecisions：
parties.forEach(p => {
    decisions[p.uid] = { action: 'use_docx' }  // 不管 diff_status 都預設 accept
})
```

- **Pro**: 一行改完，user 不用每次手動 accept
- **Con**: 既有 parties 若被 docx 改了會被覆蓋 — user 可能沒看 diff 就誤改

### Option B: Per diff_status differentiate（推薦）

```javascript
parties.forEach(p => {
    if (p.diff_status === 'added') {
        decisions[p.uid] = { action: 'use_docx' }   // 新 party，預設加入
    } else if (p.diff_status === 'conflict') {
        decisions[p.uid] = { action: 'skip' }       // 衝突要 user 確認
    } else {
        // 'unchanged' 或其他
        decisions[p.uid] = { action: 'skip' }       // 不動既有
    }
})
```

- **Pro**: 對齊 user expectation — added 自動加入（user 還能取消），conflict 預設保守
- **Con**: 多兩行 logic

**推薦 Option B**，跟 BE strategy.write_parties 的「added → write / conflict → skip unless explicit accept」哲學一致。

### 額外修 Issue 1（parties preview 缺欄位）

在 stepper component render parties 表時加：

```vue
<Column field="title" :header="t('lang.xxx.col_title')" />   <!-- 職稱 -->
<Column field="address" :header="t('lang.xxx.col_address')" />
<Column field="telephone" :header="t('lang.xxx.col_telephone')" />
```

BE 已 extract，FE 只是沒 render。i18n key 可放 `lang.ssp_docx_import.col.*`。

---

## 4. Verify checklist（fix 完後）

### 4.1 BE log verify

重啟 BE → 重做 docx import → grep log：

```bash
tail -300 log/app.log | grep -E "ssp-confirm|_filter_parties|annotated party|strategy.write_parties"
```

預期看到（fix 後）：
```
[ssp-confirm] _filter_parties_for_write parties_decisions count=5 map={
    'new-...': 'use_docx',   # ← 不再是 'skip'
    ...
}
[ssp-confirm] annotated party uid=new-... diff_status=added decision=use_docx
... (5 筆)
[ssp-confirm] calling <strategy>.write_parties with ... parties
[ssp-confirm] strategy.write_parties returned parties_written=5
```

### 4.2 DB verify

```sql
SET app.is_super_admin='t';
SELECT count(*) FROM oscal.parties WHERE created_at > '2026-05-25 開始時間';
-- 預期 ≥ 5

-- responsible_parties (party → SSP/MF context link)
SELECT count(*) FROM oscal.responsible_parties
  WHERE context_type='ssp' AND context_id=<新建 SSP shell id>;
-- 預期 ≥ 5
```

### 4.3 FE 行為 verify

- 進 docx import preview → parties step 顯示 5 parties，每筆**預設打勾 (accept 狀態)**
- 顯示「電話 / 職稱 / 地址」欄位內容（不再缺）
- 直接點 confirm → BE log 顯示 decision='use_docx' → DB 寫入

### 4.4 Regression verify（重要）

- 既有 parties match 到 docx parties 的場景（diff_status='conflict' 或 'unchanged'）→ 應該**還是預設 skip**，不被誤改
  - 怎麼測：先 manual seed MF 已有 5 parties，再 docx import 帶 4 同 name + 1 新 → confirm → 4 既有不變 / 1 新加入

---

## 5. 不在本 handoff 但相關的 leftover

### 5.1 Issue 3 / Option E 已 ship（前 session）

- 啟動專案 clone 已補 InventoryItem + M2M（commit `099a86bb`）
- BE `/module-frame/<mf_uid>/template-ssp` endpoint ship（同 commit）
- FE MF /template-edit 加 v3 section reuse Phase 4 L2 元件（commit `01a51f6`）

User 該重啟 BE 後 → 進 MF /template-edit 的新「元件清冊」tab 應看到之前 docx import 寫入的 4 components + 1 LA。

### 5.2 Bug B + Issue 1 fix 後該 update 的 docs

- `design.md §11` 補 §11.22 entry 紀錄 Bug B fix（mirror §11.17 Bug A 寫法）
- `docs/features/FR-028-2605-ssp-oscal-alignment/handoff/` 加 Bug B 收尾 SUMMARY
- changelog batch（per memory `feedback_changelog` — 收尾才寫）

### 5.3 L8 / L9 進版（user 拍板才做）

- L8: jedi-oscal 進版 + Nexus push + 主專案 pin 還原
- L9: BE/FE 版號 1:1 對齊 (v1.1.0 → v1.2.0?)

詳見 `2026-05-25-phase4-COMPLETE-SUMMARY.md` §5。

---

## 6. 給 fresh session 的精簡 prompt

```
你接手 SSP OSCAL Alignment Phase 4 後續 — fix Bug B (docx import 5 parties
全 decision=skip → 0 寫入) + Issue 1 (parties FE preview 缺電話/職稱/地址欄位)。
前一 session 修了 Phase 4 L5/L1a/L2/L3/L6 + E option (E1/E2/E3) 但一直迴避
Bug B，user 點出後 handoff 給你。

按以下順序讀文件：
1. docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-b-and-parties-stepper-handoff.md（本 handoff — 必讀）
2. docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-phase4-COMPLETE-SUMMARY.md（Phase 4 完工狀態）
3. domain/oscal/parser/docx_section_extractors.py:318+（party extractor — 確認 BE 已 extract 5 fields）
4. app/oscal/service/ssp_docx_import_app_service.py:847+（_filter_parties_for_write）
5. ~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue（主 page）
6. ~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/stores/sspDocxImportStore.js
7. ~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/composables/useSspDocxDraft.js

開工前 pre-flight：
- 兩 repo branch: feature/ssp-oscal-alignment
- BE working tree: M pyproject.toml only
- FE working tree: clean
- BE 必須先重啟（前 session ship 的 E1/E2 endpoint + clone bug fix）
- BE test smoke: poetry run pytest tests/test_ssp_excel_import_app_service.py — pass

開工順位（看 handoff §3）：
1. Read FE store + diff stepper 找預設 decision 初始化
2. 改 Option B 邏輯 (per diff_status 預設 accept/skip)
3. stepper component 加電話/職稱/地址欄位 render
4. FE vite build smoke
5. user 端重新 docx import verify (log + DB)
6. design.md §11 補 §11.22 entry
7. commit (FE 改動 + design.md)
```
