Skip to content

@agentaily/annotate

统一的「页面标注 / 评审」核心 —— 框架无关的 vanilla DOM 控制器 + 薄 React 包装组件

进入标注模式后:在页面里 hover 元素高亮(outline + 角标,框在元素间平滑跟随)→ 点选任意元素 → 自动截图该元素 → 就地弹框写意见(可粘贴图片)→ 提交,可多条,Esc 退出。稳定定位器(data-testid / 稳定 id / data-mk-label / 短 CSS path / 最近 React 组件名,容错)、回调式提交、主题走 @agentaily/design-system 的 CSS token(自动双主题、对齐品牌)。

平滑跟随动画:高亮框是单个常驻节点,用 opacity 开关、CSS transition 在元素间滑动/缩放过去(不是消失+重现),跨空白不断闪;弹框淡入上浮。缓动走 DS --ease-out,prefers-reduced-motion 降级。

不内置任何后端 —— onSubmit(annotation) 把结构化标注交回给你,每个消费者插自己的(开 PR / 发到聊天框 / POST Worker…)。

设计目标:让 Storybook 评审 addon、反馈 widget、文档站、预览工作台复用同一套交互。DS 的 MarkupLayer 已收敛为本包 overlay 的 React 门面(container + targetSelector 模式);收敛 addon / feedback-widget 是后续刀。

安装

monorepo 内 workspace:*;外部仓 npm i @agentaily/annotate(React 仅可选 peerDependency)。

两个入口

入口给谁导出
@agentaily/annotatevanilla / Vue / 任意 DOMcreateAnnotator · injectStyles · buildLocator · locatorLabel · STYLES · 类型
@agentaily/annotate/reactReact 消费者<Annotate /> + 类型

React

tsx
import { Annotate } from '@agentaily/annotate/react';

// 受控:自己管 enabled
<Annotate
  enabled={annotating}
  onSubmit={(a) => openPR(a)}        // a: Annotation —— 见下
  onToggle={setAnnotating}
  getRoute={() => location.pathname}
/>

// 或自带触发器:右下角浮动气泡(FAB),不用受控
<Annotate fab fabLabel="评审" onSubmit={(a) => sendToChat(a)} />

<Annotate /> 自身不渲染任何 DOM(返回 null);overlay 挂在 <body>,由核心管理。回调经 ref 读取,换 onSubmit 不会重建控制器。

vanilla / Vue

ts
import { createAnnotator } from '@agentaily/annotate';

const annotator = createAnnotator({
  fab: true,
  onSubmit: (a) => console.log(a),
});
// 命令式:annotator.enable() / .disable() / .toggle() / .isEnabled() / .update({...}) / .destroy()

Vue 里在 onMounted 建、onUnmounteddestroy():

ts
import { onMounted, onUnmounted } from 'vue';
import { createAnnotator } from '@agentaily/annotate';

let a;
onMounted(() => { a = createAnnotator({ fab: true, onSubmit: (x) => emit('submit', x) }); });
onUnmounted(() => a?.destroy());

Annotation(onSubmit 拿到的形状)

ts
interface Annotation {
  selector: Locator | null;   // 稳定定位器(含 rect),free-floating 时为 null
  label: string;              // 短标签:组件名 → mkLabel → testid → tag/id/class
  comment: string;            // 用户写的意见(非空)
  url: string;                // location.href
  route: string;              // getRoute() 或 location.pathname
  screenshot?: string | null; // 选中元素的截图,PNG data URL(见「截图 / 粘贴图片」),无则 null
  ts: string;                 // ISO 时间戳
}

Locator@agentaily/storybook-addon-review / @agentaily/feedback-widget 的定位器形状兼容(本包是它们共享逻辑的收口处)。

截图 / 粘贴图片

点选元素后,弹框会自动用 html2canvas 把选中元素截成 PNG,显示缩略图预览 + 「重截 / 删除」(+ 浏览器支持时「复制」到剪贴板)。提交时 annotation.screenshot 带上这张图。

  • 格式:screenshotPNG data: URL(data:image/png;base64,…)—— 可直接塞进 <img src>、可 JSON 序列化(POST / 存库 / 发聊天框都行),消费者无需再转换。
  • 粘贴(标准体验):在评论框里 Cmd/Ctrl+V 粘贴剪贴板图片(系统截屏后直接贴)→ 覆盖自动截图作为附图。
  • 优雅降级:html2canvas 对跨域图 / iframe / 被污染画布有限制,截图失败时静默降级为只提交文字(screenshot: null),绝不阻断提交;用户也可粘贴一张图补上。
  • 关掉:不想要截图(纯文字 / 性能敏感 / 截不到的页面)→ captureScreenshot={false},连 html2canvas 都不会被加载。
  • 按需加载:html2canvas 是运行时依赖,核心在首次截图时才 import(),从不截图的 vanilla 消费者零成本。

主题

每个颜色 / 圆角 / 字体都读一个 DS CSS 变量(--accent / --surface-panel / --font-mono …),宿主页只要加载了 DS token CSS(@agentaily/design-system/styles.css),overlay 就自动跟随亮/暗主题与品牌。每个 var() 带亮色 fallback,没加载 DS CSS 也不至于裸奔(优雅降级)。不依赖 DS 的任何 React 组件,只吃 token 契约。

选项(AnnotateOptions / <Annotate /> props)

onSubmit(必填)· onCancel · onToggle · enabled · fab · fabLabel · pillLabel · pillActiveLabel(选中写意见时的提示条文案)· placeholder · submitLabel · getRoute · ignoreSelector(永不命中的元素)· injectStyles(默认 true)· captureScreenshot(默认 true,自动截图 + 缩略图 + 粘贴;设 false 纯文字)。详见 src/core/types.ts

收敛用(让 overlay 当别人的门面,如 DS MarkupLayer):container(scoped 模式,挂进某 position:relative 容器、只这块面板可标注,绝对定位)· targetSelector(opt-in 命中,只命中 closest(sel) 的元素)· targetLabel: (el) => string(覆盖角标 / 标题 / annotation.label 取值)。

开发 / 验证

bash
pnpm --filter @agentaily/annotate build       # tsup → dist/{index,react}.{js,d.ts}
pnpm --filter @agentaily/annotate test        # vitest(jsdom):selector + 核心交互 + React 包装
pnpm --filter @agentaily/annotate typecheck

dist/ 被 gitignore;monorepo 内消费者走 dist,pnpm build(turbo)按依赖序先 build 本包。Storybook 演示见 design-systemReview/Annotate story。更全的用法 / 坑见 skill agentaily-annotate