Bug P / Q / R — Parties Preview Form 3 個 Bug Implementation Plan

接手 SOP 註記:plan mode 限制只能寫此 ~/.claude/plans/cached-marinating-puzzle.md。 ExitPlanMode + user approve 後,Phase Z 第一步是把本 plan copy 到 CLAUDE.md 規範位置 docs/features/FR-028-2605-ssp-oscal-alignment/implementation-plan-bug-pqr.md


§1

Context — 為何要做

Bug O 收尾 E2E 測試時 user 發現 3 個跟 Bug O scope 無關的 bug,全部集中在 docx import preview UI 的負責單位 / 參與人員 sections。Bug P/Q/R 是 H-N arc 第 11~13 個 follow-up,本期 P/Q/R 是 H-N arc「user-facing complete」前的最後幾步 — 完成後 docx import update mode 整段才算 v1 stable。

3 bug 摘要:

Bug 症狀 Root Cause 影響範圍
P 對 organization party 點「連結帳號」會跳 Error,且 button label / dialog 應該是「連結組織單位」非「連結帳號」 (a) wordingPartiesSection.vue org row 跟 person row 用同個 i18n key party_link_button=「連結帳號」
(b) Error:root cause 尚未抓到 console,Phase 0.2 必須先 reproduce
FE only
Q Person party 缺「角色」下拉 PartiesSection.vue 自 commit 47e9489 起只有 read-only ROLE_LABEL從來沒有過 dropdown。template-edit 端 ModuleFramePartiesPanel.vue:438-446 才有 Dropdown + menuStore.sspPartyRoleMenu(9 role keys) FE only
R Confirm 匯入後回 template-edit 看 parties 還是舊資料 BE 確實有寫(log + import_summary.parties_written=5 雙重 verify);useModuleFrameParties.js module-scope 永久 cache 直到 invalidate(2026-05-21 故意改);SspDocxImportPage.vue:649-691 confirm 完只 reset 自己 store + router.push從沒 call invalidate() → user 回 template-edit page,ModuleFramePartiesPanel.onMounted → loadParties cache hit 拿到 stale data FE only

已驗證可重用的既有資源(不要 reinvent):

  • menuStore.sspPartyRoleMenu (9 OSCAL role keys) + roleLabel(key) 函式 → Bug Q 重用
  • useModuleFrameParties().invalidate(mfUid) → Bug R 重用
  • useSspParties (推測類似 composable,apiBase='ssp' 時 ModuleFramePartiesPanel 也適用) → Bug R 第二 case

§2

Goal

FE-only 修補 3 個 bug,不動 BE / jedi-* 套件,使 docx import preview UI 的 parties section 跟 template-edit page parties tab 完整對齊,user confirm 匯入後 template-edit page 立刻看到新資料。

§3

Architecture / 設計取捨

  1. Bug R:選 「confirm 成功後在 SspDocxImportPage 端 explicit invalidate」,不選「ModuleFramePartiesPanel.onMounted 永遠 force=true」
    • 理由:永遠 force 會讓既有 cache 機制(5s TTL 改永久的設計意圖)失效,且每次切 tab 都重打 API。confirm 是 invalidation 的單一觸發源(CRUD 已有 invalidate per 本 panel),confirm path 補上即對齊。
  2. Bug Q:選 「PartiesSection 補 Dropdown 重用 menuStore master」,不選「PartiesSection 內 hardcode role enum」
    • 理由:避免兩處 enum 走樣(C8 已清過 manager/auditor/viewer),sspPartyRoleMenu 是 SSOT。
  3. Bug P:選 「PartiesSection button label 按 party_type 分流 i18n key」,wording 跟 dialog title 對齊(dialog 已對齊 ✓,只剩 button)。Error 則先 reproduce 再針對抓到的 console error 修補。
§4

Tech Stack

  • Vue 3 (Composition API) + Pinia + Vue Router
  • PrimeVue 3.53 (Dropdown / Button / Tag / Dialog) — 注意 Dropdownvalue: null 有 bug(per memory),role 預設可不選但 sentinel 處理(本期 role 必選,無此問題)
  • vue-i18n(zh-tw + en)

§5

File Structure — 改動範圍

檔案 動作 為何
compliance-manager-fe/src/components/grc/ssp-docx-import-v2/sections/PartiesSection.vue Modify Bug P button label 分流;Bug Q 補 role Dropdown;hardcoded ROLE_LABEL 5 entry 改用 menuStore.sspPartyRoleMenu + roleLabel(key)
compliance-manager-fe/src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue Modify Bug R: confirm 成功後 (line 660 附近) call useModuleFrameParties().invalidate(targetUid);若 update-ssp mode 也要 call SSP context invalidate(探 useSspParties 是否存在)
compliance-manager-fe/src/components/grc/ssp-docx-import-v2/sections/PartyLinkDialog.vue 可能 Modify(Bug P Error 修補要看 Phase 0.2 reproduce 結果) 待 reproduce 看 console 才能 finalize 是否要動
compliance-manager-fe/src/config/locales/i18n/zh-tw/ssp-docx-import.json Modify 新增 party_link_button_org, party_link_button_user, party_relink_button_org, party_relink_button_user 4 個 wording 分流 key(若決定不分而是動態組 label 則 skip)
compliance-manager-fe/src/config/locales/i18n/en/ssp-docx-import.json Modify 同 zh-tw
compliance-manager-be/docs/features/FR-028-2605-ssp-oscal-alignment/design.md Modify Phase Z 收尾:加 §11.35 / §11.36 / §11.37 段;index 加 3 條
compliance-manager-be/docs/changelog/2026-05-25-fix-bug-pqr-parties-form.md Create Phase Z 收尾 changelog(type=fix,FE only)
compliance-manager-be/docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-pqr-FIXED-SUMMARY.md Create Phase Z 收尾 SUMMARY
原 handoff 2026-05-25-bug-p-q-r-parties-form-handoff.md Modify Phase Z 收尾加 ✓ FIXED block on top

不會動的檔案(明確排除):

  • 所有 BE app/oscal/service/ 下檔案(BE write path 已 verify OK)
  • jedi-* 套件
  • DB migration(無 schema 變更)

§6

Phase 0:Reproduce Bug P Error + Final root cause confirm

目的:Bug P 的「跳 Error」尚未抓到 console error 內容,無法寫精準 fix 程式碼。本 phase 用 playwright MCP reproduce + 抓 console / network。

Task 0.1:Reproduce Bug P

    • 若是 ORG_UNITS API 401/403 → 權限問題 → 排查 endpoint 是否需要特定 role
    • 若是 ORG_UNITS API 500 → BE 問題 → 升級為 BE bug(超出本 plan,先標 follow-up)
    • 若是 FE TypeError → 多半是 watch race condition 或 onSelect item.id undefined → 加 guard
    • 若 dialog 開不起來 → PartyLinkDialog props.party 為 null → 查 openLinkDialog 傳值

Task 0.2:Final Bug R verify(已驗 BE OK,再驗 FE cache 真會 hit stale)

Task 0.3:探 useSspParties composable 是否存在

Checkpoint:Phase 0 完,Bug P 真實 root cause 應確認;若 Bug P 升級為 BE bug 或超出 FE scope,回頭跟 user 確認是否拆分本 plan。


§7

Phase A:Bug R fix — Confirm 後 invalidate parties cache

Task A.1:在 SspDocxImportPage.vue import + call invalidate

Files:

  • Modify: compliance-manager-fe/src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue:649-691(confirm fn)+ 上方 import 區
import { useModuleFrameParties } from '@/composables/useModuleFrameParties'
// 若 Phase 0.3 確認 useSspParties 存在則一併 import:
// import { useSspParties } from '@/composables/useSspParties'
const res = await SspDocxImportService.confirm(parseUid.value, payload)
const data = res?.data?.data ?? res?.data ?? res ?? {}
draftRef.clear(parseUid.value)

// Bug R fix (2026-05-25): confirm 寫完 parties → invalidate template-edit
// 端 ModuleFramePartiesPanel 用的永久 cache,否則 router.push 回 template-edit
// 後 loadParties cache hit 拿到 stale data。詳見 design.md §11.37。
const { invalidate: invalidateMfParties } = useModuleFrameParties()
if (isCreate.value || props.mode === 'update-mf') {
    const targetMfUid = effectiveTargetUid || data.module_frame_uid || data.source_uid || props.targetUid
    if (targetMfUid) invalidateMfParties(targetMfUid)
}
// 若 Phase 0.3 useSspParties 存在:
// if (props.mode === 'update-ssp') {
//     const { invalidate: invalidateSspParties } = useSspParties()
//     invalidateSspParties(props.targetUid)
// }

// 既有邏輯 line 662 onwards
parseUid.value = null
parsedResult.value = null
// ...

預期:confirm 完 router push 回 template-edit page,parties tab 立刻顯示新名稱不需 reload

cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe
git add src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue
git commit -m "fix(ssp-oscal-alignment): Bug R — confirm 後 invalidate ModuleFrame parties cache

BE 寫入 parties OK 但 useModuleFrameParties 是 module-scope 永久 cache
(2026-05-21 故意改),SspDocxImportPage.confirm 完 router.push 回
template-edit 後 ModuleFramePartiesPanel.onMounted → loadParties cache
hit 拿到 stale data。fix:confirm 成功後 explicit call invalidate(mfUid)。

詳見 design.md §11.37。"

§8

Phase B:Bug Q fix — PartiesSection 補 role Dropdown

Task B.1:在 PartiesSection.vue 補 role Dropdown + 對齊 menuStore master

Files:

  • Modify: compliance-manager-fe/src/components/grc/ssp-docx-import-v2/sections/PartiesSection.vue 整個 script + template
import { storeToRefs } from 'pinia'
import { useMenuStore } from '@/stores/menuStore'
import Dropdown from 'primevue/dropdown'
import { onMounted, computed } from 'vue'

const menuStore = useMenuStore()
const { sspPartyRoleMenu } = storeToRefs(menuStore)

// 沿用 ModuleFramePartiesPanel.vue:101-105 同樣 pattern
function roleLabel(key) {
    if (!key) return '—'
    const i18nKey = `lang.oscal_role.ssp_party_role.${key}.label`
    const v = t(i18nKey)
    return v === i18nKey ? key : v
}

const roleOptions = computed(() =>
    sspPartyRoleMenu.value.map((m) => ({
        label: roleLabel(m.key),
        value: m.key,
    })),
)

onMounted(() => {
    menuStore.fetchSspPartyRoleMenu()  // 已有 cache + 並發合併,多 mount 共用同份
})

對 org row(line 130-134 區段):

<div class="flex align-items-center gap-2">
    <!-- 顯示用 label(保留現值時 read-only)-->
    <span v-if="isPartyReadonly(p.party_uid)" class="font-semibold">
        {{ roleLabel(p.role) }}
    </span>
    <!-- 編輯模式:Dropdown -->
    <Dropdown
        v-else
        :model-value="p.role"
        :options="roleOptions"
        option-label="label"
        option-value="value"
        placeholder="(選擇角色)"
        class="ssp-party-role-dropdown"
        @update:model-value="(v) => onFieldEdit(idx, 'role', v)"
    />
    <Tag :severity="statusSeverity(p)" :value="statusLabel(p)" />
    <Tag v-if="isPartyReadonly(p.party_uid)" severity="secondary" value="保留現值" />
</div>

對 person row(line 194-198 區段)做同樣改動。

.ssp-party-role-dropdown {
    min-width: 160px;
}
.ssp-party-role-dropdown :deep(.p-dropdown-label) {
    font-weight: 600;
}

預期:

  • person row 跟 org row 都有 role Dropdown(unless 保留現值)
  • 改 role → 切去其他 tab → 切回來 → role 保持改後值(onFieldEdit 走 props.update emit)
  • 9 個 role keys 全顯示(不只原本 5 個 hardcoded)
git add src/components/grc/ssp-docx-import-v2/sections/PartiesSection.vue
git commit -m "fix(ssp-oscal-alignment): Bug Q — PartiesSection 補 role Dropdown

docx import preview 端 PartiesSection 自 commit 47e9489 就只有 read-only
ROLE_LABEL,從來沒有過 Dropdown。template-edit 端 ModuleFramePartiesPanel
早就有完整 Dropdown + menuStore.sspPartyRoleMenu (9 role keys)。

fix:preview 端也補上 Dropdown,重用同個 menuStore master 避免雙處
enum 走樣 (per Bug O 教訓「FE store 三處同步」)。read-only 模式
(保留現值 / keep_current)仍顯示 read-only label。

詳見 design.md §11.36。"

§9

Phase C:Bug P fix — Button wording 分流 + 視 Phase 0.2 reproduce 結果加 Error 修補

Task C.1:i18n 新增 org / user 分流 button label

Files:

  • Modify: compliance-manager-fe/src/config/locales/i18n/zh-tw/ssp-docx-import.json
  • Modify: compliance-manager-fe/src/config/locales/i18n/en/ssp-docx-import.json
"party_link_button_user": "連結帳號",
"party_link_button_org": "連結組織單位",
"party_relink_button_user": "重新連結帳號",
"party_relink_button_org": "重新連結組織單位"

保留既有 party_link_button / party_relink_button 不刪(其他元件可能 reference;本期 PartiesSection 改 ref 新 key)。

Task C.2:PartiesSection 對 party_type 分流 button label

Files:

  • Modify: compliance-manager-fe/src/components/grc/ssp-docx-import-v2/sections/PartiesSection.vue org row line 145-151 + person row line 209-215
<Button
    size="small"
    outlined
    :label="isLinked(p)
        ? t('lang.ssp_docx_import.party_relink_button_org')
        : t('lang.ssp_docx_import.party_link_button_org')"
    icon="pi pi-building"
    @click="openLinkDialog(p, idx)"
/>
<Button
    size="small"
    outlined
    :label="isLinked(p)
        ? t('lang.ssp_docx_import.party_relink_button_user')
        : t('lang.ssp_docx_import.party_link_button_user')"
    icon="pi pi-user"
    @click="openLinkDialog(p, idx)"
/>

預期:

  • org row button 顯示「連結組織單位」/「重新連結組織單位」+ 建築 icon
  • person row button 顯示「連結帳號」/「重新連結帳號」+ 人形 icon

Task C.3:視 Phase 0.2 reproduce 結果加 Error 修補

待 Phase 0.2 抓到 console / network log 後再 finalize。可能的修補方向:

  • 若是 PartyLinkDialog onSelect(item) 在 org 情境 item.id undefined → 加 guard if (!item?.id) { toast.warn('選項缺 id'); return }
  • 若是 watch race condition → 改 immediate + nextTick
  • 若是 ORG_UNITS API 401 → 升級為 BE follow-up

Task C.4:commit

git add src/config/locales/i18n/zh-tw/ssp-docx-import.json \
        src/config/locales/i18n/en/ssp-docx-import.json \
        src/components/grc/ssp-docx-import-v2/sections/PartiesSection.vue \
        # 若 Phase C.3 有改 PartyLinkDialog 一併 add
git commit -m "fix(ssp-oscal-alignment): Bug P — PartiesSection link button 對 party_type 分流 wording

PartiesSection 對 organization 跟 person 共用同個 'party_link_button'
i18n key (=「連結帳號」),wording 對 org 不對。dialog title 已有
party_link_dialog_title_org / _user 分流,button label 跟著對齊。

