Skip to content

fix: show one year instead of 12 months in relative timestamps#3174

Merged
Patrick-Erichsen merged 1 commit into
openclaw:mainfrom
Yigtwxx:fix/relative-time-one-year-boundary
Jul 20, 2026
Merged

fix: show one year instead of 12 months in relative timestamps#3174
Patrick-Erichsen merged 1 commit into
openclaw:mainfrom
Yigtwxx:fix/relative-time-one-year-boundary

Conversation

@Yigtwxx

@Yigtwxx Yigtwxx commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Fixes an issue where listings last updated between 360 and 364 days ago displayed
"Updated 12mo ago" instead of "Updated 1y ago".

timeAgo measures months as 30 days but years as 365 days. Those two units do not
tile: 360-364 days still divides into twelve whole 30-day months while remaining
below the 365-day year threshold, so the month branch renders a twelve-month value
that should already have rolled over into years.

The affected surfaces are the skill list rows, skill header, browse results, plugin
detail page, dashboard catalog and needs-attention views, and the GitHub sync
timestamp in settings.

Why This Change Was Made

The repository already contains the intended semantics. formatRelativeUpdatedAt
in src/routes/user/$handle.tsx derives years from whole months and caps the month
branch at 11:

const months = Math.floor(days / 30);
if (months < 12) return `${months}mo ago`;
return `${Math.floor(months / 12)}y ago`;

timeAgo is now aligned with that rule, so the two relative-time renderers agree at
the one-year boundary. Deriving years from months also removes the unit mismatch at
its source rather than special-casing the twelve-month output.

Non-goals, kept out to hold the change to one concern:

  • Consolidating timeAgo and formatRelativeUpdatedAt into a single helper. They
    are now consistent; merging them is a separate refactor.
  • Making the 30-day month calendar-aware.
  • Future-dated timestamps. Both functions already render those as "just now", so
    clamping the difference would be a no-op here.

User Impact

Relative timestamps no longer show a twelve-month value. A listing that has not been
updated for just under a year now reads "1y ago", matching what the publisher profile
route has always shown for the same listing.

Evidence

Added src/lib/timeAgo.test.ts, which had no test coverage before this change even
though src/lib/** is inside the vitest.config.ts coverage include list.

The new boundary case fails on the parent commit and passes with the fix:

$ bunx vitest run src/lib/timeAgo.test.ts        # before the fix
FAIL  src/lib/timeAgo.test.ts > timeAgo > rolls the last days before a year over into years
AssertionError: expected '12mo ago' to be '1y ago'
 ❯ src/lib/timeAgo.test.ts:54:28
     54|     expect(agoByDays(360)).toBe("1y ago");

Test Files  1 failed (1)
     Tests  1 failed | 5 passed (6)

$ bunx vitest run src/lib/timeAgo.test.ts        # with the fix
Test Files  1 passed (1)
     Tests  6 passed (6)

The other five cases pass on both sides, so the existing minute, hour, day, week and
month branches are unchanged.

Rendered SkillListItem output, reading .skill-list-item-meta-item.is-updated from
the mounted component with the clock fixed at 2026-06-23T12:00:00Z:

updatedAt before after
now - 330 days Updated 11mo ago Updated 11mo ago
now - 359 days Updated 11mo ago Updated 11mo ago
now - 360 days Updated 12mo ago Updated 1y ago
now - 364 days Updated 12mo ago Updated 1y ago
now - 365 days Updated 1y ago Updated 1y ago
now - 400 days Updated 1y ago Updated 1y ago
now - 730 days Updated 2y ago Updated 2y ago

Regression check on the consumers and on the sibling renderer:

$ bunx vitest run src/lib/timeAgo.test.ts src/components/SkillListItem.test.tsx \
    src/__tests__/user-profile-route.test.tsx
Test Files  3 passed (3)
     Tests  29 passed (29)

Gates run locally on Windows:

  • bun run format:check -- src/lib/timeAgo.ts src/lib/timeAgo.test.ts — clean
  • bun run lint — clean
  • bunx tsc --noEmit -p tsconfig.json — clean
  • bun run deadcode:ci — clean
  • bun run ci:unit — 4722 passed. The 22 failures are pre-existing on this platform
    and unrelated to this change: they are spawn bun ENOENT in the worker and
    security-dataset suites plus the design-contract and management route suites. I
    confirmed they fail identically on the unmodified parent commit, so I have left
    them alone. CI on Linux is the authoritative signal here.

The Vercel preview check will need OpenClaw Foundation team authorization, as with
other fork pull requests.

Listings updated 360-364 days ago rendered as "12mo ago" because 30-day
months do not tile a 365-day year, leaving a five-day gap that still
divided into twelve whole months.

Derive years from whole months so they roll over at 12, matching
formatRelativeUpdatedAt in routes/user/$handle.tsx, which already caps
months at 11.
@vercel

vercel Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

@Yigtwxx is attempting to deploy a commit to the OpenClaw Foundation Team on Vercel.

A member of the Team first needs to authorize it.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. labels Jul 19, 2026
@clawsweeper

clawsweeper Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 19, 2026, 5:09 AM ET / 09:09 UTC.

Summary
The PR changes timeAgo to roll 30-day months into years at 12 months and adds fixed-clock boundary tests for minute-through-year relative timestamps.

Reproducibility: yes. source-reproducible with high confidence: a fixed Date.now() and timestamps 360–364 days earlier exercise the former 365-day versus 30-day-month boundary directly.

Review metrics: 2 noteworthy metrics.

  • Patch surface: 2 files affected; 70 added, 7 removed. The change is confined to one timestamp helper and one focused regression test file.
  • Boundary coverage: 6 test groups; 4 explicit pre-year boundary assertions. The added tests directly cover the previously inconsistent 360–364-day display interval.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Next step before merge

  • [P2] No concrete repair is needed: the patch has no actionable review finding and awaits ordinary maintainer merge review.

Security
Cleared: The diff only changes deterministic display formatting and local Vitest coverage; it adds no dependencies, execution paths, permissions, secret handling, or supply-chain surface.

Review details

Best possible solution:

Merge the focused helper and regression tests if routine CI remains clean, preserving the shared 12-month rollover convention between the relative-time renderers.

Do we have a high-confidence way to reproduce the issue?

Yes, source-reproducible with high confidence: a fixed Date.now() and timestamps 360–364 days earlier exercise the former 365-day versus 30-day-month boundary directly.

Is this the best way to solve the issue?

Yes. Deriving the year branch from the helper's existing whole-month calculation is the narrowest maintainable solution and matches the sibling formatter convention cited in the PR.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against aaa73625ed41.

Label changes

Label changes:

  • add P3: This corrects a limited-scope timestamp display inconsistency without affecting stored data, authentication, delivery, or runtime availability.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides before/after targeted test output and mounted consumer results for the affected timestamp interval, demonstrating the changed behavior in a real component path.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body provides before/after targeted test output and mounted consumer results for the affected timestamp interval, demonstrating the changed behavior in a real component path.

Label justifications:

  • P3: This corrects a limited-scope timestamp display inconsistency without affecting stored data, authentication, delivery, or runtime availability.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body provides before/after targeted test output and mounted consumer results for the affected timestamp interval, demonstrating the changed behavior in a real component path.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides before/after targeted test output and mounted consumer results for the affected timestamp interval, demonstrating the changed behavior in a real component path.
Evidence reviewed

What I checked:

  • Focused implementation: The proposed helper removes the independent 365-day year threshold and derives years from the same 30-day month unit used by the month branch, eliminating the 360–364-day 12mo ago output. (src/lib/timeAgo.ts:23, 39f424e9b06a)
  • Boundary regression coverage: The added fixed-clock tests cover the affected 330, 359, 360, and 364-day boundary values as well as the existing minute, hour, day, week, month, and year branches. (src/lib/timeAgo.test.ts:45, 39f424e9b06a)
  • Real behavior evidence: The PR body includes before/after targeted Vitest output and mounted SkillListItem results showing only the 360–364-day display changes from 12mo ago to 1y ago; its stated consumer regression run covers the list item and profile renderer. (src/components/SkillListItem.test.tsx, 39f424e9b06a)
  • Current-main comparison: The PR is based on current main aaa73625ed4100b1006653f49089f2a2d969a427; the provided diff shows the existing helper still uses a 365-day year threshold, so the central fix is not already implemented on main. (src/lib/timeAgo.ts:3, aaa73625ed41)

Likely related people:

  • unresolved: The available read-only command environment could not execute the required local git-history inspection, and the supplied PR context does not identify the author of the current-main helper or sibling formatter. (role: current-main history owner; confidence: low; files: src/lib/timeAgo.ts, src/routes/user/$handle.tsx)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper

clawsweeper Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper status: review started.

I am starting a fresh review of this pull request: fix: show one year instead of 12 months in relative timestamps This is item 1/1 in the current shard. Shard 10/20.

This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking.

Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted.

@Patrick-Erichsen Patrick-Erichsen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This fixes the shared relative-time helper so ClawHub never renders 12mo ago; at 12 whole 30-day months it now rolls over to 1y ago, matching the publisher-profile formatter.

LOC: +70/-7 (2 files)

No blocking findings.

Best-fix verdict: best. The change updates the owner helper used by skill rows, skill/plugin details, browse results, dashboard views, and GitHub sync settings. Deriving years from the same 30-day month convention is the narrowest fix that also agrees with formatRelativeUpdatedAt.

Boundary review: 359d -> 11mo, 360d -> 1y, 364d -> 1y, 365d -> 1y, and later 12-month rollovers remain internally consistent. Future timestamps still return just now. The output remains the existing locale-independent compact English format; this PR does not add or remove localization behavior.

Alternatives considered: clamping only the literal 12mo case would preserve two competing year units and let the helpers disagree; consolidating both formatters would be a broader refactor than this bug requires.

Code read: src/lib/timeAgo.ts, its new boundary tests, all production callers, src/routes/user/$handle.tsx, and the adjacent profile/list-item tests.

Validation on 39f424e9b06ab5509aae32a9705cb5417eca62e3:

  • focused consumers: 3 files, 29 tests passed
  • bun run ci:static: passed
  • bunx tsc --noEmit -p tsconfig.json: passed
  • bun run ci:unit: 4,803 passed, 1 skipped, with one unrelated package-detail-route responsive-layout timeout; that test passes immediately in isolation on both the exact PR head and a clean synthetic merge with current main
  • synthetic merge with current main 57d1e1530b5517d01c916a032efd1653abe691c9: conflict-free; the same 29 focused tests passed

Provenance: the mixed 30-day-month/365-day-year helper was introduced by Vincent Koc in commit 150470820838 through #1567 (PR author BunsDev, merged April 7, 2026). Current fix author: Yigtwxx.

Remaining uncertainty: GitHub's Vercel status is red only because the fork author is not authorized for the OpenClaw Foundation team preview. No preview is needed to validate this deterministic text-only helper change.

@Patrick-Erichsen
Patrick-Erichsen merged commit 1821e80 into openclaw:main Jul 20, 2026
15 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants