Main page redesign (fixes from #4015)#4030
Main page redesign (fixes from #4015)#4030devin-ai-integration[bot] wants to merge 7 commits intomainfrom
Conversation
Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
Co-Authored-By: Sungbin Jo <goranmoomin@daum.net>
✅ Deploy Preview for hyprnote-storybook canceled.
|
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
✅ Deploy Preview for hyprnote ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Co-Authored-By: Sungbin Jo <goranmoomin@daum.net>
| clearInterval(interval2); | ||
|
|
||
| setTimeout(() => { | ||
| setEnhancedLines(1); | ||
| setActiveTab("summary"); | ||
|
|
||
| setTimeout(() => { | ||
| setEnhancedLines(2); | ||
| setEnhancedLines(1); | ||
| setTimeout(() => { | ||
| setEnhancedLines(3); | ||
| setEnhancedLines(2); | ||
| setTimeout(() => { | ||
| setEnhancedLines(4); | ||
| setEnhancedLines(3); | ||
| setTimeout(() => { | ||
| setEnhancedLines(5); | ||
| setEnhancedLines(4); | ||
| setTimeout(() => { | ||
| setEnhancedLines(6); | ||
| setEnhancedLines(5); | ||
| setTimeout(() => { | ||
| setEnhancedLines(7); | ||
| setTimeout(() => runAnimation(), 1000); | ||
| setEnhancedLines(6); | ||
| setTimeout(() => { | ||
| setEnhancedLines(7); | ||
| setTimeout(() => runAnimation(), 2000); | ||
| }, 800); | ||
| }, 800); | ||
| }, 800); | ||
| }, 800); | ||
| }, 800); | ||
| }, 800); | ||
| }, 800); | ||
| }, 500); | ||
| }, 300); | ||
| }, 800); | ||
| } | ||
| }, 50); | ||
| } |
There was a problem hiding this comment.
🔴 HowItWorksSection animation timers are never cleaned up on unmount
The useEffect in HowItWorksSection (line 1042) calls runAnimation() which spawns a deeply nested chain of setTimeout and setInterval calls that recursively re-invoke runAnimation(). The effect has no cleanup return, so when the component unmounts (e.g., navigating away), all pending timers continue to fire and call setState (setTypedText1, setTypedText2, setEnhancedLines, setActiveTab) on an unmounted component.
Detailed Explanation
The animation loop at apps/web/src/routes/_view/index.tsx:1042-1100 creates:
- An outer
setTimeout(line 1050) setIntervalfor typing text1 (line 1051)setIntervalfor typing text2 (line 1059)- 9+ nested
setTimeoutcalls for enhancedLines (lines 1066-1091) - A final
setTimeout(() => runAnimation(), 2000)(line 1083) that restarts the entire cycle
None of these timer IDs are tracked, and the useEffect returns nothing (}, []); at line 1100). On unmount, all timers keep firing, causing React state updates on an unmounted component. This is a resource leak that can cause memory leaks and React warnings in development.
Impact: Memory leak and potential React "Can't perform a React state update on an unmounted component" warnings. On pages with frequent navigation, leaked timers accumulate.
(Refers to lines 1042-1100)
Prompt for agents
In apps/web/src/routes/_view/index.tsx, the useEffect starting at line 1042 in HowItWorksSection needs a cleanup mechanism. The approach should be:
1. Create a mutable ref (e.g., `const timersRef = useRef<(ReturnType<typeof setTimeout> | ReturnType<typeof setInterval>)[]>([])`) to track all timer IDs.
2. Wrap every `setTimeout` and `setInterval` call inside `runAnimation()` to push the returned ID into `timersRef.current`.
3. Add a boolean `cancelled` flag (or use a ref) that is checked before each `setState` call.
4. Return a cleanup function from the useEffect that sets `cancelled = true` and calls `clearTimeout`/`clearInterval` on every ID in `timersRef.current`.
Alternatively, refactor the entire animation to use a single `requestAnimationFrame` loop with elapsed-time-based state transitions, which is easier to clean up with a single `cancelAnimationFrame` call.
Was this helpful? React with 👍 or 👎 to provide feedback.
| onVideoExpand={setExpandedVideo} | ||
| heroInputRef={heroInputRef} | ||
| /> | ||
| <HeroParagraphSection onVideoExpand={setExpandedVideo} /> |
There was a problem hiding this comment.
🔴 CTASection's waitlist button does nothing because heroInputRef is never attached to a DOM element
The bottom CTA button for non-macOS platforms (Windows, Linux, mobile) scrolls to the top and attempts to focus heroInputRef.current, but this ref is always null because the HeroSection component (which contained <input ref={heroInputRef}>) was replaced by HeroParagraphSection which doesn't use the ref.
Root Cause
In the Component function at apps/web/src/routes/_view/index.tsx:118-156, heroInputRef is created at line 120 and passed to CTASection at line 147. Previously, it was also passed to HeroSection which rendered an <input ref={heroInputRef}> (line 304). But the PR replaced HeroSection with HeroParagraphSection (line 129), which does not accept or use heroInputRef.
As a result, heroInputRef.current is permanently null. When CTASection.handleCTAClick() runs (line 2425-2443), it calls window.scrollTo({ top: 0, behavior: "smooth" }) and then checks if (heroInputRef.current) — which is always false. The user sees the page scroll to top but nothing else happens. There's no email input to focus or interact with.
Impact: On Windows, Linux, and mobile platforms, the bottom CTA "Join waitlist" / "Get reminder" button is completely broken — it scrolls to the top of the page but provides no way for the user to actually join the waitlist or enter their email.
Prompt for agents
In apps/web/src/routes/_view/index.tsx, the HeroParagraphSection (line 129) replaced HeroSection but the heroInputRef is now orphaned. There are two options to fix this:
Option A: Add an email waitlist input to HeroParagraphSection (or a new section) and pass heroInputRef to it, so the CTASection button can scroll to and focus it.
Option B: Update CTASection (lines 2410-2502) to handle the waitlist action differently when there's no input to focus. For example, navigate to a dedicated waitlist/signup page, or show an inline email form within CTASection itself instead of trying to scroll to a non-existent input.
Also clean up the now-unused heroInputRef from Component() at line 120 if going with Option B.
Was this helpful? React with 👍 or 👎 to provide feedback.
| ); | ||
| } |
There was a problem hiding this comment.
🚩 HeroSection is exported but no longer rendered — dead code remains
The HeroSection function at apps/web/src/routes/_view/index.tsx:180-393 was changed from a private function to export function HeroSection(...) but is no longer used in the Component render tree (line 129 now renders HeroParagraphSection). Along with it, heroContent, mainFeatures, activeFeatureIndices, FEATURES_AUTO_ADVANCE_DURATION, MainFeaturesSection, FeaturesMobileCarousel, MobileFeatureVideo, FeatureVideo, FeaturesDesktopGrid, ValuePropsGrid, and ManifestoSection are all still defined but no longer referenced from the main page component. This is a significant amount of dead code that increases bundle size. The MainFeaturesSection and its children were previously rendered inline and are now entirely removed from the page layout.
(Refers to lines 180-393)
Was this helpful? React with 👍 or 👎 to provide feedback.
Main page redesign with scroll-reveal hero and reworked features
Summary
Redesigns the main landing page (from PR #4015 by @elstua) with the following changes:
HeroParagraphSection)CoolStuffSection,AISection(with animated research status),GrowsWithYouSection(contacts + calendar), and a tabbedHowItWorksSection(notes → summary animation)avatar.webp,contact_human.webp, hand-drawing SVGs)devscript from rootpackage.jsoncategoryinmac-productivity-apps.mdx(→"Guides")index.tsxto passdprintcheckUpdates since last revision
cijob to fail:selectedFeature,featuresScrollRef, andscrollToFeaturefromComponentaudioIndicatorWidthprop fromMockWindowusage (prop doesn't exist on the component)HeroSectionandManifestoSectionto resolve TS6133 unused-declaration errorsReview & Testing Checklist for Human
HeroSectionis commented out in JSX, not removed —heroInputRefis still created and passed toCTASection. On non-macOS platforms where the CTA action is"waitlist", the bottom CTA button scrolls to top but does nothing (the email input it targets doesn't exist). Decide whether to wire up a new input or change the waitlist behavior.audioIndicatorWidth={120}was removed, not wired up — theMockWindowcomponent doesn't accept this prop, so it was removed to fix the TS error. The audio indicator in the "How it works" section now renders at the default 17px width instead of the intended 120px. If the wider indicator was desired,MockWindowneeds to be updated to accept and forward this prop.HowItWorksSectionare never cleaned up on unmount — the recursivesetTimeout/setIntervalloop (~line 1043–1097) has no cleanup return in theuseEffect. This can cause state updates on unmounted components.ScrollRevealParagraphuses Framer Motion scroll progress; verify it works smoothly and doesn't cause jank on lower-end devices.Notes