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

Compare changes

Choose any two refs to compare.

+119 -3
CLAUDE.md
··· 78 79 **Root `goal` nodes are the ONLY valid orphans.** 80 81 ### Quick Commands 82 83 ```bash ··· 181 182 ### Audit Checklist (Before Every Sync) 183 184 - 1. Does every **outcome** link back to what caused it? 185 - 2. Does every **action** link to why you did it? 186 - 3. Any **dangling outcomes** without parents? 187 188 ### Session Start Checklist 189
··· 78 79 **Root `goal` nodes are the ONLY valid orphans.** 80 81 + ### Node Lifecycle Management 82 + 83 + **Every node has a lifecycle. Update status in REAL-TIME:** 84 + 85 + ```bash 86 + # 1. Create node (defaults to 'pending') 87 + deciduous add action "Implementing feature X" -c 85 88 + 89 + # 2. IMMEDIATELY link to parent (before doing anything else) 90 + deciduous link <parent_id> <new_node_id> -r "Reason for connection" 91 + 92 + # 3. Mark as in_progress BEFORE starting work 93 + deciduous status <node_id> in_progress 94 + 95 + # 4. Do the work... 96 + 97 + # 5. Mark as completed IMMEDIATELY after work finishes 98 + deciduous status <node_id> completed 99 + ``` 100 + 101 + **Status Transitions:** 102 + - `pending` → Default state when created 103 + - `in_progress` → Mark BEFORE starting work (only ONE at a time) 104 + - `completed` → Mark IMMEDIATELY when done (proven by git commit, test pass, etc.) 105 + 106 + **CRITICAL RULES:** 107 + - ✅ Link nodes IMMEDIATELY after creation (same command sequence) 108 + - ✅ Update status to `completed` as soon as work is done 109 + - ✅ Only ONE node should be `in_progress` at a time 110 + - ✅ Verify link exists before moving on (check `deciduous edges`) 111 + - ❌ NEVER leave completed work marked as `pending` 112 + - ❌ NEVER create orphan nodes (except root goals) 113 + - ❌ NEVER batch status updates - update immediately 114 + 115 + **Verification Workflow:** 116 + ```bash 117 + # After creating and linking a node, verify: 118 + deciduous edges | grep <new_node_id> # Should show incoming edge 119 + deciduous nodes | grep <new_node_id> # Check status is correct 120 + ``` 121 + 122 + **Common Mistakes That Break the Graph:** 123 + 124 + 1. **Creating nodes without linking** → Orphans 125 + ```bash 126 + # WRONG 127 + deciduous add action "Fix bug" -c 85 128 + # (forget to link, move on to next task) 129 + 130 + # RIGHT 131 + deciduous add action "Fix bug" -c 85 132 + deciduous link 42 43 -r "Action to resolve goal #42" 133 + ``` 134 + 135 + 2. **Leaving nodes as "pending" after work completes** → Stale status 136 + ```bash 137 + # WRONG 138 + git commit -m "fix: bug fixed" 139 + # (forget to update node status) 140 + 141 + # RIGHT 142 + git commit -m "fix: bug fixed" 143 + deciduous status 43 completed 144 + ``` 145 + 146 + 3. **Batch-creating multiple nodes before linking** → Connection gaps 147 + ```bash 148 + # WRONG 149 + deciduous add action "Task 1" -c 85 150 + deciduous add action "Task 2" -c 85 151 + deciduous add action "Task 3" -c 85 152 + # (now have to remember all IDs to link) 153 + 154 + # RIGHT 155 + deciduous add action "Task 1" -c 85 156 + deciduous link 42 43 -r "First task" 157 + deciduous add action "Task 2" -c 85 158 + deciduous link 42 44 -r "Second task" 159 + ``` 160 + 161 + 4. **Not regenerating parent list during orphan checks** → False positives 162 + ```bash 163 + # WRONG 164 + # (generate parent list once) 165 + deciduous link X Y -r "fix orphan" 166 + # (check orphans with stale parent list) 167 + 168 + # RIGHT 169 + deciduous link X Y -r "fix orphan" 170 + # Regenerate parent list before checking again 171 + deciduous edges | tail -n+3 | awk '{print $3}' | sort -u > /tmp/has_parent.txt 172 + ``` 173 + 174 ### Quick Commands 175 176 ```bash ··· 274 275 ### Audit Checklist (Before Every Sync) 276 277 + Run these checks before `deciduous sync`: 278 + 279 + 1. **Connection integrity**: Does every non-goal node have a parent? 280 + ```bash 281 + deciduous edges | tail -n+3 | awk '{print $3}' | sort -u > /tmp/has_parent.txt 282 + deciduous nodes | tail -n+3 | awk '{print $1}' > /tmp/all_nodes.txt 283 + while read id; do grep -q "^$id$" /tmp/has_parent.txt || echo "CHECK: $id"; done < /tmp/all_nodes.txt 284 + # Only root goals should appear 285 + ``` 286 + 287 + 2. **Status accuracy**: Are completed nodes marked `completed`? 288 + ```bash 289 + deciduous nodes | grep pending 290 + # Review: is this work actually still pending, or is it done? 291 + ``` 292 + 293 + 3. **Active work**: Is there exactly ONE `in_progress` node? 294 + ```bash 295 + deciduous nodes | grep in_progress 296 + # Should see 0-1 nodes, not multiple 297 + ``` 298 + 299 + 4. **Logical flow**: Does every outcome link back to what caused it? 300 + - `outcome` → `action` or `goal` 301 + - `action` → `goal` or `decision` 302 + - `observation` → related `goal` or `action` 303 304 ### Session Start Checklist 305
+68 -34
CONTRIBUTING.md
··· 27 ```bash 28 git clone <repo-url> 29 cd atlast 30 - npm install 31 ``` 32 33 2. Create .env.local ··· 40 41 3. Start Development 42 ```bash 43 - npm run dev:mock 44 ``` 45 46 4. Open Your Browser ··· 61 ### Prerequisites 62 63 - Node.js 18+ 64 - PostgreSQL (or Neon account) 65 - OpenSSL (for key generation) 66 ··· 68 ```bash 69 git clone <repo-url> 70 cd atlast 71 - npm install 72 - npm install -g netlify-cli 73 ``` 74 75 2. Database Setup ··· 144 145 7. Initialize Database 146 ```bash 147 - npm run init-db 148 ``` 149 150 8. Start Development Server 151 ```bash 152 - npm run dev:full 153 ``` 154 155 9. Test OAuth ··· 163 164 ## Project Structure 165 166 ``` 167 atlast/ 168 - ├── src/ 169 - │ ├── assets/ # Logo 170 - │ ├── components/ # UI components (React) 171 - │ ├── constants/ # 172 - │ ├── pages/ # Page components 173 - │ ├── hooks/ # Custom hooks 174 - │ ├── lib/ 175 - │ │ ├── apiClient/ # API client (real + mock) 176 - │ │ ├── fileExtractor.ts # Chooses parser, handles file upload and data extraction 177 - │ │ ├── parserLogic.ts # Parses file for usernames 178 - │ │ ├── platformDefinitions.ts # File types and username locations 179 - │ │ └── config.ts # Environment config 180 - │ └── types/ # TypeScript types 181 - ├── netlify/ 182 - │ └── functions/ # Backend API 183 - └── public/ # 184 ``` 185 186 ### UI Color System ··· 227 228 ## Task Workflows 229 230 - ### Adding a New Social Platform 231 232 - 1. Create `src/lib/platforms/yourplatform.ts` 233 - 2. Implement parser following `tiktok.ts` or `instagram.ts` 234 - 3. Register in `src/lib/platforms/registry.ts` 235 - 4. Update `src/constants/platforms.ts` 236 - 5. Test with real data file 237 238 ### Adding a New API Endpoint 239 240 - 1. Create `netlify/functions/your-endpoint.ts` 241 - 2. Add authentication check (copy from existing) 242 - 3. Update `src/lib/apiClient/realApiClient.ts` 243 - 4. Update `src/lib/apiClient/mockApiClient.ts` 244 5. Use in components via `apiClient.yourMethod()` 245 246 ### Styling Changes 247 248 - Use Tailwind utility classes ··· 257 258 ### Before Submitting 259 260 - - [ ] Test in mock mode: `npm run dev:mock` 261 - - [ ] Test in full mode (if backend changes): `npm run dev:full` 262 - [ ] Check both light and dark themes 263 - [ ] Test mobile responsiveness 264 - [ ] No console errors 265 - [ ] Code follows existing patterns 266 267 ### Pull Request Process 268
··· 27 ```bash 28 git clone <repo-url> 29 cd atlast 30 + pnpm install 31 ``` 32 33 2. Create .env.local ··· 40 41 3. Start Development 42 ```bash 43 + pnpm run dev:mock 44 ``` 45 46 4. Open Your Browser ··· 61 ### Prerequisites 62 63 - Node.js 18+ 64 + - pnpm (install with `npm install -g pnpm`) 65 - PostgreSQL (or Neon account) 66 - OpenSSL (for key generation) 67 ··· 69 ```bash 70 git clone <repo-url> 71 cd atlast 72 + pnpm install 73 ``` 74 75 2. Database Setup ··· 144 145 7. Initialize Database 146 ```bash 147 + pnpm run init-db 148 ``` 149 150 8. Start Development Server 151 ```bash 152 + npx netlify-cli dev --filter @atlast/web 153 + # Or use the alias: 154 + pnpm run dev 155 ``` 156 157 9. Test OAuth ··· 165 166 ## Project Structure 167 168 + **Monorepo using pnpm workspaces:** 169 + 170 ``` 171 atlast/ 172 + ├── packages/ 173 + │ ├── web/ # Frontend React app 174 + │ │ ├── src/ 175 + │ │ │ ├── assets/ # Logo 176 + │ │ │ ├── components/ # UI components (React) 177 + │ │ │ ├── pages/ # Page components 178 + │ │ │ ├── hooks/ # Custom hooks 179 + │ │ │ ├── lib/ 180 + │ │ │ │ ├── api/ # API client (real + mock) 181 + │ │ │ │ ├── parsers/ # File parsing logic 182 + │ │ │ │ └── config.ts # Environment config 183 + │ │ │ └── types/ # TypeScript types 184 + │ │ └── package.json 185 + │ ├── functions/ # Netlify serverless functions 186 + │ │ ├── src/ 187 + │ │ │ ├── core/ # Middleware, types, config 188 + │ │ │ ├── infrastructure/ # Database, OAuth, cache 189 + │ │ │ ├── services/ # Business logic 190 + │ │ │ ├── repositories/ # Data access layer 191 + │ │ │ └── utils/ # Shared utilities 192 + │ │ └── package.json 193 + │ ├── extension/ # Browser extension 194 + │ │ ├── src/ 195 + │ │ │ ├── content/ # Content scripts, scrapers 196 + │ │ │ ├── popup/ # Extension popup UI 197 + │ │ │ ├── background/ # Service worker 198 + │ │ │ └── lib/ # Extension utilities 199 + │ │ └── package.json 200 + │ └── shared/ # Shared types (future) 201 + ├── pnpm-workspace.yaml 202 + └── netlify.toml 203 ``` 204 205 ### UI Color System ··· 246 247 ## Task Workflows 248 249 + ### Adding a New Social Platform Parser 250 251 + 1. Add parsing rules to `packages/web/src/lib/parsers/platformDefinitions.ts` 252 + 2. Follow existing patterns (TikTok, Instagram) 253 + 3. Test with real data export file 254 + 4. Update platform selection UI if needed 255 256 ### Adding a New API Endpoint 257 258 + 1. Create `packages/functions/src/your-endpoint.ts` 259 + 2. Add authentication check using `withAuthErrorHandling()` middleware 260 + 3. Update `packages/web/src/lib/api/adapters/RealApiAdapter.ts` 261 + 4. Update `packages/web/src/lib/api/adapters/MockApiAdapter.ts` 262 5. Use in components via `apiClient.yourMethod()` 263 264 + ### Working with the Extension 265 + 266 + ```bash 267 + cd packages/extension 268 + pnpm install 269 + pnpm run build # Build for Chrome 270 + pnpm run build:prod # Build for production 271 + 272 + # Load in Chrome: 273 + # 1. Go to chrome://extensions 274 + # 2. Enable Developer mode 275 + # 3. Click "Load unpacked" 276 + # 4. Select packages/extension/dist/chrome/ 277 + ``` 278 + 279 ### Styling Changes 280 281 - Use Tailwind utility classes ··· 290 291 ### Before Submitting 292 293 + - [ ] Test in mock mode: `pnpm run dev:mock` 294 + - [ ] Test in full mode (if backend changes): `npx netlify-cli dev --filter @atlast/web` 295 - [ ] Check both light and dark themes 296 - [ ] Test mobile responsiveness 297 - [ ] No console errors 298 - [ ] Code follows existing patterns 299 + - [ ] Run `pnpm run build` successfully 300 301 ### Pull Request Process 302
-156
EXTENSION_STATUS.md
··· 1 - # Extension Implementation Status 2 - 3 - ## Current State: DEBUGGING 🔧 4 - 5 - ### What's Complete ✅ 6 - 7 - 1. **Environment Configuration** 8 - - Dev/prod builds with correct API URLs 9 - - Build: `npm run build` (dev) or `npm run build:prod` 10 - - Dev: `http://127.0.0.1:8888` 11 - - Prod: `https://atlast.byarielm.fyi` 12 - 13 - 2. **Server Health Check** 14 - - Extension checks if dev server is running (dev mode only) 15 - - Shows "Server offline" state with instructions 16 - - "Check Again" button to retry 17 - 18 - 3. **Authentication Flow** 19 - - Extension checks `/session` endpoint on init 20 - - Shows "Not logged in" state if no session 21 - - "Open ATlast" button to log in 22 - - "Check Again" to retry after login 23 - - **User must be logged in to ATlast BEFORE using extension** 24 - 25 - 4. **Upload Flow** (matches file upload) 26 - - Scan Twitter Following page 27 - - POST to `/extension-import` (requires auth) 28 - - Backend: 29 - - Gets DID from session 30 - - Creates `user_upload` entry 31 - - Saves to `source_accounts` table 32 - - Returns `uploadId` 33 - - Opens `/results?uploadId={id}` 34 - - Frontend searches and displays (same as file upload) 35 - 36 - 5. **CORS Permissions** 37 - - Extension has host_permissions for: 38 - - `http://127.0.0.1:8888/*` 39 - - `http://localhost:8888/*` 40 - - `https://atlast.byarielm.fyi/*` 41 - 42 - 6. **Cleanup Complete** 43 - - ❌ Removed `extension_imports` table 44 - - ❌ Removed `get-extension-import` function 45 - - ❌ Removed `ExtensionImport.tsx` page 46 - - ❌ Removed `/import/:id` route 47 - - ❌ Removed `utils/import-store.ts` 48 - 49 - ### What Needs Testing 🧪 50 - 51 - 1. **Full Flow Test** 52 - ```bash 53 - # 1. Start dev server 54 - npx netlify-cli dev --filter @atlast/web 55 - 56 - # 2. Build extension 57 - cd packages/extension 58 - npm run build 59 - 60 - # 3. Load extension in Chrome 61 - chrome://extensions/ → Load unpacked → packages/extension/dist/chrome/ 62 - 63 - # 4. Log in to ATlast 64 - Open http://127.0.0.1:8888 → Log in 65 - 66 - # 5. Go to Twitter 67 - https://twitter.com/justadev_atlast/following 68 - 69 - # 6. Open extension popup 70 - - Should show "Ready to scan Twitter/X" 71 - - Click "Start Scan" 72 - - Wait for completion 73 - - Click "Open in ATlast" 74 - - Should open /results?uploadId={id} 75 - - Results should load and search automatically 76 - ``` 77 - 78 - 2. **Error Cases to Test** 79 - - Not logged in → should show login prompt 80 - - Server offline → should show offline state 81 - - Empty results → should show appropriate message 82 - - Network errors → should handle gracefully 83 - 84 - ### Current Issues 🐛 85 - 86 - **Fixed:** 87 - - ✅ NaN database error - Fixed missing `matchedUsers` parameter in `extension-import.ts` (node #287) 88 - - ✅ Database initialized successfully (node #288) 89 - - ✅ API response unwrapping - Fixed api-client to access ApiResponse.data field (nodes #290-295) 90 - - Backend wraps responses in `{ success: true, data: {...} }` 91 - - Extension was expecting flat response, causing undefined errors 92 - - Fixed both `uploadToATlast` and `checkSession` functions 93 - 94 - **Active Testing:** 95 - - Ready for end-to-end testing with fixed upload flow 96 - - Extension should now properly redirect to results page 97 - 98 - ### Next Steps 📋 99 - 100 - 1. ✅ Build extension: `cd packages/extension && npm run build` 101 - 2. ✅ Reload extension in Chrome 102 - 3. ✅ Test login flow 103 - 4. ✅ Test scan and upload 104 - 5. ✅ Verify results page works 105 - 6. Fix any bugs found 106 - 7. Test production build: `npm run build:prod` 107 - 108 - ### Architecture Notes 📝 109 - 110 - **Removed temporary import storage approach:** 111 - - Previously tried in-memory storage (doesn't work in serverless) 112 - - Then tried database storage with temp table (overkill) 113 - 114 - **Current approach:** 115 - - User logs in to ATlast FIRST 116 - - Extension requires authentication 117 - - Upload creates permanent records immediately 118 - - No temporary storage needed 119 - - Matches file upload behavior exactly 120 - 121 - **Why this is better:** 122 - - Simpler architecture 123 - - No temporary storage to expire 124 - - Proper user association from the start 125 - - Reuses existing upload/search infrastructure 126 - - Same flow as file uploads (consistency) 127 - 128 - ### Files Modified in Latest Refactor 129 - 130 - **Deleted:** 131 - - `packages/functions/src/get-extension-import.ts` 132 - - `packages/functions/src/utils/import-store.ts` 133 - - `packages/web/src/pages/ExtensionImport.tsx` 134 - 135 - **Modified:** 136 - - `packages/functions/src/extension-import.ts` - Now requires auth, creates upload 137 - - `packages/functions/src/infrastructure/database/DatabaseService.ts` - Removed extension_imports table 138 - - `packages/functions/src/core/types/database.types.ts` - Removed ExtensionImportRow 139 - - `packages/web/src/Router.tsx` - Removed /import/:id route 140 - - `packages/extension/src/popup/popup.ts` - Added session check, login state 141 - - `packages/extension/src/popup/popup.html` - Added not-logged-in state 142 - - `packages/extension/src/lib/api-client.ts` - Added checkSession(), credentials: 'include' 143 - 144 - ### Decision Graph Summary 145 - 146 - **Total nodes:** 295 147 - **Key decisions tracked:** 148 - - Environment configuration approach (#261-269) 149 - - Port 8888 conflict resolution (#270-274) 150 - - CORS permissions fix (#275-277) 151 - - Storage approach: in-memory → database → proper auth flow (#278-284) 152 - - Refactor and build (#285-286) 153 - - Bug fixes: NaN parameter error (#287), database initialization (#288) 154 - - API response unwrapping fix (#290-295) 155 - 156 - **Live graph:** https://notactuallytreyanastasio.github.io/deciduous/
···
+19 -11
PLAN.md
··· 1 # ATlast Twitter/X Support Plan 2 3 - ## Current Status (2025-12-26) 4 5 - **Phase 1 Status:** ✅ Ready for Testing - Core implementation complete, all bugs fixed 6 7 - **Recent Fixes:** 8 - ✅ Environment configuration (dev/prod builds with correct API URLs) 9 - ✅ Server health check and offline state handling 10 - ✅ Authentication flow (session check before upload) ··· 13 - ✅ Fixed NaN database error (missing matchedUsers parameter) 14 - ✅ Database initialized for dev environment 15 - ✅ Fixed API response unwrapping (uploadToATlast and checkSession) 16 17 - **Active Work:** 18 - - End-to-end testing of complete flow 19 - - Verification of results page integration 20 - - See [EXTENSION_STATUS.md](./EXTENSION_STATUS.md) for detailed status 21 22 - **Decision Graph:** 295 nodes tracked - [View live graph](https://notactuallytreyanastasio.github.io/deciduous/) 23 24 --- 25 ··· 556 - [x] **0.10** Test build and dev commands 557 - [x] **0.11** Commit monorepo migration 558 559 - ### Phase 1: Chrome Extension MVP 🔧 IN PROGRESS (Debugging) 560 - [x] **1.1** Create packages/extension/ structure 561 - [x] **1.2** Write manifest.json (Manifest V3) 562 - [x] **1.3** Implement base-scraper.ts abstract class ··· 568 - [x] **1.9** Create Netlify function: extension-import.ts 569 - [x] **1.10** ~~Create ATlast import page: /import/[id]~~ (Not needed - uses /results?uploadId) 570 - [x] **1.11** Add extension build script 571 - - [ ] **1.12** Test end-to-end flow locally (Active debugging) 572 - - [ ] **1.13** Chrome Web Store submission 573 574 ### Phase 2: Firefox Support 575 - [ ] **2.1** Create manifest.firefox.json (MV2 if needed) ··· 630 | 2025-12-26 | Fixed: NaN database error, environment config, auth flow, CORS permissions | 631 | 2025-12-26 | Fixed: API response unwrapping - extension now correctly handles ApiResponse structure | 632 | 2025-12-26 | Phase 1 ready for testing - all bugs resolved, decision graph: 295 nodes tracked |
··· 1 # ATlast Twitter/X Support Plan 2 3 + ## Current Status (2025-12-27) 4 5 + **Phase 1 Status:** ✅ COMPLETE - Ready for production testing and Chrome Web Store submission 6 7 + **All Completed (Dec 2024 - Jan 2025):** 8 - ✅ Environment configuration (dev/prod builds with correct API URLs) 9 - ✅ Server health check and offline state handling 10 - ✅ Authentication flow (session check before upload) ··· 13 - ✅ Fixed NaN database error (missing matchedUsers parameter) 14 - ✅ Database initialized for dev environment 15 - ✅ Fixed API response unwrapping (uploadToATlast and checkSession) 16 + - ✅ Loading screen during extension upload search 17 + - ✅ Timezone fixes with TIMESTAMPTZ 18 + - ✅ Vite dev server optimization 19 + - ✅ Decision graph integrity fixes (18 orphan nodes resolved) 20 + - ✅ Documentation improvements (CLAUDE.md with lifecycle management) 21 22 + **Ready For:** 23 + - Production testing 24 + - Chrome Web Store submission 25 + - Firefox Add-ons development 26 27 + **Decision Graph:** 332 nodes, 333 edges - [View live graph](https://notactuallytreyanastasio.github.io/deciduous/) 28 29 --- 30 ··· 561 - [x] **0.10** Test build and dev commands 562 - [x] **0.11** Commit monorepo migration 563 564 + ### Phase 1: Chrome Extension MVP ✅ COMPLETE 565 - [x] **1.1** Create packages/extension/ structure 566 - [x] **1.2** Write manifest.json (Manifest V3) 567 - [x] **1.3** Implement base-scraper.ts abstract class ··· 573 - [x] **1.9** Create Netlify function: extension-import.ts 574 - [x] **1.10** ~~Create ATlast import page: /import/[id]~~ (Not needed - uses /results?uploadId) 575 - [x] **1.11** Add extension build script 576 + - [x] **1.12** Test end-to-end flow locally - All bugs resolved 577 + - [ ] **1.13** Chrome Web Store submission - Next step 578 579 ### Phase 2: Firefox Support 580 - [ ] **2.1** Create manifest.firefox.json (MV2 if needed) ··· 635 | 2025-12-26 | Fixed: NaN database error, environment config, auth flow, CORS permissions | 636 | 2025-12-26 | Fixed: API response unwrapping - extension now correctly handles ApiResponse structure | 637 | 2025-12-26 | Phase 1 ready for testing - all bugs resolved, decision graph: 295 nodes tracked | 638 + | 2025-12-27 | Phase 1 COMPLETE - all extension bugs fixed, ready for Chrome Web Store submission | 639 + | 2025-12-27 | Added: Loading screen, timezone fixes, Vite optimization, decision graph improvements | 640 + | 2025-12-27 | Decision graph: 332 nodes, 333 edges - orphan nodes resolved, documentation improved |
+78 -22
docs/git-history.json
··· 1 [ 2 { 3 "hash": "46626f4a18eaaaaf42368361130bb1ddc7bd9677", 4 "short_hash": "46626f4", 5 "author": "Ariel M. Lighty", ··· 14 "date": "2025-12-26T20:58:45-05:00", 15 "message": "fix: pass final search results to onComplete callback\n\nFixes issue where results were displayed but not saved to database until\npage refresh. Root cause: onComplete callback accessed stale searchResults\nfrom closure instead of updated state.\n\nChanges:\n- useSearch.searchAllUsers: onComplete now receives SearchResult[] param\n- useSearch: uses setSearchResults updater to get current state\n- App.tsx: updated all 3 searchAllUsers calls to use finalResults\n- Removed setTimeout workarounds\n\nResult: Extension and file upload flows now save immediately after search.", 16 "files_changed": 4 17 - }, 18 - { 19 - "hash": "212660a996d6b0f1db59f9532d2b3968c7113f10", 20 - "short_hash": "212660a", 21 - "author": "Ariel M. Lighty", 22 - "date": "2025-12-26T20:58:45-05:00", 23 - "message": "fix: pass final search results to onComplete callback\n\nFixes issue where results were displayed but not saved to database until\npage refresh. Root cause: onComplete callback accessed stale searchResults\nfrom closure instead of updated state.\n\nChanges:\n- useSearch.searchAllUsers: onComplete now receives SearchResult[] param\n- useSearch: uses setSearchResults updater to get current state\n- App.tsx: updated all 3 searchAllUsers calls to use finalResults\n- Removed setTimeout workarounds\n\nResult: Extension and file upload flows now save immediately after search.", 24 - "files_changed": 4 25 - }, 26 - { 27 - "hash": "6ced3f0b015af1c9126559a393996576402cfd03", 28 - "short_hash": "6ced3f0", 29 - "author": "Ariel M. Lighty", 30 - "date": "2025-12-26T14:12:46-05:00", 31 - "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", 32 - "files_changed": 5 33 }, 34 { 35 "hash": "6ced3f0b015af1c9126559a393996576402cfd03", ··· 104 "files_changed": 3 105 }, 106 { 107 - "hash": "32cdee3aeac7ef986df47e0fff786b5f7471e55b", 108 - "short_hash": "32cdee3", 109 "author": "Ariel M. Lighty", 110 "date": "2025-12-25T13:22:32-05:00", 111 "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.", 112 - "files_changed": 4 113 }, 114 { 115 - "hash": "ba29fd68872913ba0a587aa7f29f97b3d373a732", 116 - "short_hash": "ba29fd6", 117 "author": "Ariel M. Lighty", 118 "date": "2025-12-25T13:22:32-05:00", 119 "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.", 120 - "files_changed": 5 121 }, 122 { 123 "hash": "c3e7afad396d130791d801a85cbfc9643bcd6309",
··· 1 [ 2 { 3 + "hash": "15b67054a684ebb2a21761a1774ba15f9b1c29e2", 4 + "short_hash": "15b6705", 5 + "author": "Ariel M. Lighty", 6 + "date": "2025-12-28T20:38:38-05:00", 7 + "message": "fix: add health check function for extension server detection\n\n- Created /health function endpoint with CORS support\n- Updated checkServerHealth to use function endpoint instead of root URL\n- Fixes Firefox extension server detection with proper CORS headers", 8 + "files_changed": 5 9 + }, 10 + { 11 + "hash": "603cf0a187850664336a12c9e5cbb49038906f53", 12 + "short_hash": "603cf0a", 13 + "author": "Ariel M. Lighty", 14 + "date": "2025-12-27T22:42:43-05:00", 15 + "message": "fix: CORS for extension credentialed requests\n\nUpdated CORS headers to support credentials from Chrome extensions:\n- Added getCorsHeaders() to detect chrome-extension:// origins\n- Changed from wildcard Access-Control-Allow-Origin to specific origin\n- Added Access-Control-Allow-Credentials: true for credentialed requests\n- Updated session endpoint to pass event for CORS header detection", 16 + "files_changed": 4 17 + }, 18 + { 19 + "hash": "bd3aabb75abb1875aef125610fcdccb14967a8e3", 20 + "short_hash": "bd3aabb", 21 + "author": "Ariel M. Lighty", 22 + "date": "2025-12-27T22:10:11-05:00", 23 + "message": "fix: extension dark mode and build mode messaging\n\n- Changed darkMode from 'class' to 'media' for automatic system preference detection\n- Made server offline message conditional on build mode (dev vs prod)\n- Hide dev server instructions in production builds", 24 + "files_changed": 5 25 + }, 26 + { 27 + "hash": "bd3aabb75abb1875aef125610fcdccb14967a8e3", 28 + "short_hash": "bd3aabb", 29 + "author": "Ariel M. Lighty", 30 + "date": "2025-12-27T22:10:11-05:00", 31 + "message": "fix: extension dark mode and build mode messaging\n\n- Changed darkMode from 'class' to 'media' for automatic system preference detection\n- Made server offline message conditional on build mode (dev vs prod)\n- Hide dev server instructions in production builds", 32 + "files_changed": 5 33 + }, 34 + { 35 + "hash": "d07180cd3a19328b82b35118e525b59d4e2e060b", 36 + "short_hash": "d07180c", 37 + "author": "Ariel M. Lighty", 38 + "date": "2025-12-27T18:38:39-05:00", 39 + "message": "feat: add Tailwind CSS to extension\n\nReplaced 299 lines of vanilla CSS with Tailwind for design consistency with web app. Production build minified to 13KB.", 40 + "files_changed": 9 41 + }, 42 + { 43 + "hash": "fe29bb3e5faa0151f63c14724f7509af669860de", 44 + "short_hash": "fe29bb3", 45 + "author": "Ariel M. Lighty", 46 + "date": "2025-12-27T16:02:10-05:00", 47 + "message": "docs: update all .md files to reflect current project status\n\nUpdated 4 markdown files with current state:\n\nEXTENSION_STATUS.md:\n- Changed status from DEBUGGING to COMPLETE\n- Updated decision graph count (295 → 332 nodes)\n- Added recently completed section (nodes 296-332)\n- Marked all extension bugs as resolved\n\nCONTRIBUTING.md:\n- Replaced npm with pnpm throughout\n- Added monorepo structure documentation\n- Updated development commands (netlify-cli dev --filter)\n- Added extension development workflow\n\nPLAN.md:\n- Updated status to Phase 1 COMPLETE\n- Added all recent fixes to completion list\n- Updated decision graph count to 332 nodes\n- Added changelog entries for latest work\n\npackages/extension/README.md:\n- Added prerequisites section (dev server + login required)\n- Updated build commands with dev/prod distinction\n- Added Step 0: Start ATlast Dev Server\n- Added common issues for auth and server states\n\nAll files now accurately reflect completion status and use pnpm.", 48 + "files_changed": 6 49 + }, 50 + { 51 + "hash": "fcf682bb8969aca108262348e7e17531077713be", 52 + "short_hash": "fcf682b", 53 + "author": "Ariel M. Lighty", 54 + "date": "2025-12-27T15:48:44-05:00", 55 + "message": "docs: improve decision graph workflow with lifecycle management\n\nUpdated CLAUDE.md with comprehensive node lifecycle management:\n- Added node status transitions (pending → in_progress → completed)\n- Correct orphan detection commands (awk instead of cut)\n- Common mistakes section with examples\n- Enhanced audit checklist with status verification\n- Verification workflow after node creation\n\nAlso updated extension popup with ATmosphere branding.\n\nDecision graph now at 331 nodes, 332 edges - all orphans resolved.", 56 + "files_changed": 4 57 + }, 58 + { 59 + "hash": "e04934ffb5e2d78791fcd23bc3afeb4d438a5546", 60 + "short_hash": "e04934f", 61 + "author": "Ariel M. Lighty", 62 + "date": "2025-12-26T21:57:05-05:00", 63 + "message": "perf: optimize Vite dev server startup\n\nAdded explicit optimizeDeps.include to pre-bundle common dependencies:\n- React ecosystem (react, react-dom, react-router-dom)\n- Icon libraries (@icons-pack/react-simple-icons, lucide-react)\n- Other deps (date-fns, jszip, zustand, @tanstack/react-virtual)\n\nAlso added server.fs.allow config for monorepo file serving.\n\nThis should speed up subsequent dev server starts by ensuring these\ndependencies are consistently pre-bundled.", 64 + "files_changed": 1 65 + }, 66 + { 67 + "hash": "aacbbaa27797781098dacdfd0194c93cd71d7bd2", 68 + "short_hash": "aacbbaa", 69 + "author": "Ariel M. Lighty", 70 + "date": "2025-12-26T21:46:06-05:00", 71 + "message": "fix: use TIMESTAMPTZ for all timestamp columns\n\nChanged all TIMESTAMP columns to TIMESTAMPTZ (timestamp with timezone) to\nproperly handle timezone-aware timestamps across all tables:\n- oauth_states (created_at, expires_at)\n- oauth_sessions (created_at, expires_at)\n- user_sessions (created_at, expires_at)\n- user_uploads (created_at, last_checked)\n- source_accounts (last_checked, match_found_at, created_at)\n- user_source_follows (created_at)\n- atproto_matches (found_at, last_verified, last_follow_check)\n- user_match_status (notified_at, viewed_at, followed_at, dismissed_at)\n- notification_queue (created_at, sent_at)\n\nThis fixes the 5-hour timezone offset issue where timestamps were stored\nwithout timezone info, causing display errors across different timezones.", 72 + "files_changed": 1 73 + }, 74 + { 75 "hash": "46626f4a18eaaaaf42368361130bb1ddc7bd9677", 76 "short_hash": "46626f4", 77 "author": "Ariel M. Lighty", ··· 86 "date": "2025-12-26T20:58:45-05:00", 87 "message": "fix: pass final search results to onComplete callback\n\nFixes issue where results were displayed but not saved to database until\npage refresh. Root cause: onComplete callback accessed stale searchResults\nfrom closure instead of updated state.\n\nChanges:\n- useSearch.searchAllUsers: onComplete now receives SearchResult[] param\n- useSearch: uses setSearchResults updater to get current state\n- App.tsx: updated all 3 searchAllUsers calls to use finalResults\n- Removed setTimeout workarounds\n\nResult: Extension and file upload flows now save immediately after search.", 88 "files_changed": 4 89 }, 90 { 91 "hash": "6ced3f0b015af1c9126559a393996576402cfd03", ··· 160 "files_changed": 3 161 }, 162 { 163 + "hash": "ba29fd68872913ba0a587aa7f29f97b3d373a732", 164 + "short_hash": "ba29fd6", 165 "author": "Ariel M. Lighty", 166 "date": "2025-12-25T13:22:32-05:00", 167 "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.", 168 + "files_changed": 5 169 }, 170 { 171 + "hash": "32cdee3aeac7ef986df47e0fff786b5f7471e55b", 172 + "short_hash": "32cdee3", 173 "author": "Ariel M. Lighty", 174 "date": "2025-12-25T13:22:32-05:00", 175 "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.", 176 + "files_changed": 4 177 }, 178 { 179 "hash": "c3e7afad396d130791d801a85cbfc9643bcd6309",
+1689 -72
docs/graph-data.json
··· 3185 "node_type": "goal", 3186 "title": "Fix extension upload errors - undefined response and invalid URL", 3187 "description": null, 3188 - "status": "pending", 3189 "created_at": "2025-12-26T13:31:45.695565800-05:00", 3190 - "updated_at": "2025-12-26T13:31:45.695565800-05:00", 3191 "metadata_json": "{\"branch\":\"master\",\"confidence\":85}" 3192 }, 3193 { ··· 3196 "node_type": "observation", 3197 "title": "Backend returns correct structure but response might be wrapped by successResponse helper", 3198 "description": null, 3199 - "status": "pending", 3200 "created_at": "2025-12-26T13:32:20.697112800-05:00", 3201 - "updated_at": "2025-12-26T13:32:20.697112800-05:00", 3202 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3203 }, 3204 { ··· 3207 "node_type": "observation", 3208 "title": "successResponse wraps data in {success: true, data: {...}} structure - extension expects flat response", 3209 "description": null, 3210 - "status": "pending", 3211 "created_at": "2025-12-26T13:32:50.409160400-05:00", 3212 - "updated_at": "2025-12-26T13:32:50.409160400-05:00", 3213 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3214 }, 3215 { ··· 3218 "node_type": "action", 3219 "title": "Fix api-client.ts to unwrap ApiResponse.data field", 3220 "description": null, 3221 - "status": "pending", 3222 "created_at": "2025-12-26T13:32:54.625124500-05:00", 3223 - "updated_at": "2025-12-26T13:32:54.625124500-05:00", 3224 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3225 }, 3226 { ··· 3229 "node_type": "outcome", 3230 "title": "Fixed API client to unwrap ApiResponse.data - both uploadToATlast and checkSession now correctly access nested data field", 3231 "description": null, 3232 - "status": "pending", 3233 "created_at": "2025-12-26T13:34:09.012837500-05:00", 3234 - "updated_at": "2025-12-26T13:34:09.012837500-05:00", 3235 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3236 }, 3237 { ··· 3240 "node_type": "outcome", 3241 "title": "Committed API response fix to git", 3242 "description": null, 3243 - "status": "pending", 3244 "created_at": "2025-12-26T13:36:02.733197600-05:00", 3245 - "updated_at": "2025-12-26T13:36:02.733197600-05:00", 3246 "metadata_json": "{\"branch\":\"master\",\"commit\":\"9563633\",\"confidence\":95}" 3247 }, 3248 { ··· 3251 "node_type": "observation", 3252 "title": "Extension upload flow fixed and ready for testing - API response unwrapping resolves undefined errors", 3253 "description": null, 3254 - "status": "pending", 3255 "created_at": "2025-12-26T13:37:35.844832-05:00", 3256 - "updated_at": "2025-12-26T13:37:35.844832-05:00", 3257 "metadata_json": "{\"branch\":\"master\",\"commit\":\"9ca7347\",\"confidence\":95}" 3258 }, 3259 { ··· 3262 "node_type": "goal", 3263 "title": "Fix backend repository method error and missing frontend route", 3264 "description": null, 3265 - "status": "pending", 3266 "created_at": "2025-12-26T13:43:03.332690700-05:00", 3267 - "updated_at": "2025-12-26T13:43:03.332690700-05:00", 3268 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3269 }, 3270 { ··· 3273 "node_type": "observation", 3274 "title": "Two issues: 1) SourceAccountRepository has getOrCreate/bulkCreate not upsertSourceAccount, 2) Router only has / route, no /results route", 3275 "description": null, 3276 - "status": "pending", 3277 "created_at": "2025-12-26T13:43:28.902663600-05:00", 3278 - "updated_at": "2025-12-26T13:43:28.902663600-05:00", 3279 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3280 }, 3281 { ··· 3284 "node_type": "action", 3285 "title": "Fix backend to use bulkCreate and frontend to handle uploadId param", 3286 "description": null, 3287 - "status": "pending", 3288 "created_at": "2025-12-26T13:44:28.406069900-05:00", 3289 - "updated_at": "2025-12-26T13:44:28.406069900-05:00", 3290 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3291 }, 3292 { ··· 3295 "node_type": "outcome", 3296 "title": "Fixed both issues: backend uses bulkCreate, redirects to /?uploadId, frontend loads results from uploadId param", 3297 "description": null, 3298 - "status": "pending", 3299 "created_at": "2025-12-26T13:45:58.309042200-05:00", 3300 - "updated_at": "2025-12-26T13:45:58.309042200-05:00", 3301 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3302 }, 3303 { ··· 3306 "node_type": "outcome", 3307 "title": "Committed fixes for bulkCreate and uploadId handling", 3308 "description": null, 3309 - "status": "pending", 3310 "created_at": "2025-12-26T13:47:48.770693200-05:00", 3311 - "updated_at": "2025-12-26T13:47:48.770693200-05:00", 3312 "metadata_json": "{\"branch\":\"master\",\"commit\":\"581ed00\",\"confidence\":95}" 3313 }, 3314 { ··· 3317 "node_type": "observation", 3318 "title": "Frontend error: loadUploadResults not defined - need to check function scope", 3319 "description": null, 3320 - "status": "pending", 3321 "created_at": "2025-12-26T13:50:59.977950500-05:00", 3322 - "updated_at": "2025-12-26T13:50:59.977950500-05:00", 3323 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3324 }, 3325 { ··· 3328 "node_type": "action", 3329 "title": "Fix useEffect to call handleLoadUpload instead of non-existent loadUploadResults", 3330 "description": null, 3331 - "status": "pending", 3332 "created_at": "2025-12-26T13:51:36.007564400-05:00", 3333 - "updated_at": "2025-12-26T13:51:36.007564400-05:00", 3334 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3335 }, 3336 { ··· 3339 "node_type": "outcome", 3340 "title": "Fixed function name - now calls handleLoadUpload correctly", 3341 "description": null, 3342 - "status": "pending", 3343 "created_at": "2025-12-26T13:51:52.256909300-05:00", 3344 - "updated_at": "2025-12-26T13:51:52.256909300-05:00", 3345 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3346 }, 3347 { ··· 3350 "node_type": "goal", 3351 "title": "Fix extension flow: auto-search after load, history navigation, time formatting", 3352 "description": null, 3353 - "status": "pending", 3354 "created_at": "2025-12-26T14:05:53.798547500-05:00", 3355 - "updated_at": "2025-12-26T14:05:53.798547500-05:00", 3356 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3357 }, 3358 { ··· 3361 "node_type": "observation", 3362 "title": "handleLoadUpload expects existing results but extension creates empty upload - need to load source accounts and trigger search", 3363 "description": null, 3364 - "status": "pending", 3365 "created_at": "2025-12-26T14:06:18.067673100-05:00", 3366 - "updated_at": "2025-12-26T14:06:18.067673100-05:00", 3367 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3368 }, 3369 { ··· 3372 "node_type": "observation", 3373 "title": "Extension-import creates upload and source_accounts but NOT user_source_follows - get-upload-details returns empty because it queries FROM user_source_follows", 3374 "description": null, 3375 - "status": "pending", 3376 "created_at": "2025-12-26T14:08:57.918421600-05:00", 3377 - "updated_at": "2025-12-26T14:08:57.918421600-05:00", 3378 "metadata_json": "{\"branch\":\"master\",\"confidence\":100}" 3379 }, 3380 { ··· 3383 "node_type": "action", 3384 "title": "Add user_source_follows creation to extension-import endpoint", 3385 "description": null, 3386 - "status": "pending", 3387 "created_at": "2025-12-26T14:09:03.035871-05:00", 3388 - "updated_at": "2025-12-26T14:09:03.035871-05:00", 3389 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3390 }, 3391 { ··· 3394 "node_type": "outcome", 3395 "title": "Fixed all extension flow issues: added user_source_follows creation, auto-search after load, time formatting", 3396 "description": null, 3397 - "status": "pending", 3398 "created_at": "2025-12-26T14:11:09.055850200-05:00", 3399 - "updated_at": "2025-12-26T14:11:09.055850200-05:00", 3400 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3401 }, 3402 { ··· 3405 "node_type": "outcome", 3406 "title": "Committed all extension flow fixes", 3407 "description": null, 3408 - "status": "pending", 3409 "created_at": "2025-12-26T14:16:08.387214900-05:00", 3410 - "updated_at": "2025-12-26T14:16:08.387214900-05:00", 3411 "metadata_json": "{\"branch\":\"master\",\"commit\":\"6ced3f0\",\"confidence\":95}" 3412 }, 3413 { ··· 3416 "node_type": "observation", 3417 "title": "searchAllUsers called with wrong parameters - missing onProgressUpdate callback", 3418 "description": null, 3419 - "status": "pending", 3420 "created_at": "2025-12-26T16:07:21.838974100-05:00", 3421 - "updated_at": "2025-12-26T16:07:21.838974100-05:00", 3422 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3423 }, 3424 { ··· 3427 "node_type": "action", 3428 "title": "Fix searchAllUsers call with correct parameters and callbacks", 3429 "description": null, 3430 - "status": "pending", 3431 "created_at": "2025-12-26T16:08:18.523845400-05:00", 3432 - "updated_at": "2025-12-26T16:08:18.523845400-05:00", 3433 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3434 }, 3435 { ··· 3438 "node_type": "outcome", 3439 "title": "Fixed searchAllUsers call - now passes onProgressUpdate and onComplete callbacks", 3440 "description": null, 3441 - "status": "pending", 3442 "created_at": "2025-12-26T16:08:24.248208800-05:00", 3443 - "updated_at": "2025-12-26T16:08:24.248208800-05:00", 3444 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3445 }, 3446 { ··· 3449 "node_type": "goal", 3450 "title": "Fix validation error and undefined localeCompare in extension flow", 3451 "description": null, 3452 - "status": "pending", 3453 "created_at": "2025-12-26T20:17:59.516959100-05:00", 3454 - "updated_at": "2025-12-26T20:17:59.516959100-05:00", 3455 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3456 }, 3457 { ··· 3460 "node_type": "observation", 3461 "title": "Two errors: 1) batch-search-actors gets null in usernames array, 2) Frontend localeCompare on undefined - likely wrong SearchResult structure", 3462 "description": null, 3463 - "status": "pending", 3464 "created_at": "2025-12-26T20:18:03.693879700-05:00", 3465 - "updated_at": "2025-12-26T20:18:03.693879700-05:00", 3466 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3467 }, 3468 { ··· 3471 "node_type": "action", 3472 "title": "Fix SearchResult structure - sourceUser should be object not string", 3473 "description": null, 3474 - "status": "pending", 3475 "created_at": "2025-12-26T20:19:47.621459800-05:00", 3476 - "updated_at": "2025-12-26T20:19:47.621459800-05:00", 3477 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3478 }, 3479 { ··· 3482 "node_type": "outcome", 3483 "title": "Fixed SearchResult structure - sourceUser is now correct SourceUser object instead of string", 3484 "description": null, 3485 - "status": "pending", 3486 "created_at": "2025-12-26T20:20:22.507291300-05:00", 3487 - "updated_at": "2025-12-26T20:20:22.507291300-05:00", 3488 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3489 }, 3490 { ··· 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 { ··· 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 { ··· 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 { ··· 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}" 3533 }, 3534 { ··· 3537 "node_type": "observation", 3538 "title": "onComplete callback in handleLoadUpload accesses stale searchResults from closure - state updated by searchAllUsers not visible to callback", 3539 "description": null, 3540 - "status": "pending", 3541 "created_at": "2025-12-26T20:51:55.431293100-05:00", 3542 - "updated_at": "2025-12-26T20:51:55.431293100-05:00", 3543 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3544 }, 3545 { ··· 3548 "node_type": "outcome", 3549 "title": "Fixed stale closure issue - onComplete now receives finalResults from useSearch state", 3550 "description": null, 3551 - "status": "pending", 3552 "created_at": "2025-12-26T20:55:36.922743800-05:00", 3553 - "updated_at": "2025-12-26T20:55:36.922743800-05:00", 3554 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3555 }, 3556 { ··· 3559 "node_type": "outcome", 3560 "title": "Committed stale closure fix - results now save immediately after search completes", 3561 "description": null, 3562 - "status": "pending", 3563 "created_at": "2025-12-26T20:58:48.266958800-05:00", 3564 - "updated_at": "2025-12-26T20:58:48.266958800-05:00", 3565 "metadata_json": "{\"branch\":\"master\",\"commit\":\"212660a\",\"confidence\":95}" 3566 }, 3567 { ··· 3570 "node_type": "outcome", 3571 "title": "Loading screen now shows during extension upload search", 3572 "description": null, 3573 - "status": "pending", 3574 "created_at": "2025-12-26T21:20:42.635515100-05:00", 3575 - "updated_at": "2025-12-26T21:20:42.635515100-05:00", 3576 "metadata_json": "{\"branch\":\"master\",\"commit\":\"46626f4\",\"confidence\":95}" 3577 } 3578 ], 3579 "edges": [ ··· 7052 "weight": 1.0, 7053 "rationale": "User reported results showing 'none' before search completes - needed to keep user on loading screen", 7054 "created_at": "2025-12-26T21:20:53.976836200-05:00" 7055 } 7056 ] 7057 }
··· 3185 "node_type": "goal", 3186 "title": "Fix extension upload errors - undefined response and invalid URL", 3187 "description": null, 3188 + "status": "completed", 3189 "created_at": "2025-12-26T13:31:45.695565800-05:00", 3190 + "updated_at": "2025-12-27T17:49:55.246500-05:00", 3191 "metadata_json": "{\"branch\":\"master\",\"confidence\":85}" 3192 }, 3193 { ··· 3196 "node_type": "observation", 3197 "title": "Backend returns correct structure but response might be wrapped by successResponse helper", 3198 "description": null, 3199 + "status": "completed", 3200 "created_at": "2025-12-26T13:32:20.697112800-05:00", 3201 + "updated_at": "2025-12-27T17:49:55.310376600-05:00", 3202 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3203 }, 3204 { ··· 3207 "node_type": "observation", 3208 "title": "successResponse wraps data in {success: true, data: {...}} structure - extension expects flat response", 3209 "description": null, 3210 + "status": "completed", 3211 "created_at": "2025-12-26T13:32:50.409160400-05:00", 3212 + "updated_at": "2025-12-27T17:49:55.384830800-05:00", 3213 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3214 }, 3215 { ··· 3218 "node_type": "action", 3219 "title": "Fix api-client.ts to unwrap ApiResponse.data field", 3220 "description": null, 3221 + "status": "completed", 3222 "created_at": "2025-12-26T13:32:54.625124500-05:00", 3223 + "updated_at": "2025-12-27T17:49:55.449186500-05:00", 3224 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3225 }, 3226 { ··· 3229 "node_type": "outcome", 3230 "title": "Fixed API client to unwrap ApiResponse.data - both uploadToATlast and checkSession now correctly access nested data field", 3231 "description": null, 3232 + "status": "completed", 3233 "created_at": "2025-12-26T13:34:09.012837500-05:00", 3234 + "updated_at": "2025-12-27T17:49:55.512809400-05:00", 3235 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3236 }, 3237 { ··· 3240 "node_type": "outcome", 3241 "title": "Committed API response fix to git", 3242 "description": null, 3243 + "status": "completed", 3244 "created_at": "2025-12-26T13:36:02.733197600-05:00", 3245 + "updated_at": "2025-12-27T17:49:55.576426900-05:00", 3246 "metadata_json": "{\"branch\":\"master\",\"commit\":\"9563633\",\"confidence\":95}" 3247 }, 3248 { ··· 3251 "node_type": "observation", 3252 "title": "Extension upload flow fixed and ready for testing - API response unwrapping resolves undefined errors", 3253 "description": null, 3254 + "status": "completed", 3255 "created_at": "2025-12-26T13:37:35.844832-05:00", 3256 + "updated_at": "2025-12-27T17:49:55.653339900-05:00", 3257 "metadata_json": "{\"branch\":\"master\",\"commit\":\"9ca7347\",\"confidence\":95}" 3258 }, 3259 { ··· 3262 "node_type": "goal", 3263 "title": "Fix backend repository method error and missing frontend route", 3264 "description": null, 3265 + "status": "completed", 3266 "created_at": "2025-12-26T13:43:03.332690700-05:00", 3267 + "updated_at": "2025-12-27T17:49:55.729232100-05:00", 3268 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3269 }, 3270 { ··· 3273 "node_type": "observation", 3274 "title": "Two issues: 1) SourceAccountRepository has getOrCreate/bulkCreate not upsertSourceAccount, 2) Router only has / route, no /results route", 3275 "description": null, 3276 + "status": "completed", 3277 "created_at": "2025-12-26T13:43:28.902663600-05:00", 3278 + "updated_at": "2025-12-27T17:49:55.791246300-05:00", 3279 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3280 }, 3281 { ··· 3284 "node_type": "action", 3285 "title": "Fix backend to use bulkCreate and frontend to handle uploadId param", 3286 "description": null, 3287 + "status": "completed", 3288 "created_at": "2025-12-26T13:44:28.406069900-05:00", 3289 + "updated_at": "2025-12-27T17:49:55.863335500-05:00", 3290 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3291 }, 3292 { ··· 3295 "node_type": "outcome", 3296 "title": "Fixed both issues: backend uses bulkCreate, redirects to /?uploadId, frontend loads results from uploadId param", 3297 "description": null, 3298 + "status": "completed", 3299 "created_at": "2025-12-26T13:45:58.309042200-05:00", 3300 + "updated_at": "2025-12-27T17:49:55.947393200-05:00", 3301 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3302 }, 3303 { ··· 3306 "node_type": "outcome", 3307 "title": "Committed fixes for bulkCreate and uploadId handling", 3308 "description": null, 3309 + "status": "completed", 3310 "created_at": "2025-12-26T13:47:48.770693200-05:00", 3311 + "updated_at": "2025-12-27T17:49:56.029469300-05:00", 3312 "metadata_json": "{\"branch\":\"master\",\"commit\":\"581ed00\",\"confidence\":95}" 3313 }, 3314 { ··· 3317 "node_type": "observation", 3318 "title": "Frontend error: loadUploadResults not defined - need to check function scope", 3319 "description": null, 3320 + "status": "completed", 3321 "created_at": "2025-12-26T13:50:59.977950500-05:00", 3322 + "updated_at": "2025-12-27T17:49:56.093781100-05:00", 3323 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3324 }, 3325 { ··· 3328 "node_type": "action", 3329 "title": "Fix useEffect to call handleLoadUpload instead of non-existent loadUploadResults", 3330 "description": null, 3331 + "status": "completed", 3332 "created_at": "2025-12-26T13:51:36.007564400-05:00", 3333 + "updated_at": "2025-12-27T17:49:56.169258900-05:00", 3334 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3335 }, 3336 { ··· 3339 "node_type": "outcome", 3340 "title": "Fixed function name - now calls handleLoadUpload correctly", 3341 "description": null, 3342 + "status": "completed", 3343 "created_at": "2025-12-26T13:51:52.256909300-05:00", 3344 + "updated_at": "2025-12-27T17:49:56.234188500-05:00", 3345 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3346 }, 3347 { ··· 3350 "node_type": "goal", 3351 "title": "Fix extension flow: auto-search after load, history navigation, time formatting", 3352 "description": null, 3353 + "status": "completed", 3354 "created_at": "2025-12-26T14:05:53.798547500-05:00", 3355 + "updated_at": "2025-12-27T17:49:56.309329800-05:00", 3356 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3357 }, 3358 { ··· 3361 "node_type": "observation", 3362 "title": "handleLoadUpload expects existing results but extension creates empty upload - need to load source accounts and trigger search", 3363 "description": null, 3364 + "status": "completed", 3365 "created_at": "2025-12-26T14:06:18.067673100-05:00", 3366 + "updated_at": "2025-12-27T17:49:56.384145700-05:00", 3367 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3368 }, 3369 { ··· 3372 "node_type": "observation", 3373 "title": "Extension-import creates upload and source_accounts but NOT user_source_follows - get-upload-details returns empty because it queries FROM user_source_follows", 3374 "description": null, 3375 + "status": "completed", 3376 "created_at": "2025-12-26T14:08:57.918421600-05:00", 3377 + "updated_at": "2025-12-27T17:49:56.459539400-05:00", 3378 "metadata_json": "{\"branch\":\"master\",\"confidence\":100}" 3379 }, 3380 { ··· 3383 "node_type": "action", 3384 "title": "Add user_source_follows creation to extension-import endpoint", 3385 "description": null, 3386 + "status": "completed", 3387 "created_at": "2025-12-26T14:09:03.035871-05:00", 3388 + "updated_at": "2025-12-27T17:49:56.523841100-05:00", 3389 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3390 }, 3391 { ··· 3394 "node_type": "outcome", 3395 "title": "Fixed all extension flow issues: added user_source_follows creation, auto-search after load, time formatting", 3396 "description": null, 3397 + "status": "completed", 3398 "created_at": "2025-12-26T14:11:09.055850200-05:00", 3399 + "updated_at": "2025-12-27T17:49:56.588486100-05:00", 3400 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3401 }, 3402 { ··· 3405 "node_type": "outcome", 3406 "title": "Committed all extension flow fixes", 3407 "description": null, 3408 + "status": "completed", 3409 "created_at": "2025-12-26T14:16:08.387214900-05:00", 3410 + "updated_at": "2025-12-27T17:49:56.670180800-05:00", 3411 "metadata_json": "{\"branch\":\"master\",\"commit\":\"6ced3f0\",\"confidence\":95}" 3412 }, 3413 { ··· 3416 "node_type": "observation", 3417 "title": "searchAllUsers called with wrong parameters - missing onProgressUpdate callback", 3418 "description": null, 3419 + "status": "completed", 3420 "created_at": "2025-12-26T16:07:21.838974100-05:00", 3421 + "updated_at": "2025-12-27T17:49:56.746464900-05:00", 3422 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3423 }, 3424 { ··· 3427 "node_type": "action", 3428 "title": "Fix searchAllUsers call with correct parameters and callbacks", 3429 "description": null, 3430 + "status": "completed", 3431 "created_at": "2025-12-26T16:08:18.523845400-05:00", 3432 + "updated_at": "2025-12-27T17:49:56.809583600-05:00", 3433 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3434 }, 3435 { ··· 3438 "node_type": "outcome", 3439 "title": "Fixed searchAllUsers call - now passes onProgressUpdate and onComplete callbacks", 3440 "description": null, 3441 + "status": "completed", 3442 "created_at": "2025-12-26T16:08:24.248208800-05:00", 3443 + "updated_at": "2025-12-27T17:49:56.884711900-05:00", 3444 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3445 }, 3446 { ··· 3449 "node_type": "goal", 3450 "title": "Fix validation error and undefined localeCompare in extension flow", 3451 "description": null, 3452 + "status": "completed", 3453 "created_at": "2025-12-26T20:17:59.516959100-05:00", 3454 + "updated_at": "2025-12-27T17:49:56.971434500-05:00", 3455 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3456 }, 3457 { ··· 3460 "node_type": "observation", 3461 "title": "Two errors: 1) batch-search-actors gets null in usernames array, 2) Frontend localeCompare on undefined - likely wrong SearchResult structure", 3462 "description": null, 3463 + "status": "completed", 3464 "created_at": "2025-12-26T20:18:03.693879700-05:00", 3465 + "updated_at": "2025-12-27T17:49:57.049131800-05:00", 3466 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3467 }, 3468 { ··· 3471 "node_type": "action", 3472 "title": "Fix SearchResult structure - sourceUser should be object not string", 3473 "description": null, 3474 + "status": "completed", 3475 "created_at": "2025-12-26T20:19:47.621459800-05:00", 3476 + "updated_at": "2025-12-27T17:49:57.127563700-05:00", 3477 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3478 }, 3479 { ··· 3482 "node_type": "outcome", 3483 "title": "Fixed SearchResult structure - sourceUser is now correct SourceUser object instead of string", 3484 "description": null, 3485 + "status": "completed", 3486 "created_at": "2025-12-26T20:20:22.507291300-05:00", 3487 + "updated_at": "2025-12-27T17:49:57.190209200-05:00", 3488 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3489 }, 3490 { ··· 3493 "node_type": "goal", 3494 "title": "Fix results not saving to database and timestamp timezone issue", 3495 "description": null, 3496 + "status": "completed", 3497 "created_at": "2025-12-26T20:37:03.493239600-05:00", 3498 + "updated_at": "2025-12-27T17:49:57.263765-05:00", 3499 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3500 }, 3501 { ··· 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": "completed", 3508 "created_at": "2025-12-26T20:37:34.735156200-05:00", 3509 + "updated_at": "2025-12-27T15:37:51.134056500-05:00", 3510 "metadata_json": "{\"branch\":\"master\",\"confidence\":100}" 3511 }, 3512 { ··· 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": "completed", 3519 "created_at": "2025-12-26T20:38:45.703038700-05:00", 3520 + "updated_at": "2025-12-27T15:37:51.269445900-05:00", 3521 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3522 }, 3523 { ··· 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": "completed", 3530 "created_at": "2025-12-26T20:39:45.657720100-05:00", 3531 + "updated_at": "2025-12-27T15:37:51.395550200-05:00", 3532 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3533 }, 3534 { ··· 3537 "node_type": "observation", 3538 "title": "onComplete callback in handleLoadUpload accesses stale searchResults from closure - state updated by searchAllUsers not visible to callback", 3539 "description": null, 3540 + "status": "completed", 3541 "created_at": "2025-12-26T20:51:55.431293100-05:00", 3542 + "updated_at": "2025-12-27T15:37:51.544390300-05:00", 3543 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3544 }, 3545 { ··· 3548 "node_type": "outcome", 3549 "title": "Fixed stale closure issue - onComplete now receives finalResults from useSearch state", 3550 "description": null, 3551 + "status": "completed", 3552 "created_at": "2025-12-26T20:55:36.922743800-05:00", 3553 + "updated_at": "2025-12-27T15:37:51.688947900-05:00", 3554 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3555 }, 3556 { ··· 3559 "node_type": "outcome", 3560 "title": "Committed stale closure fix - results now save immediately after search completes", 3561 "description": null, 3562 + "status": "completed", 3563 "created_at": "2025-12-26T20:58:48.266958800-05:00", 3564 + "updated_at": "2025-12-27T15:37:51.824656100-05:00", 3565 "metadata_json": "{\"branch\":\"master\",\"commit\":\"212660a\",\"confidence\":95}" 3566 }, 3567 { ··· 3570 "node_type": "outcome", 3571 "title": "Loading screen now shows during extension upload search", 3572 "description": null, 3573 + "status": "completed", 3574 "created_at": "2025-12-26T21:20:42.635515100-05:00", 3575 + "updated_at": "2025-12-27T15:37:51.996612500-05:00", 3576 "metadata_json": "{\"branch\":\"master\",\"commit\":\"46626f4\",\"confidence\":95}" 3577 + }, 3578 + { 3579 + "id": 326, 3580 + "change_id": "af76ea64-b0b1-4577-b521-4ec21cc555e1", 3581 + "node_type": "outcome", 3582 + "title": "Fixed timezone issue - all timestamp columns now use TIMESTAMPTZ", 3583 + "description": null, 3584 + "status": "completed", 3585 + "created_at": "2025-12-26T21:46:14.340967100-05:00", 3586 + "updated_at": "2025-12-27T15:37:52.151895800-05:00", 3587 + "metadata_json": "{\"branch\":\"master\",\"commit\":\"aacbbaa\",\"confidence\":95}" 3588 + }, 3589 + { 3590 + "id": 327, 3591 + "change_id": "ed9ceca3-e53e-430c-8f0f-386b287b0915", 3592 + "node_type": "outcome", 3593 + "title": "Optimized Vite config with explicit dependency pre-bundling", 3594 + "description": null, 3595 + "status": "completed", 3596 + "created_at": "2025-12-26T21:57:16.155112400-05:00", 3597 + "updated_at": "2025-12-27T15:37:52.289922500-05:00", 3598 + "metadata_json": "{\"branch\":\"master\",\"commit\":\"e04934f\",\"confidence\":85}" 3599 + }, 3600 + { 3601 + "id": 328, 3602 + "change_id": "7823be1a-fca9-4cb5-9e62-dfbc8cb71e55", 3603 + "node_type": "outcome", 3604 + "title": "Fixed decision graph integrity - linked 18 orphan nodes to parent goals, marked nodes 319-327 as completed", 3605 + "description": null, 3606 + "status": "completed", 3607 + "created_at": "2025-12-27T15:38:21.291457500-05:00", 3608 + "updated_at": "2025-12-27T17:49:54.129059900-05:00", 3609 + "metadata_json": "{\"branch\":\"master\",\"confidence\":100}" 3610 + }, 3611 + { 3612 + "id": 329, 3613 + "change_id": "c839ec54-b098-4030-8ff4-857549b17363", 3614 + "node_type": "observation", 3615 + "title": "Decision graph audit revealed systematic issues: 18 orphan nodes, incorrect status (pending vs completed), wrong orphan detection commands in recovery workflow", 3616 + "description": null, 3617 + "status": "completed", 3618 + "created_at": "2025-12-27T15:40:23.238704300-05:00", 3619 + "updated_at": "2025-12-27T17:49:57.327650700-05:00", 3620 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3621 + }, 3622 + { 3623 + "id": 330, 3624 + "change_id": "1f554b87-3775-450b-a3a1-b23eeebc7e38", 3625 + "node_type": "action", 3626 + "title": "Analyzing decision graph issues and updating CLAUDE.md with improved workflow", 3627 + "description": null, 3628 + "status": "completed", 3629 + "created_at": "2025-12-27T15:41:04.067444-05:00", 3630 + "updated_at": "2025-12-27T17:49:57.403361400-05:00", 3631 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3632 + }, 3633 + { 3634 + "id": 331, 3635 + "change_id": "8c746dd6-d571-4446-8a53-af6279fc9c21", 3636 + "node_type": "outcome", 3637 + "title": "Updated CLAUDE.md and .claude/ files with node lifecycle management, correct orphan detection commands, and common mistakes section", 3638 + "description": null, 3639 + "status": "completed", 3640 + "created_at": "2025-12-27T15:47:49.308750700-05:00", 3641 + "updated_at": "2025-12-27T17:49:57.478252800-05:00", 3642 + "metadata_json": "{\"branch\":\"master\",\"confidence\":100}" 3643 + }, 3644 + { 3645 + "id": 332, 3646 + "change_id": "c4338df4-a22f-4dd5-b60c-84c7cd1c0c5c", 3647 + "node_type": "action", 3648 + "title": "Committed documentation improvements", 3649 + "description": null, 3650 + "status": "completed", 3651 + "created_at": "2025-12-27T15:48:47.658343800-05:00", 3652 + "updated_at": "2025-12-27T17:49:57.553143200-05:00", 3653 + "metadata_json": "{\"branch\":\"master\",\"commit\":\"fcf682b\",\"confidence\":100}" 3654 + }, 3655 + { 3656 + "id": 333, 3657 + "change_id": "0a0375e9-bcef-4459-b9f1-f5868276e8e4", 3658 + "node_type": "goal", 3659 + "title": "Review and update all .md files to reflect current project status", 3660 + "description": null, 3661 + "status": "completed", 3662 + "created_at": "2025-12-27T15:50:48.815758500-05:00", 3663 + "updated_at": "2025-12-27T17:49:57.630386-05:00", 3664 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90,\"prompt\":\"review and update the .md files thru the project based on current project status.\"}" 3665 + }, 3666 + { 3667 + "id": 334, 3668 + "change_id": "fe108b87-356f-4c02-85cb-7260e175d8ad", 3669 + "node_type": "action", 3670 + "title": "Identifying all project .md files excluding dependencies", 3671 + "description": null, 3672 + "status": "completed", 3673 + "created_at": "2025-12-27T15:51:22.583189100-05:00", 3674 + "updated_at": "2025-12-27T17:49:57.707946400-05:00", 3675 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3676 + }, 3677 + { 3678 + "id": 335, 3679 + "change_id": "3aac85f7-c11c-48f6-b9da-2cd333605fb2", 3680 + "node_type": "observation", 3681 + "title": "Analyzed all project .md files - found outdated information in CONTRIBUTING.md (npm→pnpm), EXTENSION_STATUS.md (debugging→completed), PLAN.md (optimization status), extension README (build commands)", 3682 + "description": null, 3683 + "status": "completed", 3684 + "created_at": "2025-12-27T15:52:06.741629200-05:00", 3685 + "updated_at": "2025-12-27T17:49:57.786343300-05:00", 3686 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3687 + }, 3688 + { 3689 + "id": 336, 3690 + "change_id": "d1a23826-c660-4f2a-bdc0-bcbbce9d0293", 3691 + "node_type": "decision", 3692 + "title": "Choose which .md files to update based on priority and impact", 3693 + "description": null, 3694 + "status": "completed", 3695 + "created_at": "2025-12-27T15:52:30.322805700-05:00", 3696 + "updated_at": "2025-12-27T17:49:57.849977800-05:00", 3697 + "metadata_json": "{\"branch\":\"master\",\"confidence\":85}" 3698 + }, 3699 + { 3700 + "id": 337, 3701 + "change_id": "28eeefda-3813-4777-8006-924a9b030c61", 3702 + "node_type": "outcome", 3703 + "title": "User chose Option B: Complete update of EXTENSION_STATUS.md, CONTRIBUTING.md, PLAN.md, extension README", 3704 + "description": null, 3705 + "status": "completed", 3706 + "created_at": "2025-12-27T15:54:31.514053500-05:00", 3707 + "updated_at": "2025-12-27T15:59:48.206341500-05:00", 3708 + "metadata_json": "{\"branch\":\"master\",\"confidence\":100}" 3709 + }, 3710 + { 3711 + "id": 338, 3712 + "change_id": "594942d8-4981-4557-9687-522d51e86ecb", 3713 + "node_type": "action", 3714 + "title": "Updating EXTENSION_STATUS.md with current completion status and recent fixes", 3715 + "description": null, 3716 + "status": "completed", 3717 + "created_at": "2025-12-27T15:54:35.960795700-05:00", 3718 + "updated_at": "2025-12-27T15:55:47.472404200-05:00", 3719 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3720 + }, 3721 + { 3722 + "id": 339, 3723 + "change_id": "4c8c5b0d-468b-4ad6-80e9-02141949aba9", 3724 + "node_type": "action", 3725 + "title": "Updating CONTRIBUTING.md to use pnpm and reflect monorepo structure", 3726 + "description": null, 3727 + "status": "completed", 3728 + "created_at": "2025-12-27T15:55:49.596595900-05:00", 3729 + "updated_at": "2025-12-27T15:57:12.280431-05:00", 3730 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3731 + }, 3732 + { 3733 + "id": 340, 3734 + "change_id": "4e3987a4-538f-4912-b6ce-39c5971e0966", 3735 + "node_type": "action", 3736 + "title": "Reviewing and updating PLAN.md optimization status", 3737 + "description": null, 3738 + "status": "completed", 3739 + "created_at": "2025-12-27T15:57:14.603410600-05:00", 3740 + "updated_at": "2025-12-27T15:58:21.116083200-05:00", 3741 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3742 + }, 3743 + { 3744 + "id": 341, 3745 + "change_id": "42bf8d79-2c24-420f-b8b8-89273fecc30d", 3746 + "node_type": "action", 3747 + "title": "Updating packages/extension/README.md with pnpm commands and current context", 3748 + "description": null, 3749 + "status": "completed", 3750 + "created_at": "2025-12-27T15:58:23.453147600-05:00", 3751 + "updated_at": "2025-12-27T15:59:39.189409100-05:00", 3752 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3753 + }, 3754 + { 3755 + "id": 342, 3756 + "change_id": "a6d1f3fb-650d-4227-b1dc-ddb24810464c", 3757 + "node_type": "outcome", 3758 + "title": "Successfully updated all 4 markdown files with current project status, pnpm commands, monorepo structure, and completion status", 3759 + "description": null, 3760 + "status": "completed", 3761 + "created_at": "2025-12-27T15:59:41.457774700-05:00", 3762 + "updated_at": "2025-12-27T15:59:45.883622500-05:00", 3763 + "metadata_json": "{\"branch\":\"master\",\"confidence\":100}" 3764 + }, 3765 + { 3766 + "id": 343, 3767 + "change_id": "9e0fcead-ea30-4b31-974b-4e07f7fc6787", 3768 + "node_type": "action", 3769 + "title": "Committed all markdown documentation updates", 3770 + "description": null, 3771 + "status": "completed", 3772 + "created_at": "2025-12-27T16:02:13.397776700-05:00", 3773 + "updated_at": "2025-12-27T16:02:56.131931100-05:00", 3774 + "metadata_json": "{\"branch\":\"master\",\"commit\":\"fe29bb3\",\"confidence\":100}" 3775 + }, 3776 + { 3777 + "id": 344, 3778 + "change_id": "2a06900e-ea62-4adf-81d5-7f0cf1a29b31", 3779 + "node_type": "goal", 3780 + "title": "Add Tailwind CSS to extension for design consistency", 3781 + "description": null, 3782 + "status": "completed", 3783 + "created_at": "2025-12-27T17:59:23.523767600-05:00", 3784 + "updated_at": "2025-12-27T18:07:53.271415-05:00", 3785 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90,\"prompt\":\"yes\"}" 3786 + }, 3787 + { 3788 + "id": 345, 3789 + "change_id": "0ef352ed-538b-4632-8b62-ebb17603f944", 3790 + "node_type": "action", 3791 + "title": "Installing Tailwind CSS and PostCSS dependencies", 3792 + "description": null, 3793 + "status": "completed", 3794 + "created_at": "2025-12-27T18:00:41.652670100-05:00", 3795 + "updated_at": "2025-12-27T18:00:43.901523100-05:00", 3796 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3797 + }, 3798 + { 3799 + "id": 346, 3800 + "change_id": "888e6ad0-5002-4cdb-b35e-f4214ca07dfa", 3801 + "node_type": "action", 3802 + "title": "Creating Tailwind and PostCSS config files", 3803 + "description": null, 3804 + "status": "completed", 3805 + "created_at": "2025-12-27T18:01:27.404433500-05:00", 3806 + "updated_at": "2025-12-27T18:01:29.980132200-05:00", 3807 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3808 + }, 3809 + { 3810 + "id": 347, 3811 + "change_id": "fae7a634-d921-4b6f-9620-0c58d88b863e", 3812 + "node_type": "action", 3813 + "title": "Updating build.js to process CSS with PostCSS + Tailwind", 3814 + "description": null, 3815 + "status": "completed", 3816 + "created_at": "2025-12-27T18:01:50.537140900-05:00", 3817 + "updated_at": "2025-12-27T18:01:53.031316700-05:00", 3818 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3819 + }, 3820 + { 3821 + "id": 348, 3822 + "change_id": "c25a8f4b-8bf1-4a33-bef9-3731dfd83627", 3823 + "node_type": "action", 3824 + "title": "Converting popup.css to use Tailwind directives", 3825 + "description": null, 3826 + "status": "completed", 3827 + "created_at": "2025-12-27T18:02:42.167814700-05:00", 3828 + "updated_at": "2025-12-27T18:02:44.488653900-05:00", 3829 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3830 + }, 3831 + { 3832 + "id": 349, 3833 + "change_id": "c65ee3d9-62a0-47aa-870a-f6422ff2536a", 3834 + "node_type": "action", 3835 + "title": "Converting popup.html to use Tailwind utility classes", 3836 + "description": null, 3837 + "status": "completed", 3838 + "created_at": "2025-12-27T18:03:00.465637900-05:00", 3839 + "updated_at": "2025-12-27T18:03:02.815261100-05:00", 3840 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3841 + }, 3842 + { 3843 + "id": 350, 3844 + "change_id": "8136e615-5baa-4fe5-9a7d-d672ff1a6f85", 3845 + "node_type": "outcome", 3846 + "title": "Successfully integrated Tailwind CSS into extension", 3847 + "description": null, 3848 + "status": "completed", 3849 + "created_at": "2025-12-27T18:07:49.869572400-05:00", 3850 + "updated_at": "2025-12-27T18:07:52.136827400-05:00", 3851 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3852 + }, 3853 + { 3854 + "id": 351, 3855 + "change_id": "9468bcb3-78ec-4dae-8d8f-968ba6f5b3fe", 3856 + "node_type": "outcome", 3857 + "title": "Committed Tailwind CSS integration to git", 3858 + "description": null, 3859 + "status": "completed", 3860 + "created_at": "2025-12-27T18:38:55.689869700-05:00", 3861 + "updated_at": "2025-12-27T18:39:01.013284600-05:00", 3862 + "metadata_json": "{\"branch\":\"master\",\"commit\":\"d07180c\",\"confidence\":95}" 3863 + }, 3864 + { 3865 + "id": 352, 3866 + "change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5", 3867 + "node_type": "goal", 3868 + "title": "Fix extension dark mode and dev/prod detection issues", 3869 + "description": null, 3870 + "status": "completed", 3871 + "created_at": "2025-12-27T22:05:50.675487800-05:00", 3872 + "updated_at": "2025-12-27T22:09:32.111749500-05:00", 3873 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90,\"prompt\":\"there now seems to be an issue with dark mode not activating, and either an issue with detecting dev vs prod or the copy is just wrong. analyze and fix.\"}" 3874 + }, 3875 + { 3876 + "id": 353, 3877 + "change_id": "eaed6e9b-9f16-4b45-8783-44ea2ea1f2a9", 3878 + "node_type": "observation", 3879 + "title": "Found two issues: 1) darkMode: 'class' requires manual .dark class addition, 2) Dev/prod detection may be incorrect", 3880 + "description": null, 3881 + "status": "completed", 3882 + "created_at": "2025-12-27T22:06:19.509001-05:00", 3883 + "updated_at": "2025-12-27T22:06:23.515277300-05:00", 3884 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3885 + }, 3886 + { 3887 + "id": 354, 3888 + "change_id": "d66fc83e-9737-4047-8ce2-e2ba857aeea9", 3889 + "node_type": "decision", 3890 + "title": "Choose dark mode strategy: media queries vs class-based with JS", 3891 + "description": null, 3892 + "status": "completed", 3893 + "created_at": "2025-12-27T22:07:01.587088200-05:00", 3894 + "updated_at": "2025-12-27T22:07:07.798171700-05:00", 3895 + "metadata_json": "{\"branch\":\"master\",\"confidence\":85}" 3896 + }, 3897 + { 3898 + "id": 355, 3899 + "change_id": "76e2a379-7803-4c82-8013-be6b62f2d360", 3900 + "node_type": "outcome", 3901 + "title": "Chose media queries - simpler and matches original behavior", 3902 + "description": null, 3903 + "status": "completed", 3904 + "created_at": "2025-12-27T22:07:04.660558100-05:00", 3905 + "updated_at": "2025-12-27T22:07:07.897193100-05:00", 3906 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3907 + }, 3908 + { 3909 + "id": 356, 3910 + "change_id": "df681aa8-e470-4ead-a0d2-a4095febfa3d", 3911 + "node_type": "action", 3912 + "title": "Fixing dark mode config to use media queries", 3913 + "description": null, 3914 + "status": "completed", 3915 + "created_at": "2025-12-27T22:07:24.774976300-05:00", 3916 + "updated_at": "2025-12-27T22:07:30.392290200-05:00", 3917 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3918 + }, 3919 + { 3920 + "id": 357, 3921 + "change_id": "57060303-5a30-4f11-a752-a02376df5ea7", 3922 + "node_type": "action", 3923 + "title": "Making server offline message conditional on build mode", 3924 + "description": null, 3925 + "status": "completed", 3926 + "created_at": "2025-12-27T22:07:49.952419800-05:00", 3927 + "updated_at": "2025-12-27T22:09:00.514201500-05:00", 3928 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3929 + }, 3930 + { 3931 + "id": 358, 3932 + "change_id": "fc211ac7-7a1a-4b69-835a-992c354e8237", 3933 + "node_type": "outcome", 3934 + "title": "Successfully fixed dark mode and dev/prod messaging", 3935 + "description": null, 3936 + "status": "completed", 3937 + "created_at": "2025-12-27T22:09:28.843864300-05:00", 3938 + "updated_at": "2025-12-27T22:09:32.017503200-05:00", 3939 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3940 + }, 3941 + { 3942 + "id": 359, 3943 + "change_id": "4a7d5885-1713-4ba7-ad13-bb12b58c9410", 3944 + "node_type": "outcome", 3945 + "title": "Committed fixes to git", 3946 + "description": null, 3947 + "status": "completed", 3948 + "created_at": "2025-12-27T22:10:25.576235500-05:00", 3949 + "updated_at": "2025-12-27T22:10:28.961887300-05:00", 3950 + "metadata_json": "{\"branch\":\"master\",\"commit\":\"bd3aabb\",\"confidence\":95}" 3951 + }, 3952 + { 3953 + "id": 360, 3954 + "change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a", 3955 + "node_type": "goal", 3956 + "title": "Fix extension not detecting login session despite dev server running", 3957 + "description": null, 3958 + "status": "completed", 3959 + "created_at": "2025-12-27T22:23:13.072419900-05:00", 3960 + "updated_at": "2025-12-27T22:41:49.160848100-05:00", 3961 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90,\"prompt\":\"dark mode is fixed, but the extension in /chrome/ uploaded still is saying login with atlast and dev server is running\"}" 3962 + }, 3963 + { 3964 + "id": 361, 3965 + "change_id": "aecf2327-d20d-4c6c-b6b0-06ccf26a2b27", 3966 + "node_type": "observation", 3967 + "title": "Extension dist/chrome contains production build, not dev build. User ran build:prod last.", 3968 + "description": null, 3969 + "status": "completed", 3970 + "created_at": "2025-12-27T22:23:45.918832500-05:00", 3971 + "updated_at": "2025-12-27T22:23:48.919570500-05:00", 3972 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3973 + }, 3974 + { 3975 + "id": 362, 3976 + "change_id": "e897db97-44d8-4993-b4c3-0d829265b2f8", 3977 + "node_type": "observation", 3978 + "title": "Dev build now deployed. Extension will check session at http://127.0.0.1:8888/.netlify/functions/session with credentials:include", 3979 + "description": null, 3980 + "status": "completed", 3981 + "created_at": "2025-12-27T22:24:17.767230200-05:00", 3982 + "updated_at": "2025-12-27T22:24:20.981953100-05:00", 3983 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3984 + }, 3985 + { 3986 + "id": 363, 3987 + "change_id": "2c62bfa3-d148-4448-8c2b-d0cf1e94ceb0", 3988 + "node_type": "observation", 3989 + "title": "Found CORS issue: successResponse uses 'Access-Control-Allow-Origin: *' which blocks credentialed requests from extension", 3990 + "description": null, 3991 + "status": "completed", 3992 + "created_at": "2025-12-27T22:24:51.861265800-05:00", 3993 + "updated_at": "2025-12-27T22:24:55.482724500-05:00", 3994 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3995 + }, 3996 + { 3997 + "id": 364, 3998 + "change_id": "560d6bea-47ec-408d-919b-15ca7198aac9", 3999 + "node_type": "action", 4000 + "title": "Updating CORS headers to support credentialed requests from extension", 4001 + "description": null, 4002 + "status": "completed", 4003 + "created_at": "2025-12-27T22:25:23.035212700-05:00", 4004 + "updated_at": "2025-12-27T22:26:03.046221900-05:00", 4005 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 4006 + }, 4007 + { 4008 + "id": 365, 4009 + "change_id": "3ef0c9e9-aa40-4914-a5f4-32bcfaf68d04", 4010 + "node_type": "outcome", 4011 + "title": "Fixed CORS to support credentialed requests from extensions", 4012 + "description": null, 4013 + "status": "completed", 4014 + "created_at": "2025-12-27T22:41:38.430661200-05:00", 4015 + "updated_at": "2025-12-27T22:41:48.981429600-05:00", 4016 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4017 + }, 4018 + { 4019 + "id": 366, 4020 + "change_id": "77b7ed7e-a113-41f6-a677-50d376f3f008", 4021 + "node_type": "outcome", 4022 + "title": "Committed CORS fixes to git", 4023 + "description": null, 4024 + "status": "completed", 4025 + "created_at": "2025-12-27T22:42:49.037783-05:00", 4026 + "updated_at": "2025-12-27T22:42:54.162857-05:00", 4027 + "metadata_json": "{\"branch\":\"master\",\"commit\":\"603cf0a\",\"confidence\":95}" 4028 + }, 4029 + { 4030 + "id": 367, 4031 + "change_id": "df6abf7a-e7a4-45f3-8485-b933319416d9", 4032 + "node_type": "goal", 4033 + "title": "Create Firefox-compatible version of Twitter scraper extension", 4034 + "description": null, 4035 + "status": "completed", 4036 + "created_at": "2025-12-28T18:09:33.241860800-05:00", 4037 + "updated_at": "2025-12-28T19:21:32.412499-05:00", 4038 + "metadata_json": "{\"branch\":\"master\",\"confidence\":85,\"prompt\":\"let's make the extension have a firefox compatible version too.\"}" 4039 + }, 4040 + { 4041 + "id": 368, 4042 + "change_id": "79721edf-aa05-4580-8c28-7d20941ef155", 4043 + "node_type": "observation", 4044 + "title": "Current extension uses Manifest V3 with Chrome-specific APIs", 4045 + "description": null, 4046 + "status": "pending", 4047 + "created_at": "2025-12-28T18:10:08.441348100-05:00", 4048 + "updated_at": "2025-12-28T18:10:08.441348100-05:00", 4049 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4050 + }, 4051 + { 4052 + "id": 369, 4053 + "change_id": "783841d0-c096-48f6-be18-193a9dcc7d4b", 4054 + "node_type": "observation", 4055 + "title": "Firefox compatibility analysis: Extension uses chrome.* APIs (runtime.sendMessage, storage.local, tabs.query/sendMessage), MV3 service worker. Firefox supports MV3 but has differences. Options: 1) Use webextension-polyfill for cross-browser, 2) Dual manifests (MV3 Chrome + MV2 Firefox), 3) Keep MV3 for both with minimal changes. Current build outputs to dist/chrome only.", 4056 + "description": null, 4057 + "status": "pending", 4058 + "created_at": "2025-12-28T18:10:48.087066800-05:00", 4059 + "updated_at": "2025-12-28T18:10:48.087066800-05:00", 4060 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 4061 + }, 4062 + { 4063 + "id": 370, 4064 + "change_id": "fd2d5b63-c26c-4592-89a6-3ccb4234c3c6", 4065 + "node_type": "decision", 4066 + "title": "Choose Firefox compatibility approach: webextension-polyfill, dual manifests, or minimal MV3 changes", 4067 + "description": null, 4068 + "status": "pending", 4069 + "created_at": "2025-12-28T18:10:50.375270400-05:00", 4070 + "updated_at": "2025-12-28T18:10:50.375270400-05:00", 4071 + "metadata_json": "{\"branch\":\"master\",\"confidence\":80}" 4072 + }, 4073 + { 4074 + "id": 371, 4075 + "change_id": "159906da-984f-4a1d-a1a6-98e0fc0cf369", 4076 + "node_type": "option", 4077 + "title": "Use webextension-polyfill library for unified cross-browser API", 4078 + "description": null, 4079 + "status": "pending", 4080 + "created_at": "2025-12-28T18:11:05.947924200-05:00", 4081 + "updated_at": "2025-12-28T18:11:05.947924200-05:00", 4082 + "metadata_json": "{\"branch\":\"master\",\"confidence\":85}" 4083 + }, 4084 + { 4085 + "id": 372, 4086 + "change_id": "df5e42e6-53c1-4b30-8b6f-f2385cd9e247", 4087 + "node_type": "option", 4088 + "title": "Dual manifests: MV3 for Chrome, MV2 for Firefox with separate builds", 4089 + "description": null, 4090 + "status": "pending", 4091 + "created_at": "2025-12-28T18:11:08.179938100-05:00", 4092 + "updated_at": "2025-12-28T18:11:08.179938100-05:00", 4093 + "metadata_json": "{\"branch\":\"master\",\"confidence\":85}" 4094 + }, 4095 + { 4096 + "id": 373, 4097 + "change_id": "7bb58202-7a9b-4e8b-8b9e-927e5106bce7", 4098 + "node_type": "option", 4099 + "title": "Keep MV3 for both browsers with minimal manifest tweaks", 4100 + "description": null, 4101 + "status": "pending", 4102 + "created_at": "2025-12-28T18:11:10.370113600-05:00", 4103 + "updated_at": "2025-12-28T18:11:10.370113600-05:00", 4104 + "metadata_json": "{\"branch\":\"master\",\"confidence\":85}" 4105 + }, 4106 + { 4107 + "id": 374, 4108 + "change_id": "d41b29e0-cd48-4dac-a6c8-c6179612702e", 4109 + "node_type": "outcome", 4110 + "title": "Chose webextension-polyfill approach. Provides unified browser.* API, Promise-based, future-proof MV3 for both browsers, +20KB but cleaner codebase", 4111 + "description": null, 4112 + "status": "pending", 4113 + "created_at": "2025-12-28T19:04:24.676770900-05:00", 4114 + "updated_at": "2025-12-28T19:04:24.676770900-05:00", 4115 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4116 + }, 4117 + { 4118 + "id": 375, 4119 + "change_id": "5bb34b8b-aec4-4f84-993e-eb9bf7a2d13f", 4120 + "node_type": "action", 4121 + "title": "Installing webextension-polyfill and updating source files to use browser.* API", 4122 + "description": null, 4123 + "status": "completed", 4124 + "created_at": "2025-12-28T19:08:14.642882400-05:00", 4125 + "updated_at": "2025-12-28T19:21:32.531034800-05:00", 4126 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 4127 + }, 4128 + { 4129 + "id": 376, 4130 + "change_id": "644181ee-5a44-4967-9657-e9dd5f648c5e", 4131 + "node_type": "outcome", 4132 + "title": "Successfully implemented Firefox compatibility with webextension-polyfill. Both Chrome and Firefox builds compile successfully. Chrome uses service_worker (MV3), Firefox uses scripts array with browser_specific_settings. All chrome.* API calls replaced with browser.* imports.", 4133 + "description": null, 4134 + "status": "completed", 4135 + "created_at": "2025-12-28T19:14:22.309457600-05:00", 4136 + "updated_at": "2025-12-28T19:21:32.658297400-05:00", 4137 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4138 + }, 4139 + { 4140 + "id": 377, 4141 + "change_id": "1dffa024-413f-4a95-b069-66db350abfaa", 4142 + "node_type": "goal", 4143 + "title": "Fix Firefox extension server detection and login check", 4144 + "description": null, 4145 + "status": "completed", 4146 + "created_at": "2025-12-28T20:14:51.646204800-05:00", 4147 + "updated_at": "2025-12-28T20:32:19.249555-05:00", 4148 + "metadata_json": "{\"branch\":\"master\",\"confidence\":85,\"prompt\":\"The extension works in chrome. In firefox, it's failing to detect that the dev server is running and open + logged in on firefox. There's no right-click to inspect on the popup either.\"}" 4149 + }, 4150 + { 4151 + "id": 378, 4152 + "change_id": "9d5626d2-a9ae-42aa-8fda-be3c7528156f", 4153 + "node_type": "observation", 4154 + "title": "Firefox extension debugging differs from Chrome - need to use about:debugging Inspect button or Browser Console, not right-click popup", 4155 + "description": null, 4156 + "status": "pending", 4157 + "created_at": "2025-12-28T20:15:11.710473-05:00", 4158 + "updated_at": "2025-12-28T20:15:11.710473-05:00", 4159 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4160 + }, 4161 + { 4162 + "id": 379, 4163 + "change_id": "7a5af3fe-8567-4f1c-85cd-e47891704974", 4164 + "node_type": "observation", 4165 + "title": "Potential Firefox issues: 1) CORS with credentials:include may be stricter, 2) Cookie partitioning/third-party cookie blocking, 3) Extension needs explicit host_permissions for cookies to work. Firefox manifest has host_permissions but may need additional cookie permissions.", 4166 + "description": null, 4167 + "status": "pending", 4168 + "created_at": "2025-12-28T20:15:31.278249900-05:00", 4169 + "updated_at": "2025-12-28T20:15:31.278249900-05:00", 4170 + "metadata_json": "{\"branch\":\"master\",\"confidence\":85}" 4171 + }, 4172 + { 4173 + "id": 380, 4174 + "change_id": "9c197aae-18d5-46ae-87e7-82c240c8f313", 4175 + "node_type": "action", 4176 + "title": "Adding cookies permission to Firefox manifest for credentials:include support", 4177 + "description": null, 4178 + "status": "pending", 4179 + "created_at": "2025-12-28T20:16:12.019659700-05:00", 4180 + "updated_at": "2025-12-28T20:16:12.019659700-05:00", 4181 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 4182 + }, 4183 + { 4184 + "id": 381, 4185 + "change_id": "485a03b0-8a25-4fdf-a8e2-9d3a25c8edf8", 4186 + "node_type": "outcome", 4187 + "title": "Fixed Firefox cookie issue by adding cookies permission to manifest. Firefox requires explicit permission even with host_permissions. Rebuild successful.", 4188 + "description": null, 4189 + "status": "pending", 4190 + "created_at": "2025-12-28T20:16:41.702322300-05:00", 4191 + "updated_at": "2025-12-28T20:16:41.702322300-05:00", 4192 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4193 + }, 4194 + { 4195 + "id": 382, 4196 + "change_id": "35b13d37-0228-435f-a4bc-c5c42811fec3", 4197 + "node_type": "observation", 4198 + "title": "Firefox blocks extension fetch with CORS error despite host_permissions. Server responds 200 but missing Access-Control-Allow-Origin header. Firefox stricter than Chrome on extension CORS.", 4199 + "description": null, 4200 + "status": "pending", 4201 + "created_at": "2025-12-28T20:17:23.414134300-05:00", 4202 + "updated_at": "2025-12-28T20:17:23.414134300-05:00", 4203 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4204 + }, 4205 + { 4206 + "id": 383, 4207 + "change_id": "adc120cd-e56d-400a-9b3e-8207880378c3", 4208 + "node_type": "action", 4209 + "title": "Adding CORS headers to netlify.toml for extension compatibility - wildcard origin with credentials for dev", 4210 + "description": null, 4211 + "status": "pending", 4212 + "created_at": "2025-12-28T20:18:22.172869600-05:00", 4213 + "updated_at": "2025-12-28T20:18:22.172869600-05:00", 4214 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 4215 + }, 4216 + { 4217 + "id": 384, 4218 + "change_id": "0f77bfd9-590f-4f1e-be08-78a9deef6d8a", 4219 + "node_type": "outcome", 4220 + "title": "Added CORS headers to netlify.toml for all paths including root and functions. Headers include Access-Control-Allow-Origin:*, Allow-Credentials:true for dev environment. User needs to restart dev server.", 4221 + "description": null, 4222 + "status": "pending", 4223 + "created_at": "2025-12-28T20:19:54.829093600-05:00", 4224 + "updated_at": "2025-12-28T20:19:54.829093600-05:00", 4225 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 4226 + }, 4227 + { 4228 + "id": 385, 4229 + "change_id": "cc0910f0-2381-4aee-bb5d-397cb0f804d1", 4230 + "node_type": "observation", 4231 + "title": "CORS wildcard (*) incompatible with credentials:include. Browser security prevents wildcard CORS with credentialed requests. Extension origins are dynamic (moz-extension://, chrome-extension://). Need to handle CORS in serverless functions by reflecting request origin.", 4232 + "description": null, 4233 + "status": "pending", 4234 + "created_at": "2025-12-28T20:27:31.848523900-05:00", 4235 + "updated_at": "2025-12-28T20:27:31.848523900-05:00", 4236 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4237 + }, 4238 + { 4239 + "id": 386, 4240 + "change_id": "ad4a5ca7-15d1-4776-8ede-6b615613f6e1", 4241 + "node_type": "action", 4242 + "title": "Adding moz-extension:// origin detection to CORS handler for Firefox extension support", 4243 + "description": null, 4244 + "status": "completed", 4245 + "created_at": "2025-12-28T20:28:31.661326900-05:00", 4246 + "updated_at": "2025-12-28T20:32:19.367968600-05:00", 4247 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4248 + }, 4249 + { 4250 + "id": 387, 4251 + "change_id": "cffdee0f-8535-4d88-83ed-fdf6101f7ac3", 4252 + "node_type": "outcome", 4253 + "title": "Fixed Firefox extension CORS by adding moz-extension:// origin detection to response.utils.ts. Reverted netlify.toml changes as functions handle CORS correctly. User needs to restart dev server.", 4254 + "description": null, 4255 + "status": "completed", 4256 + "created_at": "2025-12-28T20:29:39.856303800-05:00", 4257 + "updated_at": "2025-12-28T20:32:19.494690-05:00", 4258 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4259 + }, 4260 + { 4261 + "id": 388, 4262 + "change_id": "0ada864e-be98-4a2f-a14e-ffd3eea9aaa9", 4263 + "node_type": "observation", 4264 + "title": "Health check uses HEAD request to root URL (Vite server), not a Netlify function. Doesn't get CORS headers from getCorsHeaders. Need dedicated health endpoint or change check to use existing function.", 4265 + "description": null, 4266 + "status": "completed", 4267 + "created_at": "2025-12-28T20:37:22.132717600-05:00", 4268 + "updated_at": "2025-12-28T20:38:41.630020900-05:00", 4269 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4270 + }, 4271 + { 4272 + "id": 389, 4273 + "change_id": "f522d5b2-c325-4f34-9f27-b8ea5c50618d", 4274 + "node_type": "outcome", 4275 + "title": "Created /health function endpoint with CORS support. Updated checkServerHealth to use /.netlify/functions/health instead of root URL. Extension rebuilt successfully.", 4276 + "description": null, 4277 + "status": "completed", 4278 + "created_at": "2025-12-28T20:38:19.981309500-05:00", 4279 + "updated_at": "2025-12-28T20:38:41.780183300-05:00", 4280 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4281 + }, 4282 + { 4283 + "id": 390, 4284 + "change_id": "cfdcf45b-47b3-4239-8053-417bd31957ed", 4285 + "node_type": "observation", 4286 + "title": "Server receives session request but returns CORS wildcard (*) instead of extension origin. No session cookie received. Origin header might not be sent by Firefox extension or not detected correctly.", 4287 + "description": null, 4288 + "status": "pending", 4289 + "created_at": "2025-12-28T20:48:12.770638500-05:00", 4290 + "updated_at": "2025-12-28T20:48:12.770638500-05:00", 4291 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4292 + }, 4293 + { 4294 + "id": 391, 4295 + "change_id": "2b53a419-9a47-4285-9a12-9bdfaeeb9ff0", 4296 + "node_type": "observation", 4297 + "title": "Health endpoint gets CORS headers correctly (moz-extension detected). Session endpoint error middleware doesn't pass event to errorResponse, returns wildcard CORS. Need to fix error middleware to pass event.", 4298 + "description": null, 4299 + "status": "completed", 4300 + "created_at": "2025-12-28T20:55:32.024834200-05:00", 4301 + "updated_at": "2025-12-28T21:38:14.729731500-05:00", 4302 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4303 + }, 4304 + { 4305 + "id": 392, 4306 + "change_id": "c941d136-3405-483d-bf34-7fb011f6d072", 4307 + "node_type": "action", 4308 + "title": "Fixed error middleware to pass event to errorResponse for proper CORS headers on errors", 4309 + "description": null, 4310 + "status": "completed", 4311 + "created_at": "2025-12-28T20:56:38.876266200-05:00", 4312 + "updated_at": "2025-12-28T21:38:14.888627800-05:00", 4313 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4314 + }, 4315 + { 4316 + "id": 393, 4317 + "change_id": "aafd9977-8800-4152-9f7f-b817db6df573", 4318 + "node_type": "outcome", 4319 + "title": "Fixed Firefox extension CORS completely. Error middleware now passes event to errorResponse so Firefox extension origin is properly reflected in error responses with credentials. Debug logging removed.", 4320 + "description": null, 4321 + "status": "completed", 4322 + "created_at": "2025-12-28T21:37:22.780953600-05:00", 4323 + "updated_at": "2025-12-28T21:38:15.071425500-05:00", 4324 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4325 + }, 4326 + { 4327 + "id": 394, 4328 + "change_id": "3b0dea7a-c3cd-45a8-ba1a-f1040aa4e1d9", 4329 + "node_type": "observation", 4330 + "title": "CORS fully working - Firefox extension origin properly reflected with credentials. But cookies not sent from extension despite credentials:include. Cookie set in web context not accessible from extension context due to Firefox cookie partitioning.", 4331 + "description": null, 4332 + "status": "pending", 4333 + "created_at": "2025-12-28T21:46:45.822343200-05:00", 4334 + "updated_at": "2025-12-28T21:46:45.822343200-05:00", 4335 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4336 + }, 4337 + { 4338 + "id": 395, 4339 + "change_id": "8a93413f-a09c-4cc1-8693-4fe90dc055c4", 4340 + "node_type": "action", 4341 + "title": "Updated extension checkSession to read cookie via browser.cookies API and pass as query parameter. Workaround for Firefox SameSite=Lax cookie partitioning.", 4342 + "description": null, 4343 + "status": "pending", 4344 + "created_at": "2025-12-28T21:52:22.059862700-05:00", 4345 + "updated_at": "2025-12-28T21:52:22.059862700-05:00", 4346 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4347 + }, 4348 + { 4349 + "id": 396, 4350 + "change_id": "864dd973-5f15-4e31-a7da-c548dbbe1f0e", 4351 + "node_type": "outcome", 4352 + "title": "Extension now uses browser.cookies.get() API to read session cookie and pass as query parameter. Workaround for Firefox SameSite=Lax cookie partitioning in extensions. Extension rebuilt successfully.", 4353 + "description": null, 4354 + "status": "pending", 4355 + "created_at": "2025-12-28T22:51:31.578965200-05:00", 4356 + "updated_at": "2025-12-28T22:51:31.578965200-05:00", 4357 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 4358 } 4359 ], 4360 "edges": [ ··· 7833 "weight": 1.0, 7834 "rationale": "User reported results showing 'none' before search completes - needed to keep user on loading screen", 7835 "created_at": "2025-12-26T21:20:53.976836200-05:00" 7836 + }, 7837 + { 7838 + "id": 317, 7839 + "from_node_id": 325, 7840 + "to_node_id": 326, 7841 + "from_change_id": "e44f45f8-bac9-4a49-ac68-ac9d7d113226", 7842 + "to_change_id": "af76ea64-b0b1-4577-b521-4ec21cc555e1", 7843 + "edge_type": "leads_to", 7844 + "weight": 1.0, 7845 + "rationale": "User reported upload times showing 5 hours ahead - timezone offset issue", 7846 + "created_at": "2025-12-26T21:46:24.801578500-05:00" 7847 + }, 7848 + { 7849 + "id": 318, 7850 + "from_node_id": 326, 7851 + "to_node_id": 327, 7852 + "from_change_id": "af76ea64-b0b1-4577-b521-4ec21cc555e1", 7853 + "to_change_id": "ed9ceca3-e53e-430c-8f0f-386b287b0915", 7854 + "edge_type": "leads_to", 7855 + "weight": 1.0, 7856 + "rationale": "User reported slow dev server startup - 4.5s from Vite", 7857 + "created_at": "2025-12-26T21:57:18.723545100-05:00" 7858 + }, 7859 + { 7860 + "id": 319, 7861 + "from_node_id": 305, 7862 + "to_node_id": 325, 7863 + "from_change_id": "8ad6ef53-29a2-442e-b88f-9e0541634950", 7864 + "to_change_id": "e44f45f8-bac9-4a49-ac68-ac9d7d113226", 7865 + "edge_type": "leads_to", 7866 + "weight": 1.0, 7867 + "rationale": "Implemented loading screen for extension upload flow", 7868 + "created_at": "2025-12-27T15:22:53.706223600-05:00" 7869 + }, 7870 + { 7871 + "id": 320, 7872 + "from_node_id": 318, 7873 + "to_node_id": 326, 7874 + "from_change_id": "371f788d-46df-4651-b338-f9310f8ae810", 7875 + "to_change_id": "af76ea64-b0b1-4577-b521-4ec21cc555e1", 7876 + "edge_type": "leads_to", 7877 + "weight": 1.0, 7878 + "rationale": "Fixed timezone issue with TIMESTAMPTZ migration", 7879 + "created_at": "2025-12-27T15:22:56.160485500-05:00" 7880 + }, 7881 + { 7882 + "id": 321, 7883 + "from_node_id": 69, 7884 + "to_node_id": 67, 7885 + "from_change_id": "5754ca49-f09b-489f-a4b0-f412159f4cd4", 7886 + "to_change_id": "6aef16a0-0524-4ad9-a8ff-b335069c860d", 7887 + "edge_type": "leads_to", 7888 + "weight": 1.0, 7889 + "rationale": "Action to understand current duplicate types", 7890 + "created_at": "2025-12-27T15:36:45.647337400-05:00" 7891 + }, 7892 + { 7893 + "id": 322, 7894 + "from_node_id": 110, 7895 + "to_node_id": 117, 7896 + "from_change_id": "22b9c3db-9f95-45d7-a3ed-bdfac54677db", 7897 + "to_change_id": "d78b544a-8897-4149-ac48-4f35f6def985", 7898 + "edge_type": "leads_to", 7899 + "weight": 1.0, 7900 + "rationale": "Cleanup observation during codebase cleanup", 7901 + "created_at": "2025-12-27T15:36:47.932994300-05:00" 7902 + }, 7903 + { 7904 + "id": 323, 7905 + "from_node_id": 110, 7906 + "to_node_id": 183, 7907 + "from_change_id": "22b9c3db-9f95-45d7-a3ed-bdfac54677db", 7908 + "to_change_id": "6e1851e2-134c-4c8f-86af-5487fda7d05c", 7909 + "edge_type": "leads_to", 7910 + "weight": 1.0, 7911 + "rationale": "Removed build artifacts from git history", 7912 + "created_at": "2025-12-27T15:36:50.152456600-05:00" 7913 + }, 7914 + { 7915 + "id": 324, 7916 + "from_node_id": 184, 7917 + "to_node_id": 228, 7918 + "from_change_id": "919c42ef-9fae-473f-b755-ee32d8999204", 7919 + "to_change_id": "7958ec7b-ff18-41d4-b1e1-fc9fa5603a1b", 7920 + "edge_type": "leads_to", 7921 + "weight": 1.0, 7922 + "rationale": "Installing pnpm for monorepo structure", 7923 + "created_at": "2025-12-27T15:36:52.522283200-05:00" 7924 + }, 7925 + { 7926 + "id": 325, 7927 + "from_node_id": 258, 7928 + "to_node_id": 262, 7929 + "from_change_id": "b8c6cd90-7f32-461e-aad5-537cc1cbfafe", 7930 + "to_change_id": "b8097a68-a63f-4cb6-aeac-2ed746e90126", 7931 + "edge_type": "leads_to", 7932 + "weight": 1.0, 7933 + "rationale": "Discovered extension-import endpoint during debugging", 7934 + "created_at": "2025-12-27T15:36:55.150261400-05:00" 7935 + }, 7936 + { 7937 + "id": 326, 7938 + "from_node_id": 258, 7939 + "to_node_id": 263, 7940 + "from_change_id": "b8c6cd90-7f32-461e-aad5-537cc1cbfafe", 7941 + "to_change_id": "b5109344-a5d3-43b3-b743-b06730453514", 7942 + "edge_type": "leads_to", 7943 + "weight": 1.0, 7944 + "rationale": "Discovered routing issue during debugging", 7945 + "created_at": "2025-12-27T15:36:57.690344600-05:00" 7946 + }, 7947 + { 7948 + "id": 327, 7949 + "from_node_id": 270, 7950 + "to_node_id": 275, 7951 + "from_change_id": "8cf80c58-e909-4f0b-85e8-ac15d7cf3640", 7952 + "to_change_id": "dcc9f401-1a68-479e-97de-7a04e5597e00", 7953 + "edge_type": "leads_to", 7954 + "weight": 1.0, 7955 + "rationale": "Discovered CORS blocking health check", 7956 + "created_at": "2025-12-27T15:37:00.388733200-05:00" 7957 + }, 7958 + { 7959 + "id": 328, 7960 + "from_node_id": 278, 7961 + "to_node_id": 282, 7962 + "from_change_id": "fa11e7d7-ac30-4d0e-bc8a-d2332f724d92", 7963 + "to_change_id": "206347b5-4178-43dd-bb05-657b3788a6b0", 7964 + "edge_type": "leads_to", 7965 + "weight": 1.0, 7966 + "rationale": "Refactoring extension flow to match upload behavior", 7967 + "created_at": "2025-12-27T15:37:02.697547600-05:00" 7968 + }, 7969 + { 7970 + "id": 329, 7971 + "from_node_id": 278, 7972 + "to_node_id": 283, 7973 + "from_change_id": "fa11e7d7-ac30-4d0e-bc8a-d2332f724d92", 7974 + "to_change_id": "e3adddaf-9126-4bfa-8d75-aa8b94323077", 7975 + "edge_type": "leads_to", 7976 + "weight": 1.0, 7977 + "rationale": "Observation after implementing auth and upload creation", 7978 + "created_at": "2025-12-27T15:37:04.961909600-05:00" 7979 + }, 7980 + { 7981 + "id": 330, 7982 + "from_node_id": 328, 7983 + "to_node_id": 329, 7984 + "from_change_id": "7823be1a-fca9-4cb5-9e62-dfbc8cb71e55", 7985 + "to_change_id": "c839ec54-b098-4030-8ff4-857549b17363", 7986 + "edge_type": "leads_to", 7987 + "weight": 1.0, 7988 + "rationale": "Analysis of what went wrong during graph maintenance", 7989 + "created_at": "2025-12-27T15:40:25.442264900-05:00" 7990 + }, 7991 + { 7992 + "id": 331, 7993 + "from_node_id": 329, 7994 + "to_node_id": 330, 7995 + "from_change_id": "c839ec54-b098-4030-8ff4-857549b17363", 7996 + "to_change_id": "1f554b87-3775-450b-a3a1-b23eeebc7e38", 7997 + "edge_type": "leads_to", 7998 + "weight": 1.0, 7999 + "rationale": "Action to prevent future graph integrity issues", 8000 + "created_at": "2025-12-27T15:41:06.239618300-05:00" 8001 + }, 8002 + { 8003 + "id": 332, 8004 + "from_node_id": 330, 8005 + "to_node_id": 331, 8006 + "from_change_id": "1f554b87-3775-450b-a3a1-b23eeebc7e38", 8007 + "to_change_id": "8c746dd6-d571-4446-8a53-af6279fc9c21", 8008 + "edge_type": "leads_to", 8009 + "weight": 1.0, 8010 + "rationale": "Successfully completed documentation updates", 8011 + "created_at": "2025-12-27T15:47:51.427087400-05:00" 8012 + }, 8013 + { 8014 + "id": 333, 8015 + "from_node_id": 331, 8016 + "to_node_id": 332, 8017 + "from_change_id": "8c746dd6-d571-4446-8a53-af6279fc9c21", 8018 + "to_change_id": "c4338df4-a22f-4dd5-b60c-84c7cd1c0c5c", 8019 + "edge_type": "leads_to", 8020 + "weight": 1.0, 8021 + "rationale": "Git commit documenting the improvements", 8022 + "created_at": "2025-12-27T15:48:49.907152400-05:00" 8023 + }, 8024 + { 8025 + "id": 334, 8026 + "from_node_id": 328, 8027 + "to_node_id": 333, 8028 + "from_change_id": "7823be1a-fca9-4cb5-9e62-dfbc8cb71e55", 8029 + "to_change_id": "0a0375e9-bcef-4459-b9f1-f5868276e8e4", 8030 + "edge_type": "leads_to", 8031 + "weight": 1.0, 8032 + "rationale": "New goal from user request", 8033 + "created_at": "2025-12-27T15:50:58.493301500-05:00" 8034 + }, 8035 + { 8036 + "id": 335, 8037 + "from_node_id": 333, 8038 + "to_node_id": 334, 8039 + "from_change_id": "0a0375e9-bcef-4459-b9f1-f5868276e8e4", 8040 + "to_change_id": "fe108b87-356f-4c02-85cb-7260e175d8ad", 8041 + "edge_type": "leads_to", 8042 + "weight": 1.0, 8043 + "rationale": "First step to review markdown files", 8044 + "created_at": "2025-12-27T15:51:25.165313400-05:00" 8045 + }, 8046 + { 8047 + "id": 336, 8048 + "from_node_id": 334, 8049 + "to_node_id": 335, 8050 + "from_change_id": "fe108b87-356f-4c02-85cb-7260e175d8ad", 8051 + "to_change_id": "3aac85f7-c11c-48f6-b9da-2cd333605fb2", 8052 + "edge_type": "leads_to", 8053 + "weight": 1.0, 8054 + "rationale": "Analysis complete with findings", 8055 + "created_at": "2025-12-27T15:52:08.782592-05:00" 8056 + }, 8057 + { 8058 + "id": 337, 8059 + "from_node_id": 335, 8060 + "to_node_id": 336, 8061 + "from_change_id": "3aac85f7-c11c-48f6-b9da-2cd333605fb2", 8062 + "to_change_id": "d1a23826-c660-4f2a-bdc0-bcbbce9d0293", 8063 + "edge_type": "leads_to", 8064 + "weight": 1.0, 8065 + "rationale": "Need to decide update approach", 8066 + "created_at": "2025-12-27T15:52:32.515520400-05:00" 8067 + }, 8068 + { 8069 + "id": 338, 8070 + "from_node_id": 336, 8071 + "to_node_id": 337, 8072 + "from_change_id": "d1a23826-c660-4f2a-bdc0-bcbbce9d0293", 8073 + "to_change_id": "28eeefda-3813-4777-8006-924a9b030c61", 8074 + "edge_type": "leads_to", 8075 + "weight": 1.0, 8076 + "rationale": "User decision", 8077 + "created_at": "2025-12-27T15:54:33.702061900-05:00" 8078 + }, 8079 + { 8080 + "id": 339, 8081 + "from_node_id": 337, 8082 + "to_node_id": 338, 8083 + "from_change_id": "28eeefda-3813-4777-8006-924a9b030c61", 8084 + "to_change_id": "594942d8-4981-4557-9687-522d51e86ecb", 8085 + "edge_type": "leads_to", 8086 + "weight": 1.0, 8087 + "rationale": "First file to update", 8088 + "created_at": "2025-12-27T15:54:38.126450100-05:00" 8089 + }, 8090 + { 8091 + "id": 340, 8092 + "from_node_id": 337, 8093 + "to_node_id": 339, 8094 + "from_change_id": "28eeefda-3813-4777-8006-924a9b030c61", 8095 + "to_change_id": "4c8c5b0d-468b-4ad6-80e9-02141949aba9", 8096 + "edge_type": "leads_to", 8097 + "weight": 1.0, 8098 + "rationale": "Second file to update", 8099 + "created_at": "2025-12-27T15:55:51.716239-05:00" 8100 + }, 8101 + { 8102 + "id": 341, 8103 + "from_node_id": 337, 8104 + "to_node_id": 340, 8105 + "from_change_id": "28eeefda-3813-4777-8006-924a9b030c61", 8106 + "to_change_id": "4e3987a4-538f-4912-b6ce-39c5971e0966", 8107 + "edge_type": "leads_to", 8108 + "weight": 1.0, 8109 + "rationale": "Third file to update", 8110 + "created_at": "2025-12-27T15:57:16.830452200-05:00" 8111 + }, 8112 + { 8113 + "id": 342, 8114 + "from_node_id": 337, 8115 + "to_node_id": 341, 8116 + "from_change_id": "28eeefda-3813-4777-8006-924a9b030c61", 8117 + "to_change_id": "42bf8d79-2c24-420f-b8b8-89273fecc30d", 8118 + "edge_type": "leads_to", 8119 + "weight": 1.0, 8120 + "rationale": "Fourth and final file to update", 8121 + "created_at": "2025-12-27T15:58:25.682627400-05:00" 8122 + }, 8123 + { 8124 + "id": 343, 8125 + "from_node_id": 337, 8126 + "to_node_id": 342, 8127 + "from_change_id": "28eeefda-3813-4777-8006-924a9b030c61", 8128 + "to_change_id": "a6d1f3fb-650d-4227-b1dc-ddb24810464c", 8129 + "edge_type": "leads_to", 8130 + "weight": 1.0, 8131 + "rationale": "All updates completed successfully", 8132 + "created_at": "2025-12-27T15:59:43.630208500-05:00" 8133 + }, 8134 + { 8135 + "id": 344, 8136 + "from_node_id": 342, 8137 + "to_node_id": 343, 8138 + "from_change_id": "a6d1f3fb-650d-4227-b1dc-ddb24810464c", 8139 + "to_change_id": "9e0fcead-ea30-4b31-974b-4e07f7fc6787", 8140 + "edge_type": "leads_to", 8141 + "weight": 1.0, 8142 + "rationale": "Git commit with all documentation updates", 8143 + "created_at": "2025-12-27T16:02:15.712335700-05:00" 8144 + }, 8145 + { 8146 + "id": 345, 8147 + "from_node_id": 344, 8148 + "to_node_id": 345, 8149 + "from_change_id": "2a06900e-ea62-4adf-81d5-7f0cf1a29b31", 8150 + "to_change_id": "0ef352ed-538b-4632-8b62-ebb17603f944", 8151 + "edge_type": "leads_to", 8152 + "weight": 1.0, 8153 + "rationale": "Installation step for Tailwind integration", 8154 + "created_at": "2025-12-27T18:00:42.787737600-05:00" 8155 + }, 8156 + { 8157 + "id": 346, 8158 + "from_node_id": 344, 8159 + "to_node_id": 346, 8160 + "from_change_id": "2a06900e-ea62-4adf-81d5-7f0cf1a29b31", 8161 + "to_change_id": "888e6ad0-5002-4cdb-b35e-f4214ca07dfa", 8162 + "edge_type": "leads_to", 8163 + "weight": 1.0, 8164 + "rationale": "Configuration step for Tailwind", 8165 + "created_at": "2025-12-27T18:01:28.695956-05:00" 8166 + }, 8167 + { 8168 + "id": 347, 8169 + "from_node_id": 344, 8170 + "to_node_id": 347, 8171 + "from_change_id": "2a06900e-ea62-4adf-81d5-7f0cf1a29b31", 8172 + "to_change_id": "fae7a634-d921-4b6f-9620-0c58d88b863e", 8173 + "edge_type": "leads_to", 8174 + "weight": 1.0, 8175 + "rationale": "Build process integration", 8176 + "created_at": "2025-12-27T18:01:51.815468700-05:00" 8177 + }, 8178 + { 8179 + "id": 348, 8180 + "from_node_id": 344, 8181 + "to_node_id": 348, 8182 + "from_change_id": "2a06900e-ea62-4adf-81d5-7f0cf1a29b31", 8183 + "to_change_id": "c25a8f4b-8bf1-4a33-bef9-3731dfd83627", 8184 + "edge_type": "leads_to", 8185 + "weight": 1.0, 8186 + "rationale": "CSS conversion step", 8187 + "created_at": "2025-12-27T18:02:43.312580-05:00" 8188 + }, 8189 + { 8190 + "id": 349, 8191 + "from_node_id": 344, 8192 + "to_node_id": 349, 8193 + "from_change_id": "2a06900e-ea62-4adf-81d5-7f0cf1a29b31", 8194 + "to_change_id": "c65ee3d9-62a0-47aa-870a-f6422ff2536a", 8195 + "edge_type": "leads_to", 8196 + "weight": 1.0, 8197 + "rationale": "HTML conversion step", 8198 + "created_at": "2025-12-27T18:03:01.642571400-05:00" 8199 + }, 8200 + { 8201 + "id": 350, 8202 + "from_node_id": 344, 8203 + "to_node_id": 350, 8204 + "from_change_id": "2a06900e-ea62-4adf-81d5-7f0cf1a29b31", 8205 + "to_change_id": "8136e615-5baa-4fe5-9a7d-d672ff1a6f85", 8206 + "edge_type": "leads_to", 8207 + "weight": 1.0, 8208 + "rationale": "Final outcome of Tailwind integration", 8209 + "created_at": "2025-12-27T18:07:51.011406300-05:00" 8210 + }, 8211 + { 8212 + "id": 351, 8213 + "from_node_id": 344, 8214 + "to_node_id": 351, 8215 + "from_change_id": "2a06900e-ea62-4adf-81d5-7f0cf1a29b31", 8216 + "to_change_id": "9468bcb3-78ec-4dae-8d8f-968ba6f5b3fe", 8217 + "edge_type": "leads_to", 8218 + "weight": 1.0, 8219 + "rationale": "Git commit for Tailwind integration", 8220 + "created_at": "2025-12-27T18:38:58.347778400-05:00" 8221 + }, 8222 + { 8223 + "id": 352, 8224 + "from_node_id": 352, 8225 + "to_node_id": 353, 8226 + "from_change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5", 8227 + "to_change_id": "eaed6e9b-9f16-4b45-8783-44ea2ea1f2a9", 8228 + "edge_type": "leads_to", 8229 + "weight": 1.0, 8230 + "rationale": "Initial analysis of issues", 8231 + "created_at": "2025-12-27T22:06:21.516165300-05:00" 8232 + }, 8233 + { 8234 + "id": 353, 8235 + "from_node_id": 352, 8236 + "to_node_id": 354, 8237 + "from_change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5", 8238 + "to_change_id": "d66fc83e-9737-4047-8ce2-e2ba857aeea9", 8239 + "edge_type": "leads_to", 8240 + "weight": 1.0, 8241 + "rationale": "Need to decide dark mode approach", 8242 + "created_at": "2025-12-27T22:07:03.103941500-05:00" 8243 + }, 8244 + { 8245 + "id": 354, 8246 + "from_node_id": 354, 8247 + "to_node_id": 355, 8248 + "from_change_id": "d66fc83e-9737-4047-8ce2-e2ba857aeea9", 8249 + "to_change_id": "76e2a379-7803-4c82-8013-be6b62f2d360", 8250 + "edge_type": "leads_to", 8251 + "weight": 1.0, 8252 + "rationale": "Decision outcome", 8253 + "created_at": "2025-12-27T22:07:06.239151500-05:00" 8254 + }, 8255 + { 8256 + "id": 355, 8257 + "from_node_id": 352, 8258 + "to_node_id": 356, 8259 + "from_change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5", 8260 + "to_change_id": "df681aa8-e470-4ead-a0d2-a4095febfa3d", 8261 + "edge_type": "leads_to", 8262 + "weight": 1.0, 8263 + "rationale": "Implementation of dark mode fix", 8264 + "created_at": "2025-12-27T22:07:26.713411300-05:00" 8265 + }, 8266 + { 8267 + "id": 356, 8268 + "from_node_id": 352, 8269 + "to_node_id": 357, 8270 + "from_change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5", 8271 + "to_change_id": "57060303-5a30-4f11-a752-a02376df5ea7", 8272 + "edge_type": "leads_to", 8273 + "weight": 1.0, 8274 + "rationale": "Implementation of server message fix", 8275 + "created_at": "2025-12-27T22:07:51.662925600-05:00" 8276 + }, 8277 + { 8278 + "id": 357, 8279 + "from_node_id": 352, 8280 + "to_node_id": 358, 8281 + "from_change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5", 8282 + "to_change_id": "fc211ac7-7a1a-4b69-835a-992c354e8237", 8283 + "edge_type": "leads_to", 8284 + "weight": 1.0, 8285 + "rationale": "Final outcome of fixes", 8286 + "created_at": "2025-12-27T22:09:30.425884400-05:00" 8287 + }, 8288 + { 8289 + "id": 358, 8290 + "from_node_id": 352, 8291 + "to_node_id": 359, 8292 + "from_change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5", 8293 + "to_change_id": "4a7d5885-1713-4ba7-ad13-bb12b58c9410", 8294 + "edge_type": "leads_to", 8295 + "weight": 1.0, 8296 + "rationale": "Git commit for fixes", 8297 + "created_at": "2025-12-27T22:10:27.225192300-05:00" 8298 + }, 8299 + { 8300 + "id": 359, 8301 + "from_node_id": 360, 8302 + "to_node_id": 361, 8303 + "from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a", 8304 + "to_change_id": "aecf2327-d20d-4c6c-b6b0-06ccf26a2b27", 8305 + "edge_type": "leads_to", 8306 + "weight": 1.0, 8307 + "rationale": "Root cause analysis", 8308 + "created_at": "2025-12-27T22:23:47.445630900-05:00" 8309 + }, 8310 + { 8311 + "id": 360, 8312 + "from_node_id": 360, 8313 + "to_node_id": 362, 8314 + "from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a", 8315 + "to_change_id": "e897db97-44d8-4993-b4c3-0d829265b2f8", 8316 + "edge_type": "leads_to", 8317 + "weight": 1.0, 8318 + "rationale": "Rebuilt dev version", 8319 + "created_at": "2025-12-27T22:24:19.438433600-05:00" 8320 + }, 8321 + { 8322 + "id": 361, 8323 + "from_node_id": 360, 8324 + "to_node_id": 363, 8325 + "from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a", 8326 + "to_change_id": "2c62bfa3-d148-4448-8c2b-d0cf1e94ceb0", 8327 + "edge_type": "leads_to", 8328 + "weight": 1.0, 8329 + "rationale": "Root cause: CORS configuration", 8330 + "created_at": "2025-12-27T22:24:53.741163700-05:00" 8331 + }, 8332 + { 8333 + "id": 362, 8334 + "from_node_id": 360, 8335 + "to_node_id": 364, 8336 + "from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a", 8337 + "to_change_id": "560d6bea-47ec-408d-919b-15ca7198aac9", 8338 + "edge_type": "leads_to", 8339 + "weight": 1.0, 8340 + "rationale": "Implementation of CORS fix", 8341 + "created_at": "2025-12-27T22:25:24.843330900-05:00" 8342 + }, 8343 + { 8344 + "id": 363, 8345 + "from_node_id": 360, 8346 + "to_node_id": 365, 8347 + "from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a", 8348 + "to_change_id": "3ef0c9e9-aa40-4914-a5f4-32bcfaf68d04", 8349 + "edge_type": "leads_to", 8350 + "weight": 1.0, 8351 + "rationale": "CORS fix completed", 8352 + "created_at": "2025-12-27T22:41:44.160528300-05:00" 8353 + }, 8354 + { 8355 + "id": 364, 8356 + "from_node_id": 360, 8357 + "to_node_id": 366, 8358 + "from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a", 8359 + "to_change_id": "77b7ed7e-a113-41f6-a677-50d376f3f008", 8360 + "edge_type": "leads_to", 8361 + "weight": 1.0, 8362 + "rationale": "Git commit for CORS fixes", 8363 + "created_at": "2025-12-27T22:42:51.663598100-05:00" 8364 + }, 8365 + { 8366 + "id": 365, 8367 + "from_node_id": 367, 8368 + "to_node_id": 368, 8369 + "from_change_id": "df6abf7a-e7a4-45f3-8485-b933319416d9", 8370 + "to_change_id": "79721edf-aa05-4580-8c28-7d20941ef155", 8371 + "edge_type": "leads_to", 8372 + "weight": 1.0, 8373 + "rationale": "Analysis step for Firefox compatibility", 8374 + "created_at": "2025-12-28T18:10:09.484445500-05:00" 8375 + }, 8376 + { 8377 + "id": 366, 8378 + "from_node_id": 368, 8379 + "to_node_id": 369, 8380 + "from_change_id": "79721edf-aa05-4580-8c28-7d20941ef155", 8381 + "to_change_id": "783841d0-c096-48f6-be18-193a9dcc7d4b", 8382 + "edge_type": "leads_to", 8383 + "weight": 1.0, 8384 + "rationale": "Detailed analysis of compatibility issues", 8385 + "created_at": "2025-12-28T18:10:49.163552300-05:00" 8386 + }, 8387 + { 8388 + "id": 367, 8389 + "from_node_id": 369, 8390 + "to_node_id": 370, 8391 + "from_change_id": "783841d0-c096-48f6-be18-193a9dcc7d4b", 8392 + "to_change_id": "fd2d5b63-c26c-4592-89a6-3ccb4234c3c6", 8393 + "edge_type": "leads_to", 8394 + "weight": 1.0, 8395 + "rationale": "Need to decide implementation strategy", 8396 + "created_at": "2025-12-28T18:10:51.434960600-05:00" 8397 + }, 8398 + { 8399 + "id": 368, 8400 + "from_node_id": 370, 8401 + "to_node_id": 371, 8402 + "from_change_id": "fd2d5b63-c26c-4592-89a6-3ccb4234c3c6", 8403 + "to_change_id": "159906da-984f-4a1d-a1a6-98e0fc0cf369", 8404 + "edge_type": "leads_to", 8405 + "weight": 1.0, 8406 + "rationale": "Option A", 8407 + "created_at": "2025-12-28T18:11:07.060637-05:00" 8408 + }, 8409 + { 8410 + "id": 369, 8411 + "from_node_id": 370, 8412 + "to_node_id": 372, 8413 + "from_change_id": "fd2d5b63-c26c-4592-89a6-3ccb4234c3c6", 8414 + "to_change_id": "df5e42e6-53c1-4b30-8b6f-f2385cd9e247", 8415 + "edge_type": "leads_to", 8416 + "weight": 1.0, 8417 + "rationale": "Option B", 8418 + "created_at": "2025-12-28T18:11:09.223792400-05:00" 8419 + }, 8420 + { 8421 + "id": 370, 8422 + "from_node_id": 370, 8423 + "to_node_id": 373, 8424 + "from_change_id": "fd2d5b63-c26c-4592-89a6-3ccb4234c3c6", 8425 + "to_change_id": "7bb58202-7a9b-4e8b-8b9e-927e5106bce7", 8426 + "edge_type": "leads_to", 8427 + "weight": 1.0, 8428 + "rationale": "Option C", 8429 + "created_at": "2025-12-28T18:11:11.439827800-05:00" 8430 + }, 8431 + { 8432 + "id": 371, 8433 + "from_node_id": 370, 8434 + "to_node_id": 374, 8435 + "from_change_id": "fd2d5b63-c26c-4592-89a6-3ccb4234c3c6", 8436 + "to_change_id": "d41b29e0-cd48-4dac-a6c8-c6179612702e", 8437 + "edge_type": "leads_to", 8438 + "weight": 1.0, 8439 + "rationale": "User selected option 1", 8440 + "created_at": "2025-12-28T19:04:26.708742600-05:00" 8441 + }, 8442 + { 8443 + "id": 372, 8444 + "from_node_id": 374, 8445 + "to_node_id": 375, 8446 + "from_change_id": "d41b29e0-cd48-4dac-a6c8-c6179612702e", 8447 + "to_change_id": "5bb34b8b-aec4-4f84-993e-eb9bf7a2d13f", 8448 + "edge_type": "leads_to", 8449 + "weight": 1.0, 8450 + "rationale": "Implementation based on decision", 8451 + "created_at": "2025-12-28T19:08:16.677078600-05:00" 8452 + }, 8453 + { 8454 + "id": 373, 8455 + "from_node_id": 375, 8456 + "to_node_id": 376, 8457 + "from_change_id": "5bb34b8b-aec4-4f84-993e-eb9bf7a2d13f", 8458 + "to_change_id": "644181ee-5a44-4967-9657-e9dd5f648c5e", 8459 + "edge_type": "leads_to", 8460 + "weight": 1.0, 8461 + "rationale": "Implementation completed successfully", 8462 + "created_at": "2025-12-28T19:14:24.961595600-05:00" 8463 + }, 8464 + { 8465 + "id": 374, 8466 + "from_node_id": 377, 8467 + "to_node_id": 378, 8468 + "from_change_id": "1dffa024-413f-4a95-b069-66db350abfaa", 8469 + "to_change_id": "9d5626d2-a9ae-42aa-8fda-be3c7528156f", 8470 + "edge_type": "leads_to", 8471 + "weight": 1.0, 8472 + "rationale": "First observation about debugging", 8473 + "created_at": "2025-12-28T20:15:13.725635900-05:00" 8474 + }, 8475 + { 8476 + "id": 375, 8477 + "from_node_id": 378, 8478 + "to_node_id": 379, 8479 + "from_change_id": "9d5626d2-a9ae-42aa-8fda-be3c7528156f", 8480 + "to_change_id": "7a5af3fe-8567-4f1c-85cd-e47891704974", 8481 + "edge_type": "leads_to", 8482 + "weight": 1.0, 8483 + "rationale": "Hypothesis about root causes", 8484 + "created_at": "2025-12-28T20:15:33.187041700-05:00" 8485 + }, 8486 + { 8487 + "id": 376, 8488 + "from_node_id": 379, 8489 + "to_node_id": 380, 8490 + "from_change_id": "7a5af3fe-8567-4f1c-85cd-e47891704974", 8491 + "to_change_id": "9c197aae-18d5-46ae-87e7-82c240c8f313", 8492 + "edge_type": "leads_to", 8493 + "weight": 1.0, 8494 + "rationale": "Fix based on hypothesis", 8495 + "created_at": "2025-12-28T20:16:14.104406300-05:00" 8496 + }, 8497 + { 8498 + "id": 377, 8499 + "from_node_id": 380, 8500 + "to_node_id": 381, 8501 + "from_change_id": "9c197aae-18d5-46ae-87e7-82c240c8f313", 8502 + "to_change_id": "485a03b0-8a25-4fdf-a8e2-9d3a25c8edf8", 8503 + "edge_type": "leads_to", 8504 + "weight": 1.0, 8505 + "rationale": "Fix implemented and tested", 8506 + "created_at": "2025-12-28T20:16:43.953511400-05:00" 8507 + }, 8508 + { 8509 + "id": 378, 8510 + "from_node_id": 381, 8511 + "to_node_id": 382, 8512 + "from_change_id": "485a03b0-8a25-4fdf-a8e2-9d3a25c8edf8", 8513 + "to_change_id": "35b13d37-0228-435f-a4bc-c5c42811fec3", 8514 + "edge_type": "leads_to", 8515 + "weight": 1.0, 8516 + "rationale": "Root cause identified from error logs", 8517 + "created_at": "2025-12-28T20:17:25.488041200-05:00" 8518 + }, 8519 + { 8520 + "id": 379, 8521 + "from_node_id": 382, 8522 + "to_node_id": 383, 8523 + "from_change_id": "35b13d37-0228-435f-a4bc-c5c42811fec3", 8524 + "to_change_id": "adc120cd-e56d-400a-9b3e-8207880378c3", 8525 + "edge_type": "leads_to", 8526 + "weight": 1.0, 8527 + "rationale": "Fix for CORS issue", 8528 + "created_at": "2025-12-28T20:19:41.484076700-05:00" 8529 + }, 8530 + { 8531 + "id": 380, 8532 + "from_node_id": 383, 8533 + "to_node_id": 384, 8534 + "from_change_id": "adc120cd-e56d-400a-9b3e-8207880378c3", 8535 + "to_change_id": "0f77bfd9-590f-4f1e-be08-78a9deef6d8a", 8536 + "edge_type": "leads_to", 8537 + "weight": 1.0, 8538 + "rationale": "Implementation complete", 8539 + "created_at": "2025-12-28T20:19:56.872404900-05:00" 8540 + }, 8541 + { 8542 + "id": 381, 8543 + "from_node_id": 384, 8544 + "to_node_id": 385, 8545 + "from_change_id": "0f77bfd9-590f-4f1e-be08-78a9deef6d8a", 8546 + "to_change_id": "cc0910f0-2381-4aee-bb5d-397cb0f804d1", 8547 + "edge_type": "leads_to", 8548 + "weight": 1.0, 8549 + "rationale": "New error reveals real issue", 8550 + "created_at": "2025-12-28T20:27:34.035766400-05:00" 8551 + }, 8552 + { 8553 + "id": 382, 8554 + "from_node_id": 385, 8555 + "to_node_id": 386, 8556 + "from_change_id": "cc0910f0-2381-4aee-bb5d-397cb0f804d1", 8557 + "to_change_id": "ad4a5ca7-15d1-4776-8ede-6b615613f6e1", 8558 + "edge_type": "leads_to", 8559 + "weight": 1.0, 8560 + "rationale": "Fix for Firefox extension origin", 8561 + "created_at": "2025-12-28T20:28:33.839045700-05:00" 8562 + }, 8563 + { 8564 + "id": 383, 8565 + "from_node_id": 386, 8566 + "to_node_id": 387, 8567 + "from_change_id": "ad4a5ca7-15d1-4776-8ede-6b615613f6e1", 8568 + "to_change_id": "cffdee0f-8535-4d88-83ed-fdf6101f7ac3", 8569 + "edge_type": "leads_to", 8570 + "weight": 1.0, 8571 + "rationale": "Complete fix implemented", 8572 + "created_at": "2025-12-28T20:30:09.745415200-05:00" 8573 + }, 8574 + { 8575 + "id": 384, 8576 + "from_node_id": 387, 8577 + "to_node_id": 388, 8578 + "from_change_id": "cffdee0f-8535-4d88-83ed-fdf6101f7ac3", 8579 + "to_change_id": "0ada864e-be98-4a2f-a14e-ffd3eea9aaa9", 8580 + "edge_type": "leads_to", 8581 + "weight": 1.0, 8582 + "rationale": "New issue discovered in health check", 8583 + "created_at": "2025-12-28T20:37:24.355885500-05:00" 8584 + }, 8585 + { 8586 + "id": 385, 8587 + "from_node_id": 388, 8588 + "to_node_id": 389, 8589 + "from_change_id": "0ada864e-be98-4a2f-a14e-ffd3eea9aaa9", 8590 + "to_change_id": "f522d5b2-c325-4f34-9f27-b8ea5c50618d", 8591 + "edge_type": "leads_to", 8592 + "weight": 1.0, 8593 + "rationale": "Fix implemented", 8594 + "created_at": "2025-12-28T20:38:22.044029100-05:00" 8595 + }, 8596 + { 8597 + "id": 386, 8598 + "from_node_id": 389, 8599 + "to_node_id": 390, 8600 + "from_change_id": "f522d5b2-c325-4f34-9f27-b8ea5c50618d", 8601 + "to_change_id": "cfdcf45b-47b3-4239-8053-417bd31957ed", 8602 + "edge_type": "leads_to", 8603 + "weight": 1.0, 8604 + "rationale": "Issue persists - need to debug headers", 8605 + "created_at": "2025-12-28T20:48:14.949702100-05:00" 8606 + }, 8607 + { 8608 + "id": 387, 8609 + "from_node_id": 390, 8610 + "to_node_id": 391, 8611 + "from_change_id": "cfdcf45b-47b3-4239-8053-417bd31957ed", 8612 + "to_change_id": "2b53a419-9a47-4285-9a12-9bdfaeeb9ff0", 8613 + "edge_type": "leads_to", 8614 + "weight": 1.0, 8615 + "rationale": "Root cause identified from debug logs", 8616 + "created_at": "2025-12-28T20:55:34.094943700-05:00" 8617 + }, 8618 + { 8619 + "id": 388, 8620 + "from_node_id": 391, 8621 + "to_node_id": 392, 8622 + "from_change_id": "2b53a419-9a47-4285-9a12-9bdfaeeb9ff0", 8623 + "to_change_id": "c941d136-3405-483d-bf34-7fb011f6d072", 8624 + "edge_type": "leads_to", 8625 + "weight": 1.0, 8626 + "rationale": "Fix implemented", 8627 + "created_at": "2025-12-28T20:57:35.872426900-05:00" 8628 + }, 8629 + { 8630 + "id": 389, 8631 + "from_node_id": 392, 8632 + "to_node_id": 393, 8633 + "from_change_id": "c941d136-3405-483d-bf34-7fb011f6d072", 8634 + "to_change_id": "aafd9977-8800-4152-9f7f-b817db6df573", 8635 + "edge_type": "leads_to", 8636 + "weight": 1.0, 8637 + "rationale": "Complete fix with cleanup", 8638 + "created_at": "2025-12-28T21:37:27.704906300-05:00" 8639 + }, 8640 + { 8641 + "id": 390, 8642 + "from_node_id": 393, 8643 + "to_node_id": 394, 8644 + "from_change_id": "aafd9977-8800-4152-9f7f-b817db6df573", 8645 + "to_change_id": "3b0dea7a-c3cd-45a8-ba1a-f1040aa4e1d9", 8646 + "edge_type": "leads_to", 8647 + "weight": 1.0, 8648 + "rationale": "New issue - cookie partitioning", 8649 + "created_at": "2025-12-28T21:46:48.417911400-05:00" 8650 + }, 8651 + { 8652 + "id": 391, 8653 + "from_node_id": 394, 8654 + "to_node_id": 395, 8655 + "from_change_id": "3b0dea7a-c3cd-45a8-ba1a-f1040aa4e1d9", 8656 + "to_change_id": "8a93413f-a09c-4cc1-8693-4fe90dc055c4", 8657 + "edge_type": "leads_to", 8658 + "weight": 1.0, 8659 + "rationale": "Workaround using browser.cookies API", 8660 + "created_at": "2025-12-28T21:52:52.704792400-05:00" 8661 + }, 8662 + { 8663 + "id": 392, 8664 + "from_node_id": 395, 8665 + "to_node_id": 396, 8666 + "from_change_id": "8a93413f-a09c-4cc1-8693-4fe90dc055c4", 8667 + "to_change_id": "864dd973-5f15-4e31-a7da-c548dbbe1f0e", 8668 + "edge_type": "leads_to", 8669 + "weight": 1.0, 8670 + "rationale": "Complete workaround", 8671 + "created_at": "2025-12-28T22:51:33.159870400-05:00" 8672 } 8673 ] 8674 }
+173
packages/extension/FIREFOX.md
···
··· 1 + # Firefox Extension Installation Guide 2 + 3 + The ATlast Importer extension now supports both Chrome and Firefox! 4 + 5 + ## Building for Firefox 6 + 7 + The build system automatically creates both Chrome and Firefox versions: 8 + 9 + ```bash 10 + pnpm run build # Development build for both browsers 11 + pnpm run build:prod # Production build for both browsers 12 + ``` 13 + 14 + Output directories: 15 + - `dist/chrome/` - Chrome/Edge version (Manifest V3 with service worker) 16 + - `dist/firefox/` - Firefox version (Manifest V3 with scripts array) 17 + 18 + ## Installing in Firefox (Development) 19 + 20 + ### Option 1: Temporary Installation (for testing) 21 + 22 + 1. Open Firefox 23 + 2. Navigate to `about:debugging#/runtime/this-firefox` 24 + 3. Click "Load Temporary Add-on..." 25 + 4. Navigate to `packages/extension/dist/firefox/` 26 + 5. Select the `manifest.json` file 27 + 28 + **Note:** Temporary extensions are removed when Firefox restarts. 29 + 30 + ### Option 2: Loading from ZIP (for distribution) 31 + 32 + 1. Build the production version: 33 + ```bash 34 + pnpm run build:prod 35 + pnpm run package:firefox 36 + ``` 37 + 38 + 2. This creates `dist/firefox.zip` 39 + 40 + 3. For testing: 41 + - Go to `about:debugging#/runtime/this-firefox` 42 + - Click "Load Temporary Add-on..." 43 + - Select the `firefox.zip` file 44 + 45 + 4. For publishing: 46 + - Submit `firefox.zip` to [addons.mozilla.org](https://addons.mozilla.org/developers/) 47 + 48 + ## Key Differences from Chrome Version 49 + 50 + ### Manifest Differences 51 + 52 + **Chrome (`manifest.chrome.json`):** 53 + ```json 54 + { 55 + "manifest_version": 3, 56 + "background": { 57 + "service_worker": "background/service-worker.js", 58 + "type": "module" 59 + } 60 + } 61 + ``` 62 + 63 + **Firefox (`manifest.firefox.json`):** 64 + ```json 65 + { 66 + "manifest_version": 3, 67 + "background": { 68 + "scripts": ["background/service-worker.js"], 69 + "type": "module" 70 + }, 71 + "browser_specific_settings": { 72 + "gecko": { 73 + "id": "atlast-importer@byarielm.fyi", 74 + "strict_min_version": "109.0" 75 + } 76 + } 77 + } 78 + ``` 79 + 80 + ### Cross-Browser Compatibility 81 + 82 + - All code uses `webextension-polyfill` library 83 + - Chrome-specific `chrome.*` APIs replaced with unified `browser.*` API 84 + - Promise-based instead of callback-based 85 + - Single codebase works across both browsers 86 + 87 + ### Requirements 88 + 89 + - **Firefox:** Version 109+ (for Manifest V3 support) 90 + - **Chrome/Edge:** Latest version 91 + 92 + ## Testing 93 + 94 + After loading the extension in Firefox: 95 + 96 + 1. Navigate to Twitter/X Following page (e.g., `https://twitter.com/username/following`) 97 + 2. Click the extension icon in the toolbar 98 + 3. The popup should show "Ready to scan" state 99 + 4. Click "Start Scan" to scrape usernames 100 + 5. Click "Open on ATlast" to upload results 101 + 102 + ## Debugging 103 + 104 + ### View Console Logs 105 + 106 + **Background Script:** 107 + - Go to `about:debugging#/runtime/this-firefox` 108 + - Find "ATlast Importer" in the list 109 + - Click "Inspect" 110 + 111 + **Popup:** 112 + - Right-click extension icon → "Inspect Extension" 113 + 114 + **Content Script:** 115 + - Open DevTools on Twitter/X page (F12) 116 + - Look for `[ATlast]` prefixed logs in Console 117 + 118 + ### Common Issues 119 + 120 + 1. **Extension not loading:** 121 + - Check Firefox version is 109+ 122 + - Ensure manifest.json is valid 123 + - Check browser console for errors 124 + 125 + 2. **Scan not starting:** 126 + - Verify you're on Twitter/X Following page 127 + - Check content script is injected (look for console logs) 128 + - Ensure page is fully loaded 129 + 130 + 3. **"Server offline" message:** 131 + - Make sure dev server is running (`netlify dev`) 132 + - Check API URL in extension settings 133 + 134 + ## Packaging for Distribution 135 + 136 + Create production builds for both browsers: 137 + 138 + ```bash 139 + pnpm run package:prod 140 + ``` 141 + 142 + This creates: 143 + - `dist/chrome.zip` - Ready for Chrome Web Store 144 + - `dist/firefox.zip` - Ready for Firefox Add-ons 145 + 146 + ## Development Workflow 147 + 148 + ```bash 149 + # Watch mode (auto-rebuild on changes) 150 + pnpm run dev 151 + 152 + # In Firefox: 153 + # 1. about:debugging → Reload extension after each rebuild 154 + # 2. Or use web-ext for auto-reload: 155 + 156 + npx web-ext run --source-dir=dist/firefox 157 + ``` 158 + 159 + ## Differences You Might Notice 160 + 161 + 1. **Background page persistence:** 162 + - Chrome: Service worker (non-persistent) 163 + - Firefox: Scripts array (similar behavior in MV3) 164 + 165 + 2. **API behavior:** 166 + - Firefox: Native Promise support 167 + - Chrome: Promises via polyfill 168 + 169 + 3. **Extension ID:** 170 + - Chrome: Auto-generated 171 + - Firefox: Explicitly set as `atlast-importer@byarielm.fyi` 172 + 173 + Both versions use the same source code and should behave identically!
+35 -2
packages/extension/README.md
··· 4 5 ## Development 6 7 ### Build Extension 8 9 ```bash 10 cd packages/extension 11 pnpm install 12 - pnpm run build 13 ``` 14 15 The built extension will be in `dist/chrome/`. ··· 23 5. The extension should now appear in your extensions list 24 25 ### Testing the Extension 26 27 #### Step 1: Navigate to Twitter Following Page 28 ··· 89 90 #### Common Issues 91 92 **Issue: Popup shows "Go to x.com/following" even when on following page** 93 94 Possible causes: ··· 120 For production deployment (Chrome Web Store): 121 122 ```bash 123 - pnpm run build 124 cd dist/chrome 125 zip -r ../chrome.zip . 126 ``` 127 128 Upload `dist/chrome.zip` to Chrome Web Store. 129 130 ## Architecture 131
··· 4 5 ## Development 6 7 + **Prerequisites:** 8 + - ATlast dev server must be running at `http://127.0.0.1:8888` 9 + - You must be logged in to ATlast before using the extension 10 + 11 ### Build Extension 12 13 ```bash 14 + # From project root: 15 cd packages/extension 16 pnpm install 17 + pnpm run build # Dev build (uses http://127.0.0.1:8888) 18 + pnpm run build:prod # Production build (uses https://atlast.byarielm.fyi) 19 ``` 20 21 The built extension will be in `dist/chrome/`. ··· 29 5. The extension should now appear in your extensions list 30 31 ### Testing the Extension 32 + 33 + #### Step 0: Start ATlast Dev Server 34 + 35 + ```bash 36 + # From project root: 37 + npx netlify-cli dev --filter @atlast/web 38 + # Server will start at http://127.0.0.1:8888 39 + ``` 40 + 41 + Then open `http://127.0.0.1:8888` and log in with your Bluesky handle. 42 43 #### Step 1: Navigate to Twitter Following Page 44 ··· 105 106 #### Common Issues 107 108 + **Issue: Extension shows "Not logged in to ATlast"** 109 + 110 + Solution: 111 + 1. Open `http://127.0.0.1:8888` in a new tab 112 + 2. Log in with your Bluesky handle 113 + 3. Return to extension and click "Check Again" 114 + 115 + **Issue: Extension shows "ATlast server not running"** 116 + 117 + Solution: 118 + 1. Start dev server: `npx netlify-cli dev --filter @atlast/web` 119 + 2. Wait for server to start at `http://127.0.0.1:8888` 120 + 3. Click "Check Again" in extension 121 + 122 **Issue: Popup shows "Go to x.com/following" even when on following page** 123 124 Possible causes: ··· 150 For production deployment (Chrome Web Store): 151 152 ```bash 153 + cd packages/extension 154 + pnpm run build:prod # Uses production API URL 155 cd dist/chrome 156 zip -r ../chrome.zip . 157 ``` 158 159 Upload `dist/chrome.zip` to Chrome Web Store. 160 + 161 + **Note:** Production build connects to `https://atlast.byarielm.fyi` instead of local dev server. 162 163 ## Architecture 164
+93 -40
packages/extension/build.js
··· 2 import * as fs from 'fs'; 3 import * as path from 'path'; 4 import { fileURLToPath } from 'url'; 5 6 const __dirname = path.dirname(fileURLToPath(import.meta.url)); 7 ··· 18 console.log(`🔗 API URL: ${ATLAST_API_URL}`); 19 20 // Clean dist directory 21 - const distDir = path.join(__dirname, 'dist', 'chrome'); 22 - if (fs.existsSync(distDir)) { 23 - fs.rmSync(distDir, { recursive: true }); 24 } 25 - fs.mkdirSync(distDir, { recursive: true }); 26 27 // Build configuration base 28 const buildConfigBase = { ··· 35 '__ATLAST_API_URL__': JSON.stringify(ATLAST_API_URL), 36 '__BUILD_MODE__': JSON.stringify(mode), 37 }, 38 }; 39 40 - // Build scripts 41 - const scripts = [ 42 - { 43 - ...buildConfigBase, 44 - entryPoints: ['src/content/index.ts'], 45 - outfile: path.join(distDir, 'content', 'index.js'), 46 - }, 47 - { 48 - ...buildConfigBase, 49 - entryPoints: ['src/background/service-worker.ts'], 50 - outfile: path.join(distDir, 'background', 'service-worker.js'), 51 - }, 52 - { 53 - ...buildConfigBase, 54 - entryPoints: ['src/popup/popup.ts'], 55 - outfile: path.join(distDir, 'popup', 'popup.js'), 56 - }, 57 - ]; 58 59 // Build function 60 async function build() { 61 try { 62 - console.log('🔨 Building extension...'); 63 64 - // Build all scripts 65 - for (const config of scripts) { 66 - if (watch) { 67 - const ctx = await esbuild.context(config); 68 - await ctx.watch(); 69 - console.log(`👀 Watching ${path.basename(config.entryPoints[0])}...`); 70 - } else { 71 - await esbuild.build(config); 72 - console.log(`✅ Built ${path.basename(config.entryPoints[0])}`); 73 } 74 } 75 76 - // Copy static files 77 - copyStaticFiles(); 78 - 79 if (!watch) { 80 - console.log('✨ Build complete!'); 81 } 82 } catch (error) { 83 console.error('❌ Build failed:', error); ··· 85 } 86 } 87 88 // Copy static files 89 - function copyStaticFiles() { 90 const filesToCopy = [ 91 - { from: 'manifest.json', to: 'manifest.json' }, 92 { from: 'src/popup/popup.html', to: 'popup/popup.html' }, 93 - { from: 'src/popup/popup.css', to: 'popup/popup.css' }, 94 ]; 95 96 for (const file of filesToCopy) { 97 - const srcPath = path.join(__dirname, file.from); 98 const destPath = path.join(distDir, file.to); 99 100 // Create directory if it doesn't exist
··· 2 import * as fs from 'fs'; 3 import * as path from 'path'; 4 import { fileURLToPath } from 'url'; 5 + import postcss from 'postcss'; 6 + import tailwindcss from 'tailwindcss'; 7 + import autoprefixer from 'autoprefixer'; 8 9 const __dirname = path.dirname(fileURLToPath(import.meta.url)); 10 ··· 21 console.log(`🔗 API URL: ${ATLAST_API_URL}`); 22 23 // Clean dist directory 24 + const distBaseDir = path.join(__dirname, 'dist'); 25 + if (fs.existsSync(distBaseDir)) { 26 + fs.rmSync(distBaseDir, { recursive: true }); 27 } 28 + fs.mkdirSync(distBaseDir, { recursive: true }); 29 30 // Build configuration base 31 const buildConfigBase = { ··· 38 '__ATLAST_API_URL__': JSON.stringify(ATLAST_API_URL), 39 '__BUILD_MODE__': JSON.stringify(mode), 40 }, 41 + // Include webextension-polyfill in the bundle 42 + external: [], 43 }; 44 45 + // Build scripts for a specific browser 46 + function getScripts(browser) { 47 + const distDir = path.join(distBaseDir, browser); 48 + return [ 49 + { 50 + ...buildConfigBase, 51 + entryPoints: ['src/content/index.ts'], 52 + outfile: path.join(distDir, 'content', 'index.js'), 53 + }, 54 + { 55 + ...buildConfigBase, 56 + entryPoints: ['src/background/service-worker.ts'], 57 + outfile: path.join(distDir, 'background', 'service-worker.js'), 58 + }, 59 + { 60 + ...buildConfigBase, 61 + entryPoints: ['src/popup/popup.ts'], 62 + outfile: path.join(distDir, 'popup', 'popup.js'), 63 + }, 64 + ]; 65 + } 66 67 // Build function 68 async function build() { 69 try { 70 + console.log('🔨 Building extension for Chrome and Firefox...'); 71 72 + const browsers = ['chrome', 'firefox']; 73 + 74 + for (const browser of browsers) { 75 + console.log(`\n📦 Building ${browser} version...`); 76 + const scripts = getScripts(browser); 77 + 78 + // Build all scripts 79 + for (const config of scripts) { 80 + if (watch) { 81 + const ctx = await esbuild.context(config); 82 + await ctx.watch(); 83 + console.log(`👀 Watching ${browser}/${path.basename(config.entryPoints[0])}...`); 84 + } else { 85 + await esbuild.build(config); 86 + console.log(`✅ Built ${browser}/${path.basename(config.entryPoints[0])}`); 87 + } 88 } 89 + 90 + // Copy static files 91 + copyStaticFiles(browser); 92 + 93 + // Process CSS with Tailwind 94 + await processCSS(browser); 95 } 96 97 if (!watch) { 98 + console.log('\n✨ Build complete for both browsers!'); 99 } 100 } catch (error) { 101 console.error('❌ Build failed:', error); ··· 103 } 104 } 105 106 + // Process CSS with PostCSS (Tailwind + Autoprefixer) 107 + async function processCSS(browser) { 108 + const cssPath = path.join(__dirname, 'src/popup/popup.css'); 109 + const distDir = path.join(distBaseDir, browser); 110 + const outputPath = path.join(distDir, 'popup/popup.css'); 111 + 112 + const css = fs.readFileSync(cssPath, 'utf8'); 113 + 114 + // Import cssnano dynamically for production minification 115 + const plugins = [tailwindcss, autoprefixer]; 116 + if (isProd) { 117 + const cssnano = (await import('cssnano')).default; 118 + plugins.push(cssnano); 119 + } 120 + 121 + const result = await postcss(plugins).process(css, { 122 + from: cssPath, 123 + to: outputPath, 124 + }); 125 + 126 + // Create directory if it doesn't exist 127 + const destDir = path.dirname(outputPath); 128 + if (!fs.existsSync(destDir)) { 129 + fs.mkdirSync(destDir, { recursive: true }); 130 + } 131 + 132 + fs.writeFileSync(outputPath, result.css); 133 + console.log('🎨 Processed CSS with Tailwind'); 134 + } 135 + 136 // Copy static files 137 + function copyStaticFiles(browser) { 138 + const distDir = path.join(distBaseDir, browser); 139 + 140 const filesToCopy = [ 141 + { from: `manifest.${browser}.json`, to: 'manifest.json', fallback: 'manifest.json' }, 142 { from: 'src/popup/popup.html', to: 'popup/popup.html' }, 143 ]; 144 145 for (const file of filesToCopy) { 146 + // Try to use browser-specific file first, fall back to default 147 + let srcPath = path.join(__dirname, file.from); 148 + if (file.fallback && !fs.existsSync(srcPath)) { 149 + srcPath = path.join(__dirname, file.fallback); 150 + } 151 const destPath = path.join(distDir, file.to); 152 153 // Create directory if it doesn't exist
+44
packages/extension/manifest.chrome.json
···
··· 1 + { 2 + "manifest_version": 3, 3 + "name": "ATlast Importer", 4 + "version": "1.0.0", 5 + "description": "Import your Twitter/X follows to find them on Bluesky", 6 + "permissions": [ 7 + "activeTab", 8 + "storage" 9 + ], 10 + "host_permissions": [ 11 + "https://twitter.com/*", 12 + "https://x.com/*", 13 + "http://127.0.0.1:8888/*", 14 + "http://localhost:8888/*", 15 + "https://atlast.byarielm.fyi/*" 16 + ], 17 + "background": { 18 + "service_worker": "background/service-worker.js", 19 + "type": "module" 20 + }, 21 + "content_scripts": [ 22 + { 23 + "matches": [ 24 + "https://twitter.com/*", 25 + "https://x.com/*" 26 + ], 27 + "js": ["content/index.js"], 28 + "run_at": "document_idle" 29 + } 30 + ], 31 + "action": { 32 + "default_popup": "popup/popup.html", 33 + "default_icon": { 34 + "16": "assets/icon-16.png", 35 + "48": "assets/icon-48.png", 36 + "128": "assets/icon-128.png" 37 + } 38 + }, 39 + "icons": { 40 + "16": "assets/icon-16.png", 41 + "48": "assets/icon-48.png", 42 + "128": "assets/icon-128.png" 43 + } 44 + }
+51
packages/extension/manifest.firefox.json
···
··· 1 + { 2 + "manifest_version": 3, 3 + "name": "ATlast Importer", 4 + "version": "1.0.0", 5 + "description": "Import your Twitter/X follows to find them on Bluesky", 6 + "permissions": [ 7 + "activeTab", 8 + "storage", 9 + "cookies" 10 + ], 11 + "host_permissions": [ 12 + "https://twitter.com/*", 13 + "https://x.com/*", 14 + "http://127.0.0.1:8888/*", 15 + "http://localhost:8888/*", 16 + "https://atlast.byarielm.fyi/*" 17 + ], 18 + "background": { 19 + "scripts": ["background/service-worker.js"], 20 + "type": "module" 21 + }, 22 + "content_scripts": [ 23 + { 24 + "matches": [ 25 + "https://twitter.com/*", 26 + "https://x.com/*" 27 + ], 28 + "js": ["content/index.js"], 29 + "run_at": "document_idle" 30 + } 31 + ], 32 + "action": { 33 + "default_popup": "popup/popup.html", 34 + "default_icon": { 35 + "16": "assets/icon-16.png", 36 + "48": "assets/icon-48.png", 37 + "128": "assets/icon-128.png" 38 + } 39 + }, 40 + "icons": { 41 + "16": "assets/icon-16.png", 42 + "48": "assets/icon-48.png", 43 + "128": "assets/icon-128.png" 44 + }, 45 + "browser_specific_settings": { 46 + "gecko": { 47 + "id": "atlast-importer@byarielm.fyi", 48 + "strict_min_version": "109.0" 49 + } 50 + } 51 + }
+10 -2
packages/extension/package.json
··· 9 "build:prod": "node build.js --prod", 10 "dev": "node build.js --watch", 11 "package:chrome": "cd dist/chrome && zip -r ../chrome.zip .", 12 - "package:prod": "npm run build:prod && npm run package:chrome" 13 }, 14 "dependencies": { 15 - "@atlast/shared": "workspace:*" 16 }, 17 "devDependencies": { 18 "@types/chrome": "^0.0.256", 19 "esbuild": "^0.19.11", 20 "typescript": "^5.3.3" 21 } 22 }
··· 9 "build:prod": "node build.js --prod", 10 "dev": "node build.js --watch", 11 "package:chrome": "cd dist/chrome && zip -r ../chrome.zip .", 12 + "package:firefox": "cd dist/firefox && zip -r ../firefox.zip .", 13 + "package:all": "pnpm run package:chrome && pnpm run package:firefox", 14 + "package:prod": "pnpm run build:prod && pnpm run package:all" 15 }, 16 "dependencies": { 17 + "@atlast/shared": "workspace:*", 18 + "webextension-polyfill": "^0.12.0" 19 }, 20 "devDependencies": { 21 "@types/chrome": "^0.0.256", 22 + "@types/webextension-polyfill": "^0.12.4", 23 + "autoprefixer": "^10.4.23", 24 + "cssnano": "^7.1.2", 25 "esbuild": "^0.19.11", 26 + "postcss": "^8.5.6", 27 + "tailwindcss": "^3.4.19", 28 "typescript": "^5.3.3" 29 } 30 }
+7
packages/extension/postcss.config.js
···
··· 1 + export default { 2 + plugins: { 3 + tailwindcss: {}, 4 + autoprefixer: {}, 5 + ...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}), 6 + }, 7 + };
+2 -1
packages/extension/src/background/service-worker.ts
··· 1 import { 2 MessageType, 3 onMessage, ··· 150 /** 151 * Log extension installation 152 */ 153 - chrome.runtime.onInstalled.addListener((details) => { 154 console.log('[Background] Extension installed:', details.reason); 155 156 if (details.reason === 'install') {
··· 1 + import browser from 'webextension-polyfill'; 2 import { 3 MessageType, 4 onMessage, ··· 151 /** 152 * Log extension installation 153 */ 154 + browser.runtime.onInstalled.addListener((details) => { 155 console.log('[Background] Extension installed:', details.reason); 156 157 if (details.reason === 'install') {
+36 -9
packages/extension/src/lib/api-client.ts
··· 2 * ATlast API client for extension 3 */ 4 5 // These are replaced at build time by esbuild 6 declare const __ATLAST_API_URL__: string; 7 declare const __BUILD_MODE__: string; ··· 67 * Get extension version from manifest 68 */ 69 export function getExtensionVersion(): string { 70 - return chrome.runtime.getManifest().version; 71 } 72 73 /** ··· 76 */ 77 export async function checkServerHealth(): Promise<boolean> { 78 try { 79 - // Try to fetch the root URL with a short timeout 80 const controller = new AbortController(); 81 const timeoutId = setTimeout(() => controller.abort(), 3000); 82 83 - const response = await fetch(ATLAST_API_URL, { 84 - method: 'HEAD', 85 - signal: controller.signal 86 }); 87 88 clearTimeout(timeoutId); 89 90 - // Any response (even 404) means server is running 91 - return true; 92 } catch (error) { 93 console.error('[API Client] Server health check failed:', error); 94 return false; ··· 113 avatar?: string; 114 } | null> { 115 try { 116 - const response = await fetch(`${ATLAST_API_URL}/.netlify/functions/session`, { 117 method: 'GET', 118 - credentials: 'include', // Include cookies 119 headers: { 120 'Accept': 'application/json' 121 }
··· 2 * ATlast API client for extension 3 */ 4 5 + import browser from 'webextension-polyfill'; 6 + 7 // These are replaced at build time by esbuild 8 declare const __ATLAST_API_URL__: string; 9 declare const __BUILD_MODE__: string; ··· 69 * Get extension version from manifest 70 */ 71 export function getExtensionVersion(): string { 72 + return browser.runtime.getManifest().version; 73 } 74 75 /** ··· 78 */ 79 export async function checkServerHealth(): Promise<boolean> { 80 try { 81 + // Try to fetch the health endpoint with a short timeout 82 const controller = new AbortController(); 83 const timeoutId = setTimeout(() => controller.abort(), 3000); 84 85 + const response = await fetch(`${ATLAST_API_URL}/.netlify/functions/health`, { 86 + method: 'GET', 87 + signal: controller.signal, 88 + credentials: 'include', // Include for CORS 89 }); 90 91 clearTimeout(timeoutId); 92 93 + // Any successful response means server is running 94 + return response.ok; 95 } catch (error) { 96 console.error('[API Client] Server health check failed:', error); 97 return false; ··· 116 avatar?: string; 117 } | null> { 118 try { 119 + // Try to get session cookie using browser.cookies API 120 + // This works around Firefox's cookie partitioning for extensions 121 + let sessionId: string | null = null; 122 + 123 + try { 124 + const cookieName = __BUILD_MODE__ === 'production' ? 'atlast_session' : 'atlast_session_dev'; 125 + const cookie = await browser.cookies.get({ 126 + url: ATLAST_API_URL, 127 + name: cookieName 128 + }); 129 + 130 + if (cookie) { 131 + sessionId = cookie.value; 132 + console.log('[API Client] Found session cookie:', cookieName); 133 + } 134 + } catch (cookieError) { 135 + console.log('[API Client] Could not read cookie:', cookieError); 136 + } 137 + 138 + // Build URL with session parameter if we have one 139 + const url = sessionId 140 + ? `${ATLAST_API_URL}/.netlify/functions/session?session=${sessionId}` 141 + : `${ATLAST_API_URL}/.netlify/functions/session`; 142 + 143 + const response = await fetch(url, { 144 method: 'GET', 145 + credentials: 'include', // Include cookies as fallback 146 headers: { 147 'Accept': 'application/json' 148 }
+6 -5
packages/extension/src/lib/messaging.ts
··· 1 import type { ScraperProgress, ScraperResult } from '../content/scrapers/base-scraper.js'; 2 3 /** ··· 87 * Send message to background script 88 */ 89 export function sendToBackground<T = any>(message: Message): Promise<T> { 90 - return chrome.runtime.sendMessage(message); 91 } 92 93 /** 94 * Send message to active tab's content script 95 */ 96 export async function sendToContent(message: Message): Promise<any> { 97 - const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); 98 if (!tab.id) { 99 throw new Error('No active tab found'); 100 } 101 - return chrome.tabs.sendMessage(tab.id, message); 102 } 103 104 /** 105 * Listen for messages 106 */ 107 export function onMessage( 108 - handler: (message: Message, sender: chrome.runtime.MessageSender) => any | Promise<any> 109 ): void { 110 - chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 111 const result = handler(message, sender); 112 113 // Handle async handlers
··· 1 + import browser from 'webextension-polyfill'; 2 import type { ScraperProgress, ScraperResult } from '../content/scrapers/base-scraper.js'; 3 4 /** ··· 88 * Send message to background script 89 */ 90 export function sendToBackground<T = any>(message: Message): Promise<T> { 91 + return browser.runtime.sendMessage(message); 92 } 93 94 /** 95 * Send message to active tab's content script 96 */ 97 export async function sendToContent(message: Message): Promise<any> { 98 + const [tab] = await browser.tabs.query({ active: true, currentWindow: true }); 99 if (!tab.id) { 100 throw new Error('No active tab found'); 101 } 102 + return browser.tabs.sendMessage(tab.id, message); 103 } 104 105 /** 106 * Listen for messages 107 */ 108 export function onMessage( 109 + handler: (message: Message, sender: browser.Runtime.MessageSender) => any | Promise<any> 110 ): void { 111 + browser.runtime.onMessage.addListener((message, sender, sendResponse) => { 112 const result = handler(message, sender); 113 114 // Handle async handlers
+4 -3
packages/extension/src/lib/storage.ts
··· 1 import type { ExtensionState } from './messaging.js'; 2 3 /** ··· 11 * Get extension state from storage 12 */ 13 export async function getState(): Promise<ExtensionState> { 14 - const result = await chrome.storage.local.get(STORAGE_KEYS.STATE); 15 return result[STORAGE_KEYS.STATE] || { status: 'idle' }; 16 } 17 ··· 19 * Save extension state to storage 20 */ 21 export async function setState(state: ExtensionState): Promise<void> { 22 - await chrome.storage.local.set({ [STORAGE_KEYS.STATE]: state }); 23 } 24 25 /** 26 * Clear extension state 27 */ 28 export async function clearState(): Promise<void> { 29 - await chrome.storage.local.remove(STORAGE_KEYS.STATE); 30 }
··· 1 + import browser from 'webextension-polyfill'; 2 import type { ExtensionState } from './messaging.js'; 3 4 /** ··· 12 * Get extension state from storage 13 */ 14 export async function getState(): Promise<ExtensionState> { 15 + const result = await browser.storage.local.get(STORAGE_KEYS.STATE); 16 return result[STORAGE_KEYS.STATE] || { status: 'idle' }; 17 } 18 ··· 20 * Save extension state to storage 21 */ 22 export async function setState(state: ExtensionState): Promise<void> { 23 + await browser.storage.local.set({ [STORAGE_KEYS.STATE]: state }); 24 } 25 26 /** 27 * Clear extension state 28 */ 29 export async function clearState(): Promise<void> { 30 + await browser.storage.local.remove(STORAGE_KEYS.STATE); 31 }
+16 -283
packages/extension/src/popup/popup.css
··· 1 - * { 2 - margin: 0; 3 - padding: 0; 4 - box-sizing: border-box; 5 - } 6 - 7 - code { 8 - background: rgba(0, 0, 0, 0.1); 9 - padding: 4px 8px; 10 - border-radius: 4px; 11 - font-family: 'Courier New', monospace; 12 - font-size: 11px; 13 - display: inline-block; 14 - margin: 8px 0; 15 - } 16 - 17 - @media (prefers-color-scheme: dark) { 18 - code { 19 - background: rgba(255, 255, 255, 0.1); 20 - } 21 - } 22 - 23 - body { 24 - width: 350px; 25 - min-height: 400px; 26 - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; 27 - color: #1e293b; 28 - background: linear-gradient(135deg, #faf5ff 0%, #ffffff 50%, #ecfeff 100%); 29 - } 30 - 31 - @media (prefers-color-scheme: dark) { 32 - body { 33 - color: #e0f2fe; 34 - background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 50%, #0c4a6e 100%); 35 - } 36 - } 37 - 38 - .container { 39 - display: flex; 40 - flex-direction: column; 41 - min-height: 400px; 42 - } 43 - 44 - header { 45 - background: linear-gradient(to right, #facc15 0%, #f97316 50%, #ec4899 100%); 46 - color: white; 47 - padding: 20px; 48 - text-align: center; 49 - } 50 - 51 - h1 { 52 - font-size: 20px; 53 - font-weight: 700; 54 - margin-bottom: 4px; 55 - } 56 - 57 - .tagline { 58 - font-size: 13px; 59 - opacity: 0.9; 60 - } 61 - 62 - main { 63 - flex: 1; 64 - padding: 24px 20px; 65 - display: flex; 66 - align-items: center; 67 - justify-content: center; 68 - } 69 - 70 - .state { 71 - width: 100%; 72 - text-align: center; 73 - } 74 - 75 - .state.hidden { 76 - display: none; 77 - } 78 79 - .icon { 80 - font-size: 48px; 81 - margin-bottom: 16px; 82 - } 83 - 84 - .spinner { 85 - animation: spin 2s linear infinite; 86 - } 87 - 88 @keyframes spin { 89 - from { transform: rotate(0deg); } 90 - to { transform: rotate(360deg); } 91 - } 92 - 93 - .message { 94 - font-size: 16px; 95 - font-weight: 600; 96 - margin-bottom: 12px; 97 - color: #334155; 98 - } 99 - 100 - @media (prefers-color-scheme: dark) { 101 - .message { 102 - color: #e0f2fe; 103 } 104 - } 105 - 106 - .hint { 107 - font-size: 13px; 108 - color: #64748b; 109 - margin-top: 8px; 110 - } 111 - 112 - @media (prefers-color-scheme: dark) { 113 - .hint { 114 - color: #94a3b8; 115 } 116 } 117 118 - .btn-primary { 119 - background: #ea580c; 120 - color: white; 121 - border: none; 122 - padding: 12px 24px; 123 - border-radius: 8px; 124 - font-size: 14px; 125 - font-weight: 600; 126 - cursor: pointer; 127 - margin-top: 16px; 128 - width: 100%; 129 - transition: transform 0.2s, box-shadow 0.2s, background-color 0.2s; 130 - } 131 - 132 - .btn-primary:hover { 133 - background: #c2410c; 134 - transform: translateY(-1px); 135 - box-shadow: 0 4px 12px rgba(234, 88, 12, 0.3); 136 - } 137 - 138 - .btn-primary:active { 139 - transform: translateY(0); 140 - } 141 - 142 - .btn-secondary { 143 - background: white; 144 - color: #6b21a8; 145 - border: 2px solid #6b21a8; 146 - padding: 10px 24px; 147 - border-radius: 8px; 148 - font-size: 14px; 149 - font-weight: 600; 150 - cursor: pointer; 151 - margin-top: 16px; 152 - width: 100%; 153 - transition: all 0.2s; 154 - } 155 - 156 - .btn-secondary:hover { 157 - background: #faf5ff; 158 - } 159 - 160 - @media (prefers-color-scheme: dark) { 161 - .btn-secondary { 162 - background: #1e1b4b; 163 - color: #06b6d4; 164 - border-color: #06b6d4; 165 } 166 - 167 - .btn-secondary:hover { 168 - background: #312e81; 169 } 170 } 171 172 - .progress { 173 - margin-top: 20px; 174 - } 175 - 176 - .progress-bar { 177 - width: 100%; 178 - height: 8px; 179 - background: #f0f9ff; 180 - border-radius: 4px; 181 - overflow: hidden; 182 - margin-bottom: 12px; 183 - } 184 - 185 - @media (prefers-color-scheme: dark) { 186 - .progress-bar { 187 - background: #1e293b; 188 - } 189 } 190 191 .progress-fill { 192 - height: 100%; 193 - background: linear-gradient(90deg, #ea580c 0%, #ec4899 100%); 194 - width: 0%; 195 - transition: width 0.3s ease; 196 animation: pulse 2s infinite; 197 } 198 - 199 - @keyframes pulse { 200 - 0%, 100% { opacity: 1; } 201 - 50% { opacity: 0.7; } 202 - } 203 - 204 - .progress-text { 205 - font-size: 16px; 206 - font-weight: 600; 207 - color: #334155; 208 - } 209 - 210 - @media (prefers-color-scheme: dark) { 211 - .progress-text { 212 - color: #e0f2fe; 213 - } 214 - } 215 - 216 - .status-message { 217 - font-size: 13px; 218 - color: #64748b; 219 - margin-top: 8px; 220 - } 221 - 222 - @media (prefers-color-scheme: dark) { 223 - .status-message { 224 - color: #94a3b8; 225 - } 226 - } 227 - 228 - .count-display { 229 - font-size: 14px; 230 - color: #64748b; 231 - margin-top: 8px; 232 - } 233 - 234 - @media (prefers-color-scheme: dark) { 235 - .count-display { 236 - color: #94a3b8; 237 - } 238 - } 239 - 240 - .count-display strong { 241 - color: #ea580c; 242 - font-size: 18px; 243 - } 244 - 245 - @media (prefers-color-scheme: dark) { 246 - .count-display strong { 247 - color: #fb923c; 248 - } 249 - } 250 - 251 - .error-message { 252 - font-size: 13px; 253 - color: #dc2626; 254 - margin-top: 8px; 255 - padding: 12px; 256 - background: #fee2e2; 257 - border-radius: 6px; 258 - border-left: 3px solid #dc2626; 259 - } 260 - 261 - @media (prefers-color-scheme: dark) { 262 - .error-message { 263 - color: #fca5a5; 264 - background: #450a0a; 265 - border-left-color: #ef4444; 266 - } 267 - } 268 - 269 - footer { 270 - padding: 16px; 271 - text-align: center; 272 - border-top: 1px solid #e0e7ff; 273 - background: white; 274 - } 275 - 276 - @media (prefers-color-scheme: dark) { 277 - footer { 278 - border-top-color: #1e293b; 279 - background: #0f172a; 280 - } 281 - } 282 - 283 - footer a { 284 - color: #ea580c; 285 - text-decoration: none; 286 - font-size: 13px; 287 - font-weight: 500; 288 - } 289 - 290 - @media (prefers-color-scheme: dark) { 291 - footer a { 292 - color: #fb923c; 293 - } 294 - } 295 - 296 - footer a:hover { 297 - text-decoration: underline; 298 - }
··· 1 + @tailwind base; 2 + @tailwind components; 3 + @tailwind utilities; 4 5 + /* Custom animations for spinner */ 6 @keyframes spin { 7 + from { 8 + transform: rotate(0deg); 9 } 10 + to { 11 + transform: rotate(360deg); 12 } 13 } 14 15 + @keyframes pulse { 16 + 0%, 17 + 100% { 18 + opacity: 1; 19 } 20 + 50% { 21 + opacity: 0.7; 22 } 23 } 24 25 + .spinner { 26 + animation: spin 2s linear infinite; 27 } 28 29 .progress-fill { 30 animation: pulse 2s infinite; 31 }
+105 -87
packages/extension/src/popup/popup.html
··· 1 - <!DOCTYPE html> 2 <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 - <title>ATlast Importer</title> 7 - <link rel="stylesheet" href="popup.css"> 8 - </head> 9 - <body> 10 - <div class="container"> 11 - <header> 12 - <h1>ATlast Importer</h1> 13 - <p class="tagline">Find your follows on Bluesky</p> 14 - </header> 15 16 - <main id="app"> 17 - <!-- Idle state --> 18 - <div id="state-idle" class="state hidden"> 19 - <div class="icon">🔍</div> 20 - <p class="message">Go to your Twitter/X Following page to start</p> 21 - <p class="hint">Visit x.com/yourusername/following</p> 22 - </div> 23 24 - <!-- Ready state --> 25 - <div id="state-ready" class="state hidden"> 26 - <div class="icon">✅</div> 27 - <p class="message">Ready to scan <span id="platform-name"></span></p> 28 - <button id="btn-start" class="btn-primary">Start Scan</button> 29 - </div> 30 31 - <!-- Scraping state --> 32 - <div id="state-scraping" class="state hidden"> 33 - <div class="icon spinner">⏳</div> 34 - <p class="message">Scanning...</p> 35 - <div class="progress"> 36 - <div class="progress-bar"> 37 - <div id="progress-fill" class="progress-fill"></div> 38 - </div> 39 - <p class="progress-text"> 40 - Found <span id="count">0</span> users 41 - </p> 42 - <p id="status-message" class="status-message"></p> 43 - </div> 44 - </div> 45 46 - <!-- Complete state --> 47 - <div id="state-complete" class="state hidden"> 48 - <div class="icon">🎉</div> 49 - <p class="message">Scan complete!</p> 50 - <p class="count-display">Found <strong id="final-count">0</strong> users</p> 51 - <button id="btn-upload" class="btn-primary">Open in ATlast</button> 52 - </div> 53 54 - <!-- Uploading state --> 55 - <div id="state-uploading" class="state hidden"> 56 - <div class="icon spinner">📤</div> 57 - <p class="message">Uploading to ATlast...</p> 58 - </div> 59 60 - <!-- Error state --> 61 - <div id="state-error" class="state hidden"> 62 - <div class="icon">⚠️</div> 63 - <p class="message">Error</p> 64 - <p id="error-message" class="error-message"></p> 65 - <button id="btn-retry" class="btn-secondary">Try Again</button> 66 - </div> 67 68 - <!-- Server offline state --> 69 - <div id="state-offline" class="state hidden"> 70 - <div class="icon">🔌</div> 71 - <p class="message">ATlast server not running</p> 72 - <p class="error-message"> 73 - Start the dev server:<br> 74 - <code>npx netlify-cli dev --filter @atlast/web</code> 75 - </p> 76 - <p class="hint" id="server-url"></p> 77 - <button id="btn-check-server" class="btn-primary">Check Again</button> 78 - </div> 79 80 - <!-- Not logged in state --> 81 - <div id="state-not-logged-in" class="state hidden"> 82 - <div class="icon">🔐</div> 83 - <p class="message">Not logged in to ATlast</p> 84 - <p class="error-message"> 85 - Please log in to ATlast first, then return here to scan. 86 - </p> 87 - <button id="btn-open-atlast" class="btn-primary">Open ATlast</button> 88 - <button id="btn-retry-login" class="btn-secondary">Check Again</button> 89 - </div> 90 - </main> 91 92 - <footer> 93 - <a href="https://atlast.byarielm.fyi" target="_blank">atlast.byarielm.fyi</a> 94 - </footer> 95 - </div> 96 97 - <script type="module" src="popup.js"></script> 98 - </body> 99 </html>
··· 1 + <!doctype html> 2 <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>ATlast Importer</title> 7 + <link rel="stylesheet" href="popup.css" /> 8 + </head> 9 + <body class="w-[350px] min-h-[400px] font-sans text-slate-800 dark:text-cyan-50 bg-gradient-to-br from-purple-50 via-white to-cyan-50 dark:from-slate-900 dark:via-purple-950 dark:to-sky-900"> 10 + <div class="flex flex-col min-h-[400px]"> 11 + <header class="bg-firefly-banner text-white p-5 text-center"> 12 + <h1 class="text-xl font-bold mb-1">ATlast Importer</h1> 13 + <p class="text-[13px] opacity-90">Find your follows in the ATmosphere</p> 14 + </header> 15 16 + <main id="app" class="flex-1 px-5 py-6 flex items-center justify-center"> 17 + <!-- Idle state --> 18 + <div id="state-idle" class="w-full text-center hidden"> 19 + <div class="text-5xl mb-4">🔍</div> 20 + <p class="text-base font-semibold mb-3 text-slate-700 dark:text-cyan-50"> 21 + Go to your Twitter/X Following page to start 22 + </p> 23 + <p class="text-[13px] text-slate-500 dark:text-slate-400 mt-2">Visit x.com/yourusername/following</p> 24 + </div> 25 26 + <!-- Ready state --> 27 + <div id="state-ready" class="w-full text-center hidden"> 28 + <div class="text-5xl mb-4">✅</div> 29 + <p class="text-base font-semibold mb-3 text-slate-700 dark:text-cyan-50"> 30 + Ready to scan <span id="platform-name"></span> 31 + </p> 32 + <button id="btn-start" class="w-full bg-orange-600 hover:bg-orange-700 text-white font-semibold py-3 px-6 rounded-lg mt-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-orange-600/30 active:translate-y-0"> 33 + Start Scan 34 + </button> 35 + </div> 36 37 + <!-- Scraping state --> 38 + <div id="state-scraping" class="w-full text-center hidden"> 39 + <div class="text-5xl mb-4 spinner">⏳</div> 40 + <p class="text-base font-semibold mb-3 text-slate-700 dark:text-cyan-50">Scanning...</p> 41 + <div class="mt-5"> 42 + <div class="w-full h-2 bg-sky-50 dark:bg-slate-800 rounded overflow-hidden mb-3"> 43 + <div id="progress-fill" class="h-full bg-gradient-to-r from-orange-600 to-pink-600 w-0 transition-all duration-300 progress-fill"></div> 44 + </div> 45 + <p class="text-base font-semibold text-slate-700 dark:text-cyan-50"> 46 + Found <span id="count">0</span> users 47 + </p> 48 + <p id="status-message" class="text-[13px] text-slate-500 dark:text-slate-400 mt-2"></p> 49 + </div> 50 + </div> 51 52 + <!-- Complete state --> 53 + <div id="state-complete" class="w-full text-center hidden"> 54 + <div class="text-5xl mb-4">🎉</div> 55 + <p class="text-base font-semibold mb-3 text-slate-700 dark:text-cyan-50">Scan complete!</p> 56 + <p class="text-sm text-slate-500 dark:text-slate-400 mt-2"> 57 + Found <strong id="final-count" class="text-orange-600 dark:text-orange-400 text-lg">0</strong> users 58 + </p> 59 + <button id="btn-upload" class="w-full bg-orange-600 hover:bg-orange-700 text-white font-semibold py-3 px-6 rounded-lg mt-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-orange-600/30 active:translate-y-0"> 60 + Open in ATlast 61 + </button> 62 + </div> 63 64 + <!-- Uploading state --> 65 + <div id="state-uploading" class="w-full text-center hidden"> 66 + <div class="text-5xl mb-4 spinner">📤</div> 67 + <p class="text-base font-semibold mb-3 text-slate-700 dark:text-cyan-50">Uploading to ATlast...</p> 68 + </div> 69 70 + <!-- Error state --> 71 + <div id="state-error" class="w-full text-center hidden"> 72 + <div class="text-5xl mb-4">⚠️</div> 73 + <p class="text-base font-semibold mb-3 text-slate-700 dark:text-cyan-50">Error</p> 74 + <p id="error-message" class="text-[13px] text-red-600 dark:text-red-400 mt-2 p-3 bg-red-50 dark:bg-red-950/50 rounded border-l-[3px] border-red-600"></p> 75 + <button id="btn-retry" class="w-full bg-white dark:bg-purple-950 text-purple-700 dark:text-cyan-400 border-2 border-purple-700 dark:border-cyan-400 font-semibold py-2.5 px-6 rounded-lg mt-4 transition-all duration-200 hover:bg-purple-50 dark:hover:bg-purple-900"> 76 + Try Again 77 + </button> 78 + </div> 79 80 + <!-- Server offline state --> 81 + <div id="state-offline" class="w-full text-center hidden"> 82 + <div class="text-5xl mb-4">🔌</div> 83 + <p class="text-base font-semibold mb-3 text-slate-700 dark:text-cyan-50">Server not available</p> 84 + <p id="dev-instructions" class="text-[13px] text-red-600 dark:text-red-400 mt-2 p-3 bg-red-50 dark:bg-red-950/50 rounded border-l-[3px] border-red-600"> 85 + Start the dev server:<br /> 86 + <code class="bg-black/10 dark:bg-white/10 px-2 py-1 rounded font-mono text-[11px] inline-block my-2">npx netlify-cli dev --filter @atlast/web</code> 87 + </p> 88 + <p class="text-[13px] text-slate-500 dark:text-slate-400 mt-2" id="server-url"></p> 89 + <button id="btn-check-server" class="w-full bg-orange-600 hover:bg-orange-700 text-white font-semibold py-3 px-6 rounded-lg mt-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-orange-600/30 active:translate-y-0"> 90 + Check Again 91 + </button> 92 + </div> 93 94 + <!-- Not logged in state --> 95 + <div id="state-not-logged-in" class="w-full text-center hidden"> 96 + <div class="text-5xl mb-4">🔐</div> 97 + <p class="text-base font-semibold mb-3 text-slate-700 dark:text-cyan-50">Not logged in to ATlast</p> 98 + <p class="text-[13px] text-red-600 dark:text-red-400 mt-2 p-3 bg-red-50 dark:bg-red-950/50 rounded border-l-[3px] border-red-600"> 99 + Please log in to ATlast first, then return here to scan. 100 + </p> 101 + <button id="btn-open-atlast" class="w-full bg-orange-600 hover:bg-orange-700 text-white font-semibold py-3 px-6 rounded-lg mt-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-orange-600/30 active:translate-y-0"> 102 + Open ATlast 103 + </button> 104 + <button id="btn-retry-login" class="w-full bg-white dark:bg-purple-950 text-purple-700 dark:text-cyan-400 border-2 border-purple-700 dark:border-cyan-400 font-semibold py-2.5 px-6 rounded-lg mt-4 transition-all duration-200 hover:bg-purple-50 dark:hover:bg-purple-900"> 105 + Check Again 106 + </button> 107 + </div> 108 + </main> 109 110 + <footer class="p-4 text-center border-t border-purple-200 dark:border-slate-800 bg-white dark:bg-slate-900"> 111 + <a href="https://atlast.byarielm.fyi" target="_blank" class="text-orange-600 dark:text-orange-400 no-underline text-[13px] font-medium hover:underline">atlast.byarielm.fyi</a> 112 + </footer> 113 + </div> 114 115 + <script type="module" src="popup.js"></script> 116 + </body> 117 </html>
+22 -4
packages/extension/src/popup/popup.ts
··· 1 import { 2 MessageType, 3 sendToBackground, 4 sendToContent, 5 type ExtensionState 6 } from '../lib/messaging.js'; 7 8 /** 9 * DOM elements ··· 26 statusMessage: document.getElementById('status-message')!, 27 errorMessage: document.getElementById('error-message')!, 28 serverUrl: document.getElementById('server-url')!, 29 progressFill: document.getElementById('progress-fill')! as HTMLElement, 30 btnStart: document.getElementById('btn-start')! as HTMLButtonElement, 31 btnUpload: document.getElementById('btn-upload')! as HTMLButtonElement, ··· 163 // Open ATlast at results page with upload data 164 const { getApiUrl } = await import('../lib/api-client.js'); 165 const resultsUrl = `${getApiUrl()}${response.redirectUrl}`; 166 - chrome.tabs.create({ url: resultsUrl }); 167 168 } catch (error) { 169 console.error('[Popup] Error uploading:', error); ··· 214 if (!isOnline) { 215 console.log('[Popup] ❌ Server is offline'); 216 showState('offline'); 217 - elements.serverUrl.textContent = `Trying to reach: ${getApiUrl()}`; 218 return false; 219 } 220 ··· 264 265 // Set up login buttons 266 elements.btnOpenAtlast.addEventListener('click', () => { 267 - chrome.tabs.create({ url: getApiUrl() }); 268 }); 269 270 elements.btnRetryLogin.addEventListener('click', async () => { ··· 305 }); 306 307 // Listen for storage changes (when background updates state) 308 - chrome.storage.onChanged.addListener((changes, areaName) => { 309 if (areaName === 'local' && changes.extensionState) { 310 const newState = changes.extensionState.newValue; 311 console.log('[Popup] 🔄 Storage changed, new state:', newState);
··· 1 + import browser from 'webextension-polyfill'; 2 import { 3 MessageType, 4 sendToBackground, 5 sendToContent, 6 type ExtensionState 7 } from '../lib/messaging.js'; 8 + 9 + // Build mode injected at build time 10 + declare const __BUILD_MODE__: string; 11 12 /** 13 * DOM elements ··· 30 statusMessage: document.getElementById('status-message')!, 31 errorMessage: document.getElementById('error-message')!, 32 serverUrl: document.getElementById('server-url')!, 33 + devInstructions: document.getElementById('dev-instructions')!, 34 progressFill: document.getElementById('progress-fill')! as HTMLElement, 35 btnStart: document.getElementById('btn-start')! as HTMLButtonElement, 36 btnUpload: document.getElementById('btn-upload')! as HTMLButtonElement, ··· 168 // Open ATlast at results page with upload data 169 const { getApiUrl } = await import('../lib/api-client.js'); 170 const resultsUrl = `${getApiUrl()}${response.redirectUrl}`; 171 + browser.tabs.create({ url: resultsUrl }); 172 173 } catch (error) { 174 console.error('[Popup] Error uploading:', error); ··· 219 if (!isOnline) { 220 console.log('[Popup] ❌ Server is offline'); 221 showState('offline'); 222 + 223 + // Show appropriate message based on build mode 224 + const apiUrl = getApiUrl(); 225 + const isDev = __BUILD_MODE__ === 'development'; 226 + 227 + // Hide dev instructions in production 228 + if (!isDev) { 229 + elements.devInstructions.classList.add('hidden'); 230 + } 231 + 232 + elements.serverUrl.textContent = isDev 233 + ? `Development server at ${apiUrl}` 234 + : `Cannot reach ${apiUrl}`; 235 + 236 return false; 237 } 238 ··· 282 283 // Set up login buttons 284 elements.btnOpenAtlast.addEventListener('click', () => { 285 + browser.tabs.create({ url: getApiUrl() }); 286 }); 287 288 elements.btnRetryLogin.addEventListener('click', async () => { ··· 323 }); 324 325 // Listen for storage changes (when background updates state) 326 + browser.storage.onChanged.addListener((changes, areaName) => { 327 if (areaName === 'local' && changes.extensionState) { 328 const newState = changes.extensionState.newValue; 329 console.log('[Popup] 🔄 Storage changed, new state:', newState);
+35
packages/extension/tailwind.config.js
···
··· 1 + /** @type {import('tailwindcss').Config} */ 2 + export default { 3 + // Use media query dark mode to automatically respect system preference 4 + darkMode: "media", 5 + 6 + // Scan popup HTML and TypeScript files 7 + content: [ 8 + "./src/popup/**/*.{html,ts}", 9 + "./src/content/**/*.ts", 10 + ], 11 + 12 + // Extend with same custom config as web app 13 + theme: { 14 + extend: { 15 + colors: { 16 + firefly: { 17 + glow: "#FCD34D", 18 + amber: "#F59E0B", 19 + orange: "#F97316", 20 + pink: "#EC4899", 21 + cyan: "#10D2F4", 22 + }, 23 + cyan: { 250: "#72EEFD" }, 24 + purple: { 750: "#6A1DD1" }, 25 + yellow: { 650: "#C56508" }, 26 + orange: { 650: "#DF3F00" }, 27 + pink: { 650: "#CD206A" }, 28 + }, 29 + backgroundImage: ({ theme }) => ({ 30 + "firefly-banner": `linear-gradient(to right, ${theme("colors.yellow.400")}, ${theme("colors.orange.500")}, ${theme("colors.pink.600")})`, 31 + "firefly-banner-dark": `linear-gradient(to right, ${theme("colors.yellow.600")}, ${theme("colors.orange.600")}, ${theme("colors.pink.700")})`, 32 + }), 33 + }, 34 + }, 35 + };
+4 -2
packages/functions/src/core/middleware/error.middleware.ts
··· 21 } 22 23 if (error instanceof ApiError) { 24 - return errorResponse(error.message, error.statusCode, error.details); 25 } 26 27 // Unknown errors ··· 29 "Internal server error", 30 500, 31 error instanceof Error ? error.message : "Unknown error", 32 ); 33 } 34 }; ··· 48 console.error("Authenticated handler error:", error); 49 50 if (error instanceof ApiError) { 51 - return errorResponse(error.message, error.statusCode, error.details); 52 } 53 54 return errorResponse( 55 "Internal server error", 56 500, 57 error instanceof Error ? error.message : "Unknown error", 58 ); 59 } 60 };
··· 21 } 22 23 if (error instanceof ApiError) { 24 + return errorResponse(error.message, error.statusCode, error.details, event); 25 } 26 27 // Unknown errors ··· 29 "Internal server error", 30 500, 31 error instanceof Error ? error.message : "Unknown error", 32 + event, 33 ); 34 } 35 }; ··· 49 console.error("Authenticated handler error:", error); 50 51 if (error instanceof ApiError) { 52 + return errorResponse(error.message, error.statusCode, error.details, event); 53 } 54 55 return errorResponse( 56 "Internal server error", 57 500, 58 error instanceof Error ? error.message : "Unknown error", 59 + event, 60 ); 61 } 62 };
+21
packages/functions/src/health.ts
···
··· 1 + import { SimpleHandler } from "./core/types/api.types"; 2 + import { successResponse } from "./utils"; 3 + import { withErrorHandling } from "./core/middleware"; 4 + 5 + /** 6 + * Health check endpoint 7 + * Returns 200 OK with server status 8 + */ 9 + const healthHandler: SimpleHandler = async (event) => { 10 + return successResponse( 11 + { 12 + status: "ok", 13 + timestamp: new Date().toISOString(), 14 + }, 15 + 200, 16 + {}, 17 + event 18 + ); 19 + }; 20 + 21 + export const handler = withErrorHandling(healthHandler);
+2 -2
packages/functions/src/session.ts
··· 30 return successResponse(cached, 200, { 31 "Cache-Control": "private, max-age=300", 32 "X-Cache-Status": "HIT", 33 - }); 34 } 35 36 const { agent } = await SessionService.getAgentForSession(sessionId, event); ··· 50 return successResponse(profileData, 200, { 51 "Cache-Control": "private, max-age=300", 52 "X-Cache-Status": "MISS", 53 - }); 54 }; 55 56 export const handler = withErrorHandling(sessionHandler);
··· 30 return successResponse(cached, 200, { 31 "Cache-Control": "private, max-age=300", 32 "X-Cache-Status": "HIT", 33 + }, event); 34 } 35 36 const { agent } = await SessionService.getAgentForSession(sessionId, event); ··· 50 return successResponse(profileData, 200, { 51 "Cache-Control": "private, max-age=300", 52 "X-Cache-Status": "MISS", 53 + }, event); 54 }; 55 56 export const handler = withErrorHandling(sessionHandler);
+42 -3
packages/functions/src/utils/response.utils.ts
··· 1 - import { HandlerResponse } from "@netlify/functions"; 2 import { ApiResponse } from "../core/types"; 3 4 export function successResponse<T>( 5 data: T, 6 statusCode: number = 200, 7 additionalHeaders: Record<string, string> = {}, 8 ): HandlerResponse { 9 const response: ApiResponse<T> = { 10 success: true, ··· 15 statusCode, 16 headers: { 17 "Content-Type": "application/json", 18 - "Access-Control-Allow-Origin": "*", 19 ...additionalHeaders, 20 }, 21 body: JSON.stringify(response), ··· 26 error: string, 27 statusCode: number = 500, 28 details?: string, 29 ): HandlerResponse { 30 const response: ApiResponse = { 31 success: false, ··· 37 statusCode, 38 headers: { 39 "Content-Type": "application/json", 40 - "Access-Control-Allow-Origin": "*", 41 }, 42 body: JSON.stringify(response), 43 };
··· 1 + import { HandlerResponse, HandlerEvent } from "@netlify/functions"; 2 import { ApiResponse } from "../core/types"; 3 4 + /** 5 + * Get CORS headers based on request origin 6 + * Supports credentialed requests from extensions and localhost 7 + */ 8 + function getCorsHeaders(event?: HandlerEvent): Record<string, string> { 9 + const origin = event?.headers?.origin || event?.headers?.Origin; 10 + 11 + // Allow all origins for non-credentialed requests (backward compatibility) 12 + if (!origin) { 13 + return { 14 + "Access-Control-Allow-Origin": "*", 15 + }; 16 + } 17 + 18 + // Check if origin is allowed for credentialed requests 19 + const allowedOrigins = [ 20 + 'http://localhost:8888', 21 + 'http://127.0.0.1:8888', 22 + 'https://atlast.byarielm.fyi', 23 + ]; 24 + 25 + const isExtension = origin.startsWith('chrome-extension://') || origin.startsWith('moz-extension://'); 26 + const isAllowedOrigin = allowedOrigins.includes(origin); 27 + 28 + if (isExtension || isAllowedOrigin) { 29 + return { 30 + "Access-Control-Allow-Origin": origin, 31 + "Access-Control-Allow-Credentials": "true", 32 + }; 33 + } 34 + 35 + // Default to wildcard for unknown origins 36 + return { 37 + "Access-Control-Allow-Origin": "*", 38 + }; 39 + } 40 + 41 export function successResponse<T>( 42 data: T, 43 statusCode: number = 200, 44 additionalHeaders: Record<string, string> = {}, 45 + event?: HandlerEvent, 46 ): HandlerResponse { 47 const response: ApiResponse<T> = { 48 success: true, ··· 53 statusCode, 54 headers: { 55 "Content-Type": "application/json", 56 + ...getCorsHeaders(event), 57 ...additionalHeaders, 58 }, 59 body: JSON.stringify(response), ··· 64 error: string, 65 statusCode: number = 500, 66 details?: string, 67 + event?: HandlerEvent, 68 ): HandlerResponse { 69 const response: ApiResponse = { 70 success: false, ··· 76 statusCode, 77 headers: { 78 "Content-Type": "application/json", 79 + ...getCorsHeaders(event), 80 }, 81 body: JSON.stringify(response), 82 };
+19
packages/web/vite.config.ts
··· 5 export default defineConfig({ 6 base: "/", 7 plugins: [react(), svgr()], 8 });
··· 5 export default defineConfig({ 6 base: "/", 7 plugins: [react(), svgr()], 8 + optimizeDeps: { 9 + include: [ 10 + "react", 11 + "react-dom", 12 + "react-router-dom", 13 + "@icons-pack/react-simple-icons", 14 + "lucide-react", 15 + "date-fns", 16 + "jszip", 17 + "zustand", 18 + "@tanstack/react-virtual", 19 + ], 20 + }, 21 + server: { 22 + fs: { 23 + // Allow serving files from the monorepo root 24 + allow: ["../.."], 25 + }, 26 + }, 27 });
+579
pnpm-lock.yaml
··· 114 '@atlast/shared': 115 specifier: workspace:* 116 version: link:../shared 117 devDependencies: 118 '@types/chrome': 119 specifier: ^0.0.256 120 version: 0.0.256 121 esbuild: 122 specifier: ^0.19.11 123 version: 0.19.12 124 typescript: 125 specifier: ^5.3.3 126 version: 5.9.3 ··· 1233 '@types/triple-beam@1.3.5': 1234 resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} 1235 1236 '@types/yauzl@2.10.3': 1237 resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} 1238 ··· 1431 bindings@1.5.0: 1432 resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} 1433 1434 brace-expansion@2.0.2: 1435 resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} 1436 ··· 1471 resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} 1472 engines: {node: '>=10'} 1473 1474 caniuse-lite@1.0.30001761: 1475 resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} 1476 ··· 1513 resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} 1514 engines: {node: '>=18'} 1515 1516 commander@10.0.1: 1517 resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} 1518 engines: {node: '>=14'} 1519 1520 commander@12.1.0: 1521 resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} ··· 1586 resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 1587 engines: {node: '>= 8'} 1588 1589 cssesc@3.0.0: 1590 resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 1591 engines: {node: '>=4'} 1592 hasBin: true 1593 1594 csstype@3.2.3: 1595 resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} 1596 ··· 1668 1669 dlv@1.1.3: 1670 resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} 1671 1672 dot-case@3.0.4: 1673 resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} ··· 2121 resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} 2122 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 2123 2124 lodash@4.17.21: 2125 resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 2126 ··· 2156 make-dir@3.1.0: 2157 resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} 2158 engines: {node: '>=8'} 2159 2160 merge-options@3.0.4: 2161 resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} ··· 2258 resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} 2259 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 2260 2261 object-assign@4.1.1: 2262 resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 2263 engines: {node: '>=0.10.0'} ··· 2394 resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} 2395 engines: {node: '>=8'} 2396 2397 postcss-import@15.1.0: 2398 resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} 2399 engines: {node: '>=14.0.0'} ··· 2424 yaml: 2425 optional: true 2426 2427 postcss-nested@6.2.0: 2428 resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} 2429 engines: {node: '>=12.0'} 2430 peerDependencies: 2431 postcss: ^8.2.14 2432 2433 postcss-selector-parser@6.1.2: 2434 resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} 2435 engines: {node: '>=4'} 2436 2437 postcss-value-parser@4.2.0: 2438 resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} ··· 2596 resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} 2597 engines: {node: '>=10'} 2598 2599 scheduler@0.23.2: 2600 resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} 2601 ··· 2692 resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==} 2693 engines: {node: '>=0.10.0'} 2694 2695 sucrase@3.35.1: 2696 resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} 2697 engines: {node: '>=16 || 14 >=14.17'} ··· 2703 2704 svg-parser@2.0.4: 2705 resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} 2706 2707 tailwindcss@3.4.19: 2708 resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} ··· 2868 optional: true 2869 terser: 2870 optional: true 2871 2872 webidl-conversions@3.0.1: 2873 resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} ··· 3895 csstype: 3.2.3 3896 3897 '@types/triple-beam@1.3.5': {} 3898 3899 '@types/yauzl@2.10.3': 3900 dependencies: ··· 4125 dependencies: 4126 file-uri-to-path: 1.0.0 4127 4128 brace-expansion@2.0.2: 4129 dependencies: 4130 balanced-match: 1.0.2 ··· 4159 camelcase-css@2.0.1: {} 4160 4161 camelcase@6.3.0: {} 4162 4163 caniuse-lite@1.0.30001761: {} 4164 ··· 4207 color-convert: 3.1.3 4208 color-string: 2.1.4 4209 4210 commander@10.0.1: {} 4211 4212 commander@12.1.0: {} 4213 ··· 4267 path-key: 3.1.1 4268 shebang-command: 2.0.0 4269 which: 2.0.2 4270 4271 cssesc@3.0.0: {} 4272 4273 csstype@3.2.3: {} 4274 4275 date-fns@4.1.0: {} ··· 4349 path-type: 4.0.0 4350 4351 dlv@1.1.3: {} 4352 4353 dot-case@3.0.4: 4354 dependencies: ··· 4817 dependencies: 4818 p-locate: 6.0.0 4819 4820 lodash@4.17.21: {} 4821 4822 logform@2.7.0: ··· 4855 make-dir@3.1.0: 4856 dependencies: 4857 semver: 6.3.1 4858 4859 merge-options@3.0.4: 4860 dependencies: ··· 4941 dependencies: 4942 path-key: 4.0.0 4943 4944 object-assign@4.1.1: {} 4945 4946 object-hash@3.0.0: {} ··· 5051 dependencies: 5052 find-up: 4.1.0 5053 5054 postcss-import@15.1.0(postcss@8.5.6): 5055 dependencies: 5056 postcss: 8.5.6 ··· 5070 jiti: 1.21.7 5071 postcss: 8.5.6 5072 5073 postcss-nested@6.2.0(postcss@8.5.6): 5074 dependencies: 5075 postcss: 8.5.6 5076 postcss-selector-parser: 6.1.2 5077 5078 postcss-selector-parser@6.1.2: 5079 dependencies: 5080 cssesc: 3.0.0 5081 util-deprecate: 1.0.2 5082 5083 postcss-value-parser@4.2.0: {} 5084 5085 postcss-values-parser@6.0.2(postcss@8.5.6): ··· 5277 safe-buffer@5.2.1: {} 5278 5279 safe-stable-stringify@2.5.0: {} 5280 5281 scheduler@0.23.2: 5282 dependencies: ··· 5373 dependencies: 5374 escape-string-regexp: 1.0.5 5375 5376 sucrase@3.35.1: 5377 dependencies: 5378 '@jridgewell/gen-mapping': 0.3.13 ··· 5386 supports-preserve-symlinks-flag@1.0.0: {} 5387 5388 svg-parser@2.0.4: {} 5389 5390 tailwindcss@3.4.19: 5391 dependencies: ··· 5545 optionalDependencies: 5546 '@types/node': 24.10.4 5547 fsevents: 2.3.3 5548 5549 webidl-conversions@3.0.1: {} 5550
··· 114 '@atlast/shared': 115 specifier: workspace:* 116 version: link:../shared 117 + webextension-polyfill: 118 + specifier: ^0.12.0 119 + version: 0.12.0 120 devDependencies: 121 '@types/chrome': 122 specifier: ^0.0.256 123 version: 0.0.256 124 + '@types/webextension-polyfill': 125 + specifier: ^0.12.4 126 + version: 0.12.4 127 + autoprefixer: 128 + specifier: ^10.4.23 129 + version: 10.4.23(postcss@8.5.6) 130 + cssnano: 131 + specifier: ^7.1.2 132 + version: 7.1.2(postcss@8.5.6) 133 esbuild: 134 specifier: ^0.19.11 135 version: 0.19.12 136 + postcss: 137 + specifier: ^8.5.6 138 + version: 8.5.6 139 + tailwindcss: 140 + specifier: ^3.4.19 141 + version: 3.4.19 142 typescript: 143 specifier: ^5.3.3 144 version: 5.9.3 ··· 1251 '@types/triple-beam@1.3.5': 1252 resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} 1253 1254 + '@types/webextension-polyfill@0.12.4': 1255 + resolution: {integrity: sha512-wK8YdSI0pDiaehSLDIvtvonYmLwUUivg4Z6JCJO8rkyssMAG82cFJgwPK/V7NO61mJBLg/tXeoXQL8AFzpXZmQ==} 1256 + 1257 '@types/yauzl@2.10.3': 1258 resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} 1259 ··· 1452 bindings@1.5.0: 1453 resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} 1454 1455 + boolbase@1.0.0: 1456 + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} 1457 + 1458 brace-expansion@2.0.2: 1459 resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} 1460 ··· 1495 resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} 1496 engines: {node: '>=10'} 1497 1498 + caniuse-api@3.0.0: 1499 + resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} 1500 + 1501 caniuse-lite@1.0.30001761: 1502 resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} 1503 ··· 1540 resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} 1541 engines: {node: '>=18'} 1542 1543 + colord@2.9.3: 1544 + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} 1545 + 1546 commander@10.0.1: 1547 resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} 1548 engines: {node: '>=14'} 1549 + 1550 + commander@11.1.0: 1551 + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} 1552 + engines: {node: '>=16'} 1553 1554 commander@12.1.0: 1555 resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} ··· 1620 resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 1621 engines: {node: '>= 8'} 1622 1623 + css-declaration-sorter@7.3.0: 1624 + resolution: {integrity: sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==} 1625 + engines: {node: ^14 || ^16 || >=18} 1626 + peerDependencies: 1627 + postcss: ^8.0.9 1628 + 1629 + css-select@5.2.2: 1630 + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} 1631 + 1632 + css-tree@2.2.1: 1633 + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} 1634 + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} 1635 + 1636 + css-tree@3.1.0: 1637 + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} 1638 + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} 1639 + 1640 + css-what@6.2.2: 1641 + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} 1642 + engines: {node: '>= 6'} 1643 + 1644 cssesc@3.0.0: 1645 resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 1646 engines: {node: '>=4'} 1647 hasBin: true 1648 1649 + cssnano-preset-default@7.0.10: 1650 + resolution: {integrity: sha512-6ZBjW0Lf1K1Z+0OKUAUpEN62tSXmYChXWi2NAA0afxEVsj9a+MbcB1l5qel6BHJHmULai2fCGRthCeKSFbScpA==} 1651 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 1652 + peerDependencies: 1653 + postcss: ^8.4.32 1654 + 1655 + cssnano-utils@5.0.1: 1656 + resolution: {integrity: sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==} 1657 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 1658 + peerDependencies: 1659 + postcss: ^8.4.32 1660 + 1661 + cssnano@7.1.2: 1662 + resolution: {integrity: sha512-HYOPBsNvoiFeR1eghKD5C3ASm64v9YVyJB4Ivnl2gqKoQYvjjN/G0rztvKQq8OxocUtC6sjqY8jwYngIB4AByA==} 1663 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 1664 + peerDependencies: 1665 + postcss: ^8.4.32 1666 + 1667 + csso@5.0.5: 1668 + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} 1669 + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} 1670 + 1671 csstype@3.2.3: 1672 resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} 1673 ··· 1745 1746 dlv@1.1.3: 1747 resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} 1748 + 1749 + dom-serializer@2.0.0: 1750 + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} 1751 + 1752 + domelementtype@2.3.0: 1753 + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} 1754 + 1755 + domhandler@5.0.3: 1756 + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} 1757 + engines: {node: '>= 4'} 1758 + 1759 + domutils@3.2.2: 1760 + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} 1761 1762 dot-case@3.0.4: 1763 resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} ··· 2211 resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} 2212 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 2213 2214 + lodash.memoize@4.1.2: 2215 + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} 2216 + 2217 + lodash.uniq@4.5.0: 2218 + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} 2219 + 2220 lodash@4.17.21: 2221 resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 2222 ··· 2252 make-dir@3.1.0: 2253 resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} 2254 engines: {node: '>=8'} 2255 + 2256 + mdn-data@2.0.28: 2257 + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} 2258 + 2259 + mdn-data@2.12.2: 2260 + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} 2261 2262 merge-options@3.0.4: 2263 resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} ··· 2360 resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} 2361 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 2362 2363 + nth-check@2.1.1: 2364 + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} 2365 + 2366 object-assign@4.1.1: 2367 resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 2368 engines: {node: '>=0.10.0'} ··· 2499 resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} 2500 engines: {node: '>=8'} 2501 2502 + postcss-calc@10.1.1: 2503 + resolution: {integrity: sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==} 2504 + engines: {node: ^18.12 || ^20.9 || >=22.0} 2505 + peerDependencies: 2506 + postcss: ^8.4.38 2507 + 2508 + postcss-colormin@7.0.5: 2509 + resolution: {integrity: sha512-ekIBP/nwzRWhEMmIxHHbXHcMdzd1HIUzBECaj5KEdLz9DVP2HzT065sEhvOx1dkLjYW7jyD0CngThx6bpFi2fA==} 2510 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2511 + peerDependencies: 2512 + postcss: ^8.4.32 2513 + 2514 + postcss-convert-values@7.0.8: 2515 + resolution: {integrity: sha512-+XNKuPfkHTCEo499VzLMYn94TiL3r9YqRE3Ty+jP7UX4qjewUONey1t7CG21lrlTLN07GtGM8MqFVp86D4uKJg==} 2516 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2517 + peerDependencies: 2518 + postcss: ^8.4.32 2519 + 2520 + postcss-discard-comments@7.0.5: 2521 + resolution: {integrity: sha512-IR2Eja8WfYgN5n32vEGSctVQ1+JARfu4UH8M7bgGh1bC+xI/obsPJXaBpQF7MAByvgwZinhpHpdrmXtvVVlKcQ==} 2522 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2523 + peerDependencies: 2524 + postcss: ^8.4.32 2525 + 2526 + postcss-discard-duplicates@7.0.2: 2527 + resolution: {integrity: sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==} 2528 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2529 + peerDependencies: 2530 + postcss: ^8.4.32 2531 + 2532 + postcss-discard-empty@7.0.1: 2533 + resolution: {integrity: sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==} 2534 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2535 + peerDependencies: 2536 + postcss: ^8.4.32 2537 + 2538 + postcss-discard-overridden@7.0.1: 2539 + resolution: {integrity: sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==} 2540 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2541 + peerDependencies: 2542 + postcss: ^8.4.32 2543 + 2544 postcss-import@15.1.0: 2545 resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} 2546 engines: {node: '>=14.0.0'} ··· 2571 yaml: 2572 optional: true 2573 2574 + postcss-merge-longhand@7.0.5: 2575 + resolution: {integrity: sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==} 2576 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2577 + peerDependencies: 2578 + postcss: ^8.4.32 2579 + 2580 + postcss-merge-rules@7.0.7: 2581 + resolution: {integrity: sha512-njWJrd/Ms6XViwowaaCc+/vqhPG3SmXn725AGrnl+BgTuRPEacjiLEaGq16J6XirMJbtKkTwnt67SS+e2WGoew==} 2582 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2583 + peerDependencies: 2584 + postcss: ^8.4.32 2585 + 2586 + postcss-minify-font-values@7.0.1: 2587 + resolution: {integrity: sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==} 2588 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2589 + peerDependencies: 2590 + postcss: ^8.4.32 2591 + 2592 + postcss-minify-gradients@7.0.1: 2593 + resolution: {integrity: sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==} 2594 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2595 + peerDependencies: 2596 + postcss: ^8.4.32 2597 + 2598 + postcss-minify-params@7.0.5: 2599 + resolution: {integrity: sha512-FGK9ky02h6Ighn3UihsyeAH5XmLEE2MSGH5Tc4tXMFtEDx7B+zTG6hD/+/cT+fbF7PbYojsmmWjyTwFwW1JKQQ==} 2600 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2601 + peerDependencies: 2602 + postcss: ^8.4.32 2603 + 2604 + postcss-minify-selectors@7.0.5: 2605 + resolution: {integrity: sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==} 2606 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2607 + peerDependencies: 2608 + postcss: ^8.4.32 2609 + 2610 postcss-nested@6.2.0: 2611 resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} 2612 engines: {node: '>=12.0'} 2613 peerDependencies: 2614 postcss: ^8.2.14 2615 2616 + postcss-normalize-charset@7.0.1: 2617 + resolution: {integrity: sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==} 2618 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2619 + peerDependencies: 2620 + postcss: ^8.4.32 2621 + 2622 + postcss-normalize-display-values@7.0.1: 2623 + resolution: {integrity: sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==} 2624 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2625 + peerDependencies: 2626 + postcss: ^8.4.32 2627 + 2628 + postcss-normalize-positions@7.0.1: 2629 + resolution: {integrity: sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==} 2630 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2631 + peerDependencies: 2632 + postcss: ^8.4.32 2633 + 2634 + postcss-normalize-repeat-style@7.0.1: 2635 + resolution: {integrity: sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==} 2636 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2637 + peerDependencies: 2638 + postcss: ^8.4.32 2639 + 2640 + postcss-normalize-string@7.0.1: 2641 + resolution: {integrity: sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==} 2642 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2643 + peerDependencies: 2644 + postcss: ^8.4.32 2645 + 2646 + postcss-normalize-timing-functions@7.0.1: 2647 + resolution: {integrity: sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==} 2648 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2649 + peerDependencies: 2650 + postcss: ^8.4.32 2651 + 2652 + postcss-normalize-unicode@7.0.5: 2653 + resolution: {integrity: sha512-X6BBwiRxVaFHrb2WyBMddIeB5HBjJcAaUHyhLrM2FsxSq5TFqcHSsK7Zu1otag+o0ZphQGJewGH1tAyrD0zX1Q==} 2654 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2655 + peerDependencies: 2656 + postcss: ^8.4.32 2657 + 2658 + postcss-normalize-url@7.0.1: 2659 + resolution: {integrity: sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==} 2660 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2661 + peerDependencies: 2662 + postcss: ^8.4.32 2663 + 2664 + postcss-normalize-whitespace@7.0.1: 2665 + resolution: {integrity: sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==} 2666 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2667 + peerDependencies: 2668 + postcss: ^8.4.32 2669 + 2670 + postcss-ordered-values@7.0.2: 2671 + resolution: {integrity: sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==} 2672 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2673 + peerDependencies: 2674 + postcss: ^8.4.32 2675 + 2676 + postcss-reduce-initial@7.0.5: 2677 + resolution: {integrity: sha512-RHagHLidG8hTZcnr4FpyMB2jtgd/OcyAazjMhoy5qmWJOx1uxKh4ntk0Pb46ajKM0rkf32lRH4C8c9qQiPR6IA==} 2678 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2679 + peerDependencies: 2680 + postcss: ^8.4.32 2681 + 2682 + postcss-reduce-transforms@7.0.1: 2683 + resolution: {integrity: sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==} 2684 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2685 + peerDependencies: 2686 + postcss: ^8.4.32 2687 + 2688 postcss-selector-parser@6.1.2: 2689 resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} 2690 engines: {node: '>=4'} 2691 + 2692 + postcss-selector-parser@7.1.1: 2693 + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} 2694 + engines: {node: '>=4'} 2695 + 2696 + postcss-svgo@7.1.0: 2697 + resolution: {integrity: sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==} 2698 + engines: {node: ^18.12.0 || ^20.9.0 || >= 18} 2699 + peerDependencies: 2700 + postcss: ^8.4.32 2701 + 2702 + postcss-unique-selectors@7.0.4: 2703 + resolution: {integrity: sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==} 2704 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2705 + peerDependencies: 2706 + postcss: ^8.4.32 2707 2708 postcss-value-parser@4.2.0: 2709 resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} ··· 2867 resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} 2868 engines: {node: '>=10'} 2869 2870 + sax@1.4.3: 2871 + resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} 2872 + 2873 scheduler@0.23.2: 2874 resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} 2875 ··· 2966 resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==} 2967 engines: {node: '>=0.10.0'} 2968 2969 + stylehacks@7.0.7: 2970 + resolution: {integrity: sha512-bJkD0JkEtbRrMFtwgpJyBbFIwfDDONQ1Ov3sDLZQP8HuJ73kBOyx66H4bOcAbVWmnfLdvQ0AJwXxOMkpujcO6g==} 2971 + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} 2972 + peerDependencies: 2973 + postcss: ^8.4.32 2974 + 2975 sucrase@3.35.1: 2976 resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} 2977 engines: {node: '>=16 || 14 >=14.17'} ··· 2983 2984 svg-parser@2.0.4: 2985 resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} 2986 + 2987 + svgo@4.0.0: 2988 + resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} 2989 + engines: {node: '>=16'} 2990 + hasBin: true 2991 2992 tailwindcss@3.4.19: 2993 resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} ··· 3153 optional: true 3154 terser: 3155 optional: true 3156 + 3157 + webextension-polyfill@0.12.0: 3158 + resolution: {integrity: sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q==} 3159 3160 webidl-conversions@3.0.1: 3161 resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} ··· 4183 csstype: 3.2.3 4184 4185 '@types/triple-beam@1.3.5': {} 4186 + 4187 + '@types/webextension-polyfill@0.12.4': {} 4188 4189 '@types/yauzl@2.10.3': 4190 dependencies: ··· 4415 dependencies: 4416 file-uri-to-path: 1.0.0 4417 4418 + boolbase@1.0.0: {} 4419 + 4420 brace-expansion@2.0.2: 4421 dependencies: 4422 balanced-match: 1.0.2 ··· 4451 camelcase-css@2.0.1: {} 4452 4453 camelcase@6.3.0: {} 4454 + 4455 + caniuse-api@3.0.0: 4456 + dependencies: 4457 + browserslist: 4.28.1 4458 + caniuse-lite: 1.0.30001761 4459 + lodash.memoize: 4.1.2 4460 + lodash.uniq: 4.5.0 4461 4462 caniuse-lite@1.0.30001761: {} 4463 ··· 4506 color-convert: 3.1.3 4507 color-string: 2.1.4 4508 4509 + colord@2.9.3: {} 4510 + 4511 commander@10.0.1: {} 4512 + 4513 + commander@11.1.0: {} 4514 4515 commander@12.1.0: {} 4516 ··· 4570 path-key: 3.1.1 4571 shebang-command: 2.0.0 4572 which: 2.0.2 4573 + 4574 + css-declaration-sorter@7.3.0(postcss@8.5.6): 4575 + dependencies: 4576 + postcss: 8.5.6 4577 + 4578 + css-select@5.2.2: 4579 + dependencies: 4580 + boolbase: 1.0.0 4581 + css-what: 6.2.2 4582 + domhandler: 5.0.3 4583 + domutils: 3.2.2 4584 + nth-check: 2.1.1 4585 + 4586 + css-tree@2.2.1: 4587 + dependencies: 4588 + mdn-data: 2.0.28 4589 + source-map-js: 1.2.1 4590 + 4591 + css-tree@3.1.0: 4592 + dependencies: 4593 + mdn-data: 2.12.2 4594 + source-map-js: 1.2.1 4595 + 4596 + css-what@6.2.2: {} 4597 4598 cssesc@3.0.0: {} 4599 4600 + cssnano-preset-default@7.0.10(postcss@8.5.6): 4601 + dependencies: 4602 + browserslist: 4.28.1 4603 + css-declaration-sorter: 7.3.0(postcss@8.5.6) 4604 + cssnano-utils: 5.0.1(postcss@8.5.6) 4605 + postcss: 8.5.6 4606 + postcss-calc: 10.1.1(postcss@8.5.6) 4607 + postcss-colormin: 7.0.5(postcss@8.5.6) 4608 + postcss-convert-values: 7.0.8(postcss@8.5.6) 4609 + postcss-discard-comments: 7.0.5(postcss@8.5.6) 4610 + postcss-discard-duplicates: 7.0.2(postcss@8.5.6) 4611 + postcss-discard-empty: 7.0.1(postcss@8.5.6) 4612 + postcss-discard-overridden: 7.0.1(postcss@8.5.6) 4613 + postcss-merge-longhand: 7.0.5(postcss@8.5.6) 4614 + postcss-merge-rules: 7.0.7(postcss@8.5.6) 4615 + postcss-minify-font-values: 7.0.1(postcss@8.5.6) 4616 + postcss-minify-gradients: 7.0.1(postcss@8.5.6) 4617 + postcss-minify-params: 7.0.5(postcss@8.5.6) 4618 + postcss-minify-selectors: 7.0.5(postcss@8.5.6) 4619 + postcss-normalize-charset: 7.0.1(postcss@8.5.6) 4620 + postcss-normalize-display-values: 7.0.1(postcss@8.5.6) 4621 + postcss-normalize-positions: 7.0.1(postcss@8.5.6) 4622 + postcss-normalize-repeat-style: 7.0.1(postcss@8.5.6) 4623 + postcss-normalize-string: 7.0.1(postcss@8.5.6) 4624 + postcss-normalize-timing-functions: 7.0.1(postcss@8.5.6) 4625 + postcss-normalize-unicode: 7.0.5(postcss@8.5.6) 4626 + postcss-normalize-url: 7.0.1(postcss@8.5.6) 4627 + postcss-normalize-whitespace: 7.0.1(postcss@8.5.6) 4628 + postcss-ordered-values: 7.0.2(postcss@8.5.6) 4629 + postcss-reduce-initial: 7.0.5(postcss@8.5.6) 4630 + postcss-reduce-transforms: 7.0.1(postcss@8.5.6) 4631 + postcss-svgo: 7.1.0(postcss@8.5.6) 4632 + postcss-unique-selectors: 7.0.4(postcss@8.5.6) 4633 + 4634 + cssnano-utils@5.0.1(postcss@8.5.6): 4635 + dependencies: 4636 + postcss: 8.5.6 4637 + 4638 + cssnano@7.1.2(postcss@8.5.6): 4639 + dependencies: 4640 + cssnano-preset-default: 7.0.10(postcss@8.5.6) 4641 + lilconfig: 3.1.3 4642 + postcss: 8.5.6 4643 + 4644 + csso@5.0.5: 4645 + dependencies: 4646 + css-tree: 2.2.1 4647 + 4648 csstype@3.2.3: {} 4649 4650 date-fns@4.1.0: {} ··· 4724 path-type: 4.0.0 4725 4726 dlv@1.1.3: {} 4727 + 4728 + dom-serializer@2.0.0: 4729 + dependencies: 4730 + domelementtype: 2.3.0 4731 + domhandler: 5.0.3 4732 + entities: 4.5.0 4733 + 4734 + domelementtype@2.3.0: {} 4735 + 4736 + domhandler@5.0.3: 4737 + dependencies: 4738 + domelementtype: 2.3.0 4739 + 4740 + domutils@3.2.2: 4741 + dependencies: 4742 + dom-serializer: 2.0.0 4743 + domelementtype: 2.3.0 4744 + domhandler: 5.0.3 4745 4746 dot-case@3.0.4: 4747 dependencies: ··· 5210 dependencies: 5211 p-locate: 6.0.0 5212 5213 + lodash.memoize@4.1.2: {} 5214 + 5215 + lodash.uniq@4.5.0: {} 5216 + 5217 lodash@4.17.21: {} 5218 5219 logform@2.7.0: ··· 5252 make-dir@3.1.0: 5253 dependencies: 5254 semver: 6.3.1 5255 + 5256 + mdn-data@2.0.28: {} 5257 + 5258 + mdn-data@2.12.2: {} 5259 5260 merge-options@3.0.4: 5261 dependencies: ··· 5342 dependencies: 5343 path-key: 4.0.0 5344 5345 + nth-check@2.1.1: 5346 + dependencies: 5347 + boolbase: 1.0.0 5348 + 5349 object-assign@4.1.1: {} 5350 5351 object-hash@3.0.0: {} ··· 5456 dependencies: 5457 find-up: 4.1.0 5458 5459 + postcss-calc@10.1.1(postcss@8.5.6): 5460 + dependencies: 5461 + postcss: 8.5.6 5462 + postcss-selector-parser: 7.1.1 5463 + postcss-value-parser: 4.2.0 5464 + 5465 + postcss-colormin@7.0.5(postcss@8.5.6): 5466 + dependencies: 5467 + browserslist: 4.28.1 5468 + caniuse-api: 3.0.0 5469 + colord: 2.9.3 5470 + postcss: 8.5.6 5471 + postcss-value-parser: 4.2.0 5472 + 5473 + postcss-convert-values@7.0.8(postcss@8.5.6): 5474 + dependencies: 5475 + browserslist: 4.28.1 5476 + postcss: 8.5.6 5477 + postcss-value-parser: 4.2.0 5478 + 5479 + postcss-discard-comments@7.0.5(postcss@8.5.6): 5480 + dependencies: 5481 + postcss: 8.5.6 5482 + postcss-selector-parser: 7.1.1 5483 + 5484 + postcss-discard-duplicates@7.0.2(postcss@8.5.6): 5485 + dependencies: 5486 + postcss: 8.5.6 5487 + 5488 + postcss-discard-empty@7.0.1(postcss@8.5.6): 5489 + dependencies: 5490 + postcss: 8.5.6 5491 + 5492 + postcss-discard-overridden@7.0.1(postcss@8.5.6): 5493 + dependencies: 5494 + postcss: 8.5.6 5495 + 5496 postcss-import@15.1.0(postcss@8.5.6): 5497 dependencies: 5498 postcss: 8.5.6 ··· 5512 jiti: 1.21.7 5513 postcss: 8.5.6 5514 5515 + postcss-merge-longhand@7.0.5(postcss@8.5.6): 5516 + dependencies: 5517 + postcss: 8.5.6 5518 + postcss-value-parser: 4.2.0 5519 + stylehacks: 7.0.7(postcss@8.5.6) 5520 + 5521 + postcss-merge-rules@7.0.7(postcss@8.5.6): 5522 + dependencies: 5523 + browserslist: 4.28.1 5524 + caniuse-api: 3.0.0 5525 + cssnano-utils: 5.0.1(postcss@8.5.6) 5526 + postcss: 8.5.6 5527 + postcss-selector-parser: 7.1.1 5528 + 5529 + postcss-minify-font-values@7.0.1(postcss@8.5.6): 5530 + dependencies: 5531 + postcss: 8.5.6 5532 + postcss-value-parser: 4.2.0 5533 + 5534 + postcss-minify-gradients@7.0.1(postcss@8.5.6): 5535 + dependencies: 5536 + colord: 2.9.3 5537 + cssnano-utils: 5.0.1(postcss@8.5.6) 5538 + postcss: 8.5.6 5539 + postcss-value-parser: 4.2.0 5540 + 5541 + postcss-minify-params@7.0.5(postcss@8.5.6): 5542 + dependencies: 5543 + browserslist: 4.28.1 5544 + cssnano-utils: 5.0.1(postcss@8.5.6) 5545 + postcss: 8.5.6 5546 + postcss-value-parser: 4.2.0 5547 + 5548 + postcss-minify-selectors@7.0.5(postcss@8.5.6): 5549 + dependencies: 5550 + cssesc: 3.0.0 5551 + postcss: 8.5.6 5552 + postcss-selector-parser: 7.1.1 5553 + 5554 postcss-nested@6.2.0(postcss@8.5.6): 5555 dependencies: 5556 postcss: 8.5.6 5557 postcss-selector-parser: 6.1.2 5558 5559 + postcss-normalize-charset@7.0.1(postcss@8.5.6): 5560 + dependencies: 5561 + postcss: 8.5.6 5562 + 5563 + postcss-normalize-display-values@7.0.1(postcss@8.5.6): 5564 + dependencies: 5565 + postcss: 8.5.6 5566 + postcss-value-parser: 4.2.0 5567 + 5568 + postcss-normalize-positions@7.0.1(postcss@8.5.6): 5569 + dependencies: 5570 + postcss: 8.5.6 5571 + postcss-value-parser: 4.2.0 5572 + 5573 + postcss-normalize-repeat-style@7.0.1(postcss@8.5.6): 5574 + dependencies: 5575 + postcss: 8.5.6 5576 + postcss-value-parser: 4.2.0 5577 + 5578 + postcss-normalize-string@7.0.1(postcss@8.5.6): 5579 + dependencies: 5580 + postcss: 8.5.6 5581 + postcss-value-parser: 4.2.0 5582 + 5583 + postcss-normalize-timing-functions@7.0.1(postcss@8.5.6): 5584 + dependencies: 5585 + postcss: 8.5.6 5586 + postcss-value-parser: 4.2.0 5587 + 5588 + postcss-normalize-unicode@7.0.5(postcss@8.5.6): 5589 + dependencies: 5590 + browserslist: 4.28.1 5591 + postcss: 8.5.6 5592 + postcss-value-parser: 4.2.0 5593 + 5594 + postcss-normalize-url@7.0.1(postcss@8.5.6): 5595 + dependencies: 5596 + postcss: 8.5.6 5597 + postcss-value-parser: 4.2.0 5598 + 5599 + postcss-normalize-whitespace@7.0.1(postcss@8.5.6): 5600 + dependencies: 5601 + postcss: 8.5.6 5602 + postcss-value-parser: 4.2.0 5603 + 5604 + postcss-ordered-values@7.0.2(postcss@8.5.6): 5605 + dependencies: 5606 + cssnano-utils: 5.0.1(postcss@8.5.6) 5607 + postcss: 8.5.6 5608 + postcss-value-parser: 4.2.0 5609 + 5610 + postcss-reduce-initial@7.0.5(postcss@8.5.6): 5611 + dependencies: 5612 + browserslist: 4.28.1 5613 + caniuse-api: 3.0.0 5614 + postcss: 8.5.6 5615 + 5616 + postcss-reduce-transforms@7.0.1(postcss@8.5.6): 5617 + dependencies: 5618 + postcss: 8.5.6 5619 + postcss-value-parser: 4.2.0 5620 + 5621 postcss-selector-parser@6.1.2: 5622 dependencies: 5623 cssesc: 3.0.0 5624 util-deprecate: 1.0.2 5625 5626 + postcss-selector-parser@7.1.1: 5627 + dependencies: 5628 + cssesc: 3.0.0 5629 + util-deprecate: 1.0.2 5630 + 5631 + postcss-svgo@7.1.0(postcss@8.5.6): 5632 + dependencies: 5633 + postcss: 8.5.6 5634 + postcss-value-parser: 4.2.0 5635 + svgo: 4.0.0 5636 + 5637 + postcss-unique-selectors@7.0.4(postcss@8.5.6): 5638 + dependencies: 5639 + postcss: 8.5.6 5640 + postcss-selector-parser: 7.1.1 5641 + 5642 postcss-value-parser@4.2.0: {} 5643 5644 postcss-values-parser@6.0.2(postcss@8.5.6): ··· 5836 safe-buffer@5.2.1: {} 5837 5838 safe-stable-stringify@2.5.0: {} 5839 + 5840 + sax@1.4.3: {} 5841 5842 scheduler@0.23.2: 5843 dependencies: ··· 5934 dependencies: 5935 escape-string-regexp: 1.0.5 5936 5937 + stylehacks@7.0.7(postcss@8.5.6): 5938 + dependencies: 5939 + browserslist: 4.28.1 5940 + postcss: 8.5.6 5941 + postcss-selector-parser: 7.1.1 5942 + 5943 sucrase@3.35.1: 5944 dependencies: 5945 '@jridgewell/gen-mapping': 0.3.13 ··· 5953 supports-preserve-symlinks-flag@1.0.0: {} 5954 5955 svg-parser@2.0.4: {} 5956 + 5957 + svgo@4.0.0: 5958 + dependencies: 5959 + commander: 11.1.0 5960 + css-select: 5.2.2 5961 + css-tree: 3.1.0 5962 + css-what: 6.2.2 5963 + csso: 5.0.5 5964 + picocolors: 1.1.1 5965 + sax: 1.4.3 5966 5967 tailwindcss@3.4.19: 5968 dependencies: ··· 6122 optionalDependencies: 6123 '@types/node': 24.10.4 6124 fsevents: 2.3.3 6125 + 6126 + webextension-polyfill@0.12.0: {} 6127 6128 webidl-conversions@3.0.1: {} 6129