Skip to content

Commit 0346da3

Browse files
committed
fix(landing): clarify swipe discovery and share interactive logs
1 parent 5f8cb04 commit 0346da3

10 files changed

Lines changed: 316 additions & 165 deletions

File tree

‎apps/sim/app/(landing)/components/features/components/features-rail/features-rail.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ export function FeaturesRail({ label, children }: FeaturesRailProps) {
230230
<div className='relative'>
231231
<div
232232
ref={railRef}
233+
role='region'
233234
aria-label={label}
234235
className={cn(
235236
'flex gap-6 overflow-x-auto overscroll-x-contain pb-4 [overflow-anchor:none] [scrollbar-width:none] data-[dragging]:cursor-grabbing data-[dragging]:select-none [&::-webkit-scrollbar]:hidden',

‎apps/sim/app/(landing)/components/features/features.tsx‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const CORE_FEATURES = [
6161
href: '/logs',
6262
tone: 'light',
6363
visual: <RunTraceGraphic />,
64+
interactiveVisual: true,
6465
},
6566
] as const
6667

@@ -81,12 +82,15 @@ export function Features() {
8182
<h2
8283
id='features-heading'
8384
className={cn(
84-
'mb-16 max-w-[20ch] text-balance text-[var(--text-primary)] max-sm:mb-10 max-lg:mb-12',
85+
'mb-16 max-w-[20ch] text-balance text-[var(--text-primary)] max-sm:mb-3 max-lg:mb-12',
8586
HOME_TYPE.h2
8687
)}
8788
>
8889
Everything AI agents need to do real work
8990
</h2>
91+
<p className='mb-8 hidden text-[13px] text-[var(--text-secondary)] max-sm:block'>
92+
Swipe to explore
93+
</p>
9094

9195
<FeaturesRail label='Core Sim features'>
9296
{CORE_FEATURES.map((feature) => (

‎apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/logs-menu-preview/components/logs-run-graph/logs-run-graph.tsx‎

Lines changed: 0 additions & 84 deletions
This file was deleted.

‎apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/logs-menu-preview/logs-menu-preview.tsx‎

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,14 @@
11
import { Chip, cn } from '@sim/emcn'
22
import { ChevronDown, Search } from '@sim/emcn/icons'
3-
import { LogsRunGraph } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/logs-menu-preview/components/logs-run-graph/logs-run-graph'
43
import { MenuPreviewFrame } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/menu-preview-frame'
54
import { MenuPreviewHeader } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/menu-preview-header/menu-preview-header'
5+
import { LogsRunGraph } from '@/app/(landing)/components/shared/logs-run-graph'
6+
import { SUMMARY_STATS } from '@/app/(landing)/components/shared/logs-run-graph/constants'
67

78
interface LogsMenuPreviewProps {
89
layout?: 'menu' | 'hero'
910
}
1011

11-
/** Successful runs grouped into hourly buckets, following the earlier RunHealth overview. */
12-
const RUN_COUNTS = [
13-
3, 5, 4, 6, 4, 7, 5, 6, 8, 5, 7, 6, 9, 6, 8, 7, 10, 6, 8, 7, 9, 8, 6, 7,
14-
] as const
15-
const RUN_BUCKETS = RUN_COUNTS.map((count, hour) => ({ hour, count }))
16-
const COMPLETED_RUNS = RUN_COUNTS.reduce<number>((total, count) => total + count, 0)
17-
18-
const SUMMARY_STATS = [
19-
{ label: 'Success rate', value: '100%' },
20-
{ label: 'Median run', value: '21.8s' },
21-
{ label: 'Completed', value: String(COMPLETED_RUNS) },
22-
{ label: 'Cost', value: `$${(COMPLETED_RUNS * 0.11).toFixed(2)}` },
23-
] as const
24-
2512
/** A successful-only sample of the earlier high-level Logs overview and run-volume graph. */
2613
export function LogsMenuPreview({ layout = 'menu' }: LogsMenuPreviewProps) {
2714
return (
@@ -63,7 +50,7 @@ export function LogsMenuPreview({ layout = 'menu' }: LogsMenuPreviewProps) {
6350
</div>
6451
))}
6552
</div>
66-
<LogsRunGraph buckets={RUN_BUCKETS} />
53+
<LogsRunGraph />
6754
<div className='mt-1.5 flex justify-between text-[var(--text-secondary)] text-caption'>
6855
<span>24 hours ago</span>
6956
<span>Now</span>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/** Sample successful runs, shared by the menu and homepage Logs previews. */
2+
const RUN_COUNTS = [
3+
3, 5, 4, 6, 4, 7, 5, 6, 8, 5, 7, 6, 9, 6, 8, 7, 10, 6, 8, 7, 9, 8, 6, 7,
4+
] as const
5+
6+
export const RUN_BUCKETS = RUN_COUNTS.map((count, hour) => ({ hour, count }))
7+
export const COMPLETED_RUNS = RUN_COUNTS.reduce<number>((total, count) => total + count, 0)
8+
export const SUMMARY_STATS = [
9+
{ label: 'Success rate', value: '100%' },
10+
{ label: 'Median run', value: '21.8s' },
11+
{ label: 'Completed', value: String(COMPLETED_RUNS) },
12+
{ label: 'Cost', value: `$${(COMPLETED_RUNS * 0.11).toFixed(2)}` },
13+
] as const
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { LogsRunGraph } from './logs-run-graph'
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { act } from 'react'
5+
import { createRoot, type Root } from 'react-dom/client'
6+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
7+
import { CoreFeatureCard } from '@/app/(landing)/components/features/components/core-feature-card'
8+
import { LogsRunGraph } from '@/app/(landing)/components/shared/logs-run-graph'
9+
import { RunTraceGraphic } from '@/app/(landing)/logs/components/feature-graphics'
10+
11+
let root: Root
12+
let host: HTMLDivElement
13+
14+
beforeEach(() => {
15+
vi.useFakeTimers()
16+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
17+
host = document.createElement('div')
18+
document.body.append(host)
19+
root = createRoot(host)
20+
})
21+
22+
afterEach(() => {
23+
act(() => root.unmount())
24+
host.remove()
25+
vi.restoreAllMocks()
26+
vi.useRealTimers()
27+
})
28+
29+
function mount() {
30+
act(() => root.render(<LogsRunGraph layout='card' />))
31+
const graph = host.querySelector<HTMLButtonElement>('button')
32+
if (!graph) throw new Error('Graph not rendered')
33+
return graph
34+
}
35+
36+
function pointer(element: Element, type: string, pointerType = 'touch', x = 40, y = 300) {
37+
const event = new MouseEvent(type, { bubbles: true, clientX: x, clientY: y })
38+
Object.defineProperty(event, 'pointerType', { value: pointerType })
39+
act(() => element.dispatchEvent(event))
40+
}
41+
42+
describe('LogsRunGraph', () => {
43+
it('keeps tapped details visible and dismisses them on outside interaction', () => {
44+
const graph = mount()
45+
const bar = graph.querySelectorAll('[data-run-count]')[1]
46+
pointer(bar, 'pointerdown')
47+
pointer(bar, 'pointerup')
48+
pointer(graph, 'pointerout')
49+
expect(document.querySelector('[role="tooltip"]')?.textContent).toContain('5 succeeded')
50+
expect(graph.getAttribute('aria-label')).toContain('23–22 hours ago')
51+
pointer(document.body, 'pointerdown')
52+
expect(document.querySelector('[role="tooltip"]')).toBeNull()
53+
})
54+
55+
it.each(['pointercancel', 'swipe'])('does not open details after a %s gesture', (gesture) => {
56+
const graph = mount()
57+
pointer(graph, 'pointerdown')
58+
if (gesture === 'pointercancel') pointer(graph, 'pointercancel')
59+
pointer(graph, 'pointerup', 'touch', gesture === 'swipe' ? 120 : 40)
60+
expect(document.querySelector('[role="tooltip"]')).toBeNull()
61+
})
62+
63+
it.each(['scroll', 'resize'])(
64+
'dismisses details when the viewport changes through %s',
65+
(event) => {
66+
const graph = mount()
67+
pointer(graph, 'pointerdown')
68+
pointer(graph, 'pointerup')
69+
expect(document.querySelector('[role="tooltip"]')).not.toBeNull()
70+
act(() => (event === 'resize' ? window : host).dispatchEvent(new Event(event)))
71+
expect(document.querySelector('[role="tooltip"]')).toBeNull()
72+
}
73+
)
74+
75+
it('supports keyboard exploration and Escape dismissal', () => {
76+
const graph = mount()
77+
act(() => graph.focus())
78+
act(() => graph.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true })))
79+
expect(graph.getAttribute('aria-label')).toContain('Last hour: 7 succeeded')
80+
act(() =>
81+
graph.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true }))
82+
)
83+
expect(graph.getAttribute('aria-label')).toContain('2–1 hours ago: 6 succeeded')
84+
act(() => graph.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })))
85+
expect(document.querySelector('[role="tooltip"]')).toBeNull()
86+
})
87+
88+
it('keeps first-hour details inside a narrow viewport', () => {
89+
const graph = mount()
90+
vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(320)
91+
pointer(graph, 'pointerdown')
92+
pointer(graph, 'pointerup')
93+
const tooltip = document.querySelector<HTMLElement>('[role="tooltip"]')
94+
expect(tooltip?.style.translate).toContain('calc(192px - 100%)')
95+
expect(tooltip?.className).toContain('w-[176px]')
96+
})
97+
98+
it('keeps the interactive chart outside the route link and decorative wrappers', () => {
99+
act(() =>
100+
root.render(
101+
<CoreFeatureCard
102+
title='Logs'
103+
description='Inspect sample runs'
104+
href='/logs'
105+
visual={<RunTraceGraphic />}
106+
interactiveVisual
107+
/>
108+
)
109+
)
110+
const graph = host.querySelector('[data-run-overview-graph]')
111+
expect(graph).not.toBeNull()
112+
expect(graph?.closest('a, [aria-hidden="true"]')).toBeNull()
113+
expect(host.querySelector('a')?.getAttribute('href')).toBe('/logs')
114+
})
115+
})

0 commit comments

Comments
 (0)