diff --git a/src/components/Inspector.test.tsx b/src/components/Inspector.test.tsx index d6b71611..16451aa5 100644 --- a/src/components/Inspector.test.tsx +++ b/src/components/Inspector.test.tsx @@ -2,7 +2,7 @@ import type { ComponentProps, ReactElement } from 'react' import { render as rtlRender, screen, fireEvent } from '@testing-library/react' import { describe, it, expect, vi } from 'vitest' import { Inspector } from './Inspector' -import type { VaultEntry, GitCommit } from '../types' +import type { VaultEntry, GitCommit, WorkspaceIdentity } from '../types' import { TooltipProvider } from '@/components/ui/tooltip' function render(ui: ReactElement) { @@ -108,6 +108,21 @@ function renderSelectedInspector(overrides: Partial = {}) { }) } +function makeWorkspace(path: string, label: string, alias = label.toLowerCase()): WorkspaceIdentity { + return { + id: alias, + label, + alias, + path, + shortLabel: label.slice(0, 2).toUpperCase(), + color: null, + icon: null, + mounted: true, + available: true, + defaultForNewNotes: false, + } +} + describe('Inspector', () => { it('renders expanded state with "no note selected"', () => { render() @@ -200,6 +215,31 @@ Status: Evergreened ) }) + it('infers the current workspace for untagged default-vault notes before moving them', () => { + const laputaWorkspace = makeWorkspace('/Users/luca/Laputa', 'Laputa') + const refactoringWorkspace = makeWorkspace('/Users/luca/Refactoring', 'Refactoring') + const entry = { + ...mockEntry, + path: '/Users/luca/Laputa/project/test.md', + workspace: undefined, + } + const onChangeWorkspace = vi.fn() + + renderSelectedInspector({ + entry, + workspaces: [laputaWorkspace, refactoringWorkspace], + onChangeWorkspace, + }) + + fireEvent.pointerDown(screen.getByRole('combobox', { name: 'Laputa' }), { button: 0, pointerType: 'mouse' }) + fireEvent.click(screen.getByRole('option', { name: 'Refactoring' })) + + expect(onChangeWorkspace).toHaveBeenCalledWith( + expect.objectContaining({ path: entry.path, workspace: laputaWorkspace }), + refactoringWorkspace, + ) + }) + it('shows cadence when present', () => { // Cadence is now read from frontmatter in content (already in mockContent) render() diff --git a/src/components/Inspector.tsx b/src/components/Inspector.tsx index 8ea57394..c9191a81 100644 --- a/src/components/Inspector.tsx +++ b/src/components/Inspector.tsx @@ -13,6 +13,7 @@ import { InstancesPanel, NoteInfoPanel, } from './InspectorPanels' +import { normalizeNotePathForIdentity } from '../utils/notePathIdentity' import type { ReferencedByItem } from './InspectorPanels' import { EmptyInspector, InitializePropertiesPrompt, InspectorHeader, InvalidFrontmatterNotice } from './inspector/InspectorChrome' import { useBacklinks, useReferencedBy } from './inspector/useInspectorData' @@ -55,6 +56,26 @@ function supportsFrontmatter(entry: VaultEntry): boolean { return entry.fileKind === undefined || entry.fileKind === 'markdown' } +function pathBelongsToWorkspace(path: string, workspace: WorkspaceIdentity): boolean { + const normalizedPath = normalizeNotePathForIdentity(path) + const normalizedWorkspacePath = normalizeNotePathForIdentity(workspace.path) + if (!normalizedWorkspacePath) return false + return normalizedPath === normalizedWorkspacePath || normalizedPath.startsWith(`${normalizedWorkspacePath}/`) +} + +function inferEntryWorkspace(entry: VaultEntry, workspaces: WorkspaceIdentity[] | undefined): WorkspaceIdentity | undefined { + if (entry.workspace) return entry.workspace + return workspaces + ?.filter((workspace) => pathBelongsToWorkspace(entry.path, workspace)) + .sort((left, right) => right.path.length - left.path.length) + .at(0) +} + +function entryWithInferredWorkspace(entry: VaultEntry, workspaces: WorkspaceIdentity[] | undefined): VaultEntry { + const workspace = inferEntryWorkspace(entry, workspaces) + return workspace && workspace !== entry.workspace ? { ...entry, workspace } : entry +} + function ValidFrontmatterPanels({ entry, entries, @@ -88,10 +109,11 @@ function ValidFrontmatterPanels({ workspaces?: WorkspaceIdentity[] locale: AppLocale }) { + const entryForWorkspaceActions = entryWithInferredWorkspace(entry, workspaces) return ( <> onChangeWorkspace(entry, workspace) : undefined} + onChangeWorkspace={onChangeWorkspace ? (workspace) => onChangeWorkspace(entryForWorkspaceActions, workspace) : undefined} workspaces={workspaces} locale={locale} />