From f2768de00c357dd1bfcbb5a17685d38f6a85e720 Mon Sep 17 00:00:00 2001 From: Guilhem Lemouel Date: Thu, 1 Oct 2026 00:27:21 -0700 Subject: [PATCH] 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) --- frontend/src/lib/components/EditorHeader.svelte | 2 ++ .../src/lib/components/breadcrumbPath.test.ts | 13 ++++++++++--- frontend/src/lib/components/breadcrumbPath.ts | 16 ++++++++++------ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/frontend/src/lib/components/EditorHeader.svelte b/frontend/src/lib/components/EditorHeader.svelte index 82be29b658..e0f6ac04fb 100644 --- a/frontend/src/lib/components/EditorHeader.svelte +++ b/frontend/src/lib/components/EditorHeader.svelte @@ -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} diff --git a/frontend/src/lib/components/breadcrumbPath.test.ts b/frontend/src/lib/components/breadcrumbPath.test.ts index 987403e2e2..427b153800 100644 --- a/frontend/src/lib/components/breadcrumbPath.test.ts +++ b/frontend/src/lib/components/breadcrumbPath.test.ts @@ -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 + }) }) }) diff --git a/frontend/src/lib/components/breadcrumbPath.ts b/frontend/src/lib/components/breadcrumbPath.ts index 25834181ad..880d558360 100644 --- a/frontend/src/lib/components/breadcrumbPath.ts +++ b/frontend/src/lib/components/breadcrumbPath.ts @@ -6,21 +6,25 @@ export type PathDir = { name: string; fullPath: string } * `u/`), 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 } }