Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/lib/mock-data.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
getRecentActivity,
mockBounties,
mockMaintenancePools,
mockMilestones,
Expand Down Expand Up @@ -44,3 +45,22 @@ describe("mock-data asset coverage", () => {
expect(formatCurrency(milestone.distributed, milestone.asset)).toBe("16,837.25 XLM");
});
});

describe("recentActivity — dynamic timestamps (#707)", () => {
it("computes occurredAt timestamps relative to passed reference time", () => {
const fixedNow = new Date("2026-10-13T12:00:00Z").getTime();
const events = getRecentActivity(fixedNow);
const first = events[0];
// a1 is 6 minutes ago
expect(first.occurredAt).toBe(new Date(fixedNow - 6 * 60_000).toISOString());
});

it("advances timestamps dynamically at access time instead of freezing at module load", () => {
const time1 = new Date(recentActivity[0].occurredAt).getTime();
jest.useFakeTimers();
jest.advanceTimersByTime(3600 * 1000); // 1 hour later
const time2 = new Date(recentActivity[0].occurredAt).getTime();
expect(time2).toBeGreaterThan(time1);
jest.useRealTimers();
});
});
58 changes: 45 additions & 13 deletions src/lib/mock-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,36 +268,39 @@ export interface ActivityEvent {
occurredAt: string;
}

// minutesAgo(n) anchors each mock event n minutes before whenever this
// module is evaluated, so the feed's relative-time text keeps advancing
// (within a given server render) instead of being frozen at deploy time.
function minutesAgo(n: number): string {
return new Date(Date.now() - n * 60_000).toISOString();
interface RawActivityEventTemplate {
id: string;
handle: string;
action: string;
target: string;
amount?: number;
asset?: "USDC" | "XLM";
minutesAgo: number;
}

export const recentActivity: ActivityEvent[] = [
const RAW_RECENT_ACTIVITY: RawActivityEventTemplate[] = [
{
id: "a1",
handle: "devrel_ana",
action: "was paid",
target: "core-indexer#288",
amount: 40,
asset: "USDC",
occurredAt: minutesAgo(6),
minutesAgo: 6,
},
{
id: "a2",
handle: "0xkoda",
action: "claimed",
target: "soroban-escrow-sdk#17",
occurredAt: minutesAgo(22),
minutesAgo: 22,
},
{
id: "a3",
handle: "linh_dev",
action: "opened a pull request for",
target: "stellar-wallet-kit#475",
occurredAt: minutesAgo(48),
minutesAgo: 48,
},
{
id: "a4",
Expand All @@ -306,7 +309,7 @@ export const recentActivity: ActivityEvent[] = [
target: "v2.0: Multi-asset escrow",
amount: 2500,
asset: "USDC",
occurredAt: minutesAgo(71),
minutesAgo: 71,
},
{
id: "a4b",
Expand All @@ -315,7 +318,7 @@ export const recentActivity: ActivityEvent[] = [
target: "v1.4: Indexer performance pass",
amount: 15000,
asset: "XLM",
occurredAt: minutesAgo(95),
minutesAgo: 95,
},
{
id: "a5",
Expand All @@ -324,17 +327,46 @@ export const recentActivity: ActivityEvent[] = [
target: "core-indexer#301",
amount: 480,
asset: "USDC",
occurredAt: minutesAgo(130),
minutesAgo: 130,
},
{
id: "a6",
handle: "qa_marcus",
action: "joined as a",
target: "contributor",
occurredAt: minutesAgo(210),
minutesAgo: 210,
},
];

/**
* Returns recent activity events with timestamps computed dynamically at render/request time
* relative to `now` (#707). This prevents timestamps from freezing at module
* load time on long-running server instances.
*/
export function getRecentActivity(now: number = Date.now()): ActivityEvent[] {
return RAW_RECENT_ACTIVITY.map((e) => ({
id: e.id,
handle: e.handle,
action: e.action,
target: e.target,
amount: e.amount,
asset: e.asset,
occurredAt: new Date(now - e.minutesAgo * 60_000).toISOString(),
}));
}

/**
* Dynamic proxy ensuring callers referencing `recentActivity` receive fresh
* timestamps computed at access time rather than frozen at module evaluation (#707).
*/
export const recentActivity: ActivityEvent[] = new Proxy([] as ActivityEvent[], {
get(_target, prop, receiver) {
const list = getRecentActivity();
const val = Reflect.get(list, prop, receiver);
return typeof val === "function" ? val.bind(list) : val;
},
});


// Weekly earnings/spend series (8 weeks) for dashboard charts.
export const contributorEarningsHistory = [420, 560, 310, 780, 640, 890, 720, 1050];
Expand Down