mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-07-10 00:14:27 +00:00
feat(ui): show the session title in the header bar (#340)
Fixes #299 ## Summary - show the active session title in the instance header, just after the menu switch. - keep the title visible whenever the left session drawer is not pinned, using a quiet two-line header treatment without active-item highlighting. - when the unpinned drawer is open as a floating overlay, keep the title in the same left header slot so the drawer covers it instead of pushing toolbar controls around. - disable the feature in mobile view ## Validation - `git diff --check` - `npm run typecheck --workspace @codenomad/ui` - `npm run build --workspace @codenomad/ui` - visually validated in the rebuilt desktop app raw executable
This commit is contained in:
parent
4ae7baa8b4
commit
97dcc0a692
3 changed files with 146 additions and 100 deletions
|
|
@ -103,7 +103,6 @@ const App: Component = () => {
|
|||
binaryPath: string
|
||||
instanceId: string
|
||||
} | null>(null)
|
||||
|
||||
const phoneQuery = useMediaQuery("(max-width: 767px)")
|
||||
const isPhoneLayout = createMemo(() => phoneQuery())
|
||||
|
||||
|
|
|
|||
|
|
@ -514,6 +514,23 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
|||
</div>
|
||||
)
|
||||
|
||||
const renderPreviewToggleButton = () => (
|
||||
<Show when={!showingInfoView()}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={handlePreviewButtonClick}
|
||||
aria-label={previewToggleLabel()}
|
||||
title={previewToggleLabel()}
|
||||
size="small"
|
||||
>
|
||||
{(() => {
|
||||
const Icon = PreviewToggleIcon()
|
||||
return <Icon class="w-5 h-5" aria-hidden="true" />
|
||||
})()}
|
||||
</IconButton>
|
||||
</Show>
|
||||
)
|
||||
|
||||
const handleCommandPaletteClick = () => {
|
||||
showCommandPalette(props.instance.id)
|
||||
}
|
||||
|
|
@ -834,6 +851,50 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
|||
}
|
||||
|
||||
const showingInfoView = createMemo(() => activeSessionIdForInstance() === "info")
|
||||
const activeSessionTitle = createMemo(() => {
|
||||
if (showingInfoView()) return null
|
||||
const title = activeSessionForInstance()?.title?.trim()
|
||||
return title || t("sessionList.session.untitled")
|
||||
})
|
||||
const showHeaderLeftSlot = createMemo(() => !leftPinned())
|
||||
const showHeaderSessionTitle = createMemo(() => !compactHeaderLayout() && showHeaderLeftSlot() && Boolean(activeSessionTitle()))
|
||||
const headerToolbarHorizontalInset = createMemo(() => (isPhoneLayout() ? 16 : 24))
|
||||
const headerLeftSlotWidth = createMemo(() => Math.max(0, sessionSidebarWidth() - headerToolbarHorizontalInset()))
|
||||
const headerLeftSlotStyle = createMemo(() =>
|
||||
leftDrawerState() === "floating-open" || showHeaderSessionTitle() ? { width: `${headerLeftSlotWidth()}px` } : undefined,
|
||||
)
|
||||
|
||||
const renderActiveSessionHeaderTitle = () => (
|
||||
<Show when={showHeaderSessionTitle()}>
|
||||
<div
|
||||
class="session-header-active-title"
|
||||
dir="auto"
|
||||
title={activeSessionTitle() ?? undefined}
|
||||
>
|
||||
<span class="session-header-active-title-text">{activeSessionTitle()}</span>
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
|
||||
const renderHeaderLeftSlot = () => (
|
||||
<Show when={showHeaderLeftSlot()}>
|
||||
<div class="session-header-left-slot" style={headerLeftSlotStyle()}>
|
||||
<Show when={leftDrawerState() === "floating-closed"}>
|
||||
<IconButton
|
||||
ref={setLeftToggleButtonEl}
|
||||
color="inherit"
|
||||
onClick={handleLeftAppBarButtonClick}
|
||||
aria-label={leftAppBarButtonLabel()}
|
||||
size="small"
|
||||
aria-expanded={leftDrawerState() !== "floating-closed"}
|
||||
>
|
||||
{leftAppBarButtonIcon()}
|
||||
</IconButton>
|
||||
</Show>
|
||||
{renderActiveSessionHeaderTitle()}
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
|
||||
const isLaunching = createMemo(() => props.instance.status === "starting")
|
||||
|
||||
|
|
@ -933,94 +994,76 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
|||
fallback={
|
||||
<div class="flex flex-col w-full gap-1.5">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2 w-full">
|
||||
<Show when={leftDrawerState() === "floating-closed"}>
|
||||
<IconButton
|
||||
ref={setLeftToggleButtonEl}
|
||||
color="inherit"
|
||||
onClick={handleLeftAppBarButtonClick}
|
||||
aria-label={leftAppBarButtonLabel()}
|
||||
size="small"
|
||||
aria-expanded={leftDrawerState() !== "floating-closed"}
|
||||
>
|
||||
{leftAppBarButtonIcon()}
|
||||
</IconButton>
|
||||
</Show>
|
||||
{renderHeaderLeftSlot()}
|
||||
|
||||
<div class="flex-1 flex items-center justify-center min-w-0">
|
||||
{renderSessionHeaderIndicators()}
|
||||
</div>
|
||||
<div class="flex-1 flex items-center justify-center min-w-0">
|
||||
{renderSessionHeaderIndicators()}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-center gap-1">
|
||||
<Show when={!showingInfoView()}>
|
||||
<div class="flex flex-wrap items-center justify-center gap-1">
|
||||
<Show when={!showingInfoView()}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={handleChatSearchClick}
|
||||
aria-label={t("instanceShell.chatSearch.openAriaLabel")}
|
||||
title={t("instanceShell.chatSearch.openAriaLabel")}
|
||||
size="small"
|
||||
>
|
||||
<Search class="w-5 h-5" aria-hidden="true" />
|
||||
</IconButton>
|
||||
</Show>
|
||||
<button
|
||||
type="button"
|
||||
class="connection-status-button command-palette-button"
|
||||
onClick={handleCommandPaletteClick}
|
||||
aria-label={t("instanceShell.commandPalette.openAriaLabel")}
|
||||
style={{ flex: "0 0 auto", width: "auto" }}
|
||||
>
|
||||
{t("instanceShell.commandPalette.button")}
|
||||
</button>
|
||||
<span class="connection-status-shortcut-hint kbd-hint">
|
||||
<Kbd shortcut="cmd+shift+p" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex items-center justify-center min-w-0">
|
||||
<span
|
||||
class={`status-indicator ${connectionStatusClass()}`}
|
||||
aria-label={t("instanceShell.connection.ariaLabel", { status: connectionStatusLabel() })}
|
||||
>
|
||||
<span class="status-dot" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Show when={!isPhoneLayout()}>
|
||||
{renderPreviewToggleButton()}
|
||||
</Show>
|
||||
|
||||
<Show when={isPhoneLayout() && !props.mobileFullscreenMode}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={handleChatSearchClick}
|
||||
aria-label={t("instanceShell.chatSearch.openAriaLabel")}
|
||||
title={t("instanceShell.chatSearch.openAriaLabel")}
|
||||
onClick={props.onEnterMobileFullscreen}
|
||||
aria-label={t("instanceShell.fullscreen.enter")}
|
||||
title={t("instanceShell.fullscreen.enter")}
|
||||
size="small"
|
||||
>
|
||||
<Search class="w-5 h-5" aria-hidden="true" />
|
||||
<Maximize2 class="w-5 h-5" aria-hidden="true" />
|
||||
</IconButton>
|
||||
{renderPreviewToggleButton()}
|
||||
</Show>
|
||||
|
||||
<Show when={rightDrawerState() === "floating-closed"}>
|
||||
<IconButton
|
||||
ref={setRightToggleButtonEl}
|
||||
color="inherit"
|
||||
onClick={handlePreviewButtonClick}
|
||||
aria-label={previewToggleLabel()}
|
||||
title={previewToggleLabel()}
|
||||
onClick={handleRightAppBarButtonClick}
|
||||
aria-label={rightAppBarButtonLabel()}
|
||||
size="small"
|
||||
aria-expanded={rightDrawerState() !== "floating-closed"}
|
||||
>
|
||||
{(() => {
|
||||
const Icon = PreviewToggleIcon()
|
||||
return <Icon class="w-5 h-5" aria-hidden="true" />
|
||||
})()}
|
||||
{rightAppBarButtonIcon()}
|
||||
</IconButton>
|
||||
</Show>
|
||||
<button
|
||||
type="button"
|
||||
class="connection-status-button command-palette-button"
|
||||
onClick={handleCommandPaletteClick}
|
||||
aria-label={t("instanceShell.commandPalette.openAriaLabel")}
|
||||
style={{ flex: "0 0 auto", width: "auto" }}
|
||||
>
|
||||
{t("instanceShell.commandPalette.button")}
|
||||
</button>
|
||||
<span class="connection-status-shortcut-hint kbd-hint">
|
||||
<Kbd shortcut="cmd+shift+p" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex items-center justify-center min-w-0">
|
||||
<span
|
||||
class={`status-indicator ${connectionStatusClass()}`}
|
||||
aria-label={t("instanceShell.connection.ariaLabel", { status: connectionStatusLabel() })}
|
||||
>
|
||||
<span class="status-dot" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Show when={isPhoneLayout() && !props.mobileFullscreenMode}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={props.onEnterMobileFullscreen}
|
||||
aria-label={t("instanceShell.fullscreen.enter")}
|
||||
title={t("instanceShell.fullscreen.enter")}
|
||||
size="small"
|
||||
>
|
||||
<Maximize2 class="w-5 h-5" aria-hidden="true" />
|
||||
</IconButton>
|
||||
</Show>
|
||||
|
||||
<Show when={rightDrawerState() === "floating-closed"}>
|
||||
<IconButton
|
||||
ref={setRightToggleButtonEl}
|
||||
color="inherit"
|
||||
onClick={handleRightAppBarButtonClick}
|
||||
aria-label={rightAppBarButtonLabel()}
|
||||
size="small"
|
||||
aria-expanded={rightDrawerState() !== "floating-closed"}
|
||||
>
|
||||
{rightAppBarButtonIcon()}
|
||||
</IconButton>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-center gap-2 pb-1">
|
||||
|
|
@ -1038,18 +1081,7 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
|||
}
|
||||
>
|
||||
<div class="session-toolbar-left flex-1 flex items-center gap-3 min-w-0">
|
||||
<Show when={leftDrawerState() === "floating-closed"}>
|
||||
<IconButton
|
||||
ref={setLeftToggleButtonEl}
|
||||
color="inherit"
|
||||
onClick={handleLeftAppBarButtonClick}
|
||||
aria-label={leftAppBarButtonLabel()}
|
||||
size="small"
|
||||
aria-expanded={leftDrawerState() !== "floating-closed"}
|
||||
>
|
||||
{leftAppBarButtonIcon()}
|
||||
</IconButton>
|
||||
</Show>
|
||||
{renderHeaderLeftSlot()}
|
||||
|
||||
<Show when={!showingInfoView()}>
|
||||
<ContextMeter
|
||||
|
|
@ -1095,18 +1127,7 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
|||
>
|
||||
<Search class="w-5 h-5" aria-hidden="true" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={handlePreviewButtonClick}
|
||||
aria-label={previewToggleLabel()}
|
||||
title={previewToggleLabel()}
|
||||
size="small"
|
||||
>
|
||||
{(() => {
|
||||
const Icon = PreviewToggleIcon()
|
||||
return <Icon class="w-5 h-5" aria-hidden="true" />
|
||||
})()}
|
||||
</IconButton>
|
||||
{renderPreviewToggleButton()}
|
||||
</Show>
|
||||
<Show when={connectionStatus() === "connected"}>
|
||||
<span class="status-indicator connected">
|
||||
|
|
|
|||
|
|
@ -105,6 +105,32 @@
|
|||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.session-header-left-slot {
|
||||
@apply flex items-center gap-2 min-w-0;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.session-header-active-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
padding-inline: 0.75rem;
|
||||
border-inline: 1px solid color-mix(in oklab, var(--border-base) 72%, transparent);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.session-header-active-title-text {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.session-sidebar-shortcuts {
|
||||
@apply flex flex-col gap-1;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue