Merge branch 'main' into fix/695-claude-codex-windows-spawn-shim

This commit is contained in:
github-actions[bot]
2026-05-20 16:51:50 +00:00
committed by GitHub
3 changed files with 153 additions and 5 deletions

View File

@@ -119,6 +119,20 @@ describe('resolveEntry', () => {
expect(resolveEntry([alpha, alphaArchived], 'archive/alpha')).toBe(alphaArchived)
})
it('resolves full-path wikilinks to non-Markdown duplicate filenames', () => {
const yamlA = makeEntry({ path: '/vault/a/file.yml', filename: 'file.yml', title: 'file.yml', fileKind: 'text' })
const yamlB = makeEntry({ path: '/vault/b/file.yml', filename: 'file.yml', title: 'file.yml', fileKind: 'text' })
const pdfA = makeEntry({ path: '/vault/a/document.pdf', filename: 'document.pdf', title: 'document.pdf', fileKind: 'binary' })
const pdfB = makeEntry({ path: '/vault/b/document.pdf', filename: 'document.pdf', title: 'document.pdf', fileKind: 'binary' })
const targetA = makeEntry({ path: '/vault/a/target.md', filename: 'target.md', title: 'Target' })
const targetB = makeEntry({ path: '/vault/b/target.md', filename: 'target.md', title: 'Target' })
const duplicateEntries = [yamlA, yamlB, pdfA, pdfB, targetA, targetB]
expect(resolveEntry(duplicateEntries, 'b/file.yml')).toBe(yamlB)
expect(resolveEntry(duplicateEntries, 'b/document.pdf')).toBe(pdfB)
expect(resolveEntry(duplicateEntries, 'b/target.md')).toBe(targetB)
})
it('resolves workspace-prefixed targets inside the matching mounted workspace', () => {
const personal = makeEntry({
path: '/personal/projects/alpha.md',

View File

@@ -101,7 +101,7 @@ interface ResolutionKey {
workspaceAlias: string | null
targetWithoutWorkspace: string
lastSegment: string
pathSuffix: string | null
pathSuffixes: string[]
humanizedTarget: string | null
}
@@ -115,6 +115,10 @@ function buildResolutionKey(rawTarget: WikilinkTarget, knownWorkspaceAliases: Se
: null
const targetWithoutWorkspace = workspaceAlias ? segments.slice(1).join('/') : exactTarget
const normalizedLocalTarget = targetWithoutWorkspace.toLowerCase()
const normalizedPathTarget = normalizedLocalTarget.replace(/^\/+/, '')
const pathSuffixes = normalizedPathTarget.includes('/')
? [`/${normalizedPathTarget}`, ...normalizedPathTarget.endsWith('.md') ? [] : [`/${normalizedPathTarget}.md`]]
: []
const lastSegment = targetWithoutWorkspace.includes('/') ? (targetWithoutWorkspace.split('/').pop() ?? targetWithoutWorkspace).toLowerCase() : normalizedLocalTarget
const humanizedTarget = lastSegment.replace(/-/g, ' ')
@@ -123,7 +127,7 @@ function buildResolutionKey(rawTarget: WikilinkTarget, knownWorkspaceAliases: Se
workspaceAlias,
targetWithoutWorkspace: normalizedLocalTarget,
lastSegment,
pathSuffix: targetWithoutWorkspace.includes('/') ? `/${normalizedLocalTarget}.md` : null,
pathSuffixes,
humanizedTarget: humanizedTarget === normalizedLocalTarget ? null : humanizedTarget,
}
}
@@ -143,9 +147,8 @@ function prioritizeSourceWorkspace(entries: VaultEntry[], sourceEntry?: VaultEnt
}
function findEntryByPathSuffix(entries: VaultEntry[], resolutionKey: ResolutionKey): VaultEntry | undefined {
if (!resolutionKey.pathSuffix) return undefined
const { pathSuffix } = resolutionKey
return entries.find(entry => entry.path.toLowerCase().endsWith(pathSuffix))
if (resolutionKey.pathSuffixes.length === 0) return undefined
return entries.find(entry => resolutionKey.pathSuffixes.some(pathSuffix => entry.path.toLowerCase().endsWith(pathSuffix)))
}
function findEntryByFilename(entries: VaultEntry[], { exactTarget, targetWithoutWorkspace, lastSegment }: ResolutionKey): VaultEntry | undefined {

View File

@@ -0,0 +1,131 @@
import { test, expect, type Page } from '@playwright/test'
import fs from 'fs'
import path from 'path'
import {
createFixtureVaultCopy,
openFixtureVaultDesktopHarness,
removeFixtureVaultCopy,
} from '../helpers/fixtureVault'
const SOURCE_TITLE = 'Duplicate Attachment Links'
const SOURCE_RELATIVE_PATH = path.join('b', 'note.md')
const YAML_TARGET = path.join('b', 'file.yml')
const YAML_MARKER = 'selected-folder: b'
let tempVaultDir: string
function writeDuplicateLinkFixture(vaultPath: string): void {
fs.mkdirSync(path.join(vaultPath, 'a'), { recursive: true })
fs.mkdirSync(path.join(vaultPath, 'b'), { recursive: true })
fs.writeFileSync(path.join(vaultPath, 'a', 'file.yml'), 'selected-folder: a\n')
fs.writeFileSync(path.join(vaultPath, 'b', 'file.yml'), `${YAML_MARKER}\n`)
fs.writeFileSync(path.join(vaultPath, SOURCE_RELATIVE_PATH), `---
type: Note
---
# ${SOURCE_TITLE}
- YAML: [[b/file.yml|b/file.yml]]
`)
}
function buildFileEntry(vaultPath: string, relativePath: string) {
const filePath = path.join(vaultPath, relativePath)
const filename = path.basename(filePath)
return {
path: filePath,
filename,
title: filename,
isA: null,
aliases: [],
belongsTo: [],
relatedTo: [],
status: null,
archived: false,
modifiedAt: Date.now(),
createdAt: null,
fileSize: fs.statSync(filePath).size,
snippet: '',
wordCount: 0,
relationships: {},
icon: null,
color: null,
order: null,
sidebarLabel: null,
template: null,
sort: null,
view: null,
visible: null,
organized: false,
favorite: false,
favoriteIndex: null,
listPropertiesDisplay: [],
outgoingLinks: [],
properties: {},
hasH1: false,
fileKind: 'text',
}
}
async function includeNonMarkdownEntries(page: Page, vaultPath: string): Promise<void> {
const duplicateEntries = [
buildFileEntry(vaultPath, path.join('a', 'file.yml')),
buildFileEntry(vaultPath, YAML_TARGET),
]
await page.route('**/api/vault/list*', async (route) => {
const response = await route.fetch()
const entries = await response.json()
if (!Array.isArray(entries)) {
await route.fulfill({ response })
return
}
const existingPaths = new Set(entries.map((entry) => entry?.path).filter(Boolean))
const nextEntries = [
...entries,
...duplicateEntries.filter((entry) => !existingPaths.has(entry.path)),
]
await route.fulfill({ response, json: nextEntries })
})
}
async function readRawEditorContent(page: Page): Promise<string> {
return page.evaluate(() => {
const host = document.querySelector('.cm-content')
if (!host) return ''
type CodeMirrorHost = Element & {
cmTile?: {
view?: {
state: { doc: { toString(): string } }
}
}
}
return (host as CodeMirrorHost).cmTile?.view?.state.doc.toString() ?? host.textContent ?? ''
})
}
test.describe('non-Markdown duplicate wikilinks', () => {
test.beforeEach(() => {
tempVaultDir = createFixtureVaultCopy()
writeDuplicateLinkFixture(tempVaultDir)
})
test.afterEach(() => {
removeFixtureVaultCopy(tempVaultDir)
})
test('full-path wikilinks open the matching non-Markdown duplicate', async ({ page }) => {
await includeNonMarkdownEntries(page, tempVaultDir)
await openFixtureVaultDesktopHarness(page, tempVaultDir, { expectedReadyTitle: SOURCE_TITLE })
await page.getByText(SOURCE_TITLE, { exact: true }).first().click()
const link = page.locator('.bn-editor [data-target="b/file.yml|b/file.yml"]').first()
await expect(link).toBeVisible({ timeout: 5_000 })
await link.click({ modifiers: ['Meta'] })
await expect(page.getByTestId('raw-editor-codemirror')).toBeVisible({ timeout: 5_000 })
await expect.poll(() => readRawEditorContent(page)).toContain(YAML_MARKER)
})
})