Communitygithub.com

rinkledink/design-by-story

用于构建按用户故事组织的可点击产品原型或设计稿(Web应用、手机应用、平板应用等),生成单个自包含的HTML文件,支持设备切换和故事切换。

design-by-story 是什么?

该技能用于生成单文件HTML原型,将产品原型按用户故事(User Story)组织,而非仅按设备维度展示。它提供了一个顶部栏,包含设备选择器(如Website、Android、iPhone)和故事选择器。选择某个故事后,会重置共享数据为该故事的种子数据,将每个设备导航至故事的起始屏幕,并打开一个面板展示该故事的优先级、状态、验收条件、演练步骤和备注。所有选择状态都保存在URL哈希中(如`#story=S2&device=iphone`),便于分享到评审或工单中。它还支持添加、修改或拆分故事,并提供了详细的质量检查清单和故事格式规范,确保每个故事独立可演示、验收条件可测试。该技能完全基于前端,无需构建步骤、框架或后端,生成的文件可直接从磁盘打开。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/rinkledink/design-by-story-skill/tree/HEAD/design-by-story

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

按故事设计原型

构建一个产品的单文件HTML原型,可在多个设备上查看,并按用户故事组织,如同按设备组织一样。

顶部栏有两个选择器:

  • 设备:例如 Website · Android · iPhone。同一产品,不同框架。
  • 故事:“所有故事”以及每个用户故事对应的条目。选择某个故事会将共享数据重置为该故事的种子数据,将每个设备导航至故事的起始屏幕,并打开一个面板,展示该故事、优先级、状态、验收条件、演练步骤和备注。

两个选择状态都保存在URL哈希中(如 #story=S2&device=iphone),因此可以将某个故事在某个设备上的链接粘贴到评审或工单中。

工作流程

  1. 收集输入(仅在一次消息中询问缺失的内容):

    • 产品是什么,用户是谁。
    • 展示哪些设备。默认:Website + Android + iPhone。如果是现场/自助服务终端使用场景,则加上平板。
    • 用户故事,可以是任何形式(列表、积压工作导出、功能文档)。
    • 验收条件格式: Given/When/Then,还是纯规则?默认使用 Given/When/Then。
    • 优先级(MoSCoW)和状态,如果用户有的话。否则默认为 should / draft。
    • 主要数据模型(主记录 + 关键字段),或从故事中推断。

    将每个故事规范化为故事格式。如果用户没有故事,则从产品描述中草拟3-6个,并在构建前确认。

  2. 检查故事是否符合质量检查清单。以一个简短列表的形式提出问题。不要擅自重写用户的措辞。

  3. 规划每个故事的屏幕。 对于每个故事,列出它在每个设备上涉及的屏幕以及所需的种子数据。只有当产品确实以这种方式工作时(例如,管理员仅在网站上),故事才会跳过某个设备。在其 devices 列表中注明,并且选择该故事时,切换器会禁用其他设备。

  4. 将 assets/shell.html 复制到用户选择的路径(默认为 docs/reference/prototype.html)并填充内容。阅读 references/prototype-spec.md 了解视觉规则(框架、标记、字体、卡片、药丸、提示、抽屉、图标、文案语气)。

  5. 构建屏幕,而非故事。 屏幕是共享的;故事是入口点和种子数据。永远不要为每个故事重复创建屏幕。如果某个故事需要变体,则从种子数据中驱动。

  6. 验证 在浏览器中。如果有浏览器工具,则使用它。否则使用 headless Chrome:--dump-dom "file://…/prototype.html#story=S2&device=iphone" 检查渲染内容,--screenshot 查看,--enable-logging=stderr 捕获控制台错误。检查:

    • 每个故事在其列出的每个设备上是否都显示在正确的屏幕上;
    • 每个验收条件是否都可演示,并且演练步骤是否可点击完成;
    • 在故事中间切换设备是否保持状态;
    • 没有故事的控制出现在另一个故事下,并且“所有故事”下每个故事的控制都能协同工作;
    • 控制台没有错误。
  7. 保持同步。 当后续添加或更改故事时,在同一编辑中更新其 #story-text 条目、BEHAVIOUR 条目以及其列出的每个设备。

故事格式

一个故事在 shell 中存在于两个位置,均由它的 id 索引:

  • 它的文本位于 #story-text JSON 块中,在故事面板中以只读方式显示;
  • 它的行为位于 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作为一名<角色>,我可以<操作>,以便<收益>。收益是用户获得的好处,而不是对操作的重新陈述。
prioritymust · should · could · won't(MoSCoW)。
statusdraft · 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 I see "Nothing open right now."),永远不要写“一条友好的消息”。
  • 结果,而非样式: “它显示为‘完成’”,而不是“出现一个绿色药丸”。只有当视觉细节就是需求时,它才是一个 AC。
  • 在单一设备上可测试。 不要编写关于其他设备的 AC(“出现在所有设备上”、“仅限 Web”、“手机无法更改”)。故事覆盖哪些设备属于 devices,设备间同步是后端的问题。
  • ID 是永久的。 S1-AC3 在编辑、移动或拆分为子故事时保留其 ID,以便可以追溯到测试和工单。新 AC 取 acNext + 1。删除的 ID 永远不会再次分配,且从不重新编号。

质量检查清单

在构建每个故事之前以及每次更改时进行检查:

  • 角色是否是真实的用户角色,收益是否是他们会说的话?
  • 它是否独立交付价值,并且是否足够小以便在一分钟内演示?如果不是,拆分它。
  • 每个 AC 是否有 ID、单一行为和可观察的结果?
  • 是否涵盖了空、错误和极限情况,或者列在 questions 下?
  • 对其他故事的依赖是否可见?(一个共享的功能键,以及在 assumptions 中的提及,例如“基于 S2”。)

故事功能在故事被选中之前是关闭的

故事引入的每个交互都有一个功能键,列在该故事的 features 中。只有当选择了列出该键的故事时,can('save-search') 才为真。

  • 渲染: 将故事的控制包装在 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可以独立发布时,进行拆分。

  1. 选择一个拆分模式 并说明你使用的是哪一种:

    • 工作流程步骤(创建 → 审核 → 发布)
    • 操作(添加 / 编辑 / 删除)
    • 先快乐路径,后边缘情况(空、重复、达到限制)
    • 按设备或角色划分(手机捕获 vs 网站管理)
    • 按数据变化(一个项目 → 多个,一种货币 → 多种)
    • 先简单,后额外功能(简单排序 → 保存的排序偏好)

    每个子故事仍然必须是一个完整的 作为一名… 我可以… 以便… 并具有自身的价值。

  2. 从父故事开始编号子故事: S3 → S3.1, S3.2, … 从 #story-text 和 BEHAVIOUR 中移除父故事,并将子故事放在其位置。shell 的 readHash 会将旧的 #story=S3 链接发送到 S3.1,因此共享链接继续有效。

  3. 划分,而非复制:

    • 验收条件。 每个父故事的 AC 移动到恰好一个子故事中,并保留其原始 ID(S3-AC2 留在 S3.1 内部)。如果一个 AC 跨越两个子故事,则将其重写为两个新的 AC(S3.1-AC1, S3.2-AC1),并注明它们替换了哪个父故事 ID。
    • 功能键。 如果交互被拆分,则拆分键(filter → filter-status, filter-counts)。一个子故事也可能列出兄弟故事的键,如果它基于此键构建,如上述依赖规则所示。
    • 设备。 每个子故事仅列出它实际涉及的设备。
    • 种子。 后续子故事的 seed() 从较早子故事留下的状态开始(例如,S3.2 种子一些已保存的项目),以便可以独立评审每个子故事。
    • 备注。 为每个子故事提供适用于它的范围外项目、假设和问题。除非用户另有说明,否则子故事继承父故事的优先级。
  4. 更新屏幕: 重新门控任何移动到不同键的控件。然后检查每个子故事在其列出的每个设备上,并检查父故事的 id 是否重定向。

  5. 报告拆分 以简短的表格形式(子故事 ID、故事、优先级、设备、AC ID),以便用户可以检查每个父故事 AC 是否都已落地。如果用户在积压工作或功能文档中维护故事,则在其中镜像拆分。

规则

  • 单个HTML文件,内联CSS/JS,无需构建步骤,无需框架,无需后端。直接从磁盘打开。
  • 所有数据位于一个共享的内存中 state;每个设备读取和写入它。
  • 渲染函数接受设备 id 并从 state 重新绘制(参见 shell)。
  • 使用虚构的、中性的示例数据。永远不要将其他项目中的名称、品牌或内容复制到原型中。
  • 移动框架渲染相同的应用标记;Android 和 iPhone 之间的差异是框架本身(状态栏、刘海/岛、手势条),而不是不同的应用,除非用户要求平台特定的UI。

相关技能