Initial commit: 小羚羚小程序 Axhub Make workspace.

Include xll-miniapp prototype, PRD resources, annotation directory sync, agent skills, and cloud publishing setup.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
王冕
2026-06-30 15:32:30 +08:00
commit a619938e0c
1227 changed files with 251728 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
---
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 文档。
- `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 选择器作为唯一定位方式;能补稳定属性时优先补。

View File

@@ -0,0 +1,4 @@
interface:
display_name: "原型标注"
short_description: "用原型替代 PRD 交付时,接入页面目录、组件说明和状态说明"
default_prompt: "使用 $prototype-annotation 为这个原型接入或维护标注能力。"

View File

@@ -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` 呈现对应状态。