Skip to content

Limit visible Roadmap and Project Timeline items on About page#3080

Closed
0xgaurav wants to merge 27 commits intoOWASP:mainfrom
0xgaurav:limit-about-roadmap-timeline
Closed

Limit visible Roadmap and Project Timeline items on About page#3080
0xgaurav wants to merge 27 commits intoOWASP:mainfrom
0xgaurav:limit-about-roadmap-timeline

Conversation

@0xgaurav
Copy link

@0xgaurav 0xgaurav commented Dec 29, 2025

Proposed change

Resolves #3057

This PR improves the readability of the About page by limiting the number of items shown by default:

  • Limits Roadmap / Our Story to the first 3 items with a Show more / Show less toggle
  • Limits Project Timeline to the first 4 items with a Show more / Show less toggle
  • Reuses existing UI patterns and interaction logic to maintain consistency
  • Avoids duplicate logic and keeps changes scoped to the About page

Note:
The About page depends on seeded project metadata. When running locally, the page may show a 404 if make load-data and make index-data are not executed. The UI logic works as expected once data is available.

Checklist

  • Required: I read and followed the contributing guidelines
  • Required: I ran make check-test locally and all tests passed
  • I used AI for code, documentation, or tests in this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 29, 2025

Summary by CodeRabbit

  • New Features

    • About page now limits "Our Story" to 3 items and Project Timeline to 4 milestones by default, with a "Show More" control to expand.
  • Improvements

    • Pages now use a flexible, reusable toggleable list component for consistent item rendering and ordering.
    • "Show More" converted to a controlled component for predictable expand/collapse behavior.
  • Tests

    • Unit tests updated/added to verify item limits, Show More expand/collapse, and rendering variations.

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

Walkthrough

Refactors ToggleableList to a generic render-prop API and makes ShowMoreButton controlled; updates About page to limit Story (3) and Project Timeline (4) visible items using ToggleableList; updates CardDetailsPage usages; and adapts unit tests and mocks to the new APIs.

Changes

Cohort / File(s) Summary
ToggleableList (core)
frontend/src/components/ToggleableList.tsx
Converted to a generic <T> component requiring renderItem; added keyExtractor, optional label/icon, limit/isDisabled, visibleCount/safeLimit logic, and delegated item rendering; removed internal navigation/interaction handlers.
ShowMoreButton (API)
frontend/src/components/ShowMoreButton.tsx
Changed to controlled component: new props expanded: boolean and onToggle: () => void; removed internal state and local toggle logic.
About page
frontend/src/app/about/page.tsx
Replaced manual Story and Project Timeline rendering with ToggleableList via renderItem; added local storyExpanded state; Story defaults to first 3 items, Timeline limited to 4 by default; adjusted icon imports/markup.
CardDetailsPage usages
frontend/src/components/CardDetailsPage.tsx
Updated multiple ToggleableList usages (Languages, Topics, Tags, Domains, Labels) to pass renderItem and wrap items (no public API change beyond usage).
Unit tests — ToggleableList
frontend/__tests__/unit/components/ToggleableList.test.tsx
Tests updated to the renderItem API, adjusted IconWrapper mock signature, removed navigation-specific assertions, and validated Show More/limit behavior and edge cases.
Unit tests — About
frontend/__tests__/unit/pages/About.test.tsx
Expanded mock timeline to 6 items (2020–2025) and added tests asserting timeline defaults to 4 visible milestones.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • arkid15r
  • kasya
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: limiting visible Roadmap and Project Timeline items on the About page with show/hide toggles.
Description check ✅ Passed The description is directly related to the changeset, detailing the specific limits (3 items for Roadmap, 4 for Timeline) and implementation approach.
Linked Issues check ✅ Passed The PR successfully addresses issue #3057 by limiting Roadmap to 3 items and implementing Show more/less toggles; however, it uses 4-item limit for Project Timeline instead of the requested 6 items.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the About page item limits. Modifications to ToggleableList, ShowMoreButton, and related components serve the stated objective of reusing existing patterns.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f94c35c and 99b9536.

📒 Files selected for processing (1)
  • frontend/src/components/CardDetailsPage.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-21T12:21:32.372Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 1633
File: frontend/src/components/GradientRadialChart.tsx:67-116
Timestamp: 2025-06-21T12:21:32.372Z
Learning: The react-apexcharts Chart component does not support ARIA attributes like aria-label and role as direct props. To add accessibility attributes to ApexCharts in React, wrap the Chart component in a container div with the appropriate ARIA attributes.

Applied to files:

  • frontend/src/components/CardDetailsPage.tsx
🔇 Additional comments (4)
frontend/src/components/CardDetailsPage.tsx (4)

214-221: Render-prop adoption looks consistent; verify where React keys are assigned.
If ToggleableList doesn’t wrap items with its own keyed element, returning a bare <span> here can trigger “missing key” warnings.

Optional tweak if ToggleableList expects keys on renderItem output
-                renderItem={(item) => <span>{item}</span>}
+                renderItem={(item, index) => <span key={`${String(item)}-${index}`}>{String(item)}</span>}

222-229: Same key/type consideration for Topics renderItem.
Please confirm topics items are strings (or that ToggleableList handles object items elsewhere), otherwise this can degrade rendering.

Optional tweak (if needed)
-                renderItem={(item) => <span>{item}</span>}
+                renderItem={(item, index) => <span key={`${String(item)}-${index}`}>{String(item)}</span>}

234-256: Tags/Domains: check whether “disabled” lists still need renderItem + key semantics.
Looks fine, but same “who owns keys?” question applies; also worth confirming tags/domains are strings (esp. if domains could be structured).

Optional tweak (if needed)
-                    renderItem={(item) => <span>{item}</span>}
+                    renderItem={(item, index) => <span key={`${String(item)}-${index}`}>{String(item)}</span>}
...
-                    renderItem={(item) => <span>{item}</span>}
+                    renderItem={(item, index) => <span key={`${String(item)}-${index}`}>{String(item)}</span>}

258-267: Labels: verify key ownership + item type.
If labels can contain duplicates, also confirm ToggleableList’s keying strategy won’t collide (e.g., keying purely by label string).


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.

Copy link
Contributor

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/src/app/about/page.tsx (2)

279-298: Missing expand/collapse implementation for Project Timeline.

According to the PR objectives, the "Project Timeline" section should display only the first 6 items by default with a "Show more/Show less" toggle. Currently, all timeline items are displayed without limiting.

The timelineExpanded state (declared on line 66) should be used here to implement the same expand/collapse pattern as the "Our Story" section.

🔎 Proposed implementation following the "Our Story" pattern
         <SecondaryCard icon={FaClock} title={<AnchorTitle title="Project Timeline" />}>
           <div className="space-y-6">
