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