用 Codex 开发 MarginNote 4 插件:从能力探测到可用于现场工作的工程量工具

本文不是“让 AI 猜一个插件然后直接写代码”的教程,而是一套先验证 MarginNote 4 能力边界、再用 Codex 完成真实插件的开发方法。

实测基准:MarginNote 4.4.5(Build 2)、iPadOS、2026 年 8 月。MarginNote 更新后,涉及宿主界面层级的结论必须重新测试。

这篇文章适合谁

  • 没有完整插件开发经验,但想用 Codex 把自己的 MarginNote 工作流做成插件的用户。
  • 已经会 JavaScript,希望快速理解 MarginNote 插件运行时、工程结构和能力边界的开发者。
  • 希望了解真实用户还需要哪些接口、MarginNote 还能进入哪些专业工作场景的官方团队。

读完后,你应该能做到四件事:

  1. 从零建立一个 MarginNote 4 插件工程并打包为 .mnaddon
  2. 让 Codex 先做 API 审计和安全探针,而不是直接根据需求幻想实现。
  3. 判断一个功能属于“公开 API 可稳定实现”“真机可实现但依赖内部 UI”“尚无接口”中的哪一类。
  4. 用可验证、可回滚、可持续迭代的方式,把学习软件扩展成真正的工作工具。

一、为什么不能一上来就把需求交给 AI 写代码

MarginNote 插件开发最大的风险通常不是“代码写不出来”,而是:

  • 文档里看见了一个类,但它没有真正注册到当前 JavaScriptCore 环境。
  • 方法在头文件里存在,但目标 MarginNote 版本中的 selector 名称不同。
  • MarginNote 产品本身能做某件事,不代表插件 API 也能做。
  • AI 根据 iOS、浏览器或 Node.js 的常识,写出了宿主里根本不存在的接口。
  • 屏幕浮层看起来成功,但它没有进入 PDF 文档坐标,不能缩放、重开或导出。

我们最初的真实需求很简单:

在施工现场用 iPad、PDF 和 Apple Pencil 测量隔墙。用户在图纸上画线和箭头,在箭头末端记录墙体类型、唯一编号、长度和高度;最终批量导出到 Excel。

原来的人工流程有三个痛点:

  • 手写编号会重复或缺号。
  • 快速手写的数字容易误读。
  • 所有数据还要重新录入 Excel。

如果直接按照需求写插件,很容易假设“MarginNote 一定有创建 PDF 文本框、读取 Pencil 笔迹坐标、写入手写层”的公开 API。实际探测结果是:这些能力并不处在同一个开放层级。

因此,正确顺序应当是:

场景痛点
  → 资料审计
  → 只读能力探针
  → 受控写入探针
  → 针对关键链路的真机实验
  → 最小可用插件
  → 现场节奏优化

这套顺序看似比“直接生成代码”慢,实际上能避免在不存在的 API 上浪费整个项目。


二、先认识 MarginNote 插件的真实结构

2.1 插件主体运行在 JavaScriptCore,不是浏览器,也不是 Node.js

MarginNote 插件主体是 JavaScript,但运行环境是 JavaScriptCore。

在我们测试的 iPad 版本中:

  • PromiseglobalThisWebAssembly 可用。
  • 插件主运行时没有 fetch
  • 插件主运行时没有 setTimeoutsetInterval
  • 没有 DOM、windowdocumentlocalStorage 和 Node.js 文件系统模块。
  • 延时和轮询使用 NSTimer
  • 网络请求使用 NSURLConnectionNSMutableURLRequest 等桥接对象。

官方文档入口:

下面这种浏览器写法在插件主运行时不可依赖:

setTimeout(function () {}, 500);
fetch("https://example.com/api");
document.querySelector("#app");

应改成宿主已导出的能力:

NSTimer.scheduledTimerWithTimeInterval(0.5, false, function () {
  // 延时任务
});

如果使用 Web 模板,WebView 内部的网页环境可以有 DOM、fetch 和前端框架;但 WebView 不能直接操作 MarginNote 数据,必须通过桥接把请求交给插件侧执行。

2.2 .mnaddon 是一个归档包

最小插件至少包含:

my-addon/
├── main.js
├── mnaddon.json
└── icon.png        # 可选

mnaddon.json 示例:

{
  "addonid": "com.example.myaddon",
  "author": "Your Name",
  "title": "My Addon",
  "version": "0.1.0",
  "marginnote_version_min": "4.4.5",
  "cert_key": ""
}

入口脚本必须实现 JSB.newAddon(mainPath),并返回一个继承 JSExtension 的类,而不是实例:

JSB.newAddon = function (mainPath) {
  return JSB.defineClass(
    "MyAddon : JSExtension",
    {
      sceneWillConnect: function () {
        Application.sharedInstance().showHUD("插件已加载", self.window, 1);
      },

      queryAddonCommandStatus: function () {
        return {
          image: "icon.png",
          object: self,
          selector: "runAddon:",
          checked: false
        };
      },

      runAddon: function () {
        Application.sharedInstance().alert("Hello MarginNote");
      }
    },
    {
      addonDidConnect: function () {},
      addonWillDisconnect: function () {}
    }
  );
};

官方入门文档:快速开始


三、资料从哪里找:给用户和 Codex 的参考文献地图

建议同时保留下面四类资料,不要只看一个仓库。

资料 作用 使用原则
MarginNote 官方 Addon 仓库 Objective-C/JSB 头文件、注册实现、示例 判断一个类或方法是否真正导出时,优先级最高
MarginNote 插件文档站 入门、指南、API 参考、Cookbook 适合按任务学习,但关键 selector 仍应和头文件、真机交叉验证
marginnote-addon-docs 仓库 文档源码和本地 MCP 检索 推荐接入 Codex,让 AI 先检索再读完整文档
mn-rails 创建工程、开发脚本、打包、Web 模板 它是脚手架,不是完整 SDK
MN Rails 官方论坛教程 社区推荐的工程化工作流 用于快速进入开发和发布流程

3.1 mn-rails 里的 base/standard 是完整 SDK 吗?

不是。

它主要提供:

  • 标准插件目录结构。
  • main.js 和最小 JSExtension 示例。
  • mnaddon.json 模板。
  • 本地部署、监听、版本号和打包脚本。
  • 面向 AI 开发的 AGENTS.md

它没有提供:

  • 完整的 MarginNote 业务对象封装。
  • 所有 API 的 TypeScript SDK。
  • MarginNote 宿主模拟器。
  • 私有接口兼容层。
  • 自动化真机测试框架。
  • 后台同步、权限或安全凭据系统。

所以更准确的说法是:

mn-rails 负责把项目“搭起来、跑起来、打包出来”;官方 Addon 头文件和文档负责告诉你宿主可能暴露什么;真机探针负责确认当前版本到底能不能用。

3.2 给 Codex 配置本地文档检索

marginnote-addon-docs 内置 MCP Server,可以让 Codex 先发现相关文档,再读取全文。

配置示例:

{
  "mcpServers": {
    "mn-docs": {
      "command": "npx",
      "args": ["mn-docs-mcp"]
    }
  }
}

如果已克隆仓库,也可以使用本地路径:

{
  "mcpServers": {
    "mn-docs-local": {
      "command": "node",
      "args": ["mcp/cli.mjs"],
      "cwd": "/path/to/marginnote-addon-docs"
    }
  }
}

推荐 AI 检索顺序:

  1. 先搜索相关文档。
  2. 再读取目标文档全文。
  3. 查对应 Objective-C/JSB 头文件。
  4. .m 注册代码,确认类是否真正注入 JSContext。
  5. 最后在目标设备上做最小探针。

四、截至本次真机测试,已经证实的能力

下面严格区分证据等级。