-            {[...projectTimeline].reverse().map((milestone, index) => (
+            {[...projectTimeline].reverse()
+              .slice(0, timelineExpanded ? projectTimeline.length : 6)
+              .map((milestone, index) => (
               <div key={`${milestone.year}-${milestone.title}`} className="relative pl-10">
                 {index !== projectTimeline.length - 1 && (
                   <div className="absolute top-5 left-[5px] h-full w-0.5 bg-gray-400"></div>
                 )}
                 <div
                   aria-hidden="true"
                   className="absolute top-2.5 left-0 h-3 w-3 rounded-full bg-gray-400"
                 ></div>
                 <div>
                   <h3 className="text-lg font-semibold text-blue-400">{milestone.title}</h3>
                   <h4 className="mb-1 font-medium text-gray-400">{milestone.year}</h4>
                   <p className="text-gray-600 dark:text-gray-300">{milestone.description}</p>
                 </div>
               </div>
             ))}
           </div>
+
+          {projectTimeline.length > 6 && (
+            <button
+              onClick={() => setTimelineExpanded(!timelineExpanded)}
+              className="mt-2 flex items-center bg-transparent px-0 text-blue-400"
+            >
+              {timelineExpanded ? "Show less" : "Show more"}
+            </button>
+          )}
         </SecondaryCard>

Note: You may need to adjust the connecting line logic (line 283) when items are hidden, as the conditional check uses projectTimeline.length which should account for the visible items only.


257-298: Reuse the ShowMoreButton component instead of manual implementation.

The codebase has an established ShowMoreButton component used consistently in TopContributorsList, RepositoryCard, and MenteeContributorsList to handle expand/collapse patterns. The current implementation (lines 257-298) manually reinvents this pattern with custom state and onClick handlers, contradicting the PR objective to reuse existing UI patterns.

Replace the manual button implementation with <ShowMoreButton onToggle={() => setRoadmapExpanded(!roadmapExpanded)} /> to maintain consistency and avoid duplicate logic.

🧹 Nitpick comments (1)
frontend/src/app/about/page.tsx (1)

257-277: Implementation looks good, but improve key generation.

The expand/collapse logic for "Our Story" is implemented correctly. However, the key generation on line 261 could produce duplicate keys if multiple story items start with the same text.

🔎 Safer key generation using index
-          {projectStory
-            .slice(0, roadmapExpanded ? projectStory.length : 3)
-            .map((text) => (
+          {projectStory
+            .slice(0, storyExpanded ? projectStory.length : 3)
+            .map((text, index) => (
               <div
-                key={`story-${text.substring(0, 50).replaceAll(' ', '-')}`}
+                key={`story-${index}`}
                 className="mb-4"
               >
                 <div>
                   <Markdown content={text} />
                 </div>
               </div>
             ))}
 
           {projectStory.length > 3 && (
             <button
-              onClick={() => setRoadmapExpanded(!roadmapExpanded)}
+              onClick={() => setStoryExpanded(!storyExpanded)}
               className="mt-2 flex items-center bg-transparent px-0 text-blue-400"
             >
-              {roadmapExpanded ? "Show less" : "Show more"}
+              {storyExpanded ? "Show less" : "Show more"}
             </button>
           )}

Note: This also incorporates the variable rename from the previous comment.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b782d00 and 499b093.

📒 Files selected for processing (1)
  • frontend/src/app/about/page.tsx

Copy link
Contributor

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/app/about/page.tsx (1)

281-296: Fix the vertical line logic for collapsed timeline.

The condition on line 283 checks against the original array length (projectTimeline.length - 1), but index comes from the sliced array. When the timeline is collapsed (showing 6 items), the vertical line will incorrectly appear after the last visible item, creating a visual artifact suggesting more content below.

🔎 Proposed fix

Extract the displayed timeline to a variable and check against its length:

         <SecondaryCard icon={FaClock} title={<AnchorTitle title="Project Timeline" />}>
           <div className="space-y-6">
-            {[...projectTimeline].reverse().slice(0, timelineExpanded ? projectTimeline.length : 6).map((milestone, index) => (
+            {(() => {
+              const displayedTimeline = [...projectTimeline]
+                .reverse()
+                .slice(0, timelineExpanded ? projectTimeline.length : 6);
+              return displayedTimeline.map((milestone, index) => (
               <div key={`${milestone.year}-${milestone.title}`} className="relative pl-10">
-                {index !== projectTimeline.length - 1 && (
+                {index !== displayedTimeline.length - 1 && (
                   <div className="absolute top-5 left-[5px] h-full w-0.5 bg-gray-400"></div>
                 )}
                 <div
                   aria-hidden="true"
                   className="absolute top-2.5 left-0 h-3 w-3 rounded-full bg-gray-400"
                 ></div>
                 <div>
                   <h3 className="text-lg font-semibold text-blue-400">{milestone.title}</h3>
                   <h4 className="mb-1 font-medium text-gray-400">{milestone.year}</h4>
                   <p className="text-gray-600 dark:text-gray-300">{milestone.description}</p>
                 </div>
               </div>
-            ))}
+              ));
+            })()}
           </div>
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 499b093 and 414b0b7.

📒 Files selected for processing (1)
  • frontend/src/app/about/page.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/app/about/page.tsx (2)
frontend/src/utils/aboutData.ts (2)
  • projectStory (56-60)
  • projectTimeline (61-138)
frontend/src/components/MarkdownWrapper.tsx (1)
  • Markdown (5-20)
🔇 Additional comments (3)
frontend/src/app/about/page.tsx (3)

65-66: LGTM! State variables are properly initialized.

The state variables are well-named, correctly initialized to false to show collapsed views by default, and align with the PR objectives.


257-278: LGTM! Our Story expand/collapse implementation is correct.

The implementation correctly:

  • Limits display to 3 items when collapsed
  • Generates unique keys for each paragraph
  • Shows the toggle button only when there are more than 3 items
  • Updates button text based on state

298-305: LGTM! Project Timeline toggle button is correctly implemented.

The toggle button:

  • Shows only when there are more than 6 items
  • Correctly toggles the state
  • Displays appropriate text based on expansion state
  • Uses consistent styling with the Our Story toggle

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 29, 2025
Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

This is half-way done:

  • You didn't run make check-test.
  • You should reuse ShowMoreButton component.
  • The milestones part is not implemented.
  • You should add tests to your code.

@arkid15r arkid15r marked this pull request as draft December 29, 2025 19:25
coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 30, 2025
@0xgaurav
Copy link
Author

I ran make check-test locally.

All checks passed except frontend-related steps that require Node 24 and a running backend (pnpm format, lint, and GraphQL codegen). These failed locally due to environment constraints, but CI should have the correct Node version and backend setup to validate them.

Please let me know if you’d like me to take any further action.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 30, 2025
@0xgaurav 0xgaurav marked this pull request as ready for review December 31, 2025 03:49
Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

This code still doesn't address my comments from #3080 (review)

You didn't implement the milestones requirement and I don't see any tests.

@arkid15r arkid15r marked this pull request as draft January 1, 2026 20:01
@arkid15r
Copy link
Collaborator

arkid15r commented Jan 1, 2026

Please also resolve the conflicts.

@0xgaurav 0xgaurav marked this pull request as ready for review January 3, 2026 07:59
@0xgaurav 0xgaurav requested a review from arkid15r January 3, 2026 08:00
Copy link
Contributor

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/components/ToggleableList.tsx (1)

24-29: Remove dead code: useRouter and handleButtonClick are unused.

After the refactor to use renderItem, the handleButtonClick function is never called, and consequently router is unused. Remove these along with the useRouter import on line 1.

🔎 Proposed fix

Remove the import on line 1:

-import { useRouter } from 'next/navigation'

Remove the dead code inside the component:

 }) => {
   const [showAll, setShowAll] = useState(false)
-  const router = useRouter()

   const toggleShowAll = () => setShowAll(!showAll)
-  const handleButtonClick = ({ item }: { item: string }) => {
-    router.push(`/projects?q=${encodeURIComponent(item)}`)
-  }
   return (
🧹 Nitpick comments (1)
frontend/src/components/ToggleableList.tsx (1)

40-43: Ensure callers provide keys in renderItem.

The .map(renderItem) pattern is correct, but callers must return elements with unique key props to avoid React warnings. Consider adding a brief JSDoc comment on the renderItem prop to document this expectation:

/** Must return a React element with a unique `key` prop */
renderItem: (item: T, index: number, items: T[]) => React.ReactNode
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0676a39 and f9948c6.

📒 Files selected for processing (1)
  • frontend/src/components/ToggleableList.tsx
🔇 Additional comments (1)
frontend/src/components/ToggleableList.tsx (1)

8-21: Clean generic refactoring.

The generic type parameter <T,> with trailing comma is correct TypeScript syntax to disambiguate from JSX, and the renderItem callback signature is well-typed. This makes the component properly reusable for different item types.

@0xgaurav 0xgaurav marked this pull request as ready for review January 8, 2026 13:01
@0xgaurav 0xgaurav requested a review from kasya January 8, 2026 13:01
Copy link
Contributor

@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: 3

🤖 Fix all issues with AI agents
In @frontend/__tests__/unit/pages/About.test.tsx:
- Around line 684-691: The test in About.test.tsx asserts at most 6 milestones
but the About component (frontend/src/app/about/page.tsx) is rendered with
limit={4} and the jest.mock('utils/aboutData', ...) used in the test only
contains 2 timeline items, so update the test and mocks to align with the
implementation: change the expectation in the test to expect <= 4 (or update the
component to use limit={6} if 6 is the intended behavior), and expand the mocked
projectTimeline inside the jest.mock('utils/aboutData', ...) to include at least
5–7 items so the limit logic is actually exercised; refer to the About component
rendering in the test and the limit prop in page.tsx when making the change.

In @frontend/src/app/about/page.tsx:
- Around line 260-262: The Timeline limit is inconsistent with the PR objectives
(default should be first 6 items) — update the ToggleableList usage to set limit
to 6 (change the prop on the ToggleableList instance that passes
items={[...projectTimeline].reverse()}), and also update any related
documentation or PR description if you intentionally want to keep a different
default; ensure the ToggleableList prop name limit is the one you adjust so the
component shows six items by default.
- Around line 265-280: The connector visibility logic uses the full items array
length instead of the visible slice, causing the last visible item to still
render a connector; update the ToggleableList usage so the renderItem callback
is passed visibleItems.length (not items.length) as the third parameter, and
adjust the renderItem in this file (the anonymous renderItem that references
index !== items.length - 1) to compare index !== visibleCount - 1 (or use the
passed visible length) so the connector is hidden for the final visible
milestone.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81db77b and 18b9bcc.

📒 Files selected for processing (5)
  • frontend/__tests__/unit/components/ToggleableList.test.tsx
  • frontend/__tests__/unit/pages/About.test.tsx
  • frontend/src/app/about/page.tsx
  • frontend/src/components/CardDetailsPage.tsx
  • frontend/src/components/ToggleableList.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/components/CardDetailsPage.tsx
  • frontend/tests/unit/components/ToggleableList.test.tsx
  • frontend/src/components/ToggleableList.tsx

Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

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

@0xgaurav Could you address new Issues found by Sonar that were introduced in this pr?

Image

Also please address code rabbit comments. You're required to address all comments from Rabbit and get it's approval before we (leaders) review.

@0xgaurav 0xgaurav marked this pull request as draft January 9, 2026 05:47
Copy link
Contributor

@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: 3

🤖 Fix all issues with AI agents
In @frontend/__tests__/unit/pages/About.test.tsx:
- Line 696: The test currently uses screen.findAllByText(/20\d{2}/) which
matches any year on the page; scope the lookup to the Project Timeline container
instead (e.g., locate the timeline element via a role/label or data-testid and
then use within(timelineElement).findAllByText(/20\d{2}/)) so milestoneYears
only includes years in the Project Timeline section; update the test to get the
timeline element (e.g., via screen.getByRole or screen.getByTestId) and call
within(...) around the existing findAllByText call.
- Around line 692-699: The test, its comment, and the implementation disagree on
the timeline item limit: About.test.tsx names "6 items by default" but asserts
<=4 while page.tsx sets limit={4}; update the implementation and test to be
consistent with PR #3057 by changing the timeline component's prop from
limit={4} to limit={6} in page.tsx (the component rendering the Project
Timeline) and adjust the unit test in About.test.tsx to expect
milestoneYears.length toBeLessThanOrEqual(6) and update the test comment to
"limits project timeline milestones to 6 items by default".

In @frontend/src/app/about/page.tsx:
- Around line 260-262: The ToggleableList is currently rendered with limit={4}
which conflicts with PR objective #3057 and the test expecting 6 items; update
the limit prop on the ToggleableList instantiation (the component that passes
items={[...projectTimeline].reverse()}) to limit={6} so the default shows the
first six timeline items instead of four.
🧹 Nitpick comments (1)
frontend/src/app/about/page.tsx (1)

248-258: Consider using ToggleableList for consistency.

While the current slice + ShowMoreButton approach works correctly, you could use ToggleableList here as well (similar to the timeline section) for consistency across the codebase. This would centralize the show more/less logic.

♻️ Optional refactor using ToggleableList
-        <SecondaryCard icon={FaScroll} title={<AnchorTitle title="Our Story" />}>
-          {projectStory.slice(0, storyExpanded ? projectStory.length : 3).map((text) => (
-            <div key={`story-${text.substring(0, 50).replaceAll(' ', '-')}`} className="mb-4">
-              <div>
-                <Markdown content={text} />
-              </div>
-            </div>
-          ))}
-
-          {projectStory.length > 3 && (
-            <ShowMoreButton onToggle={() => setStoryExpanded(!storyExpanded)} />
-          )}
-        </SecondaryCard>
+        <SecondaryCard icon={FaScroll} title={<AnchorTitle title="Our Story" />}>
+          <ToggleableList
+            items={projectStory}
+            limit={3}
+            renderItem={(text) => (
+              <div className="mb-4">
+                <Markdown content={text} />
+              </div>
+            )}
+          />
+        </SecondaryCard>

This would also allow you to remove the storyExpanded state (lines 66-67).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18b9bcc and dc081b5.

📒 Files selected for processing (3)
  • frontend/__tests__/unit/pages/About.test.tsx
  • frontend/src/app/about/page.tsx
  • frontend/src/components/ToggleableList.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/unit/pages/About.test.tsx
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/pages/About.test.tsx
🧬 Code graph analysis (2)
frontend/src/components/ToggleableList.tsx (1)
frontend/src/wrappers/IconWrapper.tsx (1)
  • IconWrapper (13-13)
frontend/src/app/about/page.tsx (3)
frontend/src/utils/aboutData.ts (2)
  • projectStory (56-60)
  • projectTimeline (61-138)
backend/apps/slack/models/message.py (1)
  • text (83-85)
frontend/src/components/MarkdownWrapper.tsx (1)
  • Markdown (5-20)
🔇 Additional comments (6)
frontend/__tests__/unit/pages/About.test.tsx (1)

130-133: Good addition of explicit MarkdownWrapper mock.

The explicit mock for MarkdownWrapper helps isolate the component behavior in tests and provides consistent rendering across test scenarios.

frontend/src/app/about/page.tsx (2)

248-258: "Our Story" section correctly implements the 3-item limit.

The implementation properly limits the story paragraphs to 3 items and shows the ShowMoreButton only when there are more than 3 items, which aligns with PR objective #3057.


8-8: LGTM: Clean integration of new components and state.

The imports and state management for the Show More/Less functionality are properly structured and integrate well with the existing component.

Also applies to: 32-34, 66-67

frontend/src/components/ToggleableList.tsx (3)

6-13: Excellent refactor to a generic, reusable component.

The migration to TypeScript generics with a renderItem callback is a well-designed improvement that:

  • Provides type safety for any item type
  • Enables flexible, custom rendering per use case
  • Maintains the existing Show More/Less behavior
  • Makes the component highly reusable across the codebase

The API design is clean and intuitive, with visibleCount parameter in renderItem allowing renderers to adjust based on visible items (e.g., for connectors in timeline views).

Also applies to: 15-22


41-41: Using index as key is acceptable here.

While using key={index} is generally discouraged in React, it's acceptable in this case because:

  • Items don't have reordering operations
  • The component is display-only (no editable state per item)
  • Items are sliced from a stable source array

If items ever have unique IDs in the future, consider updating consumers to pass a getItemKey prop for better key stability.


29-36: Good: label is now optional for flexible usage.

Making the label optional allows parent components to provide their own title structure (like SecondaryCard in the About page), which improves component composition flexibility.

Copy link
Contributor

@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: 2

🤖 Fix all issues with AI agents
In @frontend/__tests__/unit/pages/About.test.tsx:
- Around line 692-697: The test "limits project timeline milestones to 4 items
by default" and the ToggleableList usage in about/page.tsx are inconsistent with
the PR objective of showing 6 items by default; update the ToggleableList
instantiation in about/page.tsx to use limit={6} (or the desired canonical
value) and modify the unit test in frontend/__tests__/unit/pages/About.test.tsx
to assert milestones.length isLessThanOrEqual(6) so both the implementation
(ToggleableList limit) and the test (limits project timeline milestones to 6
items by default) match the PR objective.

In @frontend/src/app/about/page.tsx:
- Around line 260-285: The Project Timeline is missing its header and wrapper
because ToggleableList is rendered without a label/icon like other sections; fix
by either passing a label="Project Timeline" and appropriate icon prop into the
ToggleableList invocation (so it renders the heading consistently) or wrap the
ToggleableList inside the same SecondaryCard + AnchorTitle structure used
elsewhere (use SecondaryCard and AnchorTitle around ToggleableList) to restore
styling and the section header; update the JSX around ToggleableList accordingly
(referencing ToggleableList, SecondaryCard, and AnchorTitle).
🧹 Nitpick comments (2)
frontend/src/app/about/page.tsx (1)

8-9: Consider combining useState and useEffect imports.

Minor style suggestion: these can be combined into a single import statement.

Suggested change
-import { useState } from 'react'
-import { useEffect } from 'react'
+import { useState, useEffect } from 'react'
frontend/src/components/ToggleableList.tsx (1)

40-48: Using index as key may cause rendering issues.

Using array index as key can lead to incorrect component reconciliation when items are reordered, filtered, or when list contents change. Since items are generic type T, consider requiring an id field or accepting a keyExtractor prop.

Suggested approach with keyExtractor
 type ToggleableListProps<T> = {
   items: T[]
   label?: React.ReactNode
   icon?: IconType
   limit?: number
   isDisabled?: boolean
   renderItem: (item: T, index: number, visibleCount: number) => React.ReactNode
+  keyExtractor?: (item: T, index: number) => string | number
 }

 const ToggleableList = <T,>({
   items,
   label,
   icon,
   limit = 10,
   isDisabled = false,
   renderItem,
+  keyExtractor = (_, index) => index,
 }: ToggleableListProps<T>) => {
   // ...
   {visibleItems.map((item, index) => (
     <div
-      key={index}
+      key={keyExtractor(item, index)}
       aria-disabled={isDisabled}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc081b5 and d9285c7.

📒 Files selected for processing (3)
  • frontend/__tests__/unit/pages/About.test.tsx
  • frontend/src/app/about/page.tsx
  • frontend/src/components/ToggleableList.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/src/components/ToggleableList.tsx (1)
frontend/src/wrappers/IconWrapper.tsx (1)
  • IconWrapper (13-13)
frontend/src/app/about/page.tsx (2)
frontend/src/utils/aboutData.ts (2)
  • projectStory (56-60)
  • projectTimeline (61-138)
frontend/src/components/MarkdownWrapper.tsx (1)
  • Markdown (5-20)
🔇 Additional comments (9)
frontend/__tests__/unit/pages/About.test.tsx (3)

69-80: LGTM!

Mock data for projectStory and projectTimeline is properly structured to support testing the show more/less functionality. The 6 timeline items provide enough data to test the limit behavior.


129-133: LGTM!

The MarkdownWrapper mock correctly isolates the component by rendering content in a testable way with a data-testid attribute.


170-205: LGTM!

The ShowMoreButton mock correctly implements toggle state behavior, matching the real component's interface with onToggle callback and expanded/collapsed UI states.

frontend/src/app/about/page.tsx (2)

248-258: LGTM!

The "Our Story" section correctly limits to 3 items by default and conditionally renders the ShowMoreButton when there are more than 3 items. The implementation aligns with PR objectives.


66-67: LGTM!

The storyExpanded state is properly initialized and scoped to control the "Our Story" section expansion.

frontend/src/components/ToggleableList.tsx (4)

6-13: LGTM!

The generic type definition is well-structured. The renderItem callback providing visibleCount is a thoughtful addition that enables rendering of connectors between items (as used in the timeline).


15-26: LGTM!

The generic component signature and visible items logic are correctly implemented. The slice operation properly limits items when showAll is false.


30-37: LGTM!

The optional label rendering with icon support provides good flexibility. The conditional rendering prevents empty headers when no label is provided.


51-53: LGTM!

The ShowMoreButton is correctly hidden when isDisabled is true or when all items fit within the limit, preventing unnecessary UI elements.

@github-actions github-actions bot removed the backend label Jan 9, 2026
Copy link
Contributor

@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: 2

🤖 Fix all issues with AI agents
In @frontend/src/app/about/page.tsx:
- Around line 259-278: The Project Timeline ToggleableList is missing the
section heading and icon—pass a human-readable label and an icon component to
the ToggleableList props (e.g., label="Project Timeline" and icon={FaClock} or
another timeline icon) and import the chosen icon from react-icons (e.g., import
{ FaClock } from 'react-icons/fa'); update the ToggleableList usage to include
these props so the section header renders consistently with other sections.

In @frontend/src/components/ToggleableList.tsx:
- Around line 40-48: The current ToggleableList uses JSON.stringify(item) as the
React key in visibleItems.map, which is unstable and inefficient; add a
keyExtractor prop to the ToggleableList component (e.g., keyExtractor: (item: T,
index: number) => string) and use it to compute the key inside the map instead
of JSON.stringify, falling back to index only if no keyExtractor is provided;
update the ToggleableList props/interface and component signature and replace
the key generation in the map with a call to keyExtractor(item, index) so keys
are stable and performant.
🧹 Nitpick comments (1)
frontend/src/app/about/page.tsx (1)

247-257: Consider using ToggleableList for consistency.

The manual expand/collapse implementation works correctly and limits "Our Story" to 3 items as intended. However, since you've already refactored the Project Timeline to use the new ToggleableList component, consider applying the same pattern here to maintain consistency and reduce duplication.

♻️ Proposed refactor using ToggleableList
-        <SecondaryCard icon={FaScroll} title={<AnchorTitle title="Our Story" />}>
-          {projectStory.slice(0, storyExpanded ? projectStory.length : 3).map((text) => (
-            <div key={`story-${text.substring(0, 50).replaceAll(' ', '-')}`} className="mb-4">
-              <div>
-                <Markdown content={text} />
-              </div>
-            </div>
-          ))}
-
-          {projectStory.length > 3 && (
-            <ShowMoreButton onToggle={() => setStoryExpanded(!storyExpanded)} />
-          )}
-        </SecondaryCard>
+        <ToggleableList
+          items={projectStory}
+          label={<AnchorTitle title="Our Story" />}
+          icon={FaScroll}
+          limit={3}
+          renderItem={(text) => (
+            <div className="mb-4 w-full">
+              <Markdown content={text} />
+            </div>
+          )}
+        />

This would eliminate the storyExpanded state and make both sections use the same component pattern.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9285c7 and 63a41d5.

📒 Files selected for processing (3)
  • frontend/__tests__/unit/pages/About.test.tsx
  • frontend/src/app/about/page.tsx
  • frontend/src/components/ToggleableList.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/tests/unit/pages/About.test.tsx
🔇 Additional comments (8)
frontend/src/app/about/page.tsx (4)

8-8: LGTM: Import additions are correct.

The new imports for useState, ShowMoreButton, and ToggleableList are properly structured and align with the refactoring objectives.

Also applies to: 31-31, 33-33


65-65: LGTM: State initialization is correct.

The storyExpanded state is properly initialized with false to show collapsed content by default.


261-261: Verify the timeline item limit matches requirements.

The implementation uses a limit of 4 items, but the linked issue #3057 requested showing the first 6 Project Timeline items by default. Please confirm whether 4 items is the intended behavior or if this should be increased to 6 to match the original requirements.


262-277: LGTM: Timeline rendering with connectors is well-implemented.

The renderItem callback correctly handles:

  • Dynamic connector visibility based on visibleCount (Line 264)
  • Proper absolute positioning for both the connector line and timeline dot
  • Appropriate aria-hidden="true" for decorative elements

The connector logic will correctly adjust when toggling between collapsed and expanded states.

frontend/src/components/ToggleableList.tsx (4)

1-13: LGTM: Generic type definition is well-designed.

The refactoring to make ToggleableList generic is clean and type-safe. The renderItem callback signature with (item: T, index: number, visibleCount: number) provides consumers with all necessary information for conditional rendering (as demonstrated in the timeline connector logic).


23-26: LGTM: Visibility state management is correct.

The showAll state and visibleItems calculation cleanly handle the expand/collapse behavior. Exposing visibleCount to the renderItem callback is a thoughtful design that enables conditional rendering based on the current visible set.


30-37: LGTM: Optional label rendering is implemented correctly.

The conditional rendering of the header section allows the component to be flexible—consumers can omit the label for use cases that don't need a heading (though in practice, adding labels improves consistency and accessibility).


51-53: LGTM: ShowMoreButton rendering is correct.

The conditional rendering logic correctly shows the toggle button only when there are more items than the limit and the list is not disabled. The functional state update (v) => !v is the proper pattern for toggling boolean state.

Copy link
Contributor

@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 @frontend/src/components/ToggleableList.tsx:
- Around line 32-39: The h2 in ToggleableList contains a block-level div which
is invalid HTML; replace the inner div with a phrasing element (e.g., a span) or
move the flex container outside the h2 so the heading only contains phrasing
content. Update the JSX around the label rendering (the h2 and its children that
reference IconWrapper and {label}) to use a span with className="flex
items-center" (or move that span/div outside the h2) and ensure IconWrapper and
the label remain correctly nested and styled.
- Around line 6-14: The component allows a possibly-negative or NaN "limit"
which makes items.slice(0, limit) and the visible-toggle check items.length >
limit behave incorrectly; clamp and validate limit inside the ToggleableList
logic (e.g., normalize in the component that consumes ToggleableListProps.limit)
by coercing to a safe integer: if limit is missing/NaN/<=0 treat as 0, and if
limit > items.length set to items.length, then use that normalizedLimit for
items.slice(0, normalizedLimit) and for computing visibleCount/hasMore; update
references to limit, items.slice, and any computation that passes visibleCount
to renderItem accordingly.
- Around line 53-55: ToggleableList is toggling visibility with setShowAll but
ShowMoreButton keeps its own internal isExpanded state, causing possible
mismatch on external re-renders; update ToggleableList to pass the current
showAll state into ShowMoreButton (e.g., expanded={showAll} or
showAll={showAll}) and modify ShowMoreButton to accept and use that prop instead
of its internal state (remove or fallback its internal isExpanded), keeping the
onToggle callback as-is so the button text/icon stays in sync with
ToggleableList's showAll state.
- Around line 41-50: The wrapper around each rendered item in ToggleableList
currently uses aria-disabled and pointer-events-none but leaves interactive
descendants focusable; when isDisabled is true, add the inert attribute on the
item wrapper (so descendants are unfocusable/uninteractive) and only render
aria-disabled when isDisabled is true (don't emit aria-disabled="false"); keep
the existing className toggle (pointer-events-none) and preserve the key logic
(keyExtractor/item index) and renderItem/visibleCount usage.
🧹 Nitpick comments (1)
frontend/src/components/ToggleableList.tsx (1)

42-45: Strongly prefer stable keys; index fallback is risky.

If items can reorder (sort/filter, reverse, new data), index keys can cause remounting and state bugs inside renderItem. Consider making keyExtractor required (or at least defaulting to index via a local getKey to make the fallback explicit and easy to change later).

Low-impact cleanup
-        {visibleItems.map((item, index) => (
+        const getKey = keyExtractor ?? ((_: T, i: number) => i)
+        {visibleItems.map((item, index) => (
           <div
-            key={keyExtractor ? keyExtractor(item, index) : index}
+            key={getKey(item, index)}
             aria-disabled={isDisabled}
             className={isDisabled ? 'pointer-events-none opacity-60' : undefined}
           >
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63a41d5 and 74d50e8.

📒 Files selected for processing (2)
  • frontend/src/app/about/page.tsx
  • frontend/src/components/ToggleableList.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/app/about/page.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-30T13:41:20.846Z
Learnt from: codic-yeeshu
Repo: OWASP/Nest PR: 1444
File: frontend/src/components/NavDropDown.tsx:0-0
Timestamp: 2025-04-30T13:41:20.846Z
Learning: Use React's useId() hook rather than manually generating random strings when creating accessibility identifiers for UI components. This creates stable, unique IDs without causing hydration mismatches.

Applied to files:

  • frontend/src/components/ToggleableList.tsx
🧬 Code graph analysis (1)
frontend/src/components/ToggleableList.tsx (1)
frontend/src/wrappers/IconWrapper.tsx (1)
  • IconWrapper (13-13)

Copy link
Contributor

@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: 1

🧹 Nitpick comments (2)
frontend/src/components/ToggleableList.tsx (2)

6-14: Consider exporting the props type.

The ToggleableListProps<T> type definition is well-structured. Consider exporting it for consumers who may want to reference it when creating wrapper components or for documentation purposes.

📤 Export the props type
-type ToggleableListProps<T> = {
+export type ToggleableListProps<T> = {
   items: T[]
   label?: React.ReactNode
   icon?: IconType
   limit?: number
   isDisabled?: boolean
   keyExtractor?: (item: T, index: number) => string | number
   renderItem: (item: T, index: number, visibleCount: number) => React.ReactNode
 }

56-56: Consider removing redundant aria-disabled attribute.

The aria-disabled attribute on a div is non-standard and potentially confusing. Since the inert attribute (set via the useEffect on Line 32-40) already disables all interactions and provides the necessary accessibility semantics, this attribute is redundant.

🧹 Remove aria-disabled
       <div
         ref={containerRef}
         className="flex flex-wrap gap-2"
-        aria-disabled={isDisabled}
       >
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74d50e8 and f94c35c.

📒 Files selected for processing (3)
  • frontend/src/app/about/page.tsx
  • frontend/src/components/ShowMoreButton.tsx
  • frontend/src/components/ToggleableList.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/app/about/page.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-04-30T13:41:20.846Z
Learnt from: codic-yeeshu
Repo: OWASP/Nest PR: 1444
File: frontend/src/components/NavDropDown.tsx:0-0
Timestamp: 2025-04-30T13:41:20.846Z
Learning: Use React's useId() hook rather than manually generating random strings when creating accessibility identifiers for UI components. This creates stable, unique IDs without causing hydration mismatches.

Applied to files:

  • frontend/src/components/ToggleableList.tsx
📚 Learning: 2025-04-30T13:41:20.846Z
Learnt from: codic-yeeshu
Repo: OWASP/Nest PR: 1444
File: frontend/src/components/NavDropDown.tsx:0-0
Timestamp: 2025-04-30T13:41:20.846Z
Learning: When implementing dropdown menus or similar interactive components, always include proper accessibility features: ARIA attributes (aria-expanded, aria-haspopup, aria-controls), keyboard navigation support (Enter, Space, Escape keys), and mechanisms to close dropdowns when clicking outside.

Applied to files:

  • frontend/src/components/ToggleableList.tsx
📚 Learning: 2025-12-29T22:01:53.874Z
Learnt from: ShashaankS
Repo: OWASP/Nest PR: 3086
File: frontend/src/components/SortBy.tsx:45-45
Timestamp: 2025-12-29T22:01:53.874Z
Learning: In frontend/src/components/SortBy.tsx, the SelectItem uses hover:bg-transparent intentionally for better visual appeal and theme consistency. This design choice prioritizes a cleaner aesthetic over hover background feedback.

Applied to files:

  • frontend/src/components/ToggleableList.tsx
🧬 Code graph analysis (2)
frontend/src/components/ToggleableList.tsx (1)
frontend/src/wrappers/IconWrapper.tsx (1)
  • IconWrapper (13-13)
frontend/src/components/ShowMoreButton.tsx (1)
frontend/src/types/button.ts (1)
  • Button (5-11)
🔇 Additional comments (7)
frontend/src/components/ShowMoreButton.tsx (2)

4-9: LGTM! Clean controlled component refactor.

The conversion from uncontrolled to controlled component is well-executed. The type definition is clear, and the component signature properly accepts both expanded and onToggle props, enabling parent components to manage state.


15-26: LGTM! Proper event handling and rendering.

The button correctly uses onPress (per HeroUI API) and conditionally renders based on the expanded prop. Accessibility is preserved with aria-hidden icons and proper focus styles.

frontend/src/components/ToggleableList.tsx (5)

16-30: LGTM! Solid generic component foundation.

The generic component declaration and visibility logic are well-implemented. The safeLimit calculation properly guards against invalid limits, and the slicing logic correctly implements the show more/less behavior.


32-40: Verify browser support requirements for the inert attribute.

The inert attribute is a modern accessibility feature that disables all interactions within an element. While it's well-supported in current browsers (Chrome 102+, Firefox 112+, Safari 15.5+), confirm this aligns with the project's browser support requirements. If older browsers need support, consider a polyfill or alternative approach.


44-51: LGTM! Clean header implementation.

The optional header rendering is well-structured with proper semantics and icon integration.


68-73: LGTM! Proper ShowMoreButton integration.

The conditional rendering logic is correct, and the ShowMoreButton is properly wired with the controlled expanded prop and onToggle callback.


63-63: The visibleCount parameter is actively used and should not be removed.

The parameter is necessary for at least one consumer: the milestone timeline in frontend/src/app/about/page.tsx uses it to conditionally render a vertical connector line (by checking index !== visibleCount - 1). Removing it would break this functionality.

While most simple list items don't require this information, the parameter enables consumers to implement conditional rendering based on visibility position, which is a valid use case.

@0xgaurav 0xgaurav marked this pull request as ready for review January 9, 2026 13:54
@0xgaurav 0xgaurav requested a review from kasya January 9, 2026 13:55
@sonarqubecloud
Copy link

@kasya
Copy link
Collaborator

kasya commented Jan 10, 2026

@0xgaurav Hi!

  • The milestones/Roadmap block is still not limited to the desired number:
Screenshot 2026-01-10 at 12 09 42 PM
  • The project timeline shows the correct number by default, but the styling of the blocks got messed up.
Screenshot 2026-01-10 at 12 09 36 PM
  • you also didn't run make check-test locally before your update.
Screenshot 2026-01-10 at 12 13 16 PM

Keeping in mind that deadline for this issue was on Dec 31 - I'm afraid I have to close this PR and reassign the issue to someone else.
Feel free to try your skills on a different issue, but please keep in mind things you learnt in this PR (our workflow, requirements, etc).

@kasya kasya closed this Jan 10, 2026
@0xgaurav 0xgaurav deleted the limit-about-roadmap-timeline branch January 11, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Limit visible Roadmap & Timeline items on About page

4 participants