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 }
}