feat: title = filename — wikilink resolution + slug collision detection
- Wikilink resolution order: filename stem (primary) → alias → title (fallback) - Slug collision detection: auto-suffix (-2, -3, etc.) on note creation - Add slugCollides utility for frontend collision checking - resolveNewNote accepts entries param for collision-aware creation - Title editing via H1 heading already triggers file rename (existing flow) - Update docs/ARCHITECTURE.md with migrate_to_flat_vault command Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
useNoteActions,
|
||||
DEFAULT_TEMPLATES,
|
||||
resolveTemplate,
|
||||
slugCollides,
|
||||
} from './useNoteActions'
|
||||
import type { NoteActionsConfig } from './useNoteActions'
|
||||
|
||||
@@ -111,6 +112,38 @@ describe('needsRenameOnSave', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('slugCollides', () => {
|
||||
it('detects collision with existing filename', () => {
|
||||
const entries = [makeEntry({ filename: 'my-note.md', path: '/vault/my-note.md' })]
|
||||
expect(slugCollides('My Note', entries)).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false when no collision', () => {
|
||||
const entries = [makeEntry({ filename: 'other.md', path: '/vault/other.md' })]
|
||||
expect(slugCollides('My Note', entries)).toBe(false)
|
||||
})
|
||||
|
||||
it('excludes the current note path from collision check', () => {
|
||||
const entries = [makeEntry({ filename: 'my-note.md', path: '/vault/my-note.md' })]
|
||||
expect(slugCollides('My Note', entries, '/vault/my-note.md')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveNewNote slug collision', () => {
|
||||
it('auto-suffixes slug when collision detected', () => {
|
||||
const entries = [makeEntry({ filename: 'my-project.md', path: '/vault/my-project.md' })]
|
||||
const { entry } = resolveNewNote('My Project', 'Project', '/vault', null, entries)
|
||||
expect(entry.path).toBe('/vault/my-project-2.md')
|
||||
expect(entry.filename).toBe('my-project-2.md')
|
||||
})
|
||||
|
||||
it('skips suffix when no collision', () => {
|
||||
const entries = [makeEntry({ filename: 'other.md', path: '/vault/other.md' })]
|
||||
const { entry } = resolveNewNote('My Project', 'Project', '/vault', null, entries)
|
||||
expect(entry.path).toBe('/vault/my-project.md')
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildNewEntry', () => {
|
||||
it('creates a VaultEntry with correct fields', () => {
|
||||
const entry = buildNewEntry({
|
||||
@@ -190,8 +223,8 @@ describe('entryMatchesTarget', () => {
|
||||
expect(entryMatchesTarget(entry, 'mp')).toBe(true)
|
||||
})
|
||||
|
||||
it('matches by path suffix (type/slug)', () => {
|
||||
const entry = makeEntry({ path: '/Users/luca/Laputa/project/my-project.md' })
|
||||
it('matches by filename stem from path-style wikilink', () => {
|
||||
const entry = makeEntry({ path: '/Users/luca/Laputa/my-project.md', filename: 'my-project.md' })
|
||||
expect(entryMatchesTarget(entry, 'project/my-project')).toBe(true)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user