From dfbc9dbb1528e506fec2aea9898ad2d3e00486c8 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Fri, 24 Jul 2026 22:16:49 -0500 Subject: [PATCH] feat(desktop): show resolved titles on @session chips Route session refs in the transcript through the title resolver so a dropped session reads as its title instead of a truncated id, and use Tabler's funnel for the session chip icon. --- .../src/app/chat/composer/rich-editor.ts | 7 +++-- .../assistant-ui/directive-text.test.ts | 10 +++++++ .../assistant-ui/directive-text.tsx | 26 +++++++++++-------- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/apps/desktop/src/app/chat/composer/rich-editor.ts b/apps/desktop/src/app/chat/composer/rich-editor.ts index 71491b87496d..e3ebe3dffdfd 100644 --- a/apps/desktop/src/app/chat/composer/rich-editor.ts +++ b/apps/desktop/src/app/chat/composer/rich-editor.ts @@ -15,6 +15,7 @@ import { type SlashChipKind, slashIconElement } from '@/components/assistant-ui/directive-text' +import { sessionRefFallbackLabel } from '@/lib/session-link-title' export const RICH_INPUT_SLOT = 'composer-rich-input' @@ -59,7 +60,9 @@ export function refChipHtml(kind: string, rawValue: string, displayLabel?: strin const id = unquoteRef(rawValue) const text = `@${kind}:${quoteRefValue(id)}` - return `${directiveIconSvg(kind)}${escapeHtml(displayLabel || refLabel(id))}` + const label = displayLabel || (kind === 'session' ? sessionRefFallbackLabel(id) : refLabel(id)) + + return `${directiveIconSvg(kind)}${escapeHtml(label)}` } export function refChipElement(kind: string, rawValue: string, displayLabel?: string) { @@ -74,7 +77,7 @@ export function refChipElement(kind: string, rawValue: string, displayLabel?: st chip.dataset.refKind = kind chip.className = DIRECTIVE_CHIP_CLASS label.className = 'truncate' - label.textContent = displayLabel || refLabel(id) + label.textContent = displayLabel || (kind === 'session' ? sessionRefFallbackLabel(id) : refLabel(id)) chip.append(directiveIconElement(kind), label) return chip diff --git a/apps/desktop/src/components/assistant-ui/directive-text.test.ts b/apps/desktop/src/components/assistant-ui/directive-text.test.ts index 60c89f18b1e4..36ab54d743e7 100644 --- a/apps/desktop/src/components/assistant-ui/directive-text.test.ts +++ b/apps/desktop/src/components/assistant-ui/directive-text.test.ts @@ -36,4 +36,14 @@ describe('hermesDirectiveFormatter.parse', () => { { kind: 'text', text: ' the entry point' } ]) }) + + it('parses session links with profile/id values', () => { + const segments = hermesDirectiveFormatter.parse('see @session:work/20260101_abc123 next') + + expect(segments).toEqual([ + { kind: 'text', text: 'see ' }, + { kind: 'mention', type: 'session', label: '20260101…', id: 'work/20260101_abc123' }, + { kind: 'text', text: ' next' } + ]) + }) }) diff --git a/apps/desktop/src/components/assistant-ui/directive-text.tsx b/apps/desktop/src/components/assistant-ui/directive-text.tsx index ffa63a7b8877..e1a83f1c4c0a 100644 --- a/apps/desktop/src/components/assistant-ui/directive-text.tsx +++ b/apps/desktop/src/components/assistant-ui/directive-text.tsx @@ -8,6 +8,7 @@ import { Fragment, useEffect, useMemo, useState } from 'react' import { ZoomableImage } from '@/components/chat/zoomable-image' import { extractEmbeddedImages } from '@/lib/embedded-images' import { gatewayMediaDataUrl, isRemoteGateway } from '@/lib/media' +import { sessionRefFallbackLabel, useSessionLinkTitle } from '@/lib/session-link-title' const HERMES_REF_TYPES = ['file', 'folder', 'url', 'image', 'tool', 'line', 'terminal', 'session'] as const type HermesRefType = (typeof HERMES_REF_TYPES)[number] @@ -40,11 +41,7 @@ const ICON_PATHS: Record = { tool: ['M7 10h3v-3l-3.5 -3.5a6 6 0 0 1 8 8l6 6a2 2 0 0 1 -3 3l-6 -6a6 6 0 0 1 -8 -8l3.5 3.5'], line: ['M5 9l14 0', 'M5 15l14 0', 'M11 4l-4 16', 'M17 4l-4 16'], terminal: ['M5 7l5 5l-5 5', 'M12 19l7 0'], - session: [ - 'M8 9h8', - 'M8 13h6', - 'M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3z' - ] + session: ['M4 4h16v2.172a2 2 0 0 1 -.586 1.414l-4.414 4.414v7l-6 2v-8.5l-4.48 -4.928a2 2 0 0 1 -.52 -1.345v-2.227'] } const ICON_FALLBACK = ['M8 12a4 4 0 1 0 8 0a4 4 0 1 0 -8 0', 'M16 12v1.5a2.5 2.5 0 0 0 5 0v-1.5a9 9 0 1 0 -5.5 8.28'] @@ -314,12 +311,8 @@ function shortLabel(type: HermesRefType, id: string): string { } } - // `@session:/` — show a short id; the composer chip carries the - // friendly title, but once sent the wire form only has the id. if (type === 'session') { - const sid = id.split('/').filter(Boolean).pop() || id - - return sid.length > 10 ? `${sid.slice(0, 8)}…` : sid + return sessionRefFallbackLabel(id) } const tail = id.split(/[\\/]/).filter(Boolean).pop() @@ -368,7 +361,9 @@ export function DirectiveContent({ text }: { text: string }) { {segments.map((segment, index) => segment.kind === 'text' ? ( {segment.text} - ) : segment.type === 'image' ? null : ( + ) : segment.type === 'image' ? null : segment.type === 'session' ? ( + + ) : ( ) )} @@ -451,6 +446,15 @@ const DirectiveImage: FC<{ id: string; label: string }> = ({ id, label }) => { ) } +const SessionDirectiveChip: FC<{ + label: string + id: string +}> = ({ label, id }) => { + const resolved = useSessionLinkTitle(id, label) + + return +} + const DirectiveChip: FC<{ type: string label: string