Skip to content

T1 Bounty: TimeAgo component (package-lock fixed)#627

Merged
chronoeth-creator merged 8 commits intoSolFoundry:mainfrom
davidweb3-ctrl:fix/bounty-486-timeago
Mar 22, 2026
Merged

T1 Bounty: TimeAgo component (package-lock fixed)#627
chronoeth-creator merged 8 commits intoSolFoundry:mainfrom
davidweb3-ctrl:fix/bounty-486-timeago

Conversation

@davidweb3-ctrl
Copy link
Contributor

Fixes #486

Summary

Reusable TimeAgo component with relative timestamps, auto-updates, and hover tooltips.

Changes

  • TimeAgo.tsx - Core component
  • TimeAgo.test.tsx - 16 tests, all passing
  • Applied to BountyCard, BountyListView, BountyTimeline, ActivityFeed

Fixes from review

  • 7-day boundary logic fixed (<= 7)
  • Invalid date handling added
  • Test timing issues resolved
  • act() wrapper added for React state updates

Note

Previous PR #593 was auto-closed due to package-lock.json - this branch has clean commits without dependency files.

Wallet: EDFxPF6yAQNod3nFzwV7z1qwSjt42WDYzmdT6b6YHDh7

- Create reusable TimeAgo component with auto-update
- Add comprehensive test suite
- Apply to bounty cards, list view, timeline, and activity feed
- Supports multiple formats: just now, Xm ago, Xh ago, Xd ago, date
- Includes tooltip with full datetime on hover
- Fix 7-day boundary logic (< 7 to <= 7)
- Add invalid date handling with safe fallbacks
- Fix test expectations for 60-minute boundary
- Add tests for invalid date inputs

Fixes critical issues from multi-LLM review (score: 6.4/10)
- Fix 'just now' test with deterministic timing
- Add act() wrapper for auto-update test
- Import act from testing-library/react
@davidweb3-ctrl
Copy link
Contributor Author

Wallet: EDFxPF6yAQNod3nFzwV7z1qwSjt42WDYzmdT6b6YHDh7

Ready for review! All tests passing.

@coderabbitai
Copy link

coderabbitai bot commented Mar 22, 2026

Caution

Review failed

Pull request was closed or merged during review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added a reusable TimeAgo component (with exported helpers formatTimeAgo and formatFullDate) that renders relative timestamps with a full-datetime tooltip and optional auto-update for recent dates. Replaced local/absolute date rendering with TimeAgo across BountyDetailPage, BountyTimeline, BountyCard, BountyListView, ContributorProfile, and ActivityFeed. Added unit tests exercising helpers, component rendering, update behavior, invalid inputs, and tooltip/attribute outputs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #486 — Implements the bounty's requirements for a reusable TimeAgo, relative formats, tooltip, and auto-update; changes map to the issue objectives.
  • SolFoundry/solfoundry#540 — Matches the objective to add a shared TimeAgo and replace absolute timestamps across activity/bounty/profile components.

Possibly related PRs

  • SolFoundry/solfoundry#225 — Updates BountyTimeline to use the shared TimeAgo component; touches the same file/logic changed here.
  • SolFoundry/solfoundry#231 — Replaces ActivityFeed's local relative-time helper with TimeAgo; directly related to ActivityFeed edits in this PR.

Suggested labels

approved, paid

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR partially implements issue #486 requirements: core TimeAgo component created with relative time formats and tooltips, applied to bounty list/activity feed/profile [#486], but leaderboard application missing and some formatting inconsistencies noted. Verify TimeAgo application completeness to leaderboard and profile pages; ensure all formats ('just now', '5m ago', '2h ago', '3d ago', 'Mar DD') are consistently implemented and tested.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'T1 Bounty: TimeAgo component (package-lock fixed)' clearly describes the main change: introducing a TimeAgo component for a T1 bounty, with a note about fixing package-lock issues.
Description check ✅ Passed The description is directly related to the changeset, detailing the TimeAgo component creation, test coverage, application to multiple components, and review fixes applied.
Out of Scope Changes check ✅ Passed All changes are directly scoped to TimeAgo component development and its application to bounty UI elements as specified in issue #486, with no unrelated modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/bounties/BountyListView.tsx`:
- Around line 61-63: BountyListView currently renders the TimeAgo component
unconditionally which can display "just now" when b.createdAt is undefined;
update the JSX around TimeAgo in BountyListView (the TimeAgo usage that
references b.createdAt) to match BountyCard's behavior by conditionally
rendering TimeAgo only when b.createdAt is present (e.g., render the <TimeAgo
... /> element only if b.createdAt truthy), preserving the same className and
layout when omitted.

