refactor: remove Trash system — delete is now permanent with confirm modal

Remove all vestiges of the abandoned Trash system: trashed/trashedAt fields
from types, frontmatter parsing, sidebar filtering, editor banners, inspector
components, mock data, and all related tests. Delete is already permanent via
useDeleteActions with a confirmation dialog. Notes with trashed:true in
existing vault frontmatter are now treated as normal notes (the flag is
ignored by the parser).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-04-06 12:21:56 +02:00
parent 53072eb4f3
commit d0c3a6b889
123 changed files with 359 additions and 3166 deletions

View File

@@ -12,8 +12,6 @@ const makeEntry = (overrides: Partial<VaultEntry> = {}): VaultEntry => ({
relatedTo: [],
status: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: 1700000000,
fileSize: 100,

View File

@@ -7,7 +7,6 @@ function makeEntry(overrides: Partial<VaultEntry> = {}): VaultEntry {
path: '/vault/note/test.md', filename: 'test.md', title: 'Test',
isA: 'Note', aliases: [], belongsTo: [], relatedTo: [],
status: null, organized: false, archived: false,
trashed: false, trashedAt: null,
modifiedAt: null, createdAt: null, fileSize: 0,
snippet: '', wordCount: 0, relationships: {},
icon: null, color: null, order: null, template: null, sort: null, outgoingLinks: [],
@@ -577,11 +576,6 @@ describe('isInboxEntry', () => {
expect(isInboxEntry(note)).toBe(false)
})
it('returns false for a trashed note', () => {
const note = makeEntry({ trashed: true })
expect(isInboxEntry(note)).toBe(false)
})
it('returns false for an archived note', () => {
const note = makeEntry({ archived: true })
expect(isInboxEntry(note)).toBe(false)
@@ -686,7 +680,7 @@ describe('filterEntries — folder selection', () => {
expect(result).toEqual([])
})
it('excludes archived and trashed entries by default', () => {
it('excludes archived entries by default', () => {
const withArchived = [
...entries,
makeEntry({ path: '/vault/projects/laputa/archived.md', title: 'Archived', archived: true }),

View File

@@ -2,7 +2,7 @@ import type { VaultEntry, SidebarSelection, InboxPeriod, ViewFile } from '../typ
import { evaluateView } from './viewFilters'
import { wikilinkTarget, resolveEntry } from './wikilink'
export type NoteListFilter = 'open' | 'archived' | 'trashed'
export type NoteListFilter = 'open' | 'archived'
export interface RelationshipGroup {
label: string
@@ -306,12 +306,11 @@ export function buildRelationshipGroups(
return b.groups
}
const isActive = (e: VaultEntry) => !e.archived && !e.trashed
const isActive = (e: VaultEntry) => !e.archived
const isMarkdown = (e: VaultEntry) => e.fileKind === 'markdown' || !e.fileKind
function applySubFilter(entries: VaultEntry[], subFilter: NoteListFilter): VaultEntry[] {
if (subFilter === 'archived') return entries.filter((e) => e.archived && !e.trashed)
if (subFilter === 'trashed') return entries.filter((e) => e.trashed)
if (subFilter === 'archived') return entries.filter((e) => e.archived)
return entries.filter(isActive)
}
@@ -344,9 +343,8 @@ function filterByKind(entries: VaultEntry[], selection: SidebarSelection, subFil
function filterByFilterType(entries: VaultEntry[], filter: string): VaultEntry[] {
if (filter === 'all') return entries.filter(isActive)
if (filter === 'archived') return entries.filter((e) => e.archived && !e.trashed)
if (filter === 'trash') return entries.filter((e) => e.trashed)
if (filter === 'favorites') return entries.filter((e) => e.favorite && !e.archived && !e.trashed)
if (filter === 'archived') return entries.filter((e) => e.archived)
if (filter === 'favorites') return entries.filter((e) => e.favorite && !e.archived)
if (filter === 'pulse') return []
return []
}
@@ -357,34 +355,32 @@ export function filterEntries(entries: VaultEntry[], selection: SidebarSelection
/** Count notes per sub-filter for a given type. */
export function countByFilter(entries: VaultEntry[], type: string): Record<NoteListFilter, number> {
let open = 0, archived = 0, trashed = 0
let open = 0, archived = 0
for (const e of entries) {
if (!isMarkdown(e) || e.isA !== type) continue
if (e.trashed) trashed++
else if (e.archived) archived++
if (e.archived) archived++
else open++
}
return { open, archived, trashed }
return { open, archived }
}
/** Count notes per sub-filter across all entries (no type filter). */
export function countAllByFilter(entries: VaultEntry[]): Record<NoteListFilter, number> {
let open = 0, archived = 0, trashed = 0
let open = 0, archived = 0
for (const e of entries) {
if (!isMarkdown(e)) continue
if (e.trashed) trashed++
else if (e.archived) archived++
if (e.archived) archived++
else open++
}
return { open, archived, trashed }
return { open, archived }
}
// --- Inbox ---
/** Check if entry belongs in the Inbox (markdown only, not organized, not trashed/archived, not a Type). */
/** Check if entry belongs in the Inbox (markdown only, not organized, not archived, not a Type). */
export function isInboxEntry(entry: VaultEntry): boolean {
if (!isMarkdown(entry)) return false
if (entry.trashed || entry.archived) return false
if (entry.archived) return false
if (entry.isA === 'Type') return false
return !entry.organized
}

View File

@@ -27,9 +27,9 @@ const BUILT_IN_SECTION_GROUPS: SectionGroup[] = [
const BUILT_IN_TYPE_MAP = new Map(BUILT_IN_SECTION_GROUPS.map((sg) => [sg.type, sg]))
const isMarkdown = (e: VaultEntry) => e.fileKind === 'markdown' || !e.fileKind
const isActive = (e: VaultEntry) => !e.trashed && !e.archived
const isActive = (e: VaultEntry) => !e.archived
/** Collect unique isA values from active (non-trashed, non-archived) markdown entries. Untyped entries count as 'Note'. */
/** 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) {

View File

@@ -11,7 +11,7 @@ function makeEntry(overrides: Partial<VaultEntry> = {}): VaultEntry {
return {
path: '/test.md', filename: 'test.md', title: 'Test', isA: null,
aliases: [], belongsTo: [], relatedTo: [], status: null,
archived: false, trashed: false, trashedAt: null, modifiedAt: null, createdAt: null,
archived: false, modifiedAt: null, createdAt: null,
fileSize: 0, snippet: '', wordCount: 0, relationships: {}, icon: null, color: null,
order: null, template: null, sort: null, outgoingLinks: [],
...overrides,

View File

@@ -59,7 +59,7 @@ describe('getTypeLightColor', () => {
const baseEntry: VaultEntry = {
path: '', filename: '', title: '', isA: null, aliases: [], belongsTo: [], relatedTo: [],
status: null, archived: false, trashed: false, trashedAt: null,
status: null, archived: false,
modifiedAt: null, createdAt: null, fileSize: 0, snippet: '', relationships: {},
wordCount: 0,
icon: null, color: null, order: null, sidebarLabel: null, template: null, sort: null,

View File

@@ -8,7 +8,7 @@ function makeEntry(overrides: Partial<VaultEntry>): VaultEntry {
return {
path: '/vault/test.md', filename: 'test.md', title: 'Test', isA: null,
aliases: [], belongsTo: [], relatedTo: [], status: null,
archived: false, trashed: false, trashedAt: null,
archived: false,
modifiedAt: NOW, createdAt: NOW, fileSize: 100, snippet: '',
wordCount: 0, relationships: {}, icon: null, color: null,
order: null, sidebarLabel: null, template: null, sort: null, view: null,
@@ -95,7 +95,7 @@ describe('evaluateView', () => {
expect(result.map((e) => e.title)).toEqual(['Has'])
})
it('excludes archived and trashed entries', () => {
it('excludes archived entries', () => {
const view: ViewDefinition = {
name: 'All', icon: null, color: null, sort: null,
filters: { all: [{ field: 'type', op: 'equals', value: 'Note' }] },
@@ -103,7 +103,6 @@ describe('evaluateView', () => {
const entries = [
makeEntry({ isA: 'Note', title: 'Active' }),
makeEntry({ isA: 'Note', title: 'Archived', archived: true }),
makeEntry({ isA: 'Note', title: 'Trashed', trashed: true }),
]
const result = evaluateView(view, entries)
expect(result.map((e) => e.title)).toEqual(['Active'])

View File

@@ -2,7 +2,7 @@ import type { VaultEntry, ViewDefinition, FilterGroup, FilterNode, FilterConditi
/** Evaluate a view's filters against a list of entries, returning only matches. */
export function evaluateView(definition: ViewDefinition, entries: VaultEntry[]): VaultEntry[] {
return entries.filter((e) => !e.trashed && !e.archived && evaluateGroup(definition.filters, e))
return entries.filter((e) => !e.archived && evaluateGroup(definition.filters, e))
}
function evaluateGroup(group: FilterGroup, entry: VaultEntry): boolean {
@@ -27,7 +27,6 @@ function resolveField(entry: VaultEntry, field: string): { scalar?: string | num
if (lower === 'title') return { scalar: entry.title }
if (lower === 'filename') return { scalar: entry.filename }
if (lower === 'archived') return { scalar: entry.archived }
if (lower === 'trashed') return { scalar: entry.trashed }
if (lower === 'favorite') return { scalar: entry.favorite }
if (lower === 'body') return { scalar: entry.snippet }

View File

@@ -5,7 +5,7 @@ import { wikilinkTarget, wikilinkDisplay, resolveEntry, relativePathStem } from
function makeEntry(overrides: Partial<VaultEntry>): VaultEntry {
return {
path: '/vault/note.md', filename: 'note.md', title: 'Note', isA: null,
aliases: [], belongsTo: [], relatedTo: [], status: null, archived: false, trashed: false, trashedAt: null,
aliases: [], belongsTo: [], relatedTo: [], status: null, archived: false,
modifiedAt: null, createdAt: null, fileSize: 100, snippet: '', wordCount: 0,
relationships: {}, icon: null, color: null, order: null, template: null,
sort: null, outgoingLinks: [], sidebarLabel: null, view: null, visible: null,

View File

@@ -13,8 +13,6 @@ function makeEntry(overrides: Partial<VaultEntry>): VaultEntry {
relatedTo: [],
status: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: null,
createdAt: null,
fileSize: 100,