feat: implement create new type feature

Core implementation:
- CreateTypeDialog: simple dialog with name field for creating new types
- handleCreateType in useNoteActions: creates Type documents in type/ folder
- Dynamic sidebar sections: custom types (Recipe, Book, etc.) appear as new
  sidebar sections after built-in ones, each with a + button for instances
- Updated CreateNoteDialog: accepts custom types, shows them with blue accent
- handleCreateNote now supports custom types (folder = lowercased type name)

Product decisions:
- The + on Types section opens CreateTypeDialog (not CreateNoteDialog)
- Custom type sections use FileText icon and blue accent color by default
- Section labels are pluralized (e.g., "Recipes", "Books")

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-02-21 09:41:46 +01:00
parent e739ae0028
commit 170de2a7e0
5 changed files with 277 additions and 104 deletions

View File

@@ -1,9 +1,10 @@
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { Sidebar } from './components/Sidebar'
import { NoteList } from './components/NoteList'
import { Editor } from './components/Editor'
import { ResizeHandle } from './components/ResizeHandle'
import { CreateNoteDialog, type NoteType } from './components/CreateNoteDialog'
import { CreateNoteDialog } from './components/CreateNoteDialog'
import { CreateTypeDialog } from './components/CreateTypeDialog'
import { QuickOpenPalette } from './components/QuickOpenPalette'
import { Toast } from './components/Toast'
import { CommitDialog } from './components/CommitDialog'
@@ -29,6 +30,12 @@ const VAULTS = [
{ label: 'Demo', path: '/Users/luca/Workspace/laputa-app/demo-vault' },
]
const BUILT_IN_TYPE_NAMES = new Set([
'Project', 'Experiment', 'Responsibility', 'Procedure',
'Person', 'Event', 'Topic', 'Type', 'Note', 'Essay',
'Quarter', 'Journal', 'Evergreen',
])
function App() {
const [selection, setSelection] = useState<SidebarSelection>(DEFAULT_SELECTION)
const [sidebarWidth, setSidebarWidth] = useState(250)
@@ -37,7 +44,8 @@ function App() {
const [inspectorCollapsed, setInspectorCollapsed] = useState(false)
const [gitHistory, setGitHistory] = useState<GitCommit[]>([])
const [showCreateDialog, setShowCreateDialog] = useState(false)
const [createNoteDefaultType, setCreateNoteDefaultType] = useState<NoteType | undefined>()
const [createNoteDefaultType, setCreateNoteDefaultType] = useState<string | undefined>()
const [showCreateTypeDialog, setShowCreateTypeDialog] = useState(false)
const [showQuickOpen, setShowQuickOpen] = useState(false)
const [showCommitDialog, setShowCommitDialog] = useState(false)
const [toastMessage, setToastMessage] = useState<string | null>(null)
@@ -47,6 +55,15 @@ function App() {
const vault = useVaultLoader(vaultPath)
const notes = useNoteActions(vault.addEntry, vault.updateContent, vault.entries, setToastMessage)
// Derive custom types from vault (Type entries not in built-in list)
const customTypes = useMemo(
() => vault.entries
.filter((e) => e.isA === 'Type' && !BUILT_IN_TYPE_NAMES.has(e.title))
.map((e) => e.title)
.sort(),
[vault.entries],
)
// Reset UI state when vault changes
const handleSwitchVault = useCallback((path: string) => {
setVaultPath(path)
@@ -65,10 +82,19 @@ function App() {
}, [notes.activeTabPath, vault.loadGitHistory])
const openCreateDialog = useCallback((type?: string) => {
setCreateNoteDefaultType(type as NoteType | undefined)
setCreateNoteDefaultType(type)
setShowCreateDialog(true)
}, [])
const openCreateTypeDialog = useCallback(() => {
setShowCreateTypeDialog(true)
}, [])
const handleCreateType = useCallback((name: string) => {
notes.handleCreateType(name)
setToastMessage(`Type "${name}" created`)
}, [notes, setToastMessage])
useAppKeyboard({
onQuickOpen: () => setShowQuickOpen(true),
onCreateNote: openCreateDialog,
@@ -107,7 +133,7 @@ function App() {
<div className="app-shell">
<div className="app">
<div className="app__sidebar" style={{ width: sidebarWidth }}>
<Sidebar entries={vault.entries} selection={selection} onSelect={setSelection} onSelectNote={notes.handleSelectNote} onCreateType={openCreateDialog} modifiedCount={vault.modifiedFiles.length} onCommitPush={() => setShowCommitDialog(true)} />
<Sidebar entries={vault.entries} selection={selection} onSelect={setSelection} onSelectNote={notes.handleSelectNote} onCreateType={openCreateDialog} onCreateNewType={openCreateTypeDialog} modifiedCount={vault.modifiedFiles.length} onCommitPush={() => setShowCommitDialog(true)} />
</div>
<ResizeHandle onResize={handleSidebarResize} />
<div className="app__note-list" style={{ width: noteListWidth }}>
@@ -154,6 +180,12 @@ function App() {
onClose={() => setShowCreateDialog(false)}
onCreate={notes.handleCreateNote}
defaultType={createNoteDefaultType}
customTypes={customTypes}
/>
<CreateTypeDialog
open={showCreateTypeDialog}
onClose={() => setShowCreateTypeDialog(false)}
onCreate={handleCreateType}
/>
<CommitDialog
open={showCommitDialog}