In `@frontend/src/components/common/__tests__/TimeAgo.test.tsx`:
- Around line 120-130: Add a complementary test in TimeAgo.test.tsx that
verifies no auto-updates occur for dates older than 7 days: render the TimeAgo
component (symbol: TimeAgo) with a date more than 7 days in the past and an
updateInterval (e.g., 60000), capture the initial rendered text (e.g., "8d ago"
or the expected label), then wrap vi.advanceTimersByTime(...) in act(...) to
advance several intervals and assert the displayed text remains the same; ensure
the test uses fake timers (vi.useFakeTimers()) and cleans up timers after to
mirror the existing interval test structure.

In `@frontend/src/components/common/TimeAgo.tsx`:
- Around line 103-104: The component currently masks invalid dates by setting
safeDate = isValidDate(date) ? date : new Date().toISOString(); — instead,
detect invalid dates via isValidDate(date) and in that branch (where safeDate
would be replaced) log a warning in development (check process.env.NODE_ENV ===
'development') including the original date value and the component name TimeAgo,
and change rendering to show a distinct indicator (e.g., "—" or the raw date
string) rather than using new Date().toISOString(); update any
variables/functions that consume safeDate accordingly so the invalid path
returns the indicator and does not produce a misleading "just now" UI.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 58345631-b277-43b3-9220-0b544e7def68

📥 Commits

Reviewing files that changed from the base of the PR and between c5b1d75 and 8185c15.

📒 Files selected for processing (6)
  • frontend/src/components/BountyDetailPage.tsx
  • frontend/src/components/BountyTimeline.tsx
  • frontend/src/components/bounties/BountyCard.tsx
  • frontend/src/components/bounties/BountyListView.tsx
  • frontend/src/components/common/TimeAgo.tsx
  • frontend/src/components/common/__tests__/TimeAgo.test.tsx

Comment on lines +103 to +104
// Handle invalid dates gracefully
const safeDate = isValidDate(date) ? date : new Date().toISOString();
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Silent fallback for invalid dates may mask upstream issues.

When an invalid date is passed, the component falls back to new Date().toISOString(), displaying "just now" instead of indicating an error. While this prevents UI breakage, it could hide bugs where invalid data flows through the system.

Consider logging a warning in development mode or rendering a distinct visual indicator (like "—" or the raw value) so issues are discoverable:

