Skip to content

提案:新增 projects scope,让子插件把交付物挂到项目上 #2

Description

@SongshGeo

来自 PaperOut To-Authorslongform-paperbell,本仓库子插件)。完整提案写在我们仓库里:docs/PROPOSAL_PROJECTS_SCOPE.md,这个 issue 是它的摘要 + 我们最需要答复的几个点。

刚刚适配完 0.4.7(MIGRATION-0.4.7.md,我们那边已发 2.4.0-beta.6),确认这个 scope 到 0.4.7 仍未实现。我们的类型已经 vendored 并标注为提案,每次调用都同时检查 capabilities.includes("projects")typeof client.requestProjects === "function",拿不到就回退成纯文本输入框——所以这个提案被改、被拆、被拒都不会弄坏任何东西,不用为了兼容我们而将就。

背景

PaperOut 把论文写进 50 - Outputs,但笔记里没有任何字段说明它属于哪个项目,Project Manager 因此认不出它是项目交付物。现在「新建 PaperBell 论文项目…」弹窗会问用户属于哪个项目,并写进每个 draft 的 frontmatter。这个下拉框里的内容从哪来,就是本提案要解决的。

1. 最想要答复的:frontmatter 约定(不依赖你们写任何代码)

我们目前在每个 draft 索引笔记的顶层写:

project: ColMemo

纯字符串的项目缩写,沿用 PAPERBELL_SUITE 里已经写下的约定。如果 Project Manager 实际查询的是别的形状,请在这个版本铺开到用户之前告诉我们。

主要的替代方案是 wikilink(project: "[[40 - Projects/ColMemo]]"),能拿到原生反链、且项目笔记重命名后不会断——客观上更好的性质。改我们的写入端只要一行;迁移用户磁盘上已经存在的笔记不是。所以这件事拖得越久,改错的代价越大。

一个不必中心化决策的办法:在每个 project 上返回一个 frontmatterValue 字段,我们原样写入。这样互操作格式的权威就留在 Project Manager 那边,双方以后都不用再重新约定一次。

2. 一篇论文最多四个笔记,请按 longform.title 去重

一个 PaperBell 论文项目可以包含正文、补充材料、回复信、投稿信。每个都是独立笔记、独立 frontmatter,且都带同一个 project:——这是我们有意为之:补充材料和回复信确实属于该项目的产出。

对你们的影响是:数笔记会把一篇论文数成最多四篇。两个现成的去重键:

  • longform.title——同一篇论文的所有 draft 完全一致;
  • 论文文件夹 metadata.json 里的 _longform.acronym——这篇论文自己的缩写。

注意 _longform.acronym(如 SLM,论文代号,用于 PDF 文件名)和 project(如 ColMemo,项目代号)是两个不同的东西。我们在 UI 里把它们明确区分开,建议你们也是。

3. scope 契约本身

export const PPB_PROJECTS_CHANGED_EVENT = "paperbell:projects-changed";

export interface PPBProject {
  id: string;          // 稳定 id,重命名 / 移动后不变
  name: string;        // 展示名
  acronym?: string;    // 写入 `project:` 的值;同 vault 内须唯一
  notePath?: string;   // 项目笔记路径(可选)
  status?: "active" | "planned" | "paused" | "done" | "archived";
  folder?: string;     // 项目根文件夹(可选)
  concepts?: string[]; // 关联的 featured concepts(可选)
}

export interface PPBProjectsQuery {
  status?: NonNullable<PPBProject["status"]>[];  // 缺省 ["active", "planned"]
  query?: string;
}

export interface PPBProjectsResult {
  ok: boolean;
  projects: PPBProject[];
  error?: string;
}

// 挂在 PPBClient 上:
requestProjects?(params?: PPBProjectsQuery): Promise<PPBProjectsResult | null>;
onProjectsChange?(cb: () => void): () => void;

三个关于数据的请求:

  1. id 要在重命名和移动后保持不变。 vault 路径不满足这一点。我们展示 name、写入 acronymid 留给将来的反向上报。如果你们唯一稳定的句柄就是路径,直说,我们就不在 id 上建任何东西。
  2. acronym 在同一个 vault 内必须唯一。 两个项目共用一个缩写,就会写出同样的 project: 值,交付物归属会静默出错。如果保证不了唯一性,就把 acronym 从契约里去掉,改给我们 §1 说的 frontmatterValue——每个项目一个权威字符串。
  3. 希望这个 scope 的同意成本尽量低。 这里没有任何敏感数据:项目名和缩写本来就摆在用户自己的 vault 里。作为一个需要同意的 scope,用户第一次建论文时会吃一个权限弹框——纯摩擦,没有安全收益。要么标成低摩擦,要么并进现有的 config scope。

同意门槛带来的一个具体后果

我们在新建论文弹窗打开时调 requestProjects(),而契约没有给我们取消一个在途请求的办法。如果用户在你们的权限弹框还开着的时候关掉了弹窗,那个弹框会活得比弹窗久——它孤零零地停在那里,问的是一个已经不在屏幕上的字段。两个出路,任选其一:

  • 一个免同意的探测(比如 hasProjects(): boolean,或干脆在 getPluginInfo() 里报个数量),我们只在确实有东西可展示时才触发真正的弹框;或
  • requestProjects 加一个 AbortSignal 参数,让关闭中的弹窗能撤回这次询问。

把 scope 做成低摩擦(上一条)同样能化解它——那样就没有弹框可孤立了。

4. paperbell:projects-changed

语义对齐现有的 paperbell:config-changed。没有它我们就每次开弹窗重拉一次;有了它可以缓存并按需刷新。优先级低于 §3——功能没有它也能用。

5. 请让 capabilities 诚实

加这套 API 时,请同时 bump PaperBellPluginInfo.version 并且"projects" 加进 capabilities

我们的探测刻意忽略版本号字符串,只看两件事:

capabilities.includes("projects") && typeof client.requestProjects === "function"

所以宣称了却没实现是唯一会波及用户的失败模式——我们不会抛异常,但下拉框会静默停留在文本框状态。这个改动是向后兼容的,schemaVersion 不 bump 也行;如果你们 bump 了,我们会重新 vendoring 并对齐 PPB_SCHEMA_VERSION

6. 反向通道(下一轮,不阻塞这个)

契约目前是单向的:子插件消费,没有发布路径。PPBRequestSource 上没有任何字段能让子插件暴露自己的 API,所以今天 Project Manager 想问我们「这个项目有哪些交付物、各自进展如何」,除了直接去伸手拿 app.plugins.plugins["longform-paperbell"].api 之外没有别的办法——而 0.4.7 收紧的正是这类写法,方向上我们完全赞成。

两种封口方式,我们都能接受,请挑一个

  • 注册表:让 registerPPBplugin 接受一个 api 字段,宿主按请求把某个兄弟插件的 API 交给另一个子插件。通用,一次解决所有配对。
  • 总线:给宿主一个 emit(event, payload),让我们在脚手架建好、编译完成时广播 paperout:deliverable-changed。更简单、推送式、不用设计查询面。

在这之前 Project Manager 只能扫 frontmatter——这也正是 §1 和 §2 是本文里最需要先有答复的两节的原因。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions