fix: remove legacy journal command surfaces
This commit is contained in:
@@ -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', () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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
5
src/utils/legacyTypes.ts
Normal 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
|
||||
}
|
||||
@@ -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
64
src/utils/vaultTypes.ts
Normal 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))
|
||||
}
|
||||
54
tests/smoke/command-palette-hides-legacy-journal.spec.ts
Normal file
54
tests/smoke/command-palette-hides-legacy-journal.spec.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user