Alternative approach
 export function TimeAgo({
   date,
   className = '',
   updateInterval = 60000,
 }: TimeAgoProps) {
   // Handle invalid dates gracefully
-  const safeDate = isValidDate(date) ? date : new Date().toISOString();
+  const isValid = isValidDate(date);
+  if (!isValid && process.env.NODE_ENV === 'development') {
+    console.warn('TimeAgo: received invalid date', date);
+  }
+  const safeDate = isValid ? date : new Date().toISOString();
📝 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
// Handle invalid dates gracefully
const safeDate = isValidDate(date) ? date : new Date().toISOString();
// Handle invalid dates gracefully
const isValid = isValidDate(date);
if (!isValid && process.env.NODE_ENV === 'development') {
console.warn('TimeAgo: received invalid date', date);
}
const safeDate = isValid ? date : new Date().toISOString();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/common/TimeAgo.tsx` around lines 103 - 104, The
component currently masks invalid dates by setting safeDate = isValidDate(date)
? date : new Date().toISOString(); — instead, detect invalid dates via
isValidDate(date) and in that branch (where safeDate would be replaced) log a
warning in development (check process.env.NODE_ENV === 'development') including
the original date value and the component name TimeAgo, and change rendering to
show a distinct indicator (e.g., "—" or the raw date string) rather than using
new Date().toISOString(); update any variables/functions that consume safeDate
accordingly so the invalid path returns the indicator and does not produce a
misleading "just now" UI.

- Consistent with BountyCard.tsx handling
- Shows '-' when createdAt is undefined
- Addresses CodeRabbit review feedback
@davidweb3-ctrl
Copy link
Contributor Author

Fixed! Added conditional check for missing createdAt in BountyListView to match BountyCard.tsx pattern. Shows '-' when undefined.

- Verifies that TimeAgo does not set interval for old dates
- Complements existing auto-update test for recent dates
- Addresses CodeRabbit review feedback
@davidweb3-ctrl
Copy link
Contributor Author

✅ All 3 issues fixed:

  1. BountyListView.tsx - Added conditional check for b.createdAt (already done)
  2. TimeAgo.test.tsx - Added test verifying no auto-updates for dates > 7 days

All 17 tests passing! Ready for re-review.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/common/__tests__/TimeAgo.test.tsx`:
- Around line 84-155: Add tests to cover invalid date prop handling in the
TimeAgo component: render TimeAgo with an invalid string (e.g., "not-a-date")
and with an invalid numeric timestamp (e.g., NaN or -1), assert the component
renders the fallback output used by the helper (no crash), confirm it does NOT
have datetime or title attributes, and verify it does not auto-update when
updateInterval is provided; target the TimeAgo component tests in
TimeAgo.test.tsx and reference the TimeAgo component render calls and
updateInterval behavior to locate where to add these cases.
- Around line 132-144: The test for TimeAgo should assert that no update
interval is created for dates older than 7 days; add a spy on global.setInterval
(or use vi.spyOn(global, 'setInterval')) before rendering
TimeAgo(date="2026-03-12T12:00:00Z", updateInterval=60000) and assert it was not
called, then advance timers and assert the output remains "Mar 12" to ensure no
interval-driven re-render; update the test in TimeAgo.test.tsx to install and
restore the spy around the render/assert steps and reference the TimeAgo
component and its updateInterval prop when making the assertions.
- Around line 54-58: The tests fail intermittently due to locale timezone
differences; update the date formatting in TimeAgo.tsx (functions like
formatTimeAgo / any toLocaleDateString/toLocaleString calls) to pass a fixed
timeZone: 'UTC' option to toLocaleDateString(...) and toLocaleString(...) so the
rendered strings (e.g., 'Mar 14', 'Feb 22', 'Dec 25') are deterministic across
environments, or alternatively pin the test runner timezone by setting TZ=UTC in
the test config/script if you prefer that approach.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 96432643-c7c0-4379-9b4c-bb92b0fe02b3

📥 Commits

Reviewing files that changed from the base of the PR and between 4066399 and 72c5fb8.

📒 Files selected for processing (1)
  • frontend/src/components/common/__tests__/TimeAgo.test.tsx

- Add UTC timezone for deterministic date formatting
- Add comprehensive invalid date handling tests
- Add setInterval spy tests for >7 days and invalid dates
- Fix component to skip interval for invalid dates
- Fix NaN handling in isValidDate

All 21 tests passing!
@davidweb3-ctrl
Copy link
Contributor Author

✅ All CodeRabbit issues fixed (batch 2):

  1. UTC timezone - Added to toLocaleDateString/toLocaleString for deterministic output
  2. Invalid date tests - Added tests for 'not-a-date' string and NaN
  3. setInterval spy tests - Verified no interval for invalid dates AND dates > 7 days

All 21 tests passing! Ready for final review.

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

♻️ Duplicate comments (2)
frontend/src/components/common/TimeAgo.tsx (2)

54-59: ⚠️ Potential issue | 🟠 Major

Future >7d formatting is timezone-dependent and can render the wrong calendar day.

Line [58] uses toLocaleDateString('en-US', { month, day }) without timeZone, while Line [74] explicitly uses UTC. This inconsistency can shift displayed dates across environments for the future-date fallback path.

As per coding guidelines, "frontend/**: React/TypeScript frontend. Check: Component structure and state management" and "Error/loading/empty state handling".

Also applies to: 73-75

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/common/TimeAgo.tsx` around lines 54 - 59, The future
>7d branch in the TimeAgo.tsx component (in the function that formats relative
times, e.g., TimeAgo/formatRelativeTime) uses toLocaleDateString('en-US', {
month: 'short', day: 'numeric' }) without specifying a timeZone, causing
timezone-dependent shifts; make this consistent with the other UTC formatting
path by adding timeZone: 'UTC' (or using the same UTC-based formatting helper
used around line 74) so the fallback calendar date for future dates is rendered
in UTC rather than local time.

110-116: ⚠️ Potential issue | 🟡 Minor

Invalid-date UI is internally inconsistent between text and tooltip metadata.

For invalid input, visible text is 'Invalid date' (Line [114]/Line [146]), but title is derived from fallback current time via safeDate (Line [111], Line [115], Line [143]). This produces contradictory UI semantics and can mask upstream data issues.

As per coding guidelines, "frontend/**: React/TypeScript frontend. Check: Error/loading/empty state handling".

Also applies to: 142-143

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/common/TimeAgo.tsx` around lines 110 - 116, The
component shows "Invalid date" in the visible text (formatTimeAgo(date)) but
uses safeDate (new Date().toISOString()) for the tooltip via
formatFullDate(safeDate), causing inconsistent UI; change the logic so both
displayed text and tooltip derive from the same validity check: if date is
invalid then set timeAgo and fullDate to the same "Invalid date" (or a localized
invalid-message) instead of falling back to current time, by updating the
initialization of timeAgo (useState(() => isValid ? formatTimeAgo(date) :
"Invalid date")), the useMemo for fullDate (useMemo(() => isValid ?
formatFullDate(date) : "Invalid date", [date, isValid])), and any places that
use safeDate so they use date when valid and the explicit invalid placeholder
when not.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/common/__tests__/TimeAgo.test.tsx`:
- Around line 60-65: The tests in TimeAgo.test.tsx currently exercise future
dates only up to 7 days and miss the codepath for future dates >7 days (the
date-string fallback) and its interval suppression behavior; add a unit test for
formatTimeAgo that passes a future date more than 7 days ahead (e.g., now + 8+
days) asserting the returned value uses the long date-string format (not "in
Xd") and verify any interval/auto-update suppression behavior (no periodic
short-form updates) by asserting stable output across a simulated tick/update;
locate and update the existing TimeAgo.test.tsx cases around the "handles future
dates" block and mirror the same checks near the later tests referenced (lines
~182-194) to cover regressions.

In `@frontend/src/components/common/TimeAgo.tsx`:
- Around line 127-132: The check using diffDays allows negative values for
future timestamps, so TimeAgo still sets an interval for far-future dates;
update the condition around then/diffDays in the TimeAgo component to ensure the
target time is not in the future before scheduling updates (e.g., require
diffDays >= 0 && diffDays <= 7 or check then <= Date.now()), then only call
setInterval when that stricter condition is met and keep clearing the interval
in the existing cleanup.

---

Duplicate comments:
In `@frontend/src/components/common/TimeAgo.tsx`:
- Around line 54-59: The future >7d branch in the TimeAgo.tsx component (in the
function that formats relative times, e.g., TimeAgo/formatRelativeTime) uses
toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) without
specifying a timeZone, causing timezone-dependent shifts; make this consistent
with the other UTC formatting path by adding timeZone: 'UTC' (or using the same
UTC-based formatting helper used around line 74) so the fallback calendar date
for future dates is rendered in UTC rather than local time.
- Around line 110-116: The component shows "Invalid date" in the visible text
(formatTimeAgo(date)) but uses safeDate (new Date().toISOString()) for the
tooltip via formatFullDate(safeDate), causing inconsistent UI; change the logic
so both displayed text and tooltip derive from the same validity check: if date
is invalid then set timeAgo and fullDate to the same "Invalid date" (or a
localized invalid-message) instead of falling back to current time, by updating
the initialization of timeAgo (useState(() => isValid ? formatTimeAgo(date) :
"Invalid date")), the useMemo for fullDate (useMemo(() => isValid ?
formatFullDate(date) : "Invalid date", [date, isValid])), and any places that
use safeDate so they use date when valid and the explicit invalid placeholder
when not.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e1fc1215-ac17-44be-bb12-b56701f3b977

📥 Commits

Reviewing files that changed from the base of the PR and between 72c5fb8 and b72b3fa.

📒 Files selected for processing (2)
  • frontend/src/components/common/TimeAgo.tsx
  • frontend/src/components/common/__tests__/TimeAgo.test.tsx

Comment on lines +60 to +65
it('handles future dates', () => {
expect(formatTimeAgo(new Date('2026-03-22T12:00:05Z'))).toBe('just now');
expect(formatTimeAgo(new Date('2026-03-22T12:05:00Z'))).toBe('in 5m');
expect(formatTimeAgo(new Date('2026-03-22T14:00:00Z'))).toBe('in 2h');
expect(formatTimeAgo(new Date('2026-03-25T12:00:00Z'))).toBe('in 3d');
});
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

Missing regression coverage for future dates older than 7 days.

Line [60]-Line [65] tests future values only within 7 days (in Xm/in Xh/in Xd). There is no explicit test for future values beyond 7 days (date-string fallback path), nor for interval suppression in that branch. This leaves a gap where future >7d behavior can regress undetected.

As per coding guidelines, "frontend/**: React/TypeScript frontend. Check: Component structure and state management" and "Error/loading/empty state handling".

Also applies to: 182-194

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/common/__tests__/TimeAgo.test.tsx` around lines 60 -
65, The tests in TimeAgo.test.tsx currently exercise future dates only up to 7
days and miss the codepath for future dates >7 days (the date-string fallback)
and its interval suppression behavior; add a unit test for formatTimeAgo that
passes a future date more than 7 days ahead (e.g., now + 8+ days) asserting the
returned value uses the long date-string format (not "in Xd") and verify any
interval/auto-update suppression behavior (no periodic short-form updates) by
asserting stable output across a simulated tick/update; locate and update the
existing TimeAgo.test.tsx cases around the "handles future dates" block and
mirror the same checks near the later tests referenced (lines ~182-194) to cover
regressions.

Comment on lines +127 to +132
const then = new Date(safeDate).getTime();
const diffDays = (Date.now() - then) / 86400000;

// Only auto-update if 7 days or less old
if (diffDays <= 7) {
const interval = setInterval(() => {
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

Far-future timestamps still schedule minute intervals due to signed day-diff check.

Line [128]-Line [132] computes a signed diffDays; any future date produces a negative value, which always satisfies <= 7. That means even far-future items keep an active interval, increasing timer pressure in list views without user-visible benefit.

As per coding guidelines, "frontend/**: React/TypeScript frontend. Check: Component structure and state management".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/common/TimeAgo.tsx` around lines 127 - 132, The check
using diffDays allows negative values for future timestamps, so TimeAgo still
sets an interval for far-future dates; update the condition around then/diffDays
in the TimeAgo component to ensure the target time is not in the future before
scheduling updates (e.g., require diffDays >= 0 && diffDays <= 7 or check then
<= Date.now()), then only call setInterval when that stricter condition is met
and keep clearing the interval in the existing cleanup.

- Replace custom formatRelativeTime with TimeAgo component
- Maintains auto-update functionality via TimeAgo's built-in interval
- Add TimeAgo display for most recent PR submission timestamp
- Shows 'Last PR submitted' with relative time in profile stats
@chronoeth-creator chronoeth-creator merged commit f7087fa into SolFoundry:main Mar 22, 2026
1 of 2 checks passed
@davidweb3-ctrl
Copy link
Contributor Author

Completeness issues FIXED:

  1. ActivityFeed - Replaced custom formatRelativeTime with TimeAgo component
  2. ContributorProfile - Added TimeAgo for 'Last PR submitted' timestamp
  3. Leaderboard - No timestamp fields available in API response

TimeAgo tests: 21/21 passing ✅

Note: Other repo tests have pre-existing failures (jest.clearAllMocks issue), not related to this PR.

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.

1 participant