fix: the band names the path Deploy will create, and keeps the summary readable

A brand-new app is parked at a `draft_<uuid>` placeholder, and the band named
that rather than the path its Deploy will create — `newEditedPath` refuses the
placeholder for exactly this reason, and the editor's own bar read it. Both app
editors register it now. Verified: the trail reads `u/admin/gentle_app` while the
URL is still the draft.

The band's summary gets the `title` the editor's own bar had, so one the bar
truncates can still be read on hover.

The wrapper that hid AppExportButton on a phone never hid anything:
the component draws no trigger — it is the export drawer, which portals to
`body` — so the class toggle was inert in both states and the comment read as if
something moved. Dropped from both app editors.

Two comments corrected: `NAV_PAGES`' `icon` does reach the breadcrumb for all
eleven entries (`NavBreadcrumb` renders `routePage.icon`), so only `operatorKey`
goes unread for the four the sidebar groups separately; and the raw editor's
phone note described folding that does not happen — the file-sidebar toggle stays
in the band, Jobs and the export are already in the menu, and AI is what stands
down.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Guilhem Lemouel
2026-10-01 00:42:11 -07:00
co-authored by Claude Opus 5
parent 379858a97d
commit de42171f93
3 changed files with 56 additions and 36 deletions
@@ -943,7 +943,14 @@
<!-- The editor's own top bar is the page header on this route: the app's path and summary are
the breadcrumb's, and everything else the bar carried rides along as the header's actions. -->
<PageHeaderContent
item={{ kind: 'app', path: $appPath || newPath || undefined, summaryContent: appSummary }}
item={{
// The path being edited, not the stored one: a brand-new app is parked at a
// `draft_<uuid>` placeholder, and the trail would name that instead of the path Deploy
// will create. Same chain the rename sites read, and what the editor's own bar showed.
kind: 'app',
path: newEditedPath || $appPath || newPath || undefined,
summaryContent: appSummary
}}
actions={appHeaderActions}
contexts={headerContexts}
/>
@@ -979,16 +986,19 @@
{#snippet appSummary()}
<!-- The summary stays editable where the editor's own bar had it; the path beside it is the
breadcrumb, which this editor renames from its deploy drawer. -->
<EditableInput
value={$summary ?? ''}
placeholder="Add a summary..."
commitOnInput
size="sm"
onSave={(v) => ($summary = v.trim())}
textClass="text-xs font-medium text-emphasis leading-tight"
class="max-w-full min-w-0"
/>
breadcrumb, which this editor renames from its deploy drawer. `title` so a summary the
band truncates can still be read, the way the editor's own bar did it. -->
<div class="max-w-full min-w-0" title={$summary}>
<EditableInput
value={$summary ?? ''}
placeholder="Add a summary..."
commitOnInput
size="sm"
onSave={(v) => ($summary = v.trim())}
textClass="text-xs font-medium text-emphasis leading-tight"
class="max-w-full min-w-0"
/>
</div>
{/snippet}
{#snippet appHeaderActions()}
@@ -1213,11 +1223,10 @@
</div>
</Button>
</div>
<!-- The export is in the menu on a phone; the preview switch keeps its place there, since
<!-- Draws nothing in the bar: it is the export drawer, opened from the menu's Export
entries through this instance. The preview switch keeps its place on a phone, since
seeing the app is most of what an app editor is good for on one. -->
<div class={phoneTopbar ? 'hidden' : 'contents'}>
<AppExportButton bind:this={appExport} />
</div>
<AppExportButton bind:this={appExport} />
<PreviewToggle loading={loading.save} iconOnly={phoneTopbar} />
<Button
variant="accent"
@@ -323,8 +323,9 @@
const w = ownsPageHeader ? pageHeader.barWidth : topbarWidth
return w > 0 && w < compactBelow
})
// A phone's bar holds the trail and Deploy. The file sidebar's toggle, Jobs and the export
// join the menu that compact already shows.
// A phone's bar keeps the trail, the file sidebar's toggle, Diff and Deploy. AI stands down —
// the sidebar's own Ask AI is the way in at that width — while Jobs and the export are already
// in the menu, Jobs from `compactTopbar` (true at any phone width) and the export always.
const phoneTopbar = $derived(
ownsPageHeader && pageHeader.barWidth > 0 && pageHeader.barWidth < PHONE_BAR
)
@@ -933,7 +934,14 @@
the breadcrumb's, and everything else the bar carried rides along as the header's actions.
The path is a plain label there — this editor renames from its deploy drawer. -->
<PageHeaderContent
item={{ kind: 'app', path: appPath || newPath || undefined, summaryContent: appSummary }}
item={{
// The path being edited, not the stored one: a brand-new app is parked at a
// `draft_<uuid>` placeholder, and the trail would name that instead of the path Deploy
// will create — which is why `newEditedPath` refuses the placeholder above.
kind: 'app',
path: newEditedPath || appPath || newPath || undefined,
summaryContent: appSummary
}}
actions={rawAppHeaderActions}
contexts={headerContexts}
/>
@@ -1010,16 +1018,19 @@
{#snippet appSummary()}
<!-- The summary stays editable where the editor's own bar had it; the path beside it is the
breadcrumb, which this editor renames from its deploy drawer. -->
<EditableInput
value={summary ?? ''}
placeholder="Add a summary..."
commitOnInput
size="sm"
onSave={(v) => (summary = v.trim())}
textClass="text-xs font-medium text-emphasis leading-tight"
class="max-w-full min-w-0"
/>
breadcrumb, which this editor renames from its deploy drawer. `title` so a summary the
band truncates can still be read, the way the editor's own bar did it. -->
<div class="max-w-full min-w-0" title={summary}>
<EditableInput
value={summary ?? ''}
placeholder="Add a summary..."
commitOnInput
size="sm"
onSave={(v) => (summary = v.trim())}
textClass="text-xs font-medium text-emphasis leading-tight"
class="max-w-full min-w-0"
/>
</div>
{/snippet}
{#snippet rawAppHeaderActions()}
@@ -1091,10 +1102,9 @@
</div>
</Button>
</div>
<!-- Hidden, not unmounted: the menu's own Export entry calls into this instance. -->
<span class={phoneTopbar ? 'hidden' : 'contents'}>
<AppExportButton bind:this={appExport} />
</span>
<!-- Draws nothing in the bar: it is the export drawer, opened from the menu's Export entry
through this instance. -->
<AppExportButton bind:this={appExport} />
{#if !phoneTopbar}
<OpenInSessionButton source={sessionOpen}>
{#snippet fallback()}
@@ -17,10 +17,11 @@ import type { OperatorPageKey } from './operatorRoutes'
* The names the page header's breadcrumb gives the workspace's own pages, and the rows the
* sidebar's main group is built from.
*
* Every entry names a page for the breadcrumb. Only the entries `SidebarContent`'s
* `MAIN_MENU_EXTRAS` also lists become sidebar links from here — Schedules, HTTP routes, Workers
* and Audit logs are grouped differently there and carry their own row, so for those four the
* `icon` and `operatorKey` below go unread. Keep the labels in step with those rows by hand.
* Every entry names a page for the breadcrumb, and its `icon` goes there too. Only the entries
* `SidebarContent`'s `MAIN_MENU_EXTRAS` also lists become sidebar links from here — Schedules,
* HTTP routes, Workers and Audit logs are grouped differently there and carry their own row, so
* for those four the `operatorKey` below goes unread. Keep the labels in step with those rows by
* hand.
*/
export type NavPage = {
label: string