Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,8 @@ function DetailCodeSection({
<Code.Viewer
code={jsonString}
language='json'
className='max-w-full rounded-md border-0 bg-[var(--surface-4)]! [word-break:break-all] dark:bg-[var(--surface-3)]!'
appearance='inspection'
className='max-w-full [word-break:break-all]'
wrapText
searchQuery={isSearchActive ? searchQuery : undefined}
currentMatchIndex={currentMatchIndex}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ export const WorkflowOutputSection = memo(
<Code.Viewer
code={jsonString}
language='json'
className='max-h-[300px] min-h-0 max-w-full rounded-md border-0 bg-[var(--surface-4)]! [word-break:break-all] dark:bg-[var(--surface-3)]!'
appearance='inspection'
className='max-h-[300px] min-h-0 max-w-full [word-break:break-all]'
wrapText
searchQuery={isSearchActive ? searchQuery : undefined}
currentMatchIndex={currentMatchIndex}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ function ServerDetailView({
: 'json'
}
wrapText
className='min-h-0! rounded-sm border border-[var(--border-1)]'
className='min-h-0!'
/>
{activeConfigTab === 'cursor' && (
<a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ console.log(limits);`
code={getSyncCommand()}
language={LANGUAGE_SYNTAX[language]}
wrapText
className='min-h-0! rounded-sm border border-[var(--border-1)]'
className='min-h-0!'
/>
</div>

Expand Down Expand Up @@ -533,7 +533,7 @@ console.log(limits);`
code={getStreamCommand()}
language={LANGUAGE_SYNTAX[language]}
wrapText
className='min-h-0! rounded-sm border border-[var(--border-1)]'
className='min-h-0!'
/>
</div>

Expand Down Expand Up @@ -579,7 +579,7 @@ console.log(limits);`
code={getAsyncCommand()}
language={LANGUAGE_SYNTAX[language]}
wrapText
className='min-h-0! rounded-sm border border-[var(--border-1)]'
className='min-h-0!'
/>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ const OutputCodeContent = React.memo(function OutputCodeContent({
code={code}
showGutter
language={language}
className='m-0 min-h-full rounded-none border-0 bg-[var(--bg)] dark:bg-[var(--bg)]'
appearance='flat'
className='m-0 min-h-full'
paddingLeft={8}
gutterStyle={{ backgroundColor: 'transparent' }}
wrapText={wrapText}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1226,7 +1226,8 @@ function PreviewEditorContent({
<Code.Viewer
code={formatValueAsJson(executionData.input)}
language='json'
className='max-h-[300px] min-h-0 max-w-full rounded-md border-0 bg-[var(--surface-4)]! [word-break:break-all] dark:bg-[var(--surface-3)]!'
appearance='inspection'
className='max-h-[300px] min-h-0 max-w-full [word-break:break-all]'
wrapText={wrapText}
searchQuery={isSearchActive ? searchQuery : undefined}
currentMatchIndex={currentMatchIndex}
Expand Down Expand Up @@ -1293,8 +1294,9 @@ function PreviewEditorContent({
<Code.Viewer
code={formatValueAsJson(executionData.output)}
language='json'
appearance='inspection'
className={cn(
'max-h-[300px] min-h-0 max-w-full rounded-md border-0 bg-[var(--surface-4)]! [word-break:break-all] dark:bg-[var(--surface-3)]!',
'max-h-[300px] min-h-0 max-w-full [word-break:break-all]',
executionData.status === 'error' && 'text-[var(--text-error)]'
)}
wrapText={wrapText}
Expand Down
89 changes: 87 additions & 2 deletions packages/emcn/src/components/code/code.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
/**
* @vitest-environment jsdom
*/
import { act } from 'react'
import { act, createRef } from 'react'
import { sleep } from '@sim/utils/helpers'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { Code } from './code'

let root: Root | null = null
let host: HTMLDivElement | null = null

beforeEach(() => {
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
vi.stubGlobal(
'ResizeObserver',
class {
observe() {}
unobserve() {}
disconnect() {}
}
)
host = document.createElement('div')
document.body.appendChild(host)
root = createRoot(host)
Expand All @@ -20,6 +28,7 @@ beforeEach(() => {
afterEach(() => {
if (root) act(() => root?.unmount())
host?.remove()
vi.unstubAllGlobals()
root = null
host = null
})
Expand Down Expand Up @@ -60,3 +69,79 @@ describe('Code.Viewer workflow references', () => {
expect(host?.querySelector('[data-search-match]')?.textContent).toBe('result')
})
})

describe('Code.Viewer appearances', () => {
for (const virtualized of [false, true]) {
it(`applies the inspection surface to ${virtualized ? 'virtualized' : 'standard'} output`, async () => {
await act(async () => {
root?.render(
<Code.Viewer
code='{"result": true}'
language='json'
showGutter
virtualized={virtualized}
appearance='inspection'
className='max-h-[300px]'
/>
)
await sleep(1)
})

const viewer = host?.firstElementChild
expect(viewer?.classList.contains('rounded-md')).toBe(true)
expect(viewer?.classList.contains('border-0')).toBe(true)
expect(viewer?.classList.contains('bg-[var(--surface-4)]!')).toBe(true)
expect(viewer?.classList.contains('dark:bg-[var(--surface-3)]!')).toBe(true)
expect(viewer?.classList.contains('max-h-[300px]')).toBe(true)
})
}

it('keeps the flat viewer separate from the default code container', async () => {
Comment thread
BillLeoutsakosvl346 marked this conversation as resolved.
await act(async () => {
root?.render(
<>
<Code.Viewer code='default' />
<Code.Viewer code='flat' appearance='flat' />
</>
)
await sleep(1)
})

const [defaultViewer, flatViewer] = Array.from(host?.children ?? [])
expect(defaultViewer.classList.contains('rounded-sm')).toBe(true)
expect(flatViewer.classList.contains('rounded-none')).toBe(true)
expect(flatViewer.classList.contains('bg-[var(--bg)]')).toBe(true)
expect(flatViewer.classList.contains('dark:bg-[var(--bg)]')).toBe(true)
expect(flatViewer.textContent).toContain('flat')
})

it('applies flat chrome on the virtualized gutter path used by the terminal', async () => {
const contentRef = createRef<HTMLDivElement>()
await act(async () => {
root?.render(
<Code.Viewer
code={'first line\nsecond line'}
language='json'
appearance='flat'
className='m-0 min-h-full'
showGutter
virtualized
wrapText
paddingLeft={8}
contentRef={contentRef}
/>
)
await sleep(1)
})

const viewer = host?.firstElementChild
expect(contentRef.current).toBe(viewer)
expect(viewer?.classList.contains('rounded-none')).toBe(true)
expect(viewer?.classList.contains('border-0')).toBe(true)
expect(viewer?.classList.contains('bg-[var(--bg)]')).toBe(true)
expect(viewer?.classList.contains('dark:bg-[var(--bg)]')).toBe(true)
expect(viewer?.classList.contains('overflow-x-hidden')).toBe(true)
expect(viewer?.classList.contains('min-h-full')).toBe(true)
expect(viewer?.classList.contains('rounded-sm')).toBe(false)
})
})
25 changes: 23 additions & 2 deletions packages/emcn/src/components/code/code.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { escapeRegExp } from '@sim/utils/string'
import { findWorkflowReferenceTokens } from '@sim/utils/workflow-references'
import { useVirtualizer } from '@tanstack/react-virtual'
import { cva, type VariantProps } from 'class-variance-authority'
import { ChevronRight } from '../../icons'
import { cn } from '../../lib/cn'
import './code.css'
Expand Down Expand Up @@ -856,6 +857,18 @@ function applySearchHighlightingToLine(
*/
type CodeViewerDensity = 'default' | 'compact'

/** Container appearances shared by the standard and virtualized viewers. */
export const codeViewerAppearanceVariants = cva('', {
variants: {
appearance: {
default: '',
inspection: 'rounded-md border-0 bg-[var(--surface-4)]! dark:bg-[var(--surface-3)]!',
flat: 'rounded-none border-0 bg-[var(--bg)] dark:bg-[var(--bg)]',
},
},
defaultVariants: { appearance: 'default' },
})

interface CodeViewerProps {
/** Code content to display */
code: string
Expand All @@ -865,6 +878,8 @@ interface CodeViewerProps {
language?: 'javascript' | 'json' | 'python' | 'bash' | 'toml'
/** Additional CSS classes for the container */
className?: string
/** Container appearance for code inspected in logs/previews or on flat surfaces. */
appearance?: NonNullable<VariantProps<typeof codeViewerAppearanceVariants>['appearance']>
/** Visual density for read-only code. */
density?: CodeViewerDensity
/** Highlight Sim `{{ENV}}` and `<block.output>` references with the platform accent. */
Expand Down Expand Up @@ -948,6 +963,7 @@ type ViewerInnerProps = {
language: 'javascript' | 'json' | 'python' | 'bash' | 'toml'
/** Additional CSS classes for the container */
className?: string
appearance: NonNullable<CodeViewerProps['appearance']>
/** Visual density for read-only code. */
density: CodeViewerDensity
highlightWorkflowReferences: boolean
Expand Down Expand Up @@ -978,6 +994,7 @@ const VirtualizedViewerInner = memo(function VirtualizedViewerInner({
showGutter,
language,
className,
appearance,
density,
highlightWorkflowReferences,
paddingLeft,
Expand Down Expand Up @@ -1147,6 +1164,7 @@ const VirtualizedViewerInner = memo(function VirtualizedViewerInner({
wrapText ? 'overflow-x-hidden' : 'overflow-x-auto',
'overflow-y-auto',
'dark:bg-[var(--code-bg)]',
codeViewerAppearanceVariants({ appearance }),
className
)}
style={{ height: containerHeight }}
Expand Down Expand Up @@ -1196,6 +1214,7 @@ const ViewerInner = memo(function ViewerInner({
showGutter,
language,
className,
appearance,
density,
highlightWorkflowReferences,
paddingLeft,
Expand Down Expand Up @@ -1309,7 +1328,7 @@ const ViewerInner = memo(function ViewerInner({
// Grid-based rendering for gutter alignment (works with wrap)
if (showGutter) {
return (
<Container className={className}>
<Container className={cn(codeViewerAppearanceVariants({ appearance }), className)}>
<Content className='code-editor-theme' editorRef={contentRef}>
<div
style={{
Expand Down Expand Up @@ -1370,7 +1389,7 @@ const ViewerInner = memo(function ViewerInner({

// Simple display without gutter
return (
<Container className={className}>
<Container className={cn(codeViewerAppearanceVariants({ appearance }), className)}>
<Content className='code-editor-theme' editorRef={contentRef}>
<pre
className={cn(
Expand Down Expand Up @@ -1417,6 +1436,7 @@ function Viewer({
showGutter = false,
language = 'json',
className,
appearance = 'default',
density = 'default',
highlightWorkflowReferences = false,
paddingLeft = 0,
Expand All @@ -1434,6 +1454,7 @@ function Viewer({
showGutter,
language,
className,
appearance,
density,
highlightWorkflowReferences,
paddingLeft,
Expand Down
1 change: 1 addition & 0 deletions packages/emcn/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export {
CODE_LINE_HEIGHT_PX,
Code,
calculateGutterWidth,
codeViewerAppearanceVariants,
getCodeEditorProps,
} from './components/code/code'
export {
Expand Down
3 changes: 2 additions & 1 deletion packages/workflow-renderer/src/lib/code-hover-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ export function CodeHoverCard({ preview, className, children }: CodeHoverCardPro
density='compact'
paddingLeft={8}
highlightWorkflowReferences
className='max-h-[min(16rem,calc(100vh-2rem))] min-h-0 overflow-x-hidden rounded-none border-0 bg-[var(--bg)] shadow-none dark:bg-[var(--bg)]'
appearance='flat'
className='max-h-[min(16rem,calc(100vh-2rem))] min-h-0 overflow-x-hidden shadow-none'
/>
</PopoverContent>
</Popover>
Expand Down
Loading