diff --git a/lib/ranking.ts b/lib/ranking.ts index f2cc4aac..f2a9f434 100644 --- a/lib/ranking.ts +++ b/lib/ranking.ts @@ -3,6 +3,7 @@ import { get24hPriceChange, getTokenTVL } from "./price"; import { STORY_FACTORY } from "./contracts/constants"; import type { Database, Storyline, User } from "./supabase"; import type { SupabaseClient } from "@supabase/supabase-js"; +import { getStoryStatus } from "./story-status"; interface RankedStoryline extends Storyline { trendScore: number; @@ -130,7 +131,6 @@ async function fetchCandidatesAndRatings( function applyBase(q: ReturnType) { let filtered = q .eq("hidden", false) - .eq("sunset", false) .neq("token_address", "") .eq("contract_address", STORY_FACTORY.toLowerCase()); if (writerType !== undefined) filtered = filtered.eq("writer_type", writerType); @@ -272,7 +272,13 @@ export async function getTrendingStorylines( }), ); - enriched.sort((a, b) => b.trendScore - a.trendScore); + // Active-first: active stories rank above completed/expired, then by trendScore + enriched.sort((a, b) => { + const aActive = getStoryStatus(a) === "active" ? 0 : 1; + const bActive = getStoryStatus(b) === "active" ? 0 : 1; + if (aActive !== bActive) return aActive - bActive; + return b.trendScore - a.trendScore; + }); return enriched.slice(offset, offset + limit); } diff --git a/lib/story-status.ts b/lib/story-status.ts new file mode 100644 index 00000000..8cf6af81 --- /dev/null +++ b/lib/story-status.ts @@ -0,0 +1,17 @@ +import type { Storyline } from "./supabase"; + +/** Deadline window in hours — stories expire this long after their last plot. */ +export const DEADLINE_HOURS = 168; +export const DEADLINE_MS = DEADLINE_HOURS * 60 * 60 * 1000; + +export type StoryStatus = "active" | "completed" | "expired"; + +/** Determine whether a story is active, completed, or expired. */ +export function getStoryStatus(storyline: Pick): StoryStatus { + if (storyline.sunset) return "completed"; + if (storyline.has_deadline && storyline.last_plot_time) { + const deadline = new Date(storyline.last_plot_time).getTime() + DEADLINE_MS; + if (Date.now() > deadline) return "expired"; + } + return "active"; +} diff --git a/package-lock.json b/package-lock.json index 9a15534c..5def62a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "plotlink", - "version": "0.1.47", + "version": "0.1.48", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "plotlink", - "version": "0.1.47", + "version": "0.1.48", "workspaces": [ "packages/*" ], diff --git a/package.json b/package.json index b01272bc..24c88500 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plotlink", - "version": "0.1.47", + "version": "0.1.48", "private": true, "workspaces": [ "packages/*" diff --git a/src/components/DeadlineCountdown.tsx b/src/components/DeadlineCountdown.tsx index dcd93a18..8b10cdf2 100644 --- a/src/components/DeadlineCountdown.tsx +++ b/src/components/DeadlineCountdown.tsx @@ -1,9 +1,9 @@ "use client"; import { useState, useEffect } from "react"; +import { DEADLINE_HOURS, DEADLINE_MS } from "../../lib/story-status"; -export const DEADLINE_HOURS = 168; -export const DEADLINE_MS = DEADLINE_HOURS * 60 * 60 * 1000; +export { DEADLINE_HOURS, DEADLINE_MS }; export function DeadlineCountdown({ lastPlotTime, diff --git a/src/components/StoryCard.tsx b/src/components/StoryCard.tsx index e9ae5f99..8e5fdf85 100644 --- a/src/components/StoryCard.tsx +++ b/src/components/StoryCard.tsx @@ -4,6 +4,7 @@ import { AgentBadge } from "./AgentBadge"; import { WriterIdentityClient } from "./WriterIdentityClient"; import { RatingSummary } from "./RatingSummary"; import { StoryCardTVL } from "./StoryCardStats"; +import { getStoryStatus } from "../../lib/story-status"; const DAY_MS = 24 * 60 * 60 * 1000; function isWithin24h(timestamp: string): boolean { @@ -21,9 +22,11 @@ export function StoryCard({ const isNew = storyline.last_plot_time ? isWithin24h(storyline.last_plot_time) : false; + const status = getStoryStatus(storyline); + const isActive = status === "active"; return ( -
+
{displayGenre || "Uncategorized"} - {storyline.sunset && ( - - complete - - )}
@@ -97,9 +95,20 @@ export function StoryCard({ )} - {/* Bottom: plot count + NEW badges */} + {/* Bottom: status + plot count + NEW badges */}
+ {isActive ? ( + + Active + + ) : ( + + {status === "expired" ? "Expired" : "Completed"} + + )} +
+
{storyline.plot_count} {storyline.plot_count === 1 ? "plot" : "plots"}