Skip to content

Commit c76707f

Browse files
committed
fix(landing): address Greptile/Cursor review findings
- hero.tsx: object-top-left isn't a real Tailwind v3 utility, so the crop fell back to center; use object-left-top. - hero-workflow-stage.tsx: byId/builtIds were rebuilt on every render, including every drag pointermove frame; hoist the STAGE_BLOCKS lookup to module scope and memoize builtIds on builtCount. - hero-platform-loop.tsx: prefers-reduced-motion was only read once on mount, so toggling it mid-loop didn't stop the scheduled animation; listen for the media query's change event like BuildChatAnimation does. - landing-preview/page.tsx and readme-tour-capture/[workspaceId]/page.tsx: both are dev/preview-only scaffolds (one explicitly "local-only... delete before committing") that were reachable in production with no auth guard. 404 them outside of production instead of leaving them open.
1 parent 1244612 commit c76707f

5 files changed

Lines changed: 45 additions & 16 deletions

File tree

apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop.tsx

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,22 @@ export function HeroPlatformLoop() {
9191
}, [])
9292

9393
useEffect(() => {
94-
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
94+
const media = window.matchMedia('(prefers-reduced-motion: reduce)')
95+
let timers: ReturnType<typeof setTimeout>[] = []
96+
97+
const clearScheduled = () => {
98+
timers.forEach(clearTimeout)
99+
timers = []
100+
}
101+
102+
const showFinished = () => {
103+
clearScheduled()
95104
setPhase('reply')
96105
setStageOpen(true)
97106
setBuiltCount(STAGE_BLOCKS.length)
98-
return
99107
}
100-
let timers: ReturnType<typeof setTimeout>[] = []
101-
let cancelled = false
108+
102109
const runCycle = () => {
103-
if (cancelled) return
104110
setFading(false)
105111
setPhase('idle')
106112
setStageOpen(false)
@@ -118,10 +124,21 @@ export function HeroPlatformLoop() {
118124
setTimeout(runCycle, TOTAL_MS),
119125
]
120126
}
121-
runCycle()
127+
128+
const syncMotionPreference = () => {
129+
clearScheduled()
130+
if (media.matches) {
131+
showFinished()
132+
return
133+
}
134+
runCycle()
135+
}
136+
137+
syncMotionPreference()
138+
media.addEventListener('change', syncMotionPreference)
122139
return () => {
123-
cancelled = true
124-
timers.forEach(clearTimeout)
140+
media.removeEventListener('change', syncMotionPreference)
141+
clearScheduled()
125142
}
126143
}, [])
127144

apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-workflow-stage.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { type CSSProperties, useCallback, useLayoutEffect, useRef, useState } from 'react'
3+
import { type CSSProperties, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'
44
import { cn } from '@sim/emcn'
55
import { StageBlockCard } from '@/app/(landing)/components/hero/components/hero-platform-loop/stage-block-card'
66
import {
@@ -27,6 +27,8 @@ type Positions = Record<string, { x: number; y: number }>
2727
const initialPositions = (): Positions =>
2828
Object.fromEntries(STAGE_BLOCKS.map((b) => [b.id, { x: b.x, y: b.y }]))
2929

30+
const STAGE_BLOCKS_BY_ID = new Map(STAGE_BLOCKS.map((b) => [b.id, b]))
31+
3032
/**
3133
* The hero window's live workflow canvas - the right-pane counterpart of the
3234
* chat loop. Blocks pop in one by one as `builtCount` advances (staggered
@@ -121,8 +123,10 @@ export function HeroWorkflowStage({ builtCount }: HeroWorkflowStageProps) {
121123
dragRef.current = null
122124
}, [])
123125

124-
const byId = new Map(STAGE_BLOCKS.map((b) => [b.id, b]))
125-
const builtIds = new Set(STAGE_BLOCKS.slice(0, builtCount).map((b) => b.id))
126+
const builtIds = useMemo(
127+
() => new Set(STAGE_BLOCKS.slice(0, builtCount).map((b) => b.id)),
128+
[builtCount]
129+
)
126130

127131
return (
128132
<div
@@ -154,8 +158,8 @@ export function HeroWorkflowStage({ builtCount }: HeroWorkflowStageProps) {
154158
aria-hidden='true'
155159
>
156160
{STAGE_EDGES.map(([from, to]) => {
157-
const source = byId.get(from)
158-
const target = byId.get(to)
161+
const source = STAGE_BLOCKS_BY_ID.get(from)
162+
const target = STAGE_BLOCKS_BY_ID.get(to)
159163
if (!source || !target) return null
160164
const visible = builtIds.has(from) && builtIds.has(to)
161165
const s = handleAnchors(source, positions[from]).out

apps/sim/app/(landing)/components/hero/hero.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export function Hero() {
113113
fill
114114
priority
115115
sizes='(max-width: 1460px) 83vw, 1080px'
116-
className='object-cover object-top-left'
116+
className='object-cover object-left-top'
117117
/>
118118
<HeroPlatformLoop />
119119
</div>

apps/sim/app/landing-preview/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { notFound } from 'next/navigation'
12
import { LandingShell } from '@/app/(landing)/components'
23
import Landing from '@/app/(landing)/landing'
34

@@ -6,11 +7,13 @@ import Landing from '@/app/(landing)/landing'
67
* bypasses the self-hosted `/` -> `/login` redirect (proxy only redirects `/`).
78
* Wrapped in {@link LandingShell} so the preview carries the exact prod chrome
89
* (light tokens, navbar with GitHub stars, footer, JSON-LD).
9-
* Local-only scaffold for visual iteration; delete before committing.
10+
* Local/preview-only scaffold for visual iteration — 404s in production.
1011
*/
1112
export const dynamic = 'force-dynamic'
1213

1314
export default function LandingPreviewPage() {
15+
if (process.env.NODE_ENV === 'production') notFound()
16+
1417
return (
1518
<LandingShell>
1619
<Landing />

apps/sim/app/landing-preview/readme-tour-capture/[workspaceId]/page.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { Suspense, use, useEffect, useRef, useState } from 'react'
44
import { ToastProvider } from '@sim/emcn'
55
import { type QueryClient, useQueryClient } from '@tanstack/react-query'
6+
import { notFound } from 'next/navigation'
67
import { useTheme } from 'next-themes'
78
import { WorkspaceChrome } from '@/app/workspace/[workspaceId]/components/workspace-chrome'
89
import { Files } from '@/app/workspace/[workspaceId]/files/files'
@@ -38,7 +39,9 @@ import type { WorkflowState } from '@/stores/workflows/workflow/types'
3839
* WorkspaceChrome sidebar) with the main view switched IN PLACE as the capture
3940
* cursor clicks sidebar nav items, so one continuous screencast shows a user
4041
* navigating chat → integrations → knowledge → tables → files → workflow → logs.
41-
* No auth/network (everything seeded), light mode. Not committed.
42+
* No auth/network (everything seeded), light mode. 404s in production
43+
* (see the `NODE_ENV` guard below) — only reachable in dev/preview builds
44+
* for the capture script.
4245
*/
4346
export const dynamic = 'force-dynamic'
4447

@@ -499,6 +502,8 @@ interface CapturePageProps {
499502
}
500503

501504
export default function ReadmeTourCapturePage({ searchParams }: CapturePageProps) {
505+
if (process.env.NODE_ENV === 'production') notFound()
506+
502507
const params = use(searchParams)
503508
const camW = Number(params.w ?? 1280)
504509
const camH = Number(params.h ?? 800)

0 commit comments

Comments
 (0)