diff --git a/src-tauri/src/vault/mod.rs b/src-tauri/src/vault/mod.rs index 01a391a7..f7ad1849 100644 --- a/src-tauri/src/vault/mod.rs +++ b/src-tauri/src/vault/mod.rs @@ -74,7 +74,7 @@ pub struct VaultEntry { /// Intermediate struct to capture YAML frontmatter fields. #[derive(Debug, Deserialize, Default)] struct Frontmatter { - #[serde(rename = "Is A")] + #[serde(rename = "Is A", alias = "type")] is_a: Option, #[serde(default)] aliases: Option, @@ -135,6 +135,7 @@ fn parse_frontmatter(data: &HashMap) -> Frontmatter { /// Only skip keys that can never contain wikilinks. const SKIP_KEYS: &[&str] = &[ "is a", + "type", "aliases", "status", "cadence", @@ -968,6 +969,39 @@ References: assert_eq!(entry.is_a, Some("Type".to_string())); } + // --- type key (post-migration) tests --- + + #[test] + fn test_parse_type_key_lowercase() { + let dir = TempDir::new().unwrap(); + let content = "---\ntype: Project\n---\n# My Project\n"; + let entry = parse_test_entry(&dir, "project/my-project.md", content); + assert_eq!(entry.is_a, Some("Project".to_string())); + } + + #[test] + fn test_type_key_generates_type_relationship() { + let dir = TempDir::new().unwrap(); + let content = "---\ntype: Person\n---\n# Alice\n"; + let entry = parse_test_entry(&dir, "person/alice.md", content); + assert_eq!( + entry.relationships.get("Type").unwrap(), + &vec!["[[type/person]]".to_string()] + ); + } + + #[test] + fn test_type_key_not_in_relationships_as_generic() { + let dir = TempDir::new().unwrap(); + let content = "---\ntype: Note\nHas:\n - \"[[task/foo]]\"\n---\n# Test\n"; + let entry = parse_test_entry(&dir, "note/test.md", content); + // "type" key itself should not appear as a relationship (it's in SKIP_KEYS) + // Only "Has" and the auto-generated "Type" should be relationships + assert_eq!(entry.relationships.len(), 2); + assert!(entry.relationships.get("Has").is_some()); + assert!(entry.relationships.get("Type").is_some()); + } + // --- outgoing_links tests --- #[test] diff --git a/src/components/DynamicPropertiesPanel.tsx b/src/components/DynamicPropertiesPanel.tsx index 4ff7bfe5..54c64d85 100644 --- a/src/components/DynamicPropertiesPanel.tsx +++ b/src/components/DynamicPropertiesPanel.tsx @@ -12,6 +12,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover import { Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, SelectValue } from '@/components/ui/select' import { CalendarIcon, XIcon, Check, X, Type, ToggleLeft, Circle, Link, Tag } from 'lucide-react' import { getTypeColor, getTypeLightColor } from '../utils/typeColors' +import { getTypeIcon } from './NoteItem' import { countWords } from '../utils/wikilinks' import { type PropertyDisplayMode, @@ -482,9 +483,24 @@ function ReadOnlyType({ isA, customColorKey, onNavigate }: { isA?: string | null ) } -function TypeSelector({ isA, customColorKey, availableTypes, typeColorKeys, onUpdateProperty, onNavigate }: { +function TypeSelectorItem({ type, typeColorKeys, typeIconKeys }: { + type: string; typeColorKeys: Record; typeIconKeys: Record +}) { + const Icon = getTypeIcon(type, typeIconKeys[type]) + const color = getTypeColor(type, typeColorKeys[type]) + return ( + <> + {/* eslint-disable-next-line react-hooks/static-components -- icon from static map lookup */} + + {type} + + ) +} + +function TypeSelector({ isA, customColorKey, availableTypes, typeColorKeys, typeIconKeys, onUpdateProperty, onNavigate }: { isA?: string | null; customColorKey?: string | null; availableTypes: string[] typeColorKeys: Record + typeIconKeys: Record onUpdateProperty?: (key: string, value: FrontmatterValue) => void onNavigate?: (target: string) => void }) { @@ -511,8 +527,7 @@ function TypeSelector({ isA, customColorKey, availableTypes, typeColorKeys, onUp {options.map(type => ( - - {type} + ))} @@ -642,11 +657,16 @@ function reconcileListUpdate( function deriveTypeInfo(entries: VaultEntry[] | undefined, entryIsA: string | null) { const typeEntries = (entries ?? []).filter(e => e.isA === 'Type') const typeColorKeys: Record = {} - for (const e of typeEntries) { typeColorKeys[e.title] = e.color ?? null } + const typeIconKeys: Record = {} + for (const e of typeEntries) { + typeColorKeys[e.title] = e.color ?? null + typeIconKeys[e.title] = e.icon ?? null + } return { availableTypes: typeEntries.map(e => e.title).sort((a, b) => a.localeCompare(b)), customColorKey: entryIsA ? (typeColorKeys[entryIsA] ?? null) : null, typeColorKeys, + typeIconKeys, } } @@ -714,7 +734,7 @@ function usePropertyPanelState(deps: PropertyPanelDeps) { const [showAddDialog, setShowAddDialog] = useState(false) const [displayOverrides, setDisplayOverrides] = useState(() => loadDisplayModeOverrides()) - const { availableTypes, customColorKey, typeColorKeys } = useMemo(() => deriveTypeInfo(entries, entryIsA), [entries, entryIsA]) + const { availableTypes, customColorKey, typeColorKeys, typeIconKeys } = useMemo(() => deriveTypeInfo(entries, entryIsA), [entries, entryIsA]) const vaultStatuses = useMemo(() => collectVaultStatuses(entries), [entries]) const vaultTagsByKey = useMemo(() => collectAllVaultTags(entries, allContent), [entries, allContent]) const propertyEntries = useMemo(() => Object.entries(frontmatter).filter(isVisibleProperty), [frontmatter]) @@ -746,7 +766,7 @@ function usePropertyPanelState(deps: PropertyPanelDeps) { return { editingKey, setEditingKey, showAddDialog, setShowAddDialog, displayOverrides, - availableTypes, customColorKey, typeColorKeys, vaultStatuses, vaultTagsByKey, propertyEntries, + availableTypes, customColorKey, typeColorKeys, typeIconKeys, vaultStatuses, vaultTagsByKey, propertyEntries, handleSaveValue, handleSaveList, handleAdd, handleDisplayModeChange, } } @@ -767,7 +787,7 @@ export function DynamicPropertiesPanel({ }) { const { editingKey, setEditingKey, showAddDialog, setShowAddDialog, displayOverrides, - availableTypes, customColorKey, typeColorKeys, vaultStatuses, vaultTagsByKey, propertyEntries, + availableTypes, customColorKey, typeColorKeys, typeIconKeys, vaultStatuses, vaultTagsByKey, propertyEntries, handleSaveValue, handleSaveList, handleAdd, handleDisplayModeChange, } = usePropertyPanelState({ entries, entryIsA: entry.isA, frontmatter, allContent, onUpdateProperty, onDeleteProperty, onAddProperty }) @@ -776,7 +796,7 @@ export function DynamicPropertiesPanel({ return (
- + {propertyEntries.map(([key, value]) => (