fix: remove legacy journal command surfaces

This commit is contained in:
lucaronin
2026-04-13 22:21:42 +02:00
parent f50d9127d1
commit b7cb3c2130
10 changed files with 190 additions and 42 deletions

View File

@@ -108,6 +108,20 @@ describe('buildDynamicSections', () => {
const sections = buildDynamicSections(entries, typeEntryMap)
expect(sections.map((s) => s.type)).not.toContain('Old')
})
it('excludes the legacy Journal section even when journal notes still exist', () => {
const entries: VaultEntry[] = [
{ ...baseEntry, title: 'Daily Log', isA: 'Journal' },
]
const typeEntryMap: Record<string, VaultEntry> = {
Journal: { ...baseEntry, title: 'Journal', isA: 'Type' },
journal: { ...baseEntry, title: 'Journal', isA: 'Type' },
}
const sections = buildDynamicSections(entries, typeEntryMap)
expect(sections.map((section) => section.type)).not.toContain('Journal')
})
})
describe('collectActiveTypes', () => {

View File

@@ -1,22 +1,12 @@
import type { CommandAction } from './types'
import type { SidebarSelection, VaultEntry } from '../../types'
import type { SidebarSelection } from '../../types'
import { canonicalizeTypeName } from '../../utils/vaultTypes'
const PLURAL_OVERRIDES: Record<string, string> = {
Person: 'People',
Responsibility: 'Responsibilities',
}
const DEFAULT_TYPES = ['Event', 'Person', 'Project', 'Note']
const DEFAULT_TYPE_CANONICAL_CASE = new Map(
DEFAULT_TYPES.map(type => [type.toLowerCase(), type] as const),
)
function canonicalizeTypeName(type: string): string | null {
const trimmedType = type.trim()
if (!trimmedType) return null
return DEFAULT_TYPE_CANONICAL_CASE.get(trimmedType.toLowerCase()) ?? trimmedType
}
export function pluralizeType(type: string): string {
if (PLURAL_OVERRIDES[type]) return PLURAL_OVERRIDES[type]
if (type.endsWith('s') || type.endsWith('x') || type.endsWith('ch') || type.endsWith('sh')) return `${type}es`
@@ -24,29 +14,6 @@ export function pluralizeType(type: string): string {
return `${type}s`
}
export function extractVaultTypes(entries: VaultEntry[]): string[] {
const typeMap = new Map<string, string>()
for (const e of entries) {
const rawType =
e.isA === 'Type'
? e.title
: e.isA && e.isA !== 'Type'
? e.isA
: null
if (!rawType) continue
const canonicalType = canonicalizeTypeName(rawType)
if (!canonicalType) continue
const typeKey = canonicalType.toLowerCase()
if (!typeMap.has(typeKey)) {
typeMap.set(typeKey, canonicalType)
}
}
if (typeMap.size === 0) return DEFAULT_TYPES
return Array.from(typeMap.values()).sort()
}
export function buildTypeCommands(
types: string[],
onCreateNoteOfType: (type: string) => void,

View File

@@ -306,6 +306,26 @@ describe('extractVaultTypes', () => {
expect(extractVaultTypes(entries)).toEqual(['Note', 'Project'])
})
it('omits the legacy Journal type from extracted command-palette types', () => {
const entries = [
{ path: '/journal.md', title: 'Journal', isA: 'Type' },
{ path: '/2026-03-11.md', title: 'March 11', isA: 'Journal' },
{ path: '/note.md', title: 'General Note', isA: 'Note' },
] as never[]
expect(extractVaultTypes(entries)).toEqual(['Note'])
})
it('omits hidden types from extracted command-palette types', () => {
const entries = [
{ path: '/recipe.md', title: 'Recipe', isA: 'Type', visible: false },
{ path: '/dinner.md', title: 'Dinner', isA: 'Recipe' },
{ path: '/project.md', title: 'Project', isA: 'Type' },
] as never[]
expect(extractVaultTypes(entries)).toEqual(['Project'])
})
})
describe('groupSortKey', () => {

View File

@@ -8,13 +8,15 @@ import { buildGitCommands } from './commands/gitCommands'
import { buildViewCommands } from './commands/viewCommands'
import { buildSettingsCommands } from './commands/settingsCommands'
import { buildAiAgentCommands } from './commands/aiAgentCommands'
import { buildTypeCommands, extractVaultTypes } from './commands/typeCommands'
import { buildTypeCommands } from './commands/typeCommands'
import { buildFilterCommands } from './commands/filterCommands'
import { extractVaultTypes } from '../utils/vaultTypes'
// Re-export types and helpers for backward compatibility
export type { CommandAction, CommandGroup } from './commands/types'
export { groupSortKey } from './commands/types'
export { pluralizeType, extractVaultTypes, buildTypeCommands } from './commands/typeCommands'
export { pluralizeType, buildTypeCommands } from './commands/typeCommands'
export { extractVaultTypes } from '../utils/vaultTypes'
export { buildViewCommands } from './commands/viewCommands'
interface CommandRegistryConfig {

View File

@@ -158,6 +158,12 @@ describe('resolveNewNote', () => {
const { entry } = resolveNewNote({ title: 'ML', type: 'Topic', vaultPath: '/vault' })
expect(entry.status).toBeNull()
})
it('treats Journal as a regular type after removing the journaling flow', () => {
const { entry, content } = resolveNewNote({ title: 'Reflection', type: 'Journal', vaultPath: '/vault' })
expect(entry.status).toBe('Active')
expect(content).toContain('status: Active')
})
})
describe('resolveNewType', () => {

View File

@@ -63,7 +63,7 @@ export function entryMatchesTarget({ entry, target }: EntryMatchParams): boolean
return resolveEntry([entry], target) === entry
}
const NO_STATUS_TYPES = new Set(['Topic', 'Person', 'Journal'])
const NO_STATUS_TYPES = new Set(['Topic', 'Person'])
/** Default templates for built-in types. Used when the type entry has no custom template. */
export const DEFAULT_TEMPLATES: Record<string, string> = {

5
src/utils/legacyTypes.ts Normal file
View File

@@ -0,0 +1,5 @@
const LEGACY_JOURNAL_TYPE = 'journal'
export function isLegacyJournalingType(type: string | null | undefined): boolean {
return type?.trim().toLowerCase() === LEGACY_JOURNAL_TYPE
}

View File

@@ -7,6 +7,7 @@ import type { VaultEntry } from '../types'
import type { SectionGroup } from '../components/SidebarParts'
import { resolveIcon } from './iconRegistry'
import { pluralizeType } from '../hooks/useCommandRegistry'
import { isLegacyJournalingType } from './legacyTypes'
import {
Wrench, Flask, Target, ArrowsClockwise,
Users, CalendarBlank, Tag, StackSimple,
@@ -28,12 +29,28 @@ const BUILT_IN_TYPE_MAP = new Map(BUILT_IN_SECTION_GROUPS.map((sg) => [sg.type,
const isMarkdown = (e: VaultEntry) => e.fileKind === 'markdown' || !e.fileKind
const isActive = (e: VaultEntry) => !e.archived
const isSupportedSectionType = (type: string) => !isLegacyJournalingType(type)
function resolveEntrySectionType(entry: VaultEntry): string {
return entry.isA || 'Note'
}
function shouldCollectActiveType(entry: VaultEntry): boolean {
if (!isActive(entry) || !isMarkdown(entry)) return false
return isSupportedSectionType(resolveEntrySectionType(entry))
}
function shouldIncludeTypeDefinition(name: string, entry: VaultEntry): boolean {
if (name !== entry.title || !isActive(entry)) return false
return isSupportedSectionType(name)
}
/** Collect unique isA values from active (non-archived) markdown entries. Untyped entries count as 'Note'. */
export function collectActiveTypes(entries: VaultEntry[]): Set<string> {
const types = new Set<string>()
for (const e of entries) {
if (isActive(e) && isMarkdown(e)) types.add(e.isA || 'Note')
if (!shouldCollectActiveType(e)) continue
types.add(resolveEntrySectionType(e))
}
return types
}
@@ -59,9 +76,8 @@ export function buildSectionGroup(type: string, typeEntryMap: Record<string, Vau
export function buildDynamicSections(entries: VaultEntry[], typeEntryMap: Record<string, VaultEntry>): SectionGroup[] {
const activeTypes = collectActiveTypes(entries)
for (const [name, entry] of Object.entries(typeEntryMap)) {
if (name === entry.title && isActive(entry)) {
activeTypes.add(name)
}
if (!shouldIncludeTypeDefinition(name, entry)) continue
activeTypes.add(name)
}
return Array.from(activeTypes, (type) => buildSectionGroup(type, typeEntryMap))
}

64
src/utils/vaultTypes.ts Normal file
View File

@@ -0,0 +1,64 @@
import type { VaultEntry } from '../types'
import { isLegacyJournalingType } from './legacyTypes'
const DEFAULT_TYPES = ['Event', 'Person', 'Project', 'Note']
const DEFAULT_TYPE_CANONICAL_CASE = new Map(
DEFAULT_TYPES.map((type) => [type.toLowerCase(), type] as const),
)
export function canonicalizeTypeName(type: string): string | null {
const trimmedType = type.trim()
if (!trimmedType) return null
return DEFAULT_TYPE_CANONICAL_CASE.get(trimmedType.toLowerCase()) ?? trimmedType
}
function resolveEntryType(entry: VaultEntry): string | null {
if (entry.isA === 'Type') return entry.title
if (entry.isA && entry.isA !== 'Type') return entry.isA
return null
}
function addCanonicalType(typeMap: Map<string, string>, rawType: string | null): void {
if (!rawType) return
const canonicalType = canonicalizeTypeName(rawType)
if (!canonicalType) return
const typeKey = canonicalType.toLowerCase()
if (!typeMap.has(typeKey)) {
typeMap.set(typeKey, canonicalType)
}
}
function collectHiddenTypeKeys(entries: VaultEntry[]): Set<string> {
const hiddenTypeKeys = new Set<string>()
for (const entry of entries) {
if (entry.isA !== 'Type' || entry.visible !== false) continue
const canonicalType = canonicalizeTypeName(entry.title)
if (!canonicalType) continue
hiddenTypeKeys.add(canonicalType.toLowerCase())
}
return hiddenTypeKeys
}
function shouldIncludeCommandPaletteType(type: string, hiddenTypeKeys: Set<string>): boolean {
const typeKey = type.toLowerCase()
return !hiddenTypeKeys.has(typeKey) && !isLegacyJournalingType(type)
}
export function extractVaultTypes(entries: VaultEntry[]): string[] {
const typeMap = new Map<string, string>()
for (const entry of entries) {
addCanonicalType(typeMap, resolveEntryType(entry))
}
const hiddenTypeKeys = collectHiddenTypeKeys(entries)
const sourceTypes = typeMap.size === 0 ? DEFAULT_TYPES : Array.from(typeMap.values()).sort()
return sourceTypes.filter((type) => shouldIncludeCommandPaletteType(type, hiddenTypeKeys))
}

View File

@@ -0,0 +1,54 @@
import fs from 'fs'
import path from 'path'
import { test, expect } from '@playwright/test'
import {
createFixtureVaultCopy,
openFixtureVault,
removeFixtureVaultCopy,
} from '../helpers/fixtureVault'
import { openCommandPalette } from './helpers'
let tempVaultDir: string
function seedLegacyJournalVault(vaultPath: string): void {
fs.writeFileSync(path.join(vaultPath, 'journal.md'), `---
type: Type
order: 12
icon: book-bookmark
color: yellow
---
# Journal
`)
fs.writeFileSync(path.join(vaultPath, 'daily-log.md'), `---
title: Daily Log
type: Journal
---
# Daily Log
`)
}
test.describe('command palette hides the legacy Journal type', () => {
test.beforeEach(() => {
tempVaultDir = createFixtureVaultCopy()
seedLegacyJournalVault(tempVaultDir)
})
test.afterEach(() => {
removeFixtureVaultCopy(tempVaultDir)
})
test('legacy Journal does not appear in the sidebar or command palette', async ({ page }) => {
await openFixtureVault(page, tempVaultDir)
await expect(page.locator('nav').getByText('Journals', { exact: true })).toHaveCount(0)
await openCommandPalette(page)
await page.locator('input[placeholder="Type a command..."]').fill('journal')
await expect(page.getByText('No matching commands', { exact: true })).toBeVisible()
await expect(page.locator('div.mx-1.flex.cursor-pointer')).toHaveCount(0)
})
})