初始化项目版本
This commit is contained in:
77
.agents/skills/prototype-annotation/SKILL.md
Normal file
77
.agents/skills/prototype-annotation/SKILL.md
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
name: prototype-annotation
|
||||
description: 原型标注替代 PRD 时使用:把页面目录、组件说明、状态说明和补充文档接入可运行原型,供评审、交付和后续需求说明使用。
|
||||
---
|
||||
|
||||
# 原型标注
|
||||
|
||||
## 概览
|
||||
|
||||
这是 Axhub Make 客户端原型标注的技术使用指南。它面向 `src/prototypes/` 下的多原型项目,说明如何把 `@axhub/annotation` 接到任意原型里,并维护三类标注能力:目录、组件标注、组件状态。
|
||||
|
||||
这个技能只约束技术接入方式。不要在这里替用户决定标注文案、设计原则、交付场景或页面风格;这些内容以用户需求、原型资料和项目规范为准。
|
||||
|
||||
## 使用场景
|
||||
|
||||
| 场景 | 使用能力 | 数据位置 |
|
||||
| --- | --- | --- |
|
||||
| 目录 | 原型入口、文档入口、链接入口 | `directory.nodes` |
|
||||
| 组件标注 | 给页面元素挂 marker 和说明 | `data.nodes[]` + `locator` |
|
||||
| 组件状态 | 给某个组件提供可切换状态 | `data.nodes[].controls` + `useProtoDevState` |
|
||||
|
||||
## 主流程
|
||||
|
||||
1. 找到目标原型:`src/prototypes/<prototype-id>/`。
|
||||
2. 读取该原型的页面代码、已有 `annotation-source.json` 和相邻资料。
|
||||
3. 判断本次需要哪类能力:目录、组件标注、组件状态。
|
||||
4. 使用 `AnnotationSourceDocument` wire format 维护 `annotation-source.json`。
|
||||
5. 页面里通过 `AnnotationViewer` 静态导入同一份 JSON。
|
||||
6. 多页面或多状态原型要把当前页面/状态传给 `currentPageId`,或通过 `getCurrentPageId` 返回。
|
||||
7. 目录 `route` 节点只会回调宿主;在 `onDirectoryRoute` 里切换页面、状态、数据源或 URL。
|
||||
8. 按改动范围运行验证,通常是:
|
||||
```bash
|
||||
npm run typecheck
|
||||
node scripts/check-app-ready.mjs /prototypes/<prototype-name>
|
||||
```
|
||||
|
||||
需要字段结构、接入代码或控件示例时,读取 `references/axhub-annotation.md`。
|
||||
|
||||
## 目录
|
||||
|
||||
目录不是页面 marker,不需要 `locator`。它用于把多原型项目中的原型、文档和链接组织到标注面板里。
|
||||
|
||||
- `folder`:分组目录节点。
|
||||
- `route`:交给宿主处理,可切当前原型页面、状态、数据源或路由。
|
||||
- `markdown`:打开内联 Markdown 文档。
|
||||
- `markdownPath`:只用于目录 Markdown 文档,可指向当前原型目录内的 `docs/*.md`;客户端构建链路会内联为运行时读取的 `markdown`。
|
||||
- `link`:打开其他原型地址、资源地址或外部链接。
|
||||
|
||||
多原型入口优先用 `link` 指向 `/prototypes/<prototype-id>` 或完整 URL;当前原型内部页面/状态入口再用 `route`。
|
||||
|
||||
## 组件标注
|
||||
|
||||
组件标注使用 `data.nodes[]`。每个节点至少需要稳定 `id`、`locator`、正文字段和时间字段。
|
||||
|
||||
- 优先给目标元素加稳定选择器,例如 `data-annotation-id="<node-id>"`。
|
||||
- `pageId` 可省略;省略时该节点在所有当前页面上下文下显示。
|
||||
- `pageId` 可以是字符串或字符串数组,用于限制 marker 出现在哪些页面/状态。
|
||||
- `hasMarkdown: false` 使用 `annotationText` 和 `images`。
|
||||
- `hasMarkdown: true` 使用 `markdownMap[node.id]`,运行时会忽略 `annotationText` 和 `images`。
|
||||
|
||||
## 组件状态
|
||||
|
||||
组件状态使用节点上的 `controls`。运行时会把控件值写入 proto dev state,页面通过 `useProtoDevState` 读取并渲染对应状态。
|
||||
|
||||
- JSON 中的 `controls` 只写可序列化字段。
|
||||
- 支持控件类型包括 `input`、`inputNumber`、`select`、`segmented`、`switch`、`checkbox`、`slider`、`textarea`、`text`、`button`、`colorPicker`。
|
||||
- 三个或三个以下的离散选项通常用 `segmented`,让选项直接展示。
|
||||
- 如果目录 `route` 也要切状态,在 `onDirectoryRoute` 里读取 `node.payload` 并调用页面自己的状态切换逻辑或 `setProtoDevState`。
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 不要写当前公开 API 里不存在的参数;`AnnotationViewerOptions` 以 `@axhub/annotation` 类型定义为准。
|
||||
- 不要把目录节点误写成组件标注节点;目录没有 marker。
|
||||
- 不要把组件状态只写进页面本地 state;需要出现在标注面板里的状态要写进节点 `controls`。
|
||||
- 不要依赖不稳定 CSS 选择器作为唯一定位方式;能补稳定属性时优先补。
|
||||
- `showBrandLink`、`defaultMarkerIndexVisible`、`renderToolbarActions` 这类展示增强选项只有在用户明确要求品牌入口、默认显示序号或工具栏自定义动作时才设置;常规标注接入保持默认配置。
|
||||
- Markdown 图片必须随原型发布:放到当前原型 `assets/` 并用最终可访问 URL;不要用本地路径、`/api/markdown-file` 或 `../assets/...`。
|
||||
4
.agents/skills/prototype-annotation/agents/openai.yaml
Normal file
4
.agents/skills/prototype-annotation/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "原型标注"
|
||||
short_description: "用原型替代 PRD 交付时,接入页面目录、组件说明和状态说明"
|
||||
default_prompt: "使用 $prototype-annotation 为这个原型接入或维护标注能力。"
|
||||
@@ -0,0 +1,239 @@
|
||||
# Axhub 标注参考
|
||||
|
||||
实现标注数据或把标注运行时接入 Make 客户端原型时,使用这份参考。当前推荐方式是每个原型维护 `annotation-source.json`,页面通过静态 import 传给 `AnnotationViewer`。
|
||||
|
||||
## React 接入
|
||||
|
||||
```tsx
|
||||
import {
|
||||
AnnotationViewer,
|
||||
setProtoDevState,
|
||||
useProtoDevState,
|
||||
} from '@axhub/annotation';
|
||||
import type {
|
||||
AnnotationDirectoryRouteNode,
|
||||
AnnotationSourceDocument,
|
||||
AnnotationViewerOptions,
|
||||
} from '@axhub/annotation';
|
||||
import annotationSourceDocument from './annotation-source.json';
|
||||
```
|
||||
|
||||
在原型里挂载一次 viewer。多页面原型把当前页面 id 传给 `currentPageId`;目录 route 的行为由宿主决定。
|
||||
|
||||
```tsx
|
||||
const options = useMemo<AnnotationViewerOptions>(() => ({
|
||||
showToolbar: true,
|
||||
showThemeToggle: true,
|
||||
showColorFilter: true,
|
||||
emptyWhenNoData: false,
|
||||
toolbarEdge: 'right',
|
||||
currentPageId: activePageId,
|
||||
onDirectoryRoute: (node: AnnotationDirectoryRouteNode) => {
|
||||
if (typeof node.route === 'string') setPage(node.route);
|
||||
},
|
||||
}), [activePageId, setPage]);
|
||||
|
||||
<AnnotationViewer
|
||||
source={annotationSourceDocument as AnnotationSourceDocument}
|
||||
options={options}
|
||||
/>
|
||||
```
|
||||
|
||||
当前公开的 `AnnotationViewerOptions` 包括:
|
||||
|
||||
- `showToolbar`
|
||||
- `showThemeToggle`
|
||||
- `showColorFilter`
|
||||
- `toolbarEdge`
|
||||
- `toolbarAutoHide`
|
||||
- `zIndex`
|
||||
- `emptyWhenNoData`
|
||||
- `currentPageId`
|
||||
- `getCurrentPageId`
|
||||
- `onDirectoryRoute`
|
||||
|
||||
## 数据源结构
|
||||
|
||||
```json
|
||||
{
|
||||
"documentVersion": 1,
|
||||
"format": "axhub-annotation-source",
|
||||
"data": {
|
||||
"version": 2,
|
||||
"prototypeName": "prototype-id",
|
||||
"pageId": "default-page-id",
|
||||
"updatedAt": 1779496800000,
|
||||
"nodes": []
|
||||
},
|
||||
"markdownMap": {},
|
||||
"assetMap": {},
|
||||
"directory": {
|
||||
"nodes": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 目录
|
||||
|
||||
`directory.nodes` 驱动标注面板里的目录。目录节点不绑定页面元素,不显示 marker。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "folder",
|
||||
"id": "project-directory",
|
||||
"title": "项目目录",
|
||||
"defaultExpanded": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "link",
|
||||
"id": "prototype-dashboard",
|
||||
"title": "数据看板原型",
|
||||
"href": "/prototypes/dashboard",
|
||||
"target": "self"
|
||||
},
|
||||
{
|
||||
"type": "markdown",
|
||||
"id": "doc-overview",
|
||||
"title": "需求说明",
|
||||
"markdown": "# 需求说明\n\n这里写内联文档。"
|
||||
},
|
||||
{
|
||||
"type": "link",
|
||||
"id": "external-spec",
|
||||
"title": "外部资料",
|
||||
"href": "https://example.com/spec",
|
||||
"target": "blank"
|
||||
},
|
||||
{
|
||||
"type": "route",
|
||||
"id": "route-empty",
|
||||
"title": "切换空状态",
|
||||
"route": "orders",
|
||||
"payload": { "state": "empty" }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `folder`:目录分组。
|
||||
- `link`:打开其他原型、资源或外链;多原型入口常用 `/prototypes/<prototype-id>`。
|
||||
- `markdown`:必须使用内联 `markdown` 字段;运行时不会通过 `markdownId` 自动去 `markdownMap` 查正文。
|
||||
- `route`:点击时调用 `options.onDirectoryRoute(node)`,运行时不替宿主跳转。
|
||||
|
||||
route 回调示例:
|
||||
|
||||
```tsx
|
||||
const options = useMemo<AnnotationViewerOptions>(() => ({
|
||||
currentPageId: activePageId,
|
||||
onDirectoryRoute: (node) => {
|
||||
if (typeof node.route === 'string') {
|
||||
setActivePageId(node.route);
|
||||
}
|
||||
const payload = node.payload as { state?: string } | undefined;
|
||||
if (payload?.state) {
|
||||
setProtoDevState({ order_state: payload.state });
|
||||
}
|
||||
},
|
||||
}), [activePageId]);
|
||||
```
|
||||
|
||||
## 组件标注
|
||||
|
||||
组件标注写在 `data.nodes[]`,并通过 `locator` 找到页面元素。
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "order-table",
|
||||
"index": 1,
|
||||
"title": "订单表格",
|
||||
"pageId": "orders",
|
||||
"locator": {
|
||||
"selectors": ["[data-annotation-id=\"order-table\"]"],
|
||||
"fingerprint": "section|data-annotation-id=order-table",
|
||||
"path": []
|
||||
},
|
||||
"aiPrompt": "根据订单表格标注生成实现说明。",
|
||||
"annotationText": "",
|
||||
"hasMarkdown": true,
|
||||
"color": "#D97706",
|
||||
"images": [],
|
||||
"createdAt": 1779496800000,
|
||||
"updatedAt": 1779496800000
|
||||
}
|
||||
```
|
||||
|
||||
页面元素示例:
|
||||
|
||||
```tsx
|
||||
<section data-annotation-id="order-table">
|
||||
...
|
||||
</section>
|
||||
```
|
||||
|
||||
正文规则:
|
||||
|
||||
- `hasMarkdown: false`:显示 `annotationText` 和 `images`。
|
||||
- `hasMarkdown: true`:显示 `markdownMap[node.id]`,忽略 `annotationText` 和 `images`。
|
||||
- `pageId` 为空时是全局节点;字符串数组可以绑定多个页面或状态。
|
||||
|
||||
## 组件状态
|
||||
|
||||
组件状态写在标注节点的 `controls`。控件会出现在选中标注的运行时面板中,值会进入 proto dev state。
|
||||
|
||||
JSON 示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "segmented",
|
||||
"attributeId": "order_state",
|
||||
"displayName": "订单状态",
|
||||
"info": "切换订单表格的展示状态。",
|
||||
"initialValue": "normal",
|
||||
"options": [
|
||||
{ "label": "普通", "value": "normal" },
|
||||
{ "label": "空数据", "value": "empty" },
|
||||
{ "label": "异常", "value": "error" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
页面读取示例:
|
||||
|
||||
```tsx
|
||||
type OrderState = 'normal' | 'empty' | 'error';
|
||||
|
||||
function normalizeOrderState(value: unknown): OrderState {
|
||||
return value === 'empty' || value === 'error' || value === 'normal'
|
||||
? value
|
||||
: 'normal';
|
||||
}
|
||||
|
||||
const protoState = useProtoDevState<{ order_state?: OrderState }>();
|
||||
const orderState = normalizeOrderState(protoState.order_state);
|
||||
```
|
||||
|
||||
可用控件类型:
|
||||
|
||||
- `input`
|
||||
- `inputNumber`
|
||||
- `select`
|
||||
- `segmented`
|
||||
- `switch`
|
||||
- `checkbox`
|
||||
- `slider`
|
||||
- `textarea`
|
||||
- `text`
|
||||
- `button`
|
||||
- `colorPicker`
|
||||
|
||||
JSON 数据源里只写可序列化字段。`button` 的函数型 `onClick` 不应写进 JSON。
|
||||
|
||||
## 验收清单
|
||||
|
||||
- `AnnotationViewer` 已挂载,并使用同一份 source document。
|
||||
- 每个可见节点都有稳定 selector,优先使用 `data-annotation-id`。
|
||||
- 多页面或多状态原型的 `currentPageId` 与当前页面/状态一致。
|
||||
- marker 可点击,选中后能看到短标注或 Markdown 正文。
|
||||
- directory 的 `link`、`markdown`、`route` 行为符合宿主回调和目标地址。
|
||||
- 颜色筛选展示当前页用到的所有 marker 颜色。
|
||||
- 组件状态控件变化后,页面通过 `useProtoDevState` 呈现对应状态。
|
||||
Reference in New Issue
Block a user