Claudian Collab 详情视图架构:状态所有权、评审生命周期与 Diff 渲染器的管理策略
【免费下载链接】claudianAn Obsidian plugin that embeds Claude Code/Codex as an AI collaborator in your vault项目地址: https://gitcode.com/GitHub_Trending/cl/claudian
本篇技术指南围绕 Claudian(一个将 Claude Code/Codex 嵌入 Obsidian 库中作为 AI 协作者的插件)的 Collab 协作详情面(Detail Surface)展开。Collab 是 Claudian 中面向多成员项目协作的子系统:成员把工作变更打包成 Request(变更请求),由 Manager 评审并接受(Accept),本地修改则通过 Publish 发布,发生冲突时进入冲突解决流程。详情面就是承载 Request 评审、Publication 评审、工作区评审、Ticket(工单)编辑与冲突呈现这五种界面的 Obsidian 视图层。读完本文,你可以理解该视图层如何用"会话(Session)所有权"模型隔离状态、如何保证评审与接受的并发正确性,以及它如何管理 Pierre diff 渲染器的复用与销毁,这些机制对任何在 Obsidian 插件中构建多状态视图的开发者都有直接参考价值。
一、总体结构:五种视图状态与唯一路由器
Collab 详情面全部代码位于 src/features/collab/detail/ 目录,其内部约定记录在该目录的 AGENTS.md 中。目录由四类文件组成:
- 契约层:CollabDetailContracts.ts 定义视图状态(view state)类型、端口(port)接口与工厂类型;
- 视图层:CollabDetailView.ts 是唯一接入 Obsidian 工作区(ItemView)的类,负责状态解析、路由分发与叶(leaf)生命周期;
- 会话层:sessions/ 下的 ReviewDetailSession、TicketDetailSession、ConflictDetailSession 是各自独立销毁的业务会话;
- 渲染层:review/ 下的 diff 渲染会话与冲突面板 conflict/CollabConflictResolutionPanel.ts。
1.1 视图状态是一个可辨识联合
CollabDetailContracts.ts 定义了五个视图状态接口,按kind字段区分为一个联合类型CollabDetailViewState:
| kind | 接口 | 关键字段 |
|---|---|---|
request | CollabRequestDetailViewState | requestId、reviewedHeadOid、reviewedMainOid、comparisonBaseOid/comparisonTargetOid、可选selectedPath |
publication | CollabPublicationDetailViewState | operationId、candidateOid、currentMainOid、比较基准与目标 OID |
working-tree | CollabWorkingTreeDetailViewState | baseOid、headOid、snapshotId(64 位十六进制) |
ticket | CollabTicketDetailViewState | ticketId(可选,缺省表示"新建") |
conflict | CollabConflictDetailViewState | operationId、location(my-changes或request) |
其中CollabReviewDetailViewState进一步收敛为request | publication | working-tree三种评审类状态,供评审会话共用。契约文件还定义了CollabDetailViewPort接口——它是会话与特性层之间的唯一通道,聚合了prepareReview、preparePublicationReview、prepareWorkingTreeReview、acceptRequest、confirmPublish、publish、addComment、readSnapshot、readTicket/createTicket/updateTicketContent/addTicketComment等只读与变更操作,并以subscribe(listener)提供特性失效(invalidation)订阅。
1.2 严格的状态解析
CollabDetailView作为唯一的 Obsidian 叶子/状态路由器,在setState入口对任意来源的状态(包括 Obsidian 从布局文件恢复的序列化状态)做白名单式解析。CollabDetailView.ts#L107-L208 的parseState对每种kind逐一校验:projectId必须通过isCollabProjectId,请求/操作 ID 必须通过isCollabOpaqueId,各类 OID 必须通过isCollabGitOid,working-tree的snapshotId必须匹配/^[0-9a-f]{64}$/。路径字段则交给 CollabDetailView.ts#L98-L105 的isSafePath校验:不允许绝对路径、反斜杠、NUL 字符,且每个路径段非空、不是.或..。
这个解析器的设计意图在 AGENTS.md 中写得很明确:视图只持久化"经过校验的 Project/request、发布操作、个人评审、Ticket 或冲突标识符,加上选中的路径以及在适用时的精确 OID"——凭据与 blob 内容永不进入视图状态。这保证了 Obsidian 布局恢复(restore)时恢复出来的只是无副作用的导航坐标,而不是敏感数据或大对象。
二、状态与会话所有权模型
2.1 契约文件是会话的唯一权威
AGENTS.md 第一条约定确立了模块内依赖方向:CollabDetailContracts.ts是面向会话的状态、端口与工厂权威(authority)。ReviewDetailSession、TicketDetailSession、ConflictDetailSession直接 import 契约文件,不允许importCollabDetailView.ts。这样视图实现与业务会话之间只通过接口耦合,会话可以独立测试与替换,也避免了循环依赖。
2.2 "恰好一个"会话与替换语义
核心不变量是:任意时刻恰好一个独立销毁的详情会话拥有当前呈现的 Request、Publication、个人评审、Ticket 或冲突。CollabDetailView内部持有reviewSession、ticketSession、conflictSession三个槽位,setState按kind分发到loadReview/loadTicket/loadConflict(CollabDetailView.ts#L262-L288),分发前调用activateMode→cancelWork销毁旧会话。
这里有一个重要的正确性约束:替换视图状态会销毁旧会话,且不在会话之间转移变更意图(mutation intent)。也就是说,如果用户在编辑 Ticket 描述时另一个入口切换了视图状态,正在进行的提交意图不会"漂"到新会话中;未保存的草稿是否保留由各会话自行决定(见第五节)。cancelWork支持retainReviewDiff选项(CollabDetailView.ts#L397-L406):在同一评审类视图之间切换时保留 diff 会话,切到 ticket/conflict 时则连 diff 一起清空。
会话还订阅特性失效信号:onOpen中通过this.port.subscribe(...)注册监听,ticket 类状态触发loadTicket重读,request 类状态触发reviewSession.refresh()(CollabDetailView.ts#L290-L310);onClose中先cancelWork()取消进行中的工作,再diffSession.destroy()并释放订阅。
2.3 协调器:插件生命周期共享与最新意图合并
CollabDetailViewCoordinator(CollabDetailView.ts#L410-L480)负责跨叶子的工作区转换,约定整个插件生命周期只构建一个实例,不得按点击新建,也不得让setViewState调用重叠。它的实现机制是:
- 内部维护
generation计数器与transitionTail尾链,把每次open/openInNewTab/close追加为链上的一个任务,保证工作区叶子转换被严格串行化; - 每个任务执行前检查
generation !== this.generation,一旦有更新的调用到来(latest-intent),旧任务直接跳过——这就是"最新意图合并"(latest-intent coalescing):快速连续点击多个 Request 时,只有最后一次真正落定; open会对传入状态再次执行parseState;当kind === 'request'且携带了prepared(预先准备好的评审包)时,先执行assertReviewMatchesState(prepared.review, safeState)校验评审包与目标状态一致,再把评审包存入preparedReviews缓存,然后复用已有详情叶子或新建标签页,最后revealLeaf使其可见。
"prepared review" 机制值得单独说明:打开一个 Request 评审通常需要先走一次较重的prepareReview(生成文件级差异)。为了让"点击侧栏 Request → 进入详情"与"详情内刷新"共享结果,协调器把评审包与协调快照(coordination)一起放进 handoff/CollabPreparedReviewCache,ReviewDetailSession在加载时优先读缓存,但绝不信任缓存本身——它总是重新读一次协调快照,若发现请求投影或已接受主分支 OID 发生变化就重新 prepare(详见第三节)。
2.4 评审叶子的生命周期纪律
评审类叶子(review leaves)被定义为"仅会话期存在"(session-only),配套三条纪律(来自 AGENTS.md):
- 启动时清除:插件启动后移除布局恢复出来的评审叶子;
- 卸载前摘除:插件卸载(unload)时在布局持久化之前 detach 评审叶子,避免它们被写入布局文件;
- 成功后关闭:Accept、Publish、Confirm and Publish 成功后主动关闭当前评审叶子。
源码可以印证第 3 条:ReviewDetailSession.accept成功且确认当前评审身份未漂移后执行this.leaf.detach()(ReviewDetailSession.ts#L1069-L1070);confirmPublish与publishWorkingTree在成功或转入下一评审状态时同样以leaf.detach()收尾(ReviewDetailSession.ts#L1149、L1189-L1192)。此外,setState中若port.isDetailAdmissionOpen()为假(例如布局已恢复但工作区尚未就绪),视图只保存解析后的状态而"不激活、不订阅、不读取",等启动阶段再摘除——这段注释直接写在 CollabDetailView.ts#L265-L274。
三、Request 评审呈现:Overview 优先、惰性 Changes、严格的 Accept 门槛
3.1 双标签结构:Overview 与 Changes
Request 评审打开时默认落在Overview标签(requestReviewTab初值'overview',见 ReviewDetailSession.ts#L300-L308),其中包含三部分:
- 完整描述:通过
MarkdownDraftEditor渲染的发布描述(request 评审初始为preview模式); - Request 级 Markdown 评论:按
createdAt(并列时按id)时间序排列的不可变评论列表,通过renderRequestCommentItems渲染(ReviewDetailSession.ts#L885-L924); - 一个评论编辑器(composer):仅当
request.status === 'open'时创建CollabCommentComposer(ReviewDetailSession.ts#L852-L883)。
Changes 标签是惰性加载的:只有用户首次切换到 Changes 时才调用startRequestChanges触发 diff 读取与渲染(ReviewDetailSession.ts#L1001-L1007),且用requestChangesLoaded标志防止重复加载。与之对称的是功能边界上的"减法":Request 评审没有行内评论槽(comment gutter)、批注、锚点、行区间、线程、已解决/已读状态、通知消除、普通 Close 或 Reject——评论只存在于 Request 层。
两种评审类型互相屏蔽对方的主操作:Publication 评审绝不暴露评论编辑器与 Accept 按钮;Request 评审绝不暴露 "Confirm and Publish"。源码中renderReview(ReviewDetailSession.ts#L300-L348)的分支清晰地体现了这一点:isRequestReview走renderRequestAcceptAction;isPublicationReview && review.canConfirm走 Confirm 按钮;isWorkingTreeReview走 Publish 按钮。
3.2 评论提交的幂等意图
评论提交经由submitRequestComment(ReviewDetailSession.ts#L926-L967):
- 提交前先用
MutationIntentStore的intent('comment', mutation)为"当前 payload"登记幂等意图 ID(intentId);mutation 由{ body, projectId, requestId }组成——payload 变化才会轮换 intent; - 请求携带
intentId发出,失败时按钮变为 Retry、草稿(draft)原样保留,重发走同一intentId,服务端据此去重,避免"看起来失败了两次但其实只落库一次"的歧义; - 成功后
adoptRequestComment采用幂等追加策略:若评论 ID 已存在于列表中则不重复插入,再清空编辑器(ReviewDetailSession.ts#L1243-L1275)。
描述保存(saveRequestDescription)使用同一套模式,且 mutation 中额外携带乐观并发字段expectedRequestRevision与expectedHeadOid(ReviewDetailSession.ts#L752-L758)——描述更新与 head 漂移会相互排斥。
3.3 刷新语义:绝不信任滞留的 handoff 快照
一个打开中的 Request 评审在每次特性失效后都要刷新协调状态(refresh()→refreshRequestCoordination,ReviewDetailSession.ts#L457-L533),其规则逐条对应 AGENTS.md 的约定:
- 重新 prepare 的触发条件:协调快照显示
project.mainOid与当前评审记录的currentMainOid不一致(acceptedMainChanged),或该 Request 的投影(状态、修订、更新时间等)发生变化(requestProjectionChanged)。二者任一成立才调用port.prepareReview重新生成差异; - 单调合并:若重新 prepare,则用
mergeRefreshedRequestReview(currentReview, refreshedReview)合并"更新的评论与更新的元数据",同时保留进行中的草稿与重试意图(合并发生在this.review层面,草稿由编辑器组件独立持有); - 同 OID 的评论刷新不重建 diff:
applyRefreshedRequestReview(ReviewDetailSession.ts#L535-L602)区分identityChanged(评审身份变化)与presentationChanged(仅呈现变化,如评论条数/内容变化)。仅呈现变化时只更新 Overview 区的标题、标签计数、条件徽章与评论列表;只有identityChanged才diffSession.detach()并清空内容宿主,在 Changes 标签下才重新startRequestChanges; - Accept 期间的合并:
refreshRequestCoordination开头检查this.acceptController,若 Accept 正在进行则只置位requestCoordinationRefreshPending,Accept 结束的finally中再补一次刷新——即 Accept 期间的多次失效合并成一次 Accept 后刷新(ReviewDetailSession.ts#L1076-L1085); - 刷新失败降级:若协调读取失败(
refreshFailed),则把评审标记为canAccept: false但不抛出,UI 保持可看。
3.4 Accept:可见性与启用条件是两道独立门槛
Accept 按钮的渲染在renderRequestAcceptAction(ReviewDetailSession.ts#L430-L455)中,条件分两层:
- 可见性:
review.canAccept为真且isCurrentManager(coordination)成立——即"显示的快照中当前 Member 是 Manager,且 Project 指针(协调快照)也同意"。canAccept本身由withFreshAcceptEligibility(requestReview, snapshot)在每次取得快照后重新计算,不信任旧的评审投影; - 启用性:按钮仅在
canAcceptFromCoordination判定为"在线、非陈旧、已同步"(coordination.source === 'online'、!stale、syncState.status === 'synchronized')时启用,否则禁用并显示提示。
点击后的accept(ReviewDetailSession.ts#L1028-L1086)再次做一组防御检查后才提交,提交的 mutation 由acceptMutation(ReviewDetailSession.ts#L1088-L1106)精确构造:
{ expectedHeadOid: review.detail.reviewedHeadOid, // 评审时看到的 head expectedMainOid: review.detail.currentMainOid, // 评审时 main 指针 expectedRequestRevision: review.detail.request.revision, expectedResolvingTickets: [...], // 所有 resolves 关系的 ticketId + ticketRevision projectId, requestId }expectedResolvingTickets从请求的ticketRelations中筛出kind === 'resolves'的关系并附带ticketRevision,按ticketId排序。这正是文档所说"提交评审过的精确 main/head、请求修订号与全部 resolving-Ticket 修订号":服务端可以用这组期望值做乐观并发校验,评审期间任何 head/main 移动、请求修订或 Ticket 修订变化都会使 Accept 失败并需要重新评审。"UI 状态和 WebSocket 事件不是正确性边界"——所有正确性都压在这组服务端可校验的期望值上。成功路径还会比对JSON.stringify(this.acceptMutation(current))与提交时的快照字符串相等才本地落定(防止 Accept 飞行期间评审身份已变化)。
四、Diff 与渲染器生命周期
4.1 一个可复用的 ReviewDiffSession
详情视图持有唯一一个可复用的review/ReviewDiffSession.ts 实例(在CollabDetailView构造函数中创建,CollabDetailView.ts#L229-L236),只有当前活动的 request/publication/personal 评审会话可以"借用"它来管理 Pierre 渲染实例的生命周期、选中文件读取、渐进式渲染与二进制对象 URL。借用语义由bind/show/detach/clear/destroy一组方法实现:
- 同一精确评审内的 review-to-review 变化:保留已准备好的评审,只替换选中文件的呈现;
- 跨身份(across identities)变化:复用同一个 Pierre Diffs 实例,但替换每状态请求与 object URL;包装器(wrapper)变化时通过 Pierre 的公共 API 渲染,使缓存的相同文件能够重新挂载(reattach);
- 冲突、不透明(opaque)、空、错误或已关闭的呈现:调用
clear清掉实例;叶子关闭时释放主题观察者(onClose→diffSession.destroy())。
4.2 作用域与布局:两个独立的内存选项
评审作用域(scope)与布局(layout)是相互独立的纯内存选项,不持久化:All files(continuous 连续浏览)+Unified(统一布局)是默认值。控件由ReviewDiffSession.createControls生成(ReviewDiffSession.ts#L156 起),scope 在file(单文件浏览)与continuous(全部文件连续滚动)之间切换,layout 在split(side-by-side)与unified之间切换;两种布局都换行长行(Pierre 选项overflow: 'wrap',见 CollabDiffRenderer.ts#L15-L23)。
Continuous 模式的渲染策略有明确的资源边界(ReviewDiffSession.ts#L357-L452):
- 侧栏/滚动选中的文件立即加载渲染(
continuousPrimaryPath对应 loader 被直接调用); - 其余文件通过
IntersectionObserver监听各文件 section 进入视口时才触发加载; - 后台读取/渲染最多并发一个(
continuousQueue队列 +LatestTaskScope的 latest-task 语义,新任务取消旧任务); - 已完成文件内容缓存受双重上限约束:
MAX_REVIEW_FILE_CACHE_BYTES = 16 * 1024 * 1024(16 MB)与MAX_REVIEW_FILE_CACHE_ENTRIES = 32(ReviewDiffSession.ts#L12-L13),且只归属当前精确评审(review 变化即失效); - 每文件渲染器只在临近视口时创建(
continuousRenderers按需分配),主文件使用共享渲染器实例。
4.3 文本专用 diff:固定依赖契约的 Pierre 集成
Collab 的 diff 被有意做成纯文本渲染,这是 AGENTS.md 中最硬的一条工程约束:
- review/CollabDiffRenderer.ts 必须向 Pierre 传
lang: text;其选项类型PierreDiffOptions固定了preferredHighlighter: 'shiki-js'、overflow: 'wrap'、disableErrorHandling: true(CollabDiffRenderer.ts#L15-L23); - review/CollabShikiAdapter.ts 只实现 Pierre 所验证的纯文本 Shiki 面(plain-text surface),不保留任何语法高亮运行时——例如其中对 Shiki Wasm 的调用直接抛出
"The Collab text diff renderer does not support Shiki Wasm."(CollabShikiAdapter.ts#L94),主题也是内部PlainTextTheme映射而非语法主题; - review/CollabPierreThemes.ts 只注册
pierre-dark与pierre-light两个主题(pierreThemes = createThemeCollection(...),CollabPierreThemes.ts#L63-L78),并提供空的shikiThemes集合; - 构建侧由受守卫的替换插件接管 Pierre 的 Shiki、transformer、主题目录、可选 Wasm、文件名语言、CSS 与 SVG 导入,相关打包逻辑在 scripts/pierreShikiBundle.js 中实现并挂接到 esbuild.config.mjs。
这条约束的动机可以推断为:语法高亮会引入庞大的语言语法包与不确定的主题行为,把 diff 面钉死在"已验证的纯文本契约"上,包体与渲染行为都可控。文档同时给出升级纪律:@pierre/diffs保持钉在已验证的依赖契约上;升级 Pierre 前必须先更新契约、依赖封套(dependency-envelope)以及真实 DOM 文本渲染测试,而不是直接换版本。
4.4 个人变更读取的四重再校验
针对 working-tree(个人变更)评审,文档要求文件内容返回 base/working 之前,读取路径要再校验四样东西:已发布 base、个人HEAD、本地快照身份(snapshot identity)与捕获的内容哈希。从源码结构看,ReviewDiffSession的三个读取端口(readReviewFile、readPublicationReviewFile、readWorkingTreeReviewFile,ReviewDiffSession.ts#L44-L52)把文件读取委托给特性层,视图侧只负责缓存与失效——这意味着工作区文件在评审期间被继续修改时,旧缓存不会悄悄喂给 diff。
五、Ticket 与关系(References/Resolves)
5.1 Ticket 详情的职责边界
Ticket 详情会话(sessions/TicketDetailSession.ts)拥有完整的工单生命周期操作:创建、读取、编辑、评论、已接受关系(accepted relations)以及关闭/重开。呈现上有一条安全底线:正文与评论一律通过 Obsidian Markdown 渲染(MarkdownRenderer.render),绝不渲染原始 HTML——CollabDetailView.loadTicket把MarkdownRenderer.render作为renderMarkdown注入会话(CollabDetailView.ts#L318-L334)。缓存(cached)读到的 Ticket 以"只读可见"方式呈现,且不暴露任何变更控件。
5.2 后台失效下的原位刷新与瞬态状态保留
后台特性失效会原位刷新同一个 Project/Ticket 身份。刷新期间必须保留的瞬态状态清单(来自 AGENTS.md 且由会话的意图存储支撑):未保存的创建/编辑/评论值、编辑器模式与焦点、原始编辑修订号(original edit revision)、精确的变更意图;只有在"身份替换"或"会话销毁"时才丢弃这些瞬态状态。这与评审会话的MutationIntentStore是同一套哲学:变更意图以精确 payload 为键,payload 一变意图即轮换(rotate),只有被当前 UI 消费的结果才能清除意图。
5.3#N引用与Resolves #N关闭关系
描述文本是唯一可编辑的关系来源——没有任何选择器、缓存、视图状态或变更 payload 可以持有独立的关系集合。语法规则:
- 边界分隔的裸
#N表示references(引用); - 同行的规范化关闭语法(如
Resolves #N)表示resolves; - 扫描由共享的 Markdown 语法树扫描器完成,只检查可见散文,排除代码块、HTML、实体、链接目标、定义与转义;相邻的 hash token(如
#1 #2被一个 token 覆盖)与换行分隔的关键词不算关闭关系; - 自动补全与 Resolve 操作只插入规范化文本,关系预览要么从该文本推导,要么来自 Host 投影——即文本本身是唯一事实源,UI 不另存一份关系列表;
- working-tree 与 publication 评审会恢复私有的描述草稿;若 owner 草稿与服务端描述分叉,则标记为"未同步"(
descriptionNotSynced,见 ReviewDetailSession.ts#L685-L696),而不是静默替换。
从描述里的#N跳转到对应 Ticket 由 sessions/TicketReferenceResolver.ts 承担:它把"编号 → ticketId"的查找实现为对listTickets的分页遍历(先open后closed两种状态、按CLAUDIAN_COLLAB_LIMITS.maxTicketPageSize翻页、用visitedCursors防环),并保证每个解析器至多一个查找在途——新的点击、会话销毁或状态替换都会取消旧查找,且在真正开标签页前用isCurrent()复核所有权(TicketReferenceResolver.ts#L27-L78)。
六、冲突呈现:只给证据,不给裁决
冲突面板 conflict/CollabConflictResolutionPanel.ts 把 provider 中立的冲突会话作为不可变证据呈现,其呈现约束是刻意收窄的:
- 它标识"My changes(我的变更)"与"Request(请求侧)"的归属(
CollabConflictLocation = 'my-changes' | 'request'),但不暴露:任意一侧选择、草稿编辑器、最终化(finalization)操作、Git index 的 stage/ref/冲突标记细节,或 Agent 调用——即面板不提供"三向合并选择器"这类捷径; - 冲突 UI 是无文件导航器的连续单列评审:每个文本文件拥有一个完整的"个人 vs 已接受"的 Pierre 并排渲染器(面板内
comparisonDiffs按路径缓存实例);不透明(opaque)与阻塞性冲突只暴露元数据(类型标签、大小等,如面板中的kindLabel区分 text/binary/delete-modify/rename-delete/directory-file/portability,formatBytes展示大小); - 恒定的操作指引是:去编辑真实的 Project 文件,然后再次 Publish。
读取绑定规则保证了证据不漂移:冲突读取始终绑定到原始的 base、personal 与 accepted OID,即便工作区文件继续变化。下一次 Publish 会捕获精确的本地结果,通过私有 scratch 暂存准备一次正常的发布评审,并保留既有 Request 身份(这与confirmPublish在result.status === 'conflict'时把叶子状态切换为conflict视图、location根据"当前成员是否有打开的 Request"决定为request或my-changes的实现一致,见 ReviewDetailSession.ts#L1211-L1241)。呈现层从不拥有解决进度——进度永远属于 Git 仓库与发布流程。
七、验证清单与测试布局
AGENTS.md 的 Verification 一节给出了该模块必须被测试覆盖的行为清单,每一项都通过"公共视图或所属会话"(而非内部私有)来验证:
- 已校验状态的恢复(restoration)、活动会话替换、叶子关闭行为;
- prepared handoff 的再校验(缓存评审包与实时快照不一致时的重新 prepare);
- 草稿与幂等意图的保留(评论/描述重试、面板替换不得轮换丢失响应的重试);
- Accept 预检(可见性 + 启用性 + 期望值提交);
- Pierre 的复用与清理、有界的连续渲染(视口内渲染、单一后台任务、缓存上限);
- Ticket 只读态;
- 冲突的过期最终化(stale-finalization)。
对应测试位于 tests/unit/features/collab/detail/,包括 CollabDetailView.test.ts 与review/、sessions/、conflict/、ticket/子目录,可按上述清单逐一对照源码行为。
八、小结:这套架构回答的三个问题
- 状态从哪里来?一切视图状态都是经
parseState校验的导航坐标(标识符 + OID + 路径),敏感与大对象不入状态;会话才是业务状态的持有者,且同一时刻只有一个活跃会话。 - 并发怎么不乱?协调器用 generation + 尾链串行化工作区转换并合并最新意图;评审刷新用"投影/main OID 变化才重 prepare + 单调合并"控制成本;Accept/Publish 用服务端可校验的期望 OID 与修订号做乐观并发,UI 只是提示。
- 渲染资源怎么不爆?唯一的 diff 会话跨评审复用、按身份/冲突/错误精确清理;continuous 模式用视口观察 + 单一后台任务 + 16 MB/32 条缓存上限约束内存;diff 面钉死在已验证的纯文本 Pierre 契约上,升级走契约先行流程。
对要在 Obsidian 插件(或类似的宿主环境)里构建多状态、多来源失效驱动视图的开发者而言,这份模块文档与其源码示范了一个完整答案:把"持久化什么"(视图状态)、"谁拥有它"(会话)、"何时重建"(协调快照差异)、"渲染多少"(视口与缓存边界)拆成四条各自可验证的边界,正确性才不会被 UI 时序问题侵蚀。
【免费下载链接】claudianAn Obsidian plugin that embeds Claude Code/Codex as an AI collaborator in your vault项目地址: https://gitcode.com/GitHub_Trending/cl/claudian
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考