标记 含义
公开 官方文档或头文件明确提供,适合作为长期方案基础
真机 已在指定 iPad/MN4 版本中用探针或正式插件验证
内部 UI 真机可用,但依赖 MarginNote 内部视图、类名或按钮位置,升级后可能失效
待复核 有人工结果或间接证据,但还没有形成同一条可审计链路

4.1 运行时与基础设施

能力 结论 证据
插件生命周期、工具栏命令 可用 公开 + 真机
JSB.defineClassJSB.require 可用 公开 + 真机
原生 UIViewUILabelUIButton、手势 可用 公开 + 真机
WebView 页面与 evaluateJavaScript 可用 真机对象为 MNUWebView;页面内有 Promise 和 fetch
NSUserDefaults 保存少量设置 可用 公开 + 真机
Application.documentPath/cachePath/tempPath 存在且可写 真机
JSON、文本、二进制文件读写 可用 公开 + 真机
系统文件保存选择器 可用 公开 + 真机
主运行时 fetch/setTimeout/setInterval 不可用 公开 + 真机
NSURLSession 当前目标版本全局对象不存在 真机;不要因为头文件存在就直接使用

存储文档:存储与文件

4.2 笔记、数据库、脑图和搜索

已证实可以:

  • 获取当前学习集、当前文档、当前页等上下文。
  • 列举文档和笔记本。
  • 通过 getNoteById 获取笔记。
  • 创建测试笔记、保存数据库并回读。
  • 使用 UndoManager 撤销受控写入。
  • 删除笔记或笔记树。
  • 克隆笔记、克隆为复习卡片。
  • 读取页级 sketch note 的数量、笔画数和绘图字节大小。
  • 使用文本搜索,以及目标版本中存在的部分索引和相似笔记能力。

数据库写入应遵循:

取得目标对象
  → UndoManager.undoGrouping
  → 修改或创建
  → 保存数据库
  → refreshAfterDBChanged
  → 真机回读验证

参考:笔记与数据库

4.3 PDF 页面上的原生文本标签

这是本项目最重要、也最需要说明证据边界的能力。

我们已经在正式插件链路中验证:

  1. 用户校准一次 MarginNote 顶部文本框按钮。
  2. 插件通过页面根视图 hitTest 重新取得真实 UIButton
  3. 插件发送 TouchUpInside,文本工具从未选中切换到选中。
  4. 用户点击一次 PDF 目标位置。
  5. PDFPageView 子树出现新的 ResizeTextBoxViewRichTextEditor
  6. 插件调用原生 paste,标签逐字回读一致。
  7. 插件设置文字颜色、字体和文本框 frame。
  8. resignFirstResponder 成功,编辑器失去焦点后仍显示。
  9. 文本框位于 PDF 页面坐标中,放大缩小时随 PDF 同比例移动和缩放。

正式真机日志中,三条连续标签均完成:

  • 新编辑器初始文字长度为 0。
  • 粘贴后长度与目标字符串完全一致。
  • 提交后 editable=falseselectable=falsefirstResponder=false
  • ResizeTextBoxView 仍可见,并挂在 PDFPageView
  • 用户目视确认失焦显示和缩放跟随通过。

这使下面的现场工作流成为可能:

选择墙体类型
  → 输入长×高
  → 原子分配唯一编号
  → 用户点 PDF 箭头末端
  → 自动写入彩色机器文字
  → 保存结构化台账
  → 导出 CSV/Excel

但是要特别强调:

ResizeTextBoxViewRichTextEditor、工具栏按钮校准和视图层级扫描,属于“真机已证实的内部 UI 路线”,不是官方承诺稳定的文档层 API。

官方公开了通用 UIControlUITextView 能力,但没有公开“在指定 PDF 页和指定矩形创建文本批注并返回 annotationId”的正式接口。

因此这条路线可以做真实插件,却必须:

  • 固定目标 MarginNote 版本测试。
  • 把工具按钮位置保存为归一化坐标,并允许重新校准。
  • 每次放置重新 hitTest,不能长期持有旧按钮引用。
  • 记录编辑器是否出现、文字是否一致、是否提交。
  • MarginNote 更新后重新跑回归测试。

