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.

+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 |
+53 -13
docs/git-history.json
··· 1 [ 2 { 3 - "hash": "e04934ffb5e2d78791fcd23bc3afeb4d438a5546", 4 - "short_hash": "e04934f", 5 "author": "Ariel M. Lighty", 6 - "date": "2025-12-26T21:57:05-05:00", 7 - "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.", 8 - "files_changed": 1 9 }, 10 { 11 "hash": "e04934ffb5e2d78791fcd23bc3afeb4d438a5546", ··· 21 "author": "Ariel M. Lighty", 22 "date": "2025-12-26T21:46:06-05:00", 23 "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.", 24 - "files_changed": 1 25 - }, 26 - { 27 - "hash": "46626f4a18eaaaaf42368361130bb1ddc7bd9677", 28 - "short_hash": "46626f4", 29 - "author": "Ariel M. Lighty", 30 - "date": "2025-12-26T21:20:34-05:00", 31 - "message": "fix: show loading screen during extension upload search\n\nPreviously when loading an upload from extension that hadn't been searched yet,\nthe app would immediately navigate to the results page showing 'none' for all\nmatches, then update them as the search progressed.\n\nNow it behaves like the file upload flow:\n- Shows loading screen during search\n- Navigates to results only after search completes and results are saved\n- If upload already has matches, navigates to results immediately", 32 "files_changed": 1 33 }, 34 {
··· 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", ··· 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 {
+1439 -64
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 { ··· 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": "pending", 3607 "created_at": "2025-12-27T15:38:21.291457500-05:00", 3608 - "updated_at": "2025-12-27T15:38:21.291457500-05:00", 3609 "metadata_json": "{\"branch\":\"master\",\"confidence\":100}" 3610 }, 3611 { ··· 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": "pending", 3618 "created_at": "2025-12-27T15:40:23.238704300-05:00", 3619 - "updated_at": "2025-12-27T15:40:23.238704300-05:00", 3620 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3621 }, 3622 { ··· 3627 "description": null, 3628 "status": "completed", 3629 "created_at": "2025-12-27T15:41:04.067444-05:00", 3630 - "updated_at": "2025-12-27T15:47:53.643256600-05:00", 3631 "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3632 }, 3633 { ··· 3638 "description": null, 3639 "status": "completed", 3640 "created_at": "2025-12-27T15:47:49.308750700-05:00", 3641 - "updated_at": "2025-12-27T15:47:55.838439500-05:00", 3642 "metadata_json": "{\"branch\":\"master\",\"confidence\":100}" 3643 } 3644 ], 3645 "edges": [ ··· 7294 "weight": 1.0, 7295 "rationale": "Successfully completed documentation updates", 7296 "created_at": "2025-12-27T15:47:51.427087400-05:00" 7297 } 7298 ] 7299 }
··· 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 { ··· 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 { ··· 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 { ··· 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 { ··· 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": [ ··· 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 }
+51 -53
packages/extension/src/popup/popup.html
··· 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 in the ATmosphere</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"> 21 Go to your Twitter/X Following page to start 22 </p> 23 - <p class="hint">Visit x.com/yourusername/following</p> 24 </div> 25 26 <!-- Ready state --> 27 - <div id="state-ready" class="state hidden"> 28 - <div class="icon">✅</div> 29 - <p class="message"> 30 Ready to scan <span id="platform-name"></span> 31 </p> 32 - <button id="btn-start" class="btn-primary"> 33 Start Scan 34 </button> 35 </div> 36 37 <!-- Scraping state --> 38 - <div id="state-scraping" class="state hidden"> 39 - <div class="icon spinner">⏳</div> 40 - <p class="message">Scanning...</p> 41 - <div class="progress"> 42 - <div class="progress-bar"> 43 - <div id="progress-fill" class="progress-fill"></div> 44 </div> 45 - <p class="progress-text"> 46 Found <span id="count">0</span> users 47 </p> 48 - <p id="status-message" class="status-message"></p> 49 </div> 50 </div> 51 52 <!-- Complete state --> 53 - <div id="state-complete" class="state hidden"> 54 - <div class="icon">🎉</div> 55 - <p class="message">Scan complete!</p> 56 - <p class="count-display"> 57 - Found <strong id="final-count">0</strong> users 58 </p> 59 - <button id="btn-upload" class="btn-primary"> 60 Open in ATlast 61 </button> 62 </div> 63 64 <!-- Uploading state --> 65 - <div id="state-uploading" class="state hidden"> 66 - <div class="icon spinner">📤</div> 67 - <p class="message">Uploading to ATlast...</p> 68 </div> 69 70 <!-- Error state --> 71 - <div id="state-error" class="state hidden"> 72 - <div class="icon">⚠️</div> 73 - <p class="message">Error</p> 74 - <p id="error-message" class="error-message"></p> 75 - <button id="btn-retry" class="btn-secondary"> 76 Try Again 77 </button> 78 </div> 79 80 <!-- Server offline state --> 81 - <div id="state-offline" class="state hidden"> 82 - <div class="icon">🔌</div> 83 - <p class="message">ATlast server not running</p> 84 - <p class="error-message"> 85 Start the dev server:<br /> 86 - <code>npx netlify-cli dev --filter @atlast/web</code> 87 </p> 88 - <p class="hint" id="server-url"></p> 89 - <button id="btn-check-server" class="btn-primary"> 90 Check Again 91 </button> 92 </div> 93 94 <!-- Not logged in state --> 95 - <div id="state-not-logged-in" class="state hidden"> 96 - <div class="icon">🔐</div> 97 - <p class="message">Not logged in to ATlast</p> 98 - <p class="error-message"> 99 Please log in to ATlast first, then return here to scan. 100 </p> 101 - <button id="btn-open-atlast" class="btn-primary"> 102 Open ATlast 103 </button> 104 - <button id="btn-retry-login" class="btn-secondary"> 105 Check Again 106 </button> 107 </div> 108 </main> 109 110 - <footer> 111 - <a href="https://atlast.byarielm.fyi" target="_blank" 112 - >atlast.byarielm.fyi</a 113 - > 114 </footer> 115 </div> 116
··· 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
+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 };
+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