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:
lucaronin
2026-03-15 19:50:22 +01:00
parent 69f50e5950
commit 094e806b13
4 changed files with 71 additions and 18 deletions

View File

@@ -20,23 +20,28 @@ export function wikilinkDisplay(ref: string): string {
/**
* Unified wikilink resolution: find the VaultEntry matching a wikilink target.
* Handles pipe syntax, case-insensitive matching, title/alias/filename/path lookup.
* Resolution order: filename stem (primary) → alias → title (fallback).
* Handles pipe syntax, case-insensitive matching.
*/
export function resolveEntry(entries: VaultEntry[], rawTarget: string): VaultEntry | undefined {
const key = rawTarget.includes('|') ? rawTarget.split('|')[0] : rawTarget
const keyLower = key.toLowerCase()
const suffix = '/' + key + '.md'
const lastSegment = key.split('/').pop() ?? key
const asWords = lastSegment.replace(/-/g, ' ').toLowerCase()
return entries.find(e => {
if (e.title.toLowerCase() === keyLower) return true
if (e.aliases.some(a => a.toLowerCase() === keyLower)) return true
// 1. Filename stem match (primary — filename IS identity in flat vault)
const byStem = entries.find(e => {
const stem = e.filename.replace(/\.md$/, '')
if (stem.toLowerCase() === keyLower) return true
if (e.path.endsWith(suffix)) return true
if (stem.toLowerCase() === lastSegment.toLowerCase()) return true
if (e.title.toLowerCase() === asWords) return true
return false
return stem.toLowerCase() === keyLower || stem.toLowerCase() === lastSegment.toLowerCase()
})
if (byStem) return byStem
// 2. Alias match
const byAlias = entries.find(e => e.aliases.some(a => a.toLowerCase() === keyLower))
if (byAlias) return byAlias
// 3. Title match (fallback)
return entries.find(e =>
e.title.toLowerCase() === keyLower || e.title.toLowerCase() === asWords,
)
}