Skip to content

web-next: Replace AppSidebar with responsive Navigation#194

Open
yihyunjoon wants to merge 10 commits intohackers-pub:mainfrom
yihyunjoon:navigation
Open

web-next: Replace AppSidebar with responsive Navigation#194
yihyunjoon wants to merge 10 commits intohackers-pub:mainfrom
yihyunjoon:navigation

Conversation

@yihyunjoon
Copy link
Contributor

@yihyunjoon yihyunjoon commented Jan 24, 2026

Summary

  • Replace AppSidebar and FloatingComposeButton with a unified Navigation component
  • Add mobile bottom navigation bar with key nav items and compose button
  • Add responsive breakpoints: icon-only sidebar at sm, full sidebar with labels at lg
  • Extract Footer into a standalone component
  • Add left padding to nav list at lg for better visual alignment

Screenshots

Screenshot 2026-01-24 at 4 41 57 PM Screenshot 2026-01-24 at 4 41 47 PM Screenshot 2026-01-24 at 4 41 29 PM
sm (640px) md (768px) lg (1024px)

Summary by CodeRabbit

  • New Features

    • Added a new navigation bar for improved app layout.
    • Added a footer with links to Code of Conduct and project information.
  • Refactor

    • Reorganized app layout: removed sidebar and floating compose button.
    • Navigation and content presentation restructured for updated UX.
  • Localization

    • Updated translations in English, Japanese, Korean, Simplified Chinese, and Traditional Chinese to reflect navigation changes.

✏️ Tip: You can customize this high-level summary in your review settings.

Replace FloatingComposeButton with a full Navigation component that
provides iOS-style tab bar navigation for mobile users. The navigation
includes Feed, Notifications, Create, Search, and Profile tabs with
the user's avatar displayed for the profile link.
Remove AppSidebar component and enhance Navigation to serve as
the primary navigation. Navigation now displays as a bottom bar
on mobile and a sticky left sidebar on sm+ screens with logo.
- lg: 200px width with text labels next to icons
- Dark/light mode logo switching using picture element
- Hover states for menu items
Move Create Note button to a prominent position in sidebar with
primary button styling, adjust logo size, and use rounded-full
for hover states.
Add reusable Footer component with Code of Conduct link and
license information, integrate it into Navigation sidebar.
# Conflicts:
#	web-next/src/components/AppSidebar.tsx
#	web-next/src/components/FloatingComposeButton.tsx
#	web-next/src/locales/en-US/messages.po
#	web-next/src/locales/ja-JP/messages.po
#	web-next/src/locales/ko-KR/messages.po
#	web-next/src/locales/zh-CN/messages.po
#	web-next/src/locales/zh-TW/messages.po
#	web-next/src/routes/(root).tsx
Add left padding to nav list at lg breakpoint and move compose button
outside the ul element for proper sizing and semantic correctness.
@coderabbitai
Copy link

coderabbitai bot commented Jan 24, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

This PR refactors the app's navigation layout by removing AppSidebar and FloatingComposeButton components and replacing them with new Navigation and Footer components in the root layout, with corresponding translation file updates to relocate or deprecate affected strings.

Changes

