You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
api(personal): pin manual posts/upvotes for 14 d then keep elevated
User-action rows (manual Post button + upvote) now lead the
personal page by their action timestamp for 14 d, then fall back
to a +12 score boost so they stay "quite high" but a strong recent
parsed doc can surpass them.
Schema
- documents.created_via_post BOOLEAN DEFAULT FALSE
Distinguishes manual Post-button rows from background syncs and
pipeline imports — both already share the documents table.
Backend
- BulkSaveRequest gains optional `via: String`. The /search compose
dialog sends `via: "post"`; sync calls + MCP omit it.
- INSERT stamps `created_via_post = ($12::bool)`. ON CONFLICT
preserves a prior TRUE via `OR EXCLUDED.created_via_post`.
- Personal-page ORDER BY (both VIP fast path and legacy path):
Tier 1: hard pin within 14 d by GREATEST(fav.created_at,
CASE WHEN created_via_post THEN created_at END).
Tier 2: +12 score boost for any user-action row (any age).
Older posts stay elevated; a high-scoring parsed doc
still wins on its own merit.
Tier 3: snapshot score, date, url (existing tiebreakers).
Bonus
- favorite_documents.sql was created out-of-band on prod and never
in the boot migration list. A fresh local / DR-restored install
would have booted without the table and every personal-page hit
500'd. Wired in now.
Local verification: F (1 d upvote) → A (3 d post) → D (parsed
score 40) → B (30 d post, 5+12=17) → C (90 d post) → E (parsed
score 15) — matches the spec exactly.
// Whichever timestamp is more recent wins, so an upvote made
841
+
// after a post still rises above it. Pipeline-imported rows
842
+
// leave both slots NULL and fall back to feed-score order.
793
843
letmut sql = String::from(
794
-
"SELECT ps.url,\n ps.title,\n ps.summary,\n COALESCE(to_char(ps.date, 'YYYY-MM-DD'), '') AS date,\n ps.tags,\n ps.extra_tags,\n ps.source,\n ps.source_url,\n ps.indexed,\n ps.linked_urls,\n ps.link_hosts,\n COALESCE(to_char(ps.date AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"'), '') AS created_at,\n ps.sharers,\n ps.sharer_count\n FROM personal_snapshot ps\n JOIN users u ON u.id = ps.user_id\n LEFT JOIN favorite_documents fav\n ON fav.user_id = ps.user_id AND fav.url = ps.url\n WHERE u.username = $1",
844
+
"SELECT ps.url,\n ps.title,\n ps.summary,\n COALESCE(to_char(ps.date, 'YYYY-MM-DD'), '') AS date,\n ps.tags,\n ps.extra_tags,\n ps.source,\n ps.source_url,\n ps.indexed,\n ps.linked_urls,\n ps.link_hosts,\n COALESCE(to_char(ps.date AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"'), '') AS created_at,\n ps.sharers,\n ps.sharer_count\n FROM personal_snapshot ps\n JOIN users u ON u.id = ps.user_id\n LEFT JOIN favorite_documents fav\n ON fav.user_id = ps.user_id AND fav.url = ps.url\n LEFT JOIN documents d_post\n ON d_post.user_id = ps.user_id AND d_post.url = ps.url\n AND d_post.deleted = FALSE\nWHERE u.username = $1",
// * Pipeline-imported docs leave both inputs NULL (the
884
+
// `created_via_post` guard masks them on the post side, no
885
+
// favorite_documents row on the upvote side) so they sort
886
+
// by raw `ps.score` without the bump.
824
887
sql.push_str(
825
-
"\n ORDER BY fav.created_atDESC NULLS LAST,\n ps.score DESC,\n ps.date DESC NULLS LAST,\n ps.url",
888
+
"\n ORDER BY CASE\n WHEN GREATEST(\nfav.created_at,\n CASE WHEN d_post.created_via_post THEN d_post.created_at END\n ) > now() - interval '14 days'\n THEN GREATEST(\n fav.created_at,\n CASE WHEN d_post.created_via_post THEN d_post.created_at END\n )\n END DESC NULLS LAST,\n(ps.score + CASE\n WHEN GREATEST(\n fav.created_at,\n CASE WHEN d_post.created_via_post THEN d_post.created_at END\n ) IS NOT NULL\n THEN 12.0\n ELSE 0\n END) DESC,\n ps.date DESC NULLS LAST,\n ps.url",
0 commit comments