构建 HTML 原型
所有原型都构建在此技能的 assets/template.html 文件中:一个独立的 HTML 文件。该模板已包含完整外壳:页眉、浏览器框架、状态切换器、备注面板、屏幕地图、评审者评论、带有 k-* 组件的 Koobiq 令牌以及运行时。外壳 UI 根据操作系统/浏览器语言选择英语或俄语;原型本身的文案由你编写。你只需编写 PROTOTYPE 块 (JS) 和第 5 节“PROTOTYPE STYLES”(CSS)。不要破坏模板:切勿重建、重设样式或描述外壳。
设计先于编码
原型是用于单一决策的工具,而非屏幕画廊。在编写 prototype.js 之前,请完成以下步骤。
-
收集上下文。 用户是谁(角色、专业领域),他们正在执行什么任务,触发条件是什么,以及对他们来说“完成”是什么样子。如果产品已存在(代码库、截图、Figma、文档、或早期原型),则从中提取其导航、术语、实体和数据,并仅设计变更部分。不要围绕功能凭空发明产品。
-
命名问题。 用一句话(大型原型可用几句话)说明:评审者必须决定什么,或者 UI 中最可疑的地方是什么(用户能否找到 X、理解 Y、Z 在真实数据下是否成立)。对于每个问题,记录评审者需要看到什么才能回答它,以及哪些状态或数据可能破坏设计。如果请求中未命名问题,则将风险最高的决策作为问题,并在步骤 4 中向作者提出。
-
编写场景,使用用户的语言,从触发条件到结果:“分析师看到峰值 → 打开规则 → 降低阈值 → 查看将会产生多少事件”。然后仅添加问题所需的分支:取消、验证错误、空状态或首次使用,以及那些无人预见到但可能出现的硬情况:无权限、与其他对象的冲突、限制、大量数据、对象当前状态禁止的操作。步骤变为
STEPS,分支变为状态,横切条件(角色、功能开关、数据量)变为SWITCHES。 -
在构建前与作者达成一致。通过一条简短消息发送:
- 问题;
- 你计划展示的场景及其中包含的分支和硬情况;
- 所有你猜测而非从请求或产品中获取的信息,作为编号列表,每项一条陈述,以便作者按编号回答:“2——不,需要经理批准”。
等待回答。对于对现有原型的小修改,可跳过此步骤。如果作者要求不要提问,则基于你的猜测构建,并在回复中列出它们。
-
设置深度 为每个步骤 × 状态单元格:
- 完整 = 在问题的路径上:可点击,每个状态,压力数据;
- 合理 = 上下文:静态但真实;
- 排除 = 在回复中声明未覆盖。
仅使用问题所需的状态:理想状态、空状态或首次使用、部分状态、错误、无权限,每个步骤最多约 6 个状态。仅在展示特定内容(长操作、进度、部分结果)时添加加载状态。加载失败很少值得作为一个状态,但“此对象状态下不允许操作”通常值得。切勿伪造未设计的结果。
-
先以文字形式设计每个屏幕: 其目的(一句话)、一个主要操作,以及用户在此处需要看到才能做出决策的内容(按重要性排序)。然后使用标准 Koobiq 模式(表格、表单、侧面板、模态框、空状态)进行构建。仅在问题所在处引入新颖性;其他所有内容都应像产品通常的屏幕。
- 每个操作都有一个可见的结果:发生了什么以及对象现在在哪里。
- 破坏性操作应指明对象和后果。按钮通常应显示动词。
- 错误信息应说明如何修复并保留用户输入。
- 在屏幕、面包屑、提示和 URL 中,同一事物使用相同的名称。UI 文案应使用用户的语言,如果安装了
ux-write则遵循之。
-
在视图之前编写数据。 使用能够对布局施加压力的真实领域数据:一个长名称、0/1/多个的数量、一个罕见状态。使用固定日期和值(不要使用
Date.now()/Math.random()),并且不要使用 Lorem Ipsum。
变体,仅当问题是“哪个解决方案”时使用。在 VARIANTS 中构建 2–3 个(最多 5 个)变体,而不是替评审者选择。
- 它们在结构(布局、层次结构、主要操作)上不同,而非颜色或文案。如果两个草图看起来相似,则重新制作一个,并明确禁止某种模式(“不得使用卡片网格”)。
- 变体可以共享数据,但绝不能共享布局。
- 为每个变体提供一个
title和一个单行描述text。 - 最佳反馈是“表头来自 B,列表来自 C”。
完成标准
在回复之前,对照以下各项检查原型:
- 每个显示的路径从头至尾运行。 它从用户遇到触发条件的地方开始,而不是从登录页面或仪表盘开始。每个显示的分支都以返回路径或到达结果结束。移交给其他产品或系统应是一个存根,带有可见结果(提示,或类似“在 Jira 中创建任务”的状态),切勿复制其他 UI。次要控件可以不做任何事情(菜单项可以仅是
close),只要没有场景从它开始。 - 有争议的部分已标记。 每个更改或存在争议的元素都带有注释。当问题是采用哪个解决方案时,应有 2–3 个在结构上不同的变体。
- 无多余内容。 每个状态、开关和注释要么有助于回答问题,要么测试关于需求的猜测。删除其余内容。
构建
-
获取可编辑部分。
SKILL_DIR是此技能的目录,A=$SKILL_DIR/scripts/assemble.mjs(使用node运行,无依赖),工作目录例如/tmp/proto-<name>/:- 新原型:
node $A --split $SKILL_DIR/assets/template.html /tmp/proto-<name>; - 修改现有原型:
node $A --split <file>.html /tmp/proto-<name>。重新组装也会升级其外壳。
这会生成
prototype.js(演示 PROTOTYPE 块)和prototype.css(第 5 节)。 - 新原型:
-
重写
prototype.js,替换所有演示内容:PROTO、STEPS、SWITCHES、S、NOTES、LINKS(通常为[])、数据、VIEWS、ACTIONS。自定义 CSS 放入prototype.css。分轮次进行:- 主路径,从头到尾可点击,处于理想状态;
- 其他状态和分支;
- 注释;
- 验证并对照完成标准检查。
在路径可以正常工作之前,不要打磨单个屏幕。
-
组装:
node $A <target>.html /tmp/proto-<name>/prototype.js /tmp/proto-<name>/prototype.css。它会检查语法和必需的声明。仅交付 HTML。 -
视图: 每个步骤一个
VIEWS[step](S),返回一个 HTML 字符串,并根据S.state/S.sw进行分支。使用k-*类构建;参见references/koobiq-cheatsheet.md。- 每个状态在直接打开时都应完全渲染。 直接打开(弹出、哈希链接、地图缩略图、备注、←/→、
protoCheck)从初始的S加上该状态的seed开始,例如{ id: 'confirm', title: '确认', seed: { name: '暴力破解:RDP' } }。流程内的goto()会保留用户输入。VIEWS 仅读取S和常量数据。 - 覆盖层。 模态框或侧面板绘制在它所覆盖的屏幕之上,位于同一视图内。一次性覆盖层(如确认框)是该步骤的一个状态(
form/confirm)。具有自己子流程(表单 → 错误 → 确认)的覆盖层可以是自己的步骤,并在其下绘制被覆盖的屏幕。对于面板上的模态框,先渲染面板,再渲染模态框:最后一个.k-overlay是最顶层的,只有其标记显示。 - 滚动。 比浏览器框架更高的屏幕在框架内部滚动,如同页面。新步骤从顶部开始;同一步骤上的新状态或变体保持滚动位置,以便评审者在同一位置进行比较。应用外壳(
.app-body+.app-content)在.app-content内部滚动,和之前一样。 - 使用
data-act→ACTIONS绑定点击。仅通过goto(step, state)/data-goto切换屏幕,以使状态按钮、URL 栏和哈希保持同步。
- 每个状态在直接打开时都应完全渲染。 直接打开(弹出、哈希链接、地图缩略图、备注、←/→、
-
注释(3–7 条) 仅放在问题所在的元素上:即被更改或存在争议的元素。
- 在元素上放置
class="changed" data-anchor="note-N",并在其内部放入${marker(N)}。将空白或禁用元素包裹在<span class="k-anchor changed" data-anchor="note-N">中。 - 注释
text用 1–2 句话说明更改了什么以及为什么。如果是假设,以“检查:…”结尾。 try表示点击什么可以看到它。
- 在元素上放置
-
屏幕地图 根据状态和转换自动构建。转换来源于每个状态的标记:
data-goto,以及在ACTIONS中其启用的控件、菜单项和顶层覆盖层调用的字面goto()/openState()调用(禁用控件不产生箭头)。在同一操作中计算的状态,如果其可能的 ID 以字符串形式出现在那里,则有效:goto('list', S.q ? 'empty' : 'data')。通过辅助函数、定时器或用户未点击的事件触发的转换,需放入LINKS:{ from: 'report/building', to: 'report/ready', label: '报告就绪' }。 -
验证(参见验证部分)。修复并重新组装,直到
protoCheck()干净为止,然后对照完成标准检查原型。 -
回复 时包含文件路径、步骤 × 状态表、故意未覆盖的内容,以及在达成一致后做出的任何猜测。不要描述外壳。
评审者评论
用户可能会粘贴“复制全部”中的 Markdown。它包含 File: 行上的文件名。每个 ## 是一个屏幕及其哈希(#step/state?switches);变体以 Variant B · Cards → 开头,哈希包含 variant=<id>。外壳使用评审者的 UI 语言编写,因此可能是俄语:Файл:、Вариант B。每个 ### 是一个元素,包含其在 #view 内的选择器、其 data-* 属性以及关联的注释编号,后跟引用格式的评论。
应用评论时,在 VIEWS[step] / ACTIONS 中找到该文件(该变体)中的元素,然后通过 --split 编辑并重新组装到同一文件中:评论存在于浏览器 localStorage 中,以文件路径为键。
外壳规则
- 页眉 = 仅短标题。 框架上方任何位置不得出现副标题、标题、图例或“流程原型…”等文字。
- 单一状态控件: “步骤·状态”弹出。无上一步/下一步步进器、每屏分段控件、框架上方选项卡或自定义快捷键(内置了 ←/→、Esc 和变体选择器 1–9)。
- 注释仅存在于
aside#notes中,.browser之外。 在.app内部只有编号标记。切勿将注释文本放入::after工具提示、覆盖层或应用内的“评审模式”列中。 - 不要编辑 RUNTIME,也不要移动或重设舞台、浏览器、注释、评论或屏幕地图的样式。行为放入
ACTIONS。 - 默认使用 Koobiq:
k-*类和--kbq-*令牌。不要使用十六进制/rgb 颜色、自创调色板(--bg、紫色强调色)以及仅亮色模式样式,因为主题切换必须正常工作。如果使用其他设计系统,则保留外壳,仅重设应用的样式。 - 一个文件,无外部 JS。
PROTOTYPE API
| 内容 | 方法 |
|---|---|
| 标题 / URL 栏 | PROTO.title、PROTO.url(S) |
| 弹出网格 | STEPS[{id,title,states[{id,title,seed?}]}]、SWITCHES[{id,title,options}] |
| 状态 | S.step、S.state、S.sw[id]、S.menu(打开的菜单 id),以及你自己的字段 |
| 导航 | goto(step, state, patch?) 在流程内使用(保留 S);标记 data-act="goto" data-goto="form/errors"。openState(step, state) = 直接打开(初始 S + seed) |
| 动作 | data-act="name" → ACTIONS.name(el, e);重新渲染,除非返回 false |
| 内置动作 | goto;close(S.menu = null);menu + data-menu="id"(切换);set + data-field + data-value(写入 S,关闭菜单)。菜单也可通过 Esc、外部点击和其他任何动作关闭 |
| 输入 | data-model="field" 写入 S.field;data-live 在输入时重新渲染(焦点和光标位置保留);data-live="act" 首先调用 ACTIONS.act;data-scroll="key" 保持滚动位置 |
| 焦点 | goto('form', 'errors', { focus: 'name' }) 一次性聚焦 [data-model="name"] |
| 辅助函数 | icon('search')、marker(n, '--x:100%;--y:50%')、tip(html, text, 'k-float--right')、matches(S.q, ...fields)、toast(title, {kind, text})、esc(str) |
| 注释 | NOTES[{id, step, state, title, text(html), try}]:step/state 指明锚点所在位置 |
| 屏幕地图 | 自动构建;LINKS[{from: 'step/state', to: 'step/state', label}] 添加扫描无法看到的转换 |
| 变体 | VARIANTS[{id, title, text, STEPS?, NOTES?, VIEWS?, ACTIONS?, LINKS?, S?, url?}]。全局声明为变体 A;变体仅覆盖有差异的部分(S 与全局 S 合并)。每个变体有自己的屏幕地图、注释和评论;SWITCHES 以及前进/后退是共享的。选择器在页眉中,键 1–9 |
验证
在后台启动服务器(通常 file:// 被阻止),在浏览器工具中打开打印的 URL 加上 <file>.html。每次重新组装后重新加载,因为仅哈希更改不会重新加载文件。
cd <dir> && node -e "const fs=require('fs'),T={'.html':'text/html; charset=utf-8','.js':'text/javascript','.css':'text/css','.svg':'image/svg+xml'};require('http').createServer((q,r)=>{const p='.'+decodeURIComponent(new URL(q.url,'http://x').pathname);fs.readFile(p,(e,d)=>{r.writeHead(e?404:200,{'Content-Type':T[require('path').extname(p)]||'application/octet-stream'});r.end(e?'Not found':d)})}).listen(0,'127.0.0.1',function(){console.log('http://127.0.0.1:'+this.address().port+'/')})"
- 在页面中运行
protoCheck()(控制台或浏览器工具评估),窗口宽度为 1440 或更大。它必须返回ok: true。它会打开每种步骤 × 状态 × 开关组合(在两个主题下),并报告渲染错误、空屏幕、溢出、损坏的标记或注释、未知的data-act/data-goto/goto()/LINKS目标、硬编码颜色以及模板遗留物。修复每个警告(相同状态、无效开关、非确定性数据、3–7 条之外的注释、未连接到其他步骤的步骤)或在回复中解释它。 - 在意大利面条模式下检查它(在浏览器工具中检查每个非预期的布局问题)。然后再次对照完成标准检查。