前言
完成互動式 HTML 區塊後,KnockersTaskBlocks 的下一個需求是在 Kanboard 任務中建立跨職能流程圖。
這裡的「流程泳道」不是 Kanboard 看板原本用來水平分組任務的工作泳道,而是一張獨立圖表:每條流程泳道代表角色或部門,裡面包含流程節點,節點之間可以用有方向或無方向的關聯連接。
前端決定使用 React Flow,但真正困難的問題不是把節點畫出來,而是哪些資料應該長期保存。若直接把 React Flow 的 nodes、edges、viewport 與 selection 全部存進 Task Document,外掛就會把文件格式綁在某一版 UI library 上。
需求和 Kanboard 原生泳道不同
原生工作泳道服務的是看板分組;新的跨職能流程圖則是一份任務內容,必須支援:
- 自訂流程泳道名稱、顏色與順序。
- 在泳道內建立及拖曳流程節點。
- 將節點移動到其他泳道。
- 從節點拉線建立關聯。
- 區分流程先後、必要依賴與一般關聯。
- 編輯、刪除關聯與節點。
- 自動排列、縮放、小地圖與全螢幕。
- 編輯、摘要、唯讀與公開頁呈現。
這些功能和看板任務沒有一對一同步關係。流程節點不是 Kanboard Task,流程泳道也不是 Kanboard Swimlane。先把兩個領域分開,可以避免後續權限、刪除和同步語意混在一起。
不直接保存 React Flow 狀態
React Flow 需要 parent node、handle、edge route、viewport、selection、量測尺寸與 minimap 狀態,但這些大多只是目前 renderer 的工作資料。
最後保存的是外掛自己的 swimlane_diagram v1:
{
"lanes": [
{
"id": "lane-product",
"title": "產品",
"color": "#F8D56B",
"nodes": [
{
"id": "node-confirm",
"title": "確認需求",
"position": { "x": 20, "y": 72 }
}
]
}
],
"relations": [
{
"id": "relation-confirm-design",
"sourceNodeId": "node-confirm",
"targetNodeId": "node-design",
"type": "sequence"
}
]
}
文件只保存:
- 有順序的流程泳道。
- 每條泳道內有順序的節點。
- 節點相對於所屬泳道的座標。
- 以穩定 ID 連接節點的關聯。
React Flow nodes 和 edges 每次顯示時由這份 Domain JSON 投影產生。未來即使更換 renderer,已保存的文件仍然只描述業務資料,不需要理解舊版 UI library 的內部狀態。
泳道只能當容器,不能拿來連線
流程泳道代表角色或責任範圍,本身不是流程步驟。因此只有 Flow Node 可以作為 relation endpoint。
關聯分成三種:
sequence:來源先於目標,有方向箭頭。dependency:目標依賴來源,有方向箭頭。association:一般關聯,語意上無方向。
Self relation、找不到端點的 dangling relation,以及相同語意的重複關聯都會被拒絕。循環則允許存在,因為真實流程可能回到前一步,但編輯器會提出警告,所有 traversal 也必須使用 visited set,避免圖形處理進入無限迴圈。
跨泳道移動不能換掉節點 ID
使用者把節點拖到另一條泳道時,不能刪掉舊節點再建立新節點,否則現有關聯會全部失效。
正確流程是:
- 保留 Flow Node ID。
- 將座標轉換成目標泳道的相對座標。
- 更新節點所屬容器與陣列位置。
- 保留所有仍然有效的 relation。
刪除節點則相反:所有指向它的 relation 必須在同一次 draft update 中移除,不能留下懸空端點。刪除非空泳道前也要顯示影響範圍,確認後一次刪除泳道、其中節點與相關 relation。
這些規則放在 Domain transformation,而不是散落於拖曳 callback 和 React component 中,才能由單元測試直接驗證。
自動排列必須可重現
第一版沒有引入大型 graph layout engine,而是建立確定性的 lane-stack-v1:
- 流程泳道依文件順序由左到右。
- 每條泳道內的節點依陣列順序排列。
- 圖遍歷使用 stable ID 與 visited set。
- 相同輸入必須產生相同座標。
自動排列不會在每次載入或版本更新時偷偷執行。只有作者按下按鈕後,結果才會寫回正式座標。這保留手動調整,也避免新版演算法在使用者不知情時重排舊圖。
三種原型最後選擇三欄工作台
正式實作前先製作三種可拋棄原型:
A:畫布優先
畫布最大,設定面板浮在右上方。拖曳空間充足,但面板容易遮住右側泳道。
B:三欄工作台
左側顯示泳道與節點結構,中間是 React Flow 畫布,右側固定顯示目前選取項目的設定。資訊密度最高,但大型流程也比較容易定位。
C:文件式編輯
以泳道分頁切換,設定和 JSON 放在畫布下方。它最接近一般內容區塊,卻需要頻繁垂直捲動。
最後選擇 B 作為編輯模式。摘要、唯讀與公開頁不需要三欄工具,只保留乾淨畫布、縮放、符合畫面、小地圖與全螢幕。
原型只用來確認互動方向。正式版仍然重新接回 Task Document Runtime、Revision、validator 和共用 viewer,沒有把 prototype code 直接部署。
大圖不能只靠瀏覽器硬撐
流程圖加入後,效能風險不只來自 JavaScript 檔案大小,也來自節點、關聯與 React re-render。
這個專案為文件設定明確上限:
- 最多 20 條流程泳道。
- 最多 300 個流程節點。
- 最多 600 條節點關聯。
- 單一流程圖 JSON 最多 500,000 bytes。
接近上限前先顯示 warning,server hard limit 才是正式邊界。節點與關聯增加後會停用非必要動畫與昂貴樣式,並評估只 render 可見元素。
流程圖 bundle 也和一般任務頁分開。Task Document 沒有 swimlane_diagram 時,不應下載 React Flow assets;只有區塊接近 viewport 時才載入 viewer。這避免所有普通任務為了少數流程圖支付初始成本。
測試不只驗證畫面上有節點
這個區塊的測試包括:
- Lane、Node、Relation ID 唯一性。
- 跨泳道拖曳後座標和關聯保留。
- 三種關聯的建立、編輯與刪除。
- Self、duplicate 與 dangling relation 拒絕。
- 循環關聯可保存,但 traversal 不會卡住。
- 自動排列結果可重現。
- 刪除節點和泳道時同步清除 relation。
- 編輯、摘要、唯讀與公開模式的控制項差異。
- 大型 fixture 的 mount、drag 與 connect 效能。
- JavaScript 失效時仍能看到泳道與節點文字投影。
這次最大的收穫是:使用 React Flow 不代表資料模型也要長得像 React Flow。UI library 負責互動與繪圖,長期文件則應保存產品領域真正需要的資料。
下一篇會把 Task Document 開放給外部自動化:Kanboard AI Agent API 開發實錄:用 expected_revision 避免內容互相覆蓋。
