iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0

Day 16 結尾,我替今天寫了三個驗收條件:

匯出的是整份 draft(產品決策加上 Builder 的狀態),不含 Project Specification 和 AI Coding Prompt;匯入要驗證格式;壞資料不能讓 App crash。

今天就照這三個條件,做 draft import/export。

聽起來很簡單:一個按鈕下載 JSON,一個按鈕上傳 JSON。

最快的做法,是把目前整個 app state 直接轉成 JSON 再下載:

current app state
↓
JSON.stringify
↓
download

但這樣會把畫面上那份 Project Specification 和 AI Coding Prompt 也一起包進檔案。

所以今天真正要決定的是:

什麼東西可以放進 draft 檔,什麼東西不能放進去?


Spec 和 Prompt 可以帶走,但不放進 draft 檔

這條資料流是 Day 11、Day 12 定下來的,Day 14 開始寫進程式:

使用者確認的產品決策
↓
Canonical Project Model
↓
Project Specification
↓
AI Coding Prompt

Project Specification 和 AI Coding Prompt 當然可以被使用者帶走。

Spec Preview 本來就有「匯出 Markdown」。

Prompt Preview 也本來就有 Copy Prompt。

但那是 output action。

使用者要拿走輸出結果,沒有問題。

今天處理的是另一件事:

draft 檔裡要不要放 generated output?

我的答案是不要。

因為 draft 是來源資料。

Project Specification 和 AI Coding Prompt 是從來源資料產生出來的結果。

如果 draft 檔裡同時放了來源資料和產生後的結果,之後匯入時就會出現兩份真相:

  • 目前 draft 可以重新產生一份 Spec。
  • 檔案裡可能也藏著一份舊 Spec。

匯出檔會比現在這版 App 活得更久。

ClarifyBuild 的 Project Specification 18 個章節還沒有完全補齊,generator 之後也可能調整。到那時,同一份 draft 可能會產生更完整的 Spec。

如果檔案裡還帶著今天產生的舊 Spec,它就會變成第二個版本的真相。

所以 Day 17 的規則很直接:

draft 檔裡不放 generated output。

匯出時,如果使用者現在停在 Spec Preview 或 Prompt Preview,檔案裡的 stage 也會被改存成 specification_setup。

匯入後,使用者要從目前 draft 重新產生 Project Specification,再重新產生 AI Coding Prompt。

這多了一步。

但這一步是在保護來源資料的邊界。


draft 檔裡放什麼:決策加上 Interaction State

今天匯出的檔案叫 clarifybuild-draft.json。

它放的是兩類資料。

第一類是使用者已經做出的產品決策,也就是 Canonical Project Model 裡的內容:idea、target user、problem、goal、platform、features、scope、Must Have feature specification、Target Project User Flow、project constraints、tech stack decision。

第二類是 Builder 需要恢復流程的 Interaction State:目前 stage、activeGoal、Candidate 是否仍等待確認、scopeBaseline、needsReview、optional details 是否已經提供過。

這些 Interaction State 不算產品決策。

但少了它們,匯入後的 draft 會看起來完整,流程語意卻已經壞掉。

Day 16 那個「研究生」Candidate 也一樣:

系統猜到 target user 可能是研究生,匯出時它是 needs_confirmation,匯入後也必須仍然是 needs_confirmation。

這對應的是 Day 10 就鎖定的規則:

Inference ≠ Resolved。

系統猜到的,不等於使用者確認的。

所以 export 存的是:

Canonical Project Model + Interaction State。


檔案要先說明自己是什麼

今天沒有讓 import 直接相信任意 JSON。

匯出的檔案會帶一個很小的格式邊界:

kind: "clarifybuild.draft"
version: 1
exportedAt: ...

kind 用來說這是一份 ClarifyBuild draft。

version 用來說這是目前支援的 draft file 格式。

exportedAt 只是給人看的匯出時間;目前驗證只確認它是一個字串,不拿它做版本判斷。

這些欄位讓 import 可以先擋掉明顯不對的檔案。

例如不是 ClarifyBuild draft、版本不支援、或根本不是一份合法 draft shape。

沒有這層邊界,import 就會變成「能 JSON.parse 就先塞進 state」。


匯入的是外部檔案,所以要先驗證

Day 16 我留了一題:

壞資料要不要提示使用者?

localStorage 還原失敗時怎麼處理,今天沒動。

如果瀏覽器裡存著壞資料,目前仍然是安靜地回到乾淨的 Builder。不過它和匯入共用同一套驗證,今天補強的檢查,還原時也一起套用。

但 draft import 不一樣。

匯入是使用者主動給 ClarifyBuild 一個檔案。

如果失敗,使用者需要知道。

所以今天的匯入流程是:

先讀檔。

再驗證。

驗證通過,才替換目前 state。

驗證失敗,就顯示錯誤 toast,而且目前 draft 不會被取代。

這裡特別補強了幾個會讓 App 出問題的形狀:

  • flow steps 裡混進 null
  • resolved 的 decision boundary 不是 { aiMayDecide, aiMustAsk }
  • activeGoal 不是 ClarifyBuild 認得的形狀
  • Candidate 不是字串或 null

我也把 App 的 commit 順序改成先 render,再寫入 localStorage。

這樣即使之後有漏網的壞資料穿過驗證,只要 render 失敗,就不會先把壞 draft 寫進 localStorage。

