grain.social is a photo sharing platform built on atproto. grain.social
atproto photography appview
57
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: clamp gallery sort key to indexed_at to ignore future-dated createdAt

A gallery published with a future-dated createdAt (e.g. client clock
skew) was pinning itself to the top of /recent — and inflating the
freshness term in /foryou's time-decay scoring — until wall-clock time
caught up. Order chronological feeds by min(created_at, indexed_at)
instead, so future-dated records slot in at their actual ingest time.
Backdated values (e.g. /settings/import preserving the original
Bluesky post date) still sort by created_at since min picks the
smaller value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+38 -21
+11
server/feeds/_galleryTable.ts
··· 1 + // Use in place of `"social.grain.gallery" t` in chronological feed queries. 2 + // Exposes `t.sort_at = min(created_at, indexed_at)` so that future-dated 3 + // `createdAt` values (client clock skew) don't pin galleries to the top of 4 + // `/recent` and friends. Backdated values (e.g. /settings/import sets 5 + // createdAt to the original Bluesky post date) are preserved, so historical 6 + // imports still slot into history rather than the top of the feed. 7 + // 8 + // Pair with `orderBy: "t.sort_at"` and select `t.sort_at` for the cursor. 9 + export const galleryFeedTable = `( 10 + SELECT *, min(created_at, indexed_at) AS sort_at FROM "social.grain.gallery" 11 + ) t`;
+3 -2
server/feeds/camera.ts
··· 10 10 import { hideLabelsFilter } from "../labels/_hidden.ts"; 11 11 import { blockMuteFilter } from "../filters/blockMute.ts"; 12 12 import { cleanCameraName } from "../helpers/cameraName.ts"; 13 + import { galleryFeedTable } from "./_galleryTable.ts"; 13 14 14 15 export default defineFeed({ 15 16 collection: "social.grain.gallery", ··· 43 44 const bmParams = viewer ? [viewer] : []; 44 45 45 46 const { rows, cursor } = await ctx.paginate<{ uri: string }>( 46 - `SELECT t.uri, t.cid, t.created_at FROM "social.grain.gallery" t 47 + `SELECT t.uri, t.cid, t.sort_at FROM ${galleryFeedTable} 47 48 LEFT JOIN _repos r ON t.did = r.did 48 49 WHERE (r.status IS NULL OR r.status != 'takendown') 49 50 AND EXISTS ( ··· 54 55 AND ${hideLabelsFilter("t.uri")} 55 56 AND (SELECT count(*) FROM "social.grain.gallery.item" gi WHERE gi.gallery = t.uri) > 0 56 57 ${bmFilter}`, 57 - { orderBy: "t.created_at", params: [...matchingRaws, ...bmParams] }, 58 + { orderBy: "t.sort_at", params: [...matchingRaws, ...bmParams] }, 58 59 ); 59 60 60 61 return ctx.ok({ uris: rows.map((r) => r.uri), cursor });
+3 -2
server/feeds/following.ts
··· 2 2 import { hydrateGalleries } from "../hydrate/galleries.ts"; 3 3 import { hideLabelsFilter } from "../labels/_hidden.ts"; 4 4 import { blockMuteFilter } from "../filters/blockMute.ts"; 5 + import { galleryFeedTable } from "./_galleryTable.ts"; 5 6 6 7 export default defineFeed({ 7 8 collection: "social.grain.gallery", ··· 14 15 if (!actor) return ctx.ok({ uris: [] }); 15 16 16 17 const { rows, cursor } = await ctx.paginate<{ uri: string }>( 17 - `SELECT t.uri, t.cid, t.created_at FROM "social.grain.gallery" t 18 + `SELECT t.uri, t.cid, t.sort_at FROM ${galleryFeedTable} 18 19 LEFT JOIN _repos r ON t.did = r.did 19 20 WHERE (r.status IS NULL OR r.status != 'takendown') 20 21 AND t.did IN (SELECT subject FROM "social.grain.graph.follow" WHERE did = $1) 21 22 AND ${hideLabelsFilter("t.uri")} 22 23 AND (SELECT count(*) FROM "social.grain.gallery.item" gi WHERE gi.gallery = t.uri) > 0 23 24 AND ${blockMuteFilter("t.did", "$1")}`, 24 - { orderBy: "t.created_at", params: [actor] }, 25 + { orderBy: "t.sort_at", params: [actor] }, 25 26 ); 26 27 27 28 return ctx.ok({ uris: rows.map((r) => r.uri), cursor });
+6 -5
server/feeds/foryou.ts
··· 2 2 import { hydrateGalleries } from "../hydrate/galleries.ts"; 3 3 import { hideLabelsFilter } from "../labels/_hidden.ts"; 4 4 import { blockMuteFilter } from "../filters/blockMute.ts"; 5 + import { galleryFeedTable } from "./_galleryTable.ts"; 5 6 6 7 // ─── Scoring parameters (spacecowboy17's optimized A/B values) ─────── 7 8 const HALF_LIFE_HOURS = 6; ··· 98 99 99 100 const [candidateRows, likeCounts] = await Promise.all([ 100 101 ctx.db.query( 101 - `SELECT f.did AS coliker, f.subject AS gallery_uri, t.created_at AS gallery_created_at 102 + `SELECT f.did AS coliker, f.subject AS gallery_uri, t.sort_at AS gallery_created_at 102 103 FROM "social.grain.favorite" f 103 - JOIN "social.grain.gallery" t ON t.uri = f.subject 104 + JOIN ${galleryFeedTable} ON t.uri = f.subject 104 105 LEFT JOIN _repos r ON t.did = r.did 105 106 WHERE f.did IN (${colikerPlaceholders}) 106 107 AND f.subject NOT IN (${seedPlaceholders}) ··· 233 234 234 235 const rows = (await ctx.db.query( 235 236 `SELECT t.uri, COUNT(f.did) as fav_count 236 - FROM "social.grain.gallery" t 237 + FROM ${galleryFeedTable} 237 238 LEFT JOIN "social.grain.favorite" f ON f.subject = t.uri 238 239 LEFT JOIN _repos r ON t.did = r.did 239 240 WHERE (r.status IS NULL OR r.status != 'takendown') 240 241 AND t.did != $1 241 - AND t.created_at > $2 242 + AND t.sort_at > $2 242 243 AND ${hideLabelsFilter("t.uri")} 243 244 AND (SELECT count(*) FROM "social.grain.gallery.item" gi WHERE gi.gallery = t.uri) > 0 244 245 AND ${blockMuteFilter("t.did", "$1")} 245 246 GROUP BY t.uri 246 - ORDER BY fav_count DESC, t.created_at DESC 247 + ORDER BY fav_count DESC, t.sort_at DESC 247 248 LIMIT $3 OFFSET $4`, 248 249 [actor, thirtyDaysAgo, limit, offset], 249 250 )) as { uri: string; fav_count: number }[];
+3 -2
server/feeds/hashtag.ts
··· 2 2 import { hydrateGalleries } from "../hydrate/galleries.ts"; 3 3 import { hideLabelsFilter } from "../labels/_hidden.ts"; 4 4 import { blockMuteFilter } from "../filters/blockMute.ts"; 5 + import { galleryFeedTable } from "./_galleryTable.ts"; 5 6 6 7 export default defineFeed({ 7 8 collection: "social.grain.gallery", ··· 19 20 const bmParams = viewer ? [viewer] : []; 20 21 21 22 const { rows, cursor } = await ctx.paginate<{ uri: string }>( 22 - `SELECT t.uri, t.cid, t.created_at FROM "social.grain.gallery" t 23 + `SELECT t.uri, t.cid, t.sort_at FROM ${galleryFeedTable} 23 24 LEFT JOIN _repos r ON t.did = r.did 24 25 WHERE (r.status IS NULL OR r.status != 'takendown') 25 26 AND ( ··· 29 30 AND ${hideLabelsFilter("t.uri")} 30 31 AND (SELECT count(*) FROM "social.grain.gallery.item" gi WHERE gi.gallery = t.uri) > 0 31 32 ${bmFilter}`, 32 - { orderBy: "t.created_at", params: [pattern, ...bmParams] }, 33 + { orderBy: "t.sort_at", params: [pattern, ...bmParams] }, 33 34 ); 34 35 35 36 return ctx.ok({ uris: rows.map((r) => r.uri), cursor });
+9 -8
server/feeds/location.ts
··· 27 27 import { hideLabelsFilter } from "../labels/_hidden.ts"; 28 28 import { blockMuteFilter } from "../filters/blockMute.ts"; 29 29 import { expandCountryAliases } from "../helpers/country.ts"; 30 + import { galleryFeedTable } from "./_galleryTable.ts"; 30 31 31 32 export default defineFeed({ 32 33 collection: "social.grain.gallery", ··· 134 135 if (viewer) params.push(viewer); 135 136 136 137 const { rows, cursor } = await ctx.paginate<{ uri: string }>( 137 - `SELECT t.uri, t.cid, t.created_at FROM "social.grain.gallery" t 138 + `SELECT t.uri, t.cid, t.sort_at FROM ${galleryFeedTable} 138 139 LEFT JOIN _repos r ON t.did = r.did 139 140 WHERE (r.status IS NULL OR r.status != 'takendown') 140 141 AND (${interpClauses.join(" OR ")}) 141 142 AND ${hideLabelsFilter("t.uri")} 142 143 AND (SELECT count(*) FROM "social.grain.gallery.item" gi WHERE gi.gallery = t.uri) > 0 143 144 ${bmFilter}`, 144 - { orderBy: "t.created_at", params }, 145 + { orderBy: "t.sort_at", params }, 145 146 ); 146 147 147 148 // If the name-based query found matches, or we have no H3 to fall back to, ··· 168 169 const bmFilter = viewer ? `AND ${blockMuteFilter("t.did", "$1")}` : ""; 169 170 const bmParams = viewer ? [viewer] : []; 170 171 const allRows = (await ctx.db.query( 171 - `SELECT t.uri, t.created_at, json_extract(t.location, '$.value') AS location 172 - FROM "social.grain.gallery" t 172 + `SELECT t.uri, t.sort_at, json_extract(t.location, '$.value') AS location 173 + FROM ${galleryFeedTable} 173 174 LEFT JOIN _repos r ON t.did = r.did 174 175 WHERE (r.status IS NULL OR r.status != 'takendown') 175 176 AND t.location IS NOT NULL 176 177 AND ${hideLabelsFilter("t.uri")} 177 178 AND (SELECT count(*) FROM "social.grain.gallery.item" gi WHERE gi.gallery = t.uri) > 0 178 179 ${bmFilter} 179 - ORDER BY t.created_at DESC`, 180 + ORDER BY t.sort_at DESC`, 180 181 bmParams, 181 - )) as { uri: string; created_at: string; location: string }[]; 182 + )) as { uri: string; sort_at: string; location: string }[]; 182 183 183 184 const filtered = allRows.filter((r) => { 184 185 if (!r.location) return false; ··· 211 212 const bmFilterVenue = viewer ? `AND ${blockMuteFilter("t.did", "$2")}` : ""; 212 213 const bmParamsVenue = viewer ? [viewer] : []; 213 214 const { rows, cursor } = await ctx.paginate<{ uri: string }>( 214 - `SELECT t.uri, t.cid, t.created_at FROM "social.grain.gallery" t 215 + `SELECT t.uri, t.cid, t.sort_at FROM ${galleryFeedTable} 215 216 LEFT JOIN _repos r ON t.did = r.did 216 217 WHERE (r.status IS NULL OR r.status != 'takendown') 217 218 AND json_extract(t.location, '$.value') = $1 218 219 AND ${hideLabelsFilter("t.uri")} 219 220 AND (SELECT count(*) FROM "social.grain.gallery.item" gi WHERE gi.gallery = t.uri) > 0 220 221 ${bmFilterVenue}`, 221 - { orderBy: "t.created_at", params: [location, ...bmParamsVenue] }, 222 + { orderBy: "t.sort_at", params: [location, ...bmParamsVenue] }, 222 223 ); 223 224 224 225 return ctx.ok({ uris: rows.map((r) => r.uri), cursor });
+3 -2
server/feeds/recent.ts
··· 2 2 import { hydrateGalleries } from "../hydrate/galleries.ts"; 3 3 import { hideLabelsFilter } from "../labels/_hidden.ts"; 4 4 import { blockMuteFilter } from "../filters/blockMute.ts"; 5 + import { galleryFeedTable } from "./_galleryTable.ts"; 5 6 6 7 export default defineFeed({ 7 8 collection: "social.grain.gallery", ··· 15 16 const bmParams = viewer ? [viewer] : []; 16 17 17 18 const { rows, cursor } = await ctx.paginate<{ uri: string }>( 18 - `SELECT t.uri, t.cid, t.created_at FROM "social.grain.gallery" t 19 + `SELECT t.uri, t.cid, t.sort_at FROM ${galleryFeedTable} 19 20 LEFT JOIN _repos r ON t.did = r.did 20 21 WHERE (r.status IS NULL OR r.status != 'takendown') 21 22 AND ${hideLabelsFilter("t.uri")} 22 23 AND (SELECT count(*) FROM "social.grain.gallery.item" gi WHERE gi.gallery = t.uri) > 0 23 24 ${bmFilter}`, 24 - { orderBy: "t.created_at", ...(bmParams.length ? { params: bmParams } : {}) }, 25 + { orderBy: "t.sort_at", ...(bmParams.length ? { params: bmParams } : {}) }, 25 26 ); 26 27 27 28 return ctx.ok({ uris: rows.map((r) => r.uri), cursor });