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:
@@ -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 {
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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: [],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user