匯入 action 也會保留原本 state。

如果匯入後 render 失敗,就回到原本 draft,並提示「已保留目前 draft」。

這不改 ClarifyBuild 的產品邏輯。

它只是讓外部檔案進來時,先經過一道比較可信的門。


檔案偷塞 generated output,也不採用

今天有一個測試我特別想補。

它測的是一個故意刁難的情境:

如果一份匯入檔案裡偷塞了 generatedSpec 和 generatedPrompt,ClarifyBuild 會不會採用?

理想答案是:不會。

測試檔案可以故意長這樣:

generatedSpec: "# stale generated spec"
generatedPrompt: "stale generated prompt"

匯入後:
generatedSpec: ""
generatedPrompt: ""

這只是產品的資料邊界,跟資安無關。

外部檔案只能帶回 draft。

不能帶回一份聲稱已經產生好的 Spec 或 Prompt。

因為 Spec 和 Prompt 必須由目前 draft 重新產生。

這個測試守的是:

generated output 不能跨過 import/export 邊界,變成來源資料。


今天故意沒做的:匯入前確認

UI 上今天只加了兩個小入口:匯出 Draft 和 匯入 Draft。

匯出會下載 clarifybuild-draft.json。

匯入會讓使用者選一個 JSON 檔。

如果格式正確,就替換目前 draft,並提示要重新產生輸出。

這裡有一個刻意沒做的地方:

匯入前確認。

目前匯入一份合法 draft,會直接取代現在的 draft。

而且 ClarifyBuild 每次變更都會寫進 localStorage,被取代的舊 draft 回不來。

所以真正給使用者用時,應該要有一個確認步驟:

匯入會取代目前 draft,是否繼續?

Day 17 先不做這個。

原因是今天的最小切片是把資料邊界接上:匯出合法 draft、匯入合法 draft、擋掉壞資料、不恢復 generated output。

確認對話、拖拉匯入、更好的錯誤提示,可以接在這條線後面做。


測試守住什麼

Day 17 新增 3 個測試,並補強既有 storage invalid-shape 測試。

新增測試涵蓋五種情境。

第一,匯出的 draft 可以再匯入。匯入後 stage 回到 Specification Setup,needsReview 還在,Candidate 和 scopeBaseline 也仍然維持原本語意。

第二,匯出的 JSON 裡根本沒有 generatedSpec 和 generatedPrompt 這兩個欄位。Day 16 我只是把這兩個欄位存成空字串;今天乾脆不寫,匯出檔和 localStorage 都一樣。

第三,壞檔案會被拒絕。壞 JSON、unknown kind、unsupported version、draft 為 null,匯入都會回傳 null。更細的巢狀 shape 由補強後的 storage invalid-shape 測試守著,因為 import 和 localStorage 還原共用同一套驗證。

第四,即使匯入檔案硬塞 generated output,還原後仍然是空的。

第五,流程裡選「不指定功能」的 step,featureId 是 null,load 和 import 都要接受。

這個是我補強驗證時踩到的。我一度把 featureId 驗成一定要是字串,結果這種合法 draft 匯入會失敗,重新整理後還會被清空。

測試 fixture 每個 step 都有 feature,所以沒抓到,是用真實流程一步一步重放才發現。補強驗證的時候,也要反過來確認它沒有擋掉合法資料。

實作集中在 src/storage/draftStorage.js:新增 createDraftExport 和 importDraft,並讓 localStorage 還原與檔案匯入共用同一套驗證與還原邏輯。

src/main.js 和 src/ui/render.js 接上匯出與匯入入口,也調整了前面說的 commit 順序和匯入失敗時的還原。

Clarification Engine、Scope Engine、Spec Generator、Prompt Assembler 今天都沒動。

目前所有測試都通過:原本 16 個,加上今天的 3 個,現在是 19 個。


Day 17 小結

回頭看 Day 16 的三個條件:draft 匯出時不含 Project Specification 和 AI Coding Prompt;匯入時先驗證格式;壞資料不會讓 App crash:先被驗證擋下,就算漏網,也不會蓋掉目前的 draft。

現在 ClarifyBuild 可以把 Canonical Project Model 和 Interaction State 匯出成 draft 檔,也可以在匯入時先驗證格式、拒絕壞資料、清空 generated output,讓使用者從目前 draft 重新產生 Spec 和 Prompt。

這一步讓 draft 不再只困在 localStorage 裡。

但它也沒有讓 Project Specification 或 AI Coding Prompt 趁機混進 draft 檔。

所以 Day 17 看起來是在做 import/export。

其實還是在守同一件事:

Draft 可以被保存、移動、恢復;但來源資料不能混淆。

明天 Day 18,我應該要補 browser-level test。

因為 Day 15 到 Day 17 已經改了不少真實 UI 互動:structured setup editor、draft export/import、generated output lifecycle。

Node tests 守住了資料規則。

但使用者是不是真的能在瀏覽器裡一路走過 MVP、匯出、匯入、再重新產生輸出,還需要一條瀏覽器層級的測試來確認。

Day 17 完成。

明天見。


上一篇
Day 16|把資料存起來之前,先決定什麼不能被存成真相
系列文
AI 寫不好,可能是我沒說清楚:30 天打造 ClarifyBuild,讓 Vibe Coding 從需求開始 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言