新增 4 個 i18n key (party_link_button_user/_org/relink_*),
保留舊 key 不刪(其他元件可能 reference)。icon 也分(建築 / 人形)。

(若 Phase C.3 有 Error 修補,commit message 加第二段說明)

詳見 design.md §11.35。"

§10

Phase D:E2E Verify — Playwright 全 3 bug

Task D.1:用 playwright MCP 跑完整流程

    • Bug Q ✓:person row 看到 role Dropdown,9 個 role keys 全顯示
    • Bug Q ✓:org row 也看到 role Dropdown
    • Bug P (wording) ✓:org row button 顯示「連結組織單位」+ 建築 icon
    • Bug P (wording) ✓:person row button 顯示「連結帳號」+ 人形 icon
    • Bug P (Error) ✓:點 org row 「連結組織單位」→ dialog 標題「連結到組織單位」,不再 Error
    • Bug P (link OK) ✓:選一個 org → linked 狀態變 success Tag
    • Bug R ✓:「責任單位」/「參與人員」tab 立刻顯示新 name(不需 reload)

Task D.2:BE pytest smoke(雖然 BE 沒動但 safety check)

cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
poetry run pytest tests/test_ssp_docx_import_app_service.py \
  tests/test_ssp_docx_diff_service.py \
  tests/test_ssp_write_strategy.py -q

預期:~210+ passed(跟 Pre-flight 一致)


§11

Phase Z:收尾文件(per CLAUDE.md「做 summary」段)

按 CLAUDE.md「做 summary」段 step 5 必跑「回頭更新所有相關文件」。

Task Z.1:把本 plan copy 到正式位置

Task Z.2:design.md 加 §11.35 / §11.36 / §11.37

Files:

  • Modify: compliance-manager-be/docs/features/FR-028-2605-ssp-oscal-alignment/design.md
    • 每段含:症狀 / root cause / 修補 / 教訓 / commit clause
    • 教訓重點:「dialog title 已分流但 button label 沒分流是橫向漏網」「永久 cache 設計必須伴隨『所有寫入 path 都 invalidate』的 SOP」「FE 兩處 UI 該用同 SSOT master,不該各自 hardcode」

Task Z.3:changelog (type=fix, FE only)

Files:

  • Create: compliance-manager-be/docs/changelog/2026-05-25-fix-bug-pqr-parties-form.md

frontmatter:

---
type: fix
modules: [ssp-oscal-alignment, frontend-only]
issue: docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-p-q-r-parties-form-handoff.md
---

內容:3 bug 各一段(症狀 / fix / commit hash 待 commit 後回填)

Task Z.4:FIXED-SUMMARY

Files:

  • Create: compliance-manager-be/docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-pqr-FIXED-SUMMARY.md

對齊 2026-05-25-bug-o-FIXED-SUMMARY.md 樣式:

  • 收尾日期 / branch / spec / plan / changelog / handoff (本期接手) 連結
  • Commits 清單(FE only)
  • 跨 repo 範圍表
  • 行為差異一句話
  • 規範文件齊全度 checkbox
  • 已知 follow-up(若有,e.g. SSP context cache 沒 cover)
  • 部署 handover(FE rebuild only,無 DB migration,BE 不需重啟)
  • E2E 驗證紀錄(playwright screenshots 列檔)

Task Z.5:原 handoff 標 ✓ FIXED

Files:

  • Modify: compliance-manager-be/docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-p-q-r-parties-form-handoff.md

在標題後加 block:

> ✓ FIXED — 2026-05-25 收尾,見
> [`2026-05-25-bug-pqr-FIXED-SUMMARY.md`](./2026-05-25-bug-pqr-FIXED-SUMMARY.md)

