◆ wport | pm_44

pm_44 封鎖公司:功能審查整合報告

封鎖公司(Block Company)

來源 doc/feature/pm_44/block-company-audit-report.md

pm_44 封鎖公司:功能審查整合報告

審查對象:block-company-prd.md v1.0.1 + Storybook Mockups/求職者/封鎖公司(storybook.wport.me 線上實測) 審查方法:功能面流程分析(不以 PRD 為標準)+ PRD×mockup 逐行靜態比對 + 瀏覽器實際操作 + 系統交互時序盤點 + 同業參照(104 / 1111 / Indeed / LinkedIn / Glassdoor / Cake) 審查日期:2026/07/14


0. TL;DR

類別數量最嚴重的一條
P0 結構性矛盾4v1 範圍矛盾:pm_44 說「封鎖隱藏再投入口」是 v1 成功條件,pm_42 把同一件事排到 v2
P1 單邊缺漏11BR-029 封鎖原因 UI 完全缺席;SEC-4 遮罩未實作
P2 文案與細節3「已達上限」文案寫死,0/50 也顯示
PRD 功能面遺漏6解除封鎖無確認/undo(高影響動作)
系統交互情境未定義12已有訊息往來後才封鎖 → 能否再發、歷史可見性全未定義
同業參照6 平台 + 3 條必帶教訓集團子公司繞過(104 最大客訴源)、封鎖名單外洩(1111 真實事故)

1. P0:結構性矛盾

1-1. 入口與寫入語意分歧:mockup 實作了 SEC-1 子集+一套 PRD 未規格化的搜尋新增流程

共通部分:mockup 的封鎖清單頁就是 PRD 的 SEC-1(含解除封鎖、空狀態 SEC-5、上限 BR-028)。 分歧部分:PRD 的進入路徑是「瀏覽中封鎖」——公司頁 / 職缺卡 overflow(SEC-2)→ 確認 Modal(雙向隱藏說明、收藏移除提示、選填原因,SEC-3)→ 確認才寫入;mockup 則發明了 PRD 沒有的「主動搜尋封鎖」——搜尋公司 → 逐筆或「封鎖全部」→ 點了立即寫入,無確認步驟,且 SEC-2/3/4 全數未做。入口集合和寫入語意(確認制 vs 即點即寫)雙雙對不上。

flowchart LR
    subgraph PRD 的世界
        A1[公司頁 / 職缺卡 overflow] --> B1[點封鎖]
        B1 --> C1[確認 Modal<br/>雙向隱藏說明+選填原因]
        C1 -->|確認| D1[寫入]
        C1 -->|離開| E1[不寫入]
    end
    subgraph Mockup 的世界
        A2[帳號設定 > 封鎖公司] --> B2[新增封鎖 Modal<br/>搜尋公司]
        B2 --> C2[逐筆點新增封鎖<br/>或 封鎖全部]
        C2 --> D2[立即寫入 無確認]
        D2 -->|按取消關窗| E2[已寫入的不回滾]
    end

為什麼重要(實例):小美想封鎖現任雇主,但她絕不想瀏覽現雇主的公司頁(怕留下「誰來看我」足跡)。PRD 的入口逼她去公司頁;mockup 的搜尋入口才符合她的需求。反過來,阿明滑職缺時看到前東家想順手封鎖——mockup 沒有這個入口。兩種入口其實互補,建議 PRD 裁決兩者都收,而非改掉任一邊。

  • 出處:PRD §6.2(SEC-2/SEC-3)、PRD:184 BlockToggle vs MockupBlockCompany.vue:159-167
  • 修正對象:PRD(裁決 IA)+ mockup(補確認語意)

1-2. 「封鎖全部」可突破 50 上限(違反 BR-028)

openModal() 在達上限時擋住開窗,但 blockOne() / blockAll() 內部不再檢查上限

實例:已封鎖 49 家 → 開新增 Modal(49 < 50 可開)→ 按「封鎖全部(5)」→ 變 54 家。

  • 出處:MockupBlockCompany.vue:50-51(只擋開窗)、:66-69(blockAll 無檢查)
  • 修正對象:mockup(PRD 若收「封鎖全部」也要補批次遇上限的截斷規則:封到 50 停?整批拒絕?)

1-3. 重複封鎖產生重複項(瀏覽器實測確認)

