From ce78abfd572d28ecf950ea0db897584002581a37 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Fri, 17 Apr 2026 15:33:17 +0200 Subject: [PATCH] Restore icon property in inspector --- ...micPropertiesPanel.systemMetadata.test.tsx | 20 +++++++++++++++-- .../DynamicPropertiesPanel.test.tsx | 13 +++++------ src/components/PropertyValueCells.tsx | 5 +++-- src/hooks/usePropertyPanelState.ts | 22 +++++++++++++++++-- src/utils/propertyLabels.ts | 3 ++- src/utils/propertyTypes.ts | 3 ++- 6 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/components/DynamicPropertiesPanel.systemMetadata.test.tsx b/src/components/DynamicPropertiesPanel.systemMetadata.test.tsx index 6e1d9d74..589a901d 100644 --- a/src/components/DynamicPropertiesPanel.systemMetadata.test.tsx +++ b/src/components/DynamicPropertiesPanel.systemMetadata.test.tsx @@ -67,7 +67,7 @@ describe('DynamicPropertiesPanel system metadata', () => { vi.clearAllMocks() }) - it('hides underscored and legacy system metadata while keeping user properties visible', () => { + it('keeps the icon visible while hiding the other system metadata', () => { render( { expect(screen.getByText('Owner')).toBeInTheDocument() expect(screen.getByText('Luca')).toBeInTheDocument() - expect(screen.queryByText('Icon')).not.toBeInTheDocument() + expect(screen.getByText('Icon')).toBeInTheDocument() + expect(screen.getByTestId('icon-editable-display')).toHaveTextContent('rocket') + expect(screen.queryByDisplayValue('legacy')).not.toBeInTheDocument() expect(screen.queryByText('Order')).not.toBeInTheDocument() expect(screen.queryByText('Sort')).not.toBeInTheDocument() expect(screen.queryByText('Sidebar label')).not.toBeInTheDocument() @@ -130,4 +132,18 @@ describe('DynamicPropertiesPanel system metadata', () => { expect(onAddProperty).toHaveBeenCalledWith('_icon', 'rocket') }) + + it('renders an existing underscored icon property with the icon picker UI', () => { + render( + , + ) + + expect(screen.getByText('Icon')).toBeInTheDocument() + expect(screen.getByTestId('icon-editable-display')).toHaveTextContent('megaphone') + }) }) diff --git a/src/components/DynamicPropertiesPanel.test.tsx b/src/components/DynamicPropertiesPanel.test.tsx index 47794f9d..87bccc7f 100644 --- a/src/components/DynamicPropertiesPanel.test.tsx +++ b/src/components/DynamicPropertiesPanel.test.tsx @@ -697,21 +697,20 @@ describe('DynamicPropertiesPanel', () => { }) describe('system property filtering', () => { - it('hides archived, archived_at, and legacy icon metadata from the properties panel', () => { + it('hides archived metadata but keeps the note icon visible in the properties panel', () => { renderEditablePanel({ archived: false, archived_at: '', icon: '📝', cadence: 'Weekly' }) expect(screen.queryByText('Archived')).not.toBeInTheDocument() expect(screen.queryByText('Archived at')).not.toBeInTheDocument() - expect(screen.queryByText('Icon')).not.toBeInTheDocument() - expect(screen.queryByText('📝')).not.toBeInTheDocument() - // Custom property still visible + expect(screen.getByText('Icon')).toBeInTheDocument() + expect(screen.getByText('📝')).toBeInTheDocument() expect(screen.getByText('Cadence')).toBeInTheDocument() }) - it('hides legacy icon metadata even when cased differently', () => { + it('keeps the note icon visible even when cased differently', () => { renderEditablePanel({ Archived: false, Icon: '🎯', cadence: 'Daily' }) expect(screen.queryByText('Archived')).not.toBeInTheDocument() - expect(screen.queryByText('Icon')).not.toBeInTheDocument() - expect(screen.queryByText('🎯')).not.toBeInTheDocument() + expect(screen.getByText('Icon')).toBeInTheDocument() + expect(screen.getByText('🎯')).toBeInTheDocument() expect(screen.getByText('Cadence')).toBeInTheDocument() }) diff --git a/src/components/PropertyValueCells.tsx b/src/components/PropertyValueCells.tsx index c8e183d5..330d144a 100644 --- a/src/components/PropertyValueCells.tsx +++ b/src/components/PropertyValueCells.tsx @@ -22,6 +22,7 @@ import { getTagStyle } from '../utils/tagStyles' import { ColorEditableValue } from './ColorInput' import { IconEditableValue } from './IconEditableValue' import { PROPERTY_CHIP_STYLE } from './propertyChipStyles' +import { canonicalSystemMetadataKey } from '../utils/systemMetadata' function parseDateValue(value: string): Date | undefined { const iso = toISODate(value) @@ -307,7 +308,7 @@ function toBooleanValue(value: FrontmatterValue): boolean { } function autoDetectFromValue(propKey: string, value: FrontmatterValue): PropertyDisplayMode { - if (propKey.toLowerCase() === 'icon') return 'text' + if (canonicalSystemMetadataKey(propKey) === '_icon') return 'text' if (typeof value === 'boolean') return 'boolean' if (typeof value === 'string' && isUrlValue(value)) return 'url' if (typeof value === 'string' && isValidCssColor(value) && value.startsWith('#')) return 'color' @@ -405,7 +406,7 @@ function ScalarValueCell(props: SmartCellProps) { onSave, }) - if (propKey.toLowerCase() === 'icon') { + if (canonicalSystemMetadataKey(propKey) === '_icon') { return } diff --git a/src/hooks/usePropertyPanelState.ts b/src/hooks/usePropertyPanelState.ts index 9d9b3b5d..6c04d75f 100644 --- a/src/hooks/usePropertyPanelState.ts +++ b/src/hooks/usePropertyPanelState.ts @@ -9,7 +9,7 @@ import { removeDisplayModeOverride, } from '../utils/propertyTypes' import { containsWikilinks } from '../components/DynamicPropertiesPanel' -import { isSystemMetadataKey } from '../utils/systemMetadata' +import { canonicalSystemMetadataKey, isSystemMetadataKey } from '../utils/systemMetadata' // Keys to skip showing in Properties (handled by dedicated UI or internal) // Compared case-insensitively via isVisibleProperty() @@ -79,6 +79,22 @@ function isVisibleProperty([key, value]: [string, FrontmatterValue]): boolean { return !isHiddenPropertyKey(key) && !containsWikilinks(value) } +function buildVisiblePropertyEntries(frontmatter: ParsedFrontmatter): [string, FrontmatterValue][] { + const result: [string, FrontmatterValue][] = [] + const seen = new Set() + + for (const [key, value] of Object.entries(frontmatter)) { + if (!isVisibleProperty([key, value])) continue + + const canonicalKey = canonicalSystemMetadataKey(key) + if (seen.has(canonicalKey)) continue + seen.add(canonicalKey) + result.push([key, value]) + } + + return result +} + function addTagValues(tagsByKey: Map>, key: string, value: unknown) { if (!Array.isArray(value)) return @@ -102,6 +118,8 @@ function toSortedTagRecord(tagsByKey: Map>): Record deriveTypeInfo(entries, entryIsA), [entries, entryIsA]) const vaultStatuses = useMemo(() => collectVaultStatuses(entries), [entries]) const vaultTagsByKey = useMemo(() => collectAllVaultTags(entries), [entries]) - const propertyEntries = useMemo(() => Object.entries(frontmatter).filter(isVisibleProperty), [frontmatter]) + const propertyEntries = useMemo(() => buildVisiblePropertyEntries(frontmatter), [frontmatter]) const handleSaveValue = useCallback((key: string, newValue: string) => { setEditingKey(null) diff --git a/src/utils/propertyLabels.ts b/src/utils/propertyLabels.ts index 664394da..867ebeea 100644 --- a/src/utils/propertyLabels.ts +++ b/src/utils/propertyLabels.ts @@ -1,5 +1,6 @@ export function humanizePropertyKey(key: string): string { - const spaced = key.replace(/[_-]/g, ' ') + const normalized = key.replace(/^_+/, '') + const spaced = normalized.replace(/[_-]/g, ' ') if (!spaced) return spaced return spaced.charAt(0).toUpperCase() + spaced.slice(1) } diff --git a/src/utils/propertyTypes.ts b/src/utils/propertyTypes.ts index 7f011fe9..26e2948f 100644 --- a/src/utils/propertyTypes.ts +++ b/src/utils/propertyTypes.ts @@ -3,6 +3,7 @@ import { getAppStorageItem } from '../constants/appStorage' import { isValidCssColor, isColorKeyName } from './colorUtils' import { updateVaultConfigField } from './vaultConfigStore' import { CalendarIcon, Type, ToggleLeft, Circle, Link, Tag, Palette } from 'lucide-react' +import { canonicalSystemMetadataKey } from './systemMetadata' export type PropertyDisplayMode = 'text' | 'date' | 'boolean' | 'status' | 'url' | 'tags' | 'color' @@ -20,7 +21,7 @@ const DATE_KEY_PATTERNS = ['date', 'deadline', 'due', 'start', 'end', 'scheduled const TAGS_KEY_PATTERNS = ['tags', 'keywords', 'categories', 'labels'] function isIconKey(key: string): boolean { - return key.toLowerCase() === 'icon' + return canonicalSystemMetadataKey(key) === '_icon' } function keyMatchesPatterns(key: string, patterns: string[]): boolean {