## 先理解调用与授权是两件事

插件窗口收到的 `api` 来自当前宿主的 `window.electronAPI`。方法存在，只代表可以发起调用；主进程还会核对路径允许范围、当前窗口授权和输入原件是否只读。

路径应来自用户明确选择或当前项目已登记的材料。另一个窗口传来一个字符串，不代表当前窗口已经取得读取或写入许可。

## 接口速查

| 方法 | 成功时应检查的内容 | 常见失败 |
| --- | --- | --- |
| `readFileContent(path, options)` | `success`，再区分 `text` 与 `table/rows` | 路径不允许、文件不存在、格式不支持 |
| `previewFileStamp(path)` | 成功状态与文件 stamp；缺失另行处理 | 文件变化、文件不存在、元数据读取失败 |
| `writeFileContent(path, text, options)` | `success: true`，可能附加其他状态 | 原件只读、路径不足、导出冲突、协作中断 |

最终返回值以目标版本的 `src/ipc/file.js` 和文件服务为准。不要对所有格式都假定 `result.text` 存在。

## 读取小型已知文本

```javascript
async function readKnownText(api, filePath) {
  if (!api || typeof api.readFileContent !== 'function') {
    throw new Error('当前宿主没有提供文本读取能力');
  }
  const result = await api.readFileContent(filePath);
  if (!result || result.success !== true || typeof result.text !== 'string') {
    throw new Error(result && result.error || '文本读取失败');
  }
  return result.text;
}
```

不要拿这个函数处理任意二进制或完整巨型表格。确认格式和用途后，再决定采用文本、预览或后台分析路径。

## 分页读取表格预览

```javascript
async function readFirstTablePage(api, filePath) {
  if (!api || typeof api.readFileContent !== 'function') {
    throw new Error('当前宿主没有提供文件预览能力');
  }
  if (!/\.(csv|tsv|xlsx|xls)$/i.test(filePath)) {
    throw new Error('请选择受支持的表格文件');
  }
  const result = await api.readFileContent(filePath, {
    preview: true,
    rowOffset: 0,
    rowLimit: 60,
    columnOffset: 0,
    columnLimit: 12
  });
  if (!result || result.success !== true || result.table !== true) {
    throw new Error(result && result.error || '表格预览失败');
  }
  return {
    rows: result.rows,
    hasMore: result.hasMore === true,
    nextOffset: result.rowOffset + result.rows.length,
    totalRows: result.totalRows ?? null
  };
}
```

当前表格预览的行数默认 60、上限 120，列数默认 16、上限 40。分页返回可能包括 `paged`、`table`、`rows`、`rowOffset`、`columnOffset`、`hasMore` 等字段。

部分文件尚未扫描到末尾时 `totalRows` 可以未知。依据 `hasMore` 决定是否请求下一页，不把未知总数当成零，不并发请求全部页面来伪装分页。

单元格用文本节点或 `textContent` 渲染，不能将文件原文拼进 `innerHTML`。当前其他格式的文本预览、二进制处理与表格预览并不是同一个契约。

## 保存文本并保留失败状态

```javascript
async function saveKnownText(api, filePath, content) {
  if (!api || typeof api.writeFileContent !== 'function') {
    throw new Error('当前宿主没有提供写入能力');
  }
  if (typeof content !== 'string') throw new Error('内容必须是文本');
  const result = await api.writeFileContent(filePath, content);
  if (!result || result.success !== true) {
    throw new Error(result && result.error || '保存失败，内容仍未保存');
  }
  return { path: filePath, watermarked: result.watermarked === true };
}
```

函数不会创建文件选择器，也不会授予权限。调用者应先取得允许的输出位置；失败时保留编辑内容。不要传 `trusted: true` 尝试取得权限，主进程信任来自当前窗口的真实选择授权。

## 预览刷新与外部变化

保存当前文件的路径和 stamp。收到受支持的变化通知或用户明确刷新时重新核对；不同则提示“文件已更新，刷新查看”。不要高频读取整个大文件进行比较。

如果本地还有未保存编辑，应把外部修改视为需要处理的冲突，不能自动覆盖编辑区。

## 文件能力验收

测试合法小文件、丢失文件、未授权路径、只读原件、未知总行数、大表分页、保存失败、文件外部变化和协作连接中断。成功与失败的提示都应能对应实际文件状态。
