feat: the pen renames the item and its summary

The pen opened the path alone, which left the summary with nowhere to be edited
from the band. It carries a Summary field above the path now — the item's name
first, the address it lives at below — and the band's text follows it as the user
types.

`TextInput` with a label rather than the bar's inline field: `EditableInput`'s own
docs rule itself out inside a popover, and it wraps this same input in its edit
mode, so the field, the "Add a summary..." placeholder and the trim on commit are
the behaviour that was there, in the shape the deploy drawer's Summary field
already uses. The keydown stops at the field because every editor binds keys of
its own and a summary is prose.

`summaryEditable` carries the hosts' `customUi.topBar.editableSummary` through,
and the editors' own bar passes it false: that bar keeps its inline field, and
`hidePath` would take the pen with it, leaving a condensed session pane no way to
name the item at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Guilhem Lemouel
2026-10-01 01:47:37 -07:00
co-authored by Claude Opus 5
parent 4f3804cf11
commit d7c7abbccf
6 changed files with 40 additions and 4 deletions
@@ -145,9 +145,12 @@
<!-- Skipped entirely when path editing is disabled so the user doesn't see an inert
button. -->
{#if pathEditable}
<!-- Path only: this bar carries its own summary field below, and `hidePath` would take
the pen with it, leaving a condensed pane no way to name the item. -->
<PathEditPopover
bind:path
bind:snapshotPath
summaryEditable={false}
{savedPath}
{kind}
{workspaceId}
@@ -1663,6 +1663,8 @@
>
{#if customUi?.topBar?.editablePath != false}
<PathEditPopover
bind:summary={flowStore.val.summary}
summaryEditable={customUi?.topBar?.editableSummary != false}
bind:path={$pathStore}
bind:snapshotPath={pathSnapshot}
savedPath={initialPath}
@@ -1,7 +1,6 @@
<!--
@component
The pen that opens an item's path for editing, beside the breadcrumb that shows it. Renders
nothing when the path is not the caller's to change.
The pen that opens an item's summary and path for editing, beside the breadcrumb that shows them.
A caller that draws the path itself should render it from `snapshotPath ?? path`: while the
popover is open this holds the path as it was when it opened, so the trail — and the pen anchored
@@ -13,6 +12,7 @@ to it — does not reflow under the pointer as the user types, which floating-ui
import Popover from '$lib/components/meltComponents/Popover.svelte'
import Path from '$lib/components/Path.svelte'
import Label from '$lib/components/Label.svelte'
import TextInput from '$lib/components/text_input/TextInput.svelte'
import type { WorkspaceItemKind } from '$lib/components/workspacePicker'
import { isOwner } from '$lib/utils'
import { userStore } from '$lib/stores'
@@ -26,6 +26,10 @@ to it — does not reflow under the pointer as the user types, which floating-ui
const actingUser = $derived(operatingUser.current)
interface Props {
/** The item's human name. Committed trimmed, like the bar's own field did. */
summary?: string
/** When false the summary is shown but not editable, for a host whose `customUi` says so. */
summaryEditable?: boolean
path?: string
/** The item's *saved* path on the server, so the popover can say a rename needs deploying. */
savedPath?: string
@@ -41,6 +45,8 @@ to it — does not reflow under the pointer as the user types, which floating-ui
}
let {
summary = $bindable(),
summaryEditable = true,
path = $bindable(),
savedPath,
kind = 'flow',
@@ -80,8 +86,8 @@ to it — does not reflow under the pointer as the user types, which floating-ui
unifiedSize="xs"
iconOnly
startIcon={{ icon: Pencil }}
title="Edit path"
aria-label="Edit path"
title={summaryEditable ? 'Edit summary and path' : 'Edit path'}
aria-label={summaryEditable ? 'Edit summary and path' : 'Edit path'}
btnClasses={penVisibility === 'hover' && !open
? 'opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100'
: ''}
@@ -89,6 +95,27 @@ to it — does not reflow under the pointer as the user types, which floating-ui
{/snippet}
{#snippet content()}
<div class="flex flex-col gap-6 w-[480px]">
<!-- The summary first: it is the name a reader sees, and the path below is where that
name lives. Not autofocused — the pen's own job is the path, which takes the cursor. -->
{#if summaryEditable}
<Label label="Summary">
<TextInput
bind:value={summary}
inputProps={{
placeholder: 'Add a summary...',
// The popover sits in a page that binds keys of its own (the editors all do),
// and a summary is prose — it must not reach them.
onkeydown: (e: KeyboardEvent) => e.stopPropagation(),
onblur: () => (summary = summary?.trim())
}}
size="sm"
/>
</Label>
{:else if summary}
<Label label="Summary">
<span class="text-xs text-secondary">{summary}</span>
</Label>
{/if}
{#if own}
<Path
autofocus
@@ -2347,6 +2347,8 @@
>
{#if customUi?.topBar?.editablePath != false}
<PathEditPopover
bind:summary={script.summary}
summaryEditable={customUi?.topBar?.editableSummary != false}
bind:path={script.path}
bind:snapshotPath={pathSnapshot}
savedPath={initialPath}
@@ -998,6 +998,7 @@
>{$summary}</span
>
<PathEditPopover
bind:summary={$summary}
bind:path={newEditedPath}
bind:snapshotPath={pathSnapshot}
savedPath={$appPath || newPath || undefined}
@@ -1028,6 +1028,7 @@
<span class="min-w-0 truncate text-xs font-medium text-emphasis" title={summary}>{summary}</span
>
<PathEditPopover
bind:summary
bind:path={newEditedPath}
bind:snapshotPath={pathSnapshot}
savedPath={appPath || undefined}