Design by Story
建立一個單一檔案的 HTML 產品原型,可在多種裝置上檢視,並按照使用者故事來劃分,就像按照裝置劃分一樣。
頂部欄有兩個切換器:
- 裝置:例如 網站 · Android · iPhone。相同的產品,不同的框架。
- 故事:「所有故事」加上每個使用者故事一個條目。選取一個故事會將共用資料重設為該故事的種子資料,將每個裝置帶到該故事的起始畫面,並開啟一個面板,顯示該故事、優先級、狀態、驗收條件、逐步演練和備註。
兩個選項都位於 URL 的 hash 中(#story=S2&device=iphone),因此可以將某個裝置上某個故事的連結貼到審查或工單中。
工作流程
-
收集輸入(僅在一次訊息中詢問缺少的項目):
- 產品是什麼以及誰在使用它。
- 要顯示哪些裝置。預設值:網站 + Android + iPhone。如果是現場/自助服務機用途,則加入平板。
- 使用者故事,可以是任何形式(列表、待辦事項清單匯出、功能文件)。
- 驗收條件格式: Given/When/Then,還是簡單規則?預設為 Given/When/Then。
- 優先級(MoSCoW)和狀態,如果他們有的話。否則預設為
should/draft。 - 主要資料模型(主要記錄 + 關鍵欄位),或者從故事中推斷出來。
將每個故事正規化為故事格式。如果使用者沒有故事,則從產品描述中草擬 3-6 個,並在建立前與他們確認。
-
檢查故事是否符合品質檢查清單。用一個簡短的列表來提出問題。不要無聲地改寫使用者的措辭。
-
規劃每個故事的畫面。 對於每個故事,列出它在每種裝置上觸及的畫面以及所需的種子資料。只有在產品真的以那種方式工作時(例如管理員僅在網站上),故事才會跳過某個裝置。它的
devices列表會說明這一點,並且在選取該故事時,切換器會停用其他裝置。 -
複製
assets/shell.html到使用者選擇的路徑(預設為docs/reference/prototype.html)並填入內容。閱讀references/prototype-spec.md以了解視覺規則(框架、標記、字型、卡片、藥丸、提示、抽屜、圖示、文案語調)。 -
建立畫面,而不是故事。 畫面是共用的;故事是入口點和種子資料。永遠不要為每個故事複製一個畫面。如果一個故事需要一個變體,則從種子資料驅動它。
-
在瀏覽器中驗證。 如果你有瀏覽器工具,請使用它。否則使用無頭 Chrome:
--dump-dom "file://…/prototype.html#story=S2&device=iphone"來檢查渲染的內容,--screenshot來查看它,以及--enable-logging=stderr來捕獲主控台錯誤。檢查:- 每個故事在其列出的每種裝置上是否都開啟在正確的畫面;
- 每個驗收條件是否都可以展示,並且可以點擊逐步演練;
- 在故事中切換裝置時是否保持狀態;
- 沒有故事的控件出現在另一個故事之下,並且「所有故事」中每個故事的控件都能共同運作;
- 主控台是乾淨的。
-
保持同步。 當稍後新增或變更故事時,在同一編輯中更新其
#story-text條目、BEHAVIOUR條目以及它列出的每個裝置。
故事格式
一個故事存在於 shell 中的兩個位置,都由其 id 索引:
- 其文字在
#story-textJSON 區塊中,在故事面板中以唯讀方式顯示; - 其行為在
BEHAVIOUR中,這只是程式碼。
<script type="application/json" id="story-text">
{
"S1": {
"title": "儲存搜尋",
"story": "身為訪客,我可以儲存搜尋,以便在無需再次搜尋的情況下收到關於新配對的通知。",
"priority": "must",
"status": "draft",
"ac": [
{ "id": "S1-AC1", "text": "Given 我搜尋了 \"橡木桌\",when 我點選儲存搜尋,then 它會出現在已儲存項目下" },
{ "id": "S1-AC2", "text": "Given 該搜尋已經儲存,when 我再次儲存它,then 已儲存項目仍然只顯示一次" }
],
"acNext": 2,
"walkthrough": ["搜尋 \"橡木桌\"", "點選儲存搜尋", "開啟已儲存項目"],
"outOfScope": ["電子郵件提醒"],
"assumptions": ["訪客必須登入才能儲存"],
"questions": ["已儲存搜尋是否有數量限制?"]
}
}
</script>
const BEHAVIOUR = {
S1: {
devices: ['web', 'android', 'iphone'], // 此故事適用的框架
start: { web: 'search', android: 'search', iphone: 'search' }, // 每個裝置的畫面
features: ['save-search'], // 此故事切換的功能
seed: () => ({ user: SIGNED_IN, saved: [] }), // 此故事的新鮮資料
},
};
| 欄位 | 規則 |
|---|---|
story | 身為 <角色>,我可以 <動作>,以便 <效益>. 效益是使用者獲得的東西,不是動作的重新陳述。 |
priority | must · should · could · won't (MoSCoW)。 |
status | draft · in review · agreed。 |
ac | 物件 { id, text }。ID 為 <故事>-AC<n>,永不重新編號,永不重複使用。請參閱下文。 |
acNext | 此故事曾發出的最高 AC 編號。新的 AC 使用 acNext + 1,並且 acNext 會更新以匹配。每當你新增 AC 時都要更新它。 |
walkthrough | 審查者點擊瀏覽的示範腳本。不是驗收條件;保持簡短。 |
outOfScope, assumptions, questions | 純文字列表。寧可留空也不要編造填充物。將使用者不確定的任何內容記錄為問題。 |
seed() 在每次呼叫時都回傳一個新鮮的物件,因此重新選擇一個故事總會重新乾淨地啟動它。「所有故事」使用 DEFAULT_SEED 和每個裝置的首頁。
驗收條件
- 每個 AC 一個可測試的行為,採用使用者選擇的格式。Given/When/Then 一行完成:
Given <上下文>, when <動作>, then <可觀察的結果>。 - 精確的文案: 當 AC 是關於一則訊息時,引用該訊息(
then 我看到「目前沒有開啟的項目。」),永遠不要寫「一條友善的訊息」。 - 結果,而非樣式: 「它顯示為已完成」,而不是「一個綠色藥丸出現」。只有當視覺細節就是需求時,它才是一個 AC。
- 在一個裝置上可測試。 不要撰寫關於其他裝置的 AC(「出現在每個裝置上」、「僅限網站」、「手機無法更改」)。一個故事涵蓋哪些裝置屬於
devices,而裝置之間的同步是後端的責任。 - ID 是永久的。
S1-AC3在編輯、移動或拆分到子故事時保留其 ID,以便可以追溯到測試和工單。新的 AC 使用acNext + 1。已刪除的 ID 不會再次發出,也不會重新編號。
品質檢查清單
在建立每個故事之前,以及每次它發生變化時進行檢查:
- 角色是否是真正的使用者角色,並且效益是否是他們會說的話?
- 它是否獨立交付價值,並且是否足夠小以在一分鐘內演示?如果不是,拆分它。
- 每個 AC 是否都有 ID、單一行為和可觀察的結果?
- 空值、錯誤和限制情況是否都已涵蓋,或列在
questions下? - 對其他故事的依賴關係是否可見?(一個共用的功能鍵,加上
assumptions中的提及,例如「基於 S2 構建」。)
故事功能在其故事被選取之前是關閉的
每個故事引入的互動都有一個功能鍵,列在該故事的 features 中。can('save-search') 只有在選擇了列出它的故事時才為 true。
- 渲染: 將故事的控件包裹在
can(...)中,這樣它們就不存在(沒有按鈕、沒有表單、沒有選單項目)。不要將它們顯示為禁用狀態。審查者應該只看到所選故事新增的內容。 - 處理器: 每個故事處理器以
if (!can('save-search')) return;開頭,這樣在故事之外什麼都不會觸發,即使是來自過時的 DOM 或主控台。 - 「所有故事」 會同時開啟每個故事的功能:整個產品的當前樣貌,使用
DEFAULT_SEED。檢查故事是否在那裡發生衝突(例如,兩個控件在同一個位置)。 - 依賴另一個故事互動的故事會列出兩個鍵(例如「按狀態篩選」列出
['filter', 'complete'],這樣就可以顯示勾選任務會如何影響篩選後的列表)。 - 故事僅顯示的資料(例如受指派者的姓名縮寫)不受限制;只有更改它的能力才受限制。
變更故事
原型僅顯示故事;它永遠不會編輯或儲存故事。所有的故事更改都透過 Claude 在聊天中進行:
- 新故事: 草擬故事、AC 和問題,與使用者一起執行品質檢查清單,然後在一個編輯中寫入
#story-text條目和BEHAVIOUR條目並建立畫面。一個故事永遠不會只作為文字存在。如果它只是文字存在,shell 會將其排除在切換器之外,並在主控台中記錄一個錯誤。 - 已變更的故事(措辭、AC、優先級、狀態、備註):編輯
#story-text。如果更改改變了故事的功能(新的 AC、不同的逐步演練),請在同一編輯中更新畫面和BEHAVIOUR以匹配。 - 審查回饋: 當使用者轉達審查者的評論時,將每個評論轉換為上述變更之一,如果仍然懸而未決,則轉換為
questions下的一個條目。 - 總是說明你建立或更改了什麼,按故事和 AC ID 來區分。
如果使用者將故事保留在追蹤器(Jira 或類似工具)中,則該追蹤器是主副本。在此處鏡像其措辭,而不是僅在原型中編輯故事。
拆分故事
當使用者要求拆分一個故事,或者一個故事有大約 5 個以上的 AC、超過一個主要功能鍵,或者有可以單獨交付的 AC 時,將其拆分。
-
選擇一個拆分模式並說明你使用了哪一種:
- 工作流程步驟(建立 → 審查 → 發布)
- 操作(新增 / 編輯 / 刪除)
- 先快樂路徑,然後邊緣情況(空值、重複、達到限制)
- 按裝置或角色(手機擷取 vs. 網站管理)
- 按資料變化(一個項目 → 許多,一種貨幣 → 幾種)
- 先簡單,然後附加功能(普通排序 → 已儲存的排序偏好)
每個子故事仍然必須是一個完整的
身為 <角色>,我可以 <動作>,以便 <效益>,並且有其自身的價值。 -
從父故事開始編號子故事:
S3→S3.1,S3.2, … 從#story-text和BEHAVIOUR中移除父故事,並將子故事放在原來的位置。shell 的readHash會將舊的#story=S3連結發送到S3.1,因此共享連結可以繼續運作。 -
劃分,不要重複:
- 驗收條件。 每個父故事的 AC 都移動到恰好一個子故事中,並保留其原始 ID(
S3-AC2在S3.1內部保持為S3-AC2)。如果一個 AC 跨越兩個子故事,將它重寫為兩個新的 AC(S3.1-AC1,S3.2-AC1),並註明它們取代了哪個父故事 ID。 - 功能鍵。 如果一個鍵的互動被拆分,則拆分它(
filter→filter-status,filter-counts)。一個子故事也可能列出其兄弟故事的功能鍵,當它基於該功能鍵構建時,如上述依賴規則所述。 - 裝置。 每個子故事只列出它真正觸及的裝置。
- 種子資料。 後續子故事的
seed()從較早子故事會留下的狀態開始(例如 S3.2 種子資料已儲存了某些項目),這樣每個子故事都可以獨立審查。 - 備註。 為每個子故事提供適用於它的範圍外項目、假設和問題。除非使用者另有說明,否則子故事繼承父故事的優先級。
- 驗收條件。 每個父故事的 AC 都移動到恰好一個子故事中,並保留其原始 ID(
-
更新畫面: 對任何移動到不同鍵的控件重新設限。然後,在其列出的每種裝置上檢查每個子故事,並檢查父故事的 id 是否重定向。
-
將拆分報告為一個簡短的表格(子故事 id、故事、優先級、裝置、AC ID),以便使用者可以檢查每個父故事的 AC 是否都落到了某個地方。如果使用者在那裡保留故事,請在他們的待辦事項或功能文件中鏡像此拆分。
規則
- 一個 HTML 檔案,內嵌 CSS/JS,無建置步驟,無框架,無後端。直接從磁碟開啟。
- 所有資料在一個共用的記憶體中
state;每個裝置讀取和寫入它。 - 渲染函數接受裝置 id 並從
state重新繪製(參見 shell)。 - 使用虛構的、中立的範例資料。永遠不要將其他專案中的名稱、品牌或內容複製到原型中。
- 行動框架渲染相同的應用程式標記;Android 和 iPhone 之間的差異是瀏覽器裝飾(狀態列、瀏海/動態島、手勢條),而不是不同的應用程式,除非使用者要求特定平台的 UI。