4.4 结构化台账与导出

现场工程量插件已经实现并验证的业务能力包括:

  • Q1~Q5 多种墙体类型独立编号。
  • 每种类型从 001 开始递增。
  • 点击放置前先原子占用编号。
  • 取消记为 void,失败记为 pending,编号不回收。
  • 标签使用机器文字,避免手写误读。
  • 同时保存长度、高度、面积、图纸、页码、状态和时间。
  • 使用 JSON 持久化结构化数据。
  • 导出带 UTF-8 BOM 的 CSV,可直接由 Excel 打开。

这说明插件不必把所有数据都“塞进 MarginNote 笔记字段”。更稳健的模式是:

PDF 上的可视标签 = 给人看
插件自己的结构化台账 = 给程序、Excel 和校验逻辑使用
唯一编号 = 两者之间的稳定主键

当前原生文本框没有取得公开的 noteId/annotationId,所以编号加 docMd5 + page + normalizedFrame + label 是现实的关联方式,但不等同于官方稳定标识。


五、哪些路线失败了,以及为什么失败

失败结果同样是开发文档的一部分。

5.1 直接调用 EditTextBox command

在目标 PDF 上,EditTextBoxEditPaste 多轮查询均返回禁用。移除插件面板、切换输入状态、变更 window 参数后仍然无效。

结论:

  • 不能仅因为内置命令表中有 EditTextBox,就把它当成当前上下文可调用 API。
  • 最终成功路线是命中真实工具栏 UIControl,不是 command。
  • 官方若开放正式的文档批注创建 API,插件将不再需要模拟用户点击工具栏。

5.2 在屏幕坐标上添加普通浮层

普通 UILabel 或自定义面板可以快速显示文字,但如果挂载在学习界面根视图:

  • PDF 放大缩小时位置不跟随。
  • 关闭文档后不会持久化。
  • 导出 PDF 时不会出现。

它适合做工具面板、HUD、临时提示,不适合做最终文档批注。

5.3 假设文本框等于普通笔记

原生文本框出现时,没有新的 focusNote,也没有取得 noteId。因此不能假设它是 MbBookNote,再去写 noteTitle 或评论字段。

产品里看起来相似的对象,数据模型可能完全不同。

5.4 从手写层读取或写入完整笔画几何

目标版本可以读取 sketch note 的汇总信息,例如笔画数量和绘图数据大小;但没有发现公开 API 提供:

  • 每一笔的坐标序列。
  • Apple Pencil 压力、倾角和时间戳。
  • 创建 stroke 并写回手写层。
  • 创建直线、箭头、矩形等矢量形状。
  • 把机器生成文字或图形合并到手写层。

因此当前插件仍让用户使用 MarginNote 原生画笔绘制线和箭头,插件只负责结构化数据和原生文本标签。

5.5 手动插入 PDF 留白

施工图四周可能都有有效内容,统一插入横向留白会切断图纸或影响阅读,不适合现场 CAD/PDF 场景。

一个功能“技术上能调用”,不代表它符合真实工作流程。


六、MarginNote 4 插件的能力边界

6.1 可以作为稳定基础的能力

  • 插件生命周期和工具栏入口。
  • 笔记、笔记本、文档的公开数据操作。
  • Undo、数据库刷新和批量处理。
  • 原生 UIKit 小面板。
  • WebView 复杂界面及桥接。
  • 本地配置、JSON/文本/二进制文件。
  • 系统文件导入导出。
  • 前台网络请求。
  • 用户主动触发的自动化工作流。

6.2 能做,但需要版本探针和降级方案

  • 扫描 MarginNote 内部视图层级。
  • 通过真实工具栏按钮切换宿主工具。
  • 捕获内部文本编辑器并修改内容。
  • 使用未形成正式文档契约的搜索或索引方法。
  • 依赖具体 controller 结构、类名或 selector 的功能。

