@ptengine/inline-editor 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +286 -2
  3. package/dist/InlineEditor.d.ts +349 -0
  4. package/dist/InlineEditor.js +886 -0
  5. package/dist/PagePreview.d.ts +83 -0
  6. package/dist/PagePreview.js +111 -0
  7. package/dist/assets/AssetsPanel.d.ts +19 -0
  8. package/dist/assets/AssetsPanel.js +23 -0
  9. package/dist/assets/componentLibrary.d.ts +23 -0
  10. package/dist/assets/componentLibrary.js +12 -0
  11. package/dist/canvas-ui/index.d.ts +17 -0
  12. package/dist/canvas-ui/index.js +7 -0
  13. package/dist/canvas-ui/scope.d.ts +13 -0
  14. package/dist/canvas-ui/scope.js +1 -0
  15. package/dist/canvas-ui/selection/classNames.d.ts +30 -0
  16. package/dist/canvas-ui/selection/classNames.js +15 -0
  17. package/dist/canvas-ui/selection/tokens.css +204 -0
  18. package/dist/canvas-ui/tree/TreePanel.d.ts +47 -0
  19. package/dist/canvas-ui/tree/TreePanel.js +132 -0
  20. package/dist/canvas-ui/tree/classNames.d.ts +21 -0
  21. package/dist/canvas-ui/tree/classNames.js +8 -0
  22. package/dist/canvas-ui/tree/order.d.ts +38 -0
  23. package/dist/canvas-ui/tree/order.js +38 -0
  24. package/dist/canvas-ui/tree/tree.css +131 -0
  25. package/dist/chrome/EditorChrome.d.ts +83 -0
  26. package/dist/chrome/EditorChrome.js +10 -0
  27. package/dist/chrome/VersionMenu.d.ts +26 -0
  28. package/dist/chrome/VersionMenu.js +86 -0
  29. package/dist/chrome/topbarPrimitives.d.ts +69 -0
  30. package/dist/chrome/topbarPrimitives.js +39 -0
  31. package/dist/chrome/useDismissOnOutside.d.ts +23 -0
  32. package/dist/chrome/useDismissOnOutside.js +27 -0
  33. package/dist/dialogs/ClickActionDialog.d.ts +24 -0
  34. package/dist/dialogs/ClickActionDialog.js +35 -0
  35. package/dist/dialogs/HtmlEditDialog.d.ts +23 -0
  36. package/dist/dialogs/HtmlEditDialog.js +23 -0
  37. package/dist/dialogs/ImageEditDialog.d.ts +30 -0
  38. package/dist/dialogs/ImageEditDialog.js +57 -0
  39. package/dist/dialogs/ImageSettingDialog.d.ts +16 -0
  40. package/dist/dialogs/ImageSettingDialog.js +17 -0
  41. package/dist/dialogs/InsertElementDialog.d.ts +23 -0
  42. package/dist/dialogs/InsertElementDialog.js +28 -0
  43. package/dist/dialogs/ReplaceVideoDialog.d.ts +18 -0
  44. package/dist/dialogs/ReplaceVideoDialog.js +16 -0
  45. package/dist/dialogs/actionTypes.d.ts +53 -0
  46. package/dist/dialogs/actionTypes.js +74 -0
  47. package/dist/dialogs/htmlValidation.d.ts +24 -0
  48. package/dist/dialogs/htmlValidation.js +37 -0
  49. package/dist/dialogs/imageEdit.d.ts +49 -0
  50. package/dist/dialogs/imageEdit.js +30 -0
  51. package/dist/index.d.ts +101 -0
  52. package/dist/index.js +53 -0
  53. package/dist/kernel/CanvasContextBar.d.ts +23 -0
  54. package/dist/kernel/CanvasContextBar.js +12 -0
  55. package/dist/kernel/CanvasFrame.d.ts +56 -0
  56. package/dist/kernel/CanvasFrame.js +53 -0
  57. package/dist/kernel/ai/aiInlineApply.d.ts +330 -0
  58. package/dist/kernel/ai/aiInlineApply.js +477 -0
  59. package/dist/kernel/ai/imageSource.d.ts +24 -0
  60. package/dist/kernel/ai/imageSource.js +19 -0
  61. package/dist/kernel/ai/insertHtmlGate.d.ts +71 -0
  62. package/dist/kernel/ai/insertHtmlGate.js +123 -0
  63. package/dist/kernel/ai/postApplyCheck.d.ts +56 -0
  64. package/dist/kernel/ai/postApplyCheck.js +74 -0
  65. package/dist/kernel/ai/replaceHtmlGate.d.ts +71 -0
  66. package/dist/kernel/ai/replaceHtmlGate.js +147 -0
  67. package/dist/kernel/blocks/BlockOverlay.d.ts +16 -0
  68. package/dist/kernel/blocks/BlockOverlay.js +66 -0
  69. package/dist/kernel/blocks/annotateAndTile.d.ts +106 -0
  70. package/dist/kernel/blocks/annotateAndTile.js +122 -0
  71. package/dist/kernel/blocks/resolveBlocks.d.ts +153 -0
  72. package/dist/kernel/blocks/resolveBlocks.js +233 -0
  73. package/dist/kernel/canvasCapabilities.d.ts +31 -0
  74. package/dist/kernel/canvasCapabilities.js +39 -0
  75. package/dist/kernel/canvasCommands.d.ts +126 -0
  76. package/dist/kernel/canvasCommands.js +146 -0
  77. package/dist/kernel/canvasErrors.d.ts +1 -0
  78. package/dist/kernel/canvasErrors.js +14 -0
  79. package/dist/kernel/captureGeometry.d.ts +35 -0
  80. package/dist/kernel/captureGeometry.js +25 -0
  81. package/dist/kernel/captureOrchestration.d.ts +48 -0
  82. package/dist/kernel/captureOrchestration.js +43 -0
  83. package/dist/kernel/captureSlices.d.ts +36 -0
  84. package/dist/kernel/captureSlices.js +20 -0
  85. package/dist/kernel/changelist/ChangeListPanel.d.ts +13 -0
  86. package/dist/kernel/changelist/ChangeListPanel.js +47 -0
  87. package/dist/kernel/changelist/ChangeListPopover.d.ts +20 -0
  88. package/dist/kernel/changelist/ChangeListPopover.js +10 -0
  89. package/dist/kernel/changelist/builders.d.ts +89 -0
  90. package/dist/kernel/changelist/builders.js +81 -0
  91. package/dist/kernel/changelist/changeSummary.d.ts +19 -0
  92. package/dist/kernel/changelist/changeSummary.js +85 -0
  93. package/dist/kernel/changelist/clipboard.d.ts +16 -0
  94. package/dist/kernel/changelist/clipboard.js +4 -0
  95. package/dist/kernel/changelist/exportForSave.d.ts +30 -0
  96. package/dist/kernel/changelist/exportForSave.js +11 -0
  97. package/dist/kernel/changelist/mergeForSave.d.ts +79 -0
  98. package/dist/kernel/changelist/mergeForSave.js +40 -0
  99. package/dist/kernel/changelist/moveObserverSafety.d.ts +70 -0
  100. package/dist/kernel/changelist/moveObserverSafety.js +39 -0
  101. package/dist/kernel/changelist/overrides.d.ts +22 -0
  102. package/dist/kernel/changelist/overrides.js +63 -0
  103. package/dist/kernel/changelist/useChangeList.d.ts +200 -0
  104. package/dist/kernel/changelist/useChangeList.js +282 -0
  105. package/dist/kernel/config.d.ts +29 -0
  106. package/dist/kernel/config.js +10 -0
  107. package/dist/kernel/dataUrlToBlob.d.ts +8 -0
  108. package/dist/kernel/dataUrlToBlob.js +17 -0
  109. package/dist/kernel/linkHref.d.ts +25 -0
  110. package/dist/kernel/linkHref.js +16 -0
  111. package/dist/kernel/mobileFit.d.ts +46 -0
  112. package/dist/kernel/mobileFit.js +44 -0
  113. package/dist/kernel/moveState.d.ts +35 -0
  114. package/dist/kernel/moveState.js +14 -0
  115. package/dist/kernel/selection/SelectionOverlay.d.ts +10 -0
  116. package/dist/kernel/selection/SelectionOverlay.js +25 -0
  117. package/dist/kernel/selection/SelectionToolbar.d.ts +30 -0
  118. package/dist/kernel/selection/SelectionToolbar.js +82 -0
  119. package/dist/kernel/selection/SelectorPathBar.d.ts +27 -0
  120. package/dist/kernel/selection/SelectorPathBar.js +55 -0
  121. package/dist/kernel/selection/buildDescendantShelf.d.ts +40 -0
  122. package/dist/kernel/selection/buildDescendantShelf.js +50 -0
  123. package/dist/kernel/selection/buildRootShelf.d.ts +26 -0
  124. package/dist/kernel/selection/buildRootShelf.js +149 -0
  125. package/dist/kernel/selection/elementAiContext.d.ts +324 -0
  126. package/dist/kernel/selection/elementAiContext.js +122 -0
  127. package/dist/kernel/selection/elementGeometryForAi.d.ts +9 -0
  128. package/dist/kernel/selection/elementGeometryForAi.js +15 -0
  129. package/dist/kernel/selection/geometry.d.ts +55 -0
  130. package/dist/kernel/selection/geometry.js +18 -0
  131. package/dist/kernel/selection/resolveAnchors.d.ts +116 -0
  132. package/dist/kernel/selection/resolveAnchors.js +173 -0
  133. package/dist/kernel/selection/shelfRow.d.ts +33 -0
  134. package/dist/kernel/selection/shelfRow.js +21 -0
  135. package/dist/kernel/selection/tagLabels.d.ts +9 -0
  136. package/dist/kernel/selection/tagLabels.js +46 -0
  137. package/dist/kernel/selection/toolbarButtons.d.ts +60 -0
  138. package/dist/kernel/selection/toolbarButtons.js +105 -0
  139. package/dist/kernel/selection/useAiSelectionContext.d.ts +26 -0
  140. package/dist/kernel/selection/useAiSelectionContext.js +26 -0
  141. package/dist/kernel/selection/useCanvasGeometry.d.ts +32 -0
  142. package/dist/kernel/selection/useCanvasGeometry.js +47 -0
  143. package/dist/kernel/selection/useSelectionRestore.d.ts +18 -0
  144. package/dist/kernel/selection/useSelectionRestore.js +25 -0
  145. package/dist/kernel/shrinkImageForUpload.d.ts +45 -0
  146. package/dist/kernel/shrinkImageForUpload.js +61 -0
  147. package/dist/kernel/useCanvasBridge.d.ts +36 -0
  148. package/dist/kernel/useCanvasBridge.js +41 -0
  149. package/dist/kernel/useCanvasGate.d.ts +22 -0
  150. package/dist/kernel/useCanvasGate.js +35 -0
  151. package/dist/kernel/useCanvasSession.d.ts +44 -0
  152. package/dist/kernel/useCanvasSession.js +182 -0
  153. package/dist/kernel/useCanvasStream.d.ts +39 -0
  154. package/dist/kernel/useCanvasStream.js +45 -0
  155. package/dist/kernel/useMobileFit.d.ts +40 -0
  156. package/dist/kernel/useMobileFit.js +139 -0
  157. package/dist/locales.d.ts +1094 -0
  158. package/dist/locales.js +545 -0
  159. package/dist/panels/StylePanel.d.ts +24 -0
  160. package/dist/panels/StylePanel.js +40 -0
  161. package/dist/panels/styleFields.d.ts +39 -0
  162. package/dist/panels/styleFields.js +91 -0
  163. package/dist/tree/LayersPanel.d.ts +30 -0
  164. package/dist/tree/LayersPanel.js +15 -0
  165. package/dist/tree/domTreeAdapter.d.ts +63 -0
  166. package/dist/tree/domTreeAdapter.js +90 -0
  167. package/dist/useInlineEditor.d.ts +161 -0
  168. package/dist/useInlineEditor.js +346 -0
  169. package/package.json +58 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1
4
+
5
+ 自 frontend-base `feat/dp/inline-v2` 搬来编辑器包的改动(源仓 develop 已删 `packages/inline-editor`,此后只在本仓改)。
6
+ 全部是新增,不传新 props 的宿主行为不变。
7
+
8
+ **新增**
9
+ - `InlineEditor` 可选 props `onAiInsertedRemoved` / `onAiInsertedRestored`(`useInlineEditor` 的同名输入、
10
+ `useChangeList(sessionId, { onInsertedRemoved, onInsertedRestored })` 同步透传):撤销(单条 / 整批 / 换版本)
11
+ 拿掉 AI 插入的元素时回报其 selector,并从包内的 AI 作用域剔除;回放(切版本 / 切设备)把它们放回画布时回报,
12
+ 并并回作用域。宿主据此同步 AI 的货架与授权票。
13
+ - `AiDescendant.aiInserted` / `AiDescendant.block`:`formatShelfRow` 在能力位之后写 ` [ai-inserted]`,行尾写区块身份 ` [B2 首屏 h:740]`。
14
+ - `AiRejectReason` 新增 `GARBLED_TEXT`(content / summary 含 U+FFFD,该条不落地)与 `RESOLVE_FAILED`
15
+ (selector 探测本身抛错,`detail` 带原文,应重试);真找不到仍是 `ELEMENTS_NOT_FOUND`,并带 `detail`。
16
+
17
+ ## 0.1.0
18
+
19
+ 首个发布版。自 frontend-base(`cecb6de77`)迁入 `inline-editor`,其内部依赖的 `canvas-ui` 并入本包的内部目录(不单独发布)。
20
+
21
+ **产物**
22
+ - 仅 ESM,**逐模块输出**:每个源文件各出一个 `.js` 与 `.d.ts`,目录结构与源码一致,入口 `dist/index.js` / `dist/index.d.ts`;
23
+ 相对导入均带 `.js` 扩展名(Node ESM 可解析);无 sourcemap。消费方打包器能像吃源码时一样按模块摇树、分 chunk
24
+ (单文件产物会被主应用对 `inlineEditorLocales` 等的静态引用整个拉进主 chunk)。
25
+ - `exports` 只开放 `.` 与 `./package.json`,不开放深层子路径。
26
+ - 样式随 canvas-ui 模块带出:`dist/canvas-ui/index.js` 里 `import './selection/tokens.css'`、`import './tree/tree.css'`,
27
+ 两份 CSS 原样位于 `dist/canvas-ui/` 下。`sideEffects: ["*.css", "./dist/canvas-ui/index.js"]` —— 后一项不能少:
28
+ 该文件是纯再导出的桶文件,不列进去打包器会判它无副作用、连同 CSS import 一起摇掉。不做运行时 `<style>` 注入。
29
+ - 外部依赖(不打进包):`react` / `react-dom` / `react-i18next` / `@ptengine/design-components`(peer)、
30
+ `lucide-react` / `@ptengine/inline-core`(dependencies)。
31
+
32
+ **配置入口(不兼容变更,相对迁入前的源码级用法)**
33
+ - 新增 `configureInlineEditor({ extensionId, disabledCapabilities })`,以及 `getInlineEditorConfig()` / `resetInlineEditorConfig()`。
34
+ 预构建产物不再读 `import.meta.env`,扩展 id 与禁用能力改由宿主在应用启动时显式传入。
35
+ 不配 `extensionId` = 内测模式(回退读 `<html data-ptengine-kit-id>`);配了 = 严格模式,只认这一个 id。
36
+ - 删除 `EXTENSION_ID` 常量导出;需要当前 id 请用 `getExtensionId()`。
37
+
38
+ **发布前校验**
39
+ - `pnpm build && pnpm test:build`:断言逐模块产物齐全、无测试文件、相对导入带扩展名且可解析、
40
+ canvas-ui 的 CSS import 与 CSS 文件在位且被 `sideEffects` 命中、外部依赖未内联、d.ts 不引 Node 类型;
41
+ 在临时「装包方」目录里以 `--skipLibCheck false` 编译类型夹具(含 `DomTreeNode.href` 模块增强),
42
+ 并用 vite 真打一次只 import `InlineEditor` 的入口,断言产出 CSS 含 canvas-ui 作用域选择器。
package/README.md CHANGED
@@ -1,3 +1,287 @@
1
- # Temporary Holding Version
1
+ # @ptengine/inline-editor
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 页面内联编辑器:把客户页在产品内 iframe 里渲染出来,选中元素、可视化编辑,
4
+ 产出与 engage-editor 结构一致的 change list。
5
+
6
+ 依赖 Ptengine 浏览器插件(ptengine-kit,不在本仓)提供跨域画布能力:插件负责在客户页里
7
+ 执行画布命令、上报扩展 id,本包通过 externally_connectable 与它通信(见下文「配置扩展 ID」)。
8
+
9
+ ## 两层 API
10
+
11
+ **按宿主的外壳需求选,不要只用成品层。**
12
+
13
+ ```tsx
14
+ // ① headless —— 宿主完全自定义 UI
15
+ const editor = useInlineEditor({ url, device, onChangesChange });
16
+ editor.session // gating / opening / ready / failed
17
+ editor.selection // { selector, identifier, tag, chain, capabilities } | null
18
+ editor.geometry // 选中/hover 矩形 + 滚动态
19
+ editor.changeList // 明文 change list + addStyle/addContent/revert
20
+ editor.commands // select / hover / readOuterHtml / startTextEdit
21
+
22
+ // ② 成品部件 —— 按需拼装
23
+ <EditorChrome variant="full" ... /> // 或 "minimal"
24
+ <CanvasFrame {...editor.frameProps} />
25
+ <SelectorPathBar chain={editor.selection.chain} ... />
26
+ <SelectionOverlay geometry={editor.geometry.latest} ... />
27
+ <SelectionToolbar capabilities={...} ... />
28
+ <StylePanel ... /> <HtmlEditDialog ... />
29
+ <ChangeListPanel ... />
30
+ ```
31
+
32
+ **为什么必须有 headless 层**:编辑器在不同入口下**外壳差异极大** ——
33
+ experience 创建流程要版本管理/设备切换/保存预览的双层顶栏,从 AI 对话打开则只要
34
+ 一条极简顶栏。只提供成品组件的话,后者要么被迫塞进不需要的 UI,要么就得加一堆
35
+ `showXxx` 布尔开关 —— 那是组件复杂度失控的典型开端。
36
+
37
+ ### `<PagePreview>` —— 上面那条主张的样板
38
+
39
+ 分析阶段(AI「创建 experience」的左侧客户页)要的是一块**被动预览板**:
40
+ 顶栏 + 画布 + 编号区块框,**没有**图层面板 / 改动清单 / 选中框 / 选中工具条 /
41
+ 属性面板 / 任何弹框。
42
+
43
+ 它是用 headless 层拼出来的第二个外壳,**不是** `<InlineEditor>` 的一个模式 ——
44
+ `chrome="minimal"` 只换顶栏,上面那一串部件照样全在渲染;而给 `<InlineEditor>`
45
+ 加 `showLayers` / `showToolbar` 之类开关正是本节明令不走的那条路。
46
+
47
+ ```tsx
48
+ <PagePreview
49
+ url={page.url}
50
+ device="mobile"
51
+ viewportWidthOverride={{ mobile: 375 }} // 热图区块是按 375 量的
52
+ blocks={blocks} // resolveBlocks 的产物
53
+ activeBlockId={hoveredInChat}
54
+ onHoverBlock={setHoveredInChat}
55
+ title={hostAndPath}
56
+ subtitle={page.reason}
57
+ onClose={close}
58
+ onDeviceChange={setDevice}
59
+ onSessionReady={sid => void resolveBlocksFor(sid)}
60
+ onReady={setHandle} // capturePage / scrollToSelector / getSessionId
61
+ />
62
+ ```
63
+
64
+ ⚠️ **拾取(pick mode)在预览板里照样开着** —— 那正是这块板子「惰性」的成因:
65
+ `picker.ts` 在客户页上 `preventDefault`,用户点链接不会真的跳走。关掉它,嵌进来的
66
+ 客户页就活了:点一下就导航到别的页,而 sessionId 不变,于是区块坐标/几何/截图全部
67
+ 对着一个已经不在那儿的页面,且零报错。
68
+
69
+ ## 安装与接入
70
+
71
+ ```sh
72
+ pnpm add @ptengine/inline-editor
73
+ ```
74
+
75
+ **peer 依赖**由宿主提供(不会被打进包):`react >=18`、`react-dom >=18`、`react-i18next >=13`、
76
+ `@ptengine/design-components >=0.5.3 <1`。`lucide-react` 与 `@ptengine/inline-core` 是本包的
77
+ dependencies,随包安装。包只发 ESM,逐模块输出(每个源文件一个 `.js` + `.d.ts`,入口 `dist/index.js`),
78
+ 不带 sourcemap;宿主打包器会按模块摇树、分 chunk,编辑器代码可以留在宿主的懒加载 chunk 里。
79
+ 只从包根 `@ptengine/inline-editor` 导入,不开放深层子路径。
80
+
81
+ **样式随 canvas-ui 模块自动带出**:`dist/canvas-ui/index.js` 里 `import './selection/tokens.css'` 与
82
+ `import './tree/tree.css'`,宿主只要用到编辑器(`InlineEditor` / `PagePreview` 等),画布选中框 / 图层树等样式就一并加载,
83
+ 不需要(也不应)单独引 CSS;没有运行时 `<style>` 注入,CSS 由宿主打包器正常抽取(会跟着编辑器进懒加载 chunk)。
84
+ 前提是宿主的打包器遵守 `package.json` 的 `sideEffects: ["*.css", "./dist/canvas-ui/index.js"]`(Vite / webpack 默认都遵守):
85
+ `canvas-ui/index.js` 是纯再导出的桶文件,CSS import 是它唯一的副作用,所以要显式列出,否则会被连同 CSS 一起摇掉。
86
+ 这份 CSS 全部挂在 `.inline-editor-canvas` 作用域下,不会漏进宿主的全局样式。
87
+
88
+ **vitest**:`dist/canvas-ui/index.js` import 了 `.css`,Node 原生 ESM 不认这个扩展名,所以宿主的 vitest
89
+ 要把本包内联交给 Vite 处理,否则凡是引到本包的测试文件都会报 `Unknown file extension ".css"`:
90
+
91
+ ```ts
92
+ // vitest.config.ts
93
+ test: { server: { deps: { inline: ['@ptengine/inline-editor'] } } }
94
+ ```
95
+
96
+ **Tailwind**:包里的组件用了 Tailwind 工具类,宿主的 `content` 必须覆盖产物,否则这些类不会生成:
97
+
98
+ ```js
99
+ // tailwind.config.js
100
+ content: [
101
+ './src/**/*.{ts,tsx}',
102
+ './node_modules/@ptengine/inline-editor/dist/**/*.js'
103
+ ]
104
+ ```
105
+
106
+ **启动时配置**:应用入口处调用一次 `configureInlineEditor`(扩展 id、禁用能力),见下面
107
+ 「宿主接入」第 2 步。i18n 合并见第 1 步。
108
+
109
+ ## 宿主接入
110
+
111
+ **1. 合并 i18n**(必须做,否则满屏 raw key 且不会报错):
112
+
113
+ ```ts
114
+ import { inlineEditorLocales } from '@ptengine/inline-editor';
115
+ // 把 inlineEditorLocales['zh-CN'] 等 merge 进宿主自己的 i18n 资源
116
+ ```
117
+
118
+ **2. 配置扩展 ID**:本包是预构建 ESM,**不再读 `import.meta.env`**,扩展 id 与禁用能力
119
+ 由宿主在应用启动时显式传入。Vite 宿主在入口处调用一次:
120
+
121
+ ```ts
122
+ import { configureInlineEditor } from '@ptengine/inline-editor';
123
+
124
+ configureInlineEditor({
125
+ extensionId: import.meta.env.VITE_PTENGINE_KIT_EXTENSION_ID,
126
+ disabledCapabilities: import.meta.env.VITE_CANVAS_DISABLED_CAPABILITIES
127
+ });
128
+ ```
129
+
130
+ - `extensionId` 非空 = 严格模式,只认这一个 id(正式上线填商店 id)。
131
+ - 不配(或空串、`undefined`)= 内测模式:回退读插件在 `<html data-ptengine-kit-id>`
132
+ 上报的 id。读不到就是没装插件 / 插件没在本页生效,以 `NO_EXTENSION` 失败并走安装引导
133
+ (不配 id 是合法配置,不会出 `EXTENSION_ID_MISSING`)。
134
+ - `disabledCapabilities` 接受逗号分隔字符串或字符串数组(trim、去空),列出的能力即使
135
+ 插件支持也不启用;CORE 能力不能被关掉。
136
+ - 多次调用是浅合并;`getInlineEditorConfig()` 读当前配置,`resetInlineEditorConfig()`
137
+ 清空(仅测试用)。
138
+ - ⚠️ 旧的 `EXTENSION_ID` 常量导出已删除,需要当前 id 请用 `getExtensionId()`。
139
+
140
+ ## 目录
141
+
142
+ | | |
143
+ |---|---|
144
+ | `kernel/` | 会话编排 / 画布 iframe / 几何通道 / 选中层 / change list 生成 |
145
+ | `tree/` | 图层树:DOM 树 → 面板形状的适配器 + 面板外壳 |
146
+ | `assets/` | 组件库:库组件 → 插入 HTML + 资产,面板外壳 |
147
+ | `panels/` `dialogs/` `chrome/` | 属性面板、编辑弹框、两档外壳 |
148
+ | `locales.ts` | 自带三语文案 |
149
+
150
+ `kernel/` 里的东西原先放在产品前端的某个 view 目录下 —— 那是错的位置
151
+ (它不是一个 view,是基础设施),已随本包成型搬到这里。
152
+
153
+ ## 与 v2 编辑器的一致性
154
+
155
+ 图层树的**纯逻辑与 class 名**、选中框/hover 框/工具条的**观感**,都来自本包内部目录
156
+ `canvas-ui/`(不单独发布,随本包一起打进 `dist`)—— 与落地页 v2 编辑器同一套。
157
+ **不要在本包里另写 class 名或改颜色**,要改就改 `canvas-ui/`,别处不要再另起一份。
158
+
159
+ ⚠️ 目前 v2 仍保留它自己那份实现(本期不动线上),所以同一套语义**暂时有两份**。
160
+ 改 `canvas-ui/` 的 `tree/order.ts` 或 `selection/tokens.css` 时,同步确认 v2 侧是否要跟。
161
+
162
+ **本期有意不做的**:缩放手柄(客户页尺寸多由 flex/grid 决定,`elementStyle` 写死 px 会在
163
+ 其它视口坏掉;画出手柄却拖不动比没有更让人困惑)、同屏多设备(每个设备是独立跨域 iframe,
164
+ 而 UA 覆盖靠 DNR,DNR 的规则条件里**没有帧级维度**)。
165
+
166
+ ## 多版本(AI 创建 experience 那条路)
167
+
168
+ **宿主持有数据,包只负责切换机制。** 包里不存多版本状态:它只知道「当前清单」这一份。
169
+
170
+ ```tsx
171
+ <InlineEditor
172
+ versions={[{ id: 'v-a', name: '原始版本' }, { id: 'v-b', name: '版本 B' }]}
173
+ activeVersionId={active}
174
+ versionChanges={changesByVersion} // 宿主持有的明文 change list
175
+ onVersionChange={setActive} // 用户选了 / AI 那批指名了另一版
176
+ onVersionLeave={(id, records) => save(id, records)} // ⚠️ 不接 = 那一版的改动丢了
177
+ onVersionEnter={(id, idMap) => remapCardIds(id, idMap)}
178
+ resolveChangeId={id => aliasTable.get(id) ?? id}
179
+ />
180
+ ```
181
+
182
+ - **不传 `versions` 时行为与今天完全一致**(顶栏仍是 `versionLabel` 那个静态标签,
183
+ 上面这组回调一个都不会触发)。旧调用方不用改。
184
+ - 切换 = `changeList.replaceAll()`:撤光当前的、回放目标版本的。**回放会铸新
185
+ `changeId`**(撤销闭包活在 agent 侧、随 iframe 重建而丢)—— 所以 `onVersionEnter`
186
+ 会交出一张 `idMap`(旧 id → 新 id)。**不拿它维护 `resolveChangeId` 的话**,
187
+ 切走再切回来之后,AI 卡片上的「撤销这一批」会一条都找不到,而且不报错。
188
+ - 首次挂载时 `versionChanges[activeVersionId]` **压过** `initialChanges`,只回放一次
189
+ —— 两边都放的话页面上会出现两遍同样的改动。
190
+ - `AiApplyApi.apply(changes, { versionId })` 指名了另一版时会**先切过去再应用**
191
+ (与切换共用一条串行队列);切换失败则整批 `VERSION_SWITCH_FAILED`,停在当前版本。
192
+ - **切换进行中,编辑器里一切手工写入都被拒**(工具条收起、属性面板关掉、组件插入禁用,
193
+ 清单撤销与切换前就打开的弹框点了会提示「正在切换版本」)。这不是体验取舍:
194
+ 回放那一步有「清单非空就不放」的守卫,切换途中落进一条手改就会让**整个目标版本回放不了**,
195
+ 接着被 `onVersionLeave` 覆盖掉 —— 那一版就此消失,而且不报任何错。
196
+ - 首次回放与切换走**同一条串行队列**,回放也会交出 `onVersionEnter(id, idMap)`
197
+ —— 换设备/重载会重铸全部 changeId,没有这张表,宿主手上卡片的「撤销这一批」全失效。
198
+ - 用户选的版本切换失败时,包会**回推** `onVersionChange(画布上真正装着的那一版)`,
199
+ 宿主据此把顶栏改回去;不回推的话两边永久分叉,而且连重试都不会发生。
200
+ - `revert()` 返回 `{ ok:false, reason:'VERSION_NOT_ACTIVE' }` 表示这一批属于另一个版本
201
+ (包判,宿主判不了)——宿主据此把按钮禁掉,别硬撤:那是静默 no-op。
202
+
203
+ ### `onEditorReady`:宿主拿得到的三件事
204
+
205
+ ```tsx
206
+ <InlineEditor onEditorReady={h => store.setEditorHandle(h)} />
207
+ // h.clearSelection() 清空选中 → AI 的作用域退回整页档(首版生成前必须做)
208
+ // h.peekList() 同步读当前改动清单(不是渲染期 state,不会漏刚写的那条)
209
+ // h.hasUnsavedChanges() 离开确认 / 保存按钮禁用态
210
+ ```
211
+
212
+ **每个画布会话交一次**(切设备 / 重载会再交一次,旧把手发的命令没人应答)。
213
+
214
+ ### AI apply 的两档作用域
215
+
216
+ | | 根 | 货架(授权清单) | 什么时候 |
217
+ |---|---|---|---|
218
+ | 元素 | 用户选中的那个元素 | 它的子树,30 项 | 有选中时 |
219
+ | 整页 | `body` | 整页货架,60 项(`ROOT_SHELF_LIMIT`) | **没有选中**且宿主接了 `onPageContextReady` |
220
+
221
+ 整页档是给首版生成(AI 自己挑改哪儿)的。**它不是把闸放开**:agent 仍然只能逐字抄
222
+ 我们交给它的那份货架,自己拼的 selector 照样 `OUT_OF_SCOPE`。
223
+
224
+ - **没接 `onPageContextReady` 的宿主行为不变**:没选中时 `onAiApplyReady(null)`。
225
+ - 选中一个元素 → 自动收窄成元素档;选中没了 → 退回整页档。
226
+ - 整页货架**每个会话算一次**并缓存(与交给 `onPageContextReady` 的是同一份);
227
+ 换会话(切设备 / 重载)立刻作废,重新采集之前整页档只授权 `body` 一个。
228
+ - `MOVE_ELEMENT` 的 target 仍然只能是**被移动那个元素**的祖先 —— 整页档下这条链取自
229
+ 建货架那次遍历顺带记的路径,不是 body 的祖先(body 没有)。
230
+
231
+ ## AI 改动的八个 op
232
+
233
+ `AI_SAFE_OPS` 与 mastra 的 `EDITOR_INLINE_OPS` **逐字一致**(顺序也一样)。
234
+ 其中换图、改跳转与整块重写多一道**来源闸**,依据由宿主注入:
235
+
236
+ - `allowedImageOrigins` —— `CHANGE_IMAGE` 的 `src` 允许的 origin(图片上传域等;
237
+ 客户页自己的 origin 由包自动加上)。不传 = 只允许同源的图,**这是有意的 fail closed**:
238
+ 模型凭记忆敲出来的域名会被每一个真实访客加载。
239
+ - `allowedLinkUrls` —— `SET_LINK` 允许的**站外**地址,用户在对话里逐字给过的那些
240
+ (**整串**相等,不是 origin 相等)。同源地址不用进这张表。
241
+
242
+ `EDIT_HTML`(整块重写)与 `INSERT_ELEMENT` 是**两道不同的闸**,别复用:
243
+ `insertHtmlGate` 审的是「加一个小元素」,`replaceHtmlGate` 审的是「顶替一个既有元素」——
244
+ 后者强制**单根**(多根会让其后所有 `:nth-of-type` selector 错位,且上线才暴露)、
245
+ 放行 `img`(含图的区块否则永远重写不了,`src` 走 `allowedImageOrigins`,`srcset` 剥掉)、
246
+ 预算 8KB。
247
+
248
+ ## 几条容易踩的约束
249
+
250
+ - **change list 用明文形态**(DEC-22)。change list **跟随 experience 配置一起存库**,
251
+ 包本身不碰存储 —— 宿主在 `onSave` 里拿 `editor.changeList.changes`,经
252
+ **`exportChangesForSave()`** 转成数值形态再放进配置。
253
+ ⚠️ 直接存明文不会报任何错,要等发布后 SDK 读到 `type: 'elementStyle'` 而不是 `53`
254
+ 才暴露。**任何地方都不要手搓 `{type:1,string:...}`**。
255
+ - **样式面板的门控只用能力位**(那一次批量往返拿到的),不发新的探测命令 ——
256
+ 现役 `usesStyleProperty` 往客户页插临时元素测默认值,会让同层 `nth-of-type` 偏移、
257
+ 生成的 selector 上线后指错元素。
258
+ - **length 类样式值必须补单位**:`font-size: 18` 是非法 CSS,浏览器直接忽略,
259
+ 画布上看着"没反应"而 change 已经记下了。
260
+ - **图层树的 id 就是 selector**,会原样交回 `setSelection` / `elementMove`。换成别的
261
+ (下标之类)就要维护映射表,而表一旦与树不同步,点某一行会选中另一个元素**且不报错**。
262
+ selector 撞号时给 **id** 加 `#dup2` 后缀,**绝不能污染 selector** —— 带上后缀发下去
263
+ 在客户页里什么都匹配不到。
264
+ - **树的落点档位与 `elementMove` 的 mode 是两套词表**:树是 before/after/**inside**,
265
+ mode 是 before/after/**top**/**bottom**。用 `moveModeFromDropPos()` 转,别直接传。
266
+ - **组件库插入必须同时注入 css/js**,否则是个没样式的空壳而用户以为插入失败。
267
+ 且 `setCode` 是**全量替换**语义 —— 必须累积已注入的资产、每次发完整集合,
268
+ 只发当前组件会把前面插入的组件样式全清掉;换会话时那份记账要清空。
269
+ - **带 JS 的库组件在画布里不会运行**(`setCode` 的 javascript 条目 agent 只存不执行 ——
270
+ 客户页很可能没装 SDK)。Shadow DOM 组件因此不会升级,看到的是未渲染的自定义标签。
271
+ 这是能力边界,要明确告知用户。**线上生效还需要发布链路带上组件资产**,不在本包职责内。
272
+ - **`isTextChange` 是「编辑文字」与 `</>` 的唯一区别**,两者共用 `mode: 'replace'`。
273
+ 传错会让锚点元素一侧存在一侧不存在,后续 selector 全部错位。
274
+ - **改文字是页面内原位实时编辑**(`commands.startTextEdit`),不是弹框 —— 弹框里看不到
275
+ 换行、容器撑开、撞到相邻元素这些真实排版后果。原来的 `TextEditDialog` 已删除。
276
+ - **开始原位编辑前必须 `iframe.focus()`**(产品侧做)。agent 在客户页里调 `el.focus()`
277
+ 只是在那个文档内部选中元素,浏览器的活动 frame 仍停在产品页 —— 用户敲的字会打到产品页上,
278
+ 表现是「点了编辑文字,什么都没发生,也不报错」。
279
+ - **agent 提交时会先把 innerHTML 还原成编辑前的值**,新 html 只经 `textEditCommit` 事件
280
+ 回来走正常 `applyChange`。不还原的话 `applyChange` 记的 `previousHtml` 就是编辑后的值,
281
+ 撤销会把元素「还原」成编辑后的样子 —— 静默,且看起来像撤销没生效。
282
+
283
+ ## 授权 / 公开可见
284
+
285
+ `UNLICENSED`,Ptmind 内部使用。包发布在公共 npm registry 上,**产物公开可见**:JS 是可读的
286
+ 逐模块 ESM(去掉了注释),`.d.ts` 保留 JSDoc。所以导出类型上的注释与源码里都不要写入机密信息
287
+ (密钥、内部地址、未公开的客户信息等)。