-
Notifications
You must be signed in to change notification settings - Fork 467
feat(web): add hover-reveal links for VS pages and use cases in footer #2134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ources column - Add hover-based dropdown for VS comparison pages (Otter, Granola, Fireflies, etc.) - Add hover-based dropdown for platform-specific download pages (macOS, Windows, Linux) - Include accessibility improvements: ARIA attributes, keyboard support, touch/click support - Follow existing header dropdown pattern for consistency Co-Authored-By: [email protected] <[email protected]>
🤖 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-storybook ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for hyprnote ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughAdds hover-driven randomization and new dynamic links to the footer's ResourcesLinks and introduces four new solution route pages (coaching, consulting, journalism, research), each exporting a Route via createFileRoute with component and head metadata. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Replace dropdown menus with simpler hover-reveal links - 'Versus ???' reveals random competitor name on hover, links to VS page - 'Hyprnote for ???' reveals random use case on hover, links to solution page - Cleaner, less cluttered footer design Co-Authored-By: [email protected] <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/web/src/components/footer.tsx (1)
168-179: AvoiduseMemo+Math.randomfor behavior; prefer lazyuseState(and reduce SSR mismatch risk)Using
useMemowithMath.random()and an empty dependency array makes the random pick part of component behavior, butuseMemoisn’t guaranteed for that and will recompute on the client during hydration, which can desync from any server-rendered output.To make the random choice truly “once per mount” and SSR-safe, consider lazy-initializing state instead:
- const randomVs = useMemo( - () => vsList[Math.floor(Math.random() * vsList.length)], - [], - ); - const randomUseCase = useMemo( - () => useCasesList[Math.floor(Math.random() * useCasesList.length)], - [], - ); + const [randomVs] = useState( + () => vsList[Math.floor(Math.random() * vsList.length)], + ); + const [randomUseCase] = useState( + () => useCasesList[Math.floor(Math.random() * useCasesList.length)], + );This keeps the behavior stable across re-renders and better matches React 19 guidance about not relying on
useMemofor observable behavior.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/components/footer.tsx(3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, usecn(import from@hypr/utils). It is similar toclsx. Always pass an array and split by logical grouping.
Usemotion/reactinstead offramer-motion.
Files:
apps/web/src/components/footer.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote
- GitHub Check: ci
- GitHub Check: fmt
- GitHub Check: Pages changed - hyprnote
- GitHub Check: Devin
🔇 Additional comments (1)
apps/web/src/components/footer.tsx (1)
3-21: Confirm lists align with “Compare” vs “Platforms” footer specThe
vsListanduseCasesListstructures look fine, but they encode competitor and “solution” use cases rather than the platform download targets described in the PR summary (macOS Apple Silicon/Intel, Windows, Linux). Please double‑check that this is the intended UX for the new Resources items; if the goal is truly “Platforms”, these should probably be aplatformsListpointing at the platform-specific download URLs instead.
- Each hover picks a different random item from the list - Uses getNextRandomIndex helper to avoid showing same item twice in a row - Versus ??? and Hyprnote for ??? now shuffle on repeated hovers Co-Authored-By: [email protected] <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/web/src/components/footer.tsx (1)
224-256: Hover-only reveal still breaks a11y and non-pointer UX; move handlers toLinkand add focus/ARIA (plus verify randomization behavior)The current implementation keeps the key issues previously flagged:
- The real link purpose is hidden behind
onMouseEnter/onMouseLeaveattached to<li>:
- Keyboard users never trigger these handlers, so they only ever see “Versus ???” / “Hyprnote for ???”.
- Touch users typically don’t get hover, so they tap a link whose destination they can’t infer.
- Screen readers get the literal text “Versus ???” / “Hyprnote for ???” as the accessible name, which does not describe the actual destination.
- You also re-randomize on every mouse enter via
setVsIndex/setUseCaseIndex, so the selection is no longer “stable per page load” as described in the PR objectives; instead, pointer users can get a new random target each time they hover, while non-pointer users are stuck with the initial (hidden) choice.This is a user-facing and a11y concern, not just a cosmetic one.
Concrete suggestion:
- Move hover/focus handlers onto the
Linkelements and add keyboard support.- Expose a stable accessible name via
aria-labelso screen readers know the destination even when the visual text shows???.- (Optional/spec check) If you intended the random selection to be stable per page load, drop the
setVsIndex/setUseCaseIndexcalls from hover and rely only on the initial random indices.Example diff adapting your current code:
- <li - onMouseEnter={() => { - setIsVersusHovered(true); - setVsIndex((prev) => getNextRandomIndex(vsList.length, prev)); - }} - onMouseLeave={() => setIsVersusHovered(false)} - > - <Link - to="/vs/$slug" - params={{ slug: currentVs.slug }} - className="text-sm text-neutral-600 hover:text-stone-600 transition-colors no-underline hover:underline hover:decoration-dotted" - > - {isVersusHovered ? `Versus ${currentVs.name}` : "Versus ???"} - </Link> - </li> + <li> + <Link + to="/vs/$slug" + params={{ slug: currentVs.slug }} + className="text-sm text-neutral-600 hover:text-stone-600 transition-colors no-underline hover:underline hover:decoration-dotted" + onMouseEnter={() => { + setIsVersusHovered(true); + setVsIndex((prev) => getNextRandomIndex(vsList.length, prev)); + }} + onMouseLeave={() => setIsVersusHovered(false)} + onFocus={() => setIsVersusHovered(true)} + onBlur={() => setIsVersusHovered(false)} + aria-label={`Compare Hyprnote vs ${currentVs.name}`} + > + {isVersusHovered ? `Versus ${currentVs.name}` : "Versus ???"} + </Link> + </li> @@ - <li - onMouseEnter={() => { - setIsUsedInHovered(true); - setUseCaseIndex((prev) => - getNextRandomIndex(useCasesList.length, prev), - ); - }} - onMouseLeave={() => setIsUsedInHovered(false)} - > - <Link - to={currentUseCase.to} - className="text-sm text-neutral-600 hover:text-stone-600 transition-colors no-underline hover:underline hover:decoration-dotted" - > - {isUsedInHovered - ? `Hyprnote for ${currentUseCase.label}` - : "Hyprnote for ???"} - </Link> - </li> + <li> + <Link + to={currentUseCase.to} + className="text-sm text-neutral-600 hover:text-stone-600 transition-colors no-underline hover:underline hover:decoration-dotted" + onMouseEnter={() => { + setIsUsedInHovered(true); + setUseCaseIndex((prev) => + getNextRandomIndex(useCasesList.length, prev), + ); + }} + onMouseLeave={() => setIsUsedInHovered(false)} + onFocus={() => setIsUsedInHovered(true)} + onBlur={() => setIsUsedInHovered(false)} + aria-label={`Hyprnote for ${currentUseCase.label}`} + > + {isUsedInHovered + ? `Hyprnote for ${currentUseCase.label}` + : "Hyprnote for ???"} + </Link> + </li>This keeps the playful hover behavior but makes the links understandable and usable for keyboard, touch, and assistive tech users. Also, please double-check that
@tanstack/react-router’sLinkin your version accepts these standard DOM event handlers and ARIA props as expected.
🧹 Nitpick comments (1)
apps/web/src/components/footer.tsx (1)
176-189: State shape inResourcesLinksis reasonableThe combination of hover flags plus index state (
vsIndex,useCaseIndex) and simple derived values (currentVs,currentUseCase) is a clear and maintainable way to support the randomized footer links; no issues from a correctness or complexity standpoint. If you later touch this, you might consider renamingisUsedInHoveredto something likeisUseCaseHoveredfor clarity, but it’s non-blocking.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/components/footer.tsx(3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, usecn(import from@hypr/utils). It is similar toclsx. Always pass an array and split by logical grouping.
Usemotion/reactinstead offramer-motion.
Files:
apps/web/src/components/footer.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote
- GitHub Check: Devin
- GitHub Check: Pages changed - hyprnote
- GitHub Check: ci
- GitHub Check: fmt
🔇 Additional comments (1)
apps/web/src/components/footer.tsx (1)
3-30: Random helper and option lists look correct
getNextRandomIndexcorrectly avoids repeating the previous index and is safe for small lengths;vsListanduseCasesListare straightforward data sources with no obvious correctness issues. I don’t see any changes needed here.
- Remove hover boolean states (no longer needed) - Use Tailwind blur-sm and group-hover:blur-none for reveal effect - Only blur the dynamic part (competitor name / use case label) - Add smooth transition with duration-150 Co-Authored-By: [email protected] <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/components/footer.tsx(3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, usecn(import from@hypr/utils). It is similar toclsx. Always pass an array and split by logical grouping.
Usemotion/reactinstead offramer-motion.
Files:
apps/web/src/components/footer.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Redirect rules - hyprnote-storybook
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote-storybook
- GitHub Check: Header rules - hyprnote
- GitHub Check: Pages changed - hyprnote-storybook
- GitHub Check: Pages changed - hyprnote
- GitHub Check: ci
- GitHub Check: fmt
🔇 Additional comments (4)
apps/web/src/components/footer.tsx (4)
3-3: LGTM!The
useStateimport is necessary for the new hover-reveal functionality.
5-12: LGTM!The helper correctly prevents immediate repeats by looping until a different index is found. Edge cases (length ≤ 1) are handled appropriately.
177-186: LGTM!State initialization correctly uses functional form to compute random initial indices only once, and derived values are cleanly computed from state.
14-21: All VS comparison routes are properly implemented through the dynamic route handler atapps/web/src/routes/_view/vs/$slug.tsx. The vsList slugs (otter, granola, fireflies, fathom, notion, obsidian) are correctly used with TanStack Router's params API (params={{ slug: currentVs.slug }}), and the single dynamic route handler serves all competitor pages.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
apps/web/src/routes/_view/solution/research.tsx (1)
6-32: Research solution route is solid; consider a shared solution layoutThis page is correctly wired and consistent with the other solution routes; if you expect more of these, you might eventually extract a shared “solution page” layout (Hero/Features/Use Cases/CTA) that takes copy and icon data as props to reduce duplication across coaching/consulting/research/journalism.
Also applies to: 96-245
apps/web/src/components/footer.tsx (1)
176-259: Randomized footer links behave as intended; watch out for SSR/hydration randomnessThe hover/focus randomization plus blur reveal and
aria-labels look good and are accessible-friendly; the non-repeating selection matches the spec. The only caveat is that usingMath.random()in theuseStateinitializer can cause SSR/hydration mismatches in React (server and client choose different initial items). If hydration warnings become noisy, consider a deterministic initial index (e.g., 0 or a hash of pathname) and move the randomization to auseEffector just to the hover/focus handlers.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/web/src/routeTree.gen.tsis excluded by!**/*.gen.ts
📒 Files selected for processing (5)
apps/web/src/components/footer.tsx(3 hunks)apps/web/src/routes/_view/solution/coaching.tsx(1 hunks)apps/web/src/routes/_view/solution/consulting.tsx(1 hunks)apps/web/src/routes/_view/solution/journalism.tsx(1 hunks)apps/web/src/routes/_view/solution/research.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, usecn(import from@hypr/utils). It is similar toclsx. Always pass an array and split by logical grouping.
Usemotion/reactinstead offramer-motion.
Files:
apps/web/src/routes/_view/solution/research.tsxapps/web/src/routes/_view/solution/coaching.tsxapps/web/src/routes/_view/solution/journalism.tsxapps/web/src/components/footer.tsxapps/web/src/routes/_view/solution/consulting.tsx
🧬 Code graph analysis (2)
apps/web/src/routes/_view/solution/research.tsx (4)
apps/web/src/routes/_view/solution/coaching.tsx (1)
Route(6-32)apps/web/src/routes/_view/solution/consulting.tsx (1)
Route(6-32)apps/web/src/routes/_view/solution/journalism.tsx (1)
Route(6-32)packages/utils/src/cn.ts (1)
cn(20-22)
apps/web/src/routes/_view/solution/consulting.tsx (4)
apps/web/src/routes/_view/solution/coaching.tsx (1)
Route(6-32)apps/web/src/routes/_view/solution/journalism.tsx (1)
Route(6-32)apps/web/src/routes/_view/solution/research.tsx (1)
Route(6-32)packages/utils/src/cn.ts (1)
cn(20-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Redirect rules - hyprnote-storybook
- GitHub Check: Header rules - hyprnote-storybook
- GitHub Check: Pages changed - hyprnote-storybook
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote
- GitHub Check: Pages changed - hyprnote
- GitHub Check: ci
- GitHub Check: fmt
🔇 Additional comments (4)
apps/web/src/routes/_view/solution/coaching.tsx (1)
6-32: Coaching solution route and layout look consistent and soundRoute config, SEO meta, and the four-section layout (Hero/Features/Use Cases/CTA) are all consistent with the existing solution pages and wired correctly; I don’t see any issues here.
Also applies to: 96-244
apps/web/src/routes/_view/solution/consulting.tsx (1)
6-32: Consulting solution page matches the shared solution patternRouting, meta tags, and the sectioned layout mirror the other solution pages cleanly; content and links look correct.
Also applies to: 96-244
apps/web/src/components/footer.tsx (1)
3-30: Random index helper and option lists look correct
getNextRandomIndexcorrectly prevents immediate repeats, and thevsList/useCasesListentries line up with the described/vs/$slugand/solution/*destinations.apps/web/src/routes/_view/solution/journalism.tsx (1)
6-32: Journalism solution page is consistent and well-structuredRoute setup, SEO meta, and the four-section layout align with the other solution pages; content and internal links look correct.
Also applies to: 96-244
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/web/src/components/footer.tsx (1)
63-67: Good reuse of sharedImagecomponent for the logoSwapping the raw
<img>for the shared<Image>keeps logo styling consistent with the rest of the app and centralizes image behavior/optimizations. As a minor consistency idea, you could optionally do the same for the small Hyprnote icon in the “Versus” link for a single image pipeline.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/components/footer.tsx(5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, usecn(import from@hypr/utils). It is similar toclsx. Always pass an array and split by logical grouping.
Usemotion/reactinstead offramer-motion.
Files:
apps/web/src/components/footer.tsx
🧬 Code graph analysis (1)
apps/web/src/components/footer.tsx (1)
apps/web/src/components/image.tsx (1)
Image(4-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote
- GitHub Check: Pages changed - hyprnote
- GitHub Check: fmt
- GitHub Check: ci
🔇 Additional comments (2)
apps/web/src/components/footer.tsx (2)
3-32: Random index helper and data lists look correct and side‑effect free
getNextRandomIndexcorrectly handleslength <= 1and guarantees a different index forlength > 1, andvsList/useCasesListare simple static configs with clearslug/labelfields. The lazyuseStateinitializers usingMath.random()are appropriate here and keep the randomization encapsulated.
179-295: Dynamic Resources links behavior matches the spec; only minor polish considerationsThe
vsIndex/useCaseIndexstate plusgetNextRandomIndexgive the desired “no immediate repeat” randomization, and limiting updates toonMouseEnter/onFocusavoids the earlier UX issue of changing while blurred. The blur/focus classes (group-hover:blur-none,group-focus:blur-none) plus thearia-labelvalues provide a good balance between playful UI and accessibility.A couple of small, non‑blocking considerations:
- Randomization fires on both
onMouseEnterandonFocus, so moving from keyboard focus to mouse hover (or vice versa) can change the target again immediately; if you want slightly more predictable behavior you could choose one primary trigger (e.g., onlyonMouseEnterfor pointer, onlyonFocusfor keyboard).- Ensure the
useCasesList.topaths andvsList.slugvalues line up with actual route definitions (/solution/*and/vs/$slug), so these links never 404 if route slugs change later.Overall, the implementation is solid and aligned with the PR objectives.
Summary
Adds two interactive hover-reveal links to the Resources column in the website footer:
Each hover picks a different random item from the list, so hovering repeatedly cycles through options. The
getNextRandomIndexhelper ensures the same item isn't shown twice in a row.The blur effect uses Tailwind's
blur-smclass withgroup-hover:blur-noneand a smooth 150ms transition.Competitors included: Otter.ai, Granola, Fireflies, Fathom, Notion, Obsidian
Use cases included: Sales, Recruiting, Consulting, Coaching, Research, Journalism
Updates since last revision
useMemo) to shuffle-on-each-hover behavior per user feedbackgetNextRandomIndexhelper to avoid showing the same item twice in a rowReview & Testing Checklist for Human
/solution/sales,/solution/recruiting, etc. Confirm these routes are valid and render correctly/vs/otter,/vs/granola, etc.) exist and render correctlyTest Plan
Notes