fix: keep the folders a half-typed path already names

The trailing-separator guard gave up on `f/demo/` entirely, which dropped the
whole trail to the kind chip and brought `f/demo` back on the first character of
the name. The levels the path does name come back now; only the leaf waits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Guilhem Lemouel
2026-10-01 00:27:21 -07:00
co-authored by Claude Opus 5
parent 1d2e032224
commit f2768de00c
3 changed files with 22 additions and 9 deletions
@@ -156,6 +156,8 @@
onPick={handlePickerSelect}
/>
{/each}
{/if}
{#if segments?.leaf}
{@const leafKey = leafKeyFor(kind, segments.leaf.fullPath)}
{@const leafParent = segments.dirs[segments.dirs.length - 1]?.fullPath}
<ChevronRight size={10} class="shrink-0" />
@@ -12,9 +12,16 @@ describe('splitItemPath', () => {
})
})
it('gives up on a path that names nothing yet, trailing separator included', () => {
// The editor binds the path live, so every keystroke reaches the breadcrumb.
it('gives up before there is a scope', () => {
expect(splitItemPath('f/demo')).toBeUndefined()
expect(splitItemPath('f/demo/')).toBeUndefined()
})
it('keeps the folders while the name is still being typed', () => {
// The editor binds the path live, so every keystroke reaches the breadcrumb: the trail must
// not drop out and come back on the first character of the name.
expect(splitItemPath('f/demo/')).toEqual({
dirs: [{ name: 'f/demo', fullPath: 'f/demo' }],
leaf: undefined
})
})
})
+10 -6
View File
@@ -6,21 +6,25 @@ export type PathDir = { name: string; fullPath: string }
* `u/<user>`), then each subfolder, then the item itself. Each level carries the cumulative path,
* which is what the picker opens and highlights.
*
* Returns undefined for anything shorter than scope + name, such as a path still being typed.
* Returns undefined before there is even a scope. The leaf is undefined while the path names a
* folder but no item yet (`f/demo/`) — the levels it does name still come back.
*/
export function splitItemPath(path: string): { dirs: PathDir[]; leaf: PathDir } | undefined {
export function splitItemPath(
path: string
): { dirs: PathDir[]; leaf: PathDir | undefined } | undefined {
const parts = path.split('/')
if (parts.length < 3) return undefined
const scope = parts.slice(0, 2).join('/')
const slug = parts.slice(2)
// `f/demo/` is three parts but names nothing yet, and a leaf with an empty name draws a blank
// segment. The editor's path is bound live, so the breadcrumb sees every keystroke.
if (slug[slug.length - 1] === '') return undefined
const dirs: PathDir[] = [{ name: scope, fullPath: scope }]
let acc = scope
for (let i = 0; i < slug.length - 1; i++) {
acc = `${acc}/${slug[i]}`
dirs.push({ name: slug[i], fullPath: acc })
}
return { dirs, leaf: { name: slug[slug.length - 1], fullPath: path } }
// A leaf with an empty name draws a blank segment. The editor binds the path live, so the
// breadcrumb sees every keystroke: the folders stay put while the name is being typed rather
// than the whole trail dropping out and coming back.
const name = slug[slug.length - 1]
return { dirs, leaf: name ? { name, fullPath: path } : undefined }
}