Task Z.6:更新橫向文件(per CLAUDE.md「回頭更新文件」step)

    • feedback_permanent_cache_needs_invalidate_at_all_writers.md:永久 cache 設計必須伴隨「每個寫入 path 都 invalidate」SOP,本期 confirm path 漏掉
    • feedback_button_label_align_dialog_title_per_type.md:dialog title 已對 type 分流,但 button label 沒跟著分是常見漏網點
    • 加完 MEMORY.md index 同步加 1 行

Task Z.7:commit + 對話歷史歸檔

cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
git add docs/features/FR-028-2605-ssp-oscal-alignment/design.md \
        docs/features/FR-028-2605-ssp-oscal-alignment/implementation-plan-bug-pqr.md \
        docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-pqr-FIXED-SUMMARY.md \
        docs/features/FR-028-2605-ssp-oscal-alignment/handoff/2026-05-25-bug-p-q-r-parties-form-handoff.md \
        docs/changelog/2026-05-25-fix-bug-pqr-parties-form.md
git commit -m "docs(ssp-oscal-alignment): Bug P/Q/R 收尾 — plan + design §11.35-37 + changelog + FIXED SUMMARY"
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
python3 scripts/extract_claude_sessions.py --date 2026-05-25 --topic bug-pqr-parties-form --auto
wc -l docs/conversation-history/2026-05-25/bug-pqr-parties-form/*.md
git add docs/conversation-history/2026-05-25/bug-pqr-parties-form/
git commit -m "docs(conversation-history): 2026-05-25 — bug-pqr-parties-form session dump"

不自動 push。報告 push 規模(FE N commits + BE M commits)給 user 拍板。


§12

Verification — 端到端怎麼驗

Check 命令 / 動作 預期
Bug P wording playwright Step 3 預覽 → 看 org row button label 顯示「連結組織單位」
Bug P Error 點 org row 「連結組織單位」 dialog 開「連結到組織單位」標題,無 console error
Bug Q dropdown playwright Step 3 預覽 → person/org row 看 role 欄 顯示 Dropdown(非保留現值時),9 個 role 全列
Bug R cache playwright 改 party name → confirm → router 回 template-edit parties tab 不需 reload 立刻顯示新 name
BE 不退化 poetry run pytest tests/test_ssp_docx_import_app_service.py tests/test_ssp_docx_diff_service.py tests/test_ssp_write_strategy.py -q 210+ passed
BE log 健全 tail -100 log/app.log | grep ERROR 無新 ERROR

§13

行為規範重申(per CLAUDE.md + memory)

  • ✗ 不切 branch — 兩 repo 永遠 feature/ssp-oscal-alignment
  • ✗ 不自動 push — 收尾 Phase Z.7 報 push 規模給 user 拍板
  • ✓ 跨 repo 改 FE 前已 read FE CLAUDE.md(接手 §0 第 6 點已 cover)
  • ✓ 本期 不改 BE service 層 → 不需要提醒 user 重啟 BE
  • ✓ 改 FE → vite hot reload,不需 user 動作
  • git add 顯式檔名(per memory feedback_subagent_explicit_git_add.md)— 每個 commit 都列檔
  • ✓ 不晶晶體(per memory)— commit message 用中文動詞
  • ✓ Plan 假設先 verify — Phase 0 reproduce Bug P Error 確認後才寫 Phase C.3 fix code
  • ✓ 收尾 Phase Z 回頭更新橫向文件(per CLAUDE.md「做 summary」step 5)
§14

不在本期 scope

  • BE / jedi-* 套件變更
  • SSP context cache invalidate(若 Phase 0.3 verify 確認 useSspParties 不存在,標 follow-up)
  • BE / FE 版號 bump(user 拍板才動)
  • DB migration
  • Bug O / H-6 / H-N 回頭 revisit(已 closed)
§15

預估時間

Phase 時間
Phase 0 (Reproduce + verify) 30 分
Phase A (Bug R) 30 分
Phase B (Bug Q) 60 分
Phase C (Bug P) 60-90 分(依 C.3 結果)
Phase D (E2E verify) 30 分
Phase Z (收尾) 60 分
總計 3.5-5 小時