commits
- Extension now reads session cookie using browser.cookies.get()
- Passes session as query parameter to work around SameSite=Lax
- Firefox cookie partitioning prevents credentials:include from working
- Works for both development and production cookie names
- Error middleware now passes event parameter to errorResponse
- Fixes Firefox extension CORS headers on authentication errors
- Both withErrorHandling and withAuthErrorHandling updated
- Extension origin properly reflected in all error responses
- Created /health function endpoint with CORS support
- Updated checkServerHealth to use function endpoint instead of root URL
- Fixes Firefox extension server detection with proper CORS headers
- Added moz-extension:// origin detection to CORS handler
- Added cookies permission to Firefox manifest for credentialed requests
- Functions now properly reflect Firefox extension origins with credentials
Implemented cross-browser compatibility for the extension:
- Installed webextension-polyfill for unified browser.* API
- Replaced all chrome.* API calls with browser.* imports
- Updated build system to output both chrome/ and firefox/ directories
- Created Firefox-specific manifest with browser_specific_settings
Updated CORS headers to support credentials from Chrome extensions:
- Added getCorsHeaders() to detect chrome-extension:// origins
- Changed from wildcard Access-Control-Allow-Origin to specific origin
- Added Access-Control-Allow-Credentials: true for credentialed requests
- Updated session endpoint to pass event for CORS header detection
- Changed darkMode from 'class' to 'media' for automatic system preference detection
- Made server offline message conditional on build mode (dev vs prod)
- Hide dev server instructions in production builds
Replaced 299 lines of vanilla CSS with Tailwind for design consistency with web app. Production build minified to 13KB.
Updated 4 markdown files with current state:
EXTENSION_STATUS.md:
- Changed status from DEBUGGING to COMPLETE
- Updated decision graph count (295 → 332 nodes)
- Added recently completed section (nodes 296-332)
- Marked all extension bugs as resolved
CONTRIBUTING.md:
- Replaced npm with pnpm throughout
- Added monorepo structure documentation
- Updated development commands (netlify-cli dev --filter)
- Added extension development workflow
PLAN.md:
- Updated status to Phase 1 COMPLETE
- Added all recent fixes to completion list
- Updated decision graph count to 332 nodes
- Added changelog entries for latest work
packages/extension/README.md:
- Added prerequisites section (dev server + login required)
- Updated build commands with dev/prod distinction
- Added Step 0: Start ATlast Dev Server
- Added common issues for auth and server states
All files now accurately reflect completion status and use pnpm.
Updated CLAUDE.md with comprehensive node lifecycle management:
- Added node status transitions (pending → in_progress → completed)
- Correct orphan detection commands (awk instead of cut)
- Common mistakes section with examples
- Enhanced audit checklist with status verification
- Verification workflow after node creation
Also updated extension popup with ATmosphere branding.
Decision graph now at 331 nodes, 332 edges - all orphans resolved.
Added explicit optimizeDeps.include to pre-bundle common dependencies:
- React ecosystem (react, react-dom, react-router-dom)
- Icon libraries (@icons-pack/react-simple-icons, lucide-react)
- Other deps (date-fns, jszip, zustand, @tanstack/react-virtual)
Also added server.fs.allow config for monorepo file serving.
This should speed up subsequent dev server starts by ensuring these
dependencies are consistently pre-bundled.
Changed all TIMESTAMP columns to TIMESTAMPTZ (timestamp with timezone) to
properly handle timezone-aware timestamps across all tables:
- oauth_states (created_at, expires_at)
- oauth_sessions (created_at, expires_at)
- user_sessions (created_at, expires_at)
- user_uploads (created_at, last_checked)
- source_accounts (last_checked, match_found_at, created_at)
- user_source_follows (created_at)
- atproto_matches (found_at, last_verified, last_follow_check)
- user_match_status (notified_at, viewed_at, followed_at, dismissed_at)
- notification_queue (created_at, sent_at)
This fixes the 5-hour timezone offset issue where timestamps were stored
without timezone info, causing display errors across different timezones.
Previously when loading an upload from extension that hadn't been searched yet,
the app would immediately navigate to the results page showing 'none' for all
matches, then update them as the search progressed.
Now it behaves like the file upload flow:
- Shows loading screen during search
- Navigates to results only after search completes and results are saved
- If upload already has matches, navigates to results immediately
Fixes issue where results were displayed but not saved to database until
page refresh. Root cause: onComplete callback accessed stale searchResults
from closure instead of updated state.
Changes:
- useSearch.searchAllUsers: onComplete now receives SearchResult[] param
- useSearch: uses setSearchResults updater to get current state
- App.tsx: updated all 3 searchAllUsers calls to use finalResults
- Removed setTimeout workarounds
Result: Extension and file upload flows now save immediately after search.
Changed from hasRecentUpload time check to getUpload ID check.
Extension flow:
1. extension-import creates upload with no matches
2. Frontend searches and finds matches
3. save-results now UPDATES upload with matches (was skipping)
File upload flow still works:
- Upload doesn't exist -> creates it
- Upload exists -> updates it with new matches
Removes race condition where save within 5 seconds would be skipped.
Was setting sourceUser to result.sourceUser.username (string)
Should be result.sourceUser (SourceUser object)
This caused:
- useSearch to call batch.map(r => r.sourceUser.username) on strings
- .username on string returns undefined
- batch-search-actors received null values
- ValidationError: expected string, received null
Also caused localeCompare error when sorting undefined values.
Backend (extension-import.ts):
- Now creates user_source_follows entries linking upload to source accounts
- Without these, get-upload-details returned empty (queries FROM user_source_follows)
- Uses bulkCreate return value (Map<username, id>) to create links
Frontend (App.tsx):
- handleLoadUpload now detects if upload has no matches yet
- Sets isSearching: true for new uploads
- Automatically triggers searchAllUsers for new uploads
- Saves results after search completes
- Changed platform from hardcoded "tiktok" to "twitter"
Frontend (HistoryTab.tsx):
- Fixed time display: removed "Uploaded" prefix
- Now shows "about 5 hours ago" instead of "Uploaded in about 5 hours"
- formatRelativeTime with addSuffix already provides complete sentence
Resolves:
- Empty results on page load
- No automatic searching
- History navigation not working (will work after search)
- Grammatically incorrect time display
Backend fixes:
- Use SourceAccountRepository.bulkCreate() instead of non-existent upsertSourceAccount()
- Change redirectUrl from /results?uploadId= to /?uploadId=
- More efficient bulk insert instead of loop
Frontend fixes:
- Add useEffect to load results when uploadId param present
- Calls loadUploadResults(uploadId) automatically on page load
- Cleans up URL param after loading
Resolves:
- "sourceAccountRepo.upsertSourceAccount is not a function" error
- "No routes matched location /results?uploadId=..." routing error
Backend endpoints use successResponse() which wraps data in:
{ success: true, data: {...} }
Extension was expecting flat response structure, causing:
- uploadToATlast to return undefined (missing importId, redirectUrl)
- checkSession to return wrapped object instead of user data
- Invalid URL error: "http://127.0.0.1:8888undefined"
Fixed both uploadToATlast and checkSession to access apiResponse.data
PLAN.md updates:
- Added current status section with recent fixes and active work
- Marked Phase 0 as complete
- Marked Phase 1 as in progress (debugging)
- Updated changelog with 2025-12-26 progress
- Updated decision graph count to 288 nodes
EXTENSION_STATUS.md updates:
- Changed state from READY FOR TESTING to DEBUGGING
- Added fixed issues section (NaN bug, database init)
- Added active debugging section
- Updated decision graph summary to 288 nodes
- Added node references for recent fixes (#287-288)
Decision graph:
- Synced with latest nodes (288 total, 276 edges)
- Tracked database initialization outcome
The createUpload method expects 5 parameters but we were only passing 4,
causing NaN to be inserted for unmatched_users calculation. Now passing 0
for matchedUsers (will be updated after search is performed).
Removed temporary storage approach and implemented proper authentication flow:
Extension changes:
- Added session check to popup init flow (checkSession in api-client)
- Added "not logged in" state with login prompts
- Updated uploadToATlast to include credentials for cookie-based auth
- Extension now requires user to be logged in BEFORE scanning
Backend changes:
- Converted extension-import to AuthenticatedHandler (requires auth)
- Now creates upload records immediately (no temporary storage)
- Removed extension_imports table from database schema
- Deleted get-extension-import function (no longer needed)
- Deleted import-store utility (temporary approach removed)
Frontend changes:
- Removed ExtensionImport page and /import/:id route
- Extension uploads now use same flow as file uploads
This matches the correct user flow: user logs in to ATlast first, then
extension creates permanent upload records directly (same as file uploads).
Built extension successfully for dev environment.
Fix CORS blocking extension health checks and API calls:
- Add http://127.0.0.1:8888/* (dev)
- Add http://localhost:8888/* (alt dev)
- Add https://atlast.byarielm.fyi/* (prod)
Extension can now make requests to ATlast servers without
CORS errors.
Features:
- Check server health on popup init (dev mode only)
- Show 'server offline' state with setup instructions
- 'Check Again' button to retry connection
- Display target server URL for debugging
- 3-second timeout for health checks
Fixes port 8888 conflict workflow - extension now prompts user
to start dev server instead of hanging silently.
Critical bug fix: extension-import and get-extension-import were using
separate in-memory Maps, causing 404 errors when fetching import data.
- Create shared utils/import-store.ts module
- Both functions now use same Map instance
- Add logging for debugging
- Note: In-memory storage only works for dev (single process)
Production needs database/Redis/Netlify Blobs
- Install react-router-dom
- Create Router component with / and /import/:id routes
- Update main.tsx to use Router
- Enables URL-based navigation for extension imports
- Add dev vs prod build modes using --prod flag
- Inject API URL at build time via esbuild define
- Dev: http://127.0.0.1:8888
- Prod: https://atlast.byarielm.fyi
- Add build:prod and package:prod scripts
Check if usernames array has items before attempting upload.
Shows clear error message instead of hanging.
Changed from [data-testid="UserName"] (doesn't exist) to
[data-testid="UserCell"] (actual DOM element). Extract username
from profile link href instead of span text.
Uses @media (prefers-color-scheme: dark) to match browser preference.
Dark theme: slate-900/indigo-950/blue-900 backgrounds, cyan-50 text,
cyan borders on buttons, matches web app dark mode colors.
The onMessage wrapper in messaging.ts was only sending {success: true}
instead of the actual handler return value. This caused the popup to
receive undefined state even though the background worker was correctly
storing it.
Changes:
- messaging.ts: Changed onMessage to forward handler return values
- background service-worker.ts: Added comprehensive logging
- popup.ts: Added state change listener and detailed logging
This fixes the issue where popup showed 'Go to...' even when on the
following page.
Created comprehensive README.md with:
- Build instructions
- Chrome loading steps
- Step-by-step testing guide
- Console logging documentation
- Common issues and solutions
- Architecture overview
- Future enhancements roadmap
Includes debugging tips for URL pattern detection issues.
Changed followingPathPattern from strict /^\/[^/]+\/following$/
to flexible /^\/?([^/]+\/)?following\/?$/ to handle:
- /username/following
- /following (if Twitter uses this)
- Optional trailing slashes
This fixes detection issue where extension wouldn't recognize
the following page and show 'ready' state.
Changed production API URL from atlast.app to atlast.byarielm.fyi in:
- api-client.ts: ATLAST_API_URL constant
- popup.html: footer link
Changed color scheme from purple/indigo to purple/cyan/orange:
- Header: gradient from yellow-400 → orange-500 → pink-600 (firefly banner)
- Background: purple-50 → white → cyan-50 gradient
- Primary button: orange-600 with hover state
- Secondary button: purple-800 border
- Progress bar: orange → pink gradient
- Links and accents: orange-600
Matches web app's tailwind.config.js color system.
Built ATlast Importer browser extension with:
- Twitter Following page scraper using stable selectors
- Extension popup UI with scan progress and status
- Background service worker for state management
- API client for uploading to ATlast
- Netlify functions: extension-import, get-extension-import
- Web app integration via importId URL parameter
- Build system with esbuild and TypeScript
Extension flow:
1. User visits x.com/{username}/following
2. Click extension icon -> Start Scan
3. Extension scrolls and collects usernames
4. POST to /extension-import endpoint
5. Redirect to ATlast with importId parameter
6. Web app fetches import data and starts Bluesky search
Extensible architecture ready for Threads/Instagram/TikTok.
Fixed Netlify CLI monorepo detection issue by using --filter flag:
- Updated root package.json scripts to use 'npx netlify-cli dev --filter @atlast/web'
- Updated netlify.toml [dev] section to use npm with --prefix for framework command
- Added monorepo development instructions to CLAUDE.md
- Documented Windows Git Bash compatibility issue with netlify command
Solution: Use 'npx netlify-cli dev --filter @atlast/web' to bypass monorepo
project selection prompt and specify which workspace package to run.
Dev server now runs successfully at http://localhost:8888 with all backend
functions loaded.
Restructured codebase into pnpm workspace with three packages:
- packages/web: React frontend (from src/)
- packages/functions: Netlify serverless functions (from netlify/functions/)
- packages/shared: Shared TypeScript types for Platform and Import APIs
Changes:
- Created pnpm-workspace.yaml for workspace configuration
- Moved all web app files to packages/web/
- Moved all Netlify functions to packages/functions/src/
- Created packages/shared with Platform enum and ExtensionImportRequest/Response types
- Updated netlify.toml to point to new paths
- Updated root package.json scripts to use pnpm workspace commands
- All dependencies split appropriately between packages
Phase 0 (Monorepo Migration) from PLAN.md completed successfully.
Builds and dev server tested and working.
These directories are already in .gitignore but were committed
before the ignore rules were added. Removed from tracking while
keeping local files intact.
Added guidance to run deciduous sync before commits and stage graph
updates together with code changes. Decision graph is part of code
history and should not be committed separately.
- Removed tooltip from HeroSection (ATmosphere now plain text)
- Added superscript info icon next to 'ATmosphere' in login form text
- Tooltip content left-aligned for better readability
- Maintains platform-agnostic design
actor-typeahead component doesn't expose avatar data via events or attributes.
Added debounced API fetch (300ms) to searchActorsTypeahead endpoint when
handle is entered. Avatar now displays for both typeahead selections and
manually entered handles.
Created useRotatingPlaceholder hook:
- Rotates through 4 platform examples every 3 seconds
- .bsky.social, .blacksky.app, .tgnl.sh, .com
- Demonstrates platform flexibility without overwhelming users
Created HandleInput component:
- Shows @ symbol by default
- Replaces @ with profile pic when handle selected from typeahead
- Extracts avatar from typeahead data-avatar or actor-select event
- Clears avatar when input is cleared
Implemented cross-browser compatibility for the extension:
- Installed webextension-polyfill for unified browser.* API
- Replaced all chrome.* API calls with browser.* imports
- Updated build system to output both chrome/ and firefox/ directories
- Created Firefox-specific manifest with browser_specific_settings
Updated CORS headers to support credentials from Chrome extensions:
- Added getCorsHeaders() to detect chrome-extension:// origins
- Changed from wildcard Access-Control-Allow-Origin to specific origin
- Added Access-Control-Allow-Credentials: true for credentialed requests
- Updated session endpoint to pass event for CORS header detection
Updated 4 markdown files with current state:
EXTENSION_STATUS.md:
- Changed status from DEBUGGING to COMPLETE
- Updated decision graph count (295 → 332 nodes)
- Added recently completed section (nodes 296-332)
- Marked all extension bugs as resolved
CONTRIBUTING.md:
- Replaced npm with pnpm throughout
- Added monorepo structure documentation
- Updated development commands (netlify-cli dev --filter)
- Added extension development workflow
PLAN.md:
- Updated status to Phase 1 COMPLETE
- Added all recent fixes to completion list
- Updated decision graph count to 332 nodes
- Added changelog entries for latest work
packages/extension/README.md:
- Added prerequisites section (dev server + login required)
- Updated build commands with dev/prod distinction
- Added Step 0: Start ATlast Dev Server
- Added common issues for auth and server states
All files now accurately reflect completion status and use pnpm.
Updated CLAUDE.md with comprehensive node lifecycle management:
- Added node status transitions (pending → in_progress → completed)
- Correct orphan detection commands (awk instead of cut)
- Common mistakes section with examples
- Enhanced audit checklist with status verification
- Verification workflow after node creation
Also updated extension popup with ATmosphere branding.
Decision graph now at 331 nodes, 332 edges - all orphans resolved.
Added explicit optimizeDeps.include to pre-bundle common dependencies:
- React ecosystem (react, react-dom, react-router-dom)
- Icon libraries (@icons-pack/react-simple-icons, lucide-react)
- Other deps (date-fns, jszip, zustand, @tanstack/react-virtual)
Also added server.fs.allow config for monorepo file serving.
This should speed up subsequent dev server starts by ensuring these
dependencies are consistently pre-bundled.
Changed all TIMESTAMP columns to TIMESTAMPTZ (timestamp with timezone) to
properly handle timezone-aware timestamps across all tables:
- oauth_states (created_at, expires_at)
- oauth_sessions (created_at, expires_at)
- user_sessions (created_at, expires_at)
- user_uploads (created_at, last_checked)
- source_accounts (last_checked, match_found_at, created_at)
- user_source_follows (created_at)
- atproto_matches (found_at, last_verified, last_follow_check)
- user_match_status (notified_at, viewed_at, followed_at, dismissed_at)
- notification_queue (created_at, sent_at)
This fixes the 5-hour timezone offset issue where timestamps were stored
without timezone info, causing display errors across different timezones.
Previously when loading an upload from extension that hadn't been searched yet,
the app would immediately navigate to the results page showing 'none' for all
matches, then update them as the search progressed.
Now it behaves like the file upload flow:
- Shows loading screen during search
- Navigates to results only after search completes and results are saved
- If upload already has matches, navigates to results immediately
Fixes issue where results were displayed but not saved to database until
page refresh. Root cause: onComplete callback accessed stale searchResults
from closure instead of updated state.
Changes:
- useSearch.searchAllUsers: onComplete now receives SearchResult[] param
- useSearch: uses setSearchResults updater to get current state
- App.tsx: updated all 3 searchAllUsers calls to use finalResults
- Removed setTimeout workarounds
Result: Extension and file upload flows now save immediately after search.
Changed from hasRecentUpload time check to getUpload ID check.
Extension flow:
1. extension-import creates upload with no matches
2. Frontend searches and finds matches
3. save-results now UPDATES upload with matches (was skipping)
File upload flow still works:
- Upload doesn't exist -> creates it
- Upload exists -> updates it with new matches
Removes race condition where save within 5 seconds would be skipped.
Was setting sourceUser to result.sourceUser.username (string)
Should be result.sourceUser (SourceUser object)
This caused:
- useSearch to call batch.map(r => r.sourceUser.username) on strings
- .username on string returns undefined
- batch-search-actors received null values
- ValidationError: expected string, received null
Also caused localeCompare error when sorting undefined values.
Backend (extension-import.ts):
- Now creates user_source_follows entries linking upload to source accounts
- Without these, get-upload-details returned empty (queries FROM user_source_follows)
- Uses bulkCreate return value (Map<username, id>) to create links
Frontend (App.tsx):
- handleLoadUpload now detects if upload has no matches yet
- Sets isSearching: true for new uploads
- Automatically triggers searchAllUsers for new uploads
- Saves results after search completes
- Changed platform from hardcoded "tiktok" to "twitter"
Frontend (HistoryTab.tsx):
- Fixed time display: removed "Uploaded" prefix
- Now shows "about 5 hours ago" instead of "Uploaded in about 5 hours"
- formatRelativeTime with addSuffix already provides complete sentence
Resolves:
- Empty results on page load
- No automatic searching
- History navigation not working (will work after search)
- Grammatically incorrect time display
Backend fixes:
- Use SourceAccountRepository.bulkCreate() instead of non-existent upsertSourceAccount()
- Change redirectUrl from /results?uploadId= to /?uploadId=
- More efficient bulk insert instead of loop
Frontend fixes:
- Add useEffect to load results when uploadId param present
- Calls loadUploadResults(uploadId) automatically on page load
- Cleans up URL param after loading
Resolves:
- "sourceAccountRepo.upsertSourceAccount is not a function" error
- "No routes matched location /results?uploadId=..." routing error
Backend endpoints use successResponse() which wraps data in:
{ success: true, data: {...} }
Extension was expecting flat response structure, causing:
- uploadToATlast to return undefined (missing importId, redirectUrl)
- checkSession to return wrapped object instead of user data
- Invalid URL error: "http://127.0.0.1:8888undefined"
Fixed both uploadToATlast and checkSession to access apiResponse.data
PLAN.md updates:
- Added current status section with recent fixes and active work
- Marked Phase 0 as complete
- Marked Phase 1 as in progress (debugging)
- Updated changelog with 2025-12-26 progress
- Updated decision graph count to 288 nodes
EXTENSION_STATUS.md updates:
- Changed state from READY FOR TESTING to DEBUGGING
- Added fixed issues section (NaN bug, database init)
- Added active debugging section
- Updated decision graph summary to 288 nodes
- Added node references for recent fixes (#287-288)
Decision graph:
- Synced with latest nodes (288 total, 276 edges)
- Tracked database initialization outcome
Removed temporary storage approach and implemented proper authentication flow:
Extension changes:
- Added session check to popup init flow (checkSession in api-client)
- Added "not logged in" state with login prompts
- Updated uploadToATlast to include credentials for cookie-based auth
- Extension now requires user to be logged in BEFORE scanning
Backend changes:
- Converted extension-import to AuthenticatedHandler (requires auth)
- Now creates upload records immediately (no temporary storage)
- Removed extension_imports table from database schema
- Deleted get-extension-import function (no longer needed)
- Deleted import-store utility (temporary approach removed)
Frontend changes:
- Removed ExtensionImport page and /import/:id route
- Extension uploads now use same flow as file uploads
This matches the correct user flow: user logs in to ATlast first, then
extension creates permanent upload records directly (same as file uploads).
Built extension successfully for dev environment.
Features:
- Check server health on popup init (dev mode only)
- Show 'server offline' state with setup instructions
- 'Check Again' button to retry connection
- Display target server URL for debugging
- 3-second timeout for health checks
Fixes port 8888 conflict workflow - extension now prompts user
to start dev server instead of hanging silently.
Critical bug fix: extension-import and get-extension-import were using
separate in-memory Maps, causing 404 errors when fetching import data.
- Create shared utils/import-store.ts module
- Both functions now use same Map instance
- Add logging for debugging
- Note: In-memory storage only works for dev (single process)
Production needs database/Redis/Netlify Blobs
The onMessage wrapper in messaging.ts was only sending {success: true}
instead of the actual handler return value. This caused the popup to
receive undefined state even though the background worker was correctly
storing it.
Changes:
- messaging.ts: Changed onMessage to forward handler return values
- background service-worker.ts: Added comprehensive logging
- popup.ts: Added state change listener and detailed logging
This fixes the issue where popup showed 'Go to...' even when on the
following page.
Changed followingPathPattern from strict /^\/[^/]+\/following$/
to flexible /^\/?([^/]+\/)?following\/?$/ to handle:
- /username/following
- /following (if Twitter uses this)
- Optional trailing slashes
This fixes detection issue where extension wouldn't recognize
the following page and show 'ready' state.
Changed color scheme from purple/indigo to purple/cyan/orange:
- Header: gradient from yellow-400 → orange-500 → pink-600 (firefly banner)
- Background: purple-50 → white → cyan-50 gradient
- Primary button: orange-600 with hover state
- Secondary button: purple-800 border
- Progress bar: orange → pink gradient
- Links and accents: orange-600
Matches web app's tailwind.config.js color system.
Built ATlast Importer browser extension with:
- Twitter Following page scraper using stable selectors
- Extension popup UI with scan progress and status
- Background service worker for state management
- API client for uploading to ATlast
- Netlify functions: extension-import, get-extension-import
- Web app integration via importId URL parameter
- Build system with esbuild and TypeScript
Extension flow:
1. User visits x.com/{username}/following
2. Click extension icon -> Start Scan
3. Extension scrolls and collects usernames
4. POST to /extension-import endpoint
5. Redirect to ATlast with importId parameter
6. Web app fetches import data and starts Bluesky search
Extensible architecture ready for Threads/Instagram/TikTok.
Fixed Netlify CLI monorepo detection issue by using --filter flag:
- Updated root package.json scripts to use 'npx netlify-cli dev --filter @atlast/web'
- Updated netlify.toml [dev] section to use npm with --prefix for framework command
- Added monorepo development instructions to CLAUDE.md
- Documented Windows Git Bash compatibility issue with netlify command
Solution: Use 'npx netlify-cli dev --filter @atlast/web' to bypass monorepo
project selection prompt and specify which workspace package to run.
Dev server now runs successfully at http://localhost:8888 with all backend
functions loaded.
Restructured codebase into pnpm workspace with three packages:
- packages/web: React frontend (from src/)
- packages/functions: Netlify serverless functions (from netlify/functions/)
- packages/shared: Shared TypeScript types for Platform and Import APIs
Changes:
- Created pnpm-workspace.yaml for workspace configuration
- Moved all web app files to packages/web/
- Moved all Netlify functions to packages/functions/src/
- Created packages/shared with Platform enum and ExtensionImportRequest/Response types
- Updated netlify.toml to point to new paths
- Updated root package.json scripts to use pnpm workspace commands
- All dependencies split appropriately between packages
Phase 0 (Monorepo Migration) from PLAN.md completed successfully.
Builds and dev server tested and working.