feat: normalize system properties to _archived, _trashed, _trashed_at
Write operations now use underscore-prefixed canonical keys (_archived, _trashed, _trashed_at). Read operations accept both old keys (Archived, archived, Trashed, trashed, Trashed at, trashed_at) and new keys via serde aliases, ensuring backward compatibility without migration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,9 @@ const ENTRY_DELETE_MAP: Record<string, Partial<VaultEntry>> = {
|
||||
type: { isA: null }, is_a: { isA: null }, status: { status: null }, color: { color: null },
|
||||
icon: { icon: null },
|
||||
aliases: { aliases: [] }, belongs_to: { belongsTo: [] }, related_to: { relatedTo: [] },
|
||||
archived: { archived: false }, trashed: { trashed: false }, order: { order: null },
|
||||
_archived: { archived: false }, archived: { archived: false },
|
||||
_trashed: { trashed: false }, trashed: { trashed: false },
|
||||
order: { order: null },
|
||||
template: { template: null }, sort: { sort: null }, visible: { visible: null },
|
||||
_favorite: { favorite: false }, _favorite_index: { favoriteIndex: null },
|
||||
}
|
||||
@@ -53,7 +55,8 @@ export function frontmatterToEntryPatch(
|
||||
type: { isA: str }, is_a: { isA: str }, status: { status: str }, color: { color: str },
|
||||
icon: { icon: str },
|
||||
aliases: { aliases: arr }, belongs_to: { belongsTo: arr }, related_to: { relatedTo: arr },
|
||||
archived: { archived: Boolean(value) }, trashed: { trashed: Boolean(value) },
|
||||
_archived: { archived: Boolean(value) }, archived: { archived: Boolean(value) },
|
||||
_trashed: { trashed: Boolean(value) }, trashed: { trashed: Boolean(value) },
|
||||
order: { order: typeof value === 'number' ? value : null },
|
||||
template: { template: str },
|
||||
sort: { sort: str },
|
||||
|
||||
@@ -67,8 +67,8 @@ describe('useEntryActions', () => {
|
||||
await result.current.handleTrashNote('/vault/note/test.md')
|
||||
})
|
||||
|
||||
expect(handleUpdateFrontmatter).toHaveBeenCalledWith('/vault/note/test.md', 'Trashed', true, { silent: true })
|
||||
expect(handleUpdateFrontmatter).toHaveBeenCalledWith('/vault/note/test.md', 'Trashed at', expect.stringMatching(/^\d{4}-\d{2}-\d{2}$/), { silent: true })
|
||||
expect(handleUpdateFrontmatter).toHaveBeenCalledWith('/vault/note/test.md', '_trashed', true, { silent: true })
|
||||
expect(handleUpdateFrontmatter).toHaveBeenCalledWith('/vault/note/test.md', '_trashed_at', expect.stringMatching(/^\d{4}-\d{2}-\d{2}$/), { silent: true })
|
||||
expect(updateEntry).toHaveBeenCalledWith('/vault/note/test.md', {
|
||||
trashed: true,
|
||||
trashedAt: expect.any(Number),
|
||||
@@ -99,8 +99,8 @@ describe('useEntryActions', () => {
|
||||
await result.current.handleRestoreNote('/vault/note/test.md')
|
||||
})
|
||||
|
||||
expect(handleDeleteProperty).toHaveBeenCalledWith('/vault/note/test.md', 'Trashed', { silent: true })
|
||||
expect(handleDeleteProperty).toHaveBeenCalledWith('/vault/note/test.md', 'Trashed at', { silent: true })
|
||||
expect(handleDeleteProperty).toHaveBeenCalledWith('/vault/note/test.md', '_trashed', { silent: true })
|
||||
expect(handleDeleteProperty).toHaveBeenCalledWith('/vault/note/test.md', '_trashed_at', { silent: true })
|
||||
expect(handleUpdateFrontmatter).not.toHaveBeenCalled()
|
||||
expect(updateEntry).toHaveBeenCalledWith('/vault/note/test.md', {
|
||||
trashed: false,
|
||||
@@ -119,7 +119,7 @@ describe('useEntryActions', () => {
|
||||
await result.current.handleArchiveNote('/vault/note/test.md')
|
||||
})
|
||||
|
||||
expect(handleUpdateFrontmatter).toHaveBeenCalledWith('/vault/note/test.md', 'archived', true, { silent: true })
|
||||
expect(handleUpdateFrontmatter).toHaveBeenCalledWith('/vault/note/test.md', '_archived', true, { silent: true })
|
||||
expect(updateEntry).toHaveBeenCalledWith('/vault/note/test.md', { archived: true })
|
||||
expect(setToastMessage).toHaveBeenCalledWith('Note archived')
|
||||
expect(onFrontmatterPersisted).toHaveBeenCalledTimes(1)
|
||||
@@ -146,7 +146,7 @@ describe('useEntryActions', () => {
|
||||
await result.current.handleUnarchiveNote('/vault/note/test.md')
|
||||
})
|
||||
|
||||
expect(handleDeleteProperty).toHaveBeenCalledWith('/vault/note/test.md', 'archived', { silent: true })
|
||||
expect(handleDeleteProperty).toHaveBeenCalledWith('/vault/note/test.md', '_archived', { silent: true })
|
||||
expect(handleUpdateFrontmatter).not.toHaveBeenCalled()
|
||||
expect(updateEntry).toHaveBeenCalledWith('/vault/note/test.md', { archived: false })
|
||||
expect(setToastMessage).toHaveBeenCalledWith('Note unarchived')
|
||||
|
||||
@@ -35,8 +35,8 @@ export function useEntryActions({
|
||||
setToastMessage('Note moved to trash')
|
||||
const now = new Date().toISOString().slice(0, 10)
|
||||
try {
|
||||
await handleUpdateFrontmatter(path, 'Trashed', true, { silent: true })
|
||||
await handleUpdateFrontmatter(path, 'Trashed at', now, { silent: true })
|
||||
await handleUpdateFrontmatter(path, '_trashed', true, { silent: true })
|
||||
await handleUpdateFrontmatter(path, '_trashed_at', now, { silent: true })
|
||||
onFrontmatterPersisted?.()
|
||||
} catch (err) {
|
||||
updateEntry(path, { trashed: false, trashedAt: null })
|
||||
@@ -50,8 +50,8 @@ export function useEntryActions({
|
||||
updateEntry(path, { trashed: false, trashedAt: null })
|
||||
setToastMessage('Note restored from trash')
|
||||
try {
|
||||
await handleDeleteProperty(path, 'Trashed', { silent: true })
|
||||
await handleDeleteProperty(path, 'Trashed at', { silent: true })
|
||||
await handleDeleteProperty(path, '_trashed', { silent: true })
|
||||
await handleDeleteProperty(path, '_trashed_at', { silent: true })
|
||||
onFrontmatterPersisted?.()
|
||||
} catch (err) {
|
||||
updateEntry(path, { trashed: true, trashedAt: Date.now() / 1000 })
|
||||
@@ -66,7 +66,7 @@ export function useEntryActions({
|
||||
updateEntry(path, { archived: true })
|
||||
setToastMessage('Note archived')
|
||||
try {
|
||||
await handleUpdateFrontmatter(path, 'archived', true, { silent: true })
|
||||
await handleUpdateFrontmatter(path, '_archived', true, { silent: true })
|
||||
onFrontmatterPersisted?.()
|
||||
} catch (err) {
|
||||
updateEntry(path, { archived: false })
|
||||
@@ -80,7 +80,7 @@ export function useEntryActions({
|
||||
updateEntry(path, { archived: false })
|
||||
setToastMessage('Note unarchived')
|
||||
try {
|
||||
await handleDeleteProperty(path, 'archived', { silent: true })
|
||||
await handleDeleteProperty(path, '_archived', { silent: true })
|
||||
onFrontmatterPersisted?.()
|
||||
} catch (err) {
|
||||
updateEntry(path, { archived: true })
|
||||
|
||||
@@ -12,7 +12,7 @@ import { containsWikilinks } from '../components/DynamicPropertiesPanel'
|
||||
|
||||
// Keys to skip showing in Properties (handled by dedicated UI or internal)
|
||||
// Compared case-insensitively via isVisibleProperty()
|
||||
const SKIP_KEYS = new Set(['aliases', 'workspace', 'title', 'type', 'is_a', 'is a', 'trashed', 'trashed_at', 'archived', 'archived_at', 'icon'])
|
||||
const SKIP_KEYS = new Set(['aliases', 'workspace', 'title', 'type', 'is_a', 'is a', '_trashed', 'trashed', '_trashed_at', 'trashed_at', 'trashed at', '_archived', 'archived', 'archived_at', 'icon'])
|
||||
|
||||
function coerceValue(raw: string): FrontmatterValue {
|
||||
if (raw.toLowerCase() === 'true') return true
|
||||
|
||||
Reference in New Issue
Block a user