hermes-agent/apps/desktop/src/components/assistant-ui/session-ref-open.test.tsx
teknium1 236b1b56cd
feat(desktop): artifacts — versioned cards, sandboxed live preview, right-rail viewer
Substantial generated content (full HTML documents, large SVGs, long code)
now promotes out of the transcript into versioned, openable artifacts:

- lib/artifact-detect.ts: pure detection over fenced blocks — html docs,
  large standalone svg, and 48+ line / 3k+ char code fences become
  artifacts; prose/terminal/mermaid fences and small snippets never do.
  Titles derive from <title>/<h1>, filename comments, or declarations.
- store/artifacts.ts: per-session registry with content-hash dedupe and
  version history (same kind+title = one artifact the model iterates on),
  persisted to localStorage with per-session/per-artifact/byte caps.
- ArtifactCard (transcript): compact openable card replaces the wall of
  code; streaming shows shimmer + line count; versions accumulate
  automatically on completion but the rail only opens on click
  (offer, don't hijack). Reasoning scratchpads never register.
- ArtifactPane (right rail): artifact: tabs beside preview/file tabs with
  version stepper (v2 of 3 / Latest), PREVIEW/SOURCE toggle, copy,
  download (kind-aware extension), open-in-browser for HTML.
- Rendering: HTML runs in a sandbox=allow-scripts iframe (opaque origin,
  no network to the app, no top-nav); SVG is DOMPurify-sanitized with the
  same profile as the inline embed; source view reuses the windowed Shiki
  renderer so 5k-line artifacts scroll smoothly.
- Rail integration: artifact tabs participate in tab order, close-others/
  close-to-right, ⌘W, pane visibility, and reveal; gateway switches close
  open artifact tabs; a persisted artifact: active-tab id reconciles to
  preview on boot (artifact tabs are ephemeral).
- i18n: en/zh/zh-hant/ja/ar strings for card + pane.
- session-ref-open.test.tsx: mock now spreads the real module — the
  artifact card imports session-view, which needs $sessionStates.

Tests: artifact-detect (15), artifacts store (12), markdown-pipeline
integration (3); full desktop suite 3128 passed, electron project 751
passed, tsc no new errors.
2026-07-26 19:29:25 -07:00

44 lines
1.6 KiB
TypeScript

import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { __resetSessionLinkTitleCache } from '@/lib/session-link-title'
import { DirectiveContent } from './directive-text'
import { MarkdownTextContent } from './markdown-text'
const openSessionTile = vi.fn()
vi.mock('@/store/session-states', async importOriginal => ({
...(await importOriginal<Record<string, unknown>>()),
openSessionTile: (...args: unknown[]) => openSessionTile(...args)
}))
afterEach(() => {
cleanup()
openSessionTile.mockClear()
__resetSessionLinkTitleCache()
})
// Both surfaces render a session ref differently — an inline link in agent
// prose, a chip in the user's own message — but either one opens the session
// it names, the way its sidebar row would.
describe('session refs open the session', () => {
it('opens the session from an agent-written link', async () => {
render(<MarkdownTextContent isRunning={false} text="Picked up in @session:work/20260101_abc123 last night." />)
fireEvent.click(await screen.findByTitle('work/20260101_abc123'))
await vi.waitFor(() => expect(openSessionTile).toHaveBeenCalledWith('20260101_abc123', 'center'))
})
it('opens the session from a chip in the user transcript', async () => {
render(<DirectiveContent text="pick up @session:work/20260101_abc123 please" />)
const chip = screen.getByTitle('work/20260101_abc123')
expect(chip.tagName).toBe('BUTTON')
fireEvent.click(chip)
await vi.waitFor(() => expect(openSessionTile).toHaveBeenCalledWith('20260101_abc123', 'center'))
})
})