華勝已在封鎖清單,Modal 內仍顯示「新增封鎖」,點擊後清單出現兩筆華勝(實測畫面 6/50 含重複)。搜尋候選的 isBlocked 旗標沒有和清單同步,違反 PRD edge case「(user,company) 唯一、idempotent」(PRD:234)。

  • 出處:block-company.ts:37-43(候選資料寫死 isBlocked:false
  • 修正對象:mockup

1-4. v1 交付範圍矛盾:「封鎖隱藏再投入口」到底 v1 還 v2

pm_44 把它寫成 v1 成功條件:「該公司『再次應徵』入口在封鎖下隱藏」(PRD:55)。 pm_42 把同一件事明確排到 v2:「pm_44 封鎖公司互動(再投隱藏 / is_blocked_by_me)→ v2(pm_44 上線後)」(pm_42:52)。

兩卡對 v1 範圍的認知直接衝突——如果照 pm_42 的排程,pm_44 v1 上線後會有一段窗口期:使用者封鎖了 C 公司,但應徵紀錄頁的「再次應徵」按鈕照常可點。要嘛 pm_42 提前接 is_blocked_by_me,要嘛 pm_44 的成功條件降級並定義窗口期的過渡行為。

  • 修正對象:兩卡 PRD 對齊(v1 範圍裁決)

2. P1:單邊缺漏

PRD 有、mockup 沒做

#項目PRD 出處說明
1BR-029 封鎖原因選填 UIPRD:81, 105, 140mock 型別連 reason_code 欄位都沒有
2SEC-4 已封鎖遮罩PRD:143-145「你已封鎖此公司」畫面完全未實作
3SEC-2 BlockToggle 元件 + isolation storyPRD:184不存在
4scenario 缺 2 個PRD:183confirm-modalblocked-overlaysaved-conflict 只是 mock 第一筆剛好 isSaved:true 的副作用,資料一改就消失
5SEC-1 分頁PRD:132mockup 無分頁
6事件字典三事件PRD:280-284無任何實作痕跡

mockup 有、PRD 沒寫

#項目mockup 出處說明
7搜尋新增 Modal + 「封鎖全部」批次.vue:136-178無對應 ACT / API Hint
8「封鎖清單掛在帳號設定頁」的 IAblock-company.ts:46-51PRD 未提掛載位置

行為不一致(皆瀏覽器實測)

#項目PRD 說mockup 實際
9Modal 中離開不建立封鎖(PRD:236)已點的封鎖不回滾,按取消照樣留著
10達上限「禁用並提示」(PRD:141)按鈕變灰,點擊完全靜默
11資料結構巢狀 company 物件、ISO8601攤平結構、YYYY/MM/DD

3. P2:文案與細節

  1. 「X / 50 家(已達上限將無法再新增)」是寫死的常駐文案——0/50、5/50 都掛著這句,讀起來像已達上限(實測 0、4、5、6、50 全部同句)。.vue:114
  2. 搜尋框是假的keyword 有 v-model 但無過濾邏輯,輸入任何字清單不變,QA/PM 易誤判搜尋已模擬。.vue:44, 144-147
  3. **「封鎖行為是完全匿名的,對方不會收到通知」**是 mockup 自寫 claim。「不收到通知」做得到;「完全匿名」要逐一驗證企業端所有計數器(人才搜尋數、誰來看我數、CRM 統計)不因封鎖出現可推斷變化,否則是過度承諾(見 §6-2 的 104 教訓)。

4. PRD 功能面遺漏(不以 PRD 為標準的檢查)

  1. 解除封鎖無確認、無 undo——解除等於「讓前東家重新看到你」,是高影響動作,PRD 只寫「即時恢復曝光」。實測 mockup 也是一點即解、無任何確認。
  2. 封鎖成功/失敗的回饋機制(toast / inline)未定義。
  3. 50 家清單無搜尋/篩選規格(滿額時找一家很痛苦)。
  4. 被封鎖公司倒閉/改名/下架後,清單裡那筆怎麼顯示。
  5. System Flow 的「GET 狀態(含已封鎖數)」在 API Hints 無對應欄位。
  6. loading 態全篇未提(只有 timeout 15s 常數)。

5. 系統交互時序情境(19 條未定義,本節為核心增補)

5-1. 最高優先:先互動、後封鎖

sequenceDiagram
    participant U as 求職者 U
    participant SYS as 平台
    participant C as 公司 C(HR)
    Note over U,C: ── 封鎖前,關聯已存在 ──
    U->>C: 已投遞應徵(pm_42)
    C->>U: 已發面試邀約(pm_32)
    C->>C: 已把 U 加入人才 CRM pipeline(pm_45)
    C->>C: 已收藏 U(人才庫)
    C->>U: 已有訊息往來(既有訊息功能)
    SYS-->>U: 已排程 19:00 彙整通知(含 C 的動態)
    Note over U,C: ── U 此刻封鎖 C ──
    U->>SYS: 封鎖公司 C
    Note over SYS: ❓ 以下全部 PRD 未定義:
    Note over SYS: 應徵紀錄 → 已定義:保留、藏再投(PRD:55)✅<br/>但未告知 U「C 仍看得到已投履歷」
    Note over SYS: 面試邀約 → 通知還送嗎?邀約卡片顯示什麼?
    Note over SYS: CRM pipeline → 隱藏還是刪除?
    Note over SYS: C 對 U 的收藏 → BR-024 只寫 U→C 方向
    Note over SYS: 訊息 → C 還能發嗎?歷史對話可見性?
    Note over SYS: 已排程通知 → 送達前要重新過濾嗎?
情境現況出處業界做法建議
已應徵 → 才封鎖✅ 唯一有定義:歷史保留、隱藏再投PRD:55, 240104 / Indeed 皆「不溯及既往」,Indeed 官方明文:封鎖後雇主仍可看你已投的應徵補告知文案:「已投遞的應徵,該公司仍可查看」,防使用者誤以為封鎖=對方看不到
面試邀約已發出 → 才封鎖❌ 未定義;pm_32:111 把邀約通知當事件、pm_42:50-51 把邀約狀態 UI 延到 v2——兩卡 scope 未對齊(非邏輯矛盾),邀約實際上線狀態需向 owner 確認pm_32 / pm_42無直接對標先確認邀約上線狀態,再裁決:通知送不送、求職者端邀約卡顯示什麼
HR 已加 U 入 CRM pipeline → 被封鎖❌ BR-027#3 說不出現在 CRM,但 pipeline 記錄隱藏 vs 刪除未講。pm_45 尚未實作——趁實作前把封鎖行為寫進 pm_45 規格,成本最低pm_45:155-171 vs PRD:77LinkedIn block 回溯清除推薦信且不可恢復(有事前警告)建議隱藏不刪除(解封恢復),直接回寫 pm_45 PRD 而非事後補
企業已收藏 U → 被封鎖❌ BR-024 只寫求職者→公司方向。pm_45 尚未實作,同上趁規格期定義pm_45:173-180裁決 BR-024 是否對稱適用,回寫 pm_45 PRD
已排程未送達通知pm_32:225-230送達前重新過濾封鎖名單,否則封鎖後還收到 C 的通知
已有訊息往來 → 才封鎖訊息是既有系統功能(pm_32 以既存事件引用 talent_new_message / employer_talent_reply;上線狀態經 delivery owner 2026/07/14 確認),pm_44 對它隻字未提pm_32:278-284LinkedIn:封鎖即斷訊息、雙方互看不到v1 必須裁決:封鎖後 C 能否再發訊息?歷史對話雙方還看得到嗎?未讀訊息通知要過濾嗎?
企業直連 URL 開 U 的候選人詳情頁❌ BR-027#3 只涵蓋清單/時間軸,缺對稱於 SEC-4 的企業端遮罩pm_45:222-226補企業端直連行為(遮罩或 404 擇一,並與求職者端語意一致)
帳號刪除時 CompanyBlock 去留❌ BR-012 資料處理表沒列business-rules.md:346-369補資料處理表

5-2. 解除封鎖的恢復範圍(隱藏地雷)

BR-024 用詞是「移除」收藏(非「隱藏」)→ 隱含不可逆。

實例:小美收藏了 C 公司 3 個職缺 → 手滑封鎖 C → 收藏被連動移除 → 立刻解除封鎖 → 3 個收藏回不來,而她完全沒被警告過這件事。

flowchart TD
    A[封鎖 C] -->|BR-024 連動| B[移除公司收藏 + 旗下職缺收藏]
    A --> C[雙向隱藏生效]
    D[解除封鎖 C] --> E[曝光恢復 ✅ PRD:53 有定義]
    D --> F{收藏恢復?}
    F -->|PRD 未定義| G[❓ 移除=永久消失?<br/>❓ 還是隱藏可恢復?]
    style F fill:#f96,stroke:#333,color:#111
    style G fill:#f96,stroke:#333,color:#111

二選一並寫明:(a) 不可逆 → 確認 Modal 文案加「移除後解除封鎖也不會恢復」(LinkedIn 對推薦信就是這樣警告的);(b) 改為隱藏、解封恢復。另可參考 LinkedIn 的 48 小時冷卻期,防封鎖/解封循環。

5-3. 匹配單位與繞過路徑(業界最大漏洞區)

封鎖綁 enc_id,以下全部繞得過:

flowchart LR
    U[求職者 U 封鎖 enc_id_A] -.擋住.-> A[公司 C 帳號 A]
    B[同法人帳號 B<br/>BR-022 允許最多10家] -->|照樣看到 U| U2[U 的履歷]
    C2[C 砍帳號重註冊 enc_id_X] -->|照樣看到 U| U2
    H[獵頭代招 C 的職缺<br/>pm_46 深連結] -->|繞過 BR-027 過濾?| U2
    style B fill:#f96,color:#111
    style C2 fill:#f96,color:#111
    style H fill:#f96,color:#111
  • 同法人多帳號:BR-022 允許一個負責人開最多 10 家企業帳號(business-rules.md:191-201),封 1 個擋不住其他 9 個。104 的同款問題是最大客訴源(封母公司、子公司照看),104 的解法是「統編+關鍵字」雙軌匹配。
  • 改名/重註冊:PRD:97-102 未界定封鎖鍵綁 enc_id 還是公司實體。
  • 獵頭深連結:pm_46:63-66 的轉介連結是否套 BR-027 過濾未定義。查詢層過濾漏一個入口,隱私承諾就整個破產——LinkedIn Open to Work 只擋付費 Recruiter 席位、一般員工帳號照樣搜得到,官方只能自承「不保證隱私」,是現成的反面教材。

6. 同業參照重點

6-1. 規格對照

平台功能上限既有互動處理特色/教訓
104統編/關鍵字封鎖公司各 10 組不溯及既往;主動應徵已封鎖公司會跳二次確認關鍵字可涵蓋集團;「誰來看我」足跡一併遮蔽
1111封鎖公司8 間未載明2018 封鎖名單遭員工外洩 2 萬筆——封鎖清單本身是高敏感個資
IndeedBlock employers100 間官方明文:已投應徵雇主仍可見App 不支援只有 Web 有——平台不一致的反例
LinkedIn無封鎖公司功能Open to Work 排除只擋付費席位=假隱藏;解封 48h 冷卻期
Glassdoor無此功能
Cake / Yourator只查到整體可見度分級,無特定公司黑名單(未能完整確認)

6-2. 三條必須帶走的教訓

  1. 主動應徵已封鎖公司要二次確認(104 有做):BR-027 靠預防性隱藏讓你點不到,但獵頭深連結、外部分享連結仍可能把使用者帶到應徵頁——該補「你已封鎖此公司,主動應徵對方即可看到你的履歷」確認。
  2. 防識別是程度問題不是開關:104 實務上 HR 仍能感知「有人封鎖我」(匿名投遞痕跡、私訊勸關履歷)。mockup 的「完全匿名」文案在後端逐一驗證所有計數器前不要上。
  3. 封鎖清單要當高敏感個資管:它等於「使用者的現任雇主+防範對象清單」。1111 的外洩是真實案例。建議 BR-017 之上明確加存取管控與稽核 log 條款。

7. 行動清單與處理狀態(2026/07/14 分流後)

分流原則:delivery owner 可決事項已回寫 PRD v1.1.0;需產品拍板的 6 題移至 block-company-pm-decisions.md(Q1-Q6);mockup 修正為實作待辦。

#行動狀態
1既有訊息功能的封鎖行為➡️ PM 便箋 Q1(Blocking)
1bpm_45:pipeline 隱藏 vs 刪除、企業收藏對稱性(列表排除 pm_45:216 已接)➡️ 與 pm_45 owner 對齊(規格期)
1cv1 範圍矛盾:再投隱藏 v1/v2(§1-4)➡️ PM 便箋 Q2(Blocking)
2兩種入口是否都收已裁決 Q3=C(PRD v1.2.0 fa0ced0):只留帳號設定搜尋入口,公司頁入口廢除
3mockup 三 bug✅ 突破上限、重複封鎖已修0abb390,瀏覽器實測);取消不回滾隨 Q3 確認語意處理
4收藏移除可逆性➡️ PM 便箋 Q4
5Edge Case 表補列(排程過濾/帳號刪除/企業端直連/公司關閉/回饋)已回寫 PRD v1.1.0 §10.4;多帳號匹配範圍 ➡️ Q5
6原因 UI、SEC-4 遮罩、BlockToggle、缺 scenario🔧 mockup 待辦
7「已投對方仍可見」告知文案已回寫 PRD SEC-3;二次確認 ➡️ Q6
8封鎖名單資安等級條款已回寫 PRD §12 注意事項
9上限文案/假搜尋/「完全匿名」✅ 三項均已修0abb390,瀏覽器實測);「匿名」文案禁令 ✅ 已寫入 PRD §12
10回饋機制、清單搜尋、公司關閉顯示、loading已回寫 PRD SEC-1/SEC-3/§10.4;解封 undo 政策隨 Q4

附錄:瀏覽器實測記錄

  • 環境:storybook.wport.me(線上版),story 互動模式逐 scenario 操作
  • 實測路徑:has-items 解除封鎖(即點即移除、計數 5→4、無確認無 toast)→ 新增封鎖 Modal(三宏含收藏提示、點擊立即寫入)→ 重複封鎖華勝(清單出現兩筆,6/50)→ 取消關窗(已寫入不回滾)→ limit-reached(50/50 按鈕灰、點擊靜默)→ empty(空狀態文案正常,但 0/50 仍掛「已達上限」句)
  • 靜態比對範圍:PRD 全文 320 行 × MockupBlockCompany.vue(508 行)× block-company.ts × BlockCompany.stories.ts × block-company-demo.vue

文件結束