Cornhsu.Parity 0.9.1

Prefix Reserved
dotnet tool install --global Cornhsu.Parity --version 0.9.1
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Cornhsu.Parity --version 0.9.1
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Cornhsu.Parity&version=0.9.1
                    
nuke :add-package Cornhsu.Parity --version 0.9.1
                    

Parity

數值級設計還原度檢查工具——抓 Figma 與實作端的真實數值做程式比對,不是疊圖給人眼看。 本機 / CI / 內網都能跑,在東西公開之前就把「顏色不對、字太大、間距跑掉」擋下來。

疊圖工具(Pixelay 等) Parity
做法 疊兩張圖給人眼看 抓兩邊真實數值做程式比對
結果 「這裡怪怪的」 「paddingLeft 8px,設計 20px」
跑在哪 瀏覽器外掛 / 雲端(碰不到 localhost) 你的機器 / CI runner,localhost 天生連得到
進 CI 大多不行 PR 落差超門檻就擋

快速開始

dotnet tool install -g Cornhsu.Parity   # (發佈後)
parity init                # 產生 parity.config.json 範本
parity install-browser     # 第一次:下載 Playwright Chromium
export FIGMA_TOKEN=...     # scope 只需 file_content:read
parity check               # 比對,輸出報告 + exit code
parity report              # 從既有 report.json 重生 Markdown 報告(免重掃;--md 寫檔,預設印 stdout)
parity snapshot            # 把「現在跑著的畫面」凍結成設計基準——重構/改版守門,不需要 Figma
parity lint                # design lint:設計稿的值是否落在 design token 允許集合(只看設計,不比實作)
parity check --reverse     # 反向檢視:設計師照現有頁面重畫時,看自己的稿跟現況差在哪(不做把關)

在這個 repo 裡開發時:

dotnet run --project src/Parity.Cli -- check --config samples/demo/parity.config.json

samples/demo 是一組離線示範(設計來源用本機 JSON,不需要 Figma token): index.html 刻意做壞了幾個地方,parity check 會精確報出每一條落差與未配對清單, 並因 serious 級落差回傳 exit code 1(GATE FAIL)——這正是 CI 把關的行為。

設定:parity.config.json

{
  "figmaFileKey": "abcd1234",
  "designToken": "env:FIGMA_TOKEN",
  "mapFile": "parity.map.json",              // 手動補漏的對應檔(圖層名 → selector)
  "tokensFile": "tokens.json",               // 選配:design token(名→值);建議修法會提示對應 token
  "targets": [
    { "route": "/", "frame": "10:2", "url": "http://localhost:8080/" }
  ],
  "compare": { "position": "relative" },     // relative = 比相對位置(預設);none = 不比位置
  "tolerances": { "sizePx": 2, "spacingPx": 2, "colorDeltaE": 2.0, "positionPx": 4 },
  "ignore": ["[data-parity-ignore]"],
  "gate": {
    "failOn": ["critical", "serious"],
    "minMatchRate": 0                        // 選配:配對率低於此值(0–1)直接 FAIL;0 = 不設門檻
  }
}

gate 除了看落差,也驗配對可信度:完全 0 配對(或設計端 0 節點)一律 GATE FAIL—— 沒配到就沒落差可擋,沉默 PASS 會是假的通過(通常是 url/frame 指錯)。--baseline 模式也不豁免。

目標:網頁或 Electron 桌面 app

target.url 決定實作端是什麼,指令與報告完全一樣:

url 形式 對應
http(s)://… 一般網頁 / 內網 staging
file://…(或相對路徑) 本機 HTML 檔
cdp:http://host:port 已在跑的 Electron 桌面 app(抓活視窗的 DOM)

Electron:啟動時加遠端偵錯埠,再把 url 指過去即可——Parity 會 attach 進去讀當下畫面,不導頁、不干擾 app:

electron . --remote-debugging-port=9222      # 你的 app,加這個旗標
{ "route": "/", "frame": "20:5", "url": "cdp:http://localhost:9222" }

為什麼 Electron 幾乎免費:它的畫面就是一個 Chromium renderer,跟網頁同一套 DOM/CSS 量測。手機原生 / Flutter / 原生桌面不走 DOM,留待 v2.0。

設計來源:Figma、快照、或一張圖

設計來源 config 適合誰
Figma(主力) figmaFileKey + FIGMA_TOKEN 有 Figma 檔的正規流程
快照(parity snapshot) designFile 指向產出的快照 JSON 重構/改版守門:現在的畫面是對的,存成基準,之後 check 保證不跑版(visual regression 的數值版)。配對走 selector 身分,100% 確定性
一張圖 + 標註 designImage(PNG/JPG)+ designFile(標註) 只有圖的場景:外包 PNG、老專案只剩截圖。XD / Sketch / PS 等其他工具匯出圖片就能走這條(萬用轉接頭)。標註 = DesignNode JSON,fill 可省略——顏色由引擎從圖片對應區域取樣(TEXT 字色刻意不取樣:反鋸齒混色取不準,可手填)
手寫 JSON designFile 離線示範/測試
# 重構守門三步:
parity snapshot            # 1. 凍結現在的畫面(產出 parity.snapshot.json + 參考截圖)
#    config 改 designFile 指向它、target.frame 填 route
parity check               # 2. 大膽重構;3. check 保證與快照一致

比什麼、不比什麼

只比「不管版面怎麼流動都該一樣」的東西:

  • 自身尺寸(寬高;TEXT 節點例外——文字框量測天生不同,比了狂誤報)
    • auto-layout 的 HUG(隨內容)/ FILL(隨父層) 那一軸也跳過——Figma 量的寬 ≠ 瀏覽器渲染寬是必然的,只比 FIXED 的軸
  • 內距 / 間距:padding 四邊、auto-layout itemSpacing ↔ 實際子元素 gap
  • 相對位置(offsetX/offsetY):自由擺放(非 auto-layout)容器的子元素,比「相對最近可靠兄弟/父層邊」的偏移——抓得到「尺寸顏色全對但擺錯位置」。參照只用可靠的邊(TEXT/HUG 的框不當參照、TEXT 不當目標、上方全是文字時 Y 誠實跳過),流動版面的行高漂移不會誤報。compare.position: "none" 可關閉
  • 字體:size / weight / line-height / letter-spacing 精確比;font-family 是軟落差(不擋 gate)
  • 顏色:CIEDE2000 (ΔE) 設門檻,不是 hex 全等;解析含現代語法(rgb(37 99 235 / .5)color(srgb …)oklch()color(display-p3 …))

設計來源是 Figma 時,報告(Markdown 與本機 UI)裡的圖層名會連回 Figma 的那個節點——設計師點一下直接跳到圖層,不用自己翻。

  • 刻意不比絕對位置 x/y:彈性版面下本來就會不同,比了 = 誤報 = 失去信任

給設計師的兩個方向

新頁面守設計系統:parity lint——只看設計稿這一邊,驗每個節點的值是否落在 tokensFile 的允許集合:顏色(ΔE 容差內命中即過)、fontSize / padding / itemSpacing / cornerRadius(等於任一尺寸 token 即過;間距/字級/圓角共用同一份 scale)。違規附「最近的 token」——訊息是「改成這個」,不是只有「你錯了」。有違規 exit 1,可進 CI。沒定義某維度的 token 就不 lint 該維度。

照現況重畫/改版:parity check --reverse——方向反過來:「期望」= 現況(實作)、「實際」= 你的設計稿。給設計師一張「我的稿跟現在線上差在哪」的清單;不做把關,永遠 exit 0。

RWD 多斷點

同一個 URL、不同斷點,各對一個 Figma frame 即可——渲染視窗 = frame 尺寸,手機 frame 畫 375 寬,media query 自然生效:

"targets": [
  { "route": "/desktop", "frame": "10:2",  "url": "http://localhost:8080/" },
  { "route": "/mobile",  "frame": "10:99", "url": "http://localhost:8080/" }   // frame 是 375 寬的手機版
]

route 只是報告上的標籤,取好認的名字就行。frame 寬 ≠ 想測的視窗寬時,才需要在 target 加 width / height 覆蓋。

Shadow DOM / iframe

擷取走組合樹:open shadow root、<slot> 塞進來的內容、同源 iframe(含 srcdoc)都看得到、都會比——web components 網站不再整塊隱形。shadow / iframe 內的元素 selector 以 host >>> 內部路徑 表示。

限制(誠實列):closed shadow root 與跨域 iframe 拿不到,跳過;map 檔的 selector 搆不到 shadow 內(data-parity 屬性不受限,照常可用)。

配對策略(以設計端為錨)

  1. 自動文字錨定:設計 TEXT 文字 ↔ 頁面文字(唯一才配;多個同文字時用圖層名消歧,仍不硬湊)
  2. 圖層名 ↔ id / class / aria-label:CTA Button 自動對上 class="cta-button"
  3. 容器推論:配不到的容器,用「已配對子孫的最近共同祖先」反推(純結構,不猜)
  4. 手動補漏:HTML 加 data-parity="圖層名",或 parity.map.json{ "圖層名": "CSS selector" }
  5. 配不到 → 誠實列進未配對清單(真正需要人補的才補)

架構

一顆引擎(Parity.Engine,純函式庫)+ 多個外殼。引擎只比對「兩棵正規化的樹」:

IDesignSource ──→ DesignNode 樹 ──┐
  (Figma / JSON / 未來 XD、Sketch)  ├─→ Normalizer → Matcher → DiffEngine → FidelityReport
IImplementationSource → RenderedNode ┘
  (Playwright / 未來 WPF、桌面)
src/Parity.Engine/        引擎:唯一進入點 FidelityEngine
  DesignSources/          IDesignSource + Figma(REST + 本機快取)/ Json
  ImplementationSources/  IImplementationSource + Web(Playwright)
  Comparison/             Normalizer / Matcher / DiffEngine / ColorDelta(CIEDE2000)
src/Parity.Cli/           dotnet tool 外殼:parity check / init / install-browser
tests/Parity.Tests/       單元測試(含 CIEDE2000 標準測資)
samples/demo/             離線示範:刻意做壞的頁面 + 設計 JSON

本機報告 UI(M3)

parity serve --watch    # http://127.0.0.1:4321,檔案變更自動重掃(SSE 即時更新)
parity map              # 互動配對模式:點未配對圖層 → 點截圖上的元素 → 寫入 parity.map.json
  • 落差清單(依嚴重度排序,精確數值 + 色票 + ΔE)
  • 截圖疊框視圖:實線 = 實作框(顏色 = 嚴重度)、藍虛線 = 設計框、紅虛線 = 未配對
  • 只綁 127.0.0.1:報告含站點結構,不讓區網掃到
  • UI 是零建置的靜態 SPA,dotnet tool 不需要 node 工具鏈

進 CI(M4)

Parity 的差異化就在「進 CI 把關」。這個 repo 本身就是一個 composite action:

# .github/workflows/design-check.yml(你的專案)
name: Design fidelity
on: [pull_request]
jobs:
  parity:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write   # 讓 action 把還原度報告貼成 PR 留言
    steps:
      - uses: actions/checkout@v4
      # 起你的站(或改成部署到 preview URL,讓 config 的 url 指過去)
      - run: |
          npm ci && npm run build
          npm run preview &   # 例:serve 在 localhost:8080
      - uses: HSU-YU-MING/cornhsu-parity@v1
        with:
          config: parity.config.json
          figma-token: ${{ secrets.FIGMA_TOKEN }}   # 設計來源用本機 JSON 時可省略

行為:

  • PR 留言:自動貼一則還原度報告(分數 + 落差表 + 建議修法),同一則反覆更新不洗版——PM/reviewer 不用碰工具就看得到。
  • 擋 PR:落差超過 gate.failOn → exit 1 → PR 打紅(留言會先貼、再擋)。
  • artifact:report.json + Markdown 報告上傳供下載。

action 輸入:config / target / working-directory / version / figma-token / baseline(回歸模式) / comment(關掉 PR 留言) / upload-report

action 透過 dotnet tool install -g Cornhsu.Parity 安裝,需先把套件發佈到 nuget.org(發佈是 release 步驟,尚未做)。本 repo 的 .github/workflows/ci.yml 則是直接從原始碼建置並跑離線示範自我把關,不依賴發佈。

回歸把關:baseline(M5)

已經有一堆落差的專案,不可能一開就「零落差才給過」。baseline 讓你只擋新增/惡化:

parity baseline save     # 把當前落差 + 還原度分數存成基準快照(SQLite,存 parity.baseline.db,自動標 git commit)
parity check --baseline  # 比對現況 vs 最新基準:只有「新增或惡化」才 GATE FAIL;並顯示分數走勢(基準 75 → 現在 83 ↑)
parity baseline list     # 看歷史快照(含分數欄 = 還原度走勢,給 PM 看方向)

CI 要用 --baseline,記得 git add parity.baseline.db 一起 commit——它刻意放 repo 根(不放 .parity/,那裡通常被 gitignore),否則 CI 找不到基準會靜默退回一般 gate。路徑可用 config baselineFile 改。

  • 新增(基準沒有、現在有)或惡化(嚴重度變高)→ exit 1 擋 PR
  • 修好(基準有、現在沒了)會列出來鼓勵;不變的既有落差不擋
  • 適合漸進導入:先 baseline save 記錄現況,之後 CI 用 check --baseline,團隊只需「不要讓還原度更差」

儲存層是獨立的 Parity.Storage(EF Core + SQLite,Pooling=False 即時釋放檔案),引擎裡的 BaselineComparer 是純函式、可單元測試。

里程碑

  • M1 引擎 + CLI 雛形:設計端與實作端兩棵數值樹
  • M2 比對引擎:配對 + 數值 diff + 容差 + 未配對清單 + gate exit code
  • M3 本機報告 UI(parity serve --watch,Kestrel 綁 127.0.0.1)+ parity map 互動配對
  • M4 GitHub Action:可重用 composite action(action.yml)+ 本 repo CI(build / test / 離線示範自我把關)
  • M5 EF Core + SQLite baseline / 歷史(回歸把關 + 分數走勢)+ ImageDesignSource(圖片+標註+像素取樣)+ parity snapshot(凍結現況當基準)

未完成、已知盲點與下一步優先序見 ROADMAP.md;版本變更見 CHANGELOG.md

  • M6(選配)雲端外殼:公開網址掃描 + SSRF 防護

安全

  • Figma token 走環境變數(env:FIGMA_TOKEN),不進 log、不進 URL(用 X-Figma-Token header)
  • 抓過的 frame 存 .parity/cache(已 gitignore),重跑不再打 Figma、可離線比對

License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.9.1 35 7/18/2026
0.9.0 36 7/18/2026
0.8.0 41 7/18/2026
0.7.0 34 7/18/2026
0.6.0 37 7/18/2026
0.5.0 36 7/18/2026
0.4.1 35 7/18/2026
0.4.0 35 7/18/2026
0.3.0 39 7/18/2026
0.2.0 56 7/17/2026
0.1.0 44 7/17/2026