Cohort / File(s) Summary
Removed Navigation Components
web-next/src/components/AppSidebar.tsx, web-next/src/components/FloatingComposeButton.tsx
Complete removal of sidebar and floating compose button components (721 lines deleted), including all related mutations, event handlers, conditional rendering logic, and layout structure.
New Navigation Components
web-next/src/components/Footer.tsx, web-next/src/components/Navigation.tsx
Addition of Footer component (45 lines) with Code of Conduct and license links, and new Navigation component (162 lines) with responsive bottom/side navigation, account avatar, and compose button; both use Relay fragments and localization.
Root Layout Update
web-next/src/routes/(root).tsx
Modified to replace AppSidebar and FloatingComposeButton with conditional Navigation rendering; GraphQL fragment updated from AppSidebar/FloatingComposeButton spreads to Navigation spread; main content width constraint adjusted.
Translation File Updates
web-next/src/locales/en-US/messages.po, web-next/src/locales/ja-JP/messages.po, web-next/src/locales/ko-KR/messages.po, web-next/src/locales/zh-CN/messages.po, web-next/src/locales/zh-TW/messages.po
Relocated and deprecated UI strings from AppSidebar/FloatingComposeButton across Navigation and Footer; many entries marked obsolete (\#~); new entries added for Navigation-specific labels ("Main navigation", "Profile"); translation phrasing refinements and punctuation adjustments applied.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • dahlia

Poem

🐰 The sidebar hops away, float fades to light,
Navigation takes the stage with Footer standing tight,
Routes align, translations shift and sway,
A cleaner layout blooms in every way! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: replacing AppSidebar and FloatingComposeButton with a new responsive Navigation component, which is the primary architectural change across the pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @yihyunjoon, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the application's primary navigation system to enhance responsiveness and user experience across various devices. It consolidates previous sidebar and floating action button functionalities into a new, unified Navigation component, which dynamically adjusts its layout and features based on screen size, including a dedicated mobile bottom navigation bar.

Highlights

  • Unified Navigation Component: The AppSidebar and FloatingComposeButton components have been replaced by a single, unified Navigation component.
  • Responsive Design Implementation: The new Navigation component incorporates responsive breakpoints, displaying an icon-only sidebar on small screens (sm) and a full sidebar with labels on larger screens (lg).
  • Mobile Bottom Navigation Bar: A dedicated bottom navigation bar has been added for mobile devices, providing easy access to key navigation items and a compose button.
  • Footer Component Extraction: The application's footer content has been extracted into a standalone Footer component for better modularity and reusability.
  • Visual Alignment Improvement: Left padding has been added to the navigation list on large screens to enhance visual alignment and overall aesthetics.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request is a great step forward in improving the application's responsiveness by replacing the AppSidebar and FloatingComposeButton with a new, unified Navigation component. The implementation uses responsive design principles effectively. I've identified a potential functional regression and a minor code cleanup opportunity that would further enhance the quality of these changes. Overall, this is a solid and well-executed refactoring.

Comment on lines +147 to +156
<div class="hidden sm:flex w-full px-2 mt-5">
<button
type="button"
onClick={openNoteCompose}
class="flex lg:flex-1 items-center justify-center p-3 rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90"
aria-label={t`Create Note`}
>
<IconSquarePen class="size-5" aria-hidden="true" />
</button>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The new unified navigation is a significant improvement. However, it seems the "Create Article" functionality, which was available in both the previous sidebar and the floating action button, has been omitted. Both the mobile and desktop versions of the new compose button only trigger the note composition modal.

If this omission was unintentional, you might consider reintroducing this feature. For the desktop view, a dropdown menu on the compose button could be an effective way to offer both "Create Note" and "Create Article" options, similar to the previous implementation.

Comment on lines 53 to +57
<SidebarProvider>
<AppSidebar
$signedAccount={signedAccount()?.viewer}
signedAccountLoaded={!signedAccount.pending}
/>
<RootLayoutContent signedAccount={signedAccount}>
{props.children}
</RootLayoutContent>
</SidebarProvider>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The SidebarProvider appears to be a remnant from the old AppSidebar component. Since the new Navigation component does not utilize the useSidebar context, this provider is likely no longer necessary. Removing it, along with its import, will help clean up the code and improve maintainability.

      <RootLayoutContent signedAccount={signedAccount}>
        {props.children}
      </RootLayoutContent>

@yihyunjoon yihyunjoon marked this pull request as ready for review January 24, 2026 08:29
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@web-next/src/components/Footer.tsx`:
- Around line 23-35: The external anchor elements in Footer.tsx that render the
"GitHub repository" and the "AGPL-3.0" links use target="_blank" but lack rel
attributes, creating a tabnabbing risk; update the anchor JSX (the <a> elements
that produce the "GitHub repository" and the link for "AGPL-3.0") to include
rel="noopener noreferrer" alongside target="_blank" so external links open
safely in a new tab.
- Around line 19-40: The Trans message placeholders do not match the locale's
numeric placeholders, so update the usage in Footer.tsx to use the same numeric
placeholders or update the values keys accordingly: change the message prop to
use {0} and {1} instead of ${"GITHUB_REPOSITORY"} and ${"AGPL-3.0"}, or
alternatively replace the values keys GITHUB_REPOSITORY and "AGPL-3.0" with
numeric keys (0 and 1) so the Trans component injects the anchor elements into
the placeholders; modify the Trans call and its values object (referencing the
Trans component, the message prop, and the values entries) to ensure placeholder
names match the locale format.

In `@web-next/src/components/Navigation.tsx`:
- Around line 123-136: The Profile nav item only checks for exact match against
location.pathname, so subroutes like /@username/notes aren't treated as active;
update the logic in Navigation.tsx (the A element's aria-current and the Avatar
classList check around account.username and location.pathname) to use a prefix
match (e.g., location.pathname.startsWith(`/@${account.username}`)) or compute
an isProfileRoute variable and reuse it for both aria-current and the "ring-2
ring-foreground" class so the profile tab stays active on subroutes.

In `@web-next/src/locales/zh-CN/messages.po`:
- Around line 1207-1212: The translation swaps the placeholder order: msgid uses
{0} for repository and {1} for license but msgstr in
web-next/src/locales/zh-CN/messages.po places them opposite; update the msgstr
so that {0} corresponds to the repository and {1} to the license (as used by
src/components/Footer.tsx) to preserve correct semantics.
🧹 Nitpick comments (2)
web-next/src/components/Footer.tsx (1)

8-14: Destructure props in the component signature.

This keeps component style consistent with project conventions.

♻️ Suggested refactor
-export function Footer(props: FooterProps) {
+export function Footer({ class: className }: FooterProps) {
   const { t } = useLingui();

   return (
     <footer
-      class={`p-4 text-xs text-muted-foreground ${props.class ?? ""}`}
+      class={`p-4 text-xs text-muted-foreground ${className ?? ""}`}
     >

As per coding guidelines, use functional components with props destructuring.

web-next/src/components/Navigation.tsx (1)

19-32: Destructure props in the component signature.

Keeps component style consistent across the codebase.

♻️ Suggested refactor
-export function Navigation(props: NavigationProps) {
+export function Navigation({ $signedAccount }: NavigationProps) {
@@
-    () => props.$signedAccount,
+    () => $signedAccount,

As per coding guidelines, use functional components with props destructuring.

Comment on lines +19 to +40
<Trans
message={t`The source code of this website is available on ${"GITHUB_REPOSITORY"} under the ${"AGPL-3.0"} license.`}
values={{
GITHUB_REPOSITORY: () => (
<a
href="https://github.com/hackers-pub/hackerspub"
target="_blank"
class="underline"
>
{t`GitHub repository`}
</a>
),
"AGPL-3.0": () => (
<a
href="https://www.gnu.org/licenses/agpl-3.0.en.html"
target="_blank"
class="underline"
>
AGPL 3.0
</a>
),
}}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all .po files and search for the message
fd -e po . web-next/src/locales --exec grep -l "The source code of this website is available on" {} \;

Repository: hackers-pub/hackerspub

Length of output: 260


🏁 Script executed:

#!/bin/bash
# Get details of the message in locale files
rg -A 3 "The source code of this website is available on" web-next/src/locales

Repository: hackers-pub/hackerspub

Length of output: 1898


Align Trans component placeholders with locale file format.

The locale entries use {0} and {1} placeholders, but the Trans component uses named placeholders (GITHUB_REPOSITORY and "AGPL-3.0"). This mismatch prevents the values object from properly injecting the anchor elements.

✅ Correct placeholder names
           values={{
-            GITHUB_REPOSITORY: () => (
+            "{0}": () => (
               <a
                 href="https://github.com/hackers-pub/hackerspub"
                 target="_blank"
                 class="underline"
               >
                 {t`GitHub repository`}
               </a>
             ),
-            "AGPL-3.0": () => (
+            "{1}": () => (
               <a
                 href="https://www.gnu.org/licenses/agpl-3.0.en.html"
                 target="_blank"
                 class="underline"
               >
                 AGPL 3.0
               </a>
             ),
           }}
🧰 Tools
🪛 Biome (2.1.2)

[error] 25-25: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.

(lint/security/noBlankTarget)


[error] 34-34: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.

(lint/security/noBlankTarget)

🤖 Prompt for AI Agents
In `@web-next/src/components/Footer.tsx` around lines 19 - 40, The Trans message
placeholders do not match the locale's numeric placeholders, so update the usage
in Footer.tsx to use the same numeric placeholders or update the values keys
accordingly: change the message prop to use {0} and {1} instead of
${"GITHUB_REPOSITORY"} and ${"AGPL-3.0"}, or alternatively replace the values
keys GITHUB_REPOSITORY and "AGPL-3.0" with numeric keys (0 and 1) so the Trans
component injects the anchor elements into the placeholders; modify the Trans
call and its values object (referencing the Trans component, the message prop,
and the values entries) to ensure placeholder names match the locale format.

Comment on lines +23 to +35
<a
href="https://github.com/hackers-pub/hackerspub"
target="_blank"
class="underline"
>
{t`GitHub repository`}
</a>
),
"AGPL-3.0": () => (
<a
href="https://www.gnu.org/licenses/agpl-3.0.en.html"
target="_blank"
class="underline"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add rel="noopener noreferrer" to external links opened in a new tab.

Both external anchors open in a new tab without rel, which is a tabnabbing risk.

🛡️ Proposed fix
               <a
                 href="https://github.com/hackers-pub/hackerspub"
                 target="_blank"
+                rel="noopener noreferrer"
                 class="underline"
               >
...
               <a
                 href="https://www.gnu.org/licenses/agpl-3.0.en.html"
                 target="_blank"
+                rel="noopener noreferrer"
                 class="underline"
               >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a
href="https://github.com/hackers-pub/hackerspub"
target="_blank"
class="underline"
>
{t`GitHub repository`}
</a>
),
"AGPL-3.0": () => (
<a
href="https://www.gnu.org/licenses/agpl-3.0.en.html"
target="_blank"
class="underline"
<a
href="https://github.com/hackers-pub/hackerspub"
target="_blank"
rel="noopener noreferrer"
class="underline"
>
{t`GitHub repository`}
</a>
),
"AGPL-3.0": () => (
<a
href="https://www.gnu.org/licenses/agpl-3.0.en.html"
target="_blank"
rel="noopener noreferrer"
class="underline"
🧰 Tools
🪛 Biome (2.1.2)

[error] 25-25: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.

(lint/security/noBlankTarget)


[error] 34-34: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.

(lint/security/noBlankTarget)

🤖 Prompt for AI Agents
In `@web-next/src/components/Footer.tsx` around lines 23 - 35, The external anchor
elements in Footer.tsx that render the "GitHub repository" and the "AGPL-3.0"
links use target="_blank" but lack rel attributes, creating a tabnabbing risk;
update the anchor JSX (the <a> elements that produce the "GitHub repository" and
the link for "AGPL-3.0") to include rel="noopener noreferrer" alongside
target="_blank" so external links open safely in a new tab.

Comment on lines +123 to +136
<A
href={`/@${account.username}`}
class="flex items-center justify-center py-3 sm:p-3 lg:justify-start lg:gap-3 lg:rounded-full lg:hover:bg-accent"
aria-label={t`Profile`}
aria-current={location.pathname === `/@${account.username}`
? "page"
: undefined}
>
<Avatar
class="size-6 shrink-0"
classList={{
"ring-2 ring-foreground": location.pathname ===
`/@${account.username}`,
}}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep the Profile nav item active on subroutes.

Currently the profile item is only active on the exact /@username path. Subpages like /@username/notes won’t highlight the profile tab.

✅ Proposed fix
-      {(account) => (
+      {(account) => {
+        const profilePath = `/@${account.username}`;
+        const profileActive = isActive(profilePath);
+        return (
         <nav
           class="fixed sm:flex sm:flex-col z-50 border-border bg-background/95 bottom-0 left-0 right-0 border-t sm:sticky sm:top-0 sm:h-screen sm:w-16 sm:shrink-0 sm:border-t-0 sm:border-r lg:w-50 sm:py-3"
           role="navigation"
           aria-label={t`Main navigation`}
         >
@@
             <li class="flex-1 sm:flex-none sm:w-full">
               <A
-                href={`/@${account.username}`}
+                href={profilePath}
                 class="flex items-center justify-center py-3 sm:p-3 lg:justify-start lg:gap-3 lg:rounded-full lg:hover:bg-accent"
                 aria-label={t`Profile`}
-                aria-current={location.pathname === `/@${account.username}`
-                  ? "page"
-                  : undefined}
+                aria-current={profileActive ? "page" : undefined}
               >
                 <Avatar
                   class="size-6 shrink-0"
                   classList={{
-                    "ring-2 ring-foreground": location.pathname ===
-                      `/@${account.username}`,
+                    "ring-2 ring-foreground": profileActive,
                   }}
                 >
@@
-        </nav>
-      )}
+        </nav>
+      );}}
🤖 Prompt for AI Agents
In `@web-next/src/components/Navigation.tsx` around lines 123 - 136, The Profile
nav item only checks for exact match against location.pathname, so subroutes
like /@username/notes aren't treated as active; update the logic in
Navigation.tsx (the A element's aria-current and the Avatar classList check
around account.username and location.pathname) to use a prefix match (e.g.,
location.pathname.startsWith(`/@${account.username}`)) or compute an
isProfileRoute variable and reuse it for both aria-current and the "ring-2
ring-foreground" class so the profile tab stays active on subroutes.

Comment on lines 1207 to 1212
#. placeholder {0}: "GITHUB_REPOSITORY"
#. placeholder {1}: "AGPL-3.0"
#: src/components/AppSidebar.tsx:536
#: src/components/Footer.tsx:20
msgid "The source code of this website is available on {0} under the {1} license."
msgstr "可在 {1} 上以 {0} 许可获取该网站的源代码。"

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Swap the placeholder order to keep meaning correct.

{0} is the repository and {1} is the license, but the translation currently places them in the opposite semantic roles.

📝 Proposed fix
-msgstr "可在 {1} 上以 {0} 许可获取该网站的源代码。"
+msgstr "可在 {0} 上以 {1} 许可获取该网站的源代码。"
🤖 Prompt for AI Agents
In `@web-next/src/locales/zh-CN/messages.po` around lines 1207 - 1212, The
translation swaps the placeholder order: msgid uses {0} for repository and {1}
for license but msgstr in web-next/src/locales/zh-CN/messages.po places them
opposite; update the msgstr so that {0} corresponds to the repository and {1} to
the license (as used by src/components/Footer.tsx) to preserve correct
semantics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant