diff --git a/viz/app/dom.js b/viz/app/dom.js
index eff8d4d..5b765bd 100644
--- a/viz/app/dom.js
+++ b/viz/app/dom.js
@@ -17,9 +17,38 @@ export function esc(s) {
export function prettyTs(iso) {
const s = String(iso || "");
if (!s) return "";
- return s.replace("T", " ");
+
+ const d = new Date(s);
+ if (!Number.isFinite(d.getTime())) return "";
+
+ const parts = new Intl.DateTimeFormat("en-US", {
+ timeZone: "America/Vancouver",
+ month: "long",
+ day: "numeric",
+ hour: "numeric",
+ minute: "2-digit",
+ hour12: true,
+ }).formatToParts(d);
+
+ let month = "";
+ let day = "";
+ let hour = "";
+ let minute = "";
+ let dayPeriod = "";
+
+ for (const p of parts) {
+ if (p.type === "month") month = p.value;
+ else if (p.type === "day") day = p.value;
+ else if (p.type === "hour") hour = p.value;
+ else if (p.type === "minute") minute = p.value;
+ else if (p.type === "dayPeriod") dayPeriod = p.value;
+ }
+
+ const ampm = String(dayPeriod || "").toLowerCase(); // "am"/"pm"
+ return `${month} ${day} ${hour}:${minute}${ampm}`;
}
+
export function renderThumbHtml(imgUrl, cls = "thumb") {
const img = normImg(imgUrl);
if (!img) return `
`;
diff --git a/viz/app/search_page.js b/viz/app/search_page.js
index 0b83381..bca5259 100644
--- a/viz/app/search_page.js
+++ b/viz/app/search_page.js
@@ -213,25 +213,46 @@ export function renderSearch($app) {
$results.innerHTML = `Type to search…
`;
return;
}
-
+
const canon = typeof canonicalSkuFn === "function" ? canonicalSkuFn : (x) => x;
-
- const days = Number.isFinite(Number(recent?.windowDays)) ? Number(recent.windowDays) : 3;
-
+
+ const nowMs = Date.now();
+ const cutoffMs = nowMs - 24 * 60 * 60 * 1000;
+
+ function eventMs(r) {
+ const t = String(r?.ts || "");
+ const ms = t ? Date.parse(t) : NaN;
+ if (Number.isFinite(ms)) return ms;
+
+ // fallback: date-only => treat as start of day UTC-ish
+ const d = String(r?.date || "");
+ const ms2 = d ? Date.parse(d + "T00:00:00Z") : NaN;
+ return Number.isFinite(ms2) ? ms2 : 0;
+ }
+
+ const inWindow = items.filter((r) => {
+ const ms = eventMs(r);
+ return ms >= cutoffMs && ms <= nowMs;
+ });
+
+ if (!inWindow.length) {
+ $results.innerHTML = `No changes in the last 24 hours.
`;
+ return;
+ }
+
// rank + sort (custom)
- const ranked = items
+ const ranked = inWindow
.map((r) => ({ r, meta: rankRecent(r, canon) }))
.sort((a, b) => {
if (b.meta.score !== a.meta.score) return b.meta.score - a.meta.score;
if (b.meta.tie !== a.meta.tie) return b.meta.tie - a.meta.tie;
- // stable-ish fallback
return String(a.meta.sku || "").localeCompare(String(b.meta.sku || ""));
});
-
+
const limited = ranked.slice(0, 140);
-
+
$results.innerHTML =
- `Recently changed (last ${esc(days)} day(s)):
` +
+ `Recently changed (last 24 hours):
` +
limited
.map(({ r, meta }) => {
const kindLabel =
@@ -248,22 +269,22 @@ export function renderSearch($app) {
: meta.kind === "price_change"
? "PRICE"
: "CHANGE";
-
+
const priceLine =
meta.kind === "new" || meta.kind === "restored" || meta.kind === "removed"
? `${esc(r.price || "")}`
: `${esc(r.oldPrice || "")} → ${esc(r.newPrice || "")}`;
-
+
const when = r.ts ? prettyTs(r.ts) : r.date || "";
-
+
const sku = meta.sku;
const agg = aggBySku.get(sku) || null;
const img = agg?.img || "";
-
+
// show "+N" if the canonical SKU exists in other stores (via SKU mapping)
const storeCount = agg?.stores?.size || 0;
const plus = storeCount > 1 ? ` +${storeCount - 1}` : "";
-
+
const href = String(r.url || "").trim();
const storeBadge = href
? ``
: `${esc((r.storeLabel || "") + plus)}`;
-
+
// date as a badge so it sits nicely in the single meta row
const dateBadge = when ? `${esc(when)}` : "";
-
+
// subtle styles (inline so you don’t need to touch CSS)
const offBadge =
meta.kind === "price_down" && meta.pctOff !== null
@@ -283,12 +304,12 @@ export function renderSearch($app) {
meta.pctOff
)}% Off]`
: "";
-
+
const kindBadgeStyle =
meta.kind === "new" && meta.isNewUnique
? ` style="color:rgba(20,110,40,0.95); background:rgba(20,110,40,0.10); border:1px solid rgba(20,110,40,0.20);"`
: "";
-
+
return `
@@ -313,7 +334,7 @@ export function renderSearch($app) {
`;
})
.join("");
-
+
for (const el of Array.from($results.querySelectorAll(".item"))) {
el.addEventListener("click", () => {
const sku = el.getAttribute("data-sku") || "";