fix: flat vault type resolution — remove type/ prefix from links and protected folders
- Type relationship links: [[type/essay]] → [[essay]] (matches flat vault structure) - PROTECTED_FOLDERS / KEEP_FOLDERS: only attachments, _themes, assets - TypeSelector navigation: type/slug → slug - Updated all 14 affected test files - All 612 Rust tests + 2151 frontend tests pass
This commit is contained in:
@@ -306,14 +306,14 @@ describe('DynamicPropertiesPanel', () => {
|
||||
/>
|
||||
)
|
||||
fireEvent.click(screen.getByText('Project'))
|
||||
expect(onNavigate).toHaveBeenCalledWith('type/project')
|
||||
expect(onNavigate).toHaveBeenCalledWith('project')
|
||||
})
|
||||
|
||||
describe('TypeSelector', () => {
|
||||
const typeEntries = [
|
||||
makeEntry({ path: '/vault/type/project.md', title: 'Project', isA: 'Type' }),
|
||||
makeEntry({ path: '/vault/type/person.md', title: 'Person', isA: 'Type' }),
|
||||
makeEntry({ path: '/vault/type/topic.md', title: 'Topic', isA: 'Type' }),
|
||||
makeEntry({ path: '/vault/project.md', title: 'Project', isA: 'Type' }),
|
||||
makeEntry({ path: '/vault/person.md', title: 'Person', isA: 'Type' }),
|
||||
makeEntry({ path: '/vault/topic.md', title: 'Topic', isA: 'Type' }),
|
||||
]
|
||||
|
||||
it('renders as dropdown when editable', () => {
|
||||
|
||||
@@ -372,7 +372,7 @@ This is a test note with some words to count.
|
||||
fileSize: 500,
|
||||
snippet: '',
|
||||
wordCount: 0,
|
||||
relationships: { 'Type': ['[[type/responsibility]]'] },
|
||||
relationships: { 'Type': ['[[responsibility]]'] },
|
||||
icon: null,
|
||||
color: null,
|
||||
order: null,
|
||||
@@ -399,7 +399,7 @@ This is a test note with some words to count.
|
||||
fileSize: 300,
|
||||
snippet: '',
|
||||
wordCount: 0,
|
||||
relationships: { 'Belongs to': ['[[responsibility/grow-newsletter]]'], 'Type': ['[[type/essay]]'] },
|
||||
relationships: { 'Belongs to': ['[[responsibility/grow-newsletter]]'], 'Type': ['[[essay]]'] },
|
||||
icon: null,
|
||||
color: null,
|
||||
order: null,
|
||||
@@ -426,7 +426,7 @@ This is a test note with some words to count.
|
||||
fileSize: 400,
|
||||
snippet: '',
|
||||
wordCount: 0,
|
||||
relationships: { 'Belongs to': ['[[responsibility/grow-newsletter]]'], 'Type': ['[[type/procedure]]'] },
|
||||
relationships: { 'Belongs to': ['[[responsibility/grow-newsletter]]'], 'Type': ['[[procedure]]'] },
|
||||
icon: null,
|
||||
color: null,
|
||||
order: null,
|
||||
@@ -453,7 +453,7 @@ This is a test note with some words to count.
|
||||
fileSize: 200,
|
||||
snippet: '',
|
||||
wordCount: 0,
|
||||
relationships: { 'Related to': ['[[responsibility/grow-newsletter]]'], 'Type': ['[[type/experiment]]'] },
|
||||
relationships: { 'Related to': ['[[responsibility/grow-newsletter]]'], 'Type': ['[[experiment]]'] },
|
||||
icon: null,
|
||||
color: null,
|
||||
order: null,
|
||||
@@ -532,13 +532,13 @@ Status: Active
|
||||
it('skips Type relationships in referenced-by computation', () => {
|
||||
const typeEntry: VaultEntry = {
|
||||
...targetEntry,
|
||||
path: '/Users/luca/Laputa/type/responsibility.md',
|
||||
path: '/Users/luca/Laputa/responsibility.md',
|
||||
filename: 'responsibility.md',
|
||||
title: 'Responsibility',
|
||||
isA: 'Type',
|
||||
relationships: {},
|
||||
}
|
||||
// essayEntry has Type: [[type/responsibility]] — should NOT show as referenced-by
|
||||
// essayEntry has Type: [[responsibility]] — should NOT show as referenced-by
|
||||
render(
|
||||
<Inspector
|
||||
{...defaultProps}
|
||||
@@ -549,7 +549,7 @@ Status: Active
|
||||
/>
|
||||
)
|
||||
// On Writing Well references responsibility via "Belongs to" (path match), not via "Type"
|
||||
// But the Type entry is at type/responsibility.md, so wikilinks to
|
||||
// But the Type entry is at responsibility.md, so wikilinks to
|
||||
// responsibility/grow-newsletter won't match. Section should be hidden
|
||||
expect(screen.queryByText('Referenced by')).not.toBeInTheDocument()
|
||||
})
|
||||
@@ -561,7 +561,7 @@ Status: Active
|
||||
}
|
||||
const referrer: VaultEntry = {
|
||||
...essayEntry,
|
||||
relationships: { 'Topics': ['[[Newsletter]]'], 'Type': ['[[type/essay]]'] },
|
||||
relationships: { 'Topics': ['[[Newsletter]]'], 'Type': ['[[essay]]'] },
|
||||
}
|
||||
render(
|
||||
<Inspector
|
||||
@@ -596,7 +596,7 @@ Status: Active
|
||||
fileSize: 300,
|
||||
snippet: '',
|
||||
wordCount: 0,
|
||||
relationships: { 'Belongs to': ['[[responsibility/grow-newsletter]]'], 'Type': ['[[type/essay]]'] },
|
||||
relationships: { 'Belongs to': ['[[responsibility/grow-newsletter]]'], 'Type': ['[[essay]]'] },
|
||||
icon: null,
|
||||
color: null,
|
||||
order: null,
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('DynamicRelationshipsPanel', () => {
|
||||
const onNavigate = vi.fn()
|
||||
const onAddProperty = vi.fn()
|
||||
const personTypeEntry = makeEntry({
|
||||
path: '/vault/type/person.md', filename: 'person.md', title: 'Person',
|
||||
path: '/vault/person.md', filename: 'person.md', title: 'Person',
|
||||
isA: 'Type', color: 'yellow', icon: 'user',
|
||||
})
|
||||
const entries = [
|
||||
@@ -757,7 +757,7 @@ describe('GitHistoryPanel', () => {
|
||||
describe('InstancesPanel', () => {
|
||||
const onNavigate = vi.fn()
|
||||
const quarterType = makeEntry({
|
||||
path: '/vault/type/quarter.md', filename: 'quarter.md', title: 'Quarter',
|
||||
path: '/vault/quarter.md', filename: 'quarter.md', title: 'Quarter',
|
||||
isA: 'Type', color: 'blue', icon: 'calendar',
|
||||
})
|
||||
const typeEntryMap: Record<string, VaultEntry> = { Quarter: quarterType }
|
||||
|
||||
@@ -38,7 +38,7 @@ const entries = [
|
||||
]
|
||||
|
||||
const personTypeEntry = makeEntry({
|
||||
path: '/vault/type/person.md', filename: 'person.md', title: 'Person',
|
||||
path: '/vault/person.md', filename: 'person.md', title: 'Person',
|
||||
isA: 'Type', color: 'yellow', icon: 'user',
|
||||
})
|
||||
const typeEntryMap: Record<string, VaultEntry> = { Person: personTypeEntry }
|
||||
|
||||
@@ -422,7 +422,7 @@ describe('Sidebar', () => {
|
||||
const entriesWithCustomTypes: VaultEntry[] = [
|
||||
...mockEntries,
|
||||
{
|
||||
path: '/vault/type/recipe.md',
|
||||
path: '/vault/recipe.md',
|
||||
filename: 'recipe.md',
|
||||
title: 'Recipe',
|
||||
isA: 'Type',
|
||||
@@ -450,7 +450,7 @@ describe('Sidebar', () => {
|
||||
properties: {},
|
||||
},
|
||||
{
|
||||
path: '/vault/type/book.md',
|
||||
path: '/vault/book.md',
|
||||
filename: 'book.md',
|
||||
title: 'Book',
|
||||
isA: 'Type',
|
||||
@@ -592,7 +592,7 @@ describe('Sidebar', () => {
|
||||
|
||||
it('does not show built-in types as custom sections', () => {
|
||||
const projectTypeEntry: VaultEntry = {
|
||||
path: '/vault/type/project.md',
|
||||
path: '/vault/project.md',
|
||||
filename: 'project.md',
|
||||
title: 'Project',
|
||||
isA: 'Type',
|
||||
@@ -629,7 +629,7 @@ describe('Sidebar', () => {
|
||||
const entriesWithLabel: VaultEntry[] = [
|
||||
...mockEntries,
|
||||
{
|
||||
path: '/vault/type/news.md', filename: 'news.md', title: 'News', isA: 'Type',
|
||||
path: '/vault/news.md', filename: 'news.md', title: 'News', isA: 'Type',
|
||||
aliases: [], belongsTo: [], relatedTo: [], status: null, owner: null, cadence: null,
|
||||
archived: false, trashed: false, trashedAt: null, modifiedAt: 1700000000, createdAt: null,
|
||||
fileSize: 200, snippet: '', wordCount: 0, relationships: {},
|
||||
@@ -655,7 +655,7 @@ describe('Sidebar', () => {
|
||||
const entriesWithBuiltInOverride: VaultEntry[] = [
|
||||
...mockEntries,
|
||||
{
|
||||
path: '/vault/type/person.md', filename: 'person.md', title: 'Person', isA: 'Type',
|
||||
path: '/vault/person.md', filename: 'person.md', title: 'Person', isA: 'Type',
|
||||
aliases: [], belongsTo: [], relatedTo: [], status: null, owner: null, cadence: null,
|
||||
archived: false, trashed: false, trashedAt: null, modifiedAt: 1700000000, createdAt: null,
|
||||
fileSize: 200, snippet: '', wordCount: 0, relationships: {},
|
||||
@@ -677,7 +677,7 @@ describe('Sidebar', () => {
|
||||
|
||||
describe('type visibility via visible property', () => {
|
||||
const makeTypeEntry = (title: string, visible: boolean | null): VaultEntry => ({
|
||||
path: `/vault/type/${title.toLowerCase()}.md`,
|
||||
path: `/vault/${title.toLowerCase()}.md`,
|
||||
filename: `${title.toLowerCase()}.md`,
|
||||
title,
|
||||
isA: 'Type',
|
||||
@@ -809,7 +809,7 @@ describe('Sidebar', () => {
|
||||
...mockEntries,
|
||||
// Type entries with order values — reversed from default
|
||||
{
|
||||
path: '/vault/type/project.md', filename: 'project.md', title: 'Project', isA: 'Type',
|
||||
path: '/vault/project.md', filename: 'project.md', title: 'Project', isA: 'Type',
|
||||
aliases: [], belongsTo: [], relatedTo: [], status: null, owner: null, cadence: null,
|
||||
archived: false, trashed: false, trashedAt: null, modifiedAt: 1700000000, createdAt: null, fileSize: 200, snippet: '',
|
||||
wordCount: 0,
|
||||
@@ -817,7 +817,7 @@ describe('Sidebar', () => {
|
||||
properties: {},
|
||||
},
|
||||
{
|
||||
path: '/vault/type/topic.md', filename: 'topic.md', title: 'Topic', isA: 'Type',
|
||||
path: '/vault/topic.md', filename: 'topic.md', title: 'Topic', isA: 'Type',
|
||||
aliases: [], belongsTo: [], relatedTo: [], status: null, owner: null, cadence: null,
|
||||
archived: false, trashed: false, trashedAt: null, modifiedAt: 1700000000, createdAt: null, fileSize: 200, snippet: '',
|
||||
wordCount: 0,
|
||||
@@ -825,7 +825,7 @@ describe('Sidebar', () => {
|
||||
properties: {},
|
||||
},
|
||||
{
|
||||
path: '/vault/type/person.md', filename: 'person.md', title: 'Person', isA: 'Type',
|
||||
path: '/vault/person.md', filename: 'person.md', title: 'Person', isA: 'Type',
|
||||
aliases: [], belongsTo: [], relatedTo: [], status: null, owner: null, cadence: null,
|
||||
archived: false, trashed: false, trashedAt: null, modifiedAt: 1700000000, createdAt: null, fileSize: 200, snippet: '',
|
||||
wordCount: 0,
|
||||
|
||||
@@ -28,7 +28,7 @@ function ReadOnlyType({ isA, customColorKey, onNavigate }: { isA?: string | null
|
||||
<button
|
||||
className="min-w-0 truncate border-none text-right cursor-pointer hover:opacity-80"
|
||||
style={{ background: getTypeLightColor(isA, customColorKey), color: getTypeColor(isA, customColorKey), borderRadius: 6, padding: '2px 8px', fontSize: 12, fontWeight: 500 }}
|
||||
onClick={() => onNavigate(`type/${isA.toLowerCase()}`)} title={isA}
|
||||
onClick={() => onNavigate(isA.toLowerCase())} title={isA}
|
||||
>{isA}</button>
|
||||
) : (
|
||||
<span className="text-right text-[12px] text-secondary-foreground">{isA}</span>
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('useNoteListSort (via NoteList)', () => {
|
||||
})
|
||||
|
||||
it('reads sort from type document for sectionGroup selection', () => {
|
||||
const typeDoc = makeEntry({ path: '/type/note.md', title: 'Note', isA: 'Type', sort: 'title:asc' })
|
||||
const typeDoc = makeEntry({ path: '/note.md', title: 'Note', isA: 'Type', sort: 'title:asc' })
|
||||
const entries = [
|
||||
typeDoc,
|
||||
makeEntry({ path: '/c.md', title: 'Charlie', modifiedAt: 3000 }),
|
||||
@@ -82,7 +82,7 @@ describe('useNoteListSort (via NoteList)', () => {
|
||||
})
|
||||
|
||||
it('shows type title as header for sectionGroup selection', () => {
|
||||
const typeDoc = makeEntry({ path: '/type/project.md', title: 'Project', isA: 'Type' })
|
||||
const typeDoc = makeEntry({ path: '/project.md', title: 'Project', isA: 'Type' })
|
||||
renderNoteList({ entries: [typeDoc], selection: { kind: 'sectionGroup', type: 'Project', label: 'Projects' } })
|
||||
expect(screen.getByText('Project')).toBeInTheDocument()
|
||||
})
|
||||
@@ -91,7 +91,7 @@ describe('useNoteListSort (via NoteList)', () => {
|
||||
localStorageMock.setItem('laputa-sort-preferences', JSON.stringify({ '__list__': { option: 'title', direction: 'asc' } }))
|
||||
const onUpdateTypeSort = vi.fn()
|
||||
const updateEntry = vi.fn()
|
||||
const typeDoc = makeEntry({ path: '/type/project.md', title: 'Project', isA: 'Type', sort: null })
|
||||
const typeDoc = makeEntry({ path: '/project.md', title: 'Project', isA: 'Type', sort: null })
|
||||
const entries = [typeDoc, makeEntry()]
|
||||
|
||||
renderNoteList({
|
||||
@@ -101,15 +101,15 @@ describe('useNoteListSort (via NoteList)', () => {
|
||||
updateEntry,
|
||||
})
|
||||
|
||||
expect(onUpdateTypeSort).toHaveBeenCalledWith('/type/project.md', 'sort', 'title:asc')
|
||||
expect(updateEntry).toHaveBeenCalledWith('/type/project.md', { sort: 'title:asc' })
|
||||
expect(onUpdateTypeSort).toHaveBeenCalledWith('/project.md', 'sort', 'title:asc')
|
||||
expect(updateEntry).toHaveBeenCalledWith('/project.md', { sort: 'title:asc' })
|
||||
})
|
||||
|
||||
it('does not migrate if type already has sort', () => {
|
||||
localStorageMock.setItem('laputa-sort-preferences', JSON.stringify({ '__list__': { option: 'title', direction: 'asc' } }))
|
||||
const onUpdateTypeSort = vi.fn()
|
||||
const updateEntry = vi.fn()
|
||||
const typeDoc = makeEntry({ path: '/type/project.md', title: 'Project', isA: 'Type', sort: 'modified:desc' })
|
||||
const typeDoc = makeEntry({ path: '/project.md', title: 'Project', isA: 'Type', sort: 'modified:desc' })
|
||||
const entries = [typeDoc, makeEntry()]
|
||||
|
||||
renderNoteList({
|
||||
|
||||
Reference in New Issue
Block a user