这类功能必须配备:

  • 版本检测。
  • 方法存在性检查。
  • 一键重新校准。
  • 失败后保留数据、不重复编号。
  • 诊断日志。
  • 可关闭的实验开关。

6.3 当前没有可靠公开证据支持的能力

  • 插件退出或 MarginNote 关闭后的无限后台运行。
  • 任意访问 iPad 或其他 App 的文件系统。
  • 在插件中加载 Swift/Objective-C 原生动态库。
  • 执行系统 shell 命令。
  • 稳定读写完整 Pencil 笔画几何。
  • 通过正式 API 在 PDF 任意坐标创建和管理文本/图形批注。
  • 任意控制脑图节点的最终布局坐标和渲染器。
  • base 模板当作完整 SDK 或宿主模拟器。

七、从零开始:用 mn-rails 创建第一个工程

7.1 准备环境

建议准备:

  • 一台安装 MarginNote 4 的 Mac 或 iPad。
  • Node.js 和 npm/pnpm。
  • Git。
  • Codex。
  • 一个备份后的测试学习集,前期不要使用重要数据。

在空目录中运行:

npx mn-rails

也可以指定模板:

npx mn-rails --template standard
npx mn-rails --template web

模板选择建议:

模板 适用场景
standard(仓库内部目录常称 base 小型工具、原生按钮、浮动面板、数据处理、探针插件
web 设置中心、统计报表、复杂表格、React 界面、远程服务控制台

进入项目后:

npm install
npm run dev

构建发布包:

npm run build

Mac 上可以使用脚手架的本地热部署;iPad 测试通常是构建 .mnaddon,再通过 iCloud Drive、AirDrop 或系统“文件”导入 MarginNote。

7.2 推荐的源码分层

不要把所有代码塞进 main.js

src/
├── main.js               # 只负责 JSB.require 和 JSB.newAddon
├── Addon.js              # 生命周期与 selector
├── Context.js            # 当前学习集/文档/页面解析
├── Store.js              # JSON 数据和迁移
├── NativeUI.js           # 小型原生 UI
├── Feature.js            # 业务逻辑
├── Diagnostics.js        # 能力与错误日志
├── icon.png
└── mnaddon.json

main.js 保持简单:

JSB.require("Context");
JSB.require("Store");
JSB.require("NativeUI");
JSB.require("Feature");
JSB.require("Addon");

JSB.newAddon = function (mainPath) {
  return createMyAddon(mainPath);
};

7.3 给 Codex 写一份项目规则

在项目根目录放置 AGENTS.md,至少写清楚:

# MarginNote 插件开发规则

- 运行环境是 JavaScriptCore,不使用 DOM、fetch、setTimeout 或 setInterval。
- 延时与轮询使用 NSTimer。
- 只在 src/main.js 调用 JSB.require。
- 结构化数据写入 Application.documentPath 下的插件专用目录。
- 数据库写入使用 UndoManager,并在写后刷新和回读。
- 不调用未验证的 selector;先检查方法是否存在。
- 修改后运行语法测试、业务测试和构建。
- 真机未通过的能力不得写成“已实现”。

这能显著减少 AI 把浏览器、Node.js 或普通 iOS 开发经验错误套进 MarginNote。


八、Codex 应该怎样参与开发

8.1 第一条提示词不要先写需求实现

可以把下面这段直接交给 Codex:

我要开发一个 MarginNote 4 插件,主要在 iPad 使用。

请先不要根据最终需求直接写插件。第一阶段请完成:

1. 阅读 marginnoteapp/Addon、mn-docs.museday.top、
   Temsys-Shen/marginnote-addon-docs 和 Temsys-Shen/mn-rails。
2. 区分脚手架、官方头文件、文档、公开 API 和私有/内部 UI。
3. 输出能力矩阵:已公开、需真机验证、明确缺失、替代方案。
4. 为目标 MarginNote 版本制作只读探针,不读取笔记内容和敏感标识。
5. 探针通过后,再进入可撤销的受控写入测试。
6. 每条结论必须绑定日志或真机证据;不允许因为产品界面存在某功能,
   就推断插件一定有对应 API。

然后再补充你的场景痛点、设备、MarginNote 版本和可接受的测试权限。

8.2 让 Codex 先建立能力矩阵

建议矩阵至少包含:

能力 文档证据 当前对象是否存在 方法是否存在 是否真机执行 风险
当前文档和页码 官方文档
创建普通笔记 官方文档 受控写入通过
创建 PDF 文本批注 无正式接口 内部编辑器存在 内部路线 真机通过
写入手写 stroke 未发现 未执行 不可作为方案

8.3 分阶段授权探针

推荐安全等级:

阶段 A:只读环境探针

  • 检查全局对象和方法是否存在。
  • 检查生命周期、路径和 WebView。
  • 不读取笔记正文、标识符和原始路径。
  • 不写数据库。

阶段 B:受控写入

  • 仅在备份后的测试学习集中进行。
  • 创建带显眼前缀的测试笔记。
  • 立即回读。
  • 使用 Undo 或精确删除清理。
  • 报告是否仍残留。

阶段 C:目标链路探针

  • 一次只验证一个关键假设。
  • 记录调用前后对象、文本、状态和时间。
  • 避免同时执行两个兜底路径,否则无法判断是谁成功。
  • 需要人工确认的项目与自动检测项目分开记录。

阶段 D:正式 MVP

  • 探针和正式插件使用不同 addonid。
  • 正式数据模型先于 UI 动画。
  • 失败状态不能伪装成成功。
  • 编号、账目等关键数据在 UI 操作前先落盘。

8.4 让诊断日志成为产品的一部分

推荐日志结构:

{
  "schemaVersion": 1,
  "pluginVersion": "0.1.0",
  "host": {
    "appVersion": "4.4.5",
    "osName": "iPadOS"
  },
  "events": [
    {
      "at": "2026-08-02T00:00:00.000Z",
      "event": "feature-started",
      "details": {}
    }
  ],
  "errors": []
}

默认日志不要包含:

  • 笔记正文。
  • 用户身份。
  • 原始沙盒路径。
  • 不必要的 notebookId、noteId。
  • 网络密钥。

需要排查时,再由用户明确授权扩展范围。


九、一个可复用的能力探针写法

先检查存在性,不要直接调用:

function hasMethod(object, name) {
  if (!object) return false;
  try {
    return typeof object[name] === "function";
  } catch (error) {
    return false;
  }
}

var app = Application.sharedInstance();
var db = Database.sharedInstance();

var report = {
  application: {
    studyController: hasMethod(app, "studyController"),
    openURL: hasMethod(app, "openURL"),
    saveFileWithUti: hasMethod(app, "saveFileWithUti")
  },
  database: {
    getNoteById: hasMethod(db, "getNoteById"),
    savedb: hasMethod(db, "savedb"),
    allDocuments: hasMethod(db, "allDocuments")
  },
  runtime: {
    Promise: typeof Promise,
    fetch: typeof fetch,
    setTimeout: typeof setTimeout
  }
};

方法存在只代表“可以继续测试”,不等于业务结果已经通过。

完整验收应分成:

对象存在
  → selector 存在
  → 调用没有抛错
  → 返回值正确
  → 宿主界面发生目标变化
  → 数据可回读
  → 重开仍存在
  → 导出结果正确

十、如何设计一个不会重号、不会漏数据的插件

工程量插件里最重要的不是文本框动画,而是数据规则。

10.1 先占号,再操作 UI

用户点击“开始放置”
  → 写入 reserved 记录
  → nextSequence + 1
  → 原子保存 JSON
  → 激活文本工具
  → 用户点 PDF
  → 粘贴成功后改为 placed

如果用户取消:

reserved → void

如果文本框超时或粘贴失败:

reserved → pending

voidpending 都不回收编号。这样可能出现“作废号”,但不会出现两个实体共用同一编号。

10.2 配置和业务数据分开存

  • NSUserDefaults:面板位置、选中墙型、字号等少量设置。
  • JSON 文件:项目、墙型、计数器、工程量记录、状态。
  • 临时目录:待导出的 CSV、诊断快照。

关键 JSON 使用原子写入并回读:

var data = NSJSONSerialization.dataWithJSONObjectOptions(store, 1);
var ok = data.writeToFileAtomically(filePath, true);
if (!ok) throw new Error("数据写入失败");

var readBack = NSData.dataWithContentsOfFile(filePath);
if (!readBack || readBack.length() === 0) {
  throw new Error("写入后回读失败");
}

10.3 现场 UI 要为 PDF 让路

真正录入时,插件不应长期占用大块 PDF:

  • 常驻态只显示一个小胶囊:当前类型和下一个编号。
  • 输入时短暂展开数字卡片。
  • 等待 PDF 点击时只保留细条状态提示。
  • 写入完成立即收起。
  • 复杂设置和台账放在单独页面。

“设置页面可以大,现场录入必须小”是专业工作插件的重要设计原则。


十一、给 MarginNote 官方的 API 建议

现在的接口已经足以证明专业工作场景的价值,但大量开发精力仍花在“寻找内部按钮和视图”上。以下接口会直接改变插件生态的上限。

P0:正式的 PDF 批注 API

建议提供:

var annotation = DocumentAnnotation.createText({
  documentId: "...",
  pageIndex: 0,
  rect: { x: 0.4, y: 0.3, width: 0.2, height: 0.04 },
  coordinateSpace: "normalized-page",
  text: "Q2-017|3.60×2.80",
  color: "#D6191E",
  fontSize: 7
});

同时需要:

  • 返回稳定的 annotationId
  • 查询、更新、删除、移动和调整大小。
  • 明确页面坐标和屏幕坐标转换。
  • 重开、同步和 PDF 导出的行为契约。
  • Undo/Redo 事务。
  • 给批注附加插件自己的结构化 metadata。

有了这组 API,当前依赖 UIButton → RichTextEditor → ResizeTextBoxView 的内部 UI 路线可以全部删除。

P0:手写与矢量形状 API

建议开放:

  • 读取每一笔的点、压力、倾角、时间。
  • 创建和更新 stroke。
  • 直线、箭头、矩形、圆、多段线等形状。
  • 图层枚举、显隐、锁定和排序。
  • Pencil 事件订阅。
  • 插件生成图形进入原生同步和 PDF 导出。

这会让测量、批改、工程标注、医疗影像、设计审阅等场景真正形成闭环。

P0:工具状态与工具切换 API

建议提供:

reader.currentTool();
reader.activateTool("pen", { presetId: "red-2px" });
reader.activateTool("text");

目前插件只能通过校准并点击内部工具栏按钮切换工具,工具栏一旦改版就需要重新适配。

P1:文档事件和稳定定位

建议增加:

  • 当前可见页变化事件。
  • PDF zoom、pan、rotation 事件。
  • 页面视口与页坐标转换。
  • 批注创建、修改、删除事件。
  • 稳定的文档、页面、批注关联 ID。

P1:专业数据与导出扩展点

建议增加:

  • PDF 导出前后 hook。
  • 批注 flatten 策略。
  • 插件自定义数据随学习集备份/同步。
  • 标准 CSV/XLSX 导出帮助器。
  • 可控的文件夹授权和安全书签。

P1:安全与长期任务

建议明确:

  • 插件权限声明。
  • 安全凭据存储。
  • 前台长任务、取消和进度 API。
  • 有限后台同步的规则。
  • 插件数据容量、迁移和卸载策略。

十二、MN4 从学习阶段走向工作阶段的可能性

MarginNote 的核心优势不是单一的 PDF 阅读,也不是单一的脑图,而是:

原始文档
  + 手写/批注
  + 结构化笔记
  + 脑图关系
  + 搜索与复习
  + 插件自动化

当这些能力进入专业工作,可能形成下面的产品方向。

工程与施工

  • 现场工程量测量。
  • 图纸问题编号和整改闭环。
  • 材料、房间、设备分类统计。
  • 图纸批注与 Excel/BIM/CAD 数据联动。

建筑、设计和制造

  • 方案审阅和设计变更记录。
  • 图纸版本差异对照。
  • 零件、节点和问题卡片自动生成。
  • 评审意见按专业和责任人汇总。

法律与合规

  • 合同条款编号和风险分类。
  • 证据页码与案件时间线关联。
  • 审查意见批量导出。
  • 文档批注与结构化案件数据同步。

医疗、科研和质量管理

  • 医学图像或报告的结构化标注。
  • 文献证据卡片与研究问题关联。
  • 巡检、缺陷、实验结果的编号和统计。
  • 标准操作程序与现场记录闭环。

教育之外的知识工作

  • 咨询项目资料库。
  • 产品需求评审。
  • 会议材料到行动项。
  • 采购比价、投标文件、尽调和审计。

学习场景通常关注“理解和记忆”;工作场景还要求:

  • 唯一编号。
  • 状态流转。
  • 数据校验。
  • 批量导出。
  • 可追溯。
  • 多文档关联。
  • 可复核和可交付。

插件正是连接这两类场景的桥梁。


十三、发布前检查清单

资料与能力

  • 每个关键 API 都有官方文档、头文件或真机证据。
  • 没有把“宿主能做”写成“插件能做”。
  • 内部 UI 路线已显眼标注升级风险。
  • MarginNote 版本和设备已记录。

数据安全

  • 先在备份测试集验证。
  • 数据库写入有 Undo 或精确清理方案。
  • 关键数据先落盘,再执行 UI 动作。
  • 失败、取消和超时不会复用编号。
  • 日志默认不包含正文、身份和密钥。

交付质量

  • 运行 JavaScript 语法检查。
  • 纯业务逻辑有宿主外单元测试。
  • 真机测试失焦、缩放、翻页、重开和导出。
  • 工具栏布局变化后重新校准。
  • .mnaddon 根目录结构正确。
  • 版本号、README 和构建产物一致。

论坛发帖建议

  • 明确测试版本,避免把经验写成永久契约。
  • 同时写成功路线和失败路线。
  • 提供最小复现、日志字段和验收标准。
  • 对官方提出具体 API 形态,而不只是“请开放更多接口”。

十四、最后的经验

用 Codex 开发 MarginNote 插件,最重要的不是提示词有多长,而是证据链是否完整。

最有效的合作方式是:

用户提供真实痛点和现场约束
Codex 负责资料审计、探针、实现和日志分析
MarginNote 真机负责证明宿主行为
项目文档负责保存结论和失败历史

不要让 AI 先承诺“什么都能实现”;也不要因为公开 API 不完整,就过早认定所有路线都走不通。

这次工程量插件经历了多轮失败:command 禁用、浮层不随页、文本框归因错误、提交证据不完整。最终通过真实 UIControl、原生编辑器、PDF 页面坐标和结构化台账,完成了可用链路。

这正是 Codex 最适合发挥价值的地方:它不只是生成代码,还可以持续阅读文档、构造安全实验、分析日志、修正结论,并把一个模糊痛点逐步变成可验证的工具。

而对 MarginNote 来说,插件生态的下一步也不只是“更多学习插件”。如果正式开放 PDF 批注、手写几何、工具状态、坐标转换和专业数据接口,MN4 完全有潜力从个人学习系统继续扩展为工程、法律、医疗、科研、设计和现场管理的平台。


参考资料

1 Like

其实我是不是能直接把这个帖子发给agent,让他自己调教自己:nerd_face: