# FR-049.1 問卷顯示條件（目標側重構）— Design

> **母案**:FR-049（來源側 goto 跳題,四段已完成 commit 未部署）。本案是 user 2026-07-18 手測後拍板的**資料模型翻轉**:跳題規則從「來源選項宣告目的地」改為「目標題目/題組自己宣告顯示條件」——業界主流(Qualtrics/SurveyMonkey)形態。
> **狀態**:design 拍板(六決策見 §2),implementation plan 待寫。
> **前提**:FR-049 未部署 STG、無生產資料用 goto_* 新鍵、E2E 未 commit——**現在翻轉零遷移成本**,晚一版成本翻倍。

## 1. 為什麼推翻 FR-049 的來源側模型

user 手測 Designer 時三連問(「這兩個是不是擇一」「為什麼可複選」「BC 都要打幹嘛設跳轉」)暴露的不是文案問題,是模型錯位:

| 來源側瑕疵 | 說明 |
|-----------|------|
| 隱式副作用 | 題組 B 變條件題組,是「被別題的選項提到」的副作用,B 自己身上無跡可循 |
| 漏網坑 | 來源題新增選項忘設規則 → 答該選項者整章看不到,Designer 零警告(靜默漏答) |
| 零全局視圖 | 規則散在各題 dialog 深處,無法一眼看出誰是條件題組、被誰控制 |
| 心智模型錯位 | 「跳轉至題組(可複選)」——「混合跳去兩個地方」語意荒謬;真語意是「此答案適用哪些章節」 |

目標側一次解決:條件寫在目標身上(自描述)、條件不成立即不顯示(無覆蓋問題)、列表卡片可掛條件 badge(全局視圖免費)、「我何時出現」是使用者真正在想的問題。

**需求形狀確認**:user 拍板本產品問卷是「**通用為主、例外分流**」(多數章節人人答,少數依情況出現)——目標側模型直接命中,不需要「完全分支」的批次隱藏工具(列 backlog)。

## 2. 決策紀錄(user 2026-07-18 全數拍板)

| # | 決策 | 結論 |
|---|------|------|
| 1 | 跳題模型 | **A 案:目標側顯示條件**,來源側 goto UI 整個拿掉 |
| 2 | 子題語意 | **純排版附屬**(編號/縮排/一起出現一起答);「答了才展開」= 對子題設顯示條件的機制在 BE/引擎層保留,但 v1 不給 UI 入口(決策 9 二修:僅 FE 隱藏) |
| 3 | 子題建立入口 | **母題卡片直接「+ 新增延伸題」**,dialog 的「父題目」下拉拿掉 |
| 4 | 同組跳題操作殼 | ~~(a) 只做目標側 UI~~ → **2026-07-18 段② 手測後改判 (b)：跳題精靈提前入本案**(決策 7) |
| 5 | FR-049 已 commit 四段 | **不 revert**,FR-049.1 以 delta 疊上(保留/重做清單見 §5) |
| 6 | E2E | 舊模型場景止損,FR-049.1 定案後重派 |
| 7 | 跳題精靈(段② 手測追加) | user 實際手測體感「要跑到目標題身上設定、還要回頭找來源題選項」摩擦過大——**來源題編輯 dialog 加「依此題答案控制顯示」區**：選目標(題目/題組,可多目標) + 圈答案 → 存檔時自動翻譯成目標側 display_condition 寫到各目標身上。**資料模型不動**(仍只有目標側 display_condition,精靈是純 UI 翻譯層);目標側「顯示條件」區與 badge 照舊保留,兩入口殊途同歸、互相同步顯示 |
| 8 | 來源題型擴充(段② 手測追加) | **dropdown(下拉選擇)納入合法來源題型**——語意同為單選。BE 驗證(SOURCE_NOT_RADIO 五擋之一)與 FE 來源題過濾同步放寬為 `type ∈ {radio, dropdown}`;error code 文案改「來源題必須是單選類型(單選題/下拉選擇)」 |
| 9 | 子題跳題「僅 FE 隱藏」(段② 手測追加,2026-07-18 二修) | **延伸題(子題)的顯示條件 UI 前端不顯示**——編輯子題的 dialog 隱藏「顯示條件」區與跳題精靈區、精靈目標下拉排除子題。**BE 能力保留不加擋**(display_condition 落子題仍合法、引擎照算)——日後要開放「答了才展開」只需把 FE 顯示打開,零 BE 改動。決策 2 的「對子題設條件=答了才展開」語意在 BE/引擎層仍成立,只是 v1 不給 UI 入口。子題不作 source 維持不變(既有排除) |
| 10 | 子題題號自動衍生(段② 手測追加) | **「+新增延伸題」建立時題號自動帶「母題號-序」**:母 AC-1 → 子依序 AC-1-1、AC-1-2(取母題現有子題數+1);欄位仍可手改。刪中間子題不回填重排(避免題號漂移) |
| 11 | Badge 全文化 + 來源側反向提示(段② 填答手測追加) | (A) 目標側 badge 改完整條件語句「當 AC-2=『已全面實施』或『部分實施』時顯示」,答案多時截斷+tooltip 全文;(B) Designer 編輯來源題時,每個選項列尾端顯示反向提示「→ 顯示:AC-2-1、B 題組」/「(無影響)」(資料=精靈反向索引,純渲染) |
| 12 | 填答端選項提示(段② 填答手測追加) | 會展開追加題的選項尾端加輕量提示「＋將增加 N 題」——**只顯示題數不顯示內容**(避免答題誘導偏誤,對齊 Qualtrics 受測端預設) |
| 13 | 雙入口統一語言(段② 填答手測追加,選項 a) | **維持目標側+來源側精靈雙入口**,以視覺語言表明同一份資料:①兩區標題同詞根(目標側「顯示條件——此題何時顯示」/來源側「顯示條件——此題答案控制誰顯示」)②精靈區頂部說明「在此設定的規則會寫入各目標題的顯示條件,兩處是同一份設定」③配合決策 11 的全局視圖。不砍任一入口(使用者兩種心智模型並存) |
| 14 | 條件答案欄反面提示(snapshot 手測追加) | user 設條件後填答才發現「未圈中的答案作答=隱藏」——三處條件答案 MultiSelect(目標側/精靈/題組)下方加即時反面提示「未勾選的答案({names})作答時,本題/本題組將隱藏」,把「未命中=隱藏」的後果攤在設定當下。answers 空/未選來源/全圈不顯示 |
| 15 | 決策 12 修訂:題數提示改靜態計數(snapshot 手測追加) | what-if 動態 diff 在「未作答=顯示」語意下恆 0(受控題本來就顯示)——改靜態反向索引:N=引用「本題+本選項」的目標題數(page 目標計其下全部題目含子題),與顯示狀態無關、空白問卷即顯示 |

## 3. 資料模型

### 3.1 顯示條件(新,掛在目標上)

`SurveyQuestion.options` 之外,題目與題組本體各加一個 JSONB 鍵 `display_condition`:

```jsonc
// SurveyQuestion(題目)與 SurveyPage(題組)同形狀
"display_condition": {
  "source_question_uid": "<單選題 uid>",     // 條件來源(v1 範圍:單一來源、單選題)
  "answers": ["<option key>", ...]           // 來源題答這些值時本題/題組顯示
} | null                                      // null / 缺省 = 無條件恆顯示
```

- **語意**:`display_condition` 存在 → 僅當來源題已作答且答案 ∈ answers 時顯示;來源題**未作答時顯示**(維持 FR-049「答了才略過」語意,保證空白問卷全可見)——**注意這與「答案 ∈ answers」判斷相互獨立:未作答=顯示,已作答才比對集合**。
- **answers 比對鍵**:沿 FR-049 段④ 的教訓,用 option 的 `key || name`(FE mapOptionFromApi 同款取值)。
- **v1 範圍限制**(明確不做):多來源條件(AND/OR 跨題)、非單選來源、跨問卷——BE 驗證直接擋。
- **goto_* 鍵退役**:`goto_question_uid`/`goto_page_uids` 不讀不寫;FR-049 從未部署,無資料要遷移。jedi-survey 段① 寫的 goto 驗證邏輯同步退役。

### 3.2 儲存位置

- 題目:`SurveyQuestion.options` 同表另一 JSONB 欄?**否**——`display_condition` 是題目級屬性非選項級,落 `SurveyQuestion` 本體(實作時查 jedi-survey model 有無現成 JSONB 雜項欄可掛,無則 options 平級新增欄位——落點在 implementation plan 前提查證定案,含可翻譯欄位家族檢查:**若落在 translatable_fields 內的欄位,必須複查 jedi-common 翻譯 bug 耦合**,見 `docs/issues/pending/2026-07-18-jedi-common-translation-exclude-never-works.md`)。
- 題組:`SurveyPage` 本體同款。

### 3.3 稽核快照(沿用 FR-049 段④ 機制,換內容)

`survey.question_answer_histories.flow_rules_snapshot`(**DEV 已套的欄位照用,不回滾**)——內容從 goto 鍵集合改為「當下全部 display_condition 的快照」:

```jsonc
[{ "target_type": "question|page", "target_uid": "...",
   "source_question_uid": "...", "answers": [...] }, ...]
```

checkpoint 落快照、revert 繼承目標快照、null 容忍——三個行為與 FR-049 裁決完全一致,只換抽取來源。

## 4. UI 行為

### 4.1 Designer

- **題目/題組設定各加「顯示條件」區**:「當 [選一個單選題▼] 的答案是 [選項多選▼] 時顯示」+ 清除鈕。來源題下拉只列同問卷單選題(排除自己與自己的子題)。
- **AddQuestionDialog 拿掉**:「父題目」下拉(決策 3)、「流程規則」整區(goto UI 退役)。「進階設定」步驟只剩補充描述——步驟可視內容合併,實作時定。
- **列表卡片掛條件 badge**:有 `display_condition` 的題目/題組顯示「條件顯示:依 <來源題編號>」badge——全局視圖。
- **子題**:母題卡片 footer 加「+ 新增延伸題」;子題卡片縮排嵌套在母題卡片內(修 FR-049 遺留:目前列表模式 1-1-1 仍是平級獨立卡片,user 截圖實錘)、子題不提供獨立上移/下移(跟著母題走)。
- **來源題防呆**:刪除被引用的來源題 → 連動清空引用它的 display_condition(沿段① 清理 hook 模式,對象換掉);來源題從單選改為其他題型 → 同樣清空並提示。

### 4.2 填答端(行為不變,輸入換形狀)

`computeVisibility` 純函數保留,輸入從「掃 options goto 鍵」改為「掃全部 display_condition」;渲染/進度/必填三消費點同源架構原封不動。已填後被隱藏的答案保留不刪(稽核軌跡)——全部沿 FR-049。

### 4.3 稽核檢視端(行為不變)

「不適用 vs 漏答」判定沿 FR-049 段④:讀快照重算,灰標「不適用(依 XX 作答略過)」/紅標必填。`computeVisibilityFromSnapshot` 換輸入形狀。

## 5. FR-049 資產處置(決策 5:delta 不 revert)

| 資產 | 處置 |
|------|------|
| 可見性引擎骨架(useSurveyVisibility.js 雙函數) | **保留**,換輸入解析 |
| 快照機制(migration/model→serializer 全鏈/checkpoint/revert 繼承) | **保留**,換抽取內容 |
| 三消費點同源架構(SurveyPreview 重構) | **保留** |
| 子題 pid 接線(mapper/useSurveyApi/BE) | **保留** |
| 檔案上傳 dropzone 等樣式修 | **保留**(與模型無關) |
| jedi-survey 段① goto 驗證(_validate_flow_rules)+ 14 測試 | **重做**:改驗 display_condition(單選來源/禁自指/目標存在/同問卷——邏輯同款換對象);goto 驗證與測試退役 |
| jedi-survey 段① 清理 hook | **改造**:清理對象從「選項 goto 鍵」改為「引用該題的 display_condition」 |
| Designer 流程規則 UI(AddQuestionDialog 三行卡片) | **退役**,換目標側「顯示條件」區 |
| survey-mapper goto 鍵映射 | **重做**:改映 display_condition |
| E2E 舊模型場景 | 已止損;子題/既有回歸類經新設計比對後沿用,跳題類重寫 |

## 6. 驗收情境

1. **部署環境三分流**(通用+例外形狀):12 題組,僅 B 設「A-1∈{地端,混合}」、C 設「A-1∈{雲端,混合}」→ 答地端:A,B,D~L;答雲端:A,C,D~L;答混合:全部;未作答:全部可見。
2. **新增選項無漏網**:A-1 加新選項「其他」不動任何條件 → 答「其他」:B、C 不顯示(條件字面語意),D~L 照常——無靜默漏答整章的坑。
3. **同組跳題**:Q2-Q4 各設「Q1≠2」→ Q1 答 2 時 Q2-Q4 消失、進度分母同步;改答恢復、答案保留。
4. **必填同源**:被條件隱藏的必填題不擋提交;可見必填未填仍擋。
5. **稽核回溯**:提交後 Designer 改條件 → 稽核檢視按提交當下快照判定「不適用」,不漂移;revert 版本繼承目標快照。
6. **Designer 防呆**:刪來源題/來源題改題型 → 引用它的條件被連動清空;條件 badge 正確顯示與消失。
7. **子題**:母題卡「+ 新增延伸題」建立 → 子題嵌套顯示於母題卡內、無獨立排序;填答端跟母題一起出現。

## 7. Backlog(明確不在本案)

- ~~跳題精靈~~ → 2026-07-18 決策 7 提前入本案(見 §2)
- 多來源/複合條件(AND/OR)
- 「完全分支」批次隱藏工具
- 非單選類來源題型(checkbox/text 等;dropdown 已由決策 8 納入)
- 子題「答了才展開」的 UI 入口(BE/引擎能力已在,決策 9 二修僅 FE 隱藏;日後開放=打開 FE 顯示即可)
