ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto

fix: save-results now updates existing uploads instead of skipping

Changed from hasRecentUpload time check to getUpload ID check.

Extension flow:
1. extension-import creates upload with no matches
2. Frontend searches and finds matches
3. save-results now UPDATES upload with matches (was skipping)

File upload flow still works:
- Upload doesn't exist -> creates it
- Upload exists -> updates it with new matches

Removes race condition where save within 5 seconds would be skipped.

byarielm.fyi 92cac386 0afa0ffa

verified
Changed files
+118 -32
docs
packages
functions
+16 -16
docs/git-history.json
··· 1 1 [ 2 2 { 3 - "hash": "6ced3f0b015af1c9126559a393996576402cfd03", 4 - "short_hash": "6ced3f0", 3 + "hash": "a90f3a813fd9527e8e59bb1da242e7b67555a8eb", 4 + "short_hash": "a90f3a8", 5 5 "author": "Ariel M. Lighty", 6 - "date": "2025-12-26T14:12:46-05:00", 7 - "message": "fix extension flow: create user_source_follows, auto-search, time display\n\nBackend (extension-import.ts):\n- Now creates user_source_follows entries linking upload to source accounts\n- Without these, get-upload-details returned empty (queries FROM user_source_follows)\n- Uses bulkCreate return value (Map<username, id>) to create links\n\nFrontend (App.tsx):\n- handleLoadUpload now detects if upload has no matches yet\n- Sets isSearching: true for new uploads\n- Automatically triggers searchAllUsers for new uploads\n- Saves results after search completes\n- Changed platform from hardcoded \"tiktok\" to \"twitter\"\n\nFrontend (HistoryTab.tsx):\n- Fixed time display: removed \"Uploaded\" prefix\n- Now shows \"about 5 hours ago\" instead of \"Uploaded in about 5 hours\"\n- formatRelativeTime with addSuffix already provides complete sentence\n\nResolves:\n- Empty results on page load\n- No automatic searching\n- History navigation not working (will work after search)\n- Grammatically incorrect time display", 8 - "files_changed": 5 6 + "date": "2025-12-26T16:11:04-05:00", 7 + "message": "fix: pass onProgressUpdate and onComplete callbacks to searchAllUsers", 8 + "files_changed": 3 9 9 }, 10 10 { 11 11 "hash": "6ced3f0b015af1c9126559a393996576402cfd03", ··· 32 32 "files_changed": 4 33 33 }, 34 34 { 35 - "hash": "95636330f387598f55017eda668fb9f91ccde509", 36 - "short_hash": "9563633", 35 + "hash": "9ca734749fbaa014828f8437afc5e515610afd31", 36 + "short_hash": "9ca7347", 37 37 "author": "Ariel M. Lighty", 38 - "date": "2025-12-26T13:35:52-05:00", 39 - "message": "fix extension api-client: unwrap ApiResponse.data structure\n\nBackend endpoints use successResponse() which wraps data in:\n { success: true, data: {...} }\n\nExtension was expecting flat response structure, causing:\n- uploadToATlast to return undefined (missing importId, redirectUrl)\n- checkSession to return wrapped object instead of user data\n- Invalid URL error: \"http://127.0.0.1:8888undefined\"\n\nFixed both uploadToATlast and checkSession to access apiResponse.data", 40 - "files_changed": 2 38 + "date": "2025-12-26T13:37:24-05:00", 39 + "message": "update documentation: extension ready for testing after API response fix", 40 + "files_changed": 4 41 41 }, 42 42 { 43 43 "hash": "95636330f387598f55017eda668fb9f91ccde509", ··· 88 88 "files_changed": 3 89 89 }, 90 90 { 91 - "hash": "ba29fd68872913ba0a587aa7f29f97b3d373a732", 92 - "short_hash": "ba29fd6", 91 + "hash": "32cdee3aeac7ef986df47e0fff786b5f7471e55b", 92 + "short_hash": "32cdee3", 93 93 "author": "Ariel M. Lighty", 94 94 "date": "2025-12-25T13:22:32-05:00", 95 95 "message": "configure Netlify dev for monorepo with --filter flag\n\nFixed Netlify CLI monorepo detection issue by using --filter flag:\n- Updated root package.json scripts to use 'npx netlify-cli dev --filter @atlast/web'\n- Updated netlify.toml [dev] section to use npm with --prefix for framework command\n- Added monorepo development instructions to CLAUDE.md\n- Documented Windows Git Bash compatibility issue with netlify command\n\nSolution: Use 'npx netlify-cli dev --filter @atlast/web' to bypass monorepo\nproject selection prompt and specify which workspace package to run.\n\nDev server now runs successfully at http://localhost:8888 with all backend\nfunctions loaded.", 96 - "files_changed": 5 96 + "files_changed": 4 97 97 }, 98 98 { 99 - "hash": "32cdee3aeac7ef986df47e0fff786b5f7471e55b", 100 - "short_hash": "32cdee3", 99 + "hash": "ba29fd68872913ba0a587aa7f29f97b3d373a732", 100 + "short_hash": "ba29fd6", 101 101 "author": "Ariel M. Lighty", 102 102 "date": "2025-12-25T13:22:32-05:00", 103 103 "message": "configure Netlify dev for monorepo with --filter flag\n\nFixed Netlify CLI monorepo detection issue by using --filter flag:\n- Updated root package.json scripts to use 'npx netlify-cli dev --filter @atlast/web'\n- Updated netlify.toml [dev] section to use npm with --prefix for framework command\n- Added monorepo development instructions to CLAUDE.md\n- Documented Windows Git Bash compatibility issue with netlify command\n\nSolution: Use 'npx netlify-cli dev --filter @atlast/web' to bypass monorepo\nproject selection prompt and specify which workspace package to run.\n\nDev server now runs successfully at http://localhost:8888 with all backend\nfunctions loaded.", 104 - "files_changed": 4 104 + "files_changed": 5 105 105 }, 106 106 { 107 107 "hash": "c3e7afad396d130791d801a85cbfc9643bcd6309",
+88
docs/graph-data.json
··· 3486 3486 "created_at": "2025-12-26T20:20:22.507291300-05:00", 3487 3487 "updated_at": "2025-12-26T20:20:22.507291300-05:00", 3488 3488 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3489 + }, 3490 + { 3491 + "id": 318, 3492 + "change_id": "371f788d-46df-4651-b338-f9310f8ae810", 3493 + "node_type": "goal", 3494 + "title": "Fix results not saving to database and timestamp timezone issue", 3495 + "description": null, 3496 + "status": "pending", 3497 + "created_at": "2025-12-26T20:37:03.493239600-05:00", 3498 + "updated_at": "2025-12-26T20:37:03.493239600-05:00", 3499 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3500 + }, 3501 + { 3502 + "id": 319, 3503 + "change_id": "28681ed9-6d12-476e-a60d-291ee2034952", 3504 + "node_type": "observation", 3505 + "title": "save-results has hasRecentUpload check that skips saving if upload created within 5 seconds - extension-import creates upload then save-results is called immediately, gets skipped!", 3506 + "description": null, 3507 + "status": "pending", 3508 + "created_at": "2025-12-26T20:37:34.735156200-05:00", 3509 + "updated_at": "2025-12-26T20:37:34.735156200-05:00", 3510 + "metadata_json": "{\"branch\":\"master\",\"confidence\":100}" 3511 + }, 3512 + { 3513 + "id": 320, 3514 + "change_id": "04f6a182-c5a1-4844-b186-24605a8e74a9", 3515 + "node_type": "action", 3516 + "title": "Fix save-results to skip duplicate check for extension uploads and handle timestamps correctly", 3517 + "description": null, 3518 + "status": "pending", 3519 + "created_at": "2025-12-26T20:38:45.703038700-05:00", 3520 + "updated_at": "2025-12-26T20:38:45.703038700-05:00", 3521 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3522 + }, 3523 + { 3524 + "id": 321, 3525 + "change_id": "ac843fbc-1953-4b61-8ef3-4c88c98572f5", 3526 + "node_type": "outcome", 3527 + "title": "Fixed save-results to check if upload exists by ID instead of recent time check - extension flow now saves matches", 3528 + "description": null, 3529 + "status": "pending", 3530 + "created_at": "2025-12-26T20:39:45.657720100-05:00", 3531 + "updated_at": "2025-12-26T20:39:45.657720100-05:00", 3532 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3489 3533 } 3490 3534 ], 3491 3535 "edges": [ ··· 6876 6920 "weight": 1.0, 6877 6921 "rationale": "Goal achieved", 6878 6922 "created_at": "2025-12-26T20:20:26.885283800-05:00" 6923 + }, 6924 + { 6925 + "id": 309, 6926 + "from_node_id": 317, 6927 + "to_node_id": 318, 6928 + "from_change_id": "3a24a4a2-b4d0-4629-a29b-b33994d50e75", 6929 + "to_change_id": "371f788d-46df-4651-b338-f9310f8ae810", 6930 + "edge_type": "leads_to", 6931 + "weight": 1.0, 6932 + "rationale": "New issues found", 6933 + "created_at": "2025-12-26T20:37:06.303637800-05:00" 6934 + }, 6935 + { 6936 + "id": 310, 6937 + "from_node_id": 318, 6938 + "to_node_id": 319, 6939 + "from_change_id": "371f788d-46df-4651-b338-f9310f8ae810", 6940 + "to_change_id": "28681ed9-6d12-476e-a60d-291ee2034952", 6941 + "edge_type": "leads_to", 6942 + "weight": 1.0, 6943 + "rationale": "Root cause found", 6944 + "created_at": "2025-12-26T20:37:37.527168300-05:00" 6945 + }, 6946 + { 6947 + "id": 311, 6948 + "from_node_id": 319, 6949 + "to_node_id": 320, 6950 + "from_change_id": "28681ed9-6d12-476e-a60d-291ee2034952", 6951 + "to_change_id": "04f6a182-c5a1-4844-b186-24605a8e74a9", 6952 + "edge_type": "leads_to", 6953 + "weight": 1.0, 6954 + "rationale": "Action to fix", 6955 + "created_at": "2025-12-26T20:38:48.486046-05:00" 6956 + }, 6957 + { 6958 + "id": 312, 6959 + "from_node_id": 320, 6960 + "to_node_id": 321, 6961 + "from_change_id": "04f6a182-c5a1-4844-b186-24605a8e74a9", 6962 + "to_change_id": "ac843fbc-1953-4b61-8ef3-4c88c98572f5", 6963 + "edge_type": "leads_to", 6964 + "weight": 1.0, 6965 + "rationale": "Implementation complete", 6966 + "created_at": "2025-12-26T20:39:48.757903800-05:00" 6879 6967 } 6880 6968 ] 6881 6969 }
+14 -16
packages/functions/src/save-results.ts
··· 66 66 const matchRepo = new MatchRepository(); 67 67 let matchedCount = 0; 68 68 69 - const hasRecent = await uploadRepo.hasRecentUpload(context.did); 70 - if (hasRecent) { 71 - console.log( 72 - `User ${context.did} already saved within 5 seconds, skipping duplicate`, 69 + // Check if this specific upload already exists 70 + const existingUpload = await uploadRepo.getUpload(uploadId, context.did); 71 + 72 + if (!existingUpload) { 73 + // Upload doesn't exist - create it (file upload flow) 74 + await uploadRepo.createUpload( 75 + uploadId, 76 + context.did, 77 + sourcePlatform, 78 + results.length, 79 + 0, 73 80 ); 74 - return successResponse({ 75 - success: true, 76 - message: "Recently saved", 77 - }); 81 + } else { 82 + // Upload exists (extension flow) - just update it with matches 83 + console.log(`[save-results] Updating existing upload ${uploadId} with matches`); 78 84 } 79 - 80 - await uploadRepo.createUpload( 81 - uploadId, 82 - context.did, 83 - sourcePlatform, 84 - results.length, 85 - 0, 86 - ); 87 85 88 86 const allUsernames = results.map((r) => r.sourceUser.username); 89 87 const sourceAccountIdMap = await sourceAccountRepo.bulkCreate(