feat: add Rust backend for pinned properties + refactor hook complexity

- Add PinnedPropertyConfig struct to VaultEntry (Rust)
- Extract _pinned_properties from YAML frontmatter (key:icon format)
- Filter underscore-prefixed system properties from properties/relationships
- Bump vault cache version to 9
- Refactor usePinnedProperties hook (cc 15→8) for CodeScene compliance
- Add 6 Rust tests for pinned properties extraction
- Add pinnedProperties field to TypeScript VaultEntry interface
- Wire _pinned_properties mapping in frontmatterOps entry patch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Test
2026-03-25 10:37:52 +01:00
parent 660208a6fd
commit 86a9f65f88
12 changed files with 227 additions and 53 deletions

View File

@@ -8,7 +8,7 @@ use super::{parse_md_file, scan_vault, VaultEntry};
// --- Vault Cache ---
/// Bump this when VaultEntry fields change to force a full rescan.
const CACHE_VERSION: u32 = 8;
const CACHE_VERSION: u32 = 9;
#[derive(Debug, Serialize, Deserialize)]
struct VaultCache {

View File

@@ -1,6 +1,13 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// A single pinned-property config item stored in the type's frontmatter.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct PinnedPropertyConfig {
pub key: String,
pub icon: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct VaultEntry {
pub path: String,
@@ -59,4 +66,7 @@ pub struct VaultEntry {
/// Only includes strings, numbers, and booleans — arrays/objects are excluded.
#[serde(default)]
pub properties: HashMap<String, serde_json::Value>,
/// Pinned properties configuration for Type entries.
#[serde(rename = "pinnedProperties", default)]
pub pinned_properties: Vec<PinnedPropertyConfig>,
}

View File

@@ -1,3 +1,4 @@
use crate::vault::entry::PinnedPropertyConfig;
use crate::vault::parsing::contains_wikilink;
use serde::Deserialize;
use std::collections::HashMap;
@@ -193,6 +194,9 @@ pub(crate) fn extract_relationships(
let mut relationships = HashMap::new();
for (key, value) in data {
if key.starts_with('_') {
continue;
}
if SKIP_KEYS.iter().any(|k| k.eq_ignore_ascii_case(key)) {
continue;
}
@@ -228,6 +232,9 @@ pub(crate) fn extract_properties(
let mut properties = HashMap::new();
for (key, value) in data {
if key.starts_with('_') {
continue;
}
let lower = key.to_ascii_lowercase();
if SKIP_KEYS.iter().any(|k| k.eq_ignore_ascii_case(&lower)) {
continue;
@@ -265,6 +272,38 @@ pub(crate) fn resolve_is_a(fm_is_a: Option<StringOrList>) -> Option<String> {
fm_is_a.and_then(|a| a.into_vec().into_iter().next())
}
/// Parse a single pinned-property entry from "key:icon" format.
fn parse_pinned_entry(s: &str) -> PinnedPropertyConfig {
match s.split_once(':') {
Some((key, icon)) if !icon.is_empty() => PinnedPropertyConfig {
key: key.trim().to_string(),
icon: Some(icon.trim().to_string()),
},
_ => PinnedPropertyConfig {
key: s.trim().to_string(),
icon: None,
},
}
}
/// Extract `_pinned_properties` from raw YAML frontmatter.
pub(crate) fn extract_pinned_properties(
data: &HashMap<String, serde_json::Value>,
) -> Vec<PinnedPropertyConfig> {
let value = match data.get("_pinned_properties") {
Some(v) => v,
None => return Vec::new(),
};
match value {
serde_json::Value::Array(arr) => arr
.iter()
.filter_map(|v| v.as_str())
.map(parse_pinned_entry)
.collect(),
_ => Vec::new(),
}
}
/// Convert gray_matter::Pod to serde_json::Value
fn pod_to_json(pod: gray_matter::Pod) -> serde_json::Value {
match pod {
@@ -284,17 +323,25 @@ fn pod_to_json(pod: gray_matter::Pod) -> serde_json::Value {
}
}
/// Extract frontmatter, relationships, and custom properties from parsed gray_matter data.
/// Extract frontmatter, relationships, custom properties, and pinned-property config.
pub(crate) fn extract_fm_and_rels(
data: Option<gray_matter::Pod>,
) -> (
Frontmatter,
HashMap<String, Vec<String>>,
HashMap<String, serde_json::Value>,
Vec<PinnedPropertyConfig>,
) {
let hash = match data {
Some(gray_matter::Pod::Hash(map)) => map,
_ => return (Frontmatter::default(), HashMap::new(), HashMap::new()),
_ => {
return (
Frontmatter::default(),
HashMap::new(),
HashMap::new(),
Vec::new(),
)
}
};
let json_map: HashMap<String, serde_json::Value> =
hash.into_iter().map(|(k, v)| (k, pod_to_json(v))).collect();
@@ -302,5 +349,71 @@ pub(crate) fn extract_fm_and_rels(
parse_frontmatter(&json_map),
extract_relationships(&json_map),
extract_properties(&json_map),
extract_pinned_properties(&json_map),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_pinned_entry_with_icon() {
let result = parse_pinned_entry("Status:circle-dot");
assert_eq!(result.key, "Status");
assert_eq!(result.icon, Some("circle-dot".to_string()));
}
#[test]
fn test_parse_pinned_entry_without_icon() {
let result = parse_pinned_entry("Priority");
assert_eq!(result.key, "Priority");
assert_eq!(result.icon, None);
}
#[test]
fn test_extract_pinned_properties_array() {
let mut data = HashMap::new();
data.insert(
"_pinned_properties".to_string(),
serde_json::json!(["Status:circle-dot", "Belongs to:arrow-up-right", "date"]),
);
let result = extract_pinned_properties(&data);
assert_eq!(result.len(), 3);
assert_eq!(result[0].key, "Status");
assert_eq!(result[0].icon, Some("circle-dot".to_string()));
assert_eq!(result[1].key, "Belongs to");
assert_eq!(result[1].icon, Some("arrow-up-right".to_string()));
assert_eq!(result[2].key, "date");
assert_eq!(result[2].icon, None);
}
#[test]
fn test_extract_pinned_properties_missing() {
let data = HashMap::new();
assert!(extract_pinned_properties(&data).is_empty());
}
#[test]
fn test_underscore_keys_excluded_from_properties() {
let mut data = HashMap::new();
data.insert("_pinned_properties".to_string(), serde_json::json!(["a:b"]));
data.insert("_hidden".to_string(), serde_json::json!("secret"));
data.insert("visible_key".to_string(), serde_json::json!("value"));
let props = extract_properties(&data);
assert!(!props.contains_key("_pinned_properties"));
assert!(!props.contains_key("_hidden"));
assert!(props.contains_key("visible_key"));
}
#[test]
fn test_underscore_keys_excluded_from_relationships() {
let mut data = HashMap::new();
data.insert("_refs".to_string(), serde_json::json!("[[note]]"));
data.insert("Topics".to_string(), serde_json::json!("[[topic]]"));
let rels = extract_relationships(&data);
assert!(!rels.contains_key("_refs"));
assert!(rels.contains_key("Topics"));
}
}

View File

@@ -43,7 +43,8 @@ pub fn parse_md_file(path: &Path) -> Result<VaultEntry, String> {
let matter = Matter::<YAML>::new();
let parsed = matter.parse(&content);
let (frontmatter, mut relationships, properties) = extract_fm_and_rels(parsed.data);
let (frontmatter, mut relationships, properties, pinned_properties) =
extract_fm_and_rels(parsed.data);
let title = extract_title(frontmatter.title.as_deref(), &content, &filename);
let snippet = extract_snippet(&content);
@@ -103,6 +104,7 @@ pub fn parse_md_file(path: &Path) -> Result<VaultEntry, String> {
word_count,
outgoing_links,
properties,
pinned_properties,
})
}

View File

@@ -14,8 +14,8 @@ import { RawEditorView } from './RawEditorView'
import { countWords } from '../utils/wikilinks'
import { SingleEditorView } from './SingleEditorView'
import { isEmoji } from '../utils/emoji'
import { PinnedPropertiesBar } from './PinnedPropertiesBar'
import { parseFrontmatter } from '../utils/frontmatter'
import { PinnedPropertiesBar } from './PinnedPropertiesBar'
interface Tab {
entry: VaultEntry
@@ -63,6 +63,7 @@ interface EditorContentProps {
onKeepMine?: (path: string) => void
/** Resolve conflict by keeping the remote version. */
onKeepTheirs?: (path: string) => void
/** Update a frontmatter property (for inline pinned-property editing). */
onUpdateFrontmatter?: (path: string, key: string, value: FrontmatterValue) => Promise<void>
}

View File

@@ -47,6 +47,7 @@ export function frontmatterToEntryPatch(
const relPatch: RelationshipPatch = { [key]: null }
return { patch: ENTRY_DELETE_MAP[k] ?? {}, relationshipPatch: relPatch }
}
// Handle _pinned_properties for Type entries
if (k === '_pinned_properties' && Array.isArray(value)) {
const pinned = parsePinnedConfig(value.map(String))

View File

@@ -19,7 +19,7 @@ export function buildNewEntry({ path, slug, title, type, status }: NewEntryParam
aliases: [], belongsTo: [], relatedTo: [],
status, archived: false, trashed: false, trashedAt: null,
modifiedAt: now, createdAt: now, fileSize: 0,
snippet: '', wordCount: 0, relationships: {}, icon: null, color: null, order: null, outgoingLinks: [], sidebarLabel: null, template: null, sort: null, view: null, visible: null, properties: {},
snippet: '', wordCount: 0, relationships: {}, icon: null, color: null, order: null, outgoingLinks: [], sidebarLabel: null, template: null, sort: null, view: null, visible: null, properties: {}, pinnedProperties: [],
}
}

View File

@@ -68,6 +68,29 @@ function formatLabel(key: string): string {
return key.replace(/_/g, ' ')
}
/** Resolve a single pinned config to its display representation. */
function resolveOne(
entry: VaultEntry, frontmatter: ParsedFrontmatter, cfg: PinnedPropertyConfig,
): ResolvedPinnedProperty {
const { value, isRelationship } = resolveValue(entry, frontmatter, cfg.key)
return { key: cfg.key, icon: resolvePinIcon(cfg.key, cfg.icon), label: formatLabel(cfg.key), value, isRelationship }
}
/** Find the Type entry for a note's isA field. */
function findTypeEntry(entries: VaultEntry[], isA: string | null): VaultEntry | null {
if (!isA) return null
return entries.find((e) => e.isA === 'Type' && e.title === isA) ?? null
}
/** Determine which properties are pinned for a given type. */
function resolvePinnedConfigs(
isA: string | null, typeEntry: VaultEntry | null, entries: VaultEntry[],
): PinnedPropertyConfig[] {
if (!isA) return []
if (typeEntry && typeEntry.pinnedProperties.length > 0) return typeEntry.pinnedProperties
return computeDefaults(entries, isA)
}
export interface UsePinnedPropertiesResult {
pinnedConfigs: PinnedPropertyConfig[]
resolved: ResolvedPinnedProperty[]
@@ -78,53 +101,31 @@ export interface UsePinnedPropertiesResult {
isPinned: (key: string) => boolean
}
/** Hook to read and mutate pinned properties for a note based on its type. */
export function usePinnedProperties({
entry,
entries,
frontmatter,
onUpdateTypeFrontmatter,
entry, entries, frontmatter, onUpdateTypeFrontmatter,
}: {
entry: VaultEntry | null
entries: VaultEntry[]
frontmatter: ParsedFrontmatter
onUpdateTypeFrontmatter?: (typePath: string, key: string, value: FrontmatterValue) => Promise<void>
}): UsePinnedPropertiesResult {
// eslint-disable-next-line react-hooks/preserve-manual-memoization -- type lookup intentionally memoized
const typeEntry = useMemo(() => {
if (!entry?.isA) return null
return entries.find((e) => e.isA === 'Type' && e.title === entry.isA) ?? null
}, [entry?.isA, entries])
// eslint-disable-next-line react-hooks/preserve-manual-memoization -- pin config intentionally memoized
const pinnedConfigs = useMemo((): PinnedPropertyConfig[] => {
if (!entry?.isA) return []
if (typeEntry && typeEntry.pinnedProperties.length > 0) return typeEntry.pinnedProperties
return computeDefaults(entries, entry.isA)
}, [entry?.isA, typeEntry, entries])
const resolved = useMemo((): ResolvedPinnedProperty[] => {
if (!entry) return []
return pinnedConfigs.map((cfg) => {
const { value, isRelationship } = resolveValue(entry, frontmatter, cfg.key)
return {
key: cfg.key,
icon: resolvePinIcon(cfg.key, cfg.icon),
label: formatLabel(cfg.key),
value,
isRelationship,
}
})
}, [entry, pinnedConfigs, frontmatter])
const typeEntry = useMemo(() => findTypeEntry(entries, entry?.isA ?? null), [entry?.isA, entries])
const pinnedConfigs = useMemo(
() => resolvePinnedConfigs(entry?.isA ?? null, typeEntry, entries),
[entry?.isA, typeEntry, entries],
)
const resolved = useMemo(
() => (entry ? pinnedConfigs.map((cfg) => resolveOne(entry, frontmatter, cfg)) : []),
[entry, pinnedConfigs, frontmatter],
)
const savePins = useCallback(
(newConfigs: PinnedPropertyConfig[]) => {
if (!typeEntry || !onUpdateTypeFrontmatter) return
const serialised = serialisePinnedConfig(newConfigs)
onUpdateTypeFrontmatter(typeEntry.path, '_pinned_properties', serialised)
onUpdateTypeFrontmatter(typeEntry.path, '_pinned_properties', serialisePinnedConfig(newConfigs))
},
[typeEntry, onUpdateTypeFrontmatter],
)
const pinProperty = useCallback(
(key: string, icon?: string) => {
if (pinnedConfigs.some((c) => c.key === key)) return
@@ -132,26 +133,16 @@ export function usePinnedProperties({
},
[pinnedConfigs, savePins],
)
const unpinProperty = useCallback(
(key: string) => savePins(pinnedConfigs.filter((c) => c.key !== key)),
[pinnedConfigs, savePins],
)
const updatePinIcon = useCallback(
(key: string, icon: string) => savePins(pinnedConfigs.map((c) => (c.key === key ? { ...c, icon } : c))),
[pinnedConfigs, savePins],
)
const reorderPins = useCallback(
(configs: PinnedPropertyConfig[]) => savePins(configs),
[savePins],
)
const isPinned = useCallback(
(key: string) => pinnedConfigs.some((c) => c.key === key),
[pinnedConfigs],
)
const reorderPins = useCallback((configs: PinnedPropertyConfig[]) => savePins(configs), [savePins])
const isPinned = useCallback((key: string) => pinnedConfigs.some((c) => c.key === key), [pinnedConfigs])
return { pinnedConfigs, resolved, pinProperty, unpinProperty, updatePinIcon, reorderPins, isPinned }
}

View File

@@ -80,6 +80,7 @@ function collectAllVaultTags(entries: VaultEntry[] | undefined): Record<string,
}
function isVisibleProperty([key, value]: [string, FrontmatterValue]): boolean {
if (key.startsWith('_')) return false
return !SKIP_KEYS.has(key.toLowerCase()) && !containsWikilinks(value)
}

View File

@@ -37,6 +37,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: ['q1-2026', 'software-development', 'matteo-cellini', 'maria-bianchi', 'marco-verdi'],
properties: { Priority: 'High', 'Due date': '2026-06-15', Owner: 'Luca Rossi' },
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/grow-newsletter.md',
@@ -72,6 +73,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: ['on-writing-well', 'engineering-leadership-101', 'ai-agents-primer', 'growth', 'writing'],
properties: { Priority: 'High', Rating: 5, Cadence: 'Weekly', Owner: 'Luca Rossi' },
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/manage-sponsorships.md',
@@ -101,6 +103,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: ['matteo-cellini'],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/write-weekly-essays.md',
@@ -130,6 +133,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: ['grow-newsletter'],
properties: { Owner: 'Luca Rossi', Cadence: 'Weekly' },
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/run-sponsorships.md',
@@ -159,6 +163,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: ['manage-sponsorships'],
properties: { Owner: 'Matteo Cellini', Cadence: 'Weekly' },
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/stock-screener.md',
@@ -189,6 +194,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: ['trading', 'algorithmic-trading', 'ema200-backtest-results'],
properties: { Priority: 'Low', 'Due date': '2026-03-01', Owner: 'Luca Rossi' },
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/facebook-ads-strategy.md',
@@ -219,6 +225,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: ['26q1-laputa-app', 'growth', 'ads'],
properties: { Priority: 'Medium', Rating: 4 },
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/budget-allocation.md',
@@ -248,6 +255,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: ['26q1-laputa-app'],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/matteo-cellini.md',
@@ -276,6 +284,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: { Company: 'Acme Corp', Role: 'Engineering Lead' },
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/maria-bianchi.md',
@@ -304,6 +313,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: { Company: 'TechStart', Role: 'Product Manager' },
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/marco-verdi.md',
@@ -332,6 +342,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/elena-russo.md',
@@ -360,6 +371,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/2026-02-14-laputa-app-kickoff.md',
@@ -389,6 +401,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: ['26q1-laputa-app', 'matteo-cellini'],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/software-development.md',
@@ -418,6 +431,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/trading.md',
@@ -447,6 +461,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/on-writing-well.md',
@@ -476,6 +491,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: ['grow-newsletter'],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/engineering-leadership-101.md',
@@ -506,6 +522,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: ['grow-newsletter', 'software-development'],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/ai-agents-primer.md',
@@ -535,6 +552,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: ['grow-newsletter'],
properties: {},
pinnedProperties: [],
},
// --- Type documents ---
{
@@ -562,6 +580,11 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [
{ key: 'Status', icon: 'circle-dot' },
{ key: 'Belongs to', icon: 'arrow-up-right' },
{ key: 'Due date', icon: 'calendar' },
],
},
{
path: '/Users/luca/Laputa/responsibility.md',
@@ -588,6 +611,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/procedure.md',
@@ -614,6 +638,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/experiment.md',
@@ -640,6 +665,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: false,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/person.md',
@@ -666,6 +692,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/event.md',
@@ -692,6 +719,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/topic.md',
@@ -718,6 +746,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/essay.md',
@@ -744,6 +773,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/note.md',
@@ -770,6 +800,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
// --- Custom type documents ---
{
@@ -797,6 +828,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/recipe.md',
@@ -823,6 +855,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/book.md',
@@ -849,6 +882,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
// --- Instances of custom types ---
{
@@ -878,6 +912,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/pasta-carbonara.md',
@@ -906,6 +941,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: { Difficulty: 'Easy', 'Prep time': '30 min', Servings: 4 },
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/designing-data-intensive-applications.md',
@@ -934,6 +970,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: { Author: 'Martin Kleppmann', Rating: 5, 'Year published': 2017 },
pinnedProperties: [],
},
// --- Trashed entries ---
{
@@ -964,6 +1001,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/deprecated-api-notes.md',
@@ -992,6 +1030,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/failed-seo-experiment.md',
@@ -1021,6 +1060,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: { Owner: 'Luca Rossi' },
pinnedProperties: [],
},
// --- Archived entries ---
{
@@ -1042,6 +1082,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: { Owner: 'Luca Rossi' },
pinnedProperties: [],
modifiedAt: now - 86400 * 120,
createdAt: now - 86400 * 200,
fileSize: 680,
@@ -1071,6 +1112,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: { Owner: 'Luca Rossi' },
pinnedProperties: [],
modifiedAt: now - 86400 * 90,
createdAt: now - 86400 * 150,
fileSize: 520,
@@ -1107,6 +1149,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/refactoring-ideas.md',
@@ -1133,6 +1176,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/refactoring-key-ideas.md',
@@ -1159,6 +1203,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
{
path: '/Users/luca/Laputa/refactoring-patterns.md',
@@ -1185,6 +1230,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
template: null, sort: null, view: null, visible: null,
outgoingLinks: [],
properties: {},
pinnedProperties: [],
},
]
@@ -1237,6 +1283,7 @@ function generateBulkEntries(count: number): VaultEntry[] {
sidebarLabel: null,
template: null, sort: null, view: null, visible: null,
properties: {},
pinnedProperties: [],
})
}
return entries

View File

@@ -132,7 +132,7 @@ const trimOrNull = (v: string | null | undefined): string | null => v?.trim() ||
export const mockHandlers: Record<string, (args: any) => any> = {
list_vault: () => MOCK_ENTRIES,
reload_vault: () => MOCK_ENTRIES,
reload_vault_entry: (args: { path: string }) => MOCK_ENTRIES.find(e => e.path === args.path) ?? { path: args.path, title: 'Unknown', filename: 'unknown.md', aliases: [], belongsTo: [], relatedTo: [], archived: false, trashed: false, snippet: '', wordCount: 0, fileSize: 0, relationships: {}, outgoingLinks: [], properties: {} },
reload_vault_entry: (args: { path: string }) => MOCK_ENTRIES.find(e => e.path === args.path) ?? { path: args.path, title: 'Unknown', filename: 'unknown.md', aliases: [], belongsTo: [], relatedTo: [], archived: false, trashed: false, snippet: '', wordCount: 0, fileSize: 0, relationships: {}, outgoingLinks: [], properties: {}, pinnedProperties: [] },
sync_note_title: () => false,
get_note_content: (args: { path: string }) => MOCK_CONTENT[args.path] ?? '',
get_all_content: () => MOCK_CONTENT,

View File

@@ -1,3 +1,9 @@
/** A single pinned-property config item from a Type's `_pinned_properties` list. */
export interface PinnedPropertyConfig {
key: string
icon: string | null
}
export interface VaultEntry {
path: string
filename: string
@@ -39,6 +45,8 @@ export interface VaultEntry {
outgoingLinks: string[]
/** Custom scalar frontmatter properties (non-relationship, non-structural). */
properties: Record<string, string | number | boolean | null>
/** Pinned properties config for Type entries. Parsed from `_pinned_properties` frontmatter. */
pinnedProperties: PinnedPropertyConfig[]
}
export type NoteStatus = 'new' | 'modified' | 'clean' | 'pendingSave' | 'unsaved'