From 0f5c777a57799e0a7fc364e7d8f2d8f7ec84ac0d Mon Sep 17 00:00:00 2001 From: Jin <22962980+JYC333@users.noreply.github.com> Date: Tue, 10 Mar 2026 01:08:58 +0000 Subject: [PATCH] refactor: remove plan file --- .agents/migration_plan_autocomplete.md | 311 ------------------------- 1 file changed, 311 deletions(-) delete mode 100644 .agents/migration_plan_autocomplete.md diff --git a/.agents/migration_plan_autocomplete.md b/.agents/migration_plan_autocomplete.md deleted file mode 100644 index 1d89f89e6e..0000000000 --- a/.agents/migration_plan_autocomplete.md +++ /dev/null @@ -1,311 +0,0 @@ -# Migration Plan: `autocomplete.js` → `@algolia/autocomplete-js` - -> Issue: https://github.com/TriliumNext/Trilium/issues/5134 -> -> 目标:将旧的 `autocomplete.js@0.38.1`(jQuery 插件)迁移到 `@algolia/autocomplete-js`(独立组件) - ---- - -## 当前状态总览 - -### 两个库的架构差异 -| | 旧 `autocomplete.js` | 新 `@algolia/autocomplete-js` | -|---|---|---| -| 模式 | jQuery 插件,**增强已有 ``** | 独立组件,**传入容器 `
`,自己创建 ``** | -| 初始化 | `$el.autocomplete(config, [datasets])` | `autocomplete({ container, getSources, ... })` 返回 `api` | -| 操作 | `$el.autocomplete("open"/"close"/"val")` | `api.setIsOpen()`, `api.setQuery()`, `api.refresh()` | -| 销毁 | `$el.autocomplete("destroy")` | `api.destroy()` | -| 事件 | jQuery 事件 `autocomplete:selected` | `onSelect` 回调、`onStateChange` 回调 | -| DOM | 增强已有 input,添加 `.aa-input` 类 | 替换容器内容为自己的 DOM(`.aa-Form`、`.aa-Panel` 等) | - -### 关键迁移原则 -1. **不使用 wrapper/适配层**,直接在各 service 中调用 `autocomplete()` API -2. 消费者代码需要适配:传入容器 `
` 而非 ``,通过 API/回调读写值 -3. 增量迁移:每个使用点独立迁移,逐一验证 -4. **优先保留旧版业务逻辑与交互语义**:迁移时默认以旧版 `autocomplete.js` 行为为准,不主动重设计状态流或交互。 -5. **只有在新旧包能力或生命周期模型存在冲突、无法直接一一映射时,才允许添加补丁逻辑**;这类补丁的目标不是“接近”,而是尽可能恢复与旧版完全相同的 behavior。 - -### 涉及的功能区域 -1. **属性名称自动补全** — `attribute_autocomplete.ts` → `attribute_detail.ts`、`RelationMap.tsx` -2. **标签值自动补全** — `attribute_autocomplete.ts` → `attribute_detail.ts` -3. **笔记搜索自动补全** — `note_autocomplete.ts` → `NoteAutocomplete.tsx`、`attribute_detail.ts` -4. **关闭弹出窗口** — `dialog.ts`、`entrypoints.ts`、`tab_manager.ts` -5. **CKEditor 提及** — 不使用 autocomplete.js,**无需迁移** - ---- - -## 迁移步骤 - -### Step 0: 安装新依赖 ✅ 完成 -**文件变更:** -- `apps/client/package.json` — 添加 `@algolia/autocomplete-js@1.19.6`,暂时保留 `autocomplete.js` - -**验证方式:** -- ✅ 新依赖安装成功 - ---- - -### Step 1: 迁移属性名称自动补全 ✅ 完成 -**文件变更:** -- `apps/client/src/services/attribute_autocomplete.ts` — 将 `initAttributeNameAutocomplete()` 完全使用 **Headless API (`@algolia/autocomplete-core`)** 重写,移除遗留的 jQuery autocomplete 调用。 -- `apps/client/src/widgets/attribute_widgets/attribute_detail.ts` — 维持原有 `` 模型不变,仅需增加 `onValueChange` 处理回调。 -- `apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx` — 维持原有回调逻辑,新旧无感替换。 -- `apps/client/src/stylesheets/style.css` — 增加自定义 Headless 渲染面板样式 (`.aa-core-panel`,`.aa-core-list` 等)。 - -**架构说明:** -由于 Trilium 依赖同一页面同时运行多个 autocomplete 生命周期(边栏属性列表,底部编辑器等),原生 `@algolia/autocomplete-js` 会因为单例 DOM 冲突强行报错 "doesn't support multiple instances running at the same time"。 -解决方案是退化使用纯状态机的 `@algolia/autocomplete-core`,自己进行 DOM 劫持与面板渲染。 -- `requestAnimationFrame`:针对下拉层自动跟踪光标位置,适配面板的高频大小变化 -- 事件阻断:拦截了选择时候的 `Enter` 返回键事件气泡,避免误触外层 Dialog 销毁。 - -**验证方式:** -- ✅ 打开一个笔记 → 点击属性面板弹出 "Label detail" → 输入属性名称时正常显示下拉自动补全框 -- ✅ 放大/缩小/变形整个面板,下拉菜单粘连位置准确 -- ✅ 键盘上下方向键可高亮,按 Enter 可选中当前项填充,且对话框不关闭 -- ✅ 关系图 (Relation map) 创建关系时,关系名输入框的自动补全同样工作正常 - ---- - -### Step 2: 迁移标签值自动补全 ✅ 完成 -**文件变更:** -- `apps/client/src/services/attribute_autocomplete.ts` — 移除旧有的 jQuery `$el.autocomplete` 初始化,整体复用封装的 `@algolia/autocomplete-core` Headless 架构流。在内部设计了一套针对 Label Name 值更变时的 `cachedAttributeName` 以及 `getItems` 数据惰性更新机制。 -- `apps/client/src/widgets/attribute_widgets/attribute_detail.ts` — 取消监听不标准的 jQuery 强盗冒泡事件 `autocomplete:closed`,改为直接在配置中传入清晰的 `onValueChange` 回调函数。同时解决了所有输入遗留 Bug。 - -**说明与优化点:** -与 Step 1 类似,同样完全剔除了所有的残旧依赖与 jQuery 控制流,在此基础上还针对值类型的特异性做了几个高级改动: -1. **取消内存破坏型重建 (Fix Memory Leak)**:旧版本在每次触发聚焦 (Focus) 时都会发送摧毁指令强扫 DOM。新架构下只要容器保持存活就仅仅使用 `.refresh()` 接口来控制界面弹出与数据隐式获取。 -2. **惰性与本地缓存 (Local Fast CACHE)**:如果关联的属性名 (Attribute Name) 没有被更改,再次打开提示面板时将以 0ms 的延迟抛出旧缓存 `cachedAttributeValues`。一旦属性名被修改,则重新发起服务端网络请求。 -3. **彻底分离逻辑**:删除了文件中的 `still using old autocomplete.js` 遗留注释,此时 `attribute_autocomplete.ts` 文件内已经 100% 运行在崭新的 Autocomplete 体系上。 - -**验证方式:** -- ✅ 打开属性面板 → 点击或输入任意已有 Label 类型的 Name → 切换到值输入框 → 能瞬间弹出相应的旧值提示列表。 -- ✅ 在旧值提示列表中用上下方向键选取并回车 → 能实现无缝填充并将更变保存回右侧详细侧边栏。 -- ✅ 解决回车冲突:确认选择时系统发出的事件能干净落回所属宿主 DOM 且并不抢占外层组件快捷键。 - ---- - -### Step 3: 迁移笔记搜索自动补全核心 (拆分为 4 个增量阶段) - -由于搜索自动补全模块(`note_autocomplete.ts`)承载了系统最为复杂的交互、多态分发与 UI,我们将其拆分为 4 个逐步可验证的子阶段: - -#### Step 3.1: 基础骨架与核心接口联通 (Headless 骨架) ✅ 完成 -**目标:** 用 `@algolia/autocomplete-core` 完全接管旧版的 `$el.autocomplete` 初始化,打通搜索接口。 -**工作内容:** -- 在 `initNoteAutocomplete()` 中引入基于 `instanceMap` 的单例验证逻辑与 DOM 隔离。 -- 建立 `getSources`,实现调用 `server.get("api/search/autocomplete", ...)`。 -- 只做极其简单的 UI(比如简单的 `ul > li` text)将获取到的 `title` 渲染出来,确保网络流程畅通。 -**完成情况与验证 (**`apps/client/src/services/note_autocomplete.ts`**):** -- ✅ 彻底移除了原依赖于 jQuery `autocomplete.js` 的各种初始化配置与繁复的字符串 DOM 拼接节点。 -- ✅ 实现了对 `Jump to Note (Ctrl+J)` 等真实组件的向下兼容事件 (`autocomplete:noteselected`) 无缝派发反馈。 -- ✅ 在跳往某个具体笔记或在新建 Relation 面板选用特定目标笔记时,基础请求和简装提示版均工作正常。 - -#### Step 3.2: 复杂 UI 渲染重构与匹配高亮 (模板渲染) ✅ 基本完成 -**目标:** 实现与原版相同级别(甚至更好)的视觉体验(例如笔记图标、上级路径显示、搜索词高亮标红等)。 -**工作内容:** -- 重写原有的基于字符串或 jQuery 的构建 DOM 模板代码(专门处理带 `notePath` `icon` `isSearch` 判断等数据)。 -- 将 DOM 构建系统集成到 `onStateChange` 的渲染函数里,通过 `innerHTML` 拼装或 DOM 手工建立实现原生高性能面板。 -- 引入对应的样式 (`style.css`) 补全排版缺漏。 -**验证方式:** 下拉出的搜索面板变得非常美观,与系统的 Dark/Light 色调融合;笔记标题对应的图标出现,匹配的字样高亮突出。 -**当前验证结果:** -- ✅ `Ctrl+J / Jump to Note`:UI 渲染、recent notes、键盘/鼠标高亮联动、删空回 recent notes 等核心交互已基本恢复。 -- ✅ `attribute_detail.ts` 等依赖 jQuery 事件的目标笔记选择入口,抽查结果正常。 -- ⚠️ React 侧消费者尚未完成迁移验收。抽查 `Move to` 时发现功能不正常,这部分应归入 **Step 5** 继续处理,而不是视为 Step 3.2 已全链路完成。 - -#### Step 3.3: 差异化分发逻辑与对外事件抛出 (交互改造) ✅ 基本完成 -**目标:** 支持该组件的多态性。它能在搜笔记之外搜命令(`>` 起手)、甚至是外部链接。同时能够被外部组件监听到选择动作。 -**工作内容:** -- 在选择项(`onSelect`)的回调中,根据用户选的是“系统命令”、“外部链接”还是“普通笔记”走截然不同的行为逻辑。 -- 对外派发事件:原本通过 `$el.trigger("autocomplete:noteselected")` 的逻辑需要保留,以保证那些使用了搜索框的组件(例如右侧关系面板)依然能顺利收到选中反馈。 -**验证方式:** 选中某个建议项时能够真正实现页面的调转/关系绑定;输入 `>` 开头能够列举出所有快捷命令(如 Toggle Dark mode)。 -**当前验证结果:** -- ✅ 选择分发已按旧版语义迁移:`command`、`external-link`、`create-note`、`search-notes` 与普通 note 走独立分支。 -- ✅ `autocomplete:noteselected`、`autocomplete:externallinkselected`、`autocomplete:commandselected` 三类对外事件均已保留。 -- ✅ 鼠标点击和键盘回车现在统一走同一套 `handleSuggestionSelection()` 分发逻辑,不再额外误抛 `autocomplete:noteselected`。 -- ✅ `Ctrl+J / Jump to Note` 与 `attribute_detail.ts` 的普通 note 选择链路已抽查通过。 -- ⚠️ React 消费方整体仍应放在 **Step 5** 继续验收;`Move to` 等问题不属于 Step 3.3 本身已完成的范围。 - -#### Step 3.4: 特殊键盘事件拦截与附带按钮包容 (终极打磨) ✅ 基本完成 -**目标:** 解决在旧 jQuery 中强绑定的 IME(中日韩等输入法)防抖问题,并恢复如 `Shift+Enter`、周边附加按钮(清除等)的正常运作。 -**工作内容:** -- 将旧的输入法合成事件 (`compositionstart` / `compositionend`) 判断逻辑迁移到新的 `onInput` / `onKeyDown` 外围保护之中。 -- 重构对 `Shift+Enter` (唤起全文搜索)、`Ctrl+Enter` 等组合快捷键的劫持处理。 -- 修正周边辅助控件(例如搜索栏自带的 “最近笔记(钟表)”、“清除栏(X)” 按钮)因为 DOM 结构调整可能引发的影响。 -**验证方式:** 中文拼音输入法敲打途中不会错误地发起网络搜索;各种组合回车热键重新生效,整个搜索系统重回巅峰状态。 -**当前验证结果:** -- ✅ `compositionstart` / `compositionend` 已恢复旧版保护逻辑:合成期间不发起搜索,结束后按“清空再恢复 query”的语义重新跑一次。 -- ✅ `Shift+Enter` 与 `Ctrl+Enter` 的快捷分发仍保留,并已按旧版语义接回全文搜索 / `search-notes`。 -- ✅ `autocomplete:opened` / `autocomplete:closed` 事件已重新补回,`readonly` 与“关闭时空输入框清理”逻辑重新对齐旧版。 -- ✅ 清空按钮、最近笔记按钮、全文搜索按钮都继续走 service 内部统一入口,而不是分散拼状态。 -- ⚠️ 这一步仍以 `note_autocomplete.ts` 核心行为为主;React 消费方的问题继续留在 **Step 5**。 - ---- - -### Step 4: 迁移辅助函数 ✅ 完成 -**文件变更:** -- `apps/client/src/services/note_autocomplete.ts` — `clearText`, `setText`, `showRecentNotes` 等函数 - -**说明:** -这些函数使用旧库的操作 API(`$el.autocomplete("val", value)` 等),需要改为新库的 `api.setQuery()` / `api.setIsOpen()` / `api.refresh()`。 -这一步与 **Step 3.4** 有交叉,但并不重复: -- **Step 3.4** 关注的是 IME、快捷键、按钮点击后的交互语义是否与旧版一致 -- **Step 4** 关注的是 helper 函数本身是否已经彻底切到新 API,而不再依赖旧版 `.autocomplete("...")` - -**当前完成情况:** -- ✅ `clearText()` 已改为通过 headless instance 清空 query、关闭面板并触发 `change` -- ✅ `setText()` 已改为通过 `showQuery()` 驱动 `setQuery()` / `refresh()` -- ✅ `showRecentNotes()` 已改为走 `openRecentNotes()`,不再依赖旧版 `.autocomplete("open")` -- ✅ `showAllCommands()` 已改为直接设置 `">"` query 打开命令面板 -- ✅ `fullTextSearch()` 已改为使用新状态流重跑全文搜索 - -**验证方式:** -- 最近笔记按钮 → 下拉菜单正常打开 -- 清除按钮 → 输入框被清空 -- Shift+Enter → 触发全文搜索 - ---- - -### Step 5: 迁移 `NoteAutocomplete.tsx` (React/Preact 组件) ✅ 基本完成 -**文件变更:** -- `apps/client/src/widgets/react/NoteAutocomplete.tsx` — 传入容器 `
`,管理 `api` 生命周期 - -**验证方式:** -- 关系属性的目标笔记选择正常工作 -- `noteId` 和 `text` props 的动态更新正确 - -**当前状态:** -- ✅ `NoteAutocomplete.tsx` 已移除残留的旧 `.autocomplete("val")` 调用,改为完全走 `note_autocomplete.ts` 暴露的 helper。 -- ✅ 组件现在会显式管理 headless autocomplete 的初始化/销毁生命周期,并清理 React 侧追加的 DOM / jQuery 监听,避免重复绑定。 -- ✅ `noteId` / `text` prop 同步已切到新状态流,`setNote()` 也会同步内部 query,避免仅改 DOM 值导致的状态漂移。 -- ⚠️ 仍需继续做手动回归验收,重点应覆盖 `Move to`、`Clone to`、`Include note`、`Add link`、bulk actions 等主要 React 消费方。 - ---- - -### Step 6: 迁移"关闭弹窗"逻辑 + `attribute_detail.ts` 引用 ✅ 完成 -**文件变更:** -- `apps/client/src/services/dialog.ts` — 替换 `$(".aa-input").autocomplete("close")` -- `apps/client/src/components/entrypoints.ts` — 替换 `$(".aa-input").autocomplete("close")` -- `apps/client/src/components/tab_manager.ts` — 替换 `$(".aa-input").autocomplete("close")` -- `apps/client/src/widgets/attribute_widgets/attribute_detail.ts` — 更新 `.algolia-autocomplete` 选择器 - -**说明:** -引入了全局的 "关闭所有 headless autocomplete" 机制(通过 `closeAllHeadlessAutocompletes` 方法)。 - -**验证方式:** -- ✅ autocomplete 弹窗打开时切换标签页 → 弹窗自动关闭 -- ✅ autocomplete 弹窗打开时打开对话框 → 弹窗自动关闭 -- ✅ 点击 autocomplete 下拉菜单时属性面板不应关闭 - ---- - -### Step 7: 更新 CSS 样式 ✅ 完成 -**文件变更:** -- `apps/client/src/stylesheets/style.css` — 旧 `.aa-dropdown-menu` 兼容样式 + headless autocomplete 主样式(`.aa-core-*`、命令面板、Jump to Note contained panel) -- `apps/client/src/stylesheets/theme-next/base.css` — `Jump to Note` / 空白页结果列表的主题态样式 -- `apps/client/src/stylesheets/theme-next/pages.css` — 空白页 contained panel 的页面级样式 -- `apps/client/src/widgets/type_widgets/Empty.css` — 空白页 autocomplete 结果容器边框样式 - -**说明:** -当前实现并没有使用 `@algolia/autocomplete-js` 的默认 DOM(`aa-Autocomplete` / `aa-Form` / `aa-Input` / `aa-Panel` 等),而是基于 `@algolia/autocomplete-core` 自行渲染 headless 面板,因此这里应以**实际渲染出来的类名**为准: -- `.aa-core-panel` — headless 下拉面板 -- `.aa-core-panel--contained` — 渲染到外部容器中的 contained 模式面板(如 `Jump to Note`、空白页搜索) -- `.aa-core-list` — 结果列表 -- `.aa-core-item` — 结果项 -- `.aa-core-item--active` — 当前高亮项 -- `.aa-dropdown-menu` / `.aa-suggestions` / `.aa-suggestion` / `.aa-cursor` — 为复用旧主题样式而保留的兼容类名 -- `.algolia-autocomplete-container` — 业务侧传入的结果容器,不是新库自动生成的 wrapper - -需要注意: -- `NoteAutocomplete.tsx` 仍然渲染原生 ``,并没有 `.aa-Input` -- `note_autocomplete.ts` 会给面板附加 `aa-core-panel aa-dropdown-menu`,给结果列表附加 `aa-core-list aa-suggestions`,给结果项附加 `aa-core-item aa-suggestion` -- 因此 Step 7 的重点不是“套用 Algolia 默认主题”,而是维护 Trilium 自己的 headless 渲染样式与兼容类样式 - -**验证方式:** -- 下拉菜单样式正常(亮色/暗色模式) -- 选中项高亮正确 -- `Jump to Note`、空白页搜索等 contained panel 场景样式正常 -- 命令面板(`>`)和普通笔记建议项的布局都正确 - -**当前完成情况:** -- ✅ `Step 7` 已从“套用 `autocomplete-js` 默认类名”修正为维护当前 headless DOM 的真实样式体系。 -- ✅ `style.css` / `theme-next/base.css` / `theme-next/pages.css` / `Empty.css` 的职责范围已在文档中对齐当前实现。 -- ✅ 空白页 (`note-detail-empty`) 的 contained panel 已修正为无边框,不再被旧的 `.aa-dropdown-menu` 规则反向覆盖。 - ---- - -### Step 8: 更新类型声明 ✅ 完成 -**文件变更:** -- `apps/client/src/types.d.ts` — 移除 `AutoCompleteConfig`、`AutoCompleteArg`、jQuery `.autocomplete()` 方法 -- `apps/client/src/widgets/PromotedAttributes.tsx` — 移除最后残留的 `$input.autocomplete(...)` 调用,改为复用 `attribute_autocomplete.ts` 的 headless label value autocomplete -- `apps/client/src/services/autocomplete_core.ts` — 收紧 headless source 默认类型,补齐 internal source 所需默认钩子 -- `apps/client/src/services/note_autocomplete.ts` — 移除对不存在的 `autocomplete.destroy()` 调用,清理类型不兼容点 - -**验证方式:** -- TypeScript 编译无错误 - -**当前完成情况:** -- ✅ `types.d.ts` 中遗留的 `AutoCompleteConfig`、`AutoCompleteArg` 与 jQuery `.autocomplete()` 扩展声明已删除。 -- ✅ `PromotedAttributes.tsx` 不再依赖旧版 `autocomplete.js` 类型或初始化流程,至此 client 代码中已无 `.autocomplete(...)` 调用残留。 -- ✅ 运行 `pnpm exec tsc -p apps/client/tsconfig.app.json --noEmit` 通过。 - ---- - -### Step 9: 移除旧库和 Polyfill ✅ 完成 -**文件变更:** -- `apps/client/package.json` — 移除 `"autocomplete.js": "0.38.1"` -- `apps/client/src/desktop.ts` — 移除 `import "autocomplete.js/index_jquery.js";` -- `apps/client/src/mobile.ts` — 移除 `import "autocomplete.js/index_jquery.js";` -- `apps/client/src/runtime.ts` — 移除 jQuery polyfill -- `apps/client/src/index.ts` — 移除 jQuery polyfill - -**验证方式:** -- 完整回归测试 -- 构建无错误 - -**当前完成情况:** -- ✅ 代码中的 `autocomplete.js` 入口 import 与仅为旧库保留的 jQuery 4 polyfill 已移除。 -- ✅ `apps/client/package.json` 已删除 `autocomplete.js` 依赖声明。 -- ✅ `pnpm install` 已执行完成,lockfile / 安装状态已同步。 -- ✅ `pnpm exec tsc -p apps/client/tsconfig.app.json --noEmit` 已通过。 -- ✅ `pnpm run --filter @triliumnext/client build` 已通过。 - ---- - -### Step 10: 更新 E2E 测试 ✅ 完成 -**文件变更:** -- `apps/server-e2e/src/support/app.ts` -- `apps/server-e2e/src/layout/split_pane.spec.ts` - -**验证方式:** -- E2E 测试全部通过 - -**当前完成情况:** -- ✅ `apps/server-e2e/src/support/app.ts` 已新增基于当前 headless DOM 语义的 note autocomplete suggestion helper,不再依赖旧的“第二项就是目标笔记”顺序假设。 -- ✅ `apps/server-e2e/src/layout/split_pane.spec.ts` 已改为复用该 helper,避免被 `create-note` / `search-notes` 等 action 项扰动。 -- ✅ 定向验证 `pnpm run --filter @triliumnext/server-e2e e2e -- src/layout/split_pane.spec.ts` 已通过(2 passed)。 -- ✅ 全量验证 `pnpm run --filter @triliumnext/server-e2e e2e` 已通过退出码校验。 -- ⚠️ 全量 Playwright 结果为 `41 passed, 9 flaky`;`src/layout/split_pane.spec.ts:40` 在全量并发执行中首跑超时、retry 后通过,另有多条非 autocomplete 相关用例也存在同类 flaky 现象。 - ---- - -## 依赖关系图 - -``` -Step 0 (安装新库) ✅ - ├── Step 1 (属性名称 autocomplete) ← 最简单,优先迁移 - ├── Step 2 (标签值 autocomplete) - ├── Step 3 (笔记搜索 autocomplete 核心) ← 最复杂 - │ ├── Step 4 (辅助函数) - │ └── Step 5 (React 组件) - ├── Step 6 (关闭弹窗 + attribute_detail 引用) - └── Step 7 (CSS 样式) - └── Step 8 (类型声明) - └── Step 9 (移除旧库) ← 最后执行 - └── Step 10 (E2E 测试) -``` - -## 风险点 -1. **消费者代码需要改动**:新库要求传入容器而非 input,消费者需要调整 HTML 模板和值的读写方式。 -2. **自定义事件兼容性**:旧库通过 jQuery 事件与外部交互,新库使用回调,`attribute_detail.ts` 等消费者中的事件监听需要更新。 -3. **IME 输入处理**:新库原生支持 `ignoreCompositionEvents` 选项,但需要验证行为是否与旧的手动处理一致。 -4. **CSS 类名变化**:多处代码通过 `.aa-input`、`.algolia-autocomplete` 定位元素,需要统一更新为新的 `.aa-*` 类名。 -5. **全局关闭机制**:旧代码通过 `$(".aa-input").autocomplete("close")` 关闭所有实例,新库需要手动维护实例注册表。