diff --git a/apps/desktop/src/renderer/styles/chat-header.css b/apps/desktop/src/renderer/styles/chat-header.css index 13416f06ed..a26249d76e 100644 --- a/apps/desktop/src/renderer/styles/chat-header.css +++ b/apps/desktop/src/renderer/styles/chat-header.css @@ -84,6 +84,16 @@ -webkit-app-region: no-drag; } +/* A live Goal can keep creating Turns after the user stops typing, so its + identity and kill switches stay reachable while the transcript scrolls. + Ordinary lineage/revision context remains in-flow when no Goal is present. */ +.maka-session-context[data-has-goal="true"] { + position: sticky; + top: 0; + z-index: var(--z-sticky); + box-shadow: 0 var(--border-width-hairline) 0 var(--border-soft); +} + .maka-session-context__inner { min-width: 0; /* Control rhythm 36 (lg) — band holds sm/md controls without off-ladder 40. */ @@ -124,6 +134,23 @@ white-space: nowrap; } +.maka-session-context__goal-description { + display: block; + min-width: 0; + overflow: hidden; + padding-left: var(--space-1); + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Goal rows are intentionally single-line, but Astryx tooltip layers inherit + typography from their trigger subtree even after entering the top layer. + Restore the Tooltip contract here so long Goal copy wraps inside its 300px + content measure instead of being clipped as one unbreakable line. */ +.maka-session-context [role="tooltip"] { + white-space: normal; +} + .maka-session-context__cluster { width: min(100%, 560px); justify-self: end; diff --git a/docs/astryx-surface-file-inventory.md b/docs/astryx-surface-file-inventory.md index f174bb33c5..4f85c6583b 100644 --- a/docs/astryx-surface-file-inventory.md +++ b/docs/astryx-surface-file-inventory.md @@ -208,7 +208,7 @@ Wiki bar: Design Conventions · API Use-the-System · Theming · Container Paddi | `packages/ui/src/scheduled-task-inspector.tsx` | module-hub | Button, Divider, HStack, Heading, List, ListItem, MetadataList, MetadataListItem, Switch, Text, VStack | aligned — uses Astryx (Button, Divider, HStack, Heading, List, ListItem, MetadataList, MetadataListItem) | aligned | | `packages/ui/src/scheduled-task-panel.tsx` | module-hub | Button, Divider, EmptyState, List, ListItem, SegmentedControl, SegmentedControlItem, Selector, Text, TextInput, Toolbar | aligned — uses Astryx (Button, Divider, EmptyState, List, ListItem, SegmentedControl, SegmentedControlItem, Selector) | aligned | | `packages/ui/src/search-modal.tsx` | dialog-overlay | none | aligned — no raw controls; no Astryx JSX usage | aligned | -| `packages/ui/src/session-context-layer.tsx` | shell-chrome-or-panel | BreadcrumbItem, Breadcrumbs, IconButton, LayoutHeader, Text, Token | aligned — uses Astryx (BreadcrumbItem, Breadcrumbs, IconButton, LayoutHeader, Text, Token) | aligned | +| `packages/ui/src/session-context-layer.tsx` | shell-chrome-or-panel | BreadcrumbItem, Breadcrumbs, IconButton, LayoutHeader, Text, Token, Tooltip | aligned — uses Astryx (BreadcrumbItem, Breadcrumbs, IconButton, LayoutHeader, Text, Token, Tooltip) | aligned | | `packages/ui/src/session-history-list.tsx` | shell-chrome-or-panel | Badge, VStack | aligned — uses Astryx (Badge, VStack) | aligned | | `packages/ui/src/session-list-panel.tsx` | shell-chrome-or-panel | SegmentedControl, SegmentedControlItem, SideNav | aligned — uses Astryx (SegmentedControl, SegmentedControlItem, SideNav) | aligned | | `packages/ui/src/session-rename-dialog.tsx` | dialog-overlay | Button, Dialog, DialogHeader, HStack, Layout, LayoutContent, TextInput | aligned — uses Astryx (Button, Dialog, DialogHeader, HStack, Layout, LayoutContent, TextInput) | aligned | diff --git a/packages/ui/src/__tests__/session-context-layer-goal.test.tsx b/packages/ui/src/__tests__/session-context-layer-goal.test.tsx index ee3ac1463a..2466d8b715 100644 --- a/packages/ui/src/__tests__/session-context-layer-goal.test.tsx +++ b/packages/ui/src/__tests__/session-context-layer-goal.test.tsx @@ -50,6 +50,8 @@ test('a running goal reads as running and offers pause, with elapsed and tokens' onClear: () => undefined, }); assert.ok(markup.includes('Goal 3 of 50')); + assert.ok(markup.includes('Ship the feature')); + assert.ok(markup.includes('role="tooltip"')); assert.ok(markup.includes('12m')); assert.ok(markup.includes('12k / 100k')); assert.ok(markup.includes('Autonomous goal running')); @@ -58,6 +60,7 @@ test('a running goal reads as running and offers pause, with elapsed and tokens' assert.ok(!markup.includes('Resume autonomous goal')); // The clear kill switch stays. assert.ok(markup.includes('Clear autonomous goal after 3/50 iterations')); + assert.ok(markup.includes('data-has-goal="true"')); }); test('a paused goal reads as paused and offers resume, not pause', () => { diff --git a/packages/ui/src/session-context-layer.tsx b/packages/ui/src/session-context-layer.tsx index ec881657b3..ddc9b62e28 100644 --- a/packages/ui/src/session-context-layer.tsx +++ b/packages/ui/src/session-context-layer.tsx @@ -30,6 +30,7 @@ import { StatusDot, Text, Token, + Tooltip, type DropdownMenuOption, } from '@astryxdesign/core'; import { getConversationCopy } from './conversation-copy.js'; @@ -348,6 +349,7 @@ export function SessionContextLayer(props: { label={copy.sessionContextAriaLabel} className="maka-session-context" data-session-context-layer="true" + data-has-goal={props.goal ? 'true' : undefined} >