···11{
22 "permissions": {
33 "allow": [
44- "Bash(cat:*)",
55- "Bash(git add:*)",
66- "Bash(git commit:*)",
77- "Bash(git push)",
88- "Bash(npm run lint:*)",
99- "Bash(npm run check:*)",
1010- "Bash(npm run lint:fix:*)",
1111- "Bash(npm run db:generate:*)",
1212- "Bash(npm run:*)",
1313- "Bash(npm audit:*)",
1414- "Bash(npm install)",
1515- "Bash(curl:*)",
1616- "Bash(npx tsc:*)",
1717- "Bash(findstr:*)",
1818- "Bash(yarn install)",
1919- "Bash(node --version:*)",
2020- "Bash(npm:*)",
2121- "Bash(docker-compose restart:*)",
2222- "Bash(echo \"# Bluesky Branding Removal Checklist\n\n## 1. App Icons & Images\n- assets/app-icons/*.png - App icons (replace with Aurora Prism branding)\n- assets/favicon.png - Browser favicon\n- assets/icon-android-*.png - Android icons\n- assets/default-avatar.png - Default avatar image\n\n## 2. App Metadata\n- app.json - App name, slug, description\n- package.json - App name and description\n\n## 3. Text References (276 occurrences)\n- Onboarding screens (src/screens/Onboarding/)\n- Signup screens (src/screens/Signup/)\n- Settings/About screens\n- Terms of Service / Privacy Policy references\n- Help text and tooltips\n- Error messages mentioning Bluesky\n\n## 4. URLs\n- bsky.app references (feed URLs, profile URLs)\n- bsky.social references\n- Links to Bluesky support/help\n\n## 5. Service Names\n- Bluesky Moderation Service references\n- Default feed generator names\n\nTotal: 276 text references found\")",
2323- "Bash(psql \"$DATABASE_URL\" -c \"SELECT \n (SELECT COUNT(*) FROM users) as users,\n (SELECT COUNT(*) FROM posts) as posts,\n (SELECT COUNT(*) FROM likes) as likes,\n (SELECT COUNT(*) FROM reposts) as reposts,\n (SELECT COUNT(*) FROM follows) as follows,\n (SELECT COUNT(*) FROM blocks) as blocks;\")",
2424- "Bash(dig +short TXT _atproto.spacelawshitpost.me)",
2525- "Bash(python3 -m json.tool)",
2626- "Bash(npx --yes @expo/cli export:web)",
2727- "Bash(magick favicon.png -define icon:auto-resize=16,32,48 favicon.ico)",
2828- "Bash(convert favicon.png -define icon:auto-resize=16,32,48 favicon.ico)",
2929- "Bash(npx --yes @fiahfy/ico-cli -i assets/favicon.png -o assets/favicon.ico)"
44+ "Bash(node -e \"const {db} = require(''''./dist/server/db''''); db.execute(''''SELECT COUNT(*) as feed_count FROM feed_generators;'''').then(r => console.log(r.rows[0])).catch(e => console.error(e))\")",
55+ "Bash(node -e \"const data = require(''''fs'''').readFileSync(0, ''''utf-8''''); const json = JSON.parse(data); console.log(''''Service DID:'''', json.view.did); console.log(''''Service endpoint (from DID doc):'''', json.view.did)\")",
66+ "Bash(node -e \"const data = require(''''fs'''').readFileSync(0, ''''utf-8''''); const json = JSON.parse(data); const feedgenService = json.service?.find(s => s.id === ''''#bsky_fg''''); console.log(''''Feed Generator Endpoint:'''', feedgenService?.serviceEndpoint || ''''Not found'''')\")",
77+ "Bash(node -e \"const data = require(''''fs'''').readFileSync(0, ''''utf-8''''); console.log(''''Response:'''', data.substring(0, 1000))\")"
308 ],
319 "deny": [],
3210 "ask": []
+82
server/scripts/fix-future-dated-posts.ts
···11+/**
22+ * One-time script to fix future-dated posts in the database
33+ *
44+ * This fixes posts that were created with timestamps in the future,
55+ * which causes them to appear at the top of timelines incorrectly.
66+ *
77+ * Run with: npx tsx server/scripts/fix-future-dated-posts.ts
88+ */
99+1010+import { db } from '../db';
1111+import { posts, feedItems } from '../../shared/schema';
1212+import { sql } from 'drizzle-orm';
1313+1414+async function fixFutureDatedPosts() {
1515+ console.log('[FIX] Starting fix for future-dated posts...');
1616+1717+ try {
1818+ // Find all posts with createdAt in the future (with 5 min grace period)
1919+ const gracePeriod = sql`INTERVAL '5 minutes'`;
2020+ const futurePosts = await db.execute(sql`
2121+ SELECT uri, "authorDid", "createdAt", "indexedAt", text
2222+ FROM ${posts}
2323+ WHERE "createdAt" > NOW() + ${gracePeriod}
2424+ ORDER BY "createdAt" DESC
2525+ `);
2626+2727+ console.log(`[FIX] Found ${futurePosts.rows.length} future-dated posts`);
2828+2929+ if (futurePosts.rows.length === 0) {
3030+ console.log('[FIX] No future-dated posts found. Nothing to fix.');
3131+ return;
3232+ }
3333+3434+ // Log the posts we're about to fix
3535+ for (const post of futurePosts.rows) {
3636+ console.log(`[FIX] Post: ${post.uri}`);
3737+ console.log(` Author: ${post.authorDid}`);
3838+ console.log(` Original createdAt: ${post.createdAt}`);
3939+ console.log(` Text: ${(post.text as string).substring(0, 100)}...`);
4040+ }
4141+4242+ // Update posts: set createdAt to indexedAt (when we first saw it)
4343+ const updateResult = await db.execute(sql`
4444+ UPDATE ${posts}
4545+ SET "createdAt" = "indexedAt"
4646+ WHERE "createdAt" > NOW() + ${gracePeriod}
4747+ `);
4848+4949+ console.log(`[FIX] Updated ${updateResult.rowCount} posts`);
5050+5151+ // Also update feedItems that reference these posts
5252+ const feedItemsResult = await db.execute(sql`
5353+ UPDATE ${feedItems}
5454+ SET "sortAt" = "createdAt"
5555+ WHERE "originatorDid" IN (
5656+ SELECT "authorDid"
5757+ FROM ${posts}
5858+ WHERE "createdAt" > NOW() + ${gracePeriod}
5959+ )
6060+ `);
6161+6262+ console.log(`[FIX] Updated ${feedItemsResult.rowCount} feed items`);
6363+6464+ console.log('[FIX] ✓ Future-dated posts have been fixed!');
6565+ console.log('[FIX] These posts will now appear in the correct chronological order.');
6666+6767+ } catch (error) {
6868+ console.error('[FIX] Error fixing future-dated posts:', error);
6969+ throw error;
7070+ }
7171+}
7272+7373+// Run the fix
7474+fixFutureDatedPosts()
7575+ .then(() => {
7676+ console.log('[FIX] Script completed successfully');
7777+ process.exit(0);
7878+ })
7979+ .catch((error) => {
8080+ console.error('[FIX] Script failed:', error);
8181+ process.exit(1);
8282+ });
+18-1
server/services/event-processor.ts
···22472247 }
2248224822492249 // Guard against invalid or missing dates in upstream records
22502250+ // Also prevents future-dated posts from appearing at the top of timelines
22502251 private safeDate(value: string | Date | undefined): Date {
22512252 if (!value) return new Date();
22522253 const d = value instanceof Date ? value : new Date(value);
22532253- return isNaN(d.getTime()) ? new Date() : d;
22542254+22552255+ // If date is invalid, return current time
22562256+ if (isNaN(d.getTime())) return new Date();
22572257+22582258+ const now = new Date();
22592259+22602260+ // If date is in the future (with 5 minute grace period for clock skew),
22612261+ // clamp it to current time to prevent timeline manipulation
22622262+ const gracePeriod = 5 * 60 * 1000; // 5 minutes in milliseconds
22632263+ if (d.getTime() > now.getTime() + gracePeriod) {
22642264+ console.warn(
22652265+ `[EVENT_PROCESSOR] Future-dated record detected (${d.toISOString()}), clamping to current time`
22662266+ );
22672267+ return now;
22682268+ }
22692269+22702270+ return d;
22542271 }
2255227222562273 private async processDelete(uri: string, collection: string) {
+21-5
server/services/feed-generator-client.ts
···8383 }
84848585 const data = await response.json();
8686- const skeleton = feedSkeletonResponseSchema.parse(data);
8686+8787+ // Try to parse the response - if it fails, log the actual response for debugging
8888+ try {
8989+ const skeleton = feedSkeletonResponseSchema.parse(data);
87908888- console.log(
8989- `[FeedGenClient] Received ${skeleton.feed.length} posts from feed generator`
9090- );
9191+ console.log(
9292+ `[FeedGenClient] Received ${skeleton.feed.length} posts from feed generator`
9393+ );
91949292- return skeleton;
9595+ return skeleton;
9696+ } catch (parseError) {
9797+ console.error(
9898+ `[FeedGenClient] Invalid response format from feed generator ${serviceEndpoint}:`
9999+ );
100100+ console.error('[FeedGenClient] Response data:', JSON.stringify(data).substring(0, 500));
101101+ console.error('[FeedGenClient] Parse error:', parseError);
102102+103103+ // Throw a more user-friendly error
104104+ throw new Error(
105105+ `Feed generator at ${serviceEndpoint} returned an invalid response format. ` +
106106+ `This may indicate the feed generator is out of date or misconfigured.`
107107+ );
108108+ }
93109 } catch (error) {
94110 console.error('[FeedGenClient] Error fetching skeleton:', error);
95111 throw error;
+22
server/services/xrpc-api.ts
···16501650 return [];
16511651 }
1652165216531653+ // Check which users exist in database
16541654+ const existingUsers = await storage.getUsers(uniqueDids);
16551655+ const existingDids = new Set(existingUsers.map(u => u.did));
16561656+ const missingDids = uniqueDids.filter(did => !existingDids.has(did));
16571657+16581658+ // Fetch missing users from their PDSes
16591659+ if (missingDids.length > 0) {
16601660+ console.log(`[XRPC] Fetching ${missingDids.length} missing user(s) from their PDSes`);
16611661+16621662+ await Promise.all(
16631663+ missingDids.map(async (did) => {
16641664+ try {
16651665+ const { pdsDataFetcher } = await import('./pds-data-fetcher');
16661666+ await pdsDataFetcher.fetchUser(did);
16671667+ console.log(`[XRPC] Successfully fetched user ${did} from their PDS`);
16681668+ } catch (error) {
16691669+ console.error(`[XRPC] Failed to fetch user ${did} from PDS:`, error);
16701670+ }
16711671+ })
16721672+ );
16731673+ }
16741674+16531675 const [
16541676 users,
16551677 followersCounts,