feat: an app's Edit sits with its name

At the far end of the bar it was a journey across the window from the thing it edits —
on a page whose header is only there while the pointer holds it, a journey with a time
limit. It goes beside the app's path instead.

The slot it uses is the one Runs' documentation tooltip already had, renamed from `hint`
to `afterName` and documented for both: what belongs to the page's name, whether that is
a mark or a control that acts on what the name points at.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Guilhem Lemouel
2026-09-23 17:40:38 +02:00
co-authored by Claude Opus 5
parent 13ccf24598
commit 8f0b16e933
6 changed files with 19 additions and 15 deletions
@@ -32,13 +32,14 @@ same weight, size and icon size, so the line reads as one control rather than fo
let {
item,
section,
hint,
afterName,
actingWorkspaceId
}: {
item?: PageHeaderItem
section?: PageHeaderSection
/** A page's own mark next to its name, such as its documentation tooltip. */
hint?: Snippet
/** What sits beside the page's name: a mark like a documentation tooltip, or a control
* that acts on the thing named. */
afterName?: Snippet
/** The workspace the page acts on, when it differs from the one the app is pointed at. */
actingWorkspaceId?: string
} = $props()
@@ -262,7 +263,7 @@ same weight, size and icon size, so the line reads as one control rather than fo
<span class="truncate">{level}</span>
{/each}
</button>
{#if hint}{@render hint()}{/if}
{#if afterName}{@render afterName()}{/if}
{#if pathCopied}
<span
class="absolute left-0 top-full mt-1 z-[6000] rounded-md border bg-surface px-2 py-1 text-2xs text-secondary shadow-md whitespace-nowrap"
@@ -296,7 +297,7 @@ same weight, size and icon size, so the line reads as one control rather than fo
>
{#if section.content}{@render section.content()}{:else}{section.label}{/if}
</span>
{#if hint}{@render hint()}{/if}
{#if afterName}{@render afterName()}{/if}
{:else if routePage}
{@const RouteIcon = routePage.icon}
<!-- shrink-0: a page's own name is short and is the one part of the trail worth keeping
@@ -305,6 +306,6 @@ same weight, size and icon size, so the line reads as one control rather than fo
{#if RouteIcon}<RouteIcon size={ICON} class="flex-shrink-0 text-tertiary" />{/if}
<span class="truncate">{routePage.label}</span>
</span>
{#if hint}{@render hint()}{/if}
{#if afterName}{@render afterName()}{/if}
{/if}
</nav>
@@ -82,7 +82,7 @@ The row's height matches the sidebar's own header row, so the two read as one ba
<NavBreadcrumb
{item}
{section}
hint={content?.hint}
afterName={content?.afterName}
actingWorkspaceId={content?.actingWorkspaceId}
/>
</div>
@@ -13,7 +13,7 @@ the header, so nesting is decided by the caller rather than guessed from context
let {
item,
section,
hint,
afterName,
actions,
actionsOrder,
contexts,
@@ -26,7 +26,7 @@ the header, so nesting is decided by the caller rather than guessed from context
const id = pageHeader.register(() => ({
item,
section,
hint,
afterName,
actions,
actionsOrder,
contexts,
+1 -1
View File
@@ -869,7 +869,7 @@
<div class="w-full h-screen flex flex-col" bind:clientWidth={innerWidth}>
<!-- Everything that used to sit in a row above the graph — the page's name, its queue, the
filters and the timeframe — is in the page header. -->
<PageHeaderContent hint={runsHint} actions={headerActions} />
<PageHeaderContent afterName={runsHint} actions={headerActions} />
<!-- Graph -->
<div id="runs-chart" class="p-2 px-4 bg-surface-tertiary mx-4 mt-2 border rounded-md">
@@ -161,7 +161,9 @@
{#if canWriteApp && !hideEditBtn}
<!-- The page header carries it, with the app's name: a floating button over the app's own
canvas lands on whatever the app draws there. -->
<PageHeaderContent item={{ kind: 'app', path }} actions={editAction} fullBleed />
<!-- Edit sits with the app's name rather than at the far end of the bar: on a page whose header
is only there while hovered, the far end is a journey across the window. -->
<PageHeaderContent item={{ kind: 'app', path }} afterName={editAction} fullBleed />
{/if}
{#snippet editAction()}
@@ -29,9 +29,10 @@ export type PageHeaderContent = {
/** Orders these buttons among the other registrations', ascending; registration order breaks a
* tie. For a menu that must stay at the far end however many pages register before it. */
actionsOrder?: number
/** Rendered right after the page's name in the breadcrumb, for what reads as part of that name
* rather than as an action — a page's documentation tooltip. */
hint?: Snippet
/** Rendered right after the page's name in the breadcrumb: a mark that belongs to the name,
* like a documentation tooltip, or a control that acts on the thing named — an app's Edit,
* which at the far end of the bar would be a journey away from what it edits. */
afterName?: Snippet
/** The workspace this page acts on, when it is not the one the app is pointed at — a session
* running in a fork. The breadcrumb then names that workspace, and its fork part shows only
* when it really is a fork. */
@@ -74,7 +75,7 @@ export const pageHeader = {
const c = e.get()
if (c.item !== undefined) merged.item = c.item
if (c.section !== undefined) merged.section = c.section
if (c.hint !== undefined) merged.hint = c.hint
if (c.afterName !== undefined) merged.afterName = c.afterName
if (c.contexts !== undefined) merged.contexts = c.contexts
if (c.actingWorkspaceId !== undefined) merged.actingWorkspaceId = c.actingWorkspaceId
if (c.barRightInset !== undefined) merged.barRightInset = c.barRightInset