Tolaria ADR 0122 深度解读:标量数组 Frontmatter 属性与视图集合语义过滤

发布时间:2026/9/14 13:55:59
Tolaria ADR 0122 深度解读:标量数组 Frontmatter 属性与视图集合语义过滤 Tolaria ADR 0122 深度解读标量数组 Frontmatter 属性与视图集合语义过滤【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolariaTolaria一个管理 Markdown 知识库的桌面应用的 Saved Views 功能依赖VaultEntry.properties在渲染端和 Rust 视图求值器中做过滤。本文基于仓库中的架构决策记录 ADR 0122 及其对应的实现代码完整讲解标量数组 frontmatter 属性是如何被解析、存储、过滤和缓存的。读完本文你能理解为什么tags: [blues, chicago]这类多元素数组在视图过滤中会采用集合语义精确元素匹配而非子串匹配以及单元素数组为何会被归一化为标量并能在自己的 frontmatter 设计中正确使用这类属性。背景为什么数组属性曾经存不住在 ADR 0122状态active日期 2026-05-15之前Tolaria 的 vault 全量扫描会保留自定义标量 frontmatter 值但丢弃多元素、不含 wikilink 的数组。这导致一个具体而稳定的故障模式一个视图过滤条件如tags / contains / blues在乐观的渲染端状态里能暂时生效编辑器刚保存时渲染层能看到新数组但一旦发生 reload、切换视图或重启应用条目会被重建而数组背后的属性已经不在其中过滤结果随之跳变。与此同时含 wikilink 的数组早已走了一条独立通路这类字段被存入VaultEntry.relationships而非properties因为它们代表知识图谱中的边。问题在于纯标量数组如tags既不该被丢弃也不该被误当作关系字段——它没有 wikilink不是图谱边不应出现在关系面板里。ADR 因此给出的核心决策是Tolaria 将自定义标量数组 frontmatter 字段保留在VaultEntry.properties中含 wikilink 的数组继续作为 relationships 处理。单元素标量数组为兼容性继续归一化为标量值多元素标量数组保持数组形态。解析层extract_properties如何决定一个字段进哪条通路vault 扫描时frontmatter 的解析入口在 src-tauri/src/vault/frontmatter.rs。每个顶层 frontmatter 键都会先判断是否为保留键系统字段然后按值类型分流。数组值的判定函数是scalar_array_property_valuefrontmatter.rs#L287-L304fn scalar_array_property_value(arr: [serde_json::Value]) - Optionserde_json::Value { let mut values Vec::new(); for item in arr { let sanitized sanitize_array_item(item)?; match sanitized { // 任一项含 wikilink → 整体不作为标量数组属性 serde_json::Value::String(ref s) if contains_wikilink(s) return None, serde_json::Value::String(_) | serde_json::Value::Number(_) | serde_json::Value::Bool(_) values.push(sanitized), _ return None, // 对象等复合类型同样被排除 } } match values.as_slice() { [single] Some(single.clone()), // 单元素数组 → 归一化为标量 _ Some(serde_json::Value::Array(values)), // 多元素 → 保持数组 } }这段代码精确对应了 ADR 决策的每一条细则含 wikilink 的数组拒绝进入 properties——任何一项字符串含[[...]]时整体返回None由extract_relationshipsfrontmatter.rs#L230-L247负责把它收进VaultEntry.relationships保证关系字段与属性字段互斥复合类型对象、嵌套结构被排除只有 String / Number / Bool 三类元素能构成标量数组单元素数组归一化为标量[single] Some(single.clone())这是 ADR 明确保留的兼容性行为——status: [active]写法和status: active在属性层面等价空数组不产生属性values为空时匹配到[single]分支之外Array([])虽会被保留但配合sanitize_array_item过滤 null 后纯空列表实际等价于无值。外层分流逻辑在extract_propertiesfrontmatter.rs#L307-L337标量字符串不含 wikilink、数字、布尔、Null 直接保留数组走上面那条scalar_array_property_value通路能归一化的才写入properties。最终数据落在 src-tauri/src/vault/entry.rs 的VaultEntry上/// Custom scalar and scalar-array frontmatter properties (non-relationship, non-structural). /// Objects and arrays containing wikilinks are excluded. #[serde(default)] pub properties: HashMapString, serde_json::Value,注意类型签名properties的值类型是serde_json::Value而非String即 ADR Consequences 所说的把VaultEntry.properties从 scalar-only 放宽为 scalar-or-array。另外值得一提的健壮性设计解析前有一段 sanitizerfrontmatter.rs#L153-L179用于修正 gray_matter 对某些 YAML 写法的误解析——例如- Bitcoin: Net Unrealized会被解析成对象、- # Heading会被当成注释解析成 null。sanitizer 会把对象还原为key: value字符串、剔除 null防止整个 frontmatter 结构反序列化失败。这是标量数组属性能稳定入库的前置条件。过滤层集合语义 vs 标量文本语义ADR 明确Saved View 过滤对标量数组属性使用集合语义——contains和any_of匹配精确且大小写不敏感的元素而不是元素内部的子串标量属性则维持既有的大小写不敏感文本匹配。Rust 端的求值器在 src-tauri/src/vault/views.rs。条件字段先被解析为三种形态views.rs#L294-L298enum ConditionFielda { Scalar(OptionString), PropertyArray(VecString), Relationship(a [String]), }分流发生在resolve_dynamic_condition_fieldviews.rs#L355-L366先看properties若该值能被json_scalar_array_to_strings转为字符串数组实现在 src-tauri/src/vault/view_value_conversions.rs#L10-L14就走PropertyArray分支否则按标量处理properties 里找不到再看relationships。数组分支的操作符语义集中在evaluate_property_array_opviews.rs#L407-L425操作符数组语义contains任一元素与目标精确相等大小写不敏感not_contains上者的否定any_of目标列表支持 YAML 数组值中任一元素被属性数组精确包含none_of上者的否定equals仅当数组恰好一个元素且该元素匹配时成立not_equals上者的否定is_empty/not_empty按数组是否为空判断精确匹配的实现只有两行views.rs#L443-L447fn property_array_contains(values: [String], target: str) - bool { values .iter() .any(|value| value.eq_ignore_ascii_case(target)) }而标量分支的contains是子串包含views.rs#L481-L486fn scalar_contains(field_value: Optionstr, cond_value: Optionstr) - bool { match (field_value, cond_value) { (Some(field), Some(value)) field.to_lowercase().contains(value.to_lowercase()), _ false, } }两者对比正是 ADR 的核心语义差异。配套测试test_contains_matches_scalar_array_property_elementsrc-tauri/src/vault/view_tests.rs#L408-L434直接验证了这一点视图条件tags contains blues对属性[blues, chicago]命中但对[bluegrass, chicago]不命中——blues虽是bluegrass的子串却因不是精确元素而被排除。这正是集合语义的用意避免标签类字段出现部分匹配误命中。此外正则模式也覆盖数组分支evaluate_regex_conditionviews.rs#L388-L405对PropertyArray取任一元素匹配正则。渲染端TypeScript实现了同一套语义保证乐观更新与 Rust 求值结果一致。相关模块包括 src/utils/viewFilters.ts、src/utils/viewFilterArrayProperties.ts 与 src/utils/viewFilterArrayFields.ts它们识别数组属性字段并在渲染层做等价的元素级匹配。ADR Context 一节提到的乐观渲染端状态能看到变化的数组但重载后条目被重建导致属性丢失的问题正是双端求值器都依赖磁盘扫描产物VaultEntry.properties的结果——修复点因此在解析/缓存层而不是求值层。缓存层版本号强制重建ADR 还决定提升 vault 缓存版本使丢弃过数组属性的旧缓存从磁盘重建。当前实现在 src-tauri/src/vault/cache.rs#L25const CACHE_VERSION: u32 14;缓存加载时若cache.version ! CACHE_VERSION即判定过期并触发全量重扫cache.rs#L724 附近的版本校验逻辑并有test_stale_cache_version_forces_rescan_of_archived_yes等测试覆盖过期重建路径。对使用者而言这意味着升级到包含本决策的版本后旧缓存不会被继续沿用含数组属性的条目会在首次扫描时重新入库视图过滤即刻对多元素数组生效——不需要手动清缓存。被否决的备选方案ADR 记录了两个未采纳的选项理解它们有助于把握取舍方案 B把数组拍平成逗号分隔字符串。优点是properties保持纯标量类型缺点是精确元素与子串无法区分——tags: blues, chicago拍平后contains blues无法与contains blue区分过滤语义变得含糊。事实上这也是方案 A 相对 B 的关键增益元素边界在数据结构中被保留eq_ignore_ascii_case才能精确判断。方案 C把所有数组都当作 relationship 处理。虽然能复用现成的关系匹配逻辑但 tags 这类值不是图谱边不应出现在关系面板、反链与关系浏览中。这也解释了为什么scalar_array_property_value遇到含 wikilink 的数组要整体让位给 relationships 通路而不是混合处理。影响面UI 组件与后续开发纪律ADR 的 Consequences 部分列出了两条工程约束代码中均已落实属性 chips 与排序必须容忍数组。笔记列表的属性 chip 解析器 src/components/note-item/propertyChipValues.ts 已具备展开数组值的能力多元素数组可以渲染为多个 chip 值自定义属性排序对数组回退到字符串比较保证排序路径不因值类型变宽而 panic 或产生非确定性结果。任何后续自定义属性逻辑必须处理标量或数组双形态。从源码结构看VaultEntry.properties的值类型为serde_json::ValueRust 侧以及渲染端对应的可空/可数组取值路径如 src/utils/propertyTypes.ts、src/utils/vaultMetadataNormalization.ts 中的归一化逻辑凡是要读取properties的新功能都应先判断值是标量还是数组而不是假设String。实践要点速查写法在 frontmatter 中直接写 YAML 数组即可例如tags: [blues, chicago]或块状列表单元素tags: [blues]与tags: blues等价归一化为标量。过滤对数组属性使用contains/any_of时按精确元素大小写不敏感匹配想要子串匹配需要该字段是标量值或使用支持正则的contains模式。边界数组项含 wikilink → 字段整体进入 relationships不再作为属性可过滤属性分支与关系分支互斥数组项为对象/null → 被 sanitizer 清洗或整体排除。生效前提需要运行包含缓存版本提升CACHE_VERSION 14的版本旧缓存会在启动/扫描时自动重建无需手工干预。小结ADR 0122 用一次类型放宽换取了 frontmatter 表达力与过滤正确性VaultEntry.properties从纯标量放宽为标量或数组解析层frontmatter.rs负责按是否含 wikilink把数组分流到属性或关系两条通路求值层views.rs为数组分支提供精确元素匹配的集合语义缓存层cache.rs通过版本号保证存量缓存一次性重建。三者共同使tags / contains / blues这类视图在保存、重载、切换视图与重启后保